astro / buzzrelay

Source to relay.fedi.buzz: relay the streaming API of Mastodon instances
https://relay.fedi.buzz
GNU Affero General Public License v3.0
70 stars 9 forks source link

Feature request: use the signing key of each Actor to authenticate delivery of Activities originating from that Actor #15

Closed tsmethurst closed 4 months ago

tsmethurst commented 5 months ago

If I understand the code correctly, in the current state of things the relay uses a global private/public key pair to sign POST request deliveries of Activities to Inboxes. At least this seems to be the case for Accepting Follows. This is equivalent to using the "instance service actor" to deliver POSTs of Activities.

I know many fedi softwares use their instance actor to do GET requests to fetch remote resources, in cases where it's not clear who else should be doing the GET. When it comes to POST requests, though, which contain an Activity in their body, it would seem to make more sense to sign the POST using the key of the Actor of the Activity being POSTed (and indeed this is what the majority of other server softwares do, fedi buzz relay is the only software I'm aware of doing it differently).

The notion that you should authenticate using the Actor of the Activity is somewhat supported by the server-to-server delivery section of the ActivityPub spec, where it says:

An HTTP POST request (with authorization of the submitting user) is then made to the inbox, with the Activity as the body of the request.

Admittedly in this case, the phrase "submitting user" is rather ambiguous, but I believe you can interpret it as meaning "the user who submitted an Activity to their own Outbox, which triggered the server to send the Activity to derived Inboxes of addressees". In that case, it should be authorized by the Actor of the Activity.

Using the instance actor to sign POST requests whose Activity body belongs to a different Actor causes messages to be dropped/refused in the latest release of GoToSocial (https://github.com/superseriousbusiness/gotosocial/issues/2626), since we added much stricter origin checking to close some security gaps. It may cause issues in other fedi softwares as well, but I'm not sure. This issue seems like it could well be caused by this: https://github.com/astro/buzzrelay/issues/2

So as a feature request to ensure better compatibility with fedi servers, I think it would be a good idea if you could update your signing logic to pass in the private key + key ID of specific Actors when signing POST activities, instead of passing in the private key from the state object.

astro commented 5 months ago

Which actors do you mean?

tsmethurst commented 5 months ago

All buzzrelay actors share one keypair. Please tell me where some id looks wrong.

Oh. Hmm. That's quite unusual, so you don't store separate keypairs for each actor at all? Could you give an example of an actor on the relay so I can do some dereferencing and see what the actor model looks like?

I don't have the private key of the original action's author that I am just forwarding. Therefore, I cannot sign with it.

I'm aware of that yeah, no worries, I'm talking specifically about the actors on the relay here.

Edit: Hmm now that I look through the code again more carefully, it looks like the keyId and Accept are formatted OK , as far as I can tell (https://github.com/astro/buzzrelay/blob/186d9f008ead33f87ae2ebbfc884b1822227665d/src/main.rs#L200-L207). So it's kinda funky that all Actors use the same key, but on the other hand if you're using the keyId correctly then it should technically be no problem. There must be something else causing https://github.com/superseriousbusiness/gotosocial/issues/2626 then 🤔

I'll close this, apologies for the noise.

tsmethurst commented 5 months ago

Ah right now I think I found the problem, but perhaps you could confirm.

So, when a Follow is POSTed to an Inbox on fedi buzz, fedi buzz parses the URL of the inbox to determine which actor is being addressed, right?

Every fedi buzz actor seems to use a sharedInbox value of https://relay.fedi.buzz/instance/relay.fedi.buzz. So when something like GoToSocial makes a POST request to the sharedInbox URI provided to deliver a Follow to (for example) https://relay.fedi.buzz/instance/mastodon.social, fedi buzz will pull the instance actor https://relay.fedi.buzz/instance/relay.fedi.buzz out of storage, store it in the variable target, and use that target to sign + POST an Accept back, which uses that instance actor as the Actor property.

Am I right there? If so, then the fix for this specific case would be to check, when receiving a POST Follow to the sharedInbox URI, which actor is addressed in the Follow, and pull that one out of storage as the target, instead of the owner of the sharedInbox (ie., the instance actor).

Again, sorry for the noise :') I got nerd sniped by this real bad. I think I finally figured it out though.

astro commented 4 months ago

If I understand the issue correctly, buzzrelay needs to check the object field of a Follow request when it is posted to the sharedInbox, right?

tsmethurst commented 4 months ago

Yes, or target, I can't remember off the top of my head which one gets used. Whoever is the target of the Follow should be the one signing and POSTing back the Accept message, not the shared inbox owner.

astro commented 4 months ago

Could you please retry?

If it doesn't work, can you please post a request dump? Just for reference, that is how a real-world Follow looks from Mastodon:

{ "@context": "https://www.w3.org/ns/activitystreams"
, "id": "https://c3d2.social/c99c4436-8b78-439b-a4f4-c68028b5f04f"
, "type": "Follow"
, "actor": "https://c3d2.social/actor"
, "object": "https://www.w3.org/ns/activitystreams#Public"
}

It does not include a valid target in the object field so my change would not even make sense here, but I ignore it if hostnames don't match.

Alternatively, give me an account on your GoTo-Social to trigger the Follow myself.

Jasdemi commented 4 months ago

Not OP, but confirming that it now works. I am now able to follow and posts arrive as expected. Thank you very much!

Jasdemi commented 4 months ago

One thing is not working. I'm not able to unfollow accounts. I followed @instance-mastodon@relay.fedi.buzz for testing reasons and after unfollowing I still get all posts in my federated timeline. 12k posts and counting after 8 hours.

I tried blocking and suspending the account but as a side effect it also stops all activity from other followed fedi.buzz accounts.

I sent you login credentials to an test account on my instance.

Jasdemi commented 4 months ago

Here is the only log I see when blocking @instance-mastodon.social@relay.fedi.buzz with any account which previously followed it:

timestamp="22/02/2024 18:35:20.469" func=server.glob..func1.Logger.func13.1 level=INFO latency="3.766125ms" userAgent="buzzrelay/0.1.0 (+https://relay.fedi.buzz)" method=POST statusCode=403 path=/users/follower/inbox clientIP=2a00:8180:2c00:282:50c8:56ff:fe25:a04d pubKeyID=https://relay.fedi.buzz/instance/mastodon.social#key errors="Error #01: blocked\n" requestID=j7ay5mcd040009ghn6fg msg="Forbidden: wrote 54B"

All other activity from accounts such as @tag-news@relay.fedi.buzz stop working. Upon unblocking mastodon.social, everything starts working again. It's either all or nothing.

astro commented 4 months ago

I fixed the unfollow issue.

Thank you for following through with this.