vlucas / phpdotenv

Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.
BSD 3-Clause "New" or "Revised" License
13.15k stars 627 forks source link

When you have two or more sites on one server, the sites's .env will affect each other #219

Closed leesenlen closed 5 years ago

leesenlen commented 7 years ago

php manual: function putenv()

The environment variable will only exist for the duration of the current request.

    public function setEnvironmentVariable($name, $value = null)
    {
        list($name, $value) = $this->normaliseEnvironmentVariable($name, $value);

        // Don't overwrite existing environment variables if we're immutable
        // Ruby's dotenv does this with `ENV[key] ||= value`.
        if ($this->immutable && $this->getEnvironmentVariable($name) !== null) {
            return;
        }

        // If PHP is running as an Apache module and an existing
        // Apache environment variable exists, overwrite it
        if (function_exists('apache_getenv') && function_exists('apache_setenv') && apache_getenv($name)) {
            apache_setenv($name, $value);
        }

        if (function_exists('putenv')) {
            putenv("$name=$value");
        }

        $_ENV[$name] = $value;
        $_SERVER[$name] = $value;
    }

    public function getEnvironmentVariable($name)
    {
        switch (true) {
            case array_key_exists($name, $_ENV):
                return $_ENV[$name];
            case array_key_exists($name, $_SERVER):
                return $_SERVER[$name];
            default:
                $value = getenv($name);//****notice line****
                return $value === false ? null : $value; 
        }
    }

When one request was running, the other request would get this one's env(getenv);

we can use overload() instead of load() to circumvent this problem;

This is just a suggestion;

kAlvaro commented 7 years ago

Not sure if you refer to that but in Windows builds of the Apache module env variables randomly leak between threads. It's some complicate stuff about multi-process architecture that I can't understand, but it's been a personal source of frustration for years every time I used a technology that depended on environment: strftime(), gettext, etc.

If you want a reliable cross-platform solution you'll probably have to stick to $_SERVER.

Caroga commented 7 years ago

I've stumbled upon this very annoying bug as well. My colleauge and I spent much time debugging this and found that $_SERVER is being shared across multiple vhost configurations, causing weird problems/errors.

We've got this issue on Windows, running Apache. I've looked at the issue https://github.com/vlucas/phpdotenv/pull/208 (as referenced by https://github.com/vlucas/phpdotenv/issues/214), and think this is the most clean solution in case there is not another solution.

I was wondering what the status of this issue is, and what a possible solution would be?

Kind regards

Caroga commented 7 years ago

Any updates would be much appreciated.

leesenlen commented 7 years ago

It looks like that would only happend on Windows/Apache while using putenv(). I've tested this on Linux or Windows/nginx. It is ok.

Caroga commented 7 years ago

Wait, how can this be closed with such an answer?

leesenlen commented 7 years ago

176 The issue seems to have been solved. use Branch2.3.

Otherwise, you can use overload() instead of load() to override env variables.

ffflabs commented 7 years ago

Are we talking about Apache with modphp or does this affect fastcgi upstreams too?

It seems like using different phpfpm pools for each vhost properly isolates the scope of $_SERVER.

This should be added to the tests btw

Caroga commented 7 years ago

@leesenlen I've tried with the new branch reference as dependency in Laravel, but either it's not configured right or the solution is incorrect. Problem still exist. When will this hit release?

leesenlen commented 7 years ago

@amenadiel I didn't do the test with fastcgi only modphp, but i thought it would work fine too (apache/fastcgi) @Caroga Sorry! I got wrong! The solution #176 cannot fix the issue;

The issue would happened when using putenv and $_ENV; It has been mentioned by @kAlvaro It would happened anyway using $_ENV , I would like reopen the issue

Anyone suggestion?

Caroga commented 7 years ago

Well, I've got no suggestion for a solution (yet). I am working my way to migrate to using docker containers on our workstations to work around the problem. In the meanwhile I'll be monitoring this issue in case something pops up.

martinrios95 commented 7 years ago

The reference to Laravel was made for two sites between dotenv files that had problems on both sites.

Caroga commented 7 years ago

@martinrios95 I'm curious if you have got a working setup now, as we are also struggling with this error (windows, apache, leaky as could be).

sisve commented 7 years ago

Laravel expects you to use a cached configuration file. Create it using php artisan config:cache. Having a cached configuration files disables dotenv entirely (Laravel 5.2 and newer), or will not populate the config from dotenv (Laravel 5.1).

martinrios95 commented 7 years ago

Yes, I started using Laravel's config from cached configuration, not dotenv. That configuration disabled the API problem I had.

Caroga commented 7 years ago

I will try this as soon as possible! Thank you @sisve

