centrifugal / centrifugo

Scalable real-time messaging server in a language-agnostic way. Self-hosted alternative to Pubnub, Pusher, Ably. Set up once and forever.
https://centrifugal.dev
Apache License 2.0
8.45k stars 598 forks source link

[bug] Can't pull history with channel JWT #809

Closed ghstahl closed 6 months ago

ghstahl commented 6 months ago

works with the caps token, which means I am making a history call on the subscription vs the client object.

historyResult, err := s.sub.History(context.Background(),
    centrifuge.WithHistorySince(currentHistoricalStreamPosition),
    centrifuge.WithHistoryLimit(int32(s.config.BatchSize)),
)
if err != nil {
    s.errHistoricalCatchUp = err
    log.Error().Err(err).Msg("failed to History")
    break
}

I get a permission denied error when calling the following API using a channels token.

historyResult, err := client.History(s.ctxCatchup, s.config.Channel,
  centrifuge.WithHistorySince(currentHistoricalStreamPosition),
  centrifuge.WithHistoryLimit(int32(s.config.BatchSize)),
)
if err != nil {
  s.errHistoricalCatchUp = err
 log.Error().Err(err).Msg("failed to History")
 break
}

...

Versions

Centrifugo version

{"level":"info","version":"5.3.2","release_time":"1714371546","edition":"pro","runtime":"go1.22.2","pid":1,"engine":"memory","gomaxprocs":12,"time":"2024-05-07T16:15:50Z","message":"starting Centrifugo"}

Client library used is centrifuge-go of version v0.10.2

github.com/centrifugal/centrifuge-go v0.10.2

Config

{
  "token_jwks_public_endpoint": "http://mock-oauth2-pro:50053/.well-known/jwks",
  "admin_password": "password",
  "admin_secret": "secret",
  "admin": true,
  "allowed_origins": ["http://localhost:3000"],
  "allow_subscribe_for_client": true,
  "allow_subscribe_for_anonymous": true,
  "allow_publish_for_subscriber": true,
  "allow_publish_for_anonymous": true,
  "allow_history_for_subscriber": true,
  "allow_history_for_anonymous": true,
  "allow_presence_for_subscriber": true,
  "allow_presence_for_anonymous": true,
  "namespaces": [
    {
      "name": "connector",
      "presence": true,
      "join_leave": true,
      "history_size": 200,
      "history_ttl": "300h",
      "force_positioning": true,
      "force_recovery": true
    },
    {
      "name": "connector_private",
      "presence": true,
      "join_leave": true,
      "history_size": 200,
      "history_ttl": "300h",
      "force_positioning": true,
      "force_recovery": true
    }
  ]
}

Operating system is Docker windows 11

logs.

2024-05-07 09:15:50 {"level":"warn","time":"2024-05-07T16:15:50Z","message":"Centrifugo PRO license not found, run in sandbox mode: max connections 20, max nodes 2, max api rps 5"}
2024-05-07 09:15:50 {"level":"info","version":"5.3.2","release_time":"1714371546","edition":"pro","runtime":"go1.22.2","pid":1,"engine":"memory","gomaxprocs":12,"time":"2024-05-07T16:15:50Z","message":"starting Centrifugo"}
2024-05-07 09:15:50 {"level":"info","owner":"unlicensed","max_connections":20,"max_nodes":2,"max_api_rps":5,"time":"2024-05-07T16:15:50Z","message":"license information"}
2024-05-07 09:15:50 {"level":"info","path":"/centrifugo/config.json","time":"2024-05-07T16:15:50Z","message":"using config file"}
2024-05-07 09:15:50 {"level":"info","time":"2024-05-07T16:15:50Z","message":"serving websocket, api, admin endpoints on :8000"}
2024-05-07 09:20:24 {"level":"info","channel":"connector:foobar","client":"1041feb1-b91e-4caf-9ceb-cf2b123eed02","user":"channel-connector-foobar","time":"2024-05-07T16:20:24Z","message":"attempt to call history without sufficient permission"}
2024-05-07 09:20:24 {"level":"info","client":"1041feb1-b91e-4caf-9ceb-cf2b123eed02","code":103,"command":"id:2  history:{channel:\"connector:foobar\"  limit:3  since:{epoch:\"sCKQ\"}}","error":"permission denied","reply":"id:2  error:{code:103  message:\"permission denied\"}","user":"channel-connector-foobar","time":"2024-05-07T16:20:24Z","message":"client command error"}
FZambia commented 6 months ago

Hey @ghstahl ,

