├── src ├── Bundle │ └── SecretaryBundle │ │ ├── .gitignore │ │ ├── SecretaryBundle.php │ │ ├── services.yaml │ │ ├── LICENSE │ │ ├── composer.json │ │ ├── README.md │ │ ├── Test.php │ │ ├── EnvVar │ │ └── EnvVarProcessor.php │ │ └── DependencyInjection │ │ ├── Configuration.php │ │ └── SecretaryExtension.php ├── Core │ ├── Exception │ │ ├── ValueNotSupportedException.php │ │ └── SecretNotFoundException.php │ ├── Adapter │ │ ├── AbstractAdapter.php │ │ └── AdapterInterface.php │ ├── LICENSE │ ├── Helper │ │ └── ArrayHelper.php │ ├── composer.json │ ├── Manager.php │ ├── Secret.php │ ├── Tests │ │ └── ManagerTest.php │ └── README.md └── Adapter │ ├── AWS │ └── SecretsManager │ │ ├── README.md │ │ ├── LICENSE │ │ ├── composer.json │ │ └── AWSSecretsManagerAdapter.php │ ├── Local │ └── JSONFile │ │ ├── README.md │ │ ├── composer.json │ │ ├── LICENSE │ │ └── LocalJSONFileAdapter.php │ ├── Chain │ ├── composer.json │ ├── LICENSE │ └── ChainAdapter.php │ ├── Cache │ ├── PSR16Cache │ │ ├── LICENSE │ │ ├── composer.json │ │ └── PSR16CacheAdapter.php │ └── PSR6Cache │ │ ├── LICENSE │ │ ├── composer.json │ │ └── PSR6CacheAdapter.php │ └── Hashicorp │ └── Vault │ ├── LICENSE │ ├── composer.json │ ├── Client │ ├── Middleware │ │ └── AppRoleAuthenticator.php │ └── Client.php │ └── HashicorpVaultAdapter.php ├── .gitignore ├── .releaserc.json ├── README.md ├── psalm.xml ├── .github └── workflows │ ├── release.yml │ └── main.yml ├── examples ├── HashicorpVaultAdapter.php ├── PSR6CacheAdapter.php └── AWSSecretsManagerAdapter.php ├── LICENSE ├── phpunit.xml.dist ├── composer.json ├── ecs.php ├── CHANGELOG.md └── .editorconfig /src/Bundle/SecretaryBundle/.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | vendor 3 | .idea 4 | .vscode 5 | var -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | vendor 3 | .idea 4 | .vscode 5 | .phpunit.result.cache 6 | phpunit.xml 7 | .ecs_cache 8 | -------------------------------------------------------------------------------- /.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tagFormat": "${version}", 3 | "plugins": [ 4 | "@semantic-release/commit-analyzer", 5 | "@semantic-release/release-notes-generator", 6 | "@semantic-release/github", 7 | "@semantic-release/changelog", 8 | "@semantic-release/git" 9 | ] 10 | } -------------------------------------------------------------------------------- /src/Bundle/SecretaryBundle/SecretaryBundle.php: -------------------------------------------------------------------------------- 1 | 7 | * @date 2019 8 | * @license https://opensource.org/licenses/MIT 9 | */ 10 | 11 | namespace Secretary\Bundle\SecretaryBundle; 12 | 13 | use Symfony\Component\HttpKernel\Bundle\Bundle; 14 | 15 | /** 16 | * Class SecretaryBundle. 17 | * 18 | * @package Secretary\Bundle\SecretaryBundle 19 | */ 20 | class SecretaryBundle extends Bundle 21 | { 22 | } 23 | -------------------------------------------------------------------------------- /src/Core/Exception/ValueNotSupportedException.php: -------------------------------------------------------------------------------- 1 | 7 | * @date 2019 8 | * @license https://opensource.org/licenses/MIT 9 | */ 10 | 11 | namespace Secretary\Exception; 12 | 13 | class ValueNotSupportedException extends \Exception 14 | { 15 | public function __construct(string $key) 16 | { 17 | parent::__construct('This adapter doesn\'t support storing the value passed for: "'.$key.'"'); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Core/Exception/SecretNotFoundException.php: -------------------------------------------------------------------------------- 1 | 7 | * @date 2019 8 | * @license https://opensource.org/licenses/MIT 9 | */ 10 | 11 | namespace Secretary\Exception; 12 | 13 | class SecretNotFoundException extends \Exception 14 | { 15 | public function __construct(string $key, ?\Exception $childException = null) 16 | { 17 | parent::__construct('No secret was found with the key: "'.$key.'"', 404, $childException); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Secretary - Secrets Manager for PHP 2 | [![License](https://poser.pugx.org/secretary/core/license)](https://github.com/secretary/php-core/blob/master/LICENSE) 3 | --- 4 | 5 | Secrets are an important aspect of most applications you can build. How you store them, and keep them "secret" is a challenge. 6 | Luckily, there are tools you can use to keep them all safe. 7 | 8 | Secretary is a tool to integrate your PHP application with these tools. 9 | 10 | To get started, view the [documentation](https://github.com/secretary/php/tree/master/src/Core) 11 | -------------------------------------------------------------------------------- /psalm.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Adapter/AWS/SecretsManager/README.md: -------------------------------------------------------------------------------- 1 | # Secretary - AWS Secrets Manager Adapter 2 | 3 | AWS Secrets Manager Adapter for [Secretary](https://github.com/secretary/php) 4 | 5 | ## Table of Contents 6 | 7 | 1. [Installation](#installation) 8 | 2. [Options](#options) 9 | 10 | ### Installation 11 | 12 | ```bash 13 | $ composer require secretary/core secretary/aws-secrets-manager-adapter 14 | ``` 15 | 16 | ### Options 17 | 18 | Options passed to this adapter can be found [on the AWS PHP SDK docs](https://docs.aws.amazon.com/aws-sdk-php/v3/api/class-Aws.AwsClient.html#___construct). 19 | 20 | -------------------------------------------------------------------------------- /src/Adapter/Local/JSONFile/README.md: -------------------------------------------------------------------------------- 1 | # Secretary - JSON File Adapter 2 | 3 | JSON File Adapter for [Secretary](https://github.com/secretary/php) 4 | 5 | ## Table of Contents 6 | 7 | 1. [Installation](#installation) 8 | 2. [Secret Structure](#secrets-file-structure) 9 | 10 | ### Installation 11 | 12 | ```bash 13 | $ composer require secretary/core secretary/local-json-file-adapter 14 | ``` 15 | 16 | ### Secrets File Structure 17 | 18 | ```json 19 | [ 20 | { 21 | "key": "my-secret-key", 22 | "value": "some secret" 23 | }, 24 | { 25 | "key": "some-other-secret", 26 | "value": { 27 | "a": "b" 28 | }, 29 | "metadata": {"foo": "bar"} 30 | } 31 | ] 32 | ``` 33 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | 7 | jobs: 8 | release: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: 'Checkout' 13 | uses: 'actions/checkout@v4' 14 | 15 | - name: 'Setup Node.js' 16 | uses: 'actions/setup-node@v4' 17 | with: 18 | node-version: 20.x 19 | 20 | - name: 'Install dependencies' 21 | run: npm i -g semantic-release@22 @semantic-release/changelog @semantic-release/git @semantic-release/changelog @semantic-release/commit-analyzer @semantic-release/release-notes-generator 22 | 23 | - name: 'Release' 24 | env: 25 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 26 | run: npx semantic-release 27 | -------------------------------------------------------------------------------- /examples/HashicorpVaultAdapter.php: -------------------------------------------------------------------------------- 1 | 6 | * @date 2019 7 | * @license http://opensource.org/licenses/MIT 8 | */ 9 | 10 | 11 | require_once __DIR__.'/vendor/autoload.php'; 12 | use Secretary\Adapter\Hashicorp\Vault\HashicorpVaultAdapter; 13 | 14 | $fooSecret = new \Secretary\Secret('foo', 'bar'); 15 | $bazSecret = new \Secretary\Secret('baz', ['foo' => 'foobar']); 16 | 17 | $manager = new \Secretary\Manager(new HashicorpVaultAdapter()); 18 | 19 | try { 20 | $manager->putSecret($fooSecret); 21 | } catch (\Exception $e) { 22 | // Throws an exception because hashicopr vault requires key/value secrets 23 | } 24 | $manager->putSecret($bazSecret); 25 | 26 | var_dump( 27 | $manager->getSecret('baz'), 28 | $manager->getSecret('baz')['foo'] 29 | ); 30 | 31 | $manager->deleteSecret($bazSecret); -------------------------------------------------------------------------------- /src/Core/Adapter/AbstractAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * @date 2019 8 | * @license https://opensource.org/licenses/MIT 9 | */ 10 | 11 | namespace Secretary\Adapter; 12 | 13 | use Symfony\Component\OptionsResolver\OptionsResolver; 14 | 15 | /** 16 | * Class AbstractAdapter. 17 | * 18 | * @package Secretary\Adapter 19 | */ 20 | abstract class AbstractAdapter implements AdapterInterface 21 | { 22 | public function configureSharedOptions(OptionsResolver $resolver): void 23 | { 24 | } 25 | 26 | public function configureGetSecretOptions(OptionsResolver $resolver): void 27 | { 28 | } 29 | 30 | public function configurePutSecretOptions(OptionsResolver $resolver): void 31 | { 32 | } 33 | 34 | public function configureDeleteSecretOptions(OptionsResolver $resolver): void 35 | { 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/PSR6CacheAdapter.php: -------------------------------------------------------------------------------- 1 | 6 | * @date 2019 7 | * @license http://opensource.org/licenses/MIT 8 | */ 9 | 10 | use Cache\Adapter\Apc\ApcCachePool; 11 | use Secretary\Adapter\Cache\PSR6Cache\ChainAdapter; 12 | use Secretary\Adapter\Hashicorp\Vault\HashicorpVaultAdapter; 13 | 14 | require_once __DIR__.'/vendor/autoload.php'; 15 | 16 | $manager = new \Secretary\Manager(new ChainAdapter(new HashicorpVaultAdapter(), new ApcCachePool())); 17 | 18 | $bazSecret = new \Secretary\Secret('baz', ['foo' => 'foobar']); 19 | 20 | $manager->putSecret($bazSecret); 21 | 22 | var_dump( 23 | $manager->getSecret('baz', ['ttl' => 1000 * 60]), 24 | $manager->getSecret('baz')['foo'] // Pulls from cache! 25 | ); 26 | 27 | $manager->deleteSecret($bazSecret); 28 | 29 | 30 | var_dump($manager->getSecret('baz')); // 404, delete cleared the cache -------------------------------------------------------------------------------- /src/Bundle/SecretaryBundle/services.yaml: -------------------------------------------------------------------------------- 1 | parameters: 2 | 'kernel.secret': 'foo' 3 | foo: '%env(secret:default:SECRET_PATH:bar)%' 4 | 5 | #services: 6 | # Symfony\Component\Cache\Adapter\ApcuAdapter: ~ 7 | 8 | secretary: 9 | adapters: 10 | aws: 11 | adapter: Secretary\Adapter\AWS\SecretsManager\AWSSecretsManagerAdapter 12 | config: 13 | region: 'us-east-1' 14 | version: '2017-10-17' 15 | cache: 16 | enabled: false 17 | type: psr6 18 | service_id: Symfony\Component\Cache\Adapter\ApcuAdapter 19 | default: 20 | adapter: Secretary\Adapter\Chain\ChainAdapter 21 | config: 22 | - '@secretary.adapter.aws' 23 | cache: 24 | enabled: false 25 | type: psr6 26 | service_id: Symfony\Component\Cache\Adapter\ApcuAdapter -------------------------------------------------------------------------------- /src/Adapter/Chain/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "secretary/chain-adapter", 3 | "description": "Chain Adapter for Secretary", 4 | "type": "library", 5 | "license": "MIT", 6 | "keywords": [ 7 | "secrets", 8 | "chain", 9 | "secretary" 10 | ], 11 | "authors": [ 12 | { 13 | "name": "Aaron Scherer", 14 | "email": "aequasi@gmail.com" 15 | } 16 | ], 17 | "minimum-stability": "stable", 18 | "require": { 19 | "php": "^8.0", 20 | "secretary/core": "self.version" 21 | }, 22 | "require-dev": { 23 | "phpunit/phpunit": "^9.0 || ^10.0", 24 | "mockery/mockery": "^1.4" 25 | }, 26 | "autoload": { 27 | "psr-4": { 28 | "Secretary\\Adapter\\Chain\\": "" 29 | } 30 | }, 31 | "autoload-dev": { 32 | "psr-4": { 33 | "Secretary\\Adapter\\Chain\\Tests\\": "Tests/" 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/AWSSecretsManagerAdapter.php: -------------------------------------------------------------------------------- 1 | 6 | * @date 2019 7 | * @license http://opensource.org/licenses/MIT 8 | */ 9 | 10 | 11 | require_once __DIR__.'/../vendor/autoload.php'; 12 | 13 | use Secretary\Adapter\AWS\SecretsManager\LocalJSONFileAdapter; 14 | 15 | $manager = new \Secretary\Manager( 16 | new LocalJSONFileAdapter( 17 | [ 18 | 'region' => 'us-east-1', 19 | 'version' => '2017-10-17', 20 | ] 21 | ) 22 | ); 23 | 24 | $fooSecret = new \Secretary\Secret('foo', 'bar'); 25 | $bazSecret = new \Secretary\Secret('baz', ['foo' => 'foobar']); 26 | 27 | $manager->putSecret($fooSecret); 28 | $manager->putSecret($bazSecret); 29 | 30 | var_dump( 31 | $manager->getSecret('foo'), 32 | $manager->getSecret('baz'), 33 | $manager->getSecret('baz')['foo'] 34 | ); 35 | 36 | $manager->deleteSecret($fooSecret, ['ForceDeleteWithoutRecovery' => true]); 37 | $manager->deleteSecret($bazSecret, ['ForceDeleteWithoutRecovery' => true]); 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Aaron Scherer 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. -------------------------------------------------------------------------------- /src/Adapter/Local/JSONFile/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "secretary/local-json-file-adapter", 3 | "description": "JSON File Adapter for Secretary", 4 | "type": "library", 5 | "license": "MIT", 6 | "keywords": [ 7 | "secrets", 8 | "json", 9 | "secretary" 10 | ], 11 | "authors": [ 12 | { 13 | "name": "Aaron Scherer", 14 | "email": "aequasi@gmail.com" 15 | } 16 | ], 17 | "minimum-stability": "stable", 18 | "require": { 19 | "php": "^8.0", 20 | "ext-json": "*", 21 | "secretary/core": "self.version" 22 | }, 23 | "require-dev": { 24 | "mockery/mockery": "^1.4", 25 | "phpunit/phpunit": "^9.0 || ^10.0" 26 | }, 27 | "autoload": { 28 | "psr-4": { 29 | "Secretary\\Adapter\\Local\\JSONFile\\": "" 30 | } 31 | }, 32 | "autoload-dev": { 33 | "psr-4": { 34 | "Secretary\\Adapter\\Local\\JSONFile\\Tests\\": "Tests/" 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Core/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Aaron Scherer 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. -------------------------------------------------------------------------------- /src/Adapter/Chain/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Aaron Scherer 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. -------------------------------------------------------------------------------- /src/Adapter/AWS/SecretsManager/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Aaron Scherer 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. -------------------------------------------------------------------------------- /src/Adapter/Cache/PSR16Cache/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Aaron Scherer 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. -------------------------------------------------------------------------------- /src/Adapter/Cache/PSR6Cache/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Aaron Scherer 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. -------------------------------------------------------------------------------- /src/Adapter/Hashicorp/Vault/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Aaron Scherer 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. -------------------------------------------------------------------------------- /src/Adapter/Local/JSONFile/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Aaron Scherer 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. -------------------------------------------------------------------------------- /src/Bundle/SecretaryBundle/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Aaron Scherer 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. -------------------------------------------------------------------------------- /src/Adapter/Cache/PSR6Cache/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "secretary/psr-6-cache-adapter", 3 | "description": "PSR-6 Cache Adapter for Secretary", 4 | "type": "library", 5 | "license": "MIT", 6 | "keywords": [ 7 | "secrets", 8 | "cache", 9 | "psr-6", 10 | "secretary" 11 | ], 12 | "authors": [ 13 | { 14 | "name": "Aaron Scherer", 15 | "email": "aequasi@gmail.com" 16 | } 17 | ], 18 | "minimum-stability": "stable", 19 | "require": { 20 | "php": "^8.0", 21 | "psr/cache": "^1.0 || ^2.0 || ^3.0", 22 | "secretary/core": "self.version" 23 | }, 24 | "require-dev": { 25 | "phpunit/phpunit": "^9.0 || ^10.0", 26 | "mockery/mockery": "^1.4" 27 | }, 28 | "autoload": { 29 | "psr-4": { 30 | "Secretary\\Adapter\\Cache\\PSR6Cache\\": "" 31 | } 32 | }, 33 | "autoload-dev": { 34 | "psr-4": { 35 | "Secretary\\Adapter\\Cache\\PSR6Cache\\Tests\\": "Tests/" 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Adapter/Cache/PSR16Cache/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "secretary/psr-16-cache-adapter", 3 | "description": "PSR-16 Cache Adapter for Secretary", 4 | "type": "library", 5 | "license": "MIT", 6 | "keywords": [ 7 | "secrets", 8 | "cache", 9 | "psr-16", 10 | "secretary" 11 | ], 12 | "authors": [ 13 | { 14 | "name": "Aaron Scherer", 15 | "email": "aequasi@gmail.com" 16 | } 17 | ], 18 | "minimum-stability": "stable", 19 | "require": { 20 | "php": "^8.0", 21 | "psr/simple-cache": "^1.0 || ^2.0 || ^3.0", 22 | "secretary/core": "self.version" 23 | }, 24 | "require-dev": { 25 | "phpunit/phpunit": "^9.0 || ^10.0", 26 | "mockery/mockery": "^1.4" 27 | }, 28 | "autoload": { 29 | "psr-4": { 30 | "Secretary\\Adapter\\Cache\\PSR16Cache\\": "" 31 | } 32 | }, 33 | "autoload-dev": { 34 | "psr-4": { 35 | "Secretary\\Adapter\\Cache\\PSR16Cache\\Tests\\": "tests/" 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Adapter/Hashicorp/Vault/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "secretary/hashicorp-vault-adapter", 3 | "description": "Hashicorp Vault adapter for Secretary", 4 | "type": "library", 5 | "license": "MIT", 6 | "keywords": [ 7 | "secrets", 8 | "vault", 9 | "secretary" 10 | ], 11 | "authors": [ 12 | { 13 | "name": "Aaron Scherer", 14 | "email": "aequasi@gmail.com" 15 | } 16 | ], 17 | "minimum-stability": "stable", 18 | "require": { 19 | "php": "^8.0", 20 | "ext-json": "*", 21 | "guzzlehttp/guzzle": "^7.0", 22 | "symfony/options-resolver": "^5.4 || ^6.0 || ^7.0" 23 | }, 24 | "require-dev": { 25 | "phpunit/phpunit": "^9.0 || ^10.0", 26 | "mockery/mockery": "^1.4" 27 | }, 28 | "autoload": { 29 | "psr-4": { 30 | "Secretary\\Adapter\\Hashicorp\\Vault\\": "" 31 | } 32 | }, 33 | "autoload-dev": { 34 | "psr-4": { 35 | "Secretary\\Adapter\\Hashicorp\\Vault\\Tests\\": "Tests/" 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Adapter/AWS/SecretsManager/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "secretary/aws-secrets-manager-adapter", 3 | "description": "AWS Secrets Manager Adapter for Secretary", 4 | "type": "library", 5 | "license": "MIT", 6 | "keywords": [ 7 | "secrets", 8 | "aws", 9 | "aws secrets manager", 10 | "secretary" 11 | ], 12 | "authors": [ 13 | { 14 | "name": "Aaron Scherer", 15 | "email": "aequasi@gmail.com" 16 | } 17 | ], 18 | "minimum-stability": "stable", 19 | "require": { 20 | "php": "^8.0", 21 | "ext-json": "*", 22 | "aws/aws-sdk-php": "^3.0", 23 | "secretary/core": "self.version" 24 | }, 25 | "require-dev": { 26 | "phpunit/phpunit": "^9.0 || ^10.0", 27 | "mockery/mockery": "^1.4" 28 | }, 29 | "autoload": { 30 | "psr-4": { 31 | "Secretary\\Adapter\\AWS\\SecretsManager\\": "" 32 | } 33 | }, 34 | "autoload-dev": { 35 | "psr-4": { 36 | "Secretary\\Adapter\\AWS\\SecretsManager\\Tests\\": "Tests/" 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | ./src/Core/Tests 17 | 18 | 19 | ./src/Adapter/**/Tests 20 | 21 | 22 | ./src/Bundle/**/Tests 23 | 24 | 25 | 26 | 27 | 28 | src 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/Core/Helper/ArrayHelper.php: -------------------------------------------------------------------------------- 1 | 7 | * @date 2019 8 | * @license https://opensource.org/licenses/MIT 9 | */ 10 | 11 | namespace Secretary\Helper; 12 | 13 | /** 14 | * @package Secretary\Helper 15 | */ 16 | abstract class ArrayHelper 17 | { 18 | /** 19 | * @param string ...$keys 20 | */ 21 | public static function without(array $array, ...$keys): array 22 | { 23 | $newArray = $array; 24 | foreach ($keys as $key) { 25 | if (!empty($newArray[$key])) { 26 | unset($newArray[$key]); 27 | } 28 | } 29 | 30 | return $newArray; 31 | } 32 | 33 | /** 34 | * Remove elements from an array based on a list of keys and return a new array with the removed elements. 35 | * 36 | * @param string ...$keys 37 | */ 38 | public static function remove(array $array, ...$keys): array 39 | { 40 | $newArray = []; 41 | 42 | foreach ($keys as $key) { 43 | $newArray[$key] = $array[$key] ?? null; 44 | 45 | if (!empty($array[$key])) { 46 | unset($array[$key]); 47 | } 48 | } 49 | 50 | return $newArray; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Core/Adapter/AdapterInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * @date 2019 8 | * @license https://opensource.org/licenses/MIT 9 | */ 10 | 11 | namespace Secretary\Adapter; 12 | 13 | use Secretary\Exception\SecretNotFoundException; 14 | use Secretary\Secret; 15 | use Symfony\Component\OptionsResolver\OptionsResolver; 16 | 17 | /** 18 | * Interface AdapterInterface. 19 | * 20 | * @package Secretary\Adapter 21 | */ 22 | interface AdapterInterface 23 | { 24 | /** 25 | * Get a secret by a key. 26 | * 27 | * @throws SecretNotFoundException 28 | */ 29 | public function getSecret(string $key, ?array $options = []): Secret; 30 | 31 | /** 32 | * Add \ Update a secret by a key. 33 | */ 34 | public function putSecret(Secret $secret, ?array $options = []): Secret; 35 | 36 | /** 37 | * Delete a secret by a key. 38 | */ 39 | public function deleteSecret(Secret $secret, ?array $options = []): void; 40 | 41 | public function configureSharedOptions(OptionsResolver $resolver): void; 42 | 43 | public function configureGetSecretOptions(OptionsResolver $resolver): void; 44 | 45 | public function configurePutSecretOptions(OptionsResolver $resolver): void; 46 | 47 | public function configureDeleteSecretOptions(OptionsResolver $resolver): void; 48 | } 49 | -------------------------------------------------------------------------------- /src/Bundle/SecretaryBundle/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "secretary/secretary-bundle", 3 | "description": "Secrets Manager Bundle for Symfony", 4 | "type": "symfony-bundle", 5 | "license": "MIT", 6 | "keywords": [ 7 | "secrets", 8 | "vault", 9 | "secretsmanager", 10 | "keyvault", 11 | "secretary" 12 | ], 13 | "authors": [ 14 | { 15 | "name": "Aaron Scherer", 16 | "email": "aequasi@gmail.com" 17 | } 18 | ], 19 | "prefer-stable": true, 20 | "minimum-stability": "dev", 21 | "require": { 22 | "php": "^8.0", 23 | "secretary/core": "self.version" 24 | }, 25 | "require-dev": { 26 | "symfony/config": "^5.3 || ^6.0 || ^7.0", 27 | "symfony/dependency-injection": "^5.0 || ^6.0 || ^7.0", 28 | "symfony/http-kernel": "^5.0 || ^6.0 || ^7.0", 29 | "symfony/framework-bundle": "^5.0 || ^6.0 || ^7.0", 30 | "symfony/yaml": "^5.0 || ^6.0 || ^7.0", 31 | "aws/aws-sdk-php": "^3.91" 32 | }, 33 | "autoload": { 34 | "psr-4": { 35 | "Secretary\\Bundle\\SecretaryBundle\\": "" 36 | }, 37 | "exclude-from-classmap": [ 38 | "/Tests/" 39 | ] 40 | }, 41 | "autoload-dev": { 42 | "psr-4": { 43 | "Secretary\\Bundle\\SecretaryBundle\\Tests\\": "tests/" 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "secretary/php", 3 | "description": "Monorepo for Secretary's PHP implementation", 4 | "type": "library", 5 | "require-dev": { 6 | "php": "^8.0", 7 | "ext-json": "*", 8 | "aws/aws-sdk-php": "^3.91", 9 | "guzzlehttp/guzzle": "^7.0", 10 | "mockery/mockery": "^1.4", 11 | "phpunit/phpunit": "^9.0 || ^10.0", 12 | "psr/simple-cache": "^1.0 || ^2.0 || ^3.0", 13 | "symfony/config": "^5.3 || ^6.0 || ^7.0", 14 | "symfony/dependency-injection": "^5.0 || ^6.0 || ^7.0", 15 | "symfony/framework-bundle": "^5.0 || ^6.0 || ^7.0", 16 | "symfony/http-kernel": "^5.0 || ^6.0 || ^7.0", 17 | "symfony/options-resolver": "^5.4 || ^6.0 || ^7.0", 18 | "symfony/yaml": "^5.0 || ^6.0 || ^7.0", 19 | "symplify/easy-coding-standard": "^12", 20 | "vimeo/psalm": "^5.0 || ^6.0" 21 | }, 22 | "license": "MIT", 23 | "authors": [ 24 | { 25 | "name": "Aaron Scherer", 26 | "email": "aequasi@gmail.com" 27 | } 28 | ], 29 | "autoload": { 30 | "psr-4": { 31 | "Secretary\\": "src/Core", 32 | "Secretary\\Adapter\\": "src/Adapter", 33 | "Secretary\\Bundle\\": "src/Bundle" 34 | }, 35 | "exclude-from-classmap": [ 36 | "**/Tests/" 37 | ] 38 | }, 39 | "config": { 40 | "preferred-install": { 41 | "*": "dist" 42 | }, 43 | "sort-packages": true 44 | }, 45 | "scripts": { 46 | "ecs": "ecs check", 47 | "ecs:fix": "ecs check --fix", 48 | "psalm": "psalm --show-info" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Core/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "secretary/core", 3 | "description": "Secrets Manager for PHP", 4 | "type": "library", 5 | "license": "MIT", 6 | "keywords": [ 7 | "secrets", 8 | "vault", 9 | "secretsmanager", 10 | "keyvault", 11 | "secretary" 12 | ], 13 | "authors": [ 14 | { 15 | "name": "Aaron Scherer", 16 | "email": "aequasi@gmail.com" 17 | } 18 | ], 19 | "minimum-stability": "stable", 20 | "require": { 21 | "php": "^8.0", 22 | "symfony/options-resolver": "^5.4 || ^6.0 || ^7.0" 23 | }, 24 | "require-dev": { 25 | "phpunit/phpunit": "^9.0", 26 | "mockery/mockery": "^1.4" 27 | }, 28 | "suggest": { 29 | "secretary/aws-secrets-manager-adapter": "For reading secrets from AWS Secrets Manager", 30 | "secretary/hashicorp-vault-adapter": "For reading secrets from Hashicorp Vault", 31 | "secretary/psr6-cache-adapter": "For caching secrets using a PSR-6 Cache Interface", 32 | "secretary/psr16-cache-adapter": "For caching secrets using a PSR-16 SimpleCache Interface", 33 | "secretary/secretary-bundle": "For integrating Secretary with the Symfony Framework" 34 | }, 35 | "autoload": { 36 | "psr-4": { 37 | "Secretary\\": "" 38 | }, 39 | "exclude-from-classmap": [ 40 | "/Tests/" 41 | ] 42 | }, 43 | "autoload-dev": { 44 | "psr-4": { 45 | "Secretary\\Tests\\": "tests/" 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Bundle/SecretaryBundle/README.md: -------------------------------------------------------------------------------- 1 | # Secretary Bundle - Secrets Manager for Symfony 2 | 3 | ## This Bundle Experimental! 4 | 5 | Secrets are an important aspect of most applications you can build. How you store them, and keep them "secret" is a challenge. 6 | Luckily, there are tools you can use to keep them all safe. 7 | 8 | Secretary is a tool to integrate your PHP application with these tools. 9 | 10 | You can find more information about the underlying library over at [the main docs](https://github.com/secretary/php). 11 | 12 | ### Installation 13 | 14 | ```bash 15 | $ composer require secretary/symfony 16 | ``` 17 | 18 | ### Configuration 19 | 20 | ```yaml 21 | # config/packages/secretary.yamlg 22 | services: 23 | Symfony\Component\Cache\Adapter\ApcuAdapter: 24 | arguments: ['secrets', 300000] 25 | 26 | secretary: 27 | adapters: 28 | json: 29 | adapter: Secretary\Adapter\Local\JSONFile\LocalJSONFileAdapter 30 | config: 31 | file: '%kernel.root_dir%/config/secrets.json' 32 | aws: 33 | adapter: Secretary\Adapter\AWS\SecretsManager\AWSSecretsManagerAdapter 34 | config: 35 | region: 'us-east-1' 36 | version: 'latest' 37 | credentials: 38 | key: "%env(API_AWS_ACCESS_KEY_ID)%" 39 | secret: "%env(API_AWS_SECRET_ACCESS_KEY)%" 40 | default: # chain adapter 41 | adapter: Secretary\Adapter\Chain\ChainAdapter 42 | config: 43 | - @secretary.adapter.json 44 | - @secretary.adapter.aws 45 | cache: 46 | enabled: true 47 | type: psr6 48 | service_id: cache.secrets 49 | ``` -------------------------------------------------------------------------------- /src/Bundle/SecretaryBundle/Test.php: -------------------------------------------------------------------------------- 1 | 7 | * @date 2019 8 | * @license https://opensource.org/licenses/MIT 9 | */ 10 | 11 | require_once __DIR__.'/vendor/autoload.php'; 12 | 13 | use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; 14 | use Symfony\Component\Config\Loader\LoaderInterface; 15 | use Symfony\Component\DependencyInjection\ContainerBuilder; 16 | 17 | /** 18 | * @author Aaron Scherer 19 | * @date 2019 20 | * 21 | * @license http://opensource.org/licenses/MIT 22 | * 23 | * @internal 24 | * @coversNothing 25 | */ 26 | class Test extends \Symfony\Component\HttpKernel\Kernel 27 | { 28 | use MicroKernelTrait; 29 | public const CONFIG_EXTS = '.{php,xml,yaml,yml}'; 30 | 31 | /** 32 | * Returns an array of bundles to register. 33 | * 34 | * @return iterable|\Symfony\Component\HttpKernel\Bundle\BundleInterface An iterable of bundle instances 35 | */ 36 | public function registerBundles() 37 | { 38 | yield new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(); 39 | yield new \Secretary\Bundle\SecretaryBundle\SecretaryBundle(); 40 | } 41 | 42 | /** 43 | * Add or import routes into your application. 44 | * 45 | * $routes->import('config/routing.yml'); 46 | * $routes->add('/admin', 'App\Controller\AdminController::dashboard', 'admin_dashboard'); 47 | * 48 | */ 49 | protected function configureRoutes(Symfony\Component\Routing\RouteCollectionBuilder $routes) 50 | { 51 | } 52 | 53 | /** 54 | * {@inheritDc}. 55 | * 56 | * @throws Exception 57 | */ 58 | protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader) 59 | { 60 | $loader->load(__DIR__.'/services'.self::CONFIG_EXTS, 'glob'); 61 | } 62 | } 63 | 64 | $k = new Test('dev', true); 65 | $k->boot(); 66 | var_dump($k->getContainer()->getParameter('foo')); 67 | -------------------------------------------------------------------------------- /src/Adapter/Hashicorp/Vault/Client/Middleware/AppRoleAuthenticator.php: -------------------------------------------------------------------------------- 1 | 7 | * @date 2019 8 | * @license https://opensource.org/licenses/MIT 9 | */ 10 | 11 | namespace Secretary\Adapter\Hashicorp\Vault\Client\Middleware; 12 | 13 | use GuzzleHttp\Client; 14 | use GuzzleHttp\Utils; 15 | use Psr\Http\Message\RequestInterface; 16 | 17 | /** 18 | * Class AppRoleAuthenticator. 19 | * 20 | * @package Secretary\Adapter\Hashicorp\Vault\Client\Middleware 21 | */ 22 | class AppRoleAuthenticator 23 | { 24 | public const authRoute = 'v1/auth/approle/login'; 25 | 26 | private Client $client; 27 | 28 | private string $roleId; 29 | 30 | private string $secretId; 31 | 32 | private ?string $token = null; 33 | 34 | private ?int $tokenExpiration = null; 35 | 36 | public function __construct(Client $client, string $roleId, string $secretId) 37 | { 38 | $this->client = $client; 39 | $this->roleId = $roleId; 40 | $this->secretId = $secretId; 41 | } 42 | 43 | public function __invoke(callable $handler): callable 44 | { 45 | return function (RequestInterface $request, array $options) use ($handler) { 46 | if (empty($this->token) || time() > $this->tokenExpiration) { 47 | $this->authenticate(); 48 | } 49 | 50 | $request = $request->withHeader('X-Vault-Token', $this->token); 51 | 52 | return $handler($request, $options); 53 | }; 54 | } 55 | 56 | private function authenticate(): void 57 | { 58 | $response = $this->client->post( 59 | static::authRoute, 60 | [ 61 | 'json' => [ 62 | 'role_id' => $this->roleId, 63 | 'secret-id' => $this->secretId, 64 | ], 65 | 'handler' => Utils::chooseHandler(), 66 | ] 67 | ); 68 | 69 | $response = json_decode($response->getBody()->getContents(), true); 70 | $this->token = (string) $response['auth']['client_token']; 71 | $this->tokenExpiration = time() + (int) $response['auth']['lease_duration']; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Adapter/Hashicorp/Vault/HashicorpVaultAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * @date 2019 8 | * @license https://opensource.org/licenses/MIT 9 | */ 10 | 11 | namespace Secretary\Adapter\Hashicorp\Vault; 12 | 13 | use GuzzleHttp\Client as GuzzleClient; 14 | use Secretary\Adapter\AbstractAdapter; 15 | use Secretary\Adapter\Hashicorp\Vault\Client\Client; 16 | use Secretary\Secret; 17 | 18 | /** 19 | * Class HashicorpVaultAdapter. 20 | * 21 | * @package Secretary\Adapter\Hashicorp\Vault 22 | */ 23 | class HashicorpVaultAdapter extends AbstractAdapter 24 | { 25 | private Client $client; 26 | 27 | /** 28 | * @throws \Exception 29 | */ 30 | public function __construct(array $config = []) 31 | { 32 | if (!class_exists(GuzzleClient::class)) { 33 | throw new \Exception('guzzlehttp/guzzle is required to use the HashicorpVaultAdapter'); 34 | } 35 | 36 | $this->client = new Client($config); 37 | } 38 | 39 | /** 40 | * {@inheritdoc} 41 | */ 42 | public function getSecret(string $key, ?array $options = []): Secret 43 | { 44 | $response = $this->client->getClient()->get('/v1/secret/'.$key); 45 | $json = json_decode($response->getBody()->getContents(), true); 46 | 47 | return new Secret($key, $json['data']); 48 | } 49 | 50 | /** 51 | * {@inheritdoc} 52 | */ 53 | public function putSecret(Secret $secret, ?array $options = []): Secret 54 | { 55 | if (!is_array($secret->getValue())) { 56 | throw new \Exception('Value for this adapter must be a key/value array'); 57 | } 58 | 59 | $this->client->getClient()->post('/v1/secret/'.$secret->getKey(), ['json' => $secret->getValue()]); 60 | 61 | return $secret; 62 | } 63 | 64 | /** 65 | * {@inheritdoc} 66 | */ 67 | public function deleteSecret(Secret $secret, ?array $options = []): void 68 | { 69 | $this->deleteSecretByKey($secret->getKey(), $options); 70 | } 71 | 72 | public function deleteSecretByKey(string $key, ?array $options = []): void 73 | { 74 | $this->client->getClient()->delete('/v1/secret/'.$key); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Bundle/SecretaryBundle/EnvVar/EnvVarProcessor.php: -------------------------------------------------------------------------------- 1 | 7 | * @date 2019 8 | * @license https://opensource.org/licenses/MIT 9 | */ 10 | 11 | namespace Secretary\Bundle\SecretaryBundle\EnvVar; 12 | 13 | use Secretary\Manager; 14 | use Symfony\Component\DependencyInjection\EnvVarProcessorInterface; 15 | 16 | class EnvVarProcessor implements EnvVarProcessorInterface 17 | { 18 | /** 19 | * @var list 20 | */ 21 | private array $managers; 22 | 23 | public function __construct(\Traversable $managers) 24 | { 25 | $this->managers = iterator_to_array($managers); 26 | } 27 | 28 | /** 29 | * @psalm-suppress InvalidArrayOffset 30 | */ 31 | public function getEnv(string $prefix, string $name, \Closure $getEnv): mixed 32 | { 33 | $parts = explode(':', $name); 34 | 35 | if (!array_key_exists($parts[0], $this->managers)) { 36 | throw new \InvalidArgumentException(sprintf('%s is not a valid manager name. Available managers: %s', $parts[0], implode(', ', array_keys($this->managers)))); 37 | } 38 | 39 | $manager = $this->managers[$parts[0]]; 40 | $key = $getEnv($parts[1]); 41 | $value = $manager->getSecret($key)->getValue(); 42 | 43 | if (array_key_exists(2, $parts)) { 44 | if (!is_array($value)) { 45 | throw new \InvalidArgumentException(sprintf('Index isn\'t available n %s. Value is a string.', $key)); 46 | } 47 | 48 | if (!array_key_exists($parts[2], $value)) { 49 | throw new \InvalidArgumentException(sprintf('%s is not a valid index in that secret. Available indexes: %s', $parts[2], implode(', ', array_keys($value)))); 50 | } 51 | $value = $value[$parts[2]]; 52 | } 53 | 54 | return $value; 55 | } 56 | 57 | public static function getProvidedTypes(): array 58 | { 59 | return [ 60 | 'secretary' => 'bool|int|float|string', 61 | 'secret' => 'bool|int|float|string', 62 | 'secretArray' => 'array', 63 | 'secretaryArray' => 'array', 64 | ]; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Core/Manager.php: -------------------------------------------------------------------------------- 1 | 7 | * @date 2019 8 | * @license https://opensource.org/licenses/MIT 9 | */ 10 | 11 | namespace Secretary; 12 | 13 | use Secretary\Adapter\AdapterInterface; 14 | use Symfony\Component\OptionsResolver\OptionsResolver; 15 | 16 | /** 17 | * Class Manager. 18 | * 19 | * @package Secretary 20 | */ 21 | class Manager 22 | { 23 | private AdapterInterface $adapter; 24 | 25 | public function __construct(AdapterInterface $adapter) 26 | { 27 | $this->adapter = $adapter; 28 | } 29 | 30 | /** 31 | * @throws Exception\SecretNotFoundException 32 | */ 33 | public function getSecret(string $key, ?array $options = []): Secret 34 | { 35 | $resolver = new OptionsResolver(); 36 | $this->adapter->configureSharedOptions($resolver); 37 | $this->adapter->configureGetSecretOptions($resolver); 38 | 39 | return $this->adapter->getSecret($key, $resolver->resolve($options)); 40 | } 41 | 42 | public function putSecret(Secret $secret, ?array $options = []): Secret 43 | { 44 | $resolver = new OptionsResolver(); 45 | $this->adapter->configureSharedOptions($resolver); 46 | $this->adapter->configurePutSecretOptions($resolver); 47 | 48 | return $this->adapter->putSecret($secret, $resolver->resolve($options)); 49 | } 50 | 51 | /** 52 | * @throws Exception\SecretNotFoundException 53 | */ 54 | public function deleteSecretByKey(string $key, ?array $options = []): void 55 | { 56 | $secret = $this->getSecret($key, $options); 57 | 58 | $resolver = new OptionsResolver(); 59 | $this->adapter->configureSharedOptions($resolver); 60 | $this->adapter->configureDeleteSecretOptions($resolver); 61 | 62 | $this->adapter->deleteSecret($secret, $resolver->resolve($options)); 63 | } 64 | 65 | public function deleteSecret(Secret $secret, ?array $options = []): void 66 | { 67 | $resolver = new OptionsResolver(); 68 | $this->adapter->configureSharedOptions($resolver); 69 | $this->adapter->configureDeleteSecretOptions($resolver); 70 | 71 | $this->adapter->deleteSecret($secret, $resolver->resolve($options)); 72 | } 73 | 74 | public function getAdapter(): AdapterInterface 75 | { 76 | return $this->adapter; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Bundle/SecretaryBundle/DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | 7 | * @date 2019 8 | * @license https://opensource.org/licenses/MIT 9 | */ 10 | 11 | namespace Secretary\Bundle\SecretaryBundle\DependencyInjection; 12 | 13 | use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; 14 | use Symfony\Component\Config\Definition\Builder\TreeBuilder; 15 | use Symfony\Component\Config\Definition\ConfigurationInterface; 16 | 17 | /** 18 | * @package Secretary\Bundle\SecretaryBundle\DependencyInjection 19 | */ 20 | class Configuration implements ConfigurationInterface 21 | { 22 | public function getConfigTreeBuilder(): TreeBuilder 23 | { 24 | $treeBuilder = new TreeBuilder('secretary', 'array'); 25 | 26 | /** @var ArrayNodeDefinition $rootNode */ 27 | $rootNode = $treeBuilder->getRootNode(); 28 | 29 | $rootNode 30 | ->children() 31 | ->append($this->addAdaptersSection()) 32 | ->end(); 33 | 34 | return $treeBuilder; 35 | } 36 | 37 | /** 38 | * @psalm-suppress UndefinedInterfaceMethod 39 | */ 40 | private function addAdaptersSection(): ArrayNodeDefinition 41 | { 42 | $treeBuilder = new TreeBuilder('adapters'); 43 | 44 | /** @var ArrayNodeDefinition $node */ 45 | $node = $treeBuilder->getRootNode(); 46 | 47 | $node 48 | ->useAttributeAsKey('name') 49 | ->arrayPrototype() 50 | ->addDefaultsIfNotSet() 51 | ->children() 52 | ->scalarNode('adapter') 53 | ->isRequired() 54 | ->info('Class name, or service ID of adapter') 55 | ->end() 56 | ->arrayNode('config') 57 | ->ignoreExtraKeys(false) 58 | ->end() 59 | ->arrayNode('cache') 60 | ->canBeEnabled() 61 | ->addDefaultsIfNotSet() 62 | ->children() 63 | ->enumNode('type') 64 | ->values(['psr6', 'psr16']) 65 | ->end() 66 | ->scalarNode('service_id')->end() 67 | ->end() 68 | ->end() 69 | ->end() 70 | ->end(); 71 | 72 | return $node; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Core/Secret.php: -------------------------------------------------------------------------------- 1 | 7 | * @date 2019 8 | * @license https://opensource.org/licenses/MIT 9 | */ 10 | 11 | namespace Secretary; 12 | 13 | use Secretary\Exception\ValueNotSupportedException; 14 | 15 | /** 16 | * @implements \ArrayAccess 17 | */ 18 | class Secret implements \ArrayAccess 19 | { 20 | private string $key; 21 | 22 | private array|string $value; 23 | 24 | private ?array $metadata; 25 | 26 | public function __construct(string $key, array|string $value, ?array $metadata = null) 27 | { 28 | $this->key = $key; 29 | $this->value = $value; 30 | $this->metadata = $metadata; 31 | } 32 | 33 | public function getKey(): string 34 | { 35 | return $this->key; 36 | } 37 | 38 | /** 39 | * @return array|string 40 | */ 41 | public function getValue() 42 | { 43 | return $this->value; 44 | } 45 | 46 | public function getMetadata(): array 47 | { 48 | return $this->metadata ?? []; 49 | } 50 | 51 | /** 52 | */ 53 | public function offsetExists($offset): bool 54 | { 55 | return is_array($this->value) && array_key_exists($offset, $this->value); 56 | } 57 | 58 | /** 59 | * 60 | * @throws ValueNotSupportedException 61 | */ 62 | public function offsetGet($offset): mixed 63 | { 64 | if (!is_array($this->value)) { 65 | throw new ValueNotSupportedException($this->key); 66 | } 67 | 68 | return $this->value[$offset]; 69 | } 70 | 71 | /** 72 | * @throws \Exception 73 | */ 74 | public function offsetSet(mixed $offset, mixed $value): void 75 | { 76 | throw new \Exception('Secrets are immutable'); 77 | } 78 | 79 | /** 80 | * @throws \Exception 81 | */ 82 | public function offsetUnset(mixed $offset): void 83 | { 84 | throw new \Exception('Secrets are immutable'); 85 | } 86 | 87 | /** 88 | * Returns a new instance of this secret with the value changed. 89 | * 90 | * @param array|string $value 91 | */ 92 | public function withValue($value): self 93 | { 94 | return new self($this->key, $value, $this->metadata); 95 | } 96 | 97 | /** 98 | * Returns a new instance of this secret with the metadata changed. 99 | */ 100 | public function withMetadata(array $metadata): self 101 | { 102 | return new self($this->key, $this->value, $metadata); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/Adapter/Chain/ChainAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * @date 2019 8 | * @license https://opensource.org/licenses/MIT 9 | */ 10 | 11 | namespace Secretary\Adapter\Chain; 12 | 13 | use Secretary\Adapter\AbstractAdapter; 14 | use Secretary\Adapter\AdapterInterface; 15 | use Secretary\Exception\SecretNotFoundException; 16 | use Secretary\Secret; 17 | 18 | /** 19 | * Class ChainAdapter. 20 | * 21 | * @package Secretary\Adapter\Chain 22 | */ 23 | final class ChainAdapter extends AbstractAdapter 24 | { 25 | /** 26 | * @var list 27 | */ 28 | private array $adapters; 29 | 30 | /** 31 | * @param AdapterInterface[] $adapters 32 | */ 33 | public function __construct(array $adapters) 34 | { 35 | $this->adapters = $adapters; 36 | } 37 | 38 | /** 39 | * Note: $options is a 0-indexed array of options. Each index corresponds to the index of the adapters 40 | * {@inheritdoc} 41 | */ 42 | public function getSecret(string $key, ?array $options = []): Secret 43 | { 44 | foreach ($this->adapters as $index => $adapter) { 45 | try { 46 | return $adapter->getSecret($key, $options[$index] ?? []); 47 | } catch (SecretNotFoundException $ignored) { 48 | } 49 | } 50 | 51 | throw new SecretNotFoundException($key); 52 | } 53 | 54 | /** 55 | * {@inheritdoc} 56 | */ 57 | public function putSecret(Secret $secret, ?array $options = []): Secret 58 | { 59 | foreach ($this->adapters as $index => $adapter) { 60 | $adapter->putSecret($secret, $options[$index] ?? []); 61 | } 62 | 63 | return $secret; 64 | } 65 | 66 | /** 67 | * {@inheritdoc} 68 | */ 69 | public function deleteSecret(Secret $secret, ?array $options = []): void 70 | { 71 | foreach ($this->adapters as $index => $adapter) { 72 | $adapter->deleteSecret($secret, $options[$index] ?? []); 73 | } 74 | } 75 | 76 | /** 77 | * {@inheritdoc} 78 | */ 79 | public function deleteSecretByKey(string $key, ?array $options = []): void 80 | { 81 | $success = false; 82 | foreach ($this->adapters as $index => $adapter) { 83 | try { 84 | $adapter->deleteSecret($adapter->getSecret($key), $options[$index] ?? []); 85 | $success = true; 86 | } catch (SecretNotFoundException $ignored) { 87 | } 88 | } 89 | 90 | if (!$success) { 91 | throw new SecretNotFoundException($key); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Adapter/Cache/PSR16Cache/PSR16CacheAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * @date 2019 8 | * @license https://opensource.org/licenses/MIT 9 | */ 10 | 11 | namespace Secretary\Adapter\Cache\PSR16Cache; 12 | 13 | use Psr\SimpleCache\CacheInterface; 14 | use Secretary\Adapter\AbstractAdapter; 15 | use Secretary\Adapter\AdapterInterface; 16 | use Secretary\Helper\ArrayHelper; 17 | use Secretary\Secret; 18 | 19 | /** 20 | * @package Secretary\Adapter\Cache 21 | */ 22 | final class PSR16CacheAdapter extends AbstractAdapter 23 | { 24 | private AdapterInterface $adapter; 25 | 26 | private CacheInterface $cache; 27 | 28 | public function __construct(AdapterInterface $adapter, CacheInterface $cache) 29 | { 30 | $this->adapter = $adapter; 31 | $this->cache = $cache; 32 | } 33 | 34 | /** 35 | * {@inheritdoc} 36 | */ 37 | public function getSecret(string $key, ?array $options = []): Secret 38 | { 39 | ['ttl' => $ttl] = ArrayHelper::remove($options, 'ttl'); 40 | 41 | if ($this->cache->has(sha1($key))) { 42 | [$value, $metadata] = json_decode($this->cache->get(sha1($key)), true); 43 | 44 | return new Secret($key, $value, $metadata); 45 | } 46 | 47 | $secret = $this->adapter->getSecret($key, $options); 48 | $this->cache->set(sha1($key), json_encode([$secret->getValue(), $secret->getMetadata()]), $ttl); 49 | 50 | return $secret; 51 | } 52 | 53 | /** 54 | * {@inheritdoc} 55 | */ 56 | public function putSecret(Secret $secret, ?array $options = []): Secret 57 | { 58 | ['ttl' => $ttl] = ArrayHelper::remove($options, 'ttl'); 59 | 60 | $this->adapter->putSecret($secret, $options); 61 | 62 | if ($this->cache->has(sha1($secret->getKey())) || $ttl === 0) { 63 | $this->cache->delete(sha1($secret->getKey())); 64 | 65 | return $secret; 66 | } 67 | 68 | $this->cache->set(sha1($secret->getKey()), json_encode([$secret->getValue(), $secret->getMetadata()]), $ttl); 69 | 70 | return $secret; 71 | } 72 | 73 | /** 74 | * {@inheritdoc} 75 | */ 76 | public function deleteSecret(Secret $secret, ?array $options = []): void 77 | { 78 | $this->deleteSecretByKey($secret->getKey(), $options); 79 | } 80 | 81 | /** 82 | * {@inheritdoc} 83 | */ 84 | public function deleteSecretByKey(string $key, ?array $options = []): void 85 | { 86 | $this->adapter->deleteSecret($this->adapter->getSecret($key, $options), $options); 87 | $this->cache->delete(sha1($key)); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Adapter/Cache/PSR6Cache/PSR6CacheAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * @date 2019 8 | * @license https://opensource.org/licenses/MIT 9 | */ 10 | 11 | namespace Secretary\Adapter\Cache\PSR6Cache; 12 | 13 | use Psr\Cache\CacheItemPoolInterface; 14 | use Secretary\Adapter\AbstractAdapter; 15 | use Secretary\Adapter\AdapterInterface; 16 | use Secretary\Helper\ArrayHelper; 17 | use Secretary\Secret; 18 | 19 | /** 20 | * Class PSR6CacheAdapter. 21 | * 22 | * @package Secretary\Adapter\Cache 23 | */ 24 | final class PSR6CacheAdapter extends AbstractAdapter 25 | { 26 | private AdapterInterface $adapter; 27 | 28 | private CacheItemPoolInterface $cache; 29 | 30 | public function __construct(AdapterInterface $adapter, CacheItemPoolInterface $cache) 31 | { 32 | $this->adapter = $adapter; 33 | $this->cache = $cache; 34 | } 35 | 36 | /** 37 | * {@inheritdoc} 38 | */ 39 | public function getSecret(string $key, ?array $options = []): Secret 40 | { 41 | ['ttl' => $ttl] = ArrayHelper::remove($options, 'ttl'); 42 | 43 | $item = $this->cache->getItem(sha1($key)); 44 | 45 | if ($item->isHit()) { 46 | [$value, $metadata] = json_decode($item->get(), true); 47 | 48 | return new Secret($key, $value, $metadata); 49 | } 50 | 51 | $secret = $this->adapter->getSecret($key, $options); 52 | $item->set(json_encode([$secret->getValue(), $secret->getMetadata()])); 53 | 54 | if ($ttl !== null) { 55 | $item->expiresAfter($ttl); 56 | } 57 | $this->cache->save($item); 58 | 59 | return $secret; 60 | } 61 | 62 | /** 63 | * {@inheritdoc} 64 | */ 65 | public function putSecret(Secret $secret, ?array $options = []): Secret 66 | { 67 | ['ttl' => $ttl] = ArrayHelper::remove($options, 'ttl'); 68 | 69 | $this->adapter->putSecret($secret, $options); 70 | 71 | if ($this->cache->hasItem(sha1($secret->getKey())) || $ttl === 0) { 72 | $this->cache->deleteItem(sha1($secret->getKey())); 73 | 74 | return $secret; 75 | } 76 | 77 | $item = $this->cache->getItem(sha1($secret->getKey())); 78 | $item->set(json_encode([$secret->getValue(), $secret->getMetadata()])); 79 | 80 | if (!empty($ttl)) { 81 | $item->expiresAfter($ttl); 82 | } 83 | $this->cache->save($item); 84 | 85 | return $secret; 86 | } 87 | 88 | /** 89 | * {@inheritdoc} 90 | */ 91 | public function deleteSecret(Secret $secret, ?array $options = []): void 92 | { 93 | $this->deleteSecretByKey($secret->getKey(), $options); 94 | } 95 | 96 | public function deleteSecretByKey(string $key, ?array $options = []): void 97 | { 98 | $this->adapter->deleteSecret($this->adapter->getSecret($key, $options), $options); 99 | 100 | if ($this->cache->hasItem(sha1($key))) { 101 | $this->cache->deleteItem(sha1($key)); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/Adapter/Hashicorp/Vault/Client/Client.php: -------------------------------------------------------------------------------- 1 | 7 | * @date 2019 8 | * @license https://opensource.org/licenses/MIT 9 | */ 10 | 11 | namespace Secretary\Adapter\Hashicorp\Vault\Client; 12 | 13 | use GuzzleHttp\HandlerStack; 14 | use Secretary\Adapter\Hashicorp\Vault\Client\Middleware\AppRoleAuthenticator; 15 | use Symfony\Component\OptionsResolver\OptionsResolver; 16 | 17 | class Client 18 | { 19 | private \GuzzleHttp\Client $client; 20 | 21 | public function __construct(array $config) 22 | { 23 | $config = $this->validateOptions($config); 24 | 25 | $baseUri = rtrim($config['address'], '/'); 26 | 27 | $stack = HandlerStack::create(); 28 | $options = ['base_uri' => $baseUri, 'stack' => $stack]; 29 | 30 | if (!empty($config['credentials']['token'])) { 31 | $options['headers'] = ['X-Vault-Token' => $config['credentials']['token']]; 32 | } 33 | 34 | if (!empty($config['credentials']['appRole'])) { 35 | ['roleId' => $roleId, 'secretId' => $secretId] = $config['credentials']['appRole']; 36 | 37 | if (!empty($roleId) && !empty($secretId)) { 38 | $stack->push(new AppRoleAuthenticator($this->client, $roleId, $secretId)); 39 | } 40 | } 41 | 42 | $this->client = new \GuzzleHttp\Client($options); 43 | } 44 | 45 | public function getClient(): \GuzzleHttp\Client 46 | { 47 | return $this->client; 48 | } 49 | 50 | /** 51 | * @todo Add options for SSL cert 52 | */ 53 | private function validateOptions(array $config): array 54 | { 55 | $addr = (string) getenv('VAULT_ADDR'); 56 | $token = (string) getenv('VAULT_TOKEN'); 57 | 58 | $resolver = new OptionsResolver(); 59 | $resolver 60 | ->setDefined('address') 61 | ->setAllowedTypes('address', 'string'); 62 | 63 | if (!empty($addr)) { 64 | $resolver->setDefault('address', $addr); 65 | } 66 | 67 | $resolver->setDefault( 68 | 'credentials', 69 | function (OptionsResolver $credentials) use ($token) { 70 | $credentials 71 | ->setRequired('token') 72 | ->setAllowedTypes('token', 'string'); 73 | 74 | if (!empty($token)) { 75 | $credentials->setDefault('token', $token); 76 | } 77 | 78 | $credentials->setDefault( 79 | 'appRole', 80 | function (OptionsResolver $appRole) { 81 | $appRole 82 | ->setDefault('roleId', '') 83 | ->setRequired('roleId') 84 | ->setAllowedTypes('roleId', 'string'); 85 | 86 | $appRole 87 | ->setDefault('secretId', '') 88 | ->setRequired('secretId') 89 | ->setAllowedTypes('secretId', 'string'); 90 | } 91 | ); 92 | } 93 | ); 94 | 95 | return $resolver->resolve($config); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /ecs.php: -------------------------------------------------------------------------------- 1 | cacheDirectory(__DIR__.'/.ecs_cache'); 30 | $ecsConfig->parallel(); 31 | 32 | $ecsConfig->paths([ 33 | __DIR__.'/src', 34 | ]); 35 | 36 | $ecsConfig->skip([ 37 | IsNullFixer::class, 38 | MultilineWhitespaceBeforeSemicolonsFixer::class, 39 | NativeFunctionInvocationFixer::class, 40 | ReturnAssignmentFixer::class, 41 | SingleLineCommentStyleFixer::class, 42 | YodaStyleFixer::class, 43 | PhpdocNoPackageFixer::class, 44 | ]); 45 | 46 | $ecsConfig->sets([ 47 | SetList::PSR_12, 48 | ]); 49 | 50 | $ecsConfig->rules([ 51 | NoUselessReturnFixer::class, 52 | NoUselessElseFixer::class, 53 | ModernizeStrposFixer::class, 54 | NoUnusedImportsFixer::class, 55 | NoSuperfluousPhpdocTagsFixer::class, 56 | ReturnTypeDeclarationFixer::class, 57 | ]); 58 | 59 | $ecsConfig->ruleWithConfiguration(ConcatSpaceFixer::class, [ 60 | 'spacing' => 'none', 61 | ]); 62 | 63 | $ecsConfig->ruleWithConfiguration(IncrementStyleFixer::class, [ 64 | 'style' => 'post' 65 | ]); 66 | 67 | $ecsConfig->ruleWithConfiguration(ClassAttributesSeparationFixer::class, [ 68 | 'elements' => ['const' => 'only_if_meta', 'method' => 'one', 'property' => 'one', 'trait_import' => 'only_if_meta'], 69 | ]); 70 | 71 | 72 | $ecsConfig->ruleWithConfiguration(BlankLineBeforeStatementFixer::class, [ 73 | 'statements' => ['if', 'break', 'continue', 'declare', 'return', 'throw', 'try', 'switch'], 74 | ]); 75 | 76 | $ecsConfig->ruleWithConfiguration(BinaryOperatorSpacesFixer::class, [ 77 | 'default' => 'align_single_space_minimal', 78 | 'operators' => [ 79 | '|' => 'no_space', 80 | '/' => null, 81 | '*' => null, 82 | '||' => null, 83 | '&&' => null, 84 | ], 85 | ]); 86 | 87 | $header = <<<'EOF' 88 | @author Aaron Scherer 89 | @date 2019 90 | @license https://opensource.org/licenses/MIT 91 | EOF; 92 | 93 | $ecsConfig->ruleWithConfiguration(HeaderCommentFixer::class, [ 94 | 'header' => $header 95 | ]); 96 | }; 97 | -------------------------------------------------------------------------------- /src/Bundle/SecretaryBundle/DependencyInjection/SecretaryExtension.php: -------------------------------------------------------------------------------- 1 | 7 | * @date 2019 8 | * @license https://opensource.org/licenses/MIT 9 | */ 10 | 11 | namespace Secretary\Bundle\SecretaryBundle\DependencyInjection; 12 | 13 | use Secretary\Adapter\Cache\PSR16Cache\PSR16CacheAdapter; 14 | use Secretary\Adapter\Cache\PSR6Cache\PSR6CacheAdapter; 15 | use Secretary\Bundle\SecretaryBundle\EnvVar\EnvVarProcessor; 16 | use Secretary\Manager; 17 | use Symfony\Component\DependencyInjection\Alias; 18 | use Symfony\Component\DependencyInjection\Argument\IteratorArgument; 19 | use Symfony\Component\DependencyInjection\ContainerBuilder; 20 | use Symfony\Component\DependencyInjection\Extension\Extension; 21 | use Symfony\Component\DependencyInjection\Reference; 22 | 23 | class SecretaryExtension extends Extension 24 | { 25 | public function load(array $configs, ContainerBuilder $container): void 26 | { 27 | $config = $this->processConfiguration(new Configuration(), $configs); 28 | 29 | $services = []; 30 | $default = isset($config['adapters']['default']) ? 'default' : null; 31 | foreach ($config['adapters'] as $name => $arguments) { 32 | if ($default === null) { 33 | $default = $name; 34 | } 35 | 36 | $arguments['config'] = $this->replaceReferences($arguments['config']); 37 | 38 | if ($container->has($arguments['adapter'])) { 39 | $ref = new Reference($arguments['adapter']); 40 | } else { 41 | $adapter = $container->register('secretary.adapter.'.$name, $arguments['adapter']); 42 | $adapter->addArgument($arguments['config']); 43 | $adapter->setPublic(true); 44 | 45 | $ref = new Reference('secretary.adapter.'.$name); 46 | } 47 | 48 | if ($arguments['cache']['enabled']) { 49 | $adapter = $container->register( 50 | 'secretary.adapter.'.$name.'.cache', 51 | $arguments['cache']['type'] === 'psr6' ? PSR6CacheAdapter::class : PSR16CacheAdapter::class 52 | ); 53 | $adapter->addArgument($ref); 54 | $adapter->addArgument(new Reference($arguments['cache']['service_id'])); 55 | $adapter->setPublic(true); 56 | 57 | $ref = new Reference('secretary.adapter.'.$name.'.cache'); 58 | } 59 | 60 | $def = $container->register('secretary.manager.'.$name, Manager::class); 61 | $def->addArgument($ref); 62 | $def->addTag('secretary.manager'); 63 | $def->setPublic(true); 64 | 65 | $services[$name] = new Reference('secretary.manager.'.$name); 66 | } 67 | 68 | if ($default !== null) { 69 | $alias = new Alias('secretary.manager.'.$default, true); 70 | 71 | $container->setAlias('secretary', $alias); 72 | $container->setAlias(Manager::class, $alias); 73 | } 74 | 75 | $container->register('secretary.env_var_processor', EnvVarProcessor::class) 76 | ->addArgument(new IteratorArgument($services)) 77 | ->addTag('container.env_var_processor') 78 | ->setPublic(false); 79 | } 80 | 81 | private function replaceReferences($value) 82 | { 83 | if (is_array($value)) { 84 | foreach ($value as $k => $v) { 85 | $value[$k] = $this->replaceReferences($v); 86 | } 87 | 88 | return $value; 89 | } 90 | 91 | if (!is_string($value)) { 92 | return $value; 93 | } 94 | 95 | if (str_starts_with($value, '@')) { 96 | return new Reference(substr($value, 1)); 97 | } 98 | 99 | return $value; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | phpunit: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | php-version: [8.0, 8.1, 8.2, 8.3] 12 | dependency-version: [prefer-lowest, prefer-stable] 13 | steps: 14 | - name: 'Checkout' 15 | uses: 'actions/checkout@v4' 16 | 17 | - name: 'Install PHP' 18 | uses: 'shivammathur/setup-php@v2' 19 | with: 20 | coverage: 'none' 21 | php-version: '${{ matrix.php-version }}' 22 | 23 | - name: 'Get composer cache directory' 24 | run: echo "composer_cache=$(composer config cache-files-dir)" >> $GITHUB_ENV 25 | 26 | - name: 'Cache dependencies' 27 | uses: actions/cache@v3 28 | with: 29 | path: ${{ env.composer_cache }} 30 | key: deps-php-${{ matrix.php-version }}-composer-${{ hashFiles('composer.json') }}-dep-${{ matrix.dependency-version }} 31 | 32 | - name: 'Install dependencies' 33 | run: composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction --no-suggest 34 | 35 | - name: 'PHPUnit Tests' 36 | run: vendor/bin/phpunit 37 | 38 | static-analysis: 39 | runs-on: ubuntu-latest 40 | 41 | strategy: 42 | matrix: 43 | php-version: [8.0, 8.1, 8.2, 8.3] 44 | dependency-version: [ prefer-lowest, prefer-stable ] 45 | 46 | steps: 47 | - name: 'Checkout' 48 | uses: 'actions/checkout@v4' 49 | 50 | - name: 'Install PHP' 51 | uses: 'shivammathur/setup-php@v2' 52 | with: 53 | coverage: 'none' 54 | php-version: '${{ matrix.php-version }}' 55 | tools: cs2pr 56 | 57 | - name: 'Get composer cache directory' 58 | run: echo "composer_cache=$(composer config cache-files-dir)" >> $GITHUB_ENV 59 | 60 | - name: 'Cache dependencies' 61 | uses: actions/cache@v3 62 | with: 63 | path: ${{ env.composer_cache }} 64 | key: deps-php-${{ matrix.php-version }}-composer-${{ hashFiles('composer.json') }}-dep-${{ matrix.dependency-version }} 65 | 66 | - name: 'Install dependencies' 67 | run: composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction --no-suggest 68 | 69 | - name: 'Psalm' 70 | run: ./vendor/bin/psalm 71 | 72 | code-standards: 73 | runs-on: ubuntu-latest 74 | 75 | strategy: 76 | matrix: 77 | php-version: [8.3] 78 | dependency-version: [ prefer-lowest, prefer-stable ] 79 | 80 | steps: 81 | - name: 'Checkout' 82 | uses: 'actions/checkout@v4' 83 | 84 | - name: 'Install PHP' 85 | uses: 'shivammathur/setup-php@v2' 86 | with: 87 | coverage: 'none' 88 | php-version: '${{ matrix.php-version }}' 89 | tools: cs2pr 90 | 91 | - name: 'Get composer cache directory' 92 | run: echo "composer_cache=$(composer config cache-files-dir)" >> $GITHUB_ENV 93 | 94 | - name: 'Cache dependencies' 95 | uses: actions/cache@v3 96 | with: 97 | path: ${{ env.composer_cache }} 98 | key: deps-php-${{ matrix.php-version }}-composer-${{ hashFiles('composer.json') }}-dep-${{ matrix.dependency-version }} 99 | 100 | - name: 'Install dependencies' 101 | run: composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction --no-suggest 102 | 103 | - name: 'Check code standards' 104 | run: ./vendor/bin/ecs check 105 | -------------------------------------------------------------------------------- /src/Adapter/Local/JSONFile/LocalJSONFileAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * @date 2019 8 | * @license https://opensource.org/licenses/MIT 9 | */ 10 | 11 | namespace Secretary\Adapter\Local\JSONFile; 12 | 13 | use Secretary\Adapter\AbstractAdapter; 14 | use Secretary\Exception\SecretNotFoundException; 15 | use Secretary\Secret; 16 | 17 | /** 18 | * Class LocalJSONFileAdapter. 19 | * 20 | * @package Secretary\Adapter\Local\JSONFile 21 | */ 22 | class LocalJSONFileAdapter extends AbstractAdapter 23 | { 24 | private string $secretsFile; 25 | 26 | private int $jsonOptions; 27 | 28 | /** 29 | * @throws \Exception 30 | */ 31 | public function __construct(array $config) 32 | { 33 | if ($config === []) { 34 | throw new \Exception('Configuration is required.'); 35 | } 36 | 37 | if (!isset($config['file'])) { 38 | throw new \Exception('`file` is a required config.'); 39 | } 40 | 41 | if (!isset($config['jsonOptions'])) { 42 | $config['jsonOptions'] = JSON_PRETTY_PRINT; 43 | } 44 | 45 | $this->secretsFile = $config['file']; 46 | $this->jsonOptions = $config['jsonOptions']; 47 | } 48 | 49 | /** 50 | * {@inheritdoc} 51 | * 52 | * @throws SecretNotFoundException 53 | */ 54 | public function getSecret(string $key, ?array $options = []): Secret 55 | { 56 | $secrets = $this->loadSecrets(); 57 | $keys = array_column($secrets, 'key'); 58 | $index = array_search($key, $keys, true); 59 | 60 | if ($index === false || $index === null) { 61 | throw new SecretNotFoundException($key); 62 | } 63 | 64 | return new Secret( 65 | $secrets[$index]['key'], 66 | $secrets[$index]['value'], 67 | $secrets[$index]['metadata'] ?? null 68 | ); 69 | } 70 | 71 | /** 72 | * {@inheritdoc} 73 | */ 74 | public function putSecret(Secret $secret, ?array $options = []): Secret 75 | { 76 | $secrets = self::updateValue($secret, $this->loadSecrets()); 77 | $this->saveSecrets($secrets); 78 | 79 | return $secret; 80 | } 81 | 82 | /** 83 | * {@inheritdoc} 84 | */ 85 | public function deleteSecretByKey(string $key, ?array $options = []): void 86 | { 87 | $secrets = $this->loadSecrets(); 88 | $keys = array_column($secrets, 'key'); 89 | $index = array_search($key, $keys, true); 90 | 91 | if ($index === false || $index === null) { 92 | throw new SecretNotFoundException($key); 93 | } 94 | 95 | array_splice($secrets, $index, 1); 96 | $this->saveSecrets($secrets); 97 | } 98 | 99 | /** 100 | * {@inheritdoc} 101 | * 102 | * @throws SecretNotFoundException 103 | */ 104 | public function deleteSecret(Secret $secret, ?array $options = []): void 105 | { 106 | $this->deleteSecretByKey($secret->getKey(), $options); 107 | } 108 | 109 | /** 110 | * @param Secret[] $secrets 111 | */ 112 | private static function updateValue(Secret $secret, array $secrets): array 113 | { 114 | $keys = array_column($secrets, 'key'); 115 | $index = array_search($secret->getKey(), $keys, true); 116 | 117 | if ($index === false || $index === null) { 118 | $secrets[] = $secret; 119 | } else { 120 | $secrets[$index]['value'] = $secret->getValue(); 121 | } 122 | 123 | return $secrets; 124 | } 125 | 126 | /** 127 | * @throws \Exception 128 | * 129 | * @return list 130 | */ 131 | private function loadSecrets(): array 132 | { 133 | return json_decode(file_get_contents($this->secretsFile), true, 512, JSON_THROW_ON_ERROR); 134 | } 135 | 136 | private function saveSecrets(array $secrets): void 137 | { 138 | file_put_contents($this->secretsFile, json_encode($secrets, $this->jsonOptions)); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/Core/Tests/ManagerTest.php: -------------------------------------------------------------------------------- 1 | 7 | * @date 2019 8 | * @license https://opensource.org/licenses/MIT 9 | */ 10 | 11 | namespace Secretary\Tests; 12 | 13 | use Mockery\MockInterface; 14 | use PHPUnit\Framework\TestCase; 15 | use Secretary\Adapter\AdapterInterface; 16 | use Secretary\Exception\SecretNotFoundException; 17 | use Secretary\Manager; 18 | use Secretary\Secret; 19 | 20 | /** 21 | * @internal 22 | * @coversNothing 23 | */ 24 | class ManagerTest extends TestCase 25 | { 26 | /** 27 | * @var AdapterInterface|MockInterface 28 | */ 29 | private $adapter; 30 | 31 | protected function setUp(): void 32 | { 33 | parent::setUp(); 34 | 35 | $this->adapter = \Mockery::mock(AdapterInterface::class); 36 | } 37 | 38 | /** 39 | * @psalm-suppress RedundantCondition 40 | */ 41 | public function testConstruct(): void 42 | { 43 | $manager = new Manager($this->adapter); 44 | 45 | $this->assertInstanceOf(Manager::class, $manager); 46 | } 47 | 48 | /** 49 | * @psalm-suppress RedundantCondition 50 | */ 51 | public function testGetSecret(): void 52 | { 53 | $secret = new Secret('foo', 'bar'); 54 | $manager = new Manager($this->adapter); 55 | 56 | $this->adapter->shouldReceive('configureSharedOptions')->withAnyArgs()->once(); 57 | $this->adapter->shouldReceive('configureGetSecretOptions')->withAnyArgs()->once(); 58 | $this->adapter->shouldReceive('getSecret')->with('foo', [])->andReturn($secret)->once(); 59 | 60 | $result = $manager->getSecret('foo'); 61 | $this->assertInstanceOf(Secret::class, $result); 62 | $this->assertEquals($secret, $result); 63 | } 64 | 65 | public function testGetBadSecret(): void 66 | { 67 | $this->expectException(SecretNotFoundException::class); 68 | $this->expectExceptionMessage('No secret was found with the key: "foo"'); 69 | 70 | $manager = new Manager($this->adapter); 71 | 72 | $this->adapter->shouldReceive('configureSharedOptions')->withAnyArgs()->once(); 73 | $this->adapter->shouldReceive('configureGetSecretOptions')->withAnyArgs()->once(); 74 | $this->adapter->shouldReceive('getSecret') 75 | ->with('foo', [])->andThrow(new SecretNotFoundException('foo'))->once(); 76 | 77 | $manager->getSecret('foo'); 78 | } 79 | 80 | public function testPutSecret(): void 81 | { 82 | $manager = new Manager($this->adapter); 83 | $secret = new Secret('foo', 'bar'); 84 | 85 | $this->adapter->shouldReceive('configureSharedOptions')->withAnyArgs()->once(); 86 | $this->adapter->shouldReceive('configurePutSecretOptions')->withAnyArgs()->once(); 87 | $this->adapter->shouldReceive('putSecret')->with($secret, [])->andReturn($secret)->once(); 88 | 89 | $response = $manager->putSecret($secret); 90 | $this->assertEquals($secret, $response); 91 | } 92 | 93 | public function testDeleteSecretByKey(): void 94 | { 95 | $manager = new Manager($this->adapter); 96 | $secret = new Secret('foo', ''); 97 | 98 | $this->adapter->shouldReceive('configureSharedOptions')->withAnyArgs()->twice(); 99 | 100 | $this->adapter->shouldReceive('configureGetSecretOptions')->withAnyArgs()->once(); 101 | $this->adapter->shouldReceive('getSecret')->with('foo', [])->andReturn($secret)->once(); 102 | 103 | $this->adapter->shouldReceive('configureDeleteSecretOptions')->withAnyArgs()->once(); 104 | $this->adapter->shouldReceive('deleteSecret')->with($secret, [])->once(); 105 | 106 | $manager->deleteSecretByKey('foo'); 107 | $this->assertTrue(true); 108 | } 109 | 110 | public function testDeleteSecret(): void 111 | { 112 | $manager = new Manager($this->adapter); 113 | $secret = new Secret('foo', 'bar'); 114 | 115 | $this->adapter->shouldReceive('configureSharedOptions')->withAnyArgs()->once(); 116 | $this->adapter->shouldReceive('configureDeleteSecretOptions')->withAnyArgs()->once(); 117 | $this->adapter->shouldReceive('deleteSecret')->with($secret, [])->once(); 118 | 119 | $manager->deleteSecret($secret); 120 | $this->assertTrue(true); 121 | } 122 | 123 | public function testGetAdapter(): void 124 | { 125 | $manager = new Manager($this->adapter); 126 | 127 | $this->assertEquals($this->adapter, $manager->getAdapter()); 128 | $this->assertInstanceOf(AdapterInterface::class, $manager->getAdapter()); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/Adapter/AWS/SecretsManager/AWSSecretsManagerAdapter.php: -------------------------------------------------------------------------------- 1 | 7 | * @date 2019 8 | * @license https://opensource.org/licenses/MIT 9 | */ 10 | 11 | namespace Secretary\Adapter\AWS\SecretsManager; 12 | 13 | use Aws\SecretsManager\Exception\SecretsManagerException; 14 | use Aws\SecretsManager\SecretsManagerClient; 15 | use Secretary\Adapter\AbstractAdapter; 16 | use Secretary\Exception\SecretNotFoundException; 17 | use Secretary\Helper\ArrayHelper; 18 | use Secretary\Secret; 19 | use Symfony\Component\OptionsResolver\OptionsResolver; 20 | 21 | /** 22 | * Class AWSSecretsManagerAdapter. 23 | * 24 | * @package Secretary\Adapter\AWS\SecretsManager 25 | */ 26 | class AWSSecretsManagerAdapter extends AbstractAdapter 27 | { 28 | private ?SecretsManagerClient $client = null; 29 | 30 | private array $config; 31 | 32 | /** 33 | * @throws \Exception 34 | */ 35 | public function __construct(array $config) 36 | { 37 | if (!class_exists(SecretsManagerClient::class)) { 38 | throw new \Exception('aws/aws-sdk-php is required to use the AWSSecretsManagerAdapter'); 39 | } 40 | 41 | $this->config = $config; 42 | } 43 | 44 | /** 45 | * {@inheritdoc} 46 | */ 47 | public function getSecret(string $key, ?array $options = []): Secret 48 | { 49 | $options['SecretId'] = $key; 50 | 51 | try { 52 | $data = $this->getClient()->getSecretValue($options); 53 | 54 | /** @var string $secretString */ 55 | $secretString = $data->get('SecretString'); 56 | 57 | return new Secret( 58 | $key, 59 | static::isJson($secretString) ? json_decode($data->get('SecretString'), true) : $secretString 60 | ); 61 | } catch (SecretsManagerException $exception) { 62 | $awsMsg = $exception->getAwsErrorMessage() ?? ''; 63 | 64 | if (str_contains($awsMsg, 'Secrets Manager can’t find the specified secret')) { 65 | throw new SecretNotFoundException($key, $exception); 66 | } 67 | 68 | throw $exception; 69 | } 70 | } 71 | 72 | /** 73 | * {@inheritdoc} 74 | */ 75 | public function putSecret(Secret $secret, ?array $options = []): Secret 76 | { 77 | $options['SecretString'] = is_array($secret->getValue()) 78 | ? json_encode($secret->getValue()) : $secret->getValue(); 79 | 80 | try { 81 | $options = ArrayHelper::without($options, 'Tags'); 82 | $options['SecretId'] = $secret->getKey(); 83 | 84 | $this->getClient()->updateSecret($options); 85 | } catch (\Exception $e) { 86 | $options['Name'] = $secret->getKey(); 87 | 88 | $this->getClient()->createSecret($options); 89 | } 90 | 91 | return $secret; 92 | } 93 | 94 | /** 95 | * {@inheritdoc} 96 | */ 97 | public function deleteSecretByKey(string $key, ?array $options = []): void 98 | { 99 | $options['SecretId'] = $key; 100 | 101 | $this->getClient()->deleteSecret($options); 102 | } 103 | 104 | /** 105 | * {@inheritdoc} 106 | */ 107 | public function deleteSecret(Secret $secret, ?array $options = []): void 108 | { 109 | $this->deleteSecretByKey($secret->getKey(), $options); 110 | } 111 | 112 | public function configureGetSecretOptions(OptionsResolver $resolver): void 113 | { 114 | parent::configureSharedOptions($resolver); 115 | $resolver->setDefined(['VersionId', 'VersionStage']) 116 | ->setAllowedTypes('VersionId', 'string') 117 | ->setAllowedTypes('VersionStage', 'string'); 118 | } 119 | 120 | public function configurePutSecretOptions(OptionsResolver $resolver): void 121 | { 122 | parent::configureSharedOptions($resolver); 123 | $resolver->setDefined(['KmsKeyId', 'Tags', 'Description']) 124 | ->setAllowedTypes('KmsKeyId', 'string') 125 | ->setAllowedTypes('Description', 'string'); 126 | } 127 | 128 | public function configureDeleteSecretOptions(OptionsResolver $resolver): void 129 | { 130 | parent::configureDeleteSecretOptions($resolver); 131 | $resolver 132 | ->setDefined(['ForceDeleteWithoutRecovery', 'RecoveryWindowInDays']) 133 | ->setAllowedTypes('ForceDeleteWithoutRecovery', 'bool') 134 | ->setAllowedTypes('RecoveryWindowInDays', 'int'); 135 | } 136 | 137 | private function getClient(): SecretsManagerClient 138 | { 139 | if (!$this->client instanceof SecretsManagerClient) { 140 | $this->client = new SecretsManagerClient($this->config); 141 | } 142 | 143 | return $this->client; 144 | } 145 | 146 | private static function isJson(string $str) 147 | { 148 | $json = json_decode($str); 149 | 150 | return $json && $str != $json; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [3.0.6](https://github.com/secretary/php/compare/3.0.5...3.0.6) (2025-02-05) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * Fixed Symfony deprecation ([03f1da5](https://github.com/secretary/php/commit/03f1da511717ba3dc8cbb70faf75772218ed6373)) 7 | 8 | ## [3.0.5](https://github.com/secretary/php/compare/3.0.4...3.0.5) (2025-02-05) 9 | 10 | 11 | ### Bug Fixes 12 | 13 | * Fixed PHP 8.4 deprecation ([19e99e7](https://github.com/secretary/php/commit/19e99e7a7c6f37cdbf33fef887c7d11ba44cfef9)) 14 | 15 | ## [3.0.4](https://github.com/secretary/php/compare/3.0.3...3.0.4) (2024-01-29) 16 | 17 | 18 | ### Bug Fixes 19 | 20 | * ArrayHelper remove method ([61d1477](https://github.com/secretary/php/commit/61d147755c1091bbf9b08b6792debd041a0286fd)) 21 | 22 | ## [3.0.3](https://github.com/secretary/php/compare/3.0.2...3.0.3) (2024-01-15) 23 | 24 | 25 | ### Bug Fixes 26 | 27 | * Optimized array helper method ([f996d0b](https://github.com/secretary/php/commit/f996d0b6fd9e524787535bb3683fb884450d5d3e)) 28 | 29 | ## [3.0.2](https://github.com/secretary/php/compare/3.0.1...3.0.2) (2024-01-11) 30 | 31 | 32 | ### Bug Fixes 33 | 34 | * Array helper ([4098f7e](https://github.com/secretary/php/commit/4098f7e4d578fefb71a70739976783d72e4a238f)) 35 | 36 | ## [3.0.1](https://github.com/secretary/php/compare/3.0.0...3.0.1) (2024-01-10) 37 | 38 | 39 | ### Bug Fixes 40 | 41 | * **CI:** Updated node and semantic-release ([096e283](https://github.com/secretary/php/commit/096e283ce38a77ba0901746612f2be1320867900)) 42 | 43 | ## [2.1.1](https://github.com/secretary/php/compare/2.1.0...2.1.1) (2024-01-09) 44 | 45 | 46 | ### Bug Fixes 47 | 48 | * Allow PHP Unit 10 ([f20907e](https://github.com/secretary/php/commit/f20907efcf2b84c3c07b1d2e01747e54ecfb42e5)) 49 | 50 | ## [2.0.1](https://github.com/secretary/php/compare/2.0.0...2.0.1) (2024-01-09) 51 | 52 | 53 | ### Bug Fixes 54 | 55 | * **global:** Allow Symfony 7 ([#17](https://github.com/secretary/php/issues/17)) ([86ef910](https://github.com/secretary/php/commit/86ef9100d7f233f0c57df148a0ee24886e6eefbd)) 56 | 57 | 58 | ### Performance Improvements 59 | 60 | * **Symfony:** Allow SF 7 ([ac9146c](https://github.com/secretary/php/commit/ac9146c583a93a8706aaea7b770ab83ee510536c)) 61 | 62 | # [2.0.0](https://github.com/secretary/php/compare/1.4.0...2.0.0) (2022-02-12) 63 | 64 | 65 | ### Performance Improvements 66 | 67 | * **PHP:** Dropped support for PHP 7.4 ([b14daf1](https://github.com/secretary/php/commit/b14daf1822875fa2c3f7cb2f8840737df7fed1bc)) 68 | 69 | 70 | ### BREAKING CHANGES 71 | 72 | * **PHP:** Removed support for PHP 7.4 73 | 74 | # [1.4.0](https://github.com/secretary/php/compare/1.3.2...1.4.0) (2022-02-11) 75 | 76 | 77 | ### Bug Fixes 78 | 79 | * **CI:** Replaced Travis CI with GH workflow ([0c9fdb3](https://github.com/secretary/php/commit/0c9fdb3b3110b8060be96a06d0766bff4549bca6)) 80 | * **CI:** Replaced Travis CI with GH workflow ([a88154d](https://github.com/secretary/php/commit/a88154d88c8976d2990f6c62d1cbd8d0394c62ca)) 81 | 82 | 83 | ### Features 84 | 85 | * **global:** PHP 7.4 as minimum PHP version ([eb2ff91](https://github.com/secretary/php/commit/eb2ff91db84c3924eab57f43d59aa5c4eac61e4d)) 86 | 87 | ## [1.3.2](https://github.com/secretary/php/compare/1.3.1...1.3.2) (2021-02-28) 88 | 89 | 90 | ### Bug Fixes 91 | 92 | * **chore:** Triggering release ([7d43834](https://github.com/secretary/php/commit/7d4383441f058377bca4258408d38e05eb81853a)) 93 | 94 | ## [1.3.1](https://github.com/secretary/php/compare/1.3.0...1.3.1) (2021-02-23) 95 | 96 | 97 | ### Bug Fixes 98 | 99 | * **chore:** Triggering release ([74123e6](https://github.com/secretary/php/commit/74123e632d1c182a4d03ebf6852ca2e7a6dade1a)) 100 | 101 | # [1.3.0](https://github.com/secretary/php/compare/1.2.18...1.3.0) (2021-02-14) 102 | 103 | 104 | ### Bug Fixes 105 | 106 | * **travis:** tests for php 8 ([9889dbf](https://github.com/secretary/php/commit/9889dbfd9fa639d6a942c2b2577323beccadb923)) 107 | * **travis:** tests for php 8 ([4ddfeaa](https://github.com/secretary/php/commit/4ddfeaaf27b8558a49d1a9381c2df335dbc0a96f)) 108 | * **travis:** tests for php 8 ([425918e](https://github.com/secretary/php/commit/425918e173f99ce623ab96ac2993675788d18d51)) 109 | 110 | 111 | ### Features 112 | 113 | * **global:** PHP 8 Support ([27be33d](https://github.com/secretary/php/commit/27be33d2771b56e938d38e335c442a19c3427c74)) 114 | 115 | ## [1.2.18](https://github.com/secretary/php/compare/1.2.17...1.2.18) (2019-08-15) 116 | 117 | 118 | ### Bug Fixes 119 | 120 | * **bundle:** Fixing cache adapters ([e6fd44d](https://github.com/secretary/php/commit/e6fd44d)) 121 | * **bundle:** Fixing cache adapters ([97504a2](https://github.com/secretary/php/commit/97504a2)) 122 | 123 | ## [1.2.17](https://github.com/secretary/php/compare/1.2.16...1.2.17) (2019-08-15) 124 | 125 | 126 | ### Bug Fixes 127 | 128 | * **aws:** Throwing error when the value for a secret can't be found ([78baf8c](https://github.com/secretary/php/commit/78baf8c)) 129 | 130 | ## [1.2.16](https://github.com/secretary/php/compare/1.2.15...1.2.16) (2019-08-15) 131 | 132 | 133 | ### Bug Fixes 134 | 135 | * **aws:** Throwing error when the value for a secret can't be found ([9202f85](https://github.com/secretary/php/commit/9202f85)) 136 | 137 | ## [1.2.15](https://github.com/secretary/php/compare/1.2.14...1.2.15) (2019-08-14) 138 | 139 | 140 | ### Bug Fixes 141 | 142 | * **aws:** Throwing error when the value for a secret can't be found ([38c7818](https://github.com/secretary/php/commit/38c7818)) 143 | 144 | ## [1.2.14](https://github.com/secretary/php/compare/1.2.13...1.2.14) (2019-08-14) 145 | 146 | 147 | ### Bug Fixes 148 | 149 | * **aws:** Throwing error when the value for a secret can't be found ([e99e036](https://github.com/secretary/php/commit/e99e036)) 150 | 151 | ## [1.2.13](https://github.com/secretary/php/compare/1.2.12...1.2.13) (2019-08-14) 152 | 153 | 154 | ### Bug Fixes 155 | 156 | * **bundle:** Fixing runtime error in key-not-found scenario ([92b1694](https://github.com/secretary/php/commit/92b1694)) 157 | * **bundle:** Fixing runtime error in key-not-found scenario ([d698cf5](https://github.com/secretary/php/commit/d698cf5)) 158 | 159 | ## [1.2.12](https://github.com/secretary/php/compare/1.2.11...1.2.12) (2019-08-14) 160 | 161 | 162 | ### Bug Fixes 163 | 164 | * **chain:** Fixing chain adapter ([6449bb4](https://github.com/secretary/php/commit/6449bb4)) 165 | 166 | ## [1.2.11](https://github.com/secretary/php/compare/1.2.10...1.2.11) (2019-08-13) 167 | 168 | 169 | ### Bug Fixes 170 | 171 | * **bundle:** Making adapters public ([3b5f4ef](https://github.com/secretary/php/commit/3b5f4ef)) 172 | 173 | ## [1.2.10](https://github.com/secretary/php/compare/1.2.9...1.2.10) (2019-08-13) 174 | 175 | 176 | ### Bug Fixes 177 | 178 | * **chain:** Fixing bad package name ([074eb9c](https://github.com/secretary/php/commit/074eb9c)) 179 | 180 | ## [1.2.9](https://github.com/secretary/php/compare/1.2.8...1.2.9) (2019-08-13) 181 | 182 | 183 | ### Bug Fixes 184 | 185 | * **json:** Check metadata ([#4](https://github.com/secretary/php/issues/4)) ([80ef20e](https://github.com/secretary/php/commit/80ef20e)) 186 | 187 | ## [1.2.8](https://github.com/secretary/php/compare/1.2.7...1.2.8) (2019-08-13) 188 | 189 | 190 | ### Bug Fixes 191 | 192 | * **travis:** Fixing flow ([39284e8](https://github.com/secretary/php/commit/39284e8)) 193 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | # charset = utf-8 5 | # end_of_line = lf 6 | # indent_size = 4 7 | # indent_style = space 8 | insert_final_newline = true 9 | # max_line_length = 120 10 | # tab_width = 4 11 | # ij_continuation_indent_size = 8 12 | # ij_formatter_off_tag = @formatter:off 13 | # ij_formatter_on_tag = @formatter:on 14 | # ij_formatter_tags_enabled = false 15 | # ij_smart_tabs = false 16 | # ij_wrap_on_typing = false 17 | 18 | [{*.yml,*.yaml}] 19 | ij_continuation_indent_size = 2 20 | # ij_yaml_keep_indents_on_empty_lines = false 21 | # ij_yaml_keep_line_breaks = true 22 | 23 | [{console,*.module,*.hphp,*.phtml,*.php5,*.php4,*.php,*.ctp,*.inc}] 24 | indent_style = space 25 | indent_size = 4 26 | # ij_continuation_indent_size = 4 27 | ij_php_align_assignments = true 28 | ij_php_align_class_constants = true 29 | ij_php_align_group_field_declarations = true 30 | # ij_php_align_inline_comments = false 31 | ij_php_align_key_value_pairs = true 32 | # ij_php_align_multiline_array_initializer_expression = false 33 | # ij_php_align_multiline_binary_operation = false 34 | # ij_php_align_multiline_chained_methods = false 35 | # ij_php_align_multiline_extends_list = false 36 | # ij_php_align_multiline_for = true 37 | # ij_php_align_multiline_parameters = true 38 | # ij_php_align_multiline_parameters_in_calls = false 39 | # ij_php_align_multiline_ternary_operation = false 40 | ij_php_align_phpdoc_comments = false 41 | ij_php_align_phpdoc_param_names = true 42 | # ij_php_api_weight = 28 43 | # ij_php_array_initializer_new_line_after_left_brace = false 44 | # ij_php_array_initializer_right_brace_on_new_line = false 45 | # ij_php_array_initializer_wrap = off 46 | # ij_php_assignment_wrap = off 47 | # ij_php_author_weight = 28 48 | # ij_php_binary_operation_sign_on_next_line = false 49 | # ij_php_binary_operation_wrap = off 50 | # ij_php_blank_lines_after_class_header = 0 51 | # ij_php_blank_lines_after_function = 1 52 | # ij_php_blank_lines_after_imports = 1 53 | # ij_php_blank_lines_after_opening_tag = 0 54 | # ij_php_blank_lines_after_package = 0 55 | # ij_php_blank_lines_around_class = 1 56 | # ij_php_blank_lines_around_constants = 0 57 | # ij_php_blank_lines_around_field = 0 58 | # ij_php_blank_lines_around_method = 1 59 | # ij_php_blank_lines_before_class_end = 0 60 | # ij_php_blank_lines_before_imports = 1 61 | # ij_php_blank_lines_before_method_body = 0 62 | # ij_php_blank_lines_before_package = 1 63 | ij_php_blank_lines_before_return_statement = 1 64 | # ij_php_block_brace_style = end_of_line 65 | # ij_php_call_parameters_new_line_after_left_paren = false 66 | # ij_php_call_parameters_right_paren_on_new_line = false 67 | # ij_php_call_parameters_wrap = off 68 | # ij_php_catch_on_new_line = false 69 | # ij_php_category_weight = 28 70 | # ij_php_class_brace_style = next_line 71 | ij_php_comma_after_last_array_element = true 72 | ij_php_concat_spaces = false 73 | # ij_php_copyright_weight = 28 74 | # ij_php_deprecated_weight = 28 75 | # ij_php_do_while_brace_force = never 76 | # ij_php_else_if_style = as_is 77 | # ij_php_else_on_new_line = false 78 | # ij_php_example_weight = 28 79 | # ij_php_extends_keyword_wrap = off 80 | # ij_php_extends_list_wrap = off 81 | # ij_php_fields_default_visibility = private 82 | # ij_php_filesource_weight = 28 83 | # ij_php_finally_on_new_line = false 84 | # ij_php_for_brace_force = never 85 | # ij_php_for_statement_new_line_after_left_paren = false 86 | # ij_php_for_statement_right_paren_on_new_line = false 87 | # ij_php_for_statement_wrap = off 88 | # ij_php_force_short_declaration_array_style = false 89 | # ij_php_global_weight = 28 90 | # ij_php_group_use_wrap = on_every_item 91 | # ij_php_if_brace_force = never 92 | # ij_php_if_lparen_on_next_line = false 93 | # ij_php_if_rparen_on_next_line = false 94 | # ij_php_ignore_weight = 28 95 | # ij_php_import_sorting = alphabetic 96 | # ij_php_indent_break_from_case = true 97 | # ij_php_indent_case_from_switch = true 98 | # ij_php_indent_code_in_php_tags = false 99 | # ij_php_internal_weight = 28 100 | # ij_php_keep_blank_lines_after_lbrace = 2 101 | # ij_php_keep_blank_lines_before_right_brace = 2 102 | # ij_php_keep_blank_lines_in_code = 2 103 | # ij_php_keep_blank_lines_in_declarations = 2 104 | # ij_php_keep_control_statement_in_one_line = true 105 | # ij_php_keep_first_column_comment = true 106 | # ij_php_keep_indents_on_empty_lines = false 107 | # ij_php_keep_line_breaks = true 108 | # ij_php_keep_rparen_and_lbrace_on_one_line = false 109 | # ij_php_keep_simple_methods_in_one_line = false 110 | # ij_php_lambda_brace_style = end_of_line 111 | # ij_php_license_weight = 28 112 | # ij_php_line_comment_add_space = false 113 | # ij_php_line_comment_at_first_column = true 114 | # ij_php_link_weight = 28 115 | # ij_php_lower_case_boolean_const = false 116 | # ij_php_lower_case_null_const = false 117 | # ij_php_method_brace_style = next_line 118 | # ij_php_method_call_chain_wrap = off 119 | # ij_php_method_parameters_new_line_after_left_paren = false 120 | # ij_php_method_parameters_right_paren_on_new_line = false 121 | # ij_php_method_parameters_wrap = off 122 | # ij_php_method_weight = 28 123 | # ij_php_modifier_list_wrap = false 124 | # ij_php_multiline_chained_calls_semicolon_on_new_line = false 125 | # ij_php_namespace_brace_style = 1 126 | # ij_php_null_type_position = in_the_end 127 | # ij_php_package_weight = 28 128 | # ij_php_param_weight = 0 129 | # ij_php_parentheses_expression_new_line_after_left_paren = false 130 | # ij_php_parentheses_expression_right_paren_on_new_line = false 131 | # ij_php_phpdoc_blank_line_before_tags = false 132 | ij_php_phpdoc_blank_lines_around_parameters = true 133 | # ij_php_phpdoc_keep_blank_lines = true 134 | # ij_php_phpdoc_param_spaces_between_name_and_description = 1 135 | # ij_php_phpdoc_param_spaces_between_tag_and_type = 1 136 | # ij_php_phpdoc_param_spaces_between_type_and_name = 1 137 | # ij_php_phpdoc_use_fqcn = false 138 | # ij_php_phpdoc_wrap_long_lines = false 139 | # ij_php_place_assignment_sign_on_next_line = false 140 | # ij_php_place_parens_for_constructor = 0 141 | # ij_php_property_read_weight = 28 142 | # ij_php_property_weight = 28 143 | # ij_php_property_write_weight = 28 144 | # ij_php_return_type_on_new_line = false 145 | # ij_php_return_weight = 1 146 | # ij_php_see_weight = 28 147 | # ij_php_since_weight = 28 148 | ij_php_sort_phpdoc_elements = false 149 | # ij_php_space_after_colon = true 150 | ij_php_space_after_colon_in_return_type = true 151 | # ij_php_space_after_comma = true 152 | # ij_php_space_after_for_semicolon = true 153 | # ij_php_space_after_quest = true 154 | ij_php_space_after_type_cast = true 155 | # ij_php_space_after_unary_not = false 156 | # ij_php_space_before_array_initializer_left_brace = false 157 | # ij_php_space_before_catch_keyword = true 158 | # ij_php_space_before_catch_left_brace = true 159 | # ij_php_space_before_catch_parentheses = true 160 | # ij_php_space_before_class_left_brace = true 161 | # ij_php_space_before_closure_left_parenthesis = true 162 | # ij_php_space_before_colon = true 163 | # ij_php_space_before_colon_in_return_type = false 164 | # ij_php_space_before_comma = false 165 | # ij_php_space_before_do_left_brace = true 166 | # ij_php_space_before_else_keyword = true 167 | # ij_php_space_before_else_left_brace = true 168 | # ij_php_space_before_finally_keyword = true 169 | # ij_php_space_before_finally_left_brace = true 170 | # ij_php_space_before_for_left_brace = true 171 | # ij_php_space_before_for_parentheses = true 172 | # ij_php_space_before_for_semicolon = false 173 | # ij_php_space_before_if_left_brace = true 174 | # ij_php_space_before_if_parentheses = true 175 | # ij_php_space_before_method_call_parentheses = false 176 | # ij_php_space_before_method_left_brace = true 177 | # ij_php_space_before_method_parentheses = false 178 | # ij_php_space_before_quest = true 179 | # ij_php_space_before_switch_left_brace = true 180 | # ij_php_space_before_switch_parentheses = true 181 | # ij_php_space_before_try_left_brace = true 182 | # ij_php_space_before_unary_not = false 183 | # ij_php_space_before_while_keyword = true 184 | # ij_php_space_before_while_left_brace = true 185 | # ij_php_space_before_while_parentheses = true 186 | # ij_php_space_between_ternary_quest_and_colon = false 187 | # ij_php_spaces_around_additive_operators = true 188 | # ij_php_spaces_around_arrow = false 189 | # ij_php_spaces_around_assignment_in_declare = false 190 | # ij_php_spaces_around_assignment_operators = true 191 | # ij_php_spaces_around_bitwise_operators = true 192 | # ij_php_spaces_around_equality_operators = true 193 | # ij_php_spaces_around_logical_operators = true 194 | # ij_php_spaces_around_multiplicative_operators = true 195 | # ij_php_spaces_around_null_coalesce_operator = true 196 | # ij_php_spaces_around_relational_operators = true 197 | # ij_php_spaces_around_shift_operators = true 198 | # ij_php_spaces_around_unary_operator = false 199 | # ij_php_spaces_around_var_within_brackets = false 200 | # ij_php_spaces_within_array_initializer_braces = false 201 | # ij_php_spaces_within_brackets = false 202 | # ij_php_spaces_within_catch_parentheses = false 203 | # ij_php_spaces_within_for_parentheses = false 204 | # ij_php_spaces_within_if_parentheses = false 205 | # ij_php_spaces_within_method_call_parentheses = false 206 | # ij_php_spaces_within_method_parentheses = false 207 | # ij_php_spaces_within_parentheses = false 208 | # ij_php_spaces_within_short_echo_tags = true 209 | # ij_php_spaces_within_switch_parentheses = false 210 | # ij_php_spaces_within_while_parentheses = false 211 | # ij_php_special_else_if_treatment = false 212 | # ij_php_subpackage_weight = 28 213 | # ij_php_ternary_operation_signs_on_next_line = false 214 | # ij_php_ternary_operation_wrap = off 215 | # ij_php_throws_weight = 2 216 | # ij_php_todo_weight = 28 217 | # ij_php_unknown_tag_weight = 28 218 | # ij_php_upper_case_boolean_const = false 219 | # ij_php_upper_case_null_const = false 220 | # ij_php_uses_weight = 28 221 | # ij_php_var_weight = 28 222 | # ij_php_variable_naming_style = mixed 223 | # ij_php_version_weight = 28 224 | # ij_php_while_brace_force = never 225 | # ij_php_while_on_new_line = false 226 | -------------------------------------------------------------------------------- /src/Core/README.md: -------------------------------------------------------------------------------- 1 | # Secretary - Secrets Manager for PHP 2 | [![Latest Stable Version](https://poser.pugx.org/secretary/core/version)](https://packagist.org/packages/secretary/core) [![Total Downloads](https://poser.pugx.org/secretary/core/downloads)](https://packagist.org/packages/secretary/core) 3 | 4 | Secrets are an important aspect of most applications you can build. How you store them, and keep them "secret" is a challenge. 5 | Luckily, there are tools you can use to keep them all safe. 6 | 7 | Secretary is a tool to integrate your PHP application with these tools. 8 | 9 | ## Table of Contents 10 | 11 | 1. [Installation](#installation) 12 | 2. [Api Documentation](#api-documentation) 13 | 1. [Secretary\Manager](#manager-class) 14 | 1. [Initializing](#manager-constructor) 15 | 2. [getSecret](#manager-getSecret) 16 | 3. [putSecret](#manager-putSecret) 17 | 4. [deleteSecret](#manager-deleteSecret) 18 | 5. [getAdapter](#manager-getAdapter) 19 | 2. [Secretary\Secret](#secret-class) 20 | 1. [getKey](#secret-getKey) 21 | 1. [getValue](#secret-getValue) 22 | 23 | ## Installation 24 | 25 | ```bash 26 | $ composer req secretary/core 27 | ``` 28 | 29 | ##### Choose the version you need 30 | 31 | | Version (X.Y.Z) | PHP | Symfony | Comment | 32 | |:---------------:|:----------:|:----------:|:--------------------| 33 | | `3.*` | `>= 8.1.0` | `7.0` | **Current version** | 34 | | `2.*` | `>= 8.1.0` | `5.4, 6.0` | Previous version | 35 | | `1.*` | `>= 7.4.0` | `5.3` | Previous version | 36 | 37 | By itself, the core is useless. You will also need to add at least one adapter: 38 | 39 | | Storage Engine | Badges | 40 | | -------------- | -------- | 41 | | [AWS Secrets Manager][aws-secrets-manager-adapter] | [![Latest Stable Version](https://poser.pugx.org/secretary/aws-secrets-manager-adapter/version)](https://packagist.org/packages/secretary/aws-secrets-manager-adapter) [![Total Downloads](https://poser.pugx.org/secretary/aws-secrets-manager-adapter/downloads)](https://packagist.org/packages/secretary/aws-secrets-manager-adapter) | 42 | | [HashiCorp Vault][hashicorp-vault-adapter] | [![Latest Stable Version](https://poser.pugx.org/secretary/hashicorp-vault-adapter/version)](https://packagist.org/packages/secretary/hashicorp-vault-adapter) [![Total Downloads](https://poser.pugx.org/secretary/hashicorp-vault-adapter/downloads)](https://packagist.org/packages/secretary/hashicorp-vault-adapter) | 43 | | [JSON File][json-file-adapter] | [![Latest Stable Version](https://poser.pugx.org/secretary/local-json-file-adapter/version)](https://packagist.org/packages/secretary/local-json-file-adapter) [![Total Downloads](https://poser.pugx.org/secretary/local-json-file-adapter/downloads)](https://packagist.org/packages/secretary/local-json-file-adapter) | 44 | 45 | There are also miscellaneous packages that add on to Secretary 46 | 47 | | Package | Purpose | Badges | 48 | | ------- | ------- | ------ | 49 | | [PSR-6 Cache Adapter][psr-6-cache-adapter] | Allows for caching secrets using a PSR-6 Cache Interface | [![Latest Stable Version](https://poser.pugx.org/secretary/psr-6-cache-adapter/version)](https://packagist.org/packages/secretary/psr-6-cache-adapter) [![Total Downloads](https://poser.pugx.org/secretary/psr-6-cache-adapter/downloads)](https://packagist.org/packages/secretary/psr-6-cache-adapter) | 50 | | [PSR-16 Cache Adapter][psr-16-cache-adapter] | Allows for caching secrets using a PSR-16 Cache Interface | [![Latest Stable Version](https://poser.pugx.org/secretary/psr-16-cache-adapter/version)](https://packagist.org/packages/secretary/psr-16-cache-adapter) [![Total Downloads](https://poser.pugx.org/secretary/psr-16-cache-adapter/downloads)](https://packagist.org/packages/secretary/psr-16-cache-adapter) | 51 | | [Secretary Bundle][secretary-bundle] | Allows for integrating with the Symfony Framework | [![Latest Stable Version](https://poser.pugx.org/secretary/secretary-bundle/version)](https://packagist.org/packages/secretary/secretary-bundle) [![Total Downloads](https://poser.pugx.org/secretary/secretary-bundle/downloads)](https://packagist.org/packages/secretary/secretary-bundle) | 52 | 53 | ## Api Documentation 54 | 55 | There's two classes you interface with in Secretary: 56 | 57 | * [`Secretary\Manager`][Secretary\Manager::class] 58 | * [`Secretary\Secret`][Secretary\Secret::class] 59 | 60 | 61 | 62 | ### Secretary\Manager 63 | 64 | 65 | 66 | #### Secretary\Manager->__construct(AdapterInterface $adapter) 67 | 68 | Pass in your desired adapter. 69 | 70 | ```php 71 | 'us-east-1', 78 | 'credentials' => [ 79 | 'accessKeyId' => 'myAccessKeyId', 80 | 'secretAccessKey' => 'mySecretAccessKey' 81 | ] 82 | ]) 83 | ); 84 | ``` 85 | 86 | Optionally, you may wrap your adapter, with one of the two cache adapters. 87 | 88 | ```php 89 | 'us-east-1', 100 | 'credentials' => [ 101 | 'accessKeyId' => 'myAccessKeyId', 102 | 'secretAccessKey' => 'mySecretAccessKey' 103 | ] 104 | ]), 105 | new ApcCachePool() 106 | ) 107 | ); 108 | ``` 109 | 110 | For mor information on the arguments and options for the adapters, view their respective documentation. 111 | 112 | 113 | 114 | #### Secretary\Manager->getSecret(string $key, ?array $options): Secret 115 | 116 | Fetches a secret from the configured adapter. `$key` is the name of the secret (or path) you are trying to get. 117 | 118 | Certain adapters will take custom options as well, like VersionId and VersionStage for the AWS SecretsManager Adapter 119 | 120 | This will throw a `Secretary\SecretNotFoundException` if the secret cannot be found 121 | 122 | ```php 123 | $secret = $manager->getSecret('databases/redis/dsn'); 124 | /* 125 | Secret { 126 | "path" = "databases/redis/dsn", 127 | "value" = "redis://localhost:6379" 128 | } 129 | */ 130 | ``` 131 | 132 | Some adapters also support storing a key/value map as a secret's value. 133 | 134 | ```php 135 | $secret = $manager->getSecret('databases/redis'); 136 | /* 137 | Secret { 138 | "path" = "databases/redis", 139 | "value" = [ 140 | "dsn" => "redis://localhost:6379", 141 | "password" => "my_super_strong_password" 142 | ] 143 | } 144 | */ 145 | ``` 146 | 147 | 148 | 149 | #### Secretary\Manager->putSecret(string $key, string|array $value, ?array $options): void 150 | 151 | Puts a secret with the given `$value`, into the storage engine, under the given `$key`. 152 | 153 | If the current adapter doesn't support arrays, and you pass one it, it will throw a `Secretary\ValueNotSupportedException`. 154 | 155 | Again, some adapters allow passing in custom options to send along with the request. 156 | 157 | ```php 158 | $manager->putSecret('database/redis', 'postgres://localhost:5432'); 159 | ``` 160 | 161 | And for adapters that support a key/value map as a value: 162 | 163 | ```php 164 | $manager->putSecret('database/redis', ['dsn' => 'redis://localhost:6379', 'password' => 'my_super_strong_password']); 165 | ``` 166 | 167 | 168 | 169 | #### Secretary\Manager->deleteSecret(string $key, ?array $options): void 170 | 171 | Deletes a secret from the storage engine using the given `$key`. 172 | 173 | Again, some adapters allow passing in custom options to send along with the request. 174 | 175 | ```php 176 | $manager->deleteSecret('database/redis'); 177 | ``` 178 | 179 | 180 | 181 | #### Secretary\Manager->getAdapter(): AdapterInterface 182 | 183 | Will return the adapter that was passed to this manager during construction. 184 | 185 | 186 | 187 | ### Secretary\Secret 188 | 189 | This class implements ArrayAccess, so if your secret supports passing a key/value map, you can grab straight from the map: 190 | 191 | Secrets are immutable, so attempting to change a value will throw an Exception. 192 | 193 | ```php 194 | $secret = $manager->getSecret('database/redis'); 195 | 196 | $dsn = $secret['dsn']; 197 | ``` 198 | 199 | 200 | 201 | #### Secretary\Secret->getKey(): string 202 | 203 | Returns the key for the secret 204 | 205 | ```php 206 | $secret = $manager->getSecret('dabase/redis'); 207 | 208 | $secret->getKey() === 'database/redis'; // true 209 | ``` 210 | 211 | 212 | 213 | #### Secretary\Secret->getValue(): string | array 214 | 215 | Returns the value for the secret. If the secret is a key/value map, its an array 216 | 217 | ```php 218 | $secret = $manager->getSecret('dabase/redis/dsn'); 219 | 220 | $secret->getValue() === 'redis://localhost:6379'; // true 221 | 222 | // Or 223 | 224 | $secret = $manager->getSecret('dabase/redis'); 225 | 226 | print_r($secret->getValue()); 227 | /* 228 | [ 229 | "dsn" => "redis://localhost:6379", 230 | "password" => "my_super_strong_password" 231 | ] 232 | */ 233 | ``` 234 | 235 | 236 | [aws-secrets-manager-adapter]: https://github.com/secretary/php-aws-secrets-manager-adapter 237 | [hashicorp-vault-adapter]: https://github.com/secretary/php-hashicorp-vault-adapter 238 | [json-file-adapter]: https://github.com/secretary/php-json-file-adapter 239 | [psr-6-cache-adapter]: https://github.com/secretary/php-psr-6-cache-adapter 240 | [psr-16-cache-adapter]: https://github.com/secretary/php-psr-16-cache-adapter 241 | [secretary-bundle]: https://github.com/secretary/php-secretary-bundle 242 | [Secretary\Manager::class]: https://github.com/secretary/php/blob/master/src/Core/src/Manager.php 243 | [Secretary\Secret::class]: https://github.com/secretary/php/blob/master/src/Core/src/Secret.php 244 | --------------------------------------------------------------------------------