├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── examples ├── shell.php └── task.php └── src ├── Client.php ├── Exception ├── ClientException.php ├── ServerException.php └── VaultExceptionInterface.php ├── OptionsResolver.php ├── ServiceFactory.php └── Services ├── Auth ├── AppRole.php └── Token.php ├── Data.php ├── Sys.php └── Transit.php /.gitignore: -------------------------------------------------------------------------------- 1 | .git 2 | .idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Christian Winther 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Vault PHP SDK 2 | ============= 3 | 4 | Installation 5 | ------------ 6 | 7 | This library can be installed with composer: 8 | 9 | composer require jippi/vault-php-sdk 10 | 11 | Usage 12 | ----- 13 | 14 | The simple way to use this SDK, is to instantiate the service factory: 15 | 16 | $sf = new Jippi\Vault\ServiceFactory(); 17 | 18 | Then, a service could be retrieved from this factory: 19 | 20 | $sys = $sf->get('sys'); 21 | 22 | All services methods follow the same convention: 23 | 24 | $response = $service->method($mandatoryArgument, $someOptions); 25 | 26 | * All API mandatory arguments are placed as first; 27 | * All API optional arguments are directly mapped from `$someOptions`; 28 | * All methods return raw guzzle response. 29 | 30 | Examples 31 | -------- 32 | 33 | The `examples` directory is a pure extract from my own CakePHP 3 app using the SDK - its crude, 34 | but should show the basics of the SDK 35 | 36 | Available services 37 | ------------------ 38 | 39 | * sys 40 | * data 41 | 42 | License 43 | ------- 44 | 45 | MIT 46 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jippi/vault-php-sdk", 3 | "description": "SDK to talk with vaultproject.io API", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Christian Winther", 8 | "email": "jippignu@gmail.com" 9 | } 10 | ], 11 | "require": { 12 | "guzzlehttp/guzzle": "~7.0", 13 | "psr/log": "~1.0 | ^2.0 | ^3.0" 14 | }, 15 | "autoload": { 16 | "psr-4": { 17 | "Jippi\\Vault\\": "src" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/shell.php: -------------------------------------------------------------------------------- 1 | [ 28 | 'project' => ['help' => 'Project name (folder)', 'required' => true] 29 | ] 30 | ]; 31 | 32 | return parent::getOptionParser() 33 | ->addSubCommand('all', [ 34 | 'help' => 'Same as "start" + "create" + "unseal" + "mounts"' 35 | ]) 36 | ->addSubCommand('start', [ 37 | 'help' => 'Start vault' 38 | ]) 39 | ->addSubCommand('stop', [ 40 | 'help' => 'Stop vault' 41 | ]) 42 | ->addSubCommand('restart', [ 43 | 'help' => 'Stop vault' 44 | ]) 45 | ->addSubCommand('reset', [ 46 | 'help' => 'Wipe Vault and all its configuration' 47 | ]) 48 | ->addSubCommand('status', [ 49 | 'help' => 'Get the status of the Vault instance' 50 | ]) 51 | ->addSubCommand('create', [ 52 | 'help' => 'Initialize Vault and store the secrets safely' 53 | ]) 54 | ->addSubCommand('env', [ 55 | 'help' => join(PHP_EOL, [ 56 | 'Export the ENV variables for using the vault CLI tool directly.', 57 | 'Please run: eval $(sudo bownty-cli vault env)' 58 | ]) 59 | ]) 60 | ->addSubCommand('mounts', [ 61 | 'help' => 'Create the mounts required for global operation' 62 | ]) 63 | ->addSubCommand('seal', [ 64 | 'help' => 'Seal the Vault' 65 | ]) 66 | ->addSubCommand('unseal', [ 67 | 'help' => 'Unseal the Vault' 68 | ]) 69 | ->addSubCommand('sync_vault_key', [ 70 | 'help' => 'Sync the Vault key into Consul (used by consul-template)' 71 | ]) 72 | ->addSubCommand('wait_for', [ 73 | 'help' => 'Wait for something' 74 | ]); 75 | } 76 | 77 | /** 78 | * Same as "start" + "create" + "unseal" + "mounts" 79 | * 80 | * @return void 81 | */ 82 | public function all() 83 | { 84 | $this->hr(); 85 | $this->out('Ensure Vault is running'); 86 | $this->hr(); 87 | $this->start(); 88 | $this->out(''); 89 | 90 | $this->hr(); 91 | $this->out('Ensure Vault is initialized'); 92 | $this->hr(); 93 | $this->create(); 94 | $this->out(''); 95 | 96 | $this->Vault->waitFor(['initialized' => true]); 97 | 98 | $this->hr(); 99 | $this->out('Ensure Vault is unsealed'); 100 | $this->hr(); 101 | $this->unseal(); 102 | $this->out(''); 103 | 104 | $this->Vault->waitFor(['sealed' => false, 'standby' => false]); 105 | $this->Vault->clear(); 106 | 107 | $this->hr(); 108 | $this->out('Writing mount-points'); 109 | $this->hr(); 110 | $this->mounts(); 111 | 112 | $this->hr(); 113 | $this->syncVaultKey(true); 114 | } 115 | 116 | public function syncVaultKey($hr = false) 117 | { 118 | $this->out('Sync Vault Key'); 119 | if ($hr) { 120 | $this->hr(); 121 | } 122 | 123 | $file = $this->Vault->getVaultKeyFile(); 124 | if (!file_exists($file)) { 125 | return $this->abort('Missing ~/.bownty_vault_keys file'); 126 | } 127 | 128 | $token = json_decode(file_get_contents($file), true)['root_token']; 129 | $this->Consul->kv()->put('consul/template/VAULT_TOKEN', (string)$token); 130 | $this->success('OK'); 131 | } 132 | 133 | /** 134 | * Start Vault 135 | * 136 | * @return void 137 | */ 138 | public function start() 139 | { 140 | $this->Vault->ensureRunning(); 141 | } 142 | 143 | /** 144 | * Stop Vault 145 | * 146 | * @return void 147 | */ 148 | public function stop() 149 | { 150 | $this->Vault->ensureStopped(); 151 | } 152 | 153 | /** 154 | * Restart Vault 155 | * 156 | * @return void 157 | */ 158 | public function restart() 159 | { 160 | $this->stop(); 161 | $this->start(); 162 | } 163 | 164 | /** 165 | * Reset Vault 166 | * 167 | * - Stop Vault 168 | * - Delete the vault/ keyprefix from Consul 169 | * - Start Vault 170 | * 171 | * @return void 172 | */ 173 | public function reset() 174 | { 175 | $this->Consul->ensureRunning(); 176 | $this->Vault->ensureStopped(); 177 | 178 | $this->Consul->kv()->delete('vault/', ['recurse' => true])->json(); 179 | 180 | $file = $this->Vault->getVaultKeyFile(); 181 | if (file_exists($file)) { 182 | unlink($file); 183 | } 184 | 185 | $this->Vault->ensureRunning(); 186 | } 187 | 188 | /** 189 | * Check the status of Vault 190 | * 191 | * @return boolean 192 | */ 193 | public function status() 194 | { 195 | $status = $this->Vault->sys()->status()->json(); 196 | if ($status['initialized'] !== true) { 197 | $this->warn('Vault is not initialized. Please run the "create" command'); 198 | return false; 199 | } 200 | 201 | if ($this->Vault->sys()->sealed()) { 202 | $this->warn('Vault is not unsealed. Please run the "unseal" command'); 203 | return true; 204 | } 205 | 206 | $this->success('Vault is initialized and unsealed'); 207 | return true; 208 | } 209 | 210 | /** 211 | * Create a new Vault instance 212 | * 213 | * - initialize vault and save the response in ~/.bownty_vault_keys 214 | * 215 | * @see https://www.vaultproject.io/docs/http/sys-init.html 216 | * @return void 217 | */ 218 | public function create() 219 | { 220 | if ($this->status()) { 221 | $this->success('Vault already initialized'); 222 | return false; 223 | } 224 | 225 | $response = $this->Vault->sys()->init([ 226 | 'secret_shares' => 5, 227 | 'secret_threshold' => 3 228 | ])->json(); 229 | 230 | $file = env('HOME') . '/.bownty_vault_keys'; 231 | file_put_contents($file, json_encode($response, JSON_PRETTY_PRINT)); 232 | 233 | $this->success('Vault is initialized, keys have been written to ' . $file); 234 | $this->success('Please run "unseal" to start working with Vault'); 235 | return true; 236 | } 237 | 238 | /** 239 | * Seal the vault 240 | * 241 | * @return void 242 | */ 243 | public function seal() 244 | { 245 | $this->Vault->seal(); 246 | $this->success('Vault has been sealed. Call "unseal" to unseal it again.'); 247 | } 248 | 249 | /** 250 | * Unseal the vault using the keys provided 251 | * 252 | * @return void 253 | */ 254 | public function unseal() 255 | { 256 | $this->Vault->unseal(); 257 | } 258 | 259 | /** 260 | * Export ENV variables for Vault CLI usage 261 | * 262 | * @return void 263 | */ 264 | public function env() 265 | { 266 | $token = $this->Vault->getVaultToken(); 267 | if (empty($token)) { 268 | return $this->abort('Token file does not exist - call "create" first'); 269 | } 270 | 271 | $this->out('export VAULT_ADDR="http://127.0.0.1:8200" VAULT_TOKEN="' . $token . '";'); 272 | $this->out('echo "You can now execute Vault commands using the \"vault\" CLI tool";'); 273 | } 274 | 275 | /** 276 | * Create the required mount points 277 | * 278 | * @return void 279 | */ 280 | public function mounts() 281 | { 282 | $config = yaml_parse(file_get_contents(CONFIG . 'vault.yml')); 283 | $mounts = $this->Vault->sys()->mounts()->json(); 284 | 285 | foreach ($config['secret_backends'] as $backend) { 286 | $this->out('Backend: ' . $backend['path']); 287 | 288 | if (!array_key_exists($backend['path'] . DS, $mounts)) { 289 | $this->info(' Creating ...'); 290 | $resp = $this->Vault->sys()->createMount($backend['path'], [ 291 | 'type' => $backend['type'], 292 | 'description' => isset($backend['description']) ? $backend['description'] : '' 293 | ]); 294 | } else { 295 | $this->success(' Already exist'); 296 | } 297 | 298 | switch ($backend['type']) { 299 | case 'mysql': 300 | $this->_mysqlBackend($backend); 301 | break; 302 | 303 | default: 304 | $this->_secretBackend($backend); 305 | break; 306 | } 307 | 308 | $this->out(''); 309 | } 310 | } 311 | 312 | /** 313 | * Specific handler for "secret" secret backends 314 | * 315 | * @param array $backend 316 | * @return void 317 | */ 318 | protected function _secretBackend($backend) 319 | { 320 | $data = $this->Vault->data(); 321 | foreach ($backend['secrets'] as $secret) { 322 | $this->info(' ' . $secret['path']); 323 | $data->write('secret/' . $secret['path'], $secret['payload']); 324 | } 325 | } 326 | 327 | /** 328 | * Specific handler for MySQL secret backends 329 | * 330 | * @param array $backend 331 | * @return void 332 | */ 333 | protected function _mysqlBackend(array $backend) 334 | { 335 | if (empty($backend['config']['ini_file'])) { 336 | return $this->abort('Missing config.ini_file key for mysql backend'); 337 | } 338 | if (!is_file($backend['config']['ini_file'])) { 339 | return $this->abort('ini file do not exist (' . $backend['config']['ini_file'] . ')'); 340 | } 341 | 342 | $ini = parse_ini_file($backend['config']['ini_file']); 343 | $connection_url = sprintf('%s:%s@unix(/var/run/mysqld/mysqld.sock)/', $ini['user'], $ini['password']); 344 | 345 | $data = $this->Vault->data(); 346 | $this->info(' Writing connection string'); 347 | $max_open_connections = 10; 348 | $data->write('mysql/config/connection', compact('connection_url', 'max_open_connections')); 349 | 350 | $this->info(' Writing lease configuration'); 351 | $data->write('mysql/config/lease', ['lease' => '1h', 'lease_max' => '24h']); 352 | 353 | $this->info(' Writing role configuration(s)'); 354 | foreach ($backend['roles'] as $role) { 355 | $this->info(' ' . $role['name']); 356 | $data->write('mysql/roles/' . $role['name'], ['sql' => join(';', $role['sql'])]); 357 | } 358 | 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /examples/task.php: -------------------------------------------------------------------------------- 1 | factory()->get('sys'); 29 | } 30 | 31 | /** 32 | * Get the "data" service from the Vault Factory 33 | * 34 | * @return Jippi\Vault\Services\Data 35 | */ 36 | public function data() 37 | { 38 | return $this->factory()->get('data'); 39 | } 40 | 41 | /** 42 | * Get the Vault Service Factory 43 | * 44 | * If the ~/.bownty_vault_keys file exist, the X-Vault-Token header 45 | * will automatically be sent in all requests 46 | * 47 | * @return Jippi\Vault\ServiceFactory 48 | */ 49 | public function factory() 50 | { 51 | if (!$this->factory) { 52 | $options = []; 53 | $options['headers']['X-Vault-Token'] = $this->getVaultToken(); 54 | $this->factory = new VaultFactory($options); 55 | } 56 | 57 | return $this->factory; 58 | } 59 | 60 | public function getVaultToken() 61 | { 62 | $file = $this->getVaultKeyFile(); 63 | if (!file_exists($file)) { 64 | return null; 65 | } 66 | return json_decode(file_get_contents($file), true)['root_token']; 67 | } 68 | 69 | /** 70 | * Return the path to the Vault Key file 71 | * 72 | * @return string 73 | */ 74 | public function getVaultKeyFile() 75 | { 76 | return env('HOME') . '/.bownty_vault_keys'; 77 | } 78 | 79 | /** 80 | * Wait for Vault health to return the key/value $pairs specified 81 | * 82 | * @see https://www.vaultproject.io/docs/http/sys-health.html 83 | * @param array $pairs Key/Value to expect health to return 84 | * @param array $arguments Additional arguments for health endpoint 85 | * @return boolean 86 | */ 87 | public function waitFor(array $pairs = []) 88 | { 89 | $allowedPairs = ['initialized' => null, 'sealed' => null, 'standby' => null]; 90 | $pairs = array_intersect_key($pairs, $allowedPairs); 91 | 92 | $this->out('Waiting for health returning ' . json_encode($pairs) . ': ', 0); 93 | 94 | $count = 0; 95 | while (true) { 96 | try { 97 | $res = $this->sys()->health(['standbyok' => 1, 'standbycode' => 200, 'sealedcode' => 200])->json(); 98 | $this->info('.', 0); 99 | } catch (ServerException $e) { 100 | $res = $e->response()->json(); 101 | $this->err('.', 0); 102 | } 103 | 104 | if (!array_diff_assoc($pairs, $res)) { 105 | $this->success(' Done!'); 106 | return true; 107 | } 108 | 109 | $count++; 110 | if ($count > 120) { 111 | $this->err(' Failed after 120 attempts...'); 112 | return false; 113 | } 114 | 115 | sleep(1); 116 | } 117 | } 118 | 119 | /** 120 | * Unseal the vault using the keys provided 121 | * 122 | * @return void 123 | */ 124 | public function unseal() 125 | { 126 | $file = $this->getVaultKeyFile(); 127 | 128 | if (!file_exists($file)) { 129 | return $this->abort($file . ' does not exist - call "create" first'); 130 | } 131 | 132 | $sysService = $this->sys(); 133 | 134 | $sealed = $sysService->sealed(); 135 | if (!$sealed) { 136 | return $this->success('Vault is unsealed'); 137 | } 138 | 139 | $content = json_decode(file_get_contents($file), true); 140 | 141 | while ($sysService->sealed()) { 142 | $this->info('Unsealing with key ...'); 143 | $sysService->unseal(['key' => array_pop($content['keys'])]); 144 | } 145 | 146 | $this->success('Vault has been unsealed successfully'); 147 | } 148 | 149 | /** 150 | * Seal the Vault 151 | * 152 | * @return boolean 153 | */ 154 | public function seal() 155 | { 156 | $this->sys()->seal(); 157 | } 158 | 159 | /** 160 | * Clear the factory instance 161 | * 162 | * @return void 163 | */ 164 | public function clear() 165 | { 166 | $this->factory = null; 167 | } 168 | 169 | } 170 | -------------------------------------------------------------------------------- /src/Client.php: -------------------------------------------------------------------------------- 1 | $base_uri, 34 | 'http_errors' => false, 35 | 'headers' => [ 36 | 'User-Agent' => 'Vault-PHP-SDK/1.0', 37 | 'Content-Type' => 'application/json', 38 | ], 39 | ], $options); 40 | 41 | $this->client = $client ?: new GuzzleClient($options); 42 | $this->logger = $logger ?: new NullLogger(); 43 | } 44 | 45 | public function get($url = null, array $options = []) 46 | { 47 | return $this->send(new Request('GET', $this->getUrl($url)), $options); 48 | } 49 | 50 | public function head($url, array $options = []) 51 | { 52 | return $this->send(new Request('HEAD', $this->getUrl($url)), $options); 53 | } 54 | 55 | public function delete($url, array $options = []) 56 | { 57 | return $this->send(new Request('DELETE', $this->getUrl($url)), $options); 58 | } 59 | 60 | public function put($url, array $options = []) 61 | { 62 | return $this->send(new Request('PUT', $this->getUrl($url)), $options); 63 | } 64 | 65 | public function patch($url, array $options = []) 66 | { 67 | return $this->send(new Request('PATCH', $this->getUrl($url)), $options); 68 | } 69 | 70 | public function post($url, array $options = []) 71 | { 72 | return $this->send(new Request('POST', $this->getUrl($url)), $options); 73 | } 74 | 75 | public function options($url, array $options = []) 76 | { 77 | return $this->send(new Request('OPTIONS', $this->getUrl($url)), $options); 78 | } 79 | 80 | public function list($url, array $options = []) 81 | { 82 | return $this->send(new Request('LIST', $this->getUrl($url)), $options); 83 | } 84 | 85 | public function send(RequestInterface $request, $options = []) 86 | { 87 | $this->logger->info(sprintf('%s "%s"', $request->getMethod(), $request->getUri())); 88 | $this->logger->debug(sprintf("Request:\n%s\n%s\n%s", $request->getUri(), $request->getMethod(), json_encode($request->getHeaders()))); 89 | 90 | try { 91 | $response = $this->client->send($request, $options); 92 | } catch (TransferException $e) { 93 | $message = sprintf('Something went wrong when calling vault (%s).', $e->getMessage()); 94 | 95 | $this->logger->error($message); 96 | 97 | throw new ServerException($message); 98 | } 99 | 100 | $this->logger->debug(sprintf("Response:\n%s", (string) $response->getBody())); 101 | 102 | if (400 <= $response->getStatusCode()) { 103 | $message = sprintf('Something went wrong when calling vault (%s - %s).', $response->getStatusCode(), $response->getReasonPhrase()); 104 | 105 | $this->logger->error($message); 106 | $this->logger->debug(sprintf("Response:\n%s\n%s\n%s", $response->getStatusCode(), json_encode($response->getHeaders()), $response->getBody()->getContents())); 107 | 108 | $message .= "\n" . (string) $response->getBody(); 109 | if (500 <= $response->getStatusCode()) { 110 | throw new ServerException($message, $response->getStatusCode(), $response); 111 | } 112 | 113 | throw new ClientException($message, $response->getStatusCode(), $response); 114 | } 115 | 116 | $response->getBody()->rewind(); 117 | 118 | return $response; 119 | } 120 | 121 | private function getUrl($url = null) 122 | { 123 | return is_null($url) ?: self::VERSION . $url; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/Exception/ClientException.php: -------------------------------------------------------------------------------- 1 | response = $response; 15 | } 16 | 17 | public function response() 18 | { 19 | return $this->response; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/Exception/ServerException.php: -------------------------------------------------------------------------------- 1 | response = $response; 14 | } 15 | 16 | public function response() 17 | { 18 | return $this->response; 19 | } 20 | 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/Exception/VaultExceptionInterface.php: -------------------------------------------------------------------------------- 1 | 'Jippi\Vault\Services\Sys', 11 | 'data' => 'Jippi\Vault\Services\Data', 12 | 'auth/token' => 'Jippi\Vault\Services\Auth\Token', 13 | 'auth/approle'=>'Jippi\Vault\Services\Auth\AppRole', 14 | 'transit' => 'Jippi\Vault\Services\Transit', 15 | ]; 16 | 17 | protected $client; 18 | 19 | public function __construct(array $options = array(), LoggerInterface $logger = null, GuzzleClient $guzzleClient = null) 20 | { 21 | $this->client = new Client($options, $logger, $guzzleClient); 22 | } 23 | 24 | public function get($service) 25 | { 26 | if (!array_key_exists($service, self::$services)) { 27 | throw new \InvalidArgumentException(sprintf('The service "%s" is not available. Pick one among "%s".', $service, implode('", "', array_keys(self::$services)))); 28 | } 29 | 30 | $class = self::$services[$service]; 31 | 32 | return new $class($this->client); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Services/Auth/AppRole.php: -------------------------------------------------------------------------------- 1 | client = $client ?: new Client(); 27 | } 28 | 29 | /** 30 | * Issues a Vault token based on the presented credentials. 31 | * @param string $roleId The role_id in Vault 32 | * @param string $secretId The secret_id in Vault 33 | * @return mixed 34 | */ 35 | public function login(string $roleId, string $secretId) 36 | { 37 | $body = ['role_id' => $roleId, 'secret_id' => $secretId]; 38 | $params = [ 39 | 'body' => json_encode($body) 40 | ]; 41 | return \GuzzleHttp\json_decode($this->client->post('/auth/approle/login', $params)->getBody()); 42 | } 43 | 44 | /** 45 | * List the AppRoles defined in Vault 46 | * @return mixed 47 | */ 48 | public function listRoles() 49 | { 50 | return \GuzzleHttp\json_decode($this->client->list('/auth/approle/role')->getBody()); 51 | } 52 | 53 | /** 54 | * Get the ID for the specified AppRole 55 | * @param string $roleName 56 | * @return mixed 57 | */ 58 | public function getRoleId(string $roleName) 59 | { 60 | return \GuzzleHttp\json_decode($this->client->get("/auth/approle/role/$roleName/role-id")->getBody()); 61 | } 62 | 63 | 64 | } -------------------------------------------------------------------------------- /src/Services/Auth/Token.php: -------------------------------------------------------------------------------- 1 | client = $client ?: new Client(); 28 | } 29 | 30 | /** 31 | * Creates a new token. 32 | * 33 | * Certain options are only available when called by a root token. 34 | * 35 | * If used via the /auth/token/create-orphan endpoint, a root token is not required to create an orphan token 36 | * (otherwise set with the no_parent option). If used with a role name in the path, the token will be created 37 | * against the specified role name; this may override options set during this call. 38 | * 39 | * @see https://www.vaultproject.io/docs/auth/token.html 40 | * @param array $body 41 | * @return mixed 42 | */ 43 | public function create(array $body = []) 44 | { 45 | $body = OptionsResolver::resolve($body, [ 46 | 'id', 'policies', 'meta', 'no_parent', 'no_default_policy', 47 | 'renewable', 'ttl', 'explicit_max_ttl', 'display_name', 'num_uses' 48 | ]); 49 | 50 | $params = [ 51 | 'body' => json_encode($body) 52 | ]; 53 | 54 | return $this->client->post('/auth/token/create', $params)->json(); 55 | } 56 | 57 | /** 58 | * Returns information about the current client token. 59 | * 60 | * @see https://www.vaultproject.io/docs/auth/token.html 61 | * @return mixed 62 | */ 63 | public function lookupSelf() 64 | { 65 | return $this->client->get('/auth/token/lookup-self'); 66 | } 67 | 68 | /** 69 | * Returns information about the client token provided in the request path 70 | * 71 | * @see https://www.vaultproject.io/docs/auth/token.html 72 | * @param string $token 73 | * @return mixed 74 | */ 75 | public function lookup($token) 76 | { 77 | return $this->client->get('/auth/token/lookup/' . $token); 78 | } 79 | 80 | /** 81 | * Renews a lease associated with the calling token. 82 | * 83 | * This is used to prevent the expiration of a token, and the automatic revocation of it. 84 | * 85 | * Token renewal is possible only if there is a lease associated with it. 86 | * 87 | * @see https://www.vaultproject.io/docs/auth/token.html 88 | * @param array $body 89 | * @return mixed 90 | */ 91 | public function renewSelf(array $body = []) 92 | { 93 | $body = OptionsResolver::resolve($body, ['increment']); 94 | $params = ['body' => json_encode($body)]; 95 | return $this->client->post('/auth/token/renew-self', $params); 96 | } 97 | 98 | /** 99 | * Renews a lease associated with a token. 100 | * 101 | * This is used to prevent the expiration of a token, and the automatic revocation of it. 102 | * 103 | * Token renewal is possible only if there is a lease associated with it. 104 | * 105 | * @see https://www.vaultproject.io/docs/auth/token.html 106 | * @param array $body 107 | * @return mixed 108 | */ 109 | public function renew(array $body = []) 110 | { 111 | $body = OptionsResolver::resolve($body, ['token', 'increment']); 112 | $params = ['body' => json_encode($body)]; 113 | return $this->client->post('/auth/token/renew', $params); 114 | } 115 | 116 | /** 117 | * Revokes a token and all child tokens. 118 | * 119 | * When the token is revoked, all secrets generated with it are also revoked. 120 | * 121 | * @see https://www.vaultproject.io/docs/auth/token.html 122 | * @param array $body 123 | * @return mixed 124 | */ 125 | public function revoke(array $body = []) 126 | { 127 | $body = OptionsResolver::resolve($body, ['token']); 128 | $params = ['body' => json_encode($body)]; 129 | return $this->client->post('/auth/token/revoke', $params); 130 | } 131 | 132 | /** 133 | * Revokes a token and all child tokens. 134 | * 135 | * When the token is revoked, all secrets generated with it are also revoked. 136 | * 137 | * @see https://www.vaultproject.io/docs/auth/token.html 138 | * @param array $body 139 | * @return mixed 140 | */ 141 | public function revokeSelf(array $body = []) 142 | { 143 | return $this->client->post('/auth/token/revoke-self'); 144 | } 145 | 146 | /** 147 | * Revokes a token but not its child tokens. 148 | * 149 | * When the token is revoked, all secrets generated with it are also revoked. 150 | * 151 | * All child tokens are orphaned, but can be revoked sub-sequently using /auth/token/revoke/. 152 | * 153 | * This is a root-protected endpoint. 154 | * 155 | * @see https://www.vaultproject.io/docs/auth/token.html 156 | * @param array $body 157 | * @return mixed 158 | */ 159 | public function revokeOrphan(array $body = []) 160 | { 161 | return $this->client->post('/auth/token/revoke-orphan'); 162 | } 163 | 164 | /** 165 | * Deletes the named role. 166 | * 167 | * @see https://www.vaultproject.io/docs/auth/token.html 168 | * @param string $role 169 | * @return mixed 170 | */ 171 | public function deleteRole(string $role) 172 | { 173 | return $this->client->delete('/auth/token/roles/' . $role); 174 | } 175 | 176 | /** 177 | * Fetches the named role configuration 178 | * 179 | * @see https://www.vaultproject.io/docs/auth/token.html 180 | * @param string $role 181 | * @return mixed 182 | */ 183 | public function getRole(string $role) 184 | { 185 | return $this->client->get('/auth/token/roles/' . $role); 186 | } 187 | 188 | /** 189 | * Lists available roles. 190 | * 191 | * @see https://www.vaultproject.io/docs/auth/token.html 192 | * @return mixed 193 | */ 194 | public function listRoles() 195 | { 196 | return $this->client->get('/token/roles?list=true'); 197 | } 198 | 199 | /** 200 | * Creates (or replaces) the named role. 201 | * 202 | * Roles enforce specific behavior when creating tokens that allow token functionality that is otherwise not 203 | * available or would require sudo/root privileges to access. 204 | * 205 | * Role parameters, when set, override any provided options to the create endpoints. 206 | * 207 | * The role name is also included in the token path, allowing all tokens created against a role to be revoked 208 | * using the sys/revoke-prefix endpoint. 209 | * 210 | * @see https://www.vaultproject.io/docs/auth/token.html 211 | * @return mixed 212 | */ 213 | public function createRole(string $role, array $body = []) 214 | { 215 | $body = OptionsResolver::resolve($body, ['allowed_policies', 'orphan', 'period', 'renewable', 'path_suffix', 'explicit_max_ttl']); 216 | $params = ['body' => json_encode($body)]; 217 | return $this->client->post('/auth/token/roles/' . $role, $params); 218 | } 219 | 220 | } 221 | -------------------------------------------------------------------------------- /src/Services/Data.php: -------------------------------------------------------------------------------- 1 | client = $client ?: new Client(); 28 | } 29 | 30 | public function write($path, $body) 31 | { 32 | $params = [ 33 | 'body' => json_encode($body) 34 | ]; 35 | 36 | return $this->client->put('/' . $path, $params); 37 | } 38 | 39 | public function get($path) 40 | { 41 | return $this->client->get('/' . $path); 42 | } 43 | 44 | public function delete($path) 45 | { 46 | return $this->client->delete('/' . $path); 47 | } 48 | 49 | public function list($path) 50 | { 51 | return $this->client->list('/' . $path); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Services/Sys.php: -------------------------------------------------------------------------------- 1 | client = $client ?: new Client(); 28 | } 29 | 30 | /** 31 | * Return the initialization status of a Vault. 32 | * 33 | * @see https://www.vaultproject.io/docs/http/sys-init.html 34 | * @return mixed 35 | */ 36 | public function status() 37 | { 38 | return $this->client->get('/sys/init'); 39 | } 40 | 41 | /** 42 | * Initializes a new Vault. 43 | * 44 | * The Vault must've not been previously initialized 45 | * 46 | * @see https://www.vaultproject.io/docs/http/sys-init.html 47 | * @return mixed 48 | */ 49 | public function init(array $body = []) 50 | { 51 | $body = OptionsResolver::resolve($body, ['secret_shares', 'secret_threshold', 'pgp_keys']); 52 | $body = OptionsResolver::required($body, ['secret_shares', 'secret_threshold']); 53 | 54 | $params = [ 55 | 'body' => json_encode($body) 56 | ]; 57 | 58 | return $this->client->put('/sys/init', $params); 59 | } 60 | 61 | /** 62 | * Returns the seal status of the Vault. 63 | * 64 | * This is an unauthenticated endpoint 65 | * 66 | * @see https://www.vaultproject.io/docs/http/sys-seal-status.html 67 | * @return mixed 68 | */ 69 | public function sealStatus() 70 | { 71 | return $this->client->get('/sys/seal-status'); 72 | } 73 | 74 | /** 75 | * Seals the Vault. 76 | * 77 | * In HA mode, only an active node can be sealed. 78 | * 79 | * Standby nodes should be restarted to get the same effect. 80 | * 81 | * Requires a token with root policy or sudo capability on the path. 82 | * 83 | * @see https://www.vaultproject.io/docs/http/sys-seal.html 84 | * @return mixed 85 | */ 86 | public function seal() 87 | { 88 | return $this->client->put('/sys/seal'); 89 | } 90 | 91 | /** 92 | * Utility method for checking if the vault is sealed or not 93 | * 94 | * @return boolean 95 | */ 96 | public function sealed() 97 | { 98 | return json_decode($this->sealStatus()->getBody(), true)['sealed']; 99 | } 100 | 101 | /** 102 | * Utility method for checking if the vault is unsealed or not 103 | * 104 | * @return boolean 105 | */ 106 | public function unsealed() 107 | { 108 | return !json_decode($this->sealStatus()->getBody(), true)['sealed']; 109 | } 110 | 111 | /** 112 | * Enter a single master key share to progress the unsealing of the Vault. 113 | * 114 | * If the threshold number of master key shares is reached, Vault will attempt to unseal the Vault. 115 | * 116 | * Otherwise, this API must be called multiple times until that threshold is met. 117 | * 118 | * Either the key or reset parameter must be provided; if both are provided, reset takes precedence. 119 | * 120 | * @see https://www.vaultproject.io/docs/http/sys-unseal.html 121 | * @param array $body 122 | * @return mixed 123 | */ 124 | public function unseal(array $body = []) 125 | { 126 | $body = OptionsResolver::resolve($body, ['key', 'reset']); 127 | 128 | $params = [ 129 | 'body' => json_encode($body) 130 | ]; 131 | 132 | return $this->client->put('/sys/unseal', $params); 133 | } 134 | 135 | /** 136 | * Lists all the mounted secret backends. 137 | * 138 | * default_lease_ttl or max_lease_ttl values of 0 mean that the system defaults are used by this backend. 139 | * 140 | * @see https://www.vaultproject.io/docs/http/sys-mounts.html 141 | * @return mixed 142 | */ 143 | public function mounts() 144 | { 145 | return $this->client->get('/sys/mounts'); 146 | } 147 | 148 | /** 149 | * Mount a new secret backend to the mount point in the URL. 150 | * 151 | * @param string $name 152 | * @param array $body 153 | * @return mixed 154 | */ 155 | public function createMount($name, array $body) 156 | { 157 | $body = OptionsResolver::resolve($body, ['type', 'description', 'config']); 158 | 159 | $params = [ 160 | 'body' => json_encode($body) 161 | ]; 162 | 163 | return $this->client->post('/sys/mounts/' . $name, $params); 164 | } 165 | 166 | /** 167 | * Unmount the mount point specified in the URL. 168 | * 169 | * @see https://www.vaultproject.io/docs/http/sys-mounts.html 170 | * @param string $name 171 | * @return mixed 172 | */ 173 | public function deleteMount($name) 174 | { 175 | return $this->client->delete('/sys/mounts/' . $name); 176 | } 177 | 178 | /** 179 | * Remount an already-mounted backend to a new mount point. 180 | * 181 | * @see https://www.vaultproject.io/docs/http/sys-remount.html 182 | * @param string $from 183 | * @param string $to 184 | * @return mixed 185 | */ 186 | public function remount($from, $to) 187 | { 188 | $body = compact('from', 'to'); 189 | 190 | $params = [ 191 | 'body' => json_encode($body) 192 | ]; 193 | 194 | return $this->client->post('/sys/remount', $params); 195 | } 196 | 197 | /** 198 | * List or change the given mount's configuration. 199 | * 200 | * if `$body` is not empty, a POST to update a mount tune is assumed. 201 | * 202 | * Unlike the mounts endpoint, this will return the current time in seconds for each TTL, 203 | * which may be the system default or a mount-specific value. 204 | * 205 | * @param string $name 206 | * @param array $body 207 | * @return mixed 208 | */ 209 | public function tuneMount($name, array $body = []) 210 | { 211 | if (empty($body)) { 212 | return $this->client->get('/sys/mounts/' . $name . '/tune'); 213 | } 214 | 215 | $params = [ 216 | 'body' => json_encode(OptionsResolver::resolve($body, ['default_lease_ttl', 'max_lease_ttl'])) 217 | ]; 218 | 219 | return $this->client->post('/sys/mounts/' . $name . '/tune', $params); 220 | } 221 | 222 | /** 223 | * Lists all the available policies 224 | * 225 | * @see https://www.vaultproject.io/docs/http/sys-policy.html 226 | * @return mixed 227 | */ 228 | public function policies() 229 | { 230 | return $this->client->get('/sys/policy'); 231 | } 232 | 233 | /** 234 | * Retrieve the rules for the named policy. 235 | * 236 | * @see https://www.vaultproject.io/docs/http/sys-policy.html 237 | * @param string $name 238 | * @return mixed 239 | */ 240 | public function policy($name) 241 | { 242 | return $this->client->get('/sys/policy/' . $name); 243 | } 244 | 245 | /** 246 | * Add or update a policy. 247 | * 248 | * Once a policy is updated, it takes effect immediately to all associated users. 249 | * 250 | * @see https://www.vaultproject.io/docs/http/sys-policy.html 251 | * @param string $name 252 | * @param array $body 253 | * @return mixed 254 | */ 255 | public function putPolicy($name, array $body) 256 | { 257 | $body = OptionsResolver::resolve($body, ['policy']); 258 | 259 | $params = [ 260 | 'body' => json_encode($body) 261 | ]; 262 | 263 | return $this->client->put('/sys/policy/' . $name, $params); 264 | } 265 | 266 | /** 267 | * Delete the policy with the given name. 268 | * 269 | * This will immediately affect all associated users 270 | * 271 | * @see https://www.vaultproject.io/docs/http/sys-policy.html 272 | * @param string $name 273 | * @return mixed 274 | */ 275 | public function deletePolicy($name) 276 | { 277 | return $this->client->delete('/sys/policy/' . $name); 278 | } 279 | 280 | /** 281 | * Returns the capabilities of the token on the given path. 282 | * 283 | * If token is empty, 'capabilities-self' is assumed 284 | * 285 | * @see https://www.vaultproject.io/docs/http/sys-capabilities.html 286 | * @see https://www.vaultproject.io/docs/http/sys-capabilities-self.html 287 | * @param string $path 288 | * @param string|null $token 289 | * @return mixed 290 | */ 291 | public function capabilities($path, $token = null) 292 | { 293 | $params = [ 294 | 'body' => json_encode(array_filter(compact('token', 'path'))) 295 | ]; 296 | 297 | if (empty($token)) { 298 | return $this->client->post('/sys/capabilities-self', $params); 299 | } 300 | 301 | return $this->client->post('/sys/capabilities', $params); 302 | } 303 | 304 | /** 305 | * Renew a secret, requesting to extend the lease 306 | * 307 | * @see https://www.vaultproject.io/docs/http/sys-renew.html 308 | * @param string $leaseId 309 | * @param string|null $increment 310 | * @return mixed 311 | */ 312 | public function renew($leaseId, $increment = null) 313 | { 314 | $params = [ 315 | 'body' => json_encode(array_filter(compact('increment'))) 316 | ]; 317 | 318 | return $this->client->put('/sys/renew/' . $leaseId, $params); 319 | } 320 | 321 | /** 322 | * revoke a secret immediately 323 | * 324 | * @see https://www.vaultproject.io/docs/http/sys-revoke.html 325 | * @param string $leaseId 326 | * @return mixed 327 | */ 328 | public function revoke($leaseId) 329 | { 330 | return $this->client->put('/sys/revoke/' . $leaseId); 331 | } 332 | 333 | /** 334 | * Revoke all secrets generated under a given prefix immediately 335 | * 336 | * @see https://www.vaultproject.io/docs/http/sys-revoke-prefix.html 337 | * @param string $prefix 338 | * @return mixed 339 | */ 340 | public function revokePrefix($prefix) 341 | { 342 | return $this->client->put('/sys/revoke-prefix/' . $prefix); 343 | } 344 | 345 | /** 346 | * Revoke all secrets generated under a given prefix immediately. 347 | * 348 | * Unlike /sys/revoke-prefix, this path ignores backend errors encountered during revocation. 349 | * 350 | * This is potentially very dangerous and should only be used in specific emergency situations where errors 351 | * in the backend or the connected backend service prevent normal revocation. 352 | * 353 | * By ignoring these errors, Vault abdicates responsibility for ensuring that the issued credentials 354 | * or secrets are properly revoked and/or cleaned up. 355 | * 356 | * Access to this endpoint should be tightly controlled. 357 | * 358 | * @see https://www.vaultproject.io/docs/http/sys-revoke-force.html 359 | * @param string $prefix 360 | * @return mixed 361 | */ 362 | public function revokeForce($prefix) 363 | { 364 | return $this->client->put('/sys/revoke-force/' . $prefix); 365 | } 366 | 367 | /** 368 | * Returns the high availability status and current leader instance of Vault. 369 | * 370 | * @see https://www.vaultproject.io/docs/http/sys-leader.html 371 | * @return mixed 372 | */ 373 | public function leader() 374 | { 375 | return $this->client->get('/sys/leader'); 376 | } 377 | 378 | /** 379 | * Forces the node to give up active status. 380 | * 381 | * If the node does not have active status, this endpoint does nothing. 382 | * 383 | * Note that the node will sleep for ten seconds before attempting to grab the active lock again, 384 | * but if no standby nodes grab the active lock in the interim, the same node may become the active node again. 385 | * 386 | * Requires a token with root policy or sudo capability on the path. 387 | * 388 | * @see https://www.vaultproject.io/docs/http/sys-step-down.html 389 | * @return mixed 390 | */ 391 | public function stepDown() 392 | { 393 | return $this->client->get('/sys/step-down'); 394 | } 395 | 396 | /** 397 | * Returns information about the current encryption key used by Vault. 398 | * 399 | * @see https://www.vaultproject.io/docs/http/sys-key-status.html 400 | * @return mixed 401 | */ 402 | public function keyStatus() 403 | { 404 | return $this->client->get('/sys/key-status'); 405 | } 406 | 407 | /** 408 | * Trigger a rotation of the backend encryption key. 409 | * 410 | * This is the key that is used to encrypt data written to the storage backend, and is not provided to operators. 411 | * 412 | * This operation is done online. 413 | * 414 | * Future values are encrypted with the new key, while old values are decrypted with previous encryption keys. 415 | * 416 | * @see https://www.vaultproject.io/docs/http/sys-rotate.html 417 | * @return mixed 418 | */ 419 | public function rotate() 420 | { 421 | return $this->client->put('/sys/rotate'); 422 | } 423 | 424 | /** 425 | * Reads the value of the key at the given path. 426 | * 427 | * This is the raw path in the storage backend and not the logical path that is exposed via the mount system. 428 | * 429 | * If `$value` is empty, GET is assumed - otherwise PUT. 430 | * 431 | * @see https://www.vaultproject.io/docs/http/sys-raw.html 432 | * @param string $path 433 | * @param string|null $value 434 | * @return mixed 435 | */ 436 | public function raw($path, $value = null) 437 | { 438 | if ($value === null) { 439 | return $this->client->get('/sys/raw/' . $path); 440 | } 441 | 442 | $params = [ 443 | 'body' => json_encode(compact('value')) 444 | ]; 445 | 446 | return $this->client->put('/sys/raw/' . $path, $params); 447 | } 448 | 449 | /** 450 | * Delete the key with given path. 451 | * 452 | * This is the raw path in the storage backend and not the logical path that is exposed via the mount system 453 | * 454 | * @see https://www.vaultproject.io/docs/http/sys-raw.html 455 | * @param string $path 456 | * @return mixed 457 | */ 458 | public function deleteRaw($path) 459 | { 460 | return $this->client->delete('/sys/raw/' . $path); 461 | } 462 | 463 | /** 464 | * Returns the health status of Vault. 465 | * 466 | * This matches the semantics of a Consul HTTP health check and provides a simple way to monitor the health of a Vault instance 467 | * 468 | * @return mixed 469 | */ 470 | public function health(array $arguments = []) 471 | { 472 | $url = '/sys/health?' . http_build_query($arguments); 473 | return $this->client->get($url); 474 | } 475 | 476 | } 477 | -------------------------------------------------------------------------------- /src/Services/Transit.php: -------------------------------------------------------------------------------- 1 | client = $client ?: new Client(); 28 | } 29 | 30 | public function getKey($keyName) 31 | { 32 | return $this->client->get('/v1/transit/keys/' . urlencode($keyName)); 33 | } 34 | 35 | public function createKey($keyName, array $body = []) 36 | { 37 | $body = OptionsResolver::resolve($body, ['type', 'derived', 'convergent_encryption']); 38 | $body = OptionsResolver::required($body, ['type']); 39 | 40 | $params = [ 41 | 'body' => json_encode($body) 42 | ]; 43 | 44 | return $this->client->put('/transit/keys/' . urlencode($keyName), $params); 45 | } 46 | 47 | public function rotateKey($keyName) 48 | { 49 | return $this->client->post('/transit/keys/' . urlencode($keyName) . '/rotate'); 50 | } 51 | 52 | public function encrypt($keyName, $plainText, array $body = []) 53 | { 54 | $body = OptionsResolver::resolve($body, ['context', 'nonce']); 55 | $body['plaintext'] = base64_encode($plainText); 56 | 57 | $params = [ 58 | 'body' => json_encode($body) 59 | ]; 60 | 61 | $response = $this->client->post('/transit/encrypt/' . urlencode($keyName), $params); 62 | return json_decode($response->getBody(), true)['data']['ciphertext']; 63 | } 64 | 65 | public function decrypt($keyName, $cipherText, array $body = []) 66 | { 67 | $body = OptionsResolver::resolve($body, ['context', 'nonce']); 68 | $body['ciphertext'] = $cipherText; 69 | 70 | $params = [ 71 | 'body' => json_encode($body) 72 | ]; 73 | 74 | $response = $this->client->post('/transit/decrypt/' . urlencode($keyName), $params); 75 | return base64_decode(json_decode($response->getBody(), true)['data']['plaintext']); 76 | } 77 | 78 | public function rewrap($keyName, $cipherText, array $body = []) 79 | { 80 | $body = OptionsResolver::resolve($body, ['context', 'nonce']); 81 | $body['ciphertext'] = $cipherText; 82 | 83 | $params = [ 84 | 'body' => json_encode($body) 85 | ]; 86 | 87 | $response = $this->client->post('/transit/rewrap/' . urlencode($keyName), $params); 88 | return json_decode($response->getBody(), true)['data']['ciphertext']; 89 | } 90 | } --------------------------------------------------------------------------------