I do not see why token with channels should allow getting history. It subscribes connection to channel using server-side subscription, but there is nothing about history permission. I guess if you enable allow_history_for_subscriber in connector namespace - then you would be able to call history. But I am not sure whether adding such option on namespace level is acceptable in your case.

ghstahl commented 6 months ago

I was viewing this caps jwt the same as this channels jwt.

They indicate the same to me, subscribe to the connector:foobar channel and pull history.

I was going down the channels jwt path as we have a use case that needed the jwt hardened to an explicit channel. Limiting the caps jwt to a single channel does the same thing.

I can stick with the caps jwt for everything.

FZambia commented 6 months ago

They are not the same, as I said previously caps claim is about permissions, channels claim is about telling Centrifugo to subscribe connection to specified channels using server side subscriptions. You need to combine channels claim with caps claim to achieve the goal to call history on per-channel level. Or maybe use allow_history_for_subscriber in the namespace if it makes sense from permission side of view – in case if every subscriber can call history.

ghstahl commented 6 months ago

Ok, my mistake. The channels token works with this config

{
  "token_jwks_public_endpoint": "http://mock-oauth2-pro:50053/.well-known/jwks",
  "admin_password": "password",
  "admin_secret": "secret",
  "admin": true,
  "allowed_origins": [
    "http://localhost:3000"
  ],
  "allow_subscribe_for_client": true,
  "namespaces": [
    {
      "name": "connector",
      "presence": true,
      "join_leave": true,
      "history_size": 200,
      "history_ttl": "300h",
      "force_positioning": true,
      "force_recovery": true,
      "allow_subscribe_for_client": true,
      "allow_subscribe_for_anonymous": true,
      "allow_publish_for_subscriber": true,
      "allow_publish_for_anonymous": true,
      "allow_history_for_subscriber": true,
      "allow_history_for_anonymous": true,
      "allow_presence_for_subscriber": true,
      "allow_presence_for_anonymous": true
    },
    {
      "name": "connector_private",
      "presence": true,
      "join_leave": true,
      "history_size": 200,
      "history_ttl": "300h",
      "force_positioning": true,
      "force_recovery": true
    }
  ]
}

I had;

 "allow_history_for_subscriber": true,

outside the namespace array. Centrifigo didn't complain about it and I have seen logs that state seeing unknown configs.

is this supposed to work?

{
  "token_jwks_public_endpoint": "http://mock-oauth2-pro:50053/.well-known/jwks",
  "admin_password": "password",
  "admin_secret": "secret",
  "admin": true,
  "allowed_origins": ["http://localhost:3000"],
  "allow_subscribe_for_client": true,
  "allow_subscribe_for_anonymous": true,
  "allow_publish_for_subscriber": true,
  "allow_publish_for_anonymous": true,
  "allow_history_for_subscriber": true,
  "allow_history_for_anonymous": true,
  "allow_presence_for_subscriber": true,
  "allow_presence_for_anonymous": true,
  "namespaces": [
    {
      "name": "connector",
      "presence": true,
      "join_leave": true,
      "history_size": 200,
      "history_ttl": "300h",
      "force_positioning": true,
      "force_recovery": true
    },
    {
      "name": "connector_private",
      "presence": true,
      "join_leave": true,
      "history_size": 200,
      "history_ttl": "300h",
      "force_positioning": true,
      "force_recovery": true
    }
  ]
}
FZambia commented 6 months ago

allow_history_for_subscriber on top level corresponds to top-level namespace - for all channels without : separator. Channel namespaces and Channel config examples show this.

ghstahl commented 6 months ago

Got it, I was assuming that putting it into the top level would cover the namespaced ones with the : separator.

Test cases the JWT is used.

Fails due to permission denied - As Expected

