1

In a PHP application I am writing, I would like not to expose the production DB credentials to other developers.

I read several questions and answers here on SO, e.g. the following thread has many interesting thoughts regarding the topic:

How to secure database passwords in PHP?

I decided that I want to move the credentials in a file outside the application's root. Suppose that I am using PDO and that within my application container I create my PDO instance:

<?php

// ...
require_once __DIR__ . '/../db_pdo_outside_document_root.php';

$containerConfig = [
   'db_connection' => function() {
      return new PDO(DB_PDO_DSN, DB_PDO_USER, DB_PDO_PASSWD, DB_PDO_OPTIONS);
   }
];
$appContainer = new ApplicationContainer($containerConfig);

// Use the container and handle the request...

The DB_PDO_* constants passed to the PDO constructor all come from the file db_pdo_outside_document_root.php which is outside the document root:

<?php
// db_pdo_outside_document_root.php
define('DB_PDO_DSN', 'mysql:host=localhost;dbname=app_database');
define('DB_PDO_USER', 'db_user');
define('DB_PDO_PASSWD', 'db_fancy_passwd');
define('DB_PDO_OPTIONS', [
    PDO::ATTR_PERSISTENT => false,
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);

So far, so good. However, this doesn't help much, because if someone wants to echo the contents of the constants, they can easily do it:

<?php

// In some PHP file used by the application... 

echo DB_PDO_PASSWD; // echoes the DB password.
//mail('developer@mail.com', 'Subject', DB_PDO_PASSWD); // Or send it by email

Now, of course, you must trust the colleagues you work with, but who knows, sometimes it happens that an employee leaves, maybe it is fired, and so on. And we don't want them to have a chance to somehow store production DB credentials on their computers.

So I thought that maybe, instead of defining the constants within the file, the file itself can return the PDO object, which in turn seems not to expose the credentials it uses to connect to the database:

<?php

// ...

var_dump($appContainer->get('db_connection'))

Outputs something like:

/path/to/htdocs/file.php:4:
object(PDO)[13]

So I would end up with something like this within my application's bootstrap code:

<?php

// ...    

$containerConfig = [
   'db_connection' => function() {
      return require_once __DIR__ . '/../db_pdo_outside_document_root.php';
   }
];
$appContainer = new ApplicationContainer($containerConfig);

// Use the container and handle the request...

And inside the outer file:

<?php
// db_pdo_outside_document_root.php
return new PDO('mysql:host=localhost;dbname=app_database', 
'db_user', 'db_fancy_passwd', [
    PDO::ATTR_PERSISTENT => false,
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);;

But I don't know if this is enough or if there are some caveats I should be aware of (apart from the fact that this "workaround" is a bit ugly).

Do you think other developers will be able to somehow read the credentials from the return value of $appContainer->get('db_connection') (supposing that it returns the PDO object above)?

I also tried with ReflectionClass and I do not see any property on that object:

<?php

//...

$ref = new ReflectionClass($appContainer->get('db_connection'));
var_dump($ref->getProperties());

Outputs:

/path/to/htdocs/file.php:4:
array (size=0)
  empty

What do you think about this approach? Not the invention of the century but it seems that it does the job. Or, maybe there is still a way to access the credentials in the application's code I am not aware of.

I would like to have your opinions.

Thank you for the attention.

EDI: as @IsThisJavascript pointed out, application's code of course can still get the contents of the file and therefore access the credentials, therefore my whole reasoning was not correct as it didn't consider this very simple case. Guess I have to find another strategy, if one exists...

tonix
  • 6,671
  • 13
  • 75
  • 136
  • `maybe there is still a way to access the credentials in the application's code I am not aware of.` If they have access to the server, then all they need to do is use `file_get_contents` on `__DIR__ . '/../db_pdo_outside_document_root.php';` – IsThisJavascript Nov 22 '18 at 17:41
  • Should have thought about about it, so simple ahah – tonix Nov 22 '18 at 17:43
  • Then I can refuse all pushed code which somehow accesses that file. This though is not trivial and seems a bit complicated – tonix Nov 22 '18 at 17:44
  • Honestly, I think you should risk it. If a developer is going to be malicious then they are shooting themselves in the foot. I'm not really sure of a way to solve your issue that isn't overly complex. – IsThisJavascript Nov 22 '18 at 17:46
  • 4
    Saying that, you should set up a staging server that doesn't share the credentials of the database. Then you would be in charge of pushing everything to live. The devs don't need to work on prod server - that causes far too many problems as your codebase grows – IsThisJavascript Nov 22 '18 at 17:48
  • 3
    +1 for "devs don't ever get to touch production". Devs love to say thing like "I _just_ need to do this one thing in prod" without testing or documentation and then, even if it doesn't immediately break, you've got this landmine waiting for you to step on it the next time you deploy. – Sammitch Nov 22 '18 at 18:40

3 Answers3

1

You can combine 2 tactics:

1. Separate the dev and production environments

It's easiest to make them "include" the location of the database, the account used and the passwords from a configuration file that's different on the production and development side of things.

-> that way the developers don't get to know sensitive bits of the production side of things at all.

2. give users individual accounts

Give your developers individual accounts onto your development database server, so they get (individual) databases they can do everything with, while you can still have testing environments where integration can be done using databases that are more like those of production (and hence more controlled in what can happen to them)

This requires a bit of user management for every newly recruited developer and some action when they leave, but it's not really all that difficult to have your internal IT support staff add one more item on their checklists along with creating/removing an email address etc.

This works best if on the development side of things you give them individual configuration files to include, all ready to move toward production eventually.

-> this allows developers an area where they can do whatever they like, without affecting each-other, and once they leave, you can archive it all and take it offline.

0

Your developers need not know the password of the production DB if you have a dev server and a production server. If your devs are working straight on the production server then there is really no way you can prevent your problem. Typically only the sysadmin/devops knows the DB password and he sets that in the production server. Your developers should have 0 access to that.

IMB
  • 15,163
  • 19
  • 82
  • 140
  • But how can I inhibit them from adding lines of code which, if get published to production, would access the file containing the credentials PHP uses to connect to the DB like in my example above and like @isThisJavascript pointed out? I can only think of a filename which is unknown to the developers and which they could not guess somehow. – tonix Nov 22 '18 at 17:55
  • @tonix If your devs work only on the dev server, you (or your sysadmin/devops) will have a chance to review the code if it has malicious intent before pushing that code to production. That's what you should do anyway, all your devs' work should be reviewed/tested before pushing to production sever. – IMB Nov 22 '18 at 17:59
  • @tonix not being able to trust your developers at that level is a layer 8 problem, which is generally addressed via management/HR policies, and/or threats of violence. Going out of your way to break into the prod DB like that is a fireable offense. – Sammitch Nov 22 '18 at 18:43
  • I know, and normally no one would do it. But think about a company whose developers work remotely and are distributed around the world, you cannot really trust everyone nowadays – tonix Nov 22 '18 at 19:16
  • 1
    Maybe AOP could help in this case? If I register the AOP layer on top of the application and before calling every function I check if one of the parameters is the file storing the `PDO` instance creation, I can interrupt the program with an exception. This way the application won't even pass the staging phase where tests are performed before code is deployed... But then I would have as I guess a non-trivial overhead, I don't know, I am just thinking about the possibilities. – tonix Nov 22 '18 at 19:23
-1

The most common solution is to use a .env file to store the credentials required by the codebase.

Developers get (/make/customise) a .env file with development credentials, production credentials are only available in the production environment.

This .env file is kept in the document root, but not distributed with the code: it's not added to the repository (git/svn)

Usually, a .env.example file is included with basic development variables, such as paths to test environments for external API's and sample database credentials for their local databases. In contrast to .env, .env.example is added to the source control repository.

In terms of code, you can use a library such as dotenv to read the values of the .env file.

Assuming you'd like to protect yourself against backdoors from individual developers with malicious intentions, you can use code reviews to prevent such attempts. In modern software production companies, as well as open source projects, it's a standard practice to only allow code that has been reviewed to be merged into the master branch.

Code reviews have many other benefits as well: having an extra set of eyes on each part of the code prevents bugs and encourages developers to learn from each other.

When you follow such a protocol, it's virtually impossible for an individual developer to access the production credentials.

Stratadox
  • 1,291
  • 8
  • 21
  • .env files are plain text. Environment variables are typically also plain text. This might be a common solution, but it is not a secure solution. – Martin Thoma Apr 12 '21 at 11:18
  • Of course they are plain text. The server needs access to the unencrypted passwords, so even if you'd encrypt your production .env file (which lives only on the production server, red) you'd also need to have the decryption mechanism and key in the exact same place, making it rather useless. More secure than handing out encrypted credentials, is not handing out credentials at all to those who don't need it. Hence the approach. – Stratadox Mar 05 '22 at 14:22