Jump to content

Enhancing Security and Efficiency in ProcessWire Installations


AswinC
 Share

Recommended Posts

Hello everyone,

As a community that frequently installs ProcessWire, we understand the importance of efficiency and security in our workflows.
We’ve been contemplating the idea of leveraging auto-deploy scripts to streamline our installations, especially considering the wide array of web hosting panels available to us.

Here’s a rough sketch of how we envision this process:

  1. Git clone: The first step would involve cloning the repository.
  2. Pre-configuration: Next, we’d place an install_preconfig.php file containing an array of variables that install.php could utilize if available. This file would include pre-configured variables such as dbhost, among others.
  3. Auto Install Modules
  4. Cleanup. Deleting the pre-config file & installation file.

Currently, we’re aware that these variables are fetched using ini_get, which unfortunately poses a security risk.
If someone were to launch phpinfo(), our credentials could potentially be exposed.

To mitigate this, we propose replacing all variables in the install file with $preconfig['mysqli.default_host'] ?? ini_get("mysqli.default_host"). This way, we prioritize the use of our pre-configured variables, resorting to ini_get only when necessary.

We believe this initiative could significantly enhance both the security and efficiency of our ProcessWire installations.
However, we understand that this is a community effort.
What is the best way to get this initiative hit off?  @ryan

Kind Regards,

Link to comment
Share on other sites

  • AswinC changed the title to Enhancing Security and Efficiency in ProcessWire Installations

It's been talked about elsewhere here in the forums, but an alternate idea would be utilizing .env files. It would satisfy both the installation process and long-term management of values. Some benefits:

  • Widely used across applications, frameworks, and languages running in a web server environment and strongly recommended as a standard practice
  • Secure by default. Web servers do not serve .env files
  • Provides the ability to manage local and production configurations separately
  • Frameworks like Laravel provide a .env file as well as a .env.example file containing all variables used in the application. This is kept up to date, used to document application requirements, and is committed into the repository
  • Values that are not security-critical can be safely stored in the example file
  • Provides a convenient location for additional non-ProcessWire site/app specific values such as API keys, or those required for modules
  • Solves the issue of excluding site/config.php from a repository given that it's a required file containing specific syntax and $config object property assignments

If .env was available in ProcessWire I would have my Fluency module look for translation API keys there rather than store them in the database. A benefit there is that different environments like local/ staging/production can mean an environment that is yours and an environment that is a client's. Pulling the latest copy of a database from production wouldn't require manually re-configuring modules that require API keys, or module registration keys.

It would be good to eliminate storing security-critical values in PHP files entirely. The .env file could be generated and populated during the ProcessWire install process pre-excluded in the .gitignore file. Example:

ENVIRONMENT="development"

# ProcessWire
PW_DB_HOST=localhost
PW_DB_NAME="your_database
PW_DB_USER=your_database_user
PW_DB_PASS="LowuHeju7[BoI3"
PW_DB_PORT=3306
PW_DB_ENGINE=InnoDB
PW_DEBUG=true
PW_CHMOD_DIR=0755
PW_CHMOD_FILE=0644
PW_USE_PAGE_CLASSES=true
PW_USE_FUNCTIONS_API=true
PW_PREPEND_TEMPLATE_FILE="_init.php"
PW_USER_AUTH_SALT=d5e3ac4beda1e382255bbd8744d7e815
PW_LOCALE="en_US.UTF-8"
PW_TIMEZONE="America/Los_Angeles"
PW_DEFAULT_ADMIN_THEME=AdminThemeUikit
PW_INSTALLED=1580675413
PW_HTTP_HOSTS = "domain.com,www.domain.com"

# Some API that is used
EXTERNAL_SERVICE_API_URL="https://api.someservice.loc/v2" # Development
EXTERNAL_SERVICE_API_KEY=0d41fe1b68244f4ea51ae5e4abd24fab

# Mailgun
MG_KEY=key-192cde47ab73095e747ebe7556577b2d
MG_DOMAIN="mg.somedomain.com"

# Forecast.io
FORECAST_IO_KEY=c28096383d66bcb1e2cb9ec37153f85c

To take the idea further, it could be integrated with the ProcessWire API in a way that would prevent conflicts, keep .env variable naming organized, and make any value added by the developer available. Something like:

<?php

// An accessor method, this is probably the cleanest and resembles other frameworks
$config->env('SOME_ENV_VARIABLE');

// Nested under a property acting as a namespace
$config->env->SOME_ENV_VARIABLE;

// On ProcessWire projects I implement some extras to easily interact with .env values without needing to access them directly.
$config->envIs('production'); // boolean

I haven't built a ProcessWire site without implementing this for many years and there's a great widely-used great package that provides .env values to PHP. There are also hosting providers that make environment variables/secrets manageable through their admin UI that eliminates the need for an actual file in production entirely. If ProcessWire were to implement environment variables it could look to values that may already exist and fall back to using a file.

Implementation aside, if someone is able to access phpinfo() in production it's a critical issue beyond exposing config values given the totality of information dumped by that function.

  • Like 8
Link to comment
Share on other sites

18 hours ago, FireWire said:

I haven't built a ProcessWire site without implementing this for many years

Me too! This is now default in all my installations for all the reasons you mention.

But I feel this sort of update would be a PW4 thing? 

  • Like 1
Link to comment
Share on other sites

On 4/23/2024 at 8:20 AM, elabx said:

But I feel this sort of update would be a PW4 thing? 

I didn't think about that, but it could be. I don't want to make assumptions about ease of implementation. Breaking changes could be mitigated with fallbacks- use the env if it's available, and fall back to config object if not. I think what would be a true PW4 change would be full adoption of .env and deprecation of config.php. A case could be made that fallbacks now can introduce gradual adoption and make a breaking change in PW4 less impactful.

On 4/22/2024 at 1:57 PM, gmclelland said:

Here's one place this was talked about in the past:

I hadn't seen this discussed there in a formal repo thread before, but good to see interest elsewhere. IMHO, the initial framing of the request around 12 factor apps could have been simplified to just benefits and good practice. Conversely, the argument that not everyone is building 12 factor apps isn't a case against it. All said- I don't want to be critical of a conversation that happened that long ago, I mean- who would I be to talk? I wasn't using .env files in 2017 🤷‍♂️

I do want to share a thought on this specifically in ProcessWire context. One of my favorite things about PW is the file structure philosophy where core lives in /wire and templates, styles, additional modules, assets, etc. live in /site. Site is a living breathing volatile folder and the domain of the developer to extend ProcessWire as a project demands. Replacing /wire is one of the most frictionless upgrade concepts ever and it's flat out elegant. It would be great to see the /site folder adopt that- a true "drag-and-drop" self-contained application directory that can be deployed anywhere ProcessWire exists. The /wire directory wouldn't be dependent on /site, and (aside from core version) /site wouldn't be coupled to /wire. Consider that the root directory is already environment specific. The .htaccess file, node_modules, vendor, .git, etc. are all in environment context. The use of .env fits here. This may come off as an esoteric or abstract argument, but think about how much time you spend naming things in programming and tell me that's not the world we live in 🤣

To address a point in that Github thread as to considerations made inside /site for environment, such as an 'if' statement whether on dev or production, I think that's great for application logic that responds to the environment but does not alter it.

It would be great to see this adopted into core, but as always with a project like ProcessWire it's up to the people doing the great work of maintaining and improving it to determine priority. Even if it's not implemented in core, I hope to have made a case for adoption in individual projects.

Link to comment
Share on other sites

@ryan, Can you have a look at this, and point us into the direction how to get this into pw3 / pw4 ?
Being supported /maintained by the community.
Basically an auto install by providing a preconfig or .env variables.

Link to comment
Share on other sites

  • 3 weeks later...

I've been running processwire locally with the vendor/ folder up one level, outside the site root. Check out Moritz' post on processwire.dev for the background to this. This works fine, but requires a few tweaks. I can't find the post at the moment, but I do remember reading that Ryan has said that he considers the /index.php file as site-specific and therefore can be edited by users for their specific installations. So I've chosen to edit the vendor/ path directly in /index.php for testing.

I've also been experimenting with allowing the installation to define the vendor/ path via an environment variable, and this seems to work well, too. I've put in a speculative pull request for this.

  • Thanks 1
Link to comment
Share on other sites

  • 2 weeks later...

I've written a utility for working with .env files in ProcessWire and it makes use of the directory structure in the great post shared by @netcarver. I think this addresses the items brought up here about vendor directory placement and also provides a clean and simple method of implementation while providing some handy tools to boot.

You can view and download the code for this .env implementation strategy from this repository.

This reads and caches .env file variables and makes them easy to work with in the ProcessWire config.php file. The readme in that repository provides a full rundown of directory structure and setup. Here's an overview of how it's used once you've configured it in your project.

<?php namespace ProcessWire;

/**
 * Example usage in config.php
 **/

use App\Env\Env;

if(!defined("PROCESSWIRE")) die();

$env = Env::load();

// You can now access any variable in the .env file located in your root directory with an accessor method
$env->get('YOUR_ENV_VARIABLE'); // => returns whatever that value is in .env

// You can optionally make env values accessible globally in ProcessWire by assigning $env to a property
// Then you can use $config->env->get('NAME_OF_VARIABLE');
$config->env = $env;

// Use to set up your config values
$config->debug = $env->get('DEBUG');

// ...

But hey, why stop there? You can also do this:

<?php namespace ProcessWire;

/**
 * Example config.php file
 */

use App\Env\Env;

if(!defined("PROCESSWIRE")) die();

$env = Env::load();

$config = $env->pushToConfig($config, [
  'useFunctionsAPI' => 'USE_FUNCTIONS_API',
  'usePageClasses' => 'USE_PAGE_CLASSES',
  'useMarkupRegions' => 'USE_MARKUP_REGIONS',
  'prependTemplateFile' => 'PREPEND_TEMPLATE_FILE',
  'appendTemplateFile' => 'APPEND_TEMPLATE_FILE',
  'templateCompile' => 'TEMPLATE_COMPILE',
  'dbHost' => 'DB_HOST',
  'dbName' => 'DB_NAME',
  'dbUser' => 'DB_USER',
  'dbPass' => 'DB_PASS',
  'dbPort' => 'DB_PORT',
  'dbEngine' => 'DB_ENGINE',
  'userAuthSalt' => 'USER_AUTH_SALT',
  'tableSalt' => 'TABLE_SALT',
  'chmodDir' => 'CHMOD_DIR',
  'chmodFile' => 'CHMOD_FILE',
  'timezone' => 'TIMEZONE',
  'defaultAdminTheme' => 'DEFAULT_ADMIN_THEME',
  'installed' => 'INSTALLED',
  'debug' => 'DEBUG',
]);

// Arrays aren't for .env files...
$config->httpHosts = ['yourdomain.com'];

// You can also get all of the values at once with this method
$env->toArray();

What this does behind the scenes:

  • Parses the .env file
  • Casts booleans and ints appropriately where detected
  • Caches the .env values in a file located outside of the public directory (read why below)
  • Provides utility methods to access the .env data
  • Optionally loads values to the server $_ENV array
  • Values provided by the Env object are immutable

Because ProcessWire has pretty much all of it's out-of-the-box configurations located in config.php, by default this only loads the values and makes them accessible via the get() method available on the object returned by Env::load(), not via $_ENV. You can optionally make your values available in $_ENV by passing 'true' to the load() method.

About caching... This uses phpdotenv to read the .env file and load it's contents. There is some overhead for this process and requires significantly more work than just turning the .env into a key/value array. This application uses phpdotenv to parse the file then stores that in a cache file called env.php as a native array which is then subsequently loaded using require_once, an operation that is trivial for PHP to handle. Additionally, the object returned by Env::load() memoizes all values so the cached file is only read once during application execution and all subsequent key/value access is pulled from memory. Cache can be cleared using a provided method.

I like how this came out and hope others find it useful. Interested in everyone's thoughts.

It would really be great if installation and directory configuration were automated in a bash script, but I don't have the time. Pull requests are wide open for that 😎

  • Like 2
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...