patrickc91 commented 7 years ago

Using overload() instead of load() seemed to the resolve the issue for me.

I'm glad I checked the Issue board. This problem was driving me insane.

sisve commented 7 years ago

The main difference between overload() and load() is that overload will overwrite existing environment variables while load will not. Your change makes your code overwrite the previous site's environment variables. It's still a timing issue, multiple parallell requests will still be able to read other request's environment variables.

Using load():

  1. Site A starts executing a request.
  2. Site A loads Site A's environment variables.
  3. Site B starts executing a request.
  4. Site B loads Site B's environment variables unless the same keys are used by A.

Result:

Using overload():

  1. Site A starts executing a request.
  2. Site A loads Site A's environment variables.
  3. Site B starts executing a request.
  4. Site B loads Site B's environment variables and overwrite existing ones given by A.

Result:

TL;DR:

Neither load(), nor overload(), is a universal fix. Your site just happens to work, but there is still information disclosure and interaction with other sites via process-wide environment variables. Laravel has a workaround using cached configuration files; it only uses the environment variables to build the cache. No environment variables are used during normal web requests, and dotenv is never invoked.

This issue is related to a webserver hosting several requests within the same process. This causes problems with other things too, like setlocale(). Other configurations, like nginx + php-fpm, is not affected by this problem since it executes one process per request.

Warning: The locale information is maintained per process, not per thread. If you are running PHP on a multithreaded server API like IIS, HHVM or Apache on Windows, you may experience sudden changes in locale settings while a script is running, though the script itself never called setlocale(). This happens due to other scripts running in different threads of the same process at the same time, changing the process-wide locale using setlocale().

patrickc91 commented 7 years ago

Dang. Thank you for the breakdown on exactly what is happening. That really helps a lot.

I think I'll have to find another way to handle these environment variables than using this package if that's the case. We're running some legacy sites on a shared Windows + Apache server. As we update and rebuild our sites, we're moving them into Docker Containers. But a lot of them will have to remain on the Windows + Apache server until that time comes.

Thank you for the insight! I'm sure it will help many more to come in the future.

leesenlen commented 7 years ago

php artisan config:cache is a good suggestion when using laravel; We also need another solution for separated phpdotenv; It is an annoying bugs. Waiting...@_@

kAlvaro commented 7 years ago

Sorry if it's a dumb question but... Is there any reason to even use the environment in the first place?

Caroga commented 7 years ago

@leesenlen I've tried config:cache and it does seem to work, but as my config files are not entirely standing on their own, meaning: .env values will be ignored, this currently messes up on other areas.

I actually don't see why this can't be, or isn't, fixed?

patrickc91 commented 7 years ago

@kAlvaro I'm currently using the .env file to store things like DB credentials. We have a legacy website that has a development and production version. Before I started git tracking them, they were basically separate code bases with all of the connection and URL information hard coded in. Obviously, having DB connection information like users and passwords hard coded into your PHP is a big security risk. By using an .env, I was able to replace all hard coded instances with variables loaded from the .env. So now it's easy to just clone the repository and insert an .env with your relevant development or production information.

But the bug being discussed in this thread is causing issues that will probably push me to switch to using PHP .ini files. Both versions of this legacy site are running on sub-directories of the same Windows + Apache server. So there are issues loading up information correctly.

Hopefully that answers your question. Or at least provides one instance where it's useful.

leesenlen commented 7 years ago

load config from files with different format(not only .env) if you need here is hassankhan/config ^_^

martinrios95 commented 7 years ago

To replace .env files from a project on vanilla-PHP, I suggest not hard-code PHP variables on several files. You must include one file from a config section and call it by include or require...

I'll put an example: your-server-path/config/database.php define(server, 'localhost'); define(user, 'root'); # I know you mustn't use root user, but it's just an example, hehe define(password, 'password'); define(database, 'database');

$dbConnection = mysqli_connect(server, user, password, database);

leesenlen commented 7 years ago

@martinrios95 It's not a good idea in a complex project with different environment (production,development,etc...) We need to deploy project easily at different environment and manage our project files easily with git(version control)

The others do not need to look for configuration in your code. That's why we need environment config file(like .env or yaml or ini etc..)

martinrios95 commented 7 years ago

@leesenlen It could be on a small project, of course... I don't use Git control for EVERY project I work, but it's a good idea :1st_place_medal:

kAlvaro commented 7 years ago

@patrickc91 I was referring to the operating system environment, not the phpdotenv library. As in e.g. Dotenv\Loader\setEnvironmentVariable():

    if (function_exists('putenv')) {
        putenv("$name=$value");
    }

    $_ENV[$name] = $value;
    $_SERVER[$name] = $value;

