aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLoek Le Blansch <loek@pipeframe.xyz>2025-10-14 18:55:31 +0200
committerLoek Le Blansch <loek@pipeframe.xyz>2025-10-14 18:55:31 +0200
commite8f200337975e9f8b8d57a5a0a3a2b25395cee88 (patch)
tree07cfdfa9983c11f8454e0428475b95b3a5b1b02b
initial commit (wip)
-rw-r--r--.gitignore5
-rw-r--r--license21
-rw-r--r--nmpass/__init__.py0
-rw-r--r--nmpass/main.py61
-rw-r--r--nmpass/store.py38
-rw-r--r--pyproject.toml23
-rw-r--r--readme.md0
-rw-r--r--setup.py2
8 files changed, 150 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9c93d10
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+.venv
+venv
+*.egg-info
+build
+__pycache__
diff --git a/license b/license
new file mode 100644
index 0000000..c3c9d5e
--- /dev/null
+++ b/license
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2025 Loek Le Blansch
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/nmpass/__init__.py b/nmpass/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/nmpass/__init__.py
diff --git a/nmpass/main.py b/nmpass/main.py
new file mode 100644
index 0000000..e969e13
--- /dev/null
+++ b/nmpass/main.py
@@ -0,0 +1,61 @@
+from __future__ import annotations
+
+from asyncio import new_event_loop
+from sdbus_async.networkmanager import NetworkManagerSecretAgentInterfaceAsync, NetworkManagerAgentManager, NetworkManagerConnectionProperties
+from sdbus_async.networkmanager.settings import ConnectionProfile
+from typing import Any
+import sdbus
+
+from .store import PasswordStore
+
+class NetworkManagerPasswordStoreAgent(NetworkManagerSecretAgentInterfaceAsync):
+ store = PasswordStore()
+
+ @sdbus.dbus_method_async_override()
+ async def get_secrets(
+ self,
+ connection: NetworkManagerConnectionProperties,
+ _connection_path: str,
+ setting_name: str,
+ _hints: list[str],
+ _flags: int,
+ ) -> dict[str, dict[str, tuple[str, Any]]]:
+ profile = ConnectionProfile.from_dbus(connection)
+
+ printf(setting_name)
+ if setting_name == '802-11-wireless-security':
+ ssid = profile.wireless.ssid.decode()
+ pass_name = f"net/{ssid}/passwd"
+ password = self.store.retrieve(pass_name)
+ printf(f"SSID: {ssid} PASSWORD: {password}")
+ if password != None:
+ profile.wireless_security.psk = password
+ return profile.wireless_security.psk.to_dbus()
+
+ return {}
+
+ @sdbus.dbus_method_async_override()
+ async def save_secrets(
+ self,
+ connection: NetworkManagerConnectionProperties,
+ _connection_path: str,
+ ) -> None:
+ profile = ConnectionProfile.from_dbus(connection)
+ print(profile)
+ raise NotImplementedError
+
+def main():
+ sdbus.set_default_bus(sdbus.sd_bus_open_system())
+ agent = NetworkManagerPasswordStoreAgent()
+ loop = new_event_loop()
+ agent.export_to_dbus('/org/freedesktop/NetworkManager/SecretAgent')
+ agent_manager = NetworkManagerAgentManager()
+ try:
+ loop.run_until_complete(agent_manager.register('org.nmpassd'))
+ loop.run_forever()
+ except KeyboardInterrupt:
+ loop.stop()
+
+if __name__ == "__main__":
+ main()
+
diff --git a/nmpass/store.py b/nmpass/store.py
new file mode 100644
index 0000000..08080ba
--- /dev/null
+++ b/nmpass/store.py
@@ -0,0 +1,38 @@
+from subprocess import run, PIPE, DEVNULL
+from os import environ, path
+
+NEWLINE = "\n"
+
+class PasswordStore():
+ PASSWORD_STORE = "pass"
+ PASSWORD_STORE_DIR = path.join(environ["HOME"], ".password-store")
+
+ def __init__(self):
+ if "PASSWORD_STORE_DIR" in environ:
+ self.PASSWORD_STORE_DIR = environ["PASSWORD_STORE_DIR"]
+
+ # There's no way to check using the pass CLI whether a password name passed
+ # to `pass show` is a password or a directory, so this checks manually
+ def exists(self, name: str) -> bool:
+ return path.isfile(path.join(self.PASSWORD_STORE_DIR, f"{name}.gpg"))
+
+ def retrieve(self, name: str, truncate: bool = True) -> str | None:
+ if not self.exists(name):
+ return None
+ cmd = (self.PASSWORD_STORE, 'show', name,)
+ proc = run(cmd, stdout=PIPE)
+ output = proc.stdout.decode()
+ if not truncate:
+ return output
+ return output.split(NEWLINE)[0]
+
+ def store(self, name: str, password: str) -> None:
+ current = self.retrieve(name, False)
+ if current == None:
+ current = NEWLINE
+ to = current.find(NEWLINE)
+ current = f"{password}{current[to:]}"
+
+ cmd = (self.PASSWORD_STORE, 'insert', '--multiline', '--force', name,)
+ run(cmd, input=current.encode(), stdout=DEVNULL, stderr=DEVNULL)
+
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..dc3486a
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,23 @@
+[build-system]
+requires = ["setuptools", "setuptools-scm"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "nm-pass"
+description = "NetworkManager secret agent that reads/writes secrets to password-store"
+version = "0.0.1"
+authors = [
+ { name="Loek Le Blansch", email="loek@pipeframe.xyz" },
+]
+readme = "readme.md"
+requires-python = ">=3.9"
+license = "MIT"
+license-files = ["license"]
+dependencies = [
+ "sdbus",
+ "sdbus-networkmanager",
+]
+
+[project.scripts]
+nmpassd = "nmpass.main:main"
+
diff --git a/readme.md b/readme.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/readme.md
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..8bf1ba9
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,2 @@
+from setuptools import setup
+setup()