|
| 1 | +# ActivityPub implementation |
| 2 | + |
| 3 | +Pod implements a minimal set of ActivityPub that allows video sharing between Pod instances. |
| 4 | +The ActivityPub implementation is also compatible with Peertube. |
| 5 | + |
| 6 | +## Federation |
| 7 | + |
| 8 | +Here is what happens when two instances, say *Node A* and *Node B* (being Pod or Peertube) federate with each other, in a one way federation. |
| 9 | + |
| 10 | +### Federation |
| 11 | + |
| 12 | +- An administrator asks for Node A to federate with Node B |
| 13 | +- Node A reaches the [NodeInfo](https://github.com/jhass/nodeinfo/blob/main/PROTOCOL.md) endpoint (`/.well-known/nodeinfo`) on Node B and discover the root application endpoint URL. |
| 14 | +- Node A reaches the root application endpoint (for Pod this is `/ap/`) and get the `inbox` URL. |
| 15 | +- Node A sends a `Create` activity for a `Follow` object on the Node B root application `inbox`. |
| 16 | +- Node B reads the Node A root application endpoint URL in the `Follow` objects, reaches this endpoint and get the Node A root application `inbox` URL. |
| 17 | +- Node B creates a `Follower` objects and stores it locally |
| 18 | +- Node B sends a `Accept` activity for the `Follower` object on Node A root application enpdoint. |
| 19 | +- Later, Node A can send to Node B a `Undo` activity for the `Follow` object to de-federate. |
| 20 | + |
| 21 | +### Video discovery |
| 22 | + |
| 23 | +- Node A reaches the Node B root application `outbox`. |
| 24 | +- Node A browse the pages of the `outbox` and look for announces about `Videos` |
| 25 | +- Node A reaches the `Video` endpoints and store locally the information about the videos. |
| 26 | + |
| 27 | +### Video creation and update sharing |
| 28 | + |
| 29 | +#### Creation |
| 30 | + |
| 31 | +- A user of Node B publishes a `Video` |
| 32 | +- Node B sends a `Announce` activity on the `inbox` of all its `Followers`, including Node A with the ID of the new video. |
| 33 | +- Node A reads the information about the new `Video` on Node B video endpoint. |
| 34 | + |
| 35 | +#### Edition |
| 36 | + |
| 37 | +- A user of Node B edits a `Video` |
| 38 | +- Node B sends a `Update` activity on the `inbox` of all its `Followers`, including Node A with the ID of the new video, containing the details of the `Video`. |
| 39 | + |
| 40 | +#### Deletion |
| 41 | + |
| 42 | +- A user of Node B deletes a `Video` |
| 43 | +- Node B sends a `Delete` activity on the `inbox` of all its `Followers`, including Node A with the ID of the new video. |
| 44 | + |
| 45 | +## Implementation |
| 46 | + |
| 47 | +The ActivityPub implementation tries to replicate the network messages of Peertube. |
| 48 | +There may be things that could have been done differently while still following the ActivityPub specs, but changing the network exchanges would require checking if the Peertube compatibility is not broken. |
| 49 | +This is due to Peertube having a few undocumented behaviors that are not exactly part of the AP specs. |
| 50 | + |
| 51 | +To achieve compatibility with Peertube, Pod implements two specifications to sign ActivityPub exchanges. |
| 52 | + |
| 53 | +- [Signing HTTP Messages, draft 12](https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12). |
| 54 | + This specification is replaced by [RFC9421](https://www.rfc-editor.org/rfc/rfc9421.html) but Peertube does not implement the finale spec, |
| 55 | + and instead lurks on the writing of the [ActivityPub and HTTP Signatures](https://swicg.github.io/activitypub-http-signature/) spec, that is also still a draft. |
| 56 | + See the [related discussion](https://framacolibri.org/t/rfc9421-replaces-the-signing-http-messages-draft/20911/2). |
| 57 | + This spec describe how to sign ActivityPub payload with HTTP headers. |
| 58 | +- [Linked Data Signatures 1.0](https://web.archive.org/web/20170717200644/https://w3c-dvcg.github.io/ld-signatures/) draft. |
| 59 | + This specification is replaced by [Verifiable Credential Data Integrity](https://w3c.github.io/vc-data-integrity/) but Peertube does not implement the finale spec. |
| 60 | + This spec describe how to sign ActivityPub payload by adding fields in the payload. |
| 61 | + |
| 62 | +The state of the specification support in Peertube is similar to [Mastodon](https://docs.joinmastodon.org/spec/security/), and is probably a mean to keep the two software compatible with each other. |
| 63 | + |
| 64 | +## Limitations |
| 65 | + |
| 66 | +- Peertube instance will only be able to index Pod videos if the video thumbnails are absent. |
| 67 | +- Peertube instance will only be able to index Pod videos if the thumbnails are in JPEG format. |
| 68 | + png thumbnails are not supported at the moment (but that may come in the future |
| 69 | + [more details here](https://framacolibri.org/t/comments-and-suggestions-on-the-peertube-activitypub-implementation/21215)). |
| 70 | + In the meantime, pod fakes the mime-type of all thumbnails to be JPEG, even when they actually are PNGs. |
| 71 | + |
| 72 | +## Configuration |
| 73 | + |
| 74 | +A RSA keypair is needed for ActivityPub to work, and passed as |
| 75 | +`ACTIVITYPUB_PUBLIC_KEY` and `ACTIVITYPUB_PRIVATE_KEY` configuration settings. |
| 76 | +They can be generated from a python console: |
| 77 | + |
| 78 | +```python |
| 79 | +from Crypto.PublicKey import RSA |
| 80 | + |
| 81 | +activitypub_key = RSA.generate(2048) |
| 82 | + |
| 83 | +# Generate the private key |
| 84 | +# Add the content of this command in 'pod/custom/settings_local.py' |
| 85 | +# in a variable named ACTIVITYPUB_PRIVATE_KEY |
| 86 | +with open("pod/activitypub/ap.key", "w") as fd: |
| 87 | + fd.write(activitypub_key.export_key().decode()) |
| 88 | + |
| 89 | +# Generate the public key |
| 90 | +# Add the content of this command in 'pod/custom/settings_local.py' |
| 91 | +# in a variable named ACTIVITYPUB_PUBLIC_KEY |
| 92 | +with open("pod/activitypub/ap.pub", "w") as fd: |
| 93 | + fd.write(activitypub_key.publickey().export_key().decode()) |
| 94 | +``` |
| 95 | + |
| 96 | +The federation also needs celery to be configured with `ACTIVITYPUB_CELERY_BROKER_URL`. |
| 97 | + |
| 98 | +Here is a sample working activitypub `pod/custom/settings_local.py`: |
| 99 | + |
| 100 | +```python |
| 101 | +ACTIVITYPUB_CELERY_BROKER_URL = "redis://redis:6379/5" |
| 102 | + |
| 103 | +with open("pod/activitypub/ap.key") as fd: |
| 104 | + ACTIVITYPUB_PRIVATE_KEY = fd.read() |
| 105 | + |
| 106 | +with open("pod/activitypub/ap.pub") as fd: |
| 107 | + ACTIVITYPUB_PUBLIC_KEY = fd.read() |
| 108 | +``` |
| 109 | + |
| 110 | +## Development |
| 111 | + |
| 112 | +The `DOCKER_ENV` environment var should be set to `full` so a peertube instance and a ActivityPub celery worker are launched. |
| 113 | +Then peertube is available at http://peertube.localhost:9000. |
| 114 | + |
| 115 | +### Federate Peertube with Pod |
| 116 | + |
| 117 | +- Sign in with the `root` account |
| 118 | +- Go to [Main menu > Administration > Federation](http://peertube.localhost:9000/admin/follows/following-list) > Follow |
| 119 | +- Open the *Follow* modal and type `pod.localhost:8000` |
| 120 | + |
| 121 | +### Federate Pod with Peertube |
| 122 | + |
| 123 | +- Sign in with `admin` |
| 124 | +- Go to the [Administration pannel > Followings](http://pod.localhost:8000/admin/activitypub/following/) > Add following |
| 125 | +- Type `http://peertube.localhost:9000` in *Object* and save |
| 126 | +- On the [Followings list](http://pod.localhost:8000/admin/activitypub/following/) select the new object, and select `Send the federation request` in the action list, refresh. |
| 127 | +- If the status is *Following request accepted* then select the object again, and choose `Reindex instance videos` in the action list. |
| 128 | + |
| 129 | +## Shortcuts |
| 130 | + |
| 131 | +### Manual AP request |
| 132 | + |
| 133 | +```shell |
| 134 | +curl -H "Accept: application/activity+json, application/ld+json" -s "http://pod.localhost:9000/accounts/peertube" | jq |
| 135 | +``` |
| 136 | + |
| 137 | +### Unit tests |
| 138 | + |
| 139 | +```shell |
| 140 | +python manage.py test --settings=pod.main.test_settings pod.activitypub.tests |
| 141 | +``` |
0 commit comments