godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.17k stars 98 forks source link

Add the ability to send authentication information in before accepting RPC calls #548

Closed ghost closed 2 years ago

ghost commented 4 years ago

Describe the project you are working on:

I am working on a 3D multiplayer game with an authoritative server architecture.

Describe the problem or limitation you are having in your project:

There is no way to prevent malicious users from joining a server and starting to call random RPC functions in order to break game's logic.

Describe the feature / enhancement and how it helps to overcome the problem or limitation:

There should be a way to send authentification information before an actual handshake will occur, and the player will be able to send RPC calls to the server.

Describe how your proposal will work, with code, pseudocode, mockups, and/or diagrams:

I took this idea from a library that I worked with previously, and it did not have this problem (LiteNetLib). What they are doing, is that when you are connecting to a server, you are given a choice to additionally send some authentification info in order to validate player on the server. I am unfamiliar with GDScript, so I will be using C# in my issue instead, sorry in advance.

Currently, method CreateClient from NetworkedMultiplayerENet only accepts basic features like IP, port and bandwidth: CreateClient(string address, int port, int inBandwidth = 0, int outBandwidth = 0, int clientPort = 0)

I propose to add an additional argument Key, that is going to be a byte array, so that client could send that info like this: CreateClient(string address, int port, byte[] key, int inBandwidth = 0, int outBandwidth = 0, int clientPort = 0)

And also network_peer_connected should change arguments from (int id) to (int id, byte[] key) to suit this system.

If this enhancement will not be used often, can it be worked around with a few lines of script?:

I really don't want to see networking games in Godot to have this insecurity, which forces to write code in a manner of constant checks for player being properly registered on the server.

Is there a reason why this should be core and not an add-on in the asset library?:

My reasoning is explained in previous answer.

Calinou commented 4 years ago

What does the "authentication info" usually look like? Is it generated by a handshake process or something?

LikeLakers2 commented 4 years ago

(Edit: I misunderstood the issue. Please ignore this comment!)

ghost commented 4 years ago

That is not the case that I am trying to describe with my issue.

I do believe that those checks should still be presented, even if your client is only sending input to the server, and then visualizes the result that he got back.

My issue is that most of the player registration logic would be only executed after validation (There is a reason why in GMod/Minecraft there are two distinct types of events, like OnPlayerConnected and OnPlayerJoined) and that those in-game RPC calls probably would expect a properly registered player.

I know what you are saying, I understand that it would not prevent rogue RPC calls from being present from the client, but if there is going to be some sort of authentification signal (or maybe even an ability to block/unblock other RPC calls except for the one that could provide auth info), then there would be no need for every RPC exposed function to check for peer/player valid registration, and after that do usual anti-cheat checks.

ghost commented 4 years ago

In my project, I have made a bandaid solution like this

ghost commented 4 years ago

What does the "authentication info" usually look like? Is it generated by a handshake process or something?

If the developer would like to integrate a Steam auth, for example, then the client needs to provide an auth token to the server, that got provided to him by Steam itself.

LikeLakers2 commented 4 years ago

That is not the case that I am trying to describe with my issue.

Sorry, upon rereading your initial post, yeah it seems I misunderstood. My apologies.

jonbonazza commented 4 years ago

Authentication should happen before a connection to the game server is even attempted. It also should not happen over raw tcp or udp, but i digress. There are a bunch of different viable user authentication mechanisms out there, perhaps most notably is OAuth2+OpenID (which works over http) or simply just a simple credentials based authentication using access tokens. Regardless of the authn mechanism you choose, the user should be authenticated before any other connection (gameserver or otherwise) is made.

jonbonazza commented 4 years ago

I also want to point out that there are loads of different authn and authz methods available and which one that is used depends on the rest of the server infrastructure. There is no way to do this genericaly in godot core. It's entirely doable as a plugin though, which is exactly where it should be implemented.

IRT Steam, like every other "social network" authn provider, Steam auth is implemented via OAuth2+OpenID (last I checked, at least). This would also require JWT integration, which we've already deemed as not a good fit for godot core. I will be creating a JWT addon (or you can create your own if you do not wish to wait), but otherwise, it's just an HTTPS API that ultimately grants you a session token.

Another popular authn mechanism is HMAC. Much simpler to implement but no real support for Authz. Again though, this could easily be implemented vua GDScript as it's just HTTPS requests that use HMAC keys for message authentication. Once the hmac pr is merged into godot core this will be even easier.

The only thing i think might make sense at the core level is some sort of "middleware" support for rpcs. Some way to inject a function (or functions) to be called to for each incoming rpc before it makes it to the actual node. This could be used to automatically reject rpcs that don't contain a valid session.

tx350z commented 3 years ago

Throwing in my 2 cents (ok, maybe 10 cents).

I think there needs to be a distinction between authenticating client apps vs. players. The solution becomes much simpler if the server first authenticates the client app connecting. Once you trust the app connecting, the user authentication can be addressed using whatever method is best for the game's need. I'm finding that existing Godot core networking functionality has everything needed.