What's the exact use case of transmitting our PHP app settings elsewhere?

mirbaagheri commented 7 years ago

Complete explanations @sisve #issuecomment-325020817

ffflabs commented 7 years ago

@martinrios95 the purpose of having config vars in an .env file is to be able to either load from said file (on local enviroments, on-premise instances) or read from the environment variables set globally, and do this transparently, because the application shouldn't need to know if the config parameters are coming from a file or from global environment vars.

For example, when deploying a PHP project to an AWS instance using fabric, you can upload the .env file during the deployment stage (because you shouldn't have it versioned, of course). If you wanted to do the same on Heroku, you wouldn't be able to upload said .env file. The proper way to define these config variables is to insert them in your environment dashboard for that project. The same applies to CI providers and docker containers.

martinrios95 commented 7 years ago

Yeap, I got it. The server doesn't want to know or save database or site/server data. That is not secure, and also, is easier to config a server on production without touching PHP files anymore. And so on, you just upload the .env file to FTP and that's it.

I've just learned .env usage and purpose and now I can understand this library and what is being used for.

Caroga commented 6 years ago

I feel like this thread has been sliding towards a non-issue-related talk and would like to ask if we could get this back on track by taking a step back and ask the following questions:

Currently I am working on providing different setups for local development for me and my colleagues that should reflect our production servers, but in the meantime this is still a very active and troubling problem.

If this situation is not solvable with the provided setup, then I think this issue should marked unsolved and be closed to prevent further discussion or hopes.

Really not trying to come of as mean or angry, just looking for a direction to go with ;-)

Cheers!

ffflabs commented 6 years ago

Yeah that's kind of the idea, bit there's also the fact that some environments simply don't allow modifying files. You can't just upload an .env file to a heroku instance or a docker container. So the variables you want to read must be already present at an environment level.

martinrios95 commented 6 years ago

@Caroga I think that'll be marked as unsolved, as I saw and I could understand, this issue is related from a weakness of using .env's from a local machine to various sites. That special case, in my opinion, .env cannot cover it.

ffflabs commented 6 years ago

@caroga I believe this is an issue with Apache modphp threads leaking into each other.

See, for example, php opcache config http://php.net/manual/en/opcache.configuration.php#ini.opcache.use-cwd

It allows to explicitly use the current www_root as a namespace to avoid collisions. And this is because it is a known fact that php will leak between threads of different vhosts. One approach, if you used different .env files for different vhosts, would be to namespace the variable names for each one. Such prefix could be inferred during runtime by defining a constant as the dirname of your entry point.

Hacky as hell but it might work.

Have you tried looking at stack overflow for a similar problem?

sisve commented 6 years ago

This issue isn't about the opcache, but several requests sharing the same environment variables. The environment is per-process, so a process holding several threads running php (like Apache and modphp) will have all those php execution threads share the same environment.

Fixing this properly will be hard; it would require dotenv to implement a per-thread environment and patch the operating system to know about this when creating new threads. I'm pretty sure that is out-of-scope of this library.

ffflabs commented 6 years ago

@sisve yeah, I only used opcache as an example of how other PHP features deal with variable leak.

Caroga commented 6 years ago

@martinrios95, @amenadiel, thanks for your response on this. I feel like what @sisve is saying is the most assumable outcome of this issue...

But I surely would love to hear from someone who is a part of this repository and get some closure on this.

@vlucas: could you maybe shed some light on this issue?

mirbaagheri commented 6 years ago

You put me to the blush! @Caroga This is best solution for me: 317444983 No need to manipulate the source. just with one command: php artisan config:cache in Laravel.

mukesh-n-crest commented 6 years ago

Hi Guys, I'm also facing the same issue with Laravel and .env due to multiple requests running at the same time because i'm switching the environment based upon the subdomain and it will load the environment variables from .env.[subdomain] file but the same are visible for the different concurrent requests also for different subdomain which is an issue

Do you think it can be solved?

sisve commented 6 years ago

@mukesh-n-crest That sounds like something you should discuss in a support forum for Laravel. You could try the forums or Slack on https://larachat.co/

joshkel commented 5 years ago

I stumbled across this issue today while trying to convert a Composer application with older, ad hoc configuration handling to instead use phpdotenv.

