├── .buildpath
├── .gitignore
├── .project
├── .settings
├── org.eclipse.php.core.prefs
├── org.eclipse.wst.common.project.facet.core.xml
└── org.eclipse.wst.validation.prefs
├── LICENSE.md
├── README.md
├── composer.json
├── src
└── TgVault
│ ├── BaseVault.php
│ ├── CredentialsProvider.php
│ ├── File
│ └── FileVault.php
│ ├── Hashicorp
│ ├── Cache.php
│ ├── Config.php
│ ├── HashicorpVault.php
│ └── Token.php
│ ├── Logger.php
│ ├── Memory
│ └── MemoryVault.php
│ ├── Secret.php
│ ├── SecretProvider.php
│ ├── Vault.php
│ ├── VaultException.php
│ ├── VaultFactory.php
│ └── commons.php
└── tests
└── TgVault
├── CredentialsProviderTest.php
├── File
├── FileVaultTest.php
└── test-config.json
├── Hashicorp
└── CacheTest.php
├── Memory
└── MemoryVaultTest.php
├── SecretProviderTest.php
├── SecretTest.php
└── VaultFactoryTest.php
/.buildpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor/
2 | /composer.lock
3 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | php-vault
4 |
5 |
6 |
7 |
8 |
9 | org.eclipse.php.composer.core.builder.buildPathManagementBuilder
10 |
11 |
12 |
13 |
14 | org.eclipse.wst.common.project.facet.core.builder
15 |
16 |
17 |
18 |
19 | org.eclipse.wst.validation.validationbuilder
20 |
21 |
22 |
23 |
24 | org.eclipse.dltk.core.scriptbuilder
25 |
26 |
27 |
28 |
29 |
30 | org.eclipse.php.core.PHPNature
31 | org.eclipse.wst.common.project.facet.core.nature
32 |
33 |
34 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.php.core.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | include_path=0;/php-vault/src
3 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.wst.common.project.facet.core.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.wst.validation.prefs:
--------------------------------------------------------------------------------
1 | disabled=06vendor
2 | eclipse.preferences.version=1
3 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # technicalguru/php-vault
2 | A flexible PHP-based vault to provide secrets dynamically
3 |
4 | # License
5 | This project is licensed under [GNU LGPL 3.0](LICENSE.md).
6 |
7 | # Installation
8 |
9 | ## By Composer
10 |
11 | ```sh
12 | composer install technicalguru/vault
13 | ```
14 |
15 | ## By Package Download
16 | You can download the source code packages from [GitHub Release Page](https://github.com/technicalguru/php-vault/releases)
17 |
18 | # Hashicorp Setup
19 | The procedure is best described at [Hashicorp Blog](https://www.hashicorp.com/blog/authenticating-applications-with-vault-approle). It describes
20 | how to create an `approle`. Here is the essence of it:
21 |
22 | ```sh
23 | # Enable the auth method for approle
24 | vault auth enable approle
25 |
26 | # Create a renewal policy
27 | echo 'path "auth/token/*" { capabilities = [ "create", "read", "update", "delete", "list", "sudo" ] }' >renewal-policy.hcl
28 | vault policy write renewal-policy renewal-policy.hcl
29 |
30 | # Create a file with your policy on the respective secret path:
31 | cat 'path "secret/my-secret" { capabilities = ["read", "list"] }' >app-policy.hcl
32 |
33 | # Create the policy
34 | vault policy write my-app-policy app-policy.hcl
35 |
36 | # Create the approle with renewal-policy and your application policy
37 | vault write auth/approle/role/my-approle token_policies=renewal-policy,my-app-policy token_period=30m token_ttl=30m token_max_ttl=1h token_explicit_max_ttl=2h
38 |
39 | # Get the role ID printed
40 | vault read auth/approle/role/my-approle/role-id
41 |
42 | # Create the secret ID and print it
43 | vault write -f auth/approle/role/my-approle/secret-id
44 | ```
45 |
46 | Please notice that you need to recreate the secret ID whenever you change the application role or a policy.
47 |
48 | # Examples
49 | ## Create a HashicorpVault
50 | Please note that this vault is actually a client to an existing Hashicorp Vault.
51 |
52 | ```php
53 | // Create configuration
54 | $config = array(
55 | 'type' => 'hashicorp',
56 | 'config' => array(
57 | 'uri' => 'https://127.0.0.1:8200/v1',
58 | 'roleId' => '123456-12345-12345-123456',
59 | 'secretId' => 'abcdef-abcde-abcde-abcdef'
60 | )
61 | );
62 |
63 | // Create the vault instance
64 | try {
65 | $vault = \TgVault\VaultFactory::create($config);
66 | } catch (\TgVault\VaultException $e) {
67 | // Vault could not be created
68 | }
69 |
70 | ```
71 |
72 | ## Create a MemoryVault
73 |
74 | ```php
75 | // Create configuration
76 | $config = array(
77 | 'type' => 'memory',
78 | 'config' => array(
79 | 'secrets' => array(
80 | 'my/secret/number/1' => array(
81 | 'username' => 'my-username1',
82 | 'password' => 'my-password1',
83 | ),
84 | 'my/secret/number/2' => array(
85 | 'username' => 'my-username2',
86 | 'password' => 'my-password2',
87 | ),
88 | )
89 | )
90 | );
91 |
92 | // Create the vault instance
93 | try {
94 | $vault = \TgVault\VaultFactory::create($config);
95 | } catch (\TgVault\VaultException $e) {
96 | // Vault could not be created
97 | }
98 | ```
99 |
100 | ## Create a FileVault
101 |
102 | ```php
103 | // Create configuration
104 | $config = array(
105 | 'type' => 'file',
106 | 'config' => array(
107 | 'filename' => 'path-to-json-secret-file'
108 | )
109 | );
110 |
111 | // Create the vault instance
112 | try {
113 | $vault = \TgVault\VaultFactory::create($config);
114 | } catch (\TgVault\VaultException $e) {
115 | // Vault could not be created
116 | }
117 | ```
118 |
119 | The secrets file (JSON) shall look like this:
120 |
121 | ```json
122 | {
123 | "secrets": {
124 | "my/secret/number/1" : {
125 | "username" : "my-username1",
126 | "password" : "my-password1"
127 | },
128 | "my/secret/number/2" : {
129 | "username" : "my-username2",
130 | "password" : "my-password2"
131 | }
132 | }
133 | }
134 | ```
135 |
136 | ## Retrieving a secret
137 |
138 | ```php
139 | try {
140 | $mySecret1 = $vault->getSecret('my/secret/number/1');
141 | $mySecret2 = $vault->getSecret('my/secret/number/2');
142 | } catch (\TgVault\VaultException $e) {
143 | // secret was not found
144 | }
145 |
146 | $username1 = $mySecret1->get('username');
147 | $password1 = $mySecret1->get('password');
148 | $username2 = $mySecret2->get('username');
149 | $password2 = $mySecret2->get('password');
150 | ```
151 |
152 | A value in a secret is `NULL` when the key does not exists whereas an exception will be thrown when the secret itself cannot be found
153 | or an error occurred while retrieval.
154 |
155 | ## Using lazy callback credentials
156 | You can use the `SecretProvider` or `CredentialsProvider` helper classes to pass them credentials without knowing where they come from
157 | or how to use a vault.
158 |
159 | ```php
160 | $callback1 = new \TgVault\SecretProvider($vault, 'my/secret/number/1');
161 | $callback2 = new \TgVault\CredentialsProvider($vault, 'my/secret/number/2');
162 |
163 | try {
164 | $username1 = $callback1->get('username');
165 | $password1 = $callback1->get('password');
166 |
167 | $username2 = $callback2->getUsername();
168 | $password2 = $callback2->getPassword();
169 | } catch (\TgVault\VaultException $e) {
170 | // Secret cannot be retrieved or does not exist
171 | }
172 | ```
173 |
174 | The `CredentialsProvider` takes additional constructor arguments that define, which keys in the secret provide username and password. The
175 | defaults are as given above for the `SecretProvider`.
176 |
177 |
178 | # Contribution
179 | Report a bug, request an enhancement or pull request at the [GitHub Issue Tracker](https://github.com/technicalguru/php-vault/issues).
180 |
181 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "technicalguru/vault",
3 | "description": "A flexible, lightweight PHP-based vault to provide secrets dynamically",
4 | "type": "library",
5 | "keywords": [
6 | "framework",
7 | "vault",
8 | "vault-client",
9 | "hashicorp",
10 | "hashicorp-vault"
11 | ],
12 | "license": "LGPL-3.0-or-later",
13 | "authors": [
14 | {
15 | "name": "TechnicalGuru",
16 | "homepage": "https://github.com/technicalguru/php-vault"
17 | }
18 | ],
19 | "require": {
20 | "php": ">=7.0.0",
21 | "technicalguru/utils": "^1"
22 | },
23 | "autoload": {
24 | "psr-4": {
25 | "TgVault\\": "src/TgVault/"
26 | }
27 | },
28 | "extra": {
29 | "branch-alias": {
30 | "dev-master": "1.0-dev"
31 | }
32 | },
33 | "require-dev": {
34 | "phpunit/phpunit": "^9"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/TgVault/BaseVault.php:
--------------------------------------------------------------------------------
1 | logger = $logger;
22 | }
23 |
24 | /**
25 | * Returns the secret at the given path.
26 | * Must be overridden by subclasses.
27 | * @param string $path - an arbitrary path that uniquely identifies a secret in the vault.
28 | * @return Secret
29 | * @throws VaultException when the secret cannot be found or retrieved.
30 | */
31 | public function getSecret($path) {
32 | throw new VaultException(get_class().'::getSecret() must be implemented.', VAULT_ERR_INTERNAL);
33 | }
34 |
35 | /**
36 | * Set the logger and log all information via this object.
37 | * This can be a Psr\Log\LoggerInterface or a \TgVault\Logger.
38 | * @param Logger - the logging object.
39 | */
40 | public function setLogger($logger) {
41 | $this->logger = $logger;
42 | }
43 |
44 | /**
45 | * Log in debug level.
46 | * @see Logger interface
47 | * @param $s - the string to be logged
48 | * @param $object - the object to be logged
49 | */
50 | protected function debug($s, $object = NULL) {
51 | if ($this->logger != NULL) {
52 | $object = self::cleanObject($object);
53 | $psrInterface = '\\Psr\\Log\\LoggerInterface';
54 | if ($this->logger instanceof $psrInterface) {
55 | } else if ($this->logger instanceof Logger) {
56 | $this->logger->debug($this->getLoggerPrefix().$s, $object);
57 | }
58 | }
59 | }
60 |
61 | /**
62 | * Log in warn level.
63 | * @see Logger interface
64 | * @param $s - the string to be logged
65 | * @param $object - the object to be logged
66 | */
67 | protected function warn($s, $object = NULL) {
68 | if ($this->logger != NULL) {
69 | $object = self::cleanObject($object);
70 | $psrInterface = '\\Psr\\Log\\LoggerInterface';
71 | if ($this->logger instanceof $psrInterface) {
72 | } else if ($this->logger instanceof Logger) {
73 | $this->logger->warn($this->getLoggerPrefix().$s, $object);
74 | }
75 | }
76 | }
77 |
78 | /**
79 | * Log in info level.
80 | * @see Logger interface
81 | * @param $s - the string to be logged
82 | * @param $object - the object to be logged
83 | */
84 | protected function info($s, $object = NULL) {
85 | if ($this->logger != NULL) {
86 | $object = self::cleanObject($object);
87 | $psrInterface = '\\Psr\\Log\\LoggerInterface';
88 | if ($this->logger instanceof $psrInterface) {
89 | } else if ($this->logger instanceof Logger) {
90 | $this->logger->info($this->getLoggerPrefix().$s, $object);
91 | }
92 | }
93 | }
94 |
95 | /**
96 | * Log in error level.
97 | * @see Logger interface
98 | * @param $s - the string to be logged
99 | * @param $object - the object to be logged
100 | */
101 | protected function error($s, $object = NULL) {
102 | if ($this->logger != NULL) {
103 | $object = self::cleanObject($object);
104 | $psrInterface = '\\Psr\\Log\\LoggerInterface';
105 | if ($this->logger instanceof $psrInterface) {
106 | } else if ($this->logger instanceof Logger) {
107 | $this->logger->error($this->getLoggerPrefix().$s, $object);
108 | }
109 | }
110 | }
111 |
112 | /**
113 | * Returns a prefix for the logging string.
114 | * Default implementation uses the short class name.
115 | * @return string for prefixing logging strings.
116 | */
117 | protected function getLoggerPrefix() {
118 | if ($this->prefix == NULL) {
119 | $helper = new \ReflectionClass($this);
120 | $this->prefix = '['.$helper->getShortName().'] ';
121 | }
122 | return $this->prefix;
123 | }
124 |
125 | /**
126 | * Copies the given object and redacts any sensitive strings, such as
127 | * passwords, usernames and tokens. The decision is based on object
128 | * attribute names. Arrays are not redacted (!).
129 | * @param object $o - the object to clean
130 | * @return \stdClass a cleaned object
131 | */
132 | public static function cleanObject($o) {
133 | if ($o == NULL) return NULL;
134 | if (!is_object($o)) return $o;
135 | $copy = new \stdClass;
136 | foreach (get_object_vars($o) AS $key => $value) {
137 | if (is_object($value)) {
138 | $copy->$key = self::cleanObject($value);
139 | } else if (is_string($key)) {
140 | switch ($key) {
141 | case 'username':
142 | case 'password':
143 | case 'passwd':
144 | case 'client_token':
145 | case 'accessor':
146 | $copy->$key = '***REDACTED***';
147 | break;
148 | default:
149 | if (strpos($key, 'pass') !== FALSE) {
150 | $copy->$key = '***REDACTED***';
151 | } else {
152 | $copy->$key = $value;
153 | }
154 | }
155 | } else {
156 | $copy->$key = $value;
157 | }
158 | }
159 | return $copy;
160 | }
161 | }
162 |
163 |
--------------------------------------------------------------------------------
/src/TgVault/CredentialsProvider.php:
--------------------------------------------------------------------------------
1 | usernameKey = $usernameKey;
32 | $this->passwordKey = $passwordKey;
33 | }
34 |
35 | /**
36 | * Returns the username in the secret.
37 | * @return string the username as given in the secret
38 | * @throws VaultException when the secret cannot be retrieved or does not exist
39 | */
40 | public function getUsername() {
41 | return $this->get($this->usernameKey);
42 | }
43 |
44 | /**
45 | * Returns the password in the secret.
46 | * @return string the password as given in the secret
47 | * @throws VaultException when the secret cannot be retrieved or does not exist
48 | */
49 | public function getPassword() {
50 | return $this->get($this->passwordKey);
51 | }
52 |
53 | }
54 |
55 |
--------------------------------------------------------------------------------
/src/TgVault/File/FileVault.php:
--------------------------------------------------------------------------------
1 | secrets = NULL;
34 | if (is_object($config)) $this->filename = $config->filename;
35 | else if (is_array($config)) $this->filename = $config['filename'];
36 | else throw new VaultException('Configuration must contain filename', VAULT_ERR_CONFIG);
37 | }
38 |
39 | /**
40 | * Loads the secrets file from disk if required.
41 | */
42 | protected function load() {
43 | if ($this->secrets == NULL) {
44 | $this->secrets = array();
45 | $json = file_get_contents($this->filename);
46 | if ($json === FALSE) {
47 | throw new VaultException('Cannot find secrets file.', VAULT_ERR_FILE_NOT_FOUND);
48 | } else {
49 | $config = json_decode($json);
50 | $secrets = array();
51 | if (is_object($config) && isset($config->secrets)) $secrets = $config->secrets;
52 | foreach ($secrets AS $path => $data) {
53 | $this->secrets[$path] = new Secret(array('data' => $data));
54 | }
55 | }
56 | }
57 | }
58 |
59 | /**
60 | * Returns the secret at the given path.
61 | * @param string $path - an arbitrary path that uniquely identifies a secret in the vault.
62 | * @return Secret
63 | * @throws VaultException when the secret cannot be found or retrieved.
64 | */
65 | public function getSecret($path) {
66 | $this->load();
67 | if (!isset($this->secrets[$path])) {
68 | throw new VaultException('Secret not available', VAULT_ERR_NOT_FOUND);
69 | }
70 | return $this->secrets[$path];
71 | }
72 |
73 | }
74 |
75 |
--------------------------------------------------------------------------------
/src/TgVault/Hashicorp/Cache.php:
--------------------------------------------------------------------------------
1 | cacheFile = $cacheFile;
26 | $this->logger = $logger;
27 | }
28 |
29 | /**
30 | * Loads the cache if required.
31 | */
32 | protected function load() {
33 | if ($this->data == null) {
34 | if (($this->cacheFile) && file_exists($this->cacheFile)) {
35 | $contents = file_get_contents($this->cacheFile);
36 | if ($contents === FALSE) {
37 | if ($this->logger != null) $this->logger->error('[Cache] Cannot read cache');
38 | } else if (!empty($contents)) $this->data = json_decode($contents);
39 | else $this->data = new \stdClass;
40 | } else {
41 | $this->data = new \stdClass;
42 | }
43 | }
44 | }
45 |
46 | /**
47 | * Saves the cache.
48 | * @return TRUE when cache was written successfully, FALSE otherwise.
49 | */
50 | protected function save() {
51 | if (($this->data != null) && $this->cacheFile) {
52 | $rc = file_put_contents($this->cacheFile, json_encode($this->data), LOCK_EX);
53 | if ($rc === FALSE) {
54 | if ($this->logger != NULL) $this->logger->error('[Cache] Cannot write cache');
55 | return false;
56 | }
57 | return true;
58 | }
59 | return false;
60 | }
61 |
62 | /**
63 | * Return data from cache.
64 | * @param string $key - the key in the cache.
65 | * @return mixed - the data from the cache or NULL if not available.
66 | */
67 | public function get($key) {
68 | $this->load();
69 | if (isset($this->data->$key)) {
70 | return $this->data->$key;
71 | }
72 | return null;
73 | }
74 |
75 | /**
76 | * Sets a value in the cache.
77 | * @param string $key - the key in the cache.
78 | * @param mixed $value - the value to be stored.
79 | */
80 | public function set($key, $value) {
81 | $this->load();
82 | $this->data->$key = $value;
83 | $this->save();
84 | }
85 |
86 | /**
87 | * Deletes a value in the cache.
88 | * @param string $key - the key in the cache.
89 | */
90 | public function delete($key) {
91 | $this->load();
92 | if (isset($this->data->$key)) {
93 | unset($this->data->$key);
94 | $this->save();
95 | }
96 | }
97 |
98 | /**
99 | * Set the logger and log all information via this object.
100 | * @param object $logger - a logger, either TgVault\Logger or Psr\Log\LoggerInterface
101 | */
102 | public function setLogger($logger) {
103 | $this->logger = $logger;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/TgVault/Hashicorp/Config.php:
--------------------------------------------------------------------------------
1 | timeout = 5;
43 | $this->verifyCertificate = false;
44 | $this->debug = false;
45 | $this->cacheFile = '/tmp/vault_client.cache';
46 | $this->renewTokens = true;
47 | $this->renewalPeriod = 300;
48 | $this->maxTtl = 0;
49 |
50 | if (is_object($data)) $data = get_object_vars($data);
51 | if (is_array($data)) {
52 | foreach ($data AS $key => $value) {
53 | $this->$key = $value;
54 | }
55 | }
56 |
57 | // Check for any errors
58 | $this->check('uri', 'Vault URI not set');
59 | $this->check('roleId', 'Vault AppRole ID not set');
60 | $this->check('secretId', 'Vault Secret ID not set');
61 | }
62 |
63 | /**
64 | * check that the given value exists and is not empty.
65 | * @param string $valueKey - the config key to be verified
66 | * @param string $errorMessage - exception message to be thrown in case or error.
67 | * @throws VaultException when the given key was not set.
68 | */
69 | private function check($valueKey, $errorMessage) {
70 | if (!isset($this->$valueKey) || ($this->$valueKey == NULL) ||
71 | !is_string($this->$valueKey) || (trim($this->$valueKey) == '')) {
72 | throw new VaultException($errorMessage. ' ('.$valueKey.')', VAULT_ERR_CONFIG);
73 | }
74 | }
75 |
76 | /**
77 | * Set the vault login credentials.
78 | * @param string $roleId - the role ID in vault
79 | * @param string $secretId - the secret ID of the client
80 | */
81 | public function setVaultCredentials($roleId, $secretId) {
82 | $this->roleId = $roleId;
83 | $this->secretId = $secretId;
84 | $this->check('roleId', 'Vault AppRole ID not set');
85 | $this->check('secretId', 'Vault Secret ID not set');
86 | }
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/src/TgVault/Hashicorp/HashicorpVault.php:
--------------------------------------------------------------------------------
1 | config = new Config($config);
38 | $this->isTls = substr($this->config->uri, 0, 5) == 'https';
39 | $this->cache = new Cache($this->config->cacheFile, $logger);
40 | $this->loggedToken = FALSE;
41 | }
42 |
43 | /**
44 | * Get rid of the current token. This can be necessary when the policies changed.
45 | */
46 | public function removeToken() {
47 | $this->cache->delete('token');
48 | $this->token = NULL;
49 | }
50 |
51 | /**
52 | * Returns the secret at the given path.
53 | * @param string $path - an arbitrary path that uniquely identifies a secret in the vault.
54 | * @return Secret
55 | * @throws VaultException when the secret cannot be found or retrieved.
56 | */
57 | public function getSecret($path) {
58 | if (!isset($this->secrets[$path])) {
59 | $this->getToken();
60 | $rc = $this->GET($path);
61 | if (($rc->error == 0) && ($rc->http_code == 200) && is_object($rc->data->data)) {
62 | // It's unclear why some vaults do answer with one level less (without metadata)
63 | if (isset($rc->data->data->data)) {
64 | $this->secrets[$path] = new Secret($rc->data->data);
65 | } else {
66 | $this->secrets[$path] = new Secret($rc->data);
67 | }
68 | } else {
69 | $this->secrets[$path] = $rc;
70 | }
71 | }
72 |
73 | if (get_class($this->secrets[$path]) != 'TgVault\\Secret') {
74 | $ex = new VaultException('Secret not available', VAULT_ERR_SECRET);
75 | $ex->setDetails($this->secrets[$path]);
76 | throw $ex;
77 | }
78 | return $this->secrets[$path];
79 | }
80 |
81 | /**
82 | * Set the logger and log all information via this object.
83 | * @param object $logger - the logging object, either TgVault\Logger or Psr\Log\LoggerInterface
84 | */
85 | public function setLogger($logger) {
86 | parent::setLogger($logger);
87 | $this->cache->setLogger($logger);
88 | }
89 |
90 | public function getTokenStatus($asText = true) {
91 | if ($this->token == NULL) {
92 | return $asText ? 'no token available' : VAULT_ERR_NO_TOKEN;
93 | }
94 | if ($this->token->isExpired()) {
95 | return $asText ? 'token expired at '.$this->token->expiryString.' (current time is '.date(DATE_ATOM).')' : VAULT_ERR_TOKEN_EXPIRED;
96 | }
97 | if ($this->shallRenewToken()) {
98 | return $asText ? 'token valid but needs renewal (expires at '.$this->token->expiryString.', current time is '.date(DATE_ATOM).')' : VAULT_ERR_RENEWAL;
99 | }
100 | return $asText ? 'token valid (expires at '.$this->token->expiryString.', current time is '.date(DATE_ATOM).')' : VAULT_OK;
101 | }
102 |
103 | public function getSecretError($path, $asText = true) {
104 | $response = $this->secrets[$path];
105 | if ($response instanceof Secret) {
106 | return $asText ? 'Secret successfully retrieved' : VAULT_SECRET_OK;
107 | }
108 | if ($response->error > 0) {
109 | return $asText ? $response->errorMessage : VAULT_ERR_CURL_BASE+$response->error;
110 | }
111 | if (is_object($response->data) && is_array($response->data->errors)) {
112 | return $asText ? implode(' / ', $response->data->errors) : VAULT_ERR_HTTP_BASE+$response->http_code;
113 | }
114 | return $asText ? 'Unknown internal error' : VAULT_ERR_SECRET_INTERNAL;
115 | }
116 |
117 | public function dieOn($path) {
118 | // Error log message
119 | $status1 = $this->getTokenStatus();
120 | $this->error('Vault Token Status: '.$status1);
121 | $status2 = $this->getSecretError($path);
122 | $this->error('Database Secret Status: '.$status2);
123 |
124 | // User message
125 | $status1 = $this->getTokenStatus(false);
126 | $status2 = $this->getSecretError($path, false);
127 | $renewal = false;
128 | switch ($status1) {
129 | case VAULT_OK:
130 | // It's a secret problem
131 | break;
132 | case VAULT_ERR_RENEWAL:
133 | // Token actually ok but renewal required / So it might be a secret issue
134 | $renewal = true;
135 | break;
136 | case VAULT_ERR_TOKEN_EXPIRED:
137 | exit('No valid access token');
138 | break;
139 | case VAULT_ERR_NO_TOKEN:
140 | exit('No access token');
141 | break;
142 | }
143 |
144 | switch ($status2) {
145 | case VAULT_SECRET_OK:
146 | exit($renewal ? 'Token renewal warning' : 'OK');
147 | break;
148 | case VAULT_ERR_SECRET_INTERNAL:
149 | exit($renewal ? 'Internal error on renewable token' : 'Internal error on secret');
150 | break;
151 | default:
152 | if ($status2 >= VAULT_ERR_CURL_BASE) {
153 | exit($renewal ? 'Cannot reach vault with renewable token: '.$status2 : 'Cannot reach vault: '.$status2);
154 | }
155 | exit($renewal ? 'Vault declines renewable token: '.$status2 : 'Vault declines: '.$status2);
156 | }
157 | exit($renewal ? 'Token renewal warning' : 'OK');
158 | }
159 |
160 | /**
161 | * Returns the last result from a vault call.
162 | */
163 | public function getLastResult() {
164 | return $this->lastResult;
165 | }
166 |
167 | /**
168 | * Returns a token either from object cache, file cache or
169 | * triggers a token request.
170 | */
171 | protected function getToken() {
172 | if ($this->token == NULL) {
173 | $this->setTokenFromCache();
174 | }
175 | if ($this->token != NULL) {
176 | if ($this->isTokenExpired()) {
177 | $this->removeToken();
178 | } else if ($this->shallRenewToken()) {
179 | $this->renewToken();
180 | }
181 | }
182 |
183 | if ($this->token == NULL) {
184 | $this->requestNewToken();
185 | }
186 |
187 | if (($this->token != NULL) && !$this->loggedToken) {
188 | $this->info('Using token: '.$this->token->getInfo());
189 | $this->loggedToken = TRUE;
190 | }
191 |
192 | return $this->token;
193 | }
194 |
195 | protected function isTokenExpired() {
196 | // Token itself must exist and not be expired
197 | if (($this->token != NULL) && !$this->token->isExpired()) {
198 | // Get expiry time
199 | $expiry = $this->getTokenExpiryTime();
200 | if (time() > $expiry) return true;
201 | return false;
202 | }
203 | return true;
204 | }
205 |
206 | protected function shallRenewToken() {
207 | // Token must exist and not be expired
208 | if (($this->token != NULL) && !$this->token->isExpired()) {
209 | // Token renewable and renewal permitted
210 | if ($this->token->renewable && $this->config->renewTokens) {
211 | // Get expiry time
212 | $expiry = $this->getTokenExpiryTime();
213 | if ($expiry > 0) {
214 | $renewalTime = $expiry - 300; // Default renewal
215 | $renewalPeriod = $this->config->renewalPeriod;
216 | if ($renewalPeriod > 10) $renewalTime = $expiry - $renewalPeriod;
217 | // So, is it time?
218 | if (time() > $renewalTime) return true;
219 | }
220 |
221 | }
222 | }
223 | return false;
224 | }
225 |
226 | /**
227 | * Compute the current token's expiry time using the token and our config.
228 | * @return int - the Unix epoch expiry time, -1 if token is NULL
229 | */
230 | protected function getTokenExpiryTime() {
231 | $rc = -1;
232 | if ($this->token != NULL) {
233 | $rc = $this->token->expiryTime;
234 | if ($this->config->maxTtl > 0) {
235 | $expiry = $this->token->creationTime + $this->config->maxTtl;
236 | if ($expiry < $rc) $rc = $expiry;
237 | }
238 | }
239 | return $rc;
240 | }
241 |
242 | /**
243 | * Load the token from the file cache.
244 | */
245 | protected function setTokenFromCache() {
246 | $token = $this->cache->get('token');
247 | if ($token != NULL) {
248 | $this->token = new Token($token);
249 | }
250 | }
251 |
252 | /**
253 | * Request a token from Vault
254 | */
255 | protected function requestNewToken() {
256 | $data = new \stdClass;
257 | $data->role_id = $this->config->roleId;
258 | $data->secret_id = $this->config->secretId;
259 | $data->renewable = true;
260 | $rc = $this->POST('/auth/approle/login', $data);
261 | if (($rc->error == 0) && is_object($rc->data)) {
262 | if (isset($rc->data->auth)) {
263 | $this->token = new Token($rc->data->auth);
264 | $this->cache->set('token', $this->token);
265 | $this->info('Token replaced');
266 | }
267 | }
268 | }
269 |
270 | /**
271 | * Renew the token
272 | */
273 | protected function renewToken() {
274 | if ($this->token != NULL) {
275 | if (!$this->token->isExpired()) {
276 | $data = new \stdClass;
277 | $data->token = $this->token->client_token;
278 | $rc = $this->POST('auth/token/renew', $data);
279 | if (($rc->error == 0) && is_object($rc->data)) {
280 | if (isset($rc->data->auth)) {
281 | $this->token = new Token($rc->data->auth);
282 | $this->cache->set('token', $this->token);
283 | $this->info('Token renewed');
284 | }
285 | }
286 | } else {
287 | $this->removeToken();
288 | }
289 | }
290 | }
291 |
292 | /**
293 | * Performs a GET request on the given path
294 | * @param string $path - the relative path at Vault URI to request
295 | * @return \stdClass see #request($curl, $path) method.
296 | */
297 | protected function GET($path) {
298 | $curl = curl_init();
299 | return $this->request($curl, $path);
300 | }
301 |
302 | /**
303 | * Performs a POST request on the given path
304 | * @param string $path - the relative path at Vault URI to request
305 | * @param object $data - the data to be posted (will be json-encoded)
306 | * @return mixed see #request($curl, $path) method.
307 | */
308 | protected function POST($path, $data) {
309 | $body = json_encode($data);
310 | $headers = array(
311 | 'Content-Length: '.strlen($body),
312 | 'Content-Type: application/json',
313 | );
314 |
315 | $curl = curl_init();
316 | curl_setopt($curl, CURLOPT_POST, true);
317 | curl_setopt($curl, CURLOPT_POSTFIELDS, $body);
318 | return $this->request($curl, $path, $headers);
319 | }
320 |
321 | /**
322 | * Performs the actual curl request.
323 | * @param resource $curl - cURL handle as returned from curl_init()
324 | * @param string $path - the relative path at Vault URI to request
325 | * @return \stdClass see documentation below
326 | */
327 | protected function request($curl, $path, $additionalHeaders = array()) {
328 | /**********************************
329 | Return is an object of:
330 | {
331 | "request_id": string, // request ID given / not needed
332 | "lease_id": string, // lease created for this request / not needed
333 | "renewable": bool, // lease for current request is renewable / not needed
334 | "lease_duration": int, // duration of least for current request / not needed
335 | "data": object, // specific to request
336 | "wrap_info": object, // unknown / not needed
337 | "warnings": array, // unknown / string messages
338 | "errors": array, // unknown / string messages
339 | "auth": { object // authentication information
340 | "client_token": string, // token that was assigned
341 | "accessor": string, // accessor token / not needed
342 | "policies": array, // policies / info only
343 | "token_policies": array, // token policies / info only
344 | "metadata": object, // authorization meta data, such as "role_name"
345 | "lease_duration": int, // lease duration in seconds (remaining)
346 | "renewable": bool, // is token renewable
347 | "entity_id": string, // entity ID / not needed
348 | "token_type": string, // type of token / not needed
349 | "orphan": bool, // is orphaned token / not needed
350 | }
351 | }
352 | ***********************************/
353 | $additionalHeaders[] = 'X-Vault-Request: true';
354 | if (($this->token != NULL) && isset($this->token->client_token)) {
355 | $additionalHeaders[] = 'X-Vault-Token: '.$this->token->client_token;
356 | }
357 |
358 | // Fix path issues here
359 | if ((substr($this->config->uri, -1) != '/') && (substr($path, 0, 1) != '/')) {
360 | $path = '/'.$path;
361 | } else if ((substr($this->config->uri, -1) == '/') && (substr($path, 0, 1) == '/')) {
362 | $path = substr($path, 1);
363 | }
364 | curl_setopt($curl, CURLOPT_URL, $this->config->uri.$path);
365 | curl_setopt($curl, CURLOPT_TIMEOUT, $this->config->timeout);
366 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
367 | curl_setopt($curl, CURLOPT_USERAGENT, 'HashicorpVaultClient/1.0');
368 | if (count($additionalHeaders) > 0) {
369 | curl_setopt($curl, CURLOPT_HTTPHEADER, $additionalHeaders);
370 | }
371 |
372 | // We might need this: CURLOPT_PORT
373 | if ($this->isTls) {
374 | curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->config->verifyCertificate);
375 | curl_setopt($curl, CURLOPT_SSL_VERIFYSTATUS, $this->config->verifyCertificate);
376 | curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $this->config->verifyCertificate);
377 | }
378 | if ($this->config->debug) {
379 | curl_setopt($curl, CURLINFO_HEADER_OUT, true);
380 | }
381 |
382 | $rc = $this->createCurlResult($curl);
383 | curl_close($curl);
384 | return $rc;
385 | }
386 |
387 | protected function createCurlResult($curl) {
388 | $data = curl_exec($curl);
389 |
390 | $rc = new \stdClass;
391 | $rc->url = curl_getinfo($curl, CURLINFO_EFFECTIVE_URL);
392 | $rc->error = curl_errno($curl);
393 | $rc->errorMessage = curl_error($curl);
394 | if (!$rc->error) {
395 | $rc->http_code = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
396 | $rc->contentType = curl_getinfo($curl, CURLINFO_CONTENT_TYPE);
397 | if ($rc->contentType == 'application/json') {
398 | $rc->data = json_decode($data);
399 | if (isset($rc->data->errors)) {
400 | $this->error('Error while calling '.$rc->url.': ', $rc->data->errors);
401 | }
402 | } else {
403 | $rc->data = $data;
404 | }
405 | } else {
406 | $this->error('Error while calling '.$rc->url.': ', $rc->errorMessage);
407 | }
408 | if ($this->config->debug) {
409 | $rc->debug = new \stdClass;
410 | $rc->debug->headers = curl_getinfo($curl, CURLINFO_HEADER_OUT);
411 | }
412 | $this->lastResult = $rc;
413 | return $rc;
414 | }
415 |
416 | }
417 |
418 |
--------------------------------------------------------------------------------
/src/TgVault/Hashicorp/Token.php:
--------------------------------------------------------------------------------
1 | $value) {
33 | $this->$key = $value;
34 | }
35 | if (!isset($this->creationTime)) {
36 | $this->creationTime = time();
37 | }
38 | if (!isset($this->expiryTime) && isset($this->lease_duration)) {
39 | $this->expiryTime = time() + $this->lease_duration;
40 | }
41 | if (!isset($this->expiryString) && isset($this->expiryTime)) {
42 | $this->expiryString = date(DATE_ATOM, $this->expiryTime);
43 | }
44 | $this->now = time();
45 | }
46 |
47 | /**
48 | * Is the token expired?
49 | * @return TRUE when the token has expired, FALSE otherwise.
50 | */
51 | public function isExpired() {
52 | return time() >= $this->expiryTime-5;
53 | }
54 |
55 | /**
56 | * Returns a basic info string for logging purposes.
57 | * @return string info string about the token.
58 | */
59 | public function getInfo() {
60 | return substr($this->client_token, 0, 3).'**********'.substr($this->client_token, -3).' ['.
61 | 'created='.date(DATE_ATOM, $this->creationTime).
62 | ',expires='.$this->expiryString.
63 | ',renewable='.($this->renewable ? 'true' : 'false').
64 | ',policies='.json_encode($this->policies).
65 | ']';
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/src/TgVault/Logger.php:
--------------------------------------------------------------------------------
1 | data).
25 | * @param object $logger - the logger to be used (not used in this implementation)
26 | */
27 | public function __construct($config, $logger = NULL) {
28 | parent::__construct($logger);
29 | if ($config == NULL) throw new VaultException('Configuration must be set', VAULT_ERR_CONFIG_EMPTY);
30 | $this->secrets = array();
31 | $secrets = array();
32 | if (is_object($config) && isset($config->secrets)) $secrets = $config->secrets;
33 | else if (is_array($config) && isset($config['secrets'])) $secrets = $config['secrets'];
34 | foreach ($secrets AS $path => $data) {
35 | $this->secrets[$path] = new Secret(array('data' => $data));
36 | }
37 | }
38 |
39 | /**
40 | * Returns the secret at the given path.
41 | * @param string $path - an arbitrary path that uniquely identifies a secret in the vault.
42 | * @return Secret
43 | * @throws VaultException when the secret cannot be found or retrieved.
44 | */
45 | public function getSecret($path) {
46 | if (!isset($this->secrets[$path])) {
47 | throw new VaultException('Secret not available', VAULT_ERR_NOT_FOUND);
48 | }
49 | return $this->secrets[$path];
50 | }
51 |
52 | }
53 |
54 |
--------------------------------------------------------------------------------
/src/TgVault/Secret.php:
--------------------------------------------------------------------------------
1 | $value) {
24 | if (is_array($value)) {
25 | $this->$key = json_decode(json_encode($value));
26 | } else {
27 | $this->$key = $value;
28 | }
29 | }
30 | } else {
31 | $this->data = $data;
32 | }
33 | }
34 |
35 | /**
36 | * Returns a value from the secret.
37 | * @param string $key - the key of the value to be retrieved.
38 | * @return string the value or NULL if not set.
39 | */
40 | public function get($key) {
41 | if (isset($this->data->$key)) return $this->data->$key;
42 | return NULL;
43 | }
44 |
45 | /**
46 | * Returns the keys that are available in this secret.
47 | * @return list of keys.
48 | */
49 | public function keys() {
50 | return array_keys(get_object_vars($this->data));
51 | }
52 |
53 | /**
54 | * Returns any metadata - if set - from the vault for this secret
55 | * @return mixed the metadata or NULL if not set
56 | */
57 | public function getMeta() {
58 | return $this->metadata;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/TgVault/SecretProvider.php:
--------------------------------------------------------------------------------
1 | vault = $vault;
33 | $this->path = $path;
34 | $this->secret = NULL;
35 | }
36 |
37 | /**
38 | * Returns a value from the secret.
39 | * The provider will load the secret if not done yet.
40 | * @param string $key - the key of the value to be retrieved.
41 | * @return string the value or NULL if not set.
42 | * @throws VaultException when the secret does not exist.
43 | */
44 | public function get($key) {
45 | if ($this->secret == NULL) {
46 | $this->loadSecret();
47 | }
48 | if (($this->secret != NULL) && ($this->secret instanceof Secret)) {
49 | return $this->secret->get($key);
50 | }
51 | throw new VaultException('No such secret: '.$this->path, VAULT_ERR_NOT_FOUND);
52 | }
53 |
54 | /**
55 | * Loads the secret.
56 | * @throws VaultException when loading fails.
57 | */
58 | protected function loadSecret() {
59 | if ($this->secret == NULL) {
60 | try {
61 | $this->secret = $this->vault->getSecret($this->path);
62 | } catch (VaultException $e) {
63 | $this->secret = $e;
64 | throw $e;
65 | }
66 | }
67 | }
68 | }
69 |
70 |
--------------------------------------------------------------------------------
/src/TgVault/Vault.php:
--------------------------------------------------------------------------------
1 | details = $details;
27 | }
28 |
29 | /**
30 | * Returns some debug information if available.
31 | * @return mixed $details - some debug info
32 | */
33 | public function getDetails() {
34 | return $this->details;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/TgVault/VaultFactory.php:
--------------------------------------------------------------------------------
1 | type, $config->config, $logger);
26 | if (is_array($config)) return self::createVault($config['type'], $config['config'], $logger);
27 | throw new VaultException('Vault configuration must be object or array', VAULT_ERR_CONFIG_TYPE);
28 | }
29 |
30 | /**
31 | * Creates the vault according to type and passes the config object.
32 | * The vault class must be defined as "TgVault\Type\TypeVault".
33 | * @param string $type - the type of the vault
34 | * @param mixed $config - the configuration to pass on.
35 | * @param Logger $logger - the logger (optional)
36 | * @return Vault created and configured
37 | * @throws VaultException when the vault could not be created.
38 | */
39 | public static function createVault($type, $config = NULL, $logger = NULL) {
40 | if (($type == NULL) || (trim($type) == '')) throw new VaultException('Vault type cannot be empty', VAULT_ERR_TYPE_EMPTY);
41 | $type = ucfirst(trim($type));
42 | $className = 'TgVault\\'.$type.'\\'.$type.'Vault';
43 | if (class_exists($className)) {
44 | return new $className($config, $logger);
45 | }
46 | throw new VaultException('Cannot find Vault instance: '.$className, VAULT_ERR_TYPE_NOT_FOUND);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/TgVault/commons.php:
--------------------------------------------------------------------------------
1 | getTestVault();
16 | $provider = new CredentialsProvider($vault, 'path/to/secret1');
17 | $this->assertEquals('username1', $provider->getUsername());
18 | $this->assertEquals('password1', $provider->getPassword());
19 | }
20 |
21 | public function testWithCustomKeys(): void {
22 | $vault = $this->getTestVault();
23 | $provider = new CredentialsProvider($vault, 'path/to/secret2', 'username-2', 'password-2');
24 | $this->assertEquals('username2', $provider->getUsername());
25 | $this->assertEquals('password2', $provider->getPassword());
26 | }
27 |
28 | protected function getTestVault(): Vault {
29 | return new Memory\MemoryVault(array(
30 | 'secrets' => array(
31 | 'path/to/secret1' => array(
32 | 'username' => 'username1',
33 | 'password' => 'password1',
34 | ),
35 | 'path/to/secret2' => array(
36 | 'username-2' => 'username2',
37 | 'password-2' => 'password2',
38 | ),
39 | ),
40 | ));
41 | }
42 | }
--------------------------------------------------------------------------------
/tests/TgVault/File/FileVaultTest.php:
--------------------------------------------------------------------------------
1 | __DIR__.'/test-config.json'));
16 | for ($i = 1; $i<3; $i++) {
17 | $this->assertNotNull($vault->getSecret('my/secret/number/'.$i));
18 | $this->assertEquals('my-username'.$i, $vault->getSecret('my/secret/number/'.$i)->get('username'));
19 | $this->assertEquals('my-password'.$i, $vault->getSecret('my/secret/number/'.$i)->get('password'));
20 | }
21 | }
22 |
23 | }
--------------------------------------------------------------------------------
/tests/TgVault/File/test-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "secrets": {
3 | "my/secret/number/1" : {
4 | "username" : "my-username1",
5 | "password" : "my-password1"
6 | },
7 | "my/secret/number/2" : {
8 | "username" : "my-username2",
9 | "password" : "my-password2"
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/tests/TgVault/Hashicorp/CacheTest.php:
--------------------------------------------------------------------------------
1 | getConfig());
16 | for ($i = 1; $i<3; $i++) {
17 | $this->assertNotNull($vault->getSecret('path/to/secret'.$i));
18 | $this->assertEquals('value-'.$i.'-1', $vault->getSecret('path/to/secret'.$i)->get('key-1'));
19 | $this->assertEquals('value-'.$i.'-2', $vault->getSecret('path/to/secret'.$i)->get('key-2'));
20 | }
21 | }
22 |
23 | protected function getConfig(): array {
24 | return $config = array(
25 | 'secrets' => array(
26 | 'path/to/secret1' => $this->getSecret(1),
27 | 'path/to/secret2' => $this->getSecret(2),
28 | ),
29 | );
30 | }
31 |
32 | protected function getSecret(int $init): array {
33 | return array(
34 | 'key-1' => 'value-'.$init.'-1',
35 | 'key-2' => 'value-'.$init.'-2',
36 | );
37 | }
38 | }
--------------------------------------------------------------------------------
/tests/TgVault/SecretProviderTest.php:
--------------------------------------------------------------------------------
1 | getTestVault();
16 | $provider = new SecretProvider($vault, 'my/secret/number/2');
17 | $this->assertEquals('my-username2', $provider->get('username'));
18 | $this->assertEquals('my-password2', $provider->get('password'));
19 | }
20 |
21 | protected function getTestVault(): Vault {
22 | return new File\FileVault(array('filename' => __DIR__.'/File/test-config.json'));
23 | }
24 | }
--------------------------------------------------------------------------------
/tests/TgVault/SecretTest.php:
--------------------------------------------------------------------------------
1 | metadata = new \stdClass;
17 | $data->metadata->time = time();
18 | $data->data = new \stdClass;
19 | $data->data->key1 = "value1";
20 | $data->data->key2 = "value2";
21 | $secret = new Secret($data);
22 | $this->assertInstanceOf(Secret::class, $secret);
23 | $this->assertSame('value1', $secret->get('key1'));
24 | $this->assertSame('value2', $secret->get('key2'));
25 | }
26 |
27 | public function testConstructFromArray(): void {
28 | $data = array(
29 | 'metadata' => array(
30 | 'time' => time(),
31 | ),
32 | 'data' => array(
33 | 'key1' => "value1",
34 | 'key2' => "value2",
35 | ),
36 | );
37 | $secret = new Secret($data);
38 | $this->assertInstanceOf(Secret::class, $secret);
39 | $this->assertSame('value1', $secret->get('key1'));
40 | $this->assertSame('value2', $secret->get('key2'));
41 | }
42 |
43 | }
--------------------------------------------------------------------------------
/tests/TgVault/VaultFactoryTest.php:
--------------------------------------------------------------------------------
1 | getConfig();
16 | $vault = VaultFactory::create($config['file']);
17 | $this->assertInstanceOf(File\FileVault::class, $vault);
18 | for ($i = 1; $i<3; $i++) {
19 | $this->assertNotNull($vault->getSecret('my/secret/number/'.$i));
20 | $this->assertEquals('my-username'.$i, $vault->getSecret('my/secret/number/'.$i)->get('username'));
21 | $this->assertEquals('my-password'.$i, $vault->getSecret('my/secret/number/'.$i)->get('password'));
22 | }
23 | }
24 |
25 | public function testConstructMemoryVault(): void {
26 | $config = $this->getConfig();
27 | $vault = VaultFactory::create($config['memory']);
28 | $this->assertInstanceOf(Memory\MemoryVault::class, $vault);
29 | for ($i = 1; $i<3; $i++) {
30 | $this->assertNotNull($vault->getSecret('path/to/secret'.$i));
31 | $this->assertEquals('value-'.$i.'-1', $vault->getSecret('path/to/secret'.$i)->get('key-1'));
32 | $this->assertEquals('value-'.$i.'-2', $vault->getSecret('path/to/secret'.$i)->get('key-2'));
33 | }
34 | }
35 |
36 | protected function getConfig(): array {
37 | return array(
38 | 'file' => array(
39 | 'type' => 'file',
40 | 'config' => array(
41 | 'filename' => __DIR__.'/File/test-config.json'
42 | ),
43 | ),
44 | 'memory' => array(
45 | 'type' => 'memory',
46 | 'config' => array(
47 | 'secrets' => array(
48 | 'path/to/secret1' => array(
49 | 'key-1' => 'value-1-1',
50 | 'key-2' => 'value-1-2',
51 | ),
52 | 'path/to/secret2' => array(
53 | 'key-1' => 'value-2-1',
54 | 'key-2' => 'value-2-2',
55 | ),
56 | ),
57 | ),
58 | ),
59 | );
60 | }
61 | }
--------------------------------------------------------------------------------