I'm developing a MMO style game and am building in capability to easily scale the server-side. My planned approach is to separate server-side functionality into "servlets" by functional domain. Because RPC relies on node paths to route RPC calls, each servlet will be a child node of "/root" to make them easy to reference. The node names will be randomized at server start-up with the exception of one well-known servlet that authenticates the connecting client app. This can also be used to verify the client app version and even initiate client app updates. On the client-side there will be "servlet_connectors" for each of the server-side servlets. The servlet_connectors allow making RPC calls to the servlets. The servlet_connectors also have remote callback methods used by the servlets to return data to the calling clients.

Once the client app is authenticated, the app authentication servlet will return (via RPC callback) an authentication block (Dictionary) of config data to the client app. The authentication block contains all the info the client needs to connect/RPC with other servlet nodes (those that have random names on the server). One of the returned values will be a client authentication token that must be sent as a parameter to all other servlet RPC calls. When the client receives the authentication block, it adds the appropriate "servlet_connector" nodes as children of "/root" using the randomized node names supplied by the server.

It sounds complicated but is actually pretty simple and clean IF you've planned out your client/server functionality. One of the side benefits of the above approach is the clients can be redirected to a different server (e.g. supply IP/Port as part of the returned config data) allowing the server-side to be physically broken into functional server apps and possibly even clustering for load balancing across multiple server hosts. Another benefit (addressing the OP question) is that by having only one well-known RPC callable method exposed the scope of the problem becomes smaller. Keep in mind the app authentication and user authentication functionalities can (should) be isolated to server apps separated from the main game server.

As an added note, most large game applications will need a back-end database. Since gdscript has no database support I solved this by creating C# coded classes that use ADO to connect to the database. Works perfectly.

ka23ppa3 commented 3 years ago

@Calinou Hello. I also developing a multiplayer game with server logic which cant just accept anyone but only registered users, so looking at Godot 3.x RPC functionality I does not found what I need.

FIRST: I want to have additional RPC argument "args: Dictionary" for any additional info which we need to retrieve from client.

var args: Dictionary = ["login" : "User", "password" : "userpass"]
CreateClient(string address, int port, args)

This is also useful for creating password-protected rooms even in more simple multiplayer games!

SECOND: Need the ability to accept or reject connection server-side with callback for client.

P.S: I can go just with FIRST and create callback or disconnect player instantly as in SECOND without additional functionality using get_tree().disconnect_network_peer(id), but still need a way to get additional info from client when its connecting.

Please, can you implement this simple, but strongly useful feature, in 3.x branch? Because our planned release might be soon(also we need to test game with authorization) and I want to go with simple and powerful Godot RPC high-level API, without implementing custom-one.

Here I want to mention useful cases:

Thanks!

Calinou commented 3 years ago

Hello. I also developing a multiplayer game with server logic which cant just accept anyone but only registered users, so looking at Godot 3.x RPC functionality I does not found what I need.

You can already kick unregistered players as soon as they log in. To avoid displaying messages to other players when unregistered players log in, only display the "X joined the game" message after the join procedure has finished.

Please, can you implement this simple, but strongly useful feature, in 3.x branch? Because our planned release might be soon(also we need to test game with authorization) and I want to go with simple and powerful Godot RPC high-level API, without implementing custom-one.

I don't have network programming knowledge, and it will take longer than that :slightly_smiling_face:

ka23ppa3 commented 3 years ago

Hello. I also developing a multiplayer game with server logic which cant just accept anyone but only registered users, so looking at Godot 3.x RPC functionality I does not found what I need.

You can already kick unregistered players as soon as they log in. To avoid displaying messages to other players when unregistered players log in, only display the "X joined the game" message after the join procedure has finished.

Please, can you implement this simple, but strongly useful feature, in 3.x branch? Because our planned release might be soon(also we need to test game with authorization) and I want to go with simple and powerful Godot RPC high-level API, without implementing custom-one.

I don't have network programming knowledge, and it will take longer than that slightly_smiling_face

As always thanks for fast reply. I don`t ask for something which seems to require network knowledge, I just want to get one additional parameter with dictionary at the function when client is created, nothing special. Correct me please.

ka23ppa3 commented 3 years ago

Also I cant kick unregistered players as I have no options to check if they are registered or not - that`s the problem. I receive no data from them(except their client_id) such as login and password, so I just unable to check their registration.

ka23ppa3 commented 3 years ago

The only workaround which I see is to give to client small amount of time(several seconds) to send their login and password using RPC call just after connection and kick them if they are not confirmed them-selves in time, but I want to make it more clear just with one very small addition engine-side, which are super-useful in the above examples and possibly some more.

Calinou commented 3 years ago

@ka23ppa3 Please use the Edit button (hidden behind the dropdown next to your comments instead of multi-posting.

tx350z commented 3 years ago

@Calinou Hello. I also developing a multiplayer game with server logic which cant just accept anyone but only registered users, so looking at Godot 3.x RPC functionality I does not found what I need.

FIRST: I want to have additional RPC argument "args: Dictionary" for any additional info which we need to retrieve from client.

var args: Dictionary = ["login" : "User", "password" : "userpass"]
CreateClient(string address, int port, args)

This is also useful for creating password-protected rooms even in more simple multiplayer games!

SECOND: Need the ability to accept or reject connection server-side with callback for client.

P.S: I can go just with FIRST and create callback or disconnect player instantly as in SECOND without additional functionality using get_tree().disconnect_network_peer(id), but still need a way to get additional info from client when its connecting.

Please, can you implement this simple, but strongly useful feature, in 3.x branch? Because our planned release might be soon(also we need to test game with authorization) and I want to go with simple and powerful Godot RPC high-level API, without implementing custom-one.

Here I want to mention useful cases:

  • Password-protected rooms
  • Multiplayer games with authorization
  • Comparing client and server version
  • Blacklists

Thanks!

@Calinou Hello. I also developing a multiplayer game with server logic which cant just accept anyone but only registered users, so looking at Godot 3.x RPC functionality I does not found what I need.

FIRST: I want to have additional RPC argument "args: Dictionary" for any additional info which we need to retrieve from client.

var args: Dictionary = ["login" : "User", "password" : "userpass"]
CreateClient(string address, int port, args)

This is also useful for creating password-protected rooms even in more simple multiplayer games!

SECOND: Need the ability to accept or reject connection server-side with callback for client.

P.S: I can go just with FIRST and create callback or disconnect player instantly as in SECOND without additional functionality using get_tree().disconnect_network_peer(id), but still need a way to get additional info from client when its connecting.

Please, can you implement this simple, but strongly useful feature, in 3.x branch? Because our planned release might be soon(also we need to test game with authorization) and I want to go with simple and powerful Godot RPC high-level API, without implementing custom-one.

Here I want to mention useful cases:

  • Password-protected rooms
  • Multiplayer games with authorization
  • Comparing client and server version
  • Blacklists

Thanks!

You can already do all of this with GDScript.

ka23ppa3 commented 3 years ago

@tx350z

You can already do all of this with GDScript

Examples? I checked documentation and found nothing about it also this issue was created and still no reply about how exactly it can be done with high-level RPC godot API.

Please, keep in mind that I talking about branch 3.x and not 4.x

Jummit commented 2 years ago

I think this proposal should be resolved in Godot 4, as it's a very common thing seen in multiplayer games.

In my oppinion a built-in way of player authentication would be best, as it allows for maximal security with little risk of the game developer messing up and compromising it. It also allows for quick prototyping without the need for much boilerplate code.

I would also accept a section in the documentation talking about secure methods for user authentication. For reference, there have been some video tutorials about this topic: https://youtu.be/6pfN6NFxQQc

quiyip commented 2 years ago

If anyone is still trying to do this, right now the way I'm implementing this is as follows:

I do consider this a workaround since it basically forces you to not use any remote keywords anywhere in your game, as these will be callable by unauthenticated players, so I think this functionality still needs to be implemented in core.

Also, setting server_relay in NetworkedMultiplayerENet to false should prevent anyone but the server from calling RPCs on clients. You can then make the server only call RPCs on clients if they are authenticated using your own authentication system.

darthLeviN commented 2 years ago

I have a similar problem. this is a big problem. because you don't only need security. if you want to load something(lets say the level) while the game is connected and then create the networked nodes like npcs and players afterwards. you should be able to block useless rpc calls while connected. here is my suggestion : 1- Let Node have a boolean property called public_rpc with the default value of false. 2- Let the Server Or Client(client might want to block rpc calls) have a function to disable rpc calls for certain users. rpc calls for nodes with public_rpc set to true will still function. we can call this function disable_rpc(peer_id = 0). peer_id = 0 will be used to block rpc calls from the client side so that the client can load the whole level and create all networked nodes before accepting any rpc calls.

to my understanding if we call this by the peer_connected signal on the server side it will block all incoming-outgoing forbidden rpc calls for that user

After a connection is successful the client can communicate with the server for authentication with public_rpc nodes. the programmer decides how things are done. it's flexible and easy to use. this communication could be just authentication or asking the server to send information for all networked nodes after the level fully loads.

communication example : Server side :

  1. i'm letting anyone connect. they can authenticate by rpc calls with nodes that have their public_rpc set to true.
  2. i'm going to run everything normally.

Client side :

  1. i connected to the host. now i will authenticate.
  2. i have authenticated now i will load the level.
  3. i have loaded the level, now i will ask the host for the containing information to create all networked nodes with their latest information.
  4. i have received the information. now i will create the nodes and call enable_rpc(0) to allow rpc calls to be processed without any drops. no reliable rpc calls will be dropped and no invalid state for the networked nodes will take place.

Client side reconnecting :

  1. ok, i seem to have a connection problem, i will use disable_rpc(0), mark all the networked nodes for destruction, now i will ask the host for the containing information to create all networked nodes with their latest information.
  2. i have received the information containing the list of nodes to create with their data to load. i will update any existing nodes with information from the list, destroy nodes that aren't included in the list and create any nodes that are in the list but don't exist. then i will call enable_rpc(0) and resume the game.

Client side loading something heavy mid game that might take a while:

  1. call disable_rpc(0). pause the SceneTree, load what's needed, then do steps similar to what was done for reconnecting.