If I understand the issue correctly, there are three possible ways that phpdotenv could address this:

  1. The issue only arises from using putenv. So, phpdotenv could offer some sort of local_only option, off by default. If that's enabled, then it would skip calling putenv - so variables that it loads would be accessible with $_ENV and $_SERVER, but not getenv, and they couldn't leak to other threads.
  2. Right now, when using immutable loading, if the environment variable is accessible via getenv or $_ENV or $_SERVER, then neither putenv nor $_ENV nor $_SERVER is updated. Changing immutable loading to evaluate each of those separately (update putenv if not in getenv, update $_ENV if not in $_ENV, update $_SERVER if not in $_SERVER) would alleviate the symptoms and would arguably be more consistent, but it wouldn't address the underlying issue of environment variable leakage.
  3. phpdotenv could detect when it's using putenv in a multithreaded environment and generate a deprecation warning. I think this would be feasible (check the SAPI and warn if it's HHVM, or IIS, or Apache with an mpm other than prefork).

And these aren't mutually exclusive.

Do any or all of these approaches sound reasonable? I can work on putting together one or more PRs if there's interest.

GrahamCampbell commented 5 years ago

Hmm, I don't think this is necessary. I'd strongly recommend people use this package to one-time use this library to populate a compiled config file, exactly how Laravel does it. I'd consider that to be the correct way to use this library in a multi-threaded environment (that is, by not using it in the multi-threaded environment).

joshkel commented 5 years ago

Thanks for the feedback, @GrahamCampbell.

I like Laravel's approach of populating a compiled config file as part of the production deploy step. But I'm working on updating a legacy CodeIgniter application that doesn't provide a cached config implementation and doesn't even have a proper production deploy step. And all of that should probably someday be fixed, but right now, the elegance of saying, "Just automatically grab the config settings from environment variables or a .env file" is very appealing. Saying I either have to redo my server environments or not actually automatically grab config settings from a .env file makes it a lot less appealing.

Right now, I'm running into problems even in development; the Windows XAMPP environment that my organization's developers use is multithreaded by default. And saying that the library doesn't support being used in a multithreaded environment feels a bit backwards; I would have expected whether the server is multithreaded or single-threaded to be an implementation detail, and a high-level environment like PHP to either not care or to accommodate (as much as practical), rather than an individual library mandating a particular server.

(I also wondered if phpdotenv should try and detect when it's using putenv in a multithreaded environment and generate a deprecation warning. It seems like several people have run into this problem, so warning instead of silently failing might help. I can open a PR if you're interested.)

I'm not trying to be argumentative, and if this use case really isn't in scope for the library, then I don't want to try and drag the project in a direction that it isn't meant to go.

However, if I understand the phpdotenv 3 docs correctly, then version 3 will let you configure it to not touch getenv, which should solve this issue and should let it work in a multithreaded environment (provided you can get by with just $_ENV and $_SERVER). So this would already be supported once phpdotenv 3 is released, with those caveats. Is that correct?

Thank you.

GrahamCampbell commented 5 years ago

So this would already be supported once phpdotenv 3 is released, with those caveats.

Yes, I think so. Good point. :)

GrahamCampbell commented 5 years ago

I can open a PR if you're interested.

Yes please. We should make the isSupported() method on the PutenvAdapter return false when we're in an unsafe environment.

GrahamCampbell commented 5 years ago

I think it's likely the following example will work in a multithreaded environment. This avoids using the adapter that would have called putenv and getenv, which are not threadsafe.

<?php

use Dotenv\Environment\Adapter\EnvConstAdapter;
use Dotenv\Environment\Adapter\ServerConstAdapter;
use Dotenv\Environment\DotenvFactory;
use Dotenv\Dotenv;

$factory = new DotenvFactory([new EnvConstAdapter(), new ServerConstAdapter()]);

Dotenv::create($path, null, $factory)->load();
GrahamCampbell commented 5 years ago

// cc @joshkel

moazam1 commented 5 years ago

@GrahamCampbell I couldn't find Environment namespace in your package.

GrahamCampbell commented 5 years ago

Do you have version 3 installed?

dakujem commented 5 years ago

~This~ The following is not a solution but a bypass.

I overcame this issue by loading the variables by Apache using .htaccess.

In more detail, instead of loading

# .env
FOO=bar

.env file, instead I set the variable like this in .htaccess file:

# .htaccess
SetEnv FOO bar

This way, if it happens that .env does not load the env variables, Apache will make sure they are present anyway.

To mitigate problems with unintentional committing the variables to source code, I'm using a special "htaccess" file, which I name .htenv and add that to Git ignore list. In my vhost configuraion I then add the file to the list of files apache looks for (Note: order matters, the first one to be found will be used, in this case .htenv has priority):

<VirtualHost *:443>
    ...
    AccessFileName .htenv .htaccess
</VirtualHost>

and I then use .htenv file instead of .env. The problem is thosugh, now you have to make sure both .env and your .htenv files set the same variables, because if you use the application in a CLI mode, it will be loading .env.

Hope this helps a desperate soul.