Metadata-Version: 2.4
Name: zammad-pgp-import
Version: 0.4.0
Summary: 
License-File: LICENSE
Author: kmille
Author-email: github@androidloves.me
Requires-Python: >=3.10
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Dist: flask (>=3.1.1,<4.0.0)
Requires-Dist: flask-basicauth (>=0.2.0,<0.3.0)
Requires-Dist: mypy (>=1.16.1,<2.0.0)
Requires-Dist: requests (>=2.32.4,<3.0.0)
Requires-Dist: waitress (>=3.0.2,<4.0.0)
Description-Content-Type: text/markdown

# Zammad PGP import - Webhook & cli tools

### The what and why
Zammad helpdesk supports PGP encryption and it works quite nice. But the current workflow of importing PGP keys is a bit cumbersome. Also, agents need special admin privileges to import PGP keys. This project provides a webhook that automatically imports PGP keys when some checks are completed. You need to configure a Zammad webhook that sends a POST request to this webhook for each new incoming ticket. The webhook automatically imports PGP keys attached to the ticket or keys found on a keyserver.

There are also some cli tools to import PGP keys manually or import PGP keys from a Thunderbird profile.

### How does it work?
1) Zammad receives a new ticket
2) Zammad sends a webhook (must be configured manually in Zammad)
3) This projects contains the backend of the webhook. There are two supported scenarios where PGP keys are imported:
    - The email has a PGP key attached. If sender's email address matches with the one of the attached PGP key, the Zammad API is used to import the key
    - If the email is PGP-encrypted: Use a PGP keyserver to find a valid PGP key and import it.

#### Quickstart

```bash
pip install zammad-pgp-import

cat secrets.source
export DEBUG="1"
export ZAMMAD_BASE_URL="https://tickets.example.org"
export ZAMMAD_TOKEN="auth token"
export BASIC_AUTH_USER="test"
export BASIC_AUTH_PASSWORD="test"
export LISTEN_HOST="0.0.0.0"
export LISTEN_PORT="22000"

kmille@spring:~/projects/zammad-pgp-import# uv run zammad-pgp-import -h
usage: zammad-pgp-import [-h] [--backend] [--import-key IMPORT_KEY] [--remove-expired-keys] [--run-webhook-for-ticket RUN_WEBHOOK_FOR_TICKET]
                         [--check-all-tickets] [--import-thunderbird IMPORT_THUNDERBIRD] [--version]

Zammad webhook that automatically imports PGP keys. There is also a cli to import PGP keys manually. Configuration is done via environment variables. Docs     
can be found here: https://github.com/kmille/zammad-pgp-auto-import

options:
  -h, --help            show this help message and exit
  --backend, -b         run webhook backend
  --import-key, -i IMPORT_KEY
                        use key server to import pgp key by supplied email/key id
  --remove-expired-keys
                        Iterate over all imported PGP keys in Zammad and remove the expired ones
  --run-webhook-for-ticket RUN_WEBHOOK_FOR_TICKET
                        Run webhook for a specific ticket. Needs the ticket id
  --check-all-tickets   Iterate over all tickets and run webhook for each ticket
  --import-thunderbird, -t IMPORT_THUNDERBIRD
                        Needs a global-messages-db.sqlite file. Get all email addresses from global-messages-db.sqlite (part of a Thunderbird profile). Try    
                        to find a PGP key and import it to Zammad. As there is rate limiting, we sleep for a long time after each attempt. So you may want to  
                        run this on a server
  --version             show version

```

### Configuration

Configuration of this tool is done via environment variables.

| name of environment variable | meaning                                                     | required |
| ---------------------------- | ----------------------------------------------------------- | -------- |
| DEBUG                        | set 1 to enable debug log                                   | no       |
| ZAMMAD_BASE_URL              | url of zammad instance, like https://tickets.example.org    | yes      |
| ZAMMAD_TOKEN                 | auth token/api key with enough permissions (see below)      | yes      |
| BASIC_AUTH_USER              | username for webhook and monitoring endpoint authentication | yes      |
| BASIC_AUTH_PASSWORD          | password for webhook and monitoring endpoint authentication | yes      |
| LISTEN_HOST                  | defaults to "127.0.0.1"                                     | no       |
| LISTEN_PORT                  | defaults to 22000                                           | no       |
| KEY_SERVER                   | default PGP key server, default is https://keys.openpgp.org | no       |

### Zammad Webhook configuration

1) Define webhook

![](/docs/screenshot_webhook.png)

2) Create trigger (triggers webhook for every new ticket)

![](/docs/screenshot_trigger.png)

To get an API key for a production environment, I recommend:

1) Create a new Zammad user, only used for the API
2) Create a new role for the webhook user
3) The role needs permissions to read tickets for all groups (to download ticket attachments) and the integration permission (to import PGP keys)

### How to use it as a dev?

It's written in python and uses [uv](https://docs.astral.sh/uv/getting-started/installation/) to manage dependencies.

```bash
source secrets.txt (see above)
uv run zammad_pgp_import/__init__.py --help
```

### How to use it with Docker

