# Design: More secure DB credentials **Author**: @jvmakine ## Description Currently, we store full database DSNs including usernames and passwords to an ftl managed secret under the module accessing the database. However, this is not very secure, and we need to figure a way to connect to a DB while minimizing the risk of exposing the credentials. ## Motivation There are multiple ways platforms can manage DB credentials. Some of these are: ### No rotation at all This risks exposing the credentials in the logs, or in the AWS console. ### Rotating the password on a schedule For example, AWS RDS supports rotating a managed password on a schedule. Then, if the database connection fails because of an authentication failure, the client will refetch the latest password from the secret manager and try again. ### Multiple users with rotating passwords We can also have two separate users, one active, and one passive, where the username and passwords are rotated on a schedule. If the rotation is made less frequently than the pods are recreated, we don't need to care about rotating credentials on the pods. Unfortunately, at least AWS does not support this out of the box, and is something that would have to be implemented manually. ### Multiple passwords for a single user MySQL 8.0 supports multiple passwords for a single user to make password rotation easier. ### IAM authentication AWS supports creating one-off passwords for authentication with the correct IAM role. There are PostgreSQL and MySQL extensions in RDS instances to manage users based on IAM roles. When a client wants to connect to the database, it uses the AWS SDK to generate a connection token - a short-lived password that can be used to authenticate. This token is typically valid for 15 minutes. The client must have the appropriate IAM permissions to generate these tokens. The database instance must be configured to use IAM authentication, and database users must be created with IAM authentication enabled. This provides a secure way to manage database access without storing long-lived credentials. ## Goals We want to provide an extensible approach for credential management, that allows us to leverage features of the underlying platform when available. We also want to provide sensible defaults that are secure and easy to use. Finally, we want to make sure the process of credential management is not visible to the application developer. ### Non-Goals (optional) The managed credentials do not need to be visible or manageable among user managed FTL secrets. As a matter of fact, security wise it is better if they are not. ## Design We shall implement a runtime API for getting database DSNs. This will be implemented as a gRPC plugin, that is compiled as a separate binary to the runner images. These implementations can use platform specific knowledge on how the final credentials are fetched. The calling runtime still needs to detect authentication failures and retry, in case the underlying credentials are based on a single password rotation. Additionally, we will add a new runtime-id to identify the runtime component used to interact with the underlying platform that the provisioner saves to the resource runtime. For example, we could have a separate ID for each different supported DB authentication method we might want to use on the platform. This id is then used to verify that the runtime has the correct runtime implementation available, and to use the correct implementation for getting the DSN. This allows us to support multiple authentication implementations in a single cluster if needed. ### Required changes Currently, the DB DSNs are passed to the runners via streaming ModuleContext updates, where the controller converts FTL secrets to the DSNs. However, if we want to support dynamic, per request DSNs, we will need to implement a new runtime API with the implementation running in the runner images. The API would look like this: ```proto message GetDSNRequest { schema.Database database = 1; schema.DatabaseRuntime runtime = 2; } message GetDSNResponse { string dsn = 1; } service DatabaseRuntimeService { rpc GetDSN(GetDSNRequest) returns (GetDSNResponse); } ``` The database proto would change slightly, to contain the db endpoint rather than the DSN in the runtime, and to add the runtime_id: ```proto message Database { ... optional DatabaseRuntime runtime = 31634; ... } message DatabaseRuntime { string endpoint = 1; string runtime_id = 2; } ``` The runners would have a configured map from runtime_ids to the implementation plugins, that they could use to select the correct runtime provider based on the runtime_id populated at infrastructure provisioning time. The database runtime configuration like name and endpoint would still be stored in the ModuleContext. This approach can be extended to be used in the future to provide other platform speicific runtimes when needed. ## Rejected Alternatives (optional) ### Implement own credential rotation process to update secrets This would mean we could not support authentication mechanisms like IAM authentication, and would have to expose the internal secrets to the end users. Additionally, this would mean that FTL owns the process of DB credential rotation, which could cause issues in larger organizations with specific requirements on how credentials are managed / rotated. ### No secret rotation Not very secure. ### Have the DSN resolution logic in the controller For cases like IAM authentication, this would mean that the controller needs to have the IAM role for all databases, which would make it a runtime god module.