Search code examples
phplaravelpermissionswebserver

Laravel Storage Framework and Log File/Folder Permissions Issue


I'm having an issue with Laravel's file/folder permissions when they are generated from different sources. This may be a bit long winded, but bear with me.

Scenario

1) An artisan command, called TestCommand.php, triggered via php artisan test:command has the following code:

public function handle(){
  \Log::info("Test Command Executed from Command Line");
}

2) A Contoller Function in TestController.php, triggered when accessing http://localhost/test, which has the following code:

public function test(){
  \Log::info("Test Command Executed from Web View");
}

Both functions work correctly when triggered individually, but as soon as one is triggered, the other will not work, as the generated log file in storage/logs now has incorrect permissions, user and/or group.

Situation 1

If I run php artisan test:command, I get the following log file:

-rw-r--r-- | 1 | tim | wheel | 76B | 25 Aug 14:03 | laravel-2017-08-25.log

And navigating to http://localhost/test returns the following error:

This page isn’t working

localhost is currently unable to handle this request.

HTTP ERROR 500

No Laravel stack trace ("Woops, something went wrong") is displayed, as that only happens following writing to the Laravel logs.

Situation 2

If I navigate to http://localhost/test first, I get this log file:

-rw-r--r-- | 1 | _www | wheel | 72B | 25 Aug 14:06 | laravel-2017-08-25.log

And running the php artisan test:command returns the following:

PHP Fatal error: Uncaught exception 'UnexpectedValueException' with message 'The stream or file ".../storage/logs/laravel-2017-08-25.log" could not be opened: failed to open stream: Permission denied' in .../vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php:97 Stack trace: ...

Conclusion

Notice the user is now _www instead of tim, which is to be expected given the generation source of the file in storage/logs. Retroactively, this can be fixed quite easily by running:

sudo chmod -R 777 storage/logs

But that's not a good solution for a number of reasons:

  • 777 is a bad idea for production web servers, and 755 doesn't seem to work for my configuration.
  • Any time a new file is created (daily for logs, any time for files in storage/framework/*, etc) this command would need to be run again.
  • I don't have direct access to the webserver where this command would have to be run x number of times per day, meaning I would have to bug someone else to do it.

Given all this, how do I unify the files to allow either source to create the file and then modify the file as needed?

Webserver is apache, dev environment is Mac, production is linux

Update

I think a good solution is to have tim and _www in the same group, and assign storage/logs to that group using

chgrp -R <user_group> storage

But then the next issue is that by default, the permissions on any generated folder in Laravel is 644, which doesn't allow write access by the group, only read.

Will look into modifying this if possible.


Solution

  • Found the solution to this issue; ACLs!

    On Mac, there's an option for chmod +a ..., which will add default directory permissions to whatever folder you specify. In my case, I want to use ACLs that allow tim and _www full access to files/folders under storage, since they'll both be accessing/writing to it constantly:

    chmod -R +a 'tim allow read,write,delete,add_file,add_subdirectory,file_inherit,directory_inherit' storage
    chmod -R +a '_www allow read,write,delete,add_file,add_subdirectory,file_inherit,directory_inherit' storage
    

    Applying this recursively allows ACLs to be set on any folders generated by Laravel in the storage directory, most important being those is storage/framework/cache.

    On Linux, ACLs seem to be slightly different, but the idea is the same:

    chmod g+s storage
    setfacl -R -d -m u::rwx storage
    setfacl -R -d -m g::rx storage
    setfacl -R -d -m o::rx storage
    

    This would effectively set the file permissions to storage (and all subfolders) to 755, which seems to be what my configuration needs to allow writing properly.