You can use the [Github Docker image](https://github.com/kmille/zammad-pgp-auto-import/pkgs/container/zammad-pgp-import): 

> docker pull ghcr.io/kmille/zammad-pgp-import

Or the [Dockerhub image](https://hub.docker.com/r/kmille2/zammad-pgp-import):

> docker pull kmille2/zammad-pgp-import

You can use this docker-compose.yml:

```yaml
services:
   zammad-pgp-import:
    image: ghcr.io/kmille/zammad-pgp-import
    environment:
      DEBUG: "1"
      BASIC_AUTH_USER: "test"
      BASIC_AUTH_PASSWORD: "test"
      ZAMMAD_BASE_URL: "https://tickets.example.org"
      ZAMMAD_TOKEN: "token"
    security_opt:
      - no-new-privileges
    cap_drop:
      - ALL
```

### Example output
1) Run the webhook backend

```bash
kmille@spring:~/projects/zammad-pgp-import# uv run zammad-pgp-import --backend
[2025-08-14 17:56:01,279  INFO] Starting webhook backend on 0.0.0.0:22000 (version 0.1.1a5, debug=True)
[2025-08-14 17:56:01,281  INFO] Serving on http://0.0.0.0:22000

...

zammad-pgp-import-1  | [2025-08-04 12:08:46,377  INFO] Received a new Ticket: https://tickets.example.org/#ticket/zoom/133 (from=<redacted>, is_encrypted=False)
zammad-pgp-import-1  | [2025-08-04 12:08:46,377 DEBUG] This attachment is not a PGP key (Content-Type='')                                                                                                                                      
zammad-pgp-import-1  | [2025-08-04 12:08:46,377 DEBUG] No PGP key was attached to this email                                                                                                                                                   
zammad-pgp-import-1  | [2025-08-04 12:08:46,377  INFO] Ticket does not have a PGP key attached. It is also not encrypted and/or PGP key was not found on a keysever                  

zammad-pgp-import-1  | [2025-08-05 13:24:06,815  INFO] Received a new Ticket: https://tickets.example.org/#ticket/zoom/139 (from=<redacted>, is_encrypted=True)
zammad-pgp-import-1  | [2025-08-05 13:24:06,815  INFO] Seems like a PGP key is attached to this email
zammad-pgp-import-1  | [2025-08-05 13:24:06,816 DEBUG] Downloading ticket attachment using Zammad API
zammad-pgp-import-1  | [2025-08-05 13:24:06,972 DEBUG] Successfully downloaded email attachment
zammad-pgp-import-1  | [2025-08-05 13:24:06,987 DEBUG] Importing PGP key using Zammad API
zammad-pgp-import-1  | [2025-08-05 13:24:07,126  INFO] Successfully imported pgp key <redacted> for email <redacted>

```

2. Import PGP key via cli

```bash
[2025-08-14 17:58:26,621  INFO] Found key: PGPKey (emails=jelle@vdwaa.nl,jelle@archlinux.org,jvanderwaa@redhat.com fingerprint=E499C79F53C96A54E572FEE1C06086337C50773E
[2025-08-14 17:58:26,621  INFO] Found key: PGPKey (emails=jelle@vdwaa.nl,jelle@archlinux.org,jvanderwaa@redhat.com fingerprint=E499C79F53C96A54E572FEE1C06086337C50773E
[2025-08-14 17:58:26,626 DEBUG] Importing PGP key using Zammad API
[2025-08-14 17:58:26,789  INFO] Successfully imported PGP key
kmille@spring:~/projects/zammad-pgp-import# uv run zammad-pgp-import -i jelle@archlinux.org
[2025-08-14 17:58:35,483  INFO] Found key: PGPKey (emails=jelle@vdwaa.nl,jelle@archlinux.org,jvanderwaa@redhat.com fingerprint=E499C79F53C96A54E572FEE1C06086337C50773E
[2025-08-14 17:58:35,488 DEBUG] Importing PGP key using Zammad API
[2025-08-14 17:58:35,598 ERROR] Could not import PGP key: Key was already imported. API response: Fingerprint There is already a PGP key with the same fingerprint.
```

3. Import PGP keys from Thunderbird profile

```bash
kmille@spring:~/projects/zammad-pgp-import# uv run zammad_pgp_import/__init__.py -t ~/.config/Thunderbird-LG/.thunderbird/....default-release/global-messages-db.sqlite                                                        
[2025-08-14 17:59:34,866 DEBUG] Read email: <redacted@bla.com>
...
[2025-08-14 18:00:45,224  INFO] Checking mail 1/3460: <redacted @ ....>
[2025-08-14 18:00:45,272 DEBUG] API error message: No key found for email address < redacted>
[2025-08-14 18:00:45,273  INFO] Could not find a PGP key for < redacted > using keyserver https://keys.openpgp.org
...
[2025-08-04 10:27:53,933  INFO] Doing 6/3448: <redacted>
[2025-08-04 10:27:53,969  INFO] Found key: PGPKey <redacted>
[2025-08-04 10:27:53,970 DEBUG] Importing PGP key using Zammad API
[2025-08-04 10:27:54,144  INFO] Successfully imported PGP key
```

### Monitoring

You can monitor the webhook:

```
curl https://webhook.example.org/status -u webhook
Enter host password for user 'webhook':
{"status":"ok"}
```

In case of at least one error, `{"status":"failed"}` is returned. Both responses use status code 200. ~~The authentication credentials are the same like the ones for the webhook endpoint~~ (no credentials are required any more). In case of an error, check the logs and restart the webhook.

### KNOWN ISSUES/TODOs

- TODO: handle expired PGP keys (do not import them)
- TODO: improve tests
- Please check this Zammad PGP issue (was fixed, but it is not released yet I think): https://github.com/zammad/zammad/issues/5170
- Right now, agents don't get PGP encrypted notifications, even if they have a PGP key imported for that email address

