Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ wp config delete <name> [--type=<type>] [--config-file=<path>]
Generates a wp-config.php file.

~~~
wp config create [--dbname=<dbname>] [--dbuser=<dbuser>] [--dbpass|db_password=<dbpass>] [--dbhost=<dbhost>] [--dbprefix=<dbprefix>] [--dbcharset=<dbcharset>] [--dbcollate=<dbcollate>] [--locale=<locale>] [--extra-php] [--skip-salts] [--skip-check] [--force] [--config-file=<path>] [--insecure] [--ssl]
wp config create [--dbname=<dbname>] [--dbuser=<dbuser>] [--dbpass|db_password=<dbpass>] [--dbhost=<dbhost>] [--dbprefix=<dbprefix>] [--dbcharset=<dbcharset>] [--dbcollate=<dbcollate>] [--locale=<locale>] [--extra-php] [--skip-salts] [--skip-secrets-key] [--skip-check] [--force] [--config-file=<path>] [--insecure] [--ssl]
~~~

Creates a new wp-config.php with database constants, and verifies that
Expand Down Expand Up @@ -257,6 +257,9 @@ the database constants are correct.
[--skip-salts]
If set, keys and salts won't be generated, but should instead be passed via `--extra-php`.

[--skip-secrets-key]
If set, `WP_SECRETS_KEY` won't be generated. WordPress 7.2 and later need it to store secrets with the Secrets API, so pass it via `--extra-php` instead.

[--skip-check]
If set, the database connection is not checked.

Expand Down Expand Up @@ -576,7 +579,7 @@ wp config shuffle-salts [<keys>...] [--force] [--config-file=<path>] [--insecure
**OPTIONS**

[<keys>...]
One ore more keys to shuffle. If none are provided, this falls back to the default WordPress Core salt keys.
One ore more keys to shuffle. If none are provided, this falls back to the default WordPress Core salt keys. `WP_SECRETS_KEY` is not a salt and cannot be shuffled: changing it makes every stored secret unreadable.

[--force]
If an unknown key is requested to be shuffled, add it instead of throwing a warning.
Expand Down
63 changes: 63 additions & 0 deletions features/config-create.feature
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,69 @@ Feature: Create a wp-config file
define( 'AUTH_SALT',
"""

@require-php-7.0
Scenario: Configure with a Secrets API key generated
Given an empty directory
And WP files

When I run `wp config create {CORE_CONFIG_SETTINGS} --skip-check`
Then the wp-config.php file should contain:
"""
define( 'WP_SECRETS_KEY',
"""

When I run `wp config get WP_SECRETS_KEY`
Then STDOUT should match /^[A-Za-z0-9+\/]{43}=$/

Scenario: Configure without a Secrets API key
Given an empty directory
And WP files

When I run `wp config create {CORE_CONFIG_SETTINGS} --skip-check --skip-secrets-key`
Then the wp-config.php file should not contain:
"""
WP_SECRETS_KEY
"""

Scenario: Configure with a Secrets API key passed via --extra-php
Given an empty directory
And WP files

When I run `wp config create {CORE_CONFIG_SETTINGS} --skip-check --extra-php="define( 'WP_SECRETS_KEY', 'my-own-key' );"`
Then the wp-config.php file should contain:
"""
define( 'WP_SECRETS_KEY', 'my-own-key' );
"""

When I run `wp config get WP_SECRETS_KEY`
Then STDOUT should be:
"""
my-own-key
"""

When I run `grep -c WP_SECRETS_KEY wp-config.php`
Then STDOUT should be:
"""
1
"""

Scenario: Configure with a Secrets API key passed via --extra-php in upper case
Given an empty directory
And WP files

When I run `wp config create {CORE_CONFIG_SETTINGS} --skip-check --extra-php="DEFINE( 'WP_SECRETS_KEY', 'my-own-key' );"`
And I run `grep -c WP_SECRETS_KEY wp-config.php`
Then STDOUT should be:
"""
1
"""

When I run `wp config get WP_SECRETS_KEY`
Then STDOUT should be:
"""
my-own-key
"""

Scenario: Values are properly escaped to avoid creating invalid config files
Given an empty directory
And WP files
Expand Down
28 changes: 28 additions & 0 deletions features/config-shuffle-salts.feature
Original file line number Diff line number Diff line change
Expand Up @@ -265,3 +265,31 @@ Feature: Refresh the salts in the wp-config.php file
"""
{NEW_KEY_ORIG}
"""

Scenario: The Secrets API key is not a salt
Given an empty directory
And WP files
And I run `wp config create {CORE_CONFIG_SETTINGS} --skip-check`
And I run `wp config get WP_SECRETS_KEY`
And save STDOUT as {SECRETS_KEY}

When I run `wp config shuffle-salts`
And I run `wp config get WP_SECRETS_KEY`
Then STDOUT should be:
"""
{SECRETS_KEY}
"""

When I try `wp config shuffle-salts WP_SECRETS_KEY`
Then STDERR should contain:
"""
WP_SECRETS_KEY is not a salt and cannot be shuffled
"""
And the return code should be 1

When I try `wp config shuffle-salts WP_SECRETS_KEY --force`
Then STDERR should contain:
"""
WP_SECRETS_KEY is not a salt and cannot be shuffled
"""
And the return code should be 1
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
34 changes: 33 additions & 1 deletion src/Config_Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,9 @@ private static function get_initial_locale() {
* [--skip-salts]
* : If set, keys and salts won't be generated, but should instead be passed via `--extra-php`.
*
* [--skip-secrets-key]
* : If set, `WP_SECRETS_KEY` won't be generated. WordPress 7.2 and later need it to store secrets with the Secrets API, so pass it via `--extra-php` instead.
*
* [--skip-check]
* : If set, the database connection is not checked.
*
Expand Down Expand Up @@ -311,6 +314,7 @@ public function create( $_, $assoc_args ) {
[
'keys-and-salts' => false,
'keys-and-salts-alt' => '',
'secrets-key' => false,
'extra-php' => '',
]
);
Expand All @@ -337,6 +341,25 @@ public function create( $_, $assoc_args ) {
$template_args['extra-php'] = $extra_php;
}

/*
* WordPress 7.2 and later need WP_SECRETS_KEY to store secrets. It is a base64-encoded
* random 32-byte value, unlike the salts, and it is left alone if --extra-php defines it.
* PHP function names are case-insensitive and constant names are not, so only `define`
* is matched in any case.
*/
$extra_php_defines_secrets_key = is_string( $template_args['extra-php'] )
&& preg_match( '/(?i:define)\s*\(\s*[\'"]WP_SECRETS_KEY[\'"]/', $template_args['extra-php'] );

if ( ! Utils\get_flag_value( $assoc_args, 'skip-secrets-key' ) && ! $extra_php_defines_secrets_key ) {
try {
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- The Secrets API reads the key as base64.
$assoc_args['secrets-key'] = base64_encode( random_bytes( 32 ) );
$template_args['secrets-key'] = true;
} catch ( Exception $e ) {
WP_CLI::warning( 'Could not generate WP_SECRETS_KEY: ' . $e->getMessage() . ' Add it to the wp-config.php file to use the Secrets API.' );
}
}

$command_root = Path::phar_safe( dirname( __DIR__ ) );
$out = Utils\mustache_render( "{$command_root}/templates/wp-config.mustache", $template_args );

Expand Down Expand Up @@ -441,6 +464,10 @@ public function create( $_, $assoc_args ) {
$config_transformer->update( 'constant', $name, $assoc_args[ $arg_name ], [ 'add' => true ] );
}
}

if ( ! empty( $template_args['secrets-key'] ) ) {
$config_transformer->update( 'constant', 'WP_SECRETS_KEY', $assoc_args['secrets-key'], [ 'add' => true ] );
}
} catch ( Throwable $exception ) {
$cleanup_error = '';
if ( $created_config_file && file_exists( $assoc_args['config-file'] ) ) {
Expand Down Expand Up @@ -1246,7 +1273,7 @@ public function has( $args, $assoc_args ) {
* ## OPTIONS
*
* [<keys>...]
* : One ore more keys to shuffle. If none are provided, this falls back to the default WordPress Core salt keys.
* : One ore more keys to shuffle. If none are provided, this falls back to the default WordPress Core salt keys. `WP_SECRETS_KEY` is not a salt and cannot be shuffled: changing it makes every stored secret unreadable.
*
* [--force]
* : If an unknown key is requested to be shuffled, add it instead of throwing a warning.
Expand Down Expand Up @@ -1275,6 +1302,11 @@ public function shuffle_salts( $args, $assoc_args ) {
$keys = $args;
$force = Utils\get_flag_value( $assoc_args, 'force', false );

// Secrets stored with the Secrets API cannot be read once WP_SECRETS_KEY changes.
if ( in_array( 'WP_SECRETS_KEY', $keys, true ) ) {
WP_CLI::error( 'WP_SECRETS_KEY is not a salt and cannot be shuffled: changing it makes every secret stored with the Secrets API unreadable.' );
}

$has_keys = ( ! empty( $keys ) ) ? true : false;

if ( empty( $keys ) ) {
Expand Down
12 changes: 12 additions & 0 deletions templates/wp-config.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,18 @@ define( 'WP_CACHE_KEY_SALT', '{{wp-cache-key-salt}}' );
{{keys-and-salts-alt}}

/**#@-*/
{{#secrets-key}}

/**
* Encryption key for the Secrets API.
*
* Secrets stored by WordPress and plugins are encrypted with this key.
* Unlike the keys and salts above, changing it makes every stored secret unreadable.
*
* @since 7.2.0
*/
define( 'WP_SECRETS_KEY', '' );
{{/secrets-key}}

/**
* WordPress database table prefix.
Expand Down
Loading