{
  "token_jwks_public_endpoint": "http://mock-oauth2-pro:50053/.well-known/jwks",
  "admin_password": "password",
  "admin_secret": "secret",
  "admin": true,
  "allowed_origins": ["http://localhost:3000"],
  "allow_subscribe_for_client": true,
  "allow_subscribe_for_anonymous": true,
  "allow_publish_for_subscriber": true,
  "allow_publish_for_anonymous": true,
  "allow_history_for_subscriber": true,
  "allow_history_for_anonymous": true,
  "allow_presence_for_subscriber": true,
  "allow_presence_for_anonymous": true,
  "namespaces": [
    {
      "name": "connector",
      "presence": true,
      "join_leave": true,
      "history_size": 200,
      "history_ttl": "300h",
      "force_positioning": true,
      "force_recovery": true
    },
    {
      "name": "connector_private",
      "presence": true,
      "join_leave": true,
      "history_size": 200,
      "history_ttl": "300h",
      "force_positioning": true,
      "force_recovery": true
    }
  ]
}
2024-05-09 10:59:11 {"level":"warn","time":"2024-05-09T17:59:11Z","message":"Centrifugo PRO license not found, run in sandbox mode: max connections 20, max nodes 2, max api rps 5"}
2024-05-09 10:59:11 {"level":"info","version":"5.3.3","release_time":"1714977611","edition":"pro","runtime":"go1.22.2","pid":1,"engine":"memory","gomaxprocs":12,"time":"2024-05-09T17:59:11Z","message":"starting Centrifugo"}
2024-05-09 10:59:11 {"level":"info","owner":"unlicensed","max_connections":20,"max_nodes":2,"max_api_rps":5,"time":"2024-05-09T17:59:11Z","message":"license information"}
2024-05-09 10:59:11 {"level":"info","path":"/centrifugo/config.json","time":"2024-05-09T17:59:11Z","message":"using config file"}
2024-05-09 10:59:11 {"level":"info","time":"2024-05-09T17:59:11Z","message":"serving websocket, api, admin endpoints on :8000"}
2024-05-09 11:00:49 {"level":"info","channel":"connector:foobar","client":"e1d2e935-5c27-4b98-88cc-9138557e61c3","user":"channel-connector-foobar","time":"2024-05-09T18:00:49Z","message":"attempt to call history without sufficient permission"}
2024-05-09 11:00:49 {"level":"info","client":"e1d2e935-5c27-4b98-88cc-9138557e61c3","code":103,"command":"id:2 history:{channel:\"connector:foobar\" limit:3 since:{epoch:\"fqCt\"}}","error":"permission denied","reply":"id:2 error:{code:103 message:\"permission denied\"}","user":"channel-connector-foobar","time":"2024-05-09T18:00:49Z","message":"client command error"}

Success.

The following settings

      "allow_subscribe_for_client": true,
      "allow_subscribe_for_anonymous": true,
      "allow_publish_for_subscriber": true,
      "allow_publish_for_anonymous": true,
      "allow_history_for_subscriber": true,
      "allow_history_for_anonymous": true,
      "allow_presence_for_subscriber": true,
      "allow_presence_for_anonymous": true

where moved into the namespace object.

{
  "token_jwks_public_endpoint": "http://mock-oauth2-pro:50053/.well-known/jwks",
  "admin_password": "password",
  "admin_secret": "secret",
  "admin": true,
  "allowed_origins": ["http://localhost:3000"],
  "namespaces": [
    {
      "name": "connector",
      "presence": true,
      "join_leave": true,
      "history_size": 200,
      "history_ttl": "300h",
      "force_positioning": true,
      "force_recovery": true,
      "allow_subscribe_for_client": true,
      "allow_subscribe_for_anonymous": true,
      "allow_publish_for_subscriber": true,
      "allow_publish_for_anonymous": true,
      "allow_history_for_subscriber": true,
      "allow_history_for_anonymous": true,
      "allow_presence_for_subscriber": true,
      "allow_presence_for_anonymous": true
    },
    {
      "name": "connector_private",
      "presence": true,
      "join_leave": true,
      "history_size": 200,
      "history_ttl": "300h",
      "force_positioning": true,
      "force_recovery": true
    }
  ]
}
2024-05-09 11:07:30 {"level":"warn","time":"2024-05-09T18:07:30Z","message":"Centrifugo PRO license not found, run in sandbox mode: max connections 20, max nodes 2, max api rps 5"}
2024-05-09 11:07:30 {"level":"info","version":"5.3.3","release_time":"1714977611","edition":"pro","runtime":"go1.22.2","pid":1,"engine":"memory","gomaxprocs":12,"time":"2024-05-09T18:07:30Z","message":"starting Centrifugo"}
2024-05-09 11:07:30 {"level":"info","owner":"unlicensed","max_connections":20,"max_nodes":2,"max_api_rps":5,"time":"2024-05-09T18:07:30Z","message":"license information"}
2024-05-09 11:07:30 {"level":"info","path":"/centrifugo/config.json","time":"2024-05-09T18:07:30Z","message":"using config file"}
2024-05-09 11:07:30 {"level":"info","time":"2024-05-09T18:07:30Z","message":"serving websocket, api, admin endpoints on :8000"}
FZambia commented 6 months ago

👍

If I understood correctly the above – works as expected, closing.