I would like to add some automated logging and event tracking in my test builds. For development I am using an addon godot_dotenv so that I can keep a .env file out of my repository but keep api keys etc there. I am wondering about how this will be handled when I export my game. I would rather keep from, even temporarily, hard coding any api key into the source, but I am assuming continuing to use this plugin as is will require me to ship the test builds with a .env file that will have all my api keys in it.
What is considered the best practice for keeping secrets a secret when exporting a game with the godot engine. I am using gdscript. Iād love it if there was a way to load and encrypt a resource at export time.
Update after some questions:
I think I need to clarify a bit. I would like to keep my keys in an easy to modify place that out of source control --so no hard coding. I understand that once the thing ships then all bets are off and the best way to create some sort of client server secret management system but I donāt care that much.
This question is more about finding a way to read from a source, be it a resource or an environment variable, and make it behave like a hard coded variable.
I would add that it depends on what kind of keys/secrets they are. Depending on how well secured they need to be, the ābest practiceā might be to not ship the keys to the client at all and instead keep them on infrastructure you control.
Ideally if this is a client/server architecture, you would be generating a new client-specific private key upon install of the game, use that to connect to the server, and then the server would store all your api keys so that the information is never on the userās box. Then if say you need to make an API call somewhere you can do the call and return the results. This also allows you to encapsulate the API call on the server, and if you say decided to use a different service, you could make that change without needing to push and update to the user.
This post does not answer the updated question. Left for historical purposes.
Original post
From a more abstract viewpoint:
Firstly, whatever you ship to the client you can consider to be publicly visible and open source. It doesnāt matter how well you try to hide it and encrypt it - if your app can decrypt it on the client machine, then so can the user - with enough effort, of course.
So, you either make it so that itās not worth the effort, or you donāt rely on anything you shipped with the app.
The first approach is viable if the credentials arenāt really that important. If itās just some usage logging in aggregate form, then quite likely nobody will care to hack that. You can use some light encryption or maybe hardcode the key in the GDScript source. Either way itās mildly inconvenient to access (you need to decompile stuff and read the decompiled code), and the effort that would take isnāt worth it.
The second approach means that the credentials need to come from elsewhere. This would be normally some sort of user account that they have to create on your server. Now you donāt need to hide or encrypt anything! The user will supply the username and password, and you just use those to authenticate to your server. If someone starts to misbehave, you can immediately see who it is, and revoke their (and only their) access.
This is the preferred approach when you have more sensitive data to handle where people might be incentivized to get direct access to your API - for example high scores or multiplayer sessions.
I think I need to clarify a bit. I would like to keep my keys in an easy to modify place that out of source control --so no hard coding. I understand that once the thing ships then all bets are off and the best way to create some sort of client server secret management system but I donāt care that much.
This question is more about finding a way to read from a source, be it a resource or an environment variable, and make it behave like a hard coded variable.
You could add a simple settings menu or some kind of ālogin formā where the user can paste the API key. The key can be persisted to the user directory via ConfigFile, ResourceSaver or JSON.
This way, you donāt have to care about version control or plugins. It also allows your users (in case you want to distribute your game/app) to use their own API keys. And for you, you only have to enter the key once and it will automatically be re-used on next start.
I have rewritten RCON functionality in just GDScript with TCPServer/StreamPeerTCP and set the server password via a ConfigFile like @namelessvoid mentioned. You can fallback to a default ConfigFile from res:// whenever the version in user:// is invalid, hereās a configuration.gd autoload I use in any new project:
extends Node
var defaults: ConfigFile = ConfigFile.new()
var settings: ConfigFile = ConfigFile.new()
func _ready() -> void:
defaults.load("res://autoloads/configuration.cfg")
if !FileAccess.file_exists("user://configuration.cfg") or settings.load("user://configuration.cfg") != OK:
revert_settings()
return
else:
load_settings()
return
func load_settings() -> void:
var defaults_sections: PackedStringArray = []
var settings_sections: PackedStringArray = []
var defaults_keys: Array = []
var settings_keys: Array = []
defaults_sections = defaults.get_sections()
settings_sections = settings.get_sections()
if settings_sections != defaults_sections:
revert_settings()
return
else:
pass
for section in defaults_sections:
defaults_keys.append(defaults.get_section_keys(section))
continue
for section in settings_sections:
settings_keys.append(settings.get_section_keys(section))
continue
if settings_keys != defaults_keys:
revert_settings()
return
else:
#Sample modification and startup of my tcp.gd autoload based on ConfigFile
TCP.binding = settings.get_value("Networking", "Binding", "127.0.0.1")
TCP.port = settings.get_value("Networking", "Port", 42069)
TCP.headless = settings.get_value("Networking", "Headless", false)
TCP.rconpwd = settings.get_value("Networking", "Password", "ChangeMe!")
TCP.boot()
return
func revert_settings() -> void:
settings = defaults
settings.save("user://configuration.cfg")
load_settings()
return
If you want them to be less-persistent than a ConfigFile but moreso than hardcoding, then having users modify their executableās shortcut to include a parameter would allow for OS.get_cmdline_args( ) handling, i.e. $MyGame.exe --api-key $MyApiKeyValue
I like this approach best so far. I would still like some way to make it get included in the source as a part of the export instead of requiring it to be another file that is outside the executable. Making it an argument would satisfy that. I really donāt want to have to use a client account or anything like that for this --at least not while the game is still under development.
Maybe that is a better question. How can I have the export embed a variable in the pck before encryption?
Thatās where specifying your default in the res:// version of the ConfigFile kicks in. That way if the user never supplies one in their user:// version your version from res:// sets that value for them.
And this directly answers your final question, having it in the res:// version would include it in the PCK as long as your export settings are not filtering out *.cfg. Donāt confuse the load_settings() default values (third parameter on my TCP. variable assignments) with the default values from the res:// version of the ConfigFile, I just do both for consistencyās sake. But either way, whether I include .cfg, .db, etc. as long as my export settings are including them they stay in the PCK (or EXE if embedding the PCK in the executable which is my go-to export option).
Edit: I stand corrected! PCK-only exports donāt include asset res:// content, only full executable exports do. Do you ever plan on just shipping PCKs? What use case is at play here where you wouldnāt just do a full executable export?
I have done several projectsā¦ demand is fast, project leads donāt care, and overall ICT management either doesnāt know or has no clue.
Iāve worked with some of the leading solutions out there, but in the end, theyāre all just separate programs with their own quirks.
For your own project, keep secrets in environment variables or use a dedicated secrets manager like HashiCorp Vault, AWS Secrets Manager, or Dopplerājust donāt hardcode them in your repo.
At the very least, .env files with proper .gitignore rules are better than nothing.
Security might not be fun, but a leaked API key definitely isnāt.
Yea just for updates, DLCs, those sorts of things. People are mentioning Vault, SM, Doppler, etc. which are valid recommendations but the overall post made it sound like you needed to be able to modify these things more nimbly than even those. When I use AWS in my Godot projects, I just rely on the systemās CLI/SDK installs and local keys instead of giving Godot anything AWS cred-related.