-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdrop-in-example.php
More file actions
189 lines (176 loc) · 6.8 KB
/
Copy pathdrop-in-example.php
File metadata and controls
189 lines (176 loc) · 6.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
<?php
/**
* Reference secrets.php drop-in.
*
* This file is documentation, not code this plugin loads or tests. Copy what you
* need into wp-content/secrets.php and replace the two stub classes below with
* real calls into your platform. See docs/extending.md for the contracts these
* implement and what happens if a global ends up set to the wrong thing.
*
* A drop-in can set either global, both, or neither. Most hosts want their own
* key management long before they want their own row storage -- setting only
* $wp_secrets_keyring and leaving the store on the shipped default is a normal,
* common combination. Both are shown here for completeness.
*
* @package SecretsAPI
*/
/**
* Example read-only platform store.
*
* Modeled on real feedback from the proposal's comment thread (see
* docs/open-questions.md, "Host and platform providers"): some platforms are
* themselves the encryption boundary and want to serve their own credentials to
* WordPress without ever
* accepting a write back. Refusing from set() is how that is expressed --
* wp_set_secret() surfaces the WP_Error rather than silently no-opping or, worse,
* accepting a write it cannot actually honour.
*
* NOTE: for that deployment you almost certainly want a WP_Secrets_Provider
* rather than a store. A provider declares is_writable() false, so a settings
* screen never offers a save control in the first place, and it can be the
* encryption boundary itself. This store example remains because swapping only
* where ciphertext lives, while keeping WordPress's envelope, is a legitimate and
* simpler thing to want.
*
* A store CANNOT use this to hand WordPress a plaintext value. Every method here
* traffics only in the record array WP_Secrets_Cipher produces -- get() returns
* one, set() (were it implemented) would accept one. A platform that wants to
* serve its own plaintext to WordPress is a materially different feature than
* this interface provides, and is tracked, unresolved, at
* docs/open-questions.md, "Host and platform providers".
*/
final class Example_Platform_Store implements WP_Secrets_Store {
/**
* Reads a record from the platform. Stubbed to always report absence.
*
* @param string $name The secret's namespaced name.
* @param bool $network Whether this is a network-scope secret.
*
* @return array|null|WP_Error
*/
public function get( $name, $network = false ) {
/*
* Replace with a real call to your platform's credential API. Whatever it
* returns must be re-shaped into the record array WP_Secrets_Cipher
* produces -- this class cannot invent that shape from a plaintext value,
* because a store is never handed a plaintext value to encrypt.
*
* $response = My_Platform_Client::get_secret_record( $name, $network );
*
* if ( is_wp_error( $response ) ) {
* return new WP_Error(
* WP_SECRETS_ERROR_STORE_UNAVAILABLE,
* 'The platform API could not be reached.'
* );
* }
*
* return null === $response ? null : $response;
*/
return null;
}
/**
* Refused outright: this platform's credentials are managed by its own tooling.
*
* @param string $name The secret's namespaced name.
* @param array $record The record to store.
* @param bool $network Whether this is a network-scope secret.
*
* @return WP_Error
*/
public function set( $name, $record, $network = false ) {
return new WP_Error(
WP_SECRETS_ERROR_PROVIDER_READ_ONLY,
'This platform manages credentials outside of WordPress.'
);
}
/**
* Refused outright: deletion happens in the platform's own tooling.
*
* @param string $name The secret's namespaced name.
* @param bool $network Whether this is a network-scope secret.
*
* @return WP_Error
*/
public function delete( $name, $network = false ) {
return new WP_Error(
WP_SECRETS_ERROR_PROVIDER_READ_ONLY,
'This platform manages credentials outside of WordPress.'
);
}
/**
* Lists secret names known to the platform. Stubbed to an empty list.
*
* @param bool $network Whether to list network-scope secrets.
*
* @return array|WP_Error
*/
public function list_names( $network = false ) {
// Replace with a real listing call. Returning an empty array is also
// valid if your platform has no way to enumerate names cheaply --
// wp_list_secrets() will simply show nothing rather than erroring.
return array();
}
}
/**
* Example KMS-backed keyring.
*
* Protects exactly one thing -- the 32-byte root key -- never a secret value.
* Swapping this in changes where that one value is protected without touching
* anything else: every master key, data key, and secret still derives the same
* way, from whatever wrap()/unwrap() hand back.
*/
final class Example_KMS_Keyring implements WP_Secrets_Keyring {
/**
* Wraps root key material via the platform's KMS. Stubbed to always fail.
*
* @param string $key_material Raw key material to protect.
*
* @return string|WP_Error Opaque wrapped value on success.
*/
public function wrap( $key_material ) {
/*
* Replace with a real KMS encrypt call, e.g.:
*
* $result = My_KMS_Client::encrypt( 'alias/my-wp-root-key', $key_material );
*
* if ( is_wp_error( $result ) ) {
* return new WP_Error( WP_SECRETS_ERROR_KEY_UNAVAILABLE, 'KMS unreachable.' );
* }
*
* return $result;
*/
return new WP_Error( WP_SECRETS_ERROR_KEY_UNAVAILABLE, 'Not implemented -- example only.' );
}
/**
* Unwraps root key material via the platform's KMS. Stubbed to always fail.
*
* @param string $wrapped An opaque value previously returned by wrap().
*
* @return string|WP_Error Raw key material on success.
*/
public function unwrap( $wrapped ) {
// Replace with a real KMS decrypt call, mirroring wrap() above.
return new WP_Error( WP_SECRETS_ERROR_KEY_UNAVAILABLE, 'Not implemented -- example only.' );
}
/**
* Shown in Site Health. Never sensitive, never the key material itself.
*
* @return string
*/
public function get_key_source() {
return 'Example KMS (replace before use)';
}
}
/*
* The globals a drop-in sets. wp_secrets_api_load_dropin() (or, once this lands
* in core, the equivalent bootstrap code in wp-settings.php) checks the type of
* whatever ends up here immediately after this file is required. Anything other
* than an instance of the matching interface -- including leaving a variable set
* to null, a string, or a half-constructed object from a caught error -- fails
* that half of the API closed for the rest of the request, via
* WP_Secrets_Broken_Store / WP_Secrets_Broken_Keyring. That is deliberate: a
* credential backend that might be misconfigured must never look like one that
* is simply empty.
*/
$GLOBALS['wp_secrets_store'] = new Example_Platform_Store();
$GLOBALS['wp_secrets_keyring'] = new Example_KMS_Keyring();