From c63e103c060a865e1e06b3d5a1d97fb166d1387d Mon Sep 17 00:00:00 2001 From: Damian Kwolek Date: Sat, 1 Jul 2023 23:49:48 +0200 Subject: [PATCH 1/9] * Using correct Content-Type for PATCH method * Corrected phpdoc for namespace * PSR-12 formatting --- src/BaseClient.php | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/BaseClient.php b/src/BaseClient.php index 7b28243..ef2f796 100644 --- a/src/BaseClient.php +++ b/src/BaseClient.php @@ -25,9 +25,9 @@ */ abstract class BaseClient implements LoggerAwareInterface { - public const VERSION_1 = 'v1'; - use LoggerAwareTrait; + + public const VERSION_1 = 'v1'; /** * @var string @@ -40,7 +40,7 @@ abstract class BaseClient implements LoggerAwareInterface protected $token; /** - * @var Namespace + * @var string */ protected $namespace; @@ -116,9 +116,10 @@ public function head(string $path): Response */ public function send(string $method, string $path, string $body = ''): ResponseInterface { + $method = strtoupper($method); $headers = [ 'User-Agent' => 'VaultPHP/1.0.0', - 'Content-Type' => 'application/json', + 'Content-Type' => ($method === 'PATCH' ? 'application/merge-patch+json' : 'application/json'), ]; if ($this->token) { @@ -134,7 +135,7 @@ public function send(string $method, string $path, string $body = ''): ResponseI $this->baseUri = $this->baseUri->withQuery($query); } - $request = $this->requestFactory->createRequest(strtoupper($method), $this->baseUri->withPath($path)); + $request = $this->requestFactory->createRequest($method, $this->baseUri->withPath($path)); foreach ($headers as $name => $value) { $request = $request->withHeader($name, $value); @@ -309,7 +310,7 @@ public function setToken(Token $token) } /** - * @return Namespace + * @return string */ public function getNamespace(): string { @@ -317,7 +318,7 @@ public function getNamespace(): string } /** - * @param String $namespace + * @param string $namespace * * @return $this */ From 7f9385e00ce4431129097cedb7c7655de36ee98a Mon Sep 17 00:00:00 2001 From: Damian Kwolek Date: Sun, 2 Jul 2023 00:02:05 +0200 Subject: [PATCH 2/9] * Allowing to json_encode BaseObject * Using correct RuntimeException --- src/BaseObject.php | 17 ++++++++++++--- src/Helpers/ModelHelper.php | 41 ++++++++++++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/src/BaseObject.php b/src/BaseObject.php index 9ac5170..f1191a9 100644 --- a/src/BaseObject.php +++ b/src/BaseObject.php @@ -2,15 +2,16 @@ namespace Vault; -use RuntimeException; +use Vault\Exceptions\RuntimeException; use Vault\Helpers\ArrayHelper; +use Vault\Helpers\ModelHelper; /** - * Class Object + * Class BaseObject * * @package Vault */ -class BaseObject +class BaseObject implements \JsonSerializable { /** * Object constructor. @@ -193,4 +194,14 @@ public function getFields(): array return $result; } + + /** + * @return array + * + * @throws RuntimeException + */ + public function jsonSerialize(): array + { + return ModelHelper::snakelize($this->toArray(), false); + } } diff --git a/src/Helpers/ModelHelper.php b/src/Helpers/ModelHelper.php index fe0d7aa..d0ac12f 100644 --- a/src/Helpers/ModelHelper.php +++ b/src/Helpers/ModelHelper.php @@ -2,8 +2,10 @@ namespace Vault\Helpers; +use Vault\Exceptions\RuntimeException; + /** - * Class Model + * Class ModelHelper * * @package Vault\Helper */ @@ -36,4 +38,41 @@ private static function camelizeString(string $data): string return lcfirst($camelizedString); } + + /** + * @param array $data + * @param bool $recursive + * + * @return array + * + * @throws RuntimeException + */ + public static function snakelize(array $data, $recursive = true): array + { + $return = []; + + foreach ($data as $key => $value) { + if (is_array($value) && $recursive) { + $value = self::snakelize($value, $recursive); + } + + $return[self::snakelizeString($key)] = $value; + } + + return $return; + } + + private static function snakelizeString(string $data): string + { + $snakelizedString = preg_replace('~(?<=\\w)([A-Z])~u', '_$1', $data); + + if ($snakelizedString === null) { + throw new RuntimeException(sprintf( + 'preg_replace returned null when trying to snakelize value "%s"', + $data + )); + } + + return mb_strtolower($snakelizedString); + } } From ccd2f0add90673a284df9db1b98a46e3ebd18081 Mon Sep 17 00:00:00 2001 From: Damian Kwolek Date: Sun, 2 Jul 2023 00:06:18 +0200 Subject: [PATCH 3/9] * Added abstraction for Secret Engines --- src/SecretsEngines/AbstractSecretsEngine.php | 69 ++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 src/SecretsEngines/AbstractSecretsEngine.php diff --git a/src/SecretsEngines/AbstractSecretsEngine.php b/src/SecretsEngines/AbstractSecretsEngine.php new file mode 100644 index 0000000..f2df2fc --- /dev/null +++ b/src/SecretsEngines/AbstractSecretsEngine.php @@ -0,0 +1,69 @@ +client = $client; + if (empty($mount)) { + throw new RuntimeException('Secrets Engine require not-empty mount path'); + } + if ($mount[0] !== '/') { + $mount = '/'.$mount; + } + $this->mount = $mount; + } + + public function buildPath(string $path): string + { + return sprintf('%s/%s', $this->client->buildPath($this->mount), $path); + } + + /** + * @return Client + */ + public function getClient(): Client + { + return $this->client; + } + + /** + * @param Client $client + * + * @return $this + */ + public function setClient(Client $client): self + { + $this->client = $client; + + return $this; + } + + /** + * @return string + */ + public function getMount(): string + { + return $this->mount; + } +} From f93eb9ac8b79dc0c8b073213d4b6024a7aad137d Mon Sep 17 00:00:00 2001 From: Damian Kwolek Date: Sun, 2 Jul 2023 00:09:33 +0200 Subject: [PATCH 4/9] * Added Key/Value Version 1 implementation * Added Cubbyhole implementation --- src/SecretsEngines/CubbyholeSecretsEngine.php | 23 ++++++++++ .../KeyValueVersion1SecretsEngine.php | 44 +++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 src/SecretsEngines/CubbyholeSecretsEngine.php create mode 100644 src/SecretsEngines/KeyValueVersion1SecretsEngine.php diff --git a/src/SecretsEngines/CubbyholeSecretsEngine.php b/src/SecretsEngines/CubbyholeSecretsEngine.php new file mode 100644 index 0000000..0f8f01e --- /dev/null +++ b/src/SecretsEngines/CubbyholeSecretsEngine.php @@ -0,0 +1,23 @@ +client->get( + parent::buildPath($path) + ); + } + + public function list(string $path): Response + { + return $this->client->list( + parent::buildPath($path) + ); + } + + public function createOrUpdate(string $path, array $data = []): Response + { + return $this->client->post( + parent::buildPath($path), + json_encode($data) + ); + } + + public function delete(string $path): Response + { + return $this->client->delete( + parent::buildPath($path) + ); + } +} From 894362877eea5a1873e96e59447b5eccdfb566bd Mon Sep 17 00:00:00 2001 From: Damian Kwolek Date: Sun, 2 Jul 2023 00:54:12 +0200 Subject: [PATCH 5/9] * Added Key/Value Version 2 implementation --- .../KeyValueVersion2/ListResponseBuilder.php | 27 ++ .../ReadConfigurationResponseBuilder.php | 27 ++ .../ReadMetadataResponseBuilder.php | 36 +++ .../KeyValueVersion2/ReadResponseBuilder.php | 30 ++ .../ReadSubkeysResponseBuilder.php | 30 ++ .../VersionMetadataResponseBuilder.php | 27 ++ src/Models/KeyValueVersion2/Configuration.php | 106 +++++++ .../KeyValueVersion2/SecretMetadata.php | 42 +++ src/Models/KeyValueVersion2/WriteOptions.php | 44 +++ .../KeyValueVersion2/ListResponse.php | 26 ++ .../KeyValueVersion2/ReadMetadataResponse.php | 78 +++++ .../KeyValueVersion2/ReadResponse.php | 29 ++ .../KeyValueVersion2/ReadSubkeysResponse.php | 29 ++ .../KeyValueVersion2/Traits/MetadataTrait.php | 26 ++ .../KeyValueVersion2/VersionMetadata.php | 78 +++++ .../KeyValueVersion2SecretsEngine.php | 282 ++++++++++++++++++ 16 files changed, 917 insertions(+) create mode 100644 src/Builders/KeyValueVersion2/ListResponseBuilder.php create mode 100644 src/Builders/KeyValueVersion2/ReadConfigurationResponseBuilder.php create mode 100644 src/Builders/KeyValueVersion2/ReadMetadataResponseBuilder.php create mode 100644 src/Builders/KeyValueVersion2/ReadResponseBuilder.php create mode 100644 src/Builders/KeyValueVersion2/ReadSubkeysResponseBuilder.php create mode 100644 src/Builders/KeyValueVersion2/VersionMetadataResponseBuilder.php create mode 100644 src/Models/KeyValueVersion2/Configuration.php create mode 100644 src/Models/KeyValueVersion2/SecretMetadata.php create mode 100644 src/Models/KeyValueVersion2/WriteOptions.php create mode 100644 src/ResponseModels/KeyValueVersion2/ListResponse.php create mode 100644 src/ResponseModels/KeyValueVersion2/ReadMetadataResponse.php create mode 100644 src/ResponseModels/KeyValueVersion2/ReadResponse.php create mode 100644 src/ResponseModels/KeyValueVersion2/ReadSubkeysResponse.php create mode 100644 src/ResponseModels/KeyValueVersion2/Traits/MetadataTrait.php create mode 100644 src/ResponseModels/KeyValueVersion2/VersionMetadata.php create mode 100644 src/SecretsEngines/KeyValueVersion2SecretsEngine.php diff --git a/src/Builders/KeyValueVersion2/ListResponseBuilder.php b/src/Builders/KeyValueVersion2/ListResponseBuilder.php new file mode 100644 index 0000000..c49912c --- /dev/null +++ b/src/Builders/KeyValueVersion2/ListResponseBuilder.php @@ -0,0 +1,27 @@ +getData(); + + return new ListResponse(ModelHelper::camelize($data, false)); + } +} diff --git a/src/Builders/KeyValueVersion2/ReadConfigurationResponseBuilder.php b/src/Builders/KeyValueVersion2/ReadConfigurationResponseBuilder.php new file mode 100644 index 0000000..93e5dcf --- /dev/null +++ b/src/Builders/KeyValueVersion2/ReadConfigurationResponseBuilder.php @@ -0,0 +1,27 @@ +getData(); + + return new Configuration(ModelHelper::camelize($data, false)); + } +} diff --git a/src/Builders/KeyValueVersion2/ReadMetadataResponseBuilder.php b/src/Builders/KeyValueVersion2/ReadMetadataResponseBuilder.php new file mode 100644 index 0000000..df699c3 --- /dev/null +++ b/src/Builders/KeyValueVersion2/ReadMetadataResponseBuilder.php @@ -0,0 +1,36 @@ +getData(); + + $versions = []; + foreach ($data['versions'] as $versionNumber => $versionData) { + $versionData['custom_metadata'] = $data['custom_metadata'] ?? null; + $versionData['version'] = $versionNumber; + $versions[$versionNumber] = new VersionMetadata(ModelHelper::camelize($versionData, false)); + } + $data['versions'] = $versions; + + return new ReadMetadataResponse(ModelHelper::camelize($data, false)); + } +} diff --git a/src/Builders/KeyValueVersion2/ReadResponseBuilder.php b/src/Builders/KeyValueVersion2/ReadResponseBuilder.php new file mode 100644 index 0000000..95e6677 --- /dev/null +++ b/src/Builders/KeyValueVersion2/ReadResponseBuilder.php @@ -0,0 +1,30 @@ +getData(); + + $data['metadata'] = new VersionMetadata(ModelHelper::camelize($data['metadata'], false)); + + return new ReadResponse($data); + } +} diff --git a/src/Builders/KeyValueVersion2/ReadSubkeysResponseBuilder.php b/src/Builders/KeyValueVersion2/ReadSubkeysResponseBuilder.php new file mode 100644 index 0000000..f9176f5 --- /dev/null +++ b/src/Builders/KeyValueVersion2/ReadSubkeysResponseBuilder.php @@ -0,0 +1,30 @@ +getData(); + + $data['metadata'] = new VersionMetadata(ModelHelper::camelize($data['metadata'], false)); + + return new ReadSubkeysResponse($data); + } +} diff --git a/src/Builders/KeyValueVersion2/VersionMetadataResponseBuilder.php b/src/Builders/KeyValueVersion2/VersionMetadataResponseBuilder.php new file mode 100644 index 0000000..dd38b34 --- /dev/null +++ b/src/Builders/KeyValueVersion2/VersionMetadataResponseBuilder.php @@ -0,0 +1,27 @@ +getData(); + + return new VersionMetadata(ModelHelper::camelize($data, false)); + } +} diff --git a/src/Models/KeyValueVersion2/Configuration.php b/src/Models/KeyValueVersion2/Configuration.php new file mode 100644 index 0000000..c2ec7be --- /dev/null +++ b/src/Models/KeyValueVersion2/Configuration.php @@ -0,0 +1,106 @@ +casRequired; + } + + /** + * Set {@see $casRequired cas_required} + * + * @param bool $casRequired + * + * @return $this + */ + public function setCasRequired(bool $casRequired): self + { + $this->casRequired = $casRequired; + + return $this; + } + + /** + * Get {@see $deleteVersionAfter delete_version_after} + * + * @return string + */ + public function getDeleteVersionAfter(): string + { + return $this->deleteVersionAfter; + } + + /** + * Set {@see $deleteVersionAfter delete_version_after} + * + * @param string $deleteVersionAfter + * + * @return $this + */ + public function setDeleteVersionAfter(string $deleteVersionAfter): self + { + $this->deleteVersionAfter = $deleteVersionAfter; + + return $this; + } + + /** + * Get {@see $maxVersions max_versions} + * + * @return int + */ + public function getMaxVersions(): int + { + return $this->maxVersions; + } + + /** + * Set {@see $maxVersions max_versions} + * + * @param int $maxVersions + * + * @return $this + */ + public function setMaxVersions(int $maxVersions): self + { + $this->maxVersions = $maxVersions; + + return $this; + } +} diff --git a/src/Models/KeyValueVersion2/SecretMetadata.php b/src/Models/KeyValueVersion2/SecretMetadata.php new file mode 100644 index 0000000..970d263 --- /dev/null +++ b/src/Models/KeyValueVersion2/SecretMetadata.php @@ -0,0 +1,42 @@ +customMetadata; + } + + /** + * Set {@see $customMetadata custom_metadata} + * + * @param array|null $customMetadata + * + * @return $this + */ + public function setCustomMetadata(?array $customMetadata): self + { + $this->customMetadata = $customMetadata; + + return $this; + } +} diff --git a/src/Models/KeyValueVersion2/WriteOptions.php b/src/Models/KeyValueVersion2/WriteOptions.php new file mode 100644 index 0000000..782918f --- /dev/null +++ b/src/Models/KeyValueVersion2/WriteOptions.php @@ -0,0 +1,44 @@ +cas; + } + + /** + * Set {@see $cas cas} + * + * @param int $cas + * + * @return $this + */ + public function setCas(?int $cas): self + { + $this->cas = $cas; + + return $this; + } +} diff --git a/src/ResponseModels/KeyValueVersion2/ListResponse.php b/src/ResponseModels/KeyValueVersion2/ListResponse.php new file mode 100644 index 0000000..294ab3c --- /dev/null +++ b/src/ResponseModels/KeyValueVersion2/ListResponse.php @@ -0,0 +1,26 @@ +keys; + } +} diff --git a/src/ResponseModels/KeyValueVersion2/ReadMetadataResponse.php b/src/ResponseModels/KeyValueVersion2/ReadMetadataResponse.php new file mode 100644 index 0000000..2d2eba3 --- /dev/null +++ b/src/ResponseModels/KeyValueVersion2/ReadMetadataResponse.php @@ -0,0 +1,78 @@ +createdTime; + } + + /** + * @return int + */ + public function getCurrentVersion(): int + { + return $this->currentVersion; + } + + /** + * @return int + */ + public function getOldestVersion(): int + { + return $this->oldestVersion; + } + + /** + * @return string|null + */ + public function getUpdatedTime(): ?string + { + return $this->updatedTime; + } + + /** + * @return VersionMetadata[] + */ + public function getVersions(): array + { + return $this->versions; + } +} diff --git a/src/ResponseModels/KeyValueVersion2/ReadResponse.php b/src/ResponseModels/KeyValueVersion2/ReadResponse.php new file mode 100644 index 0000000..00fddde --- /dev/null +++ b/src/ResponseModels/KeyValueVersion2/ReadResponse.php @@ -0,0 +1,29 @@ +data; + } +} diff --git a/src/ResponseModels/KeyValueVersion2/ReadSubkeysResponse.php b/src/ResponseModels/KeyValueVersion2/ReadSubkeysResponse.php new file mode 100644 index 0000000..7660898 --- /dev/null +++ b/src/ResponseModels/KeyValueVersion2/ReadSubkeysResponse.php @@ -0,0 +1,29 @@ +subkeys; + } +} diff --git a/src/ResponseModels/KeyValueVersion2/Traits/MetadataTrait.php b/src/ResponseModels/KeyValueVersion2/Traits/MetadataTrait.php new file mode 100644 index 0000000..93703e9 --- /dev/null +++ b/src/ResponseModels/KeyValueVersion2/Traits/MetadataTrait.php @@ -0,0 +1,26 @@ +metadata; + } +} diff --git a/src/ResponseModels/KeyValueVersion2/VersionMetadata.php b/src/ResponseModels/KeyValueVersion2/VersionMetadata.php new file mode 100644 index 0000000..b0dcb33 --- /dev/null +++ b/src/ResponseModels/KeyValueVersion2/VersionMetadata.php @@ -0,0 +1,78 @@ +createdTime; + } + + /** + * @return array|null + */ + public function getCustomMetadata(): ?array + { + return $this->customMetadata; + } + + /** + * @return string|null + */ + public function getDeletionTime(): ?string + { + return $this->deletionTime; + } + + /** + * @return bool + */ + public function isDestroyed(): bool + { + return $this->destroyed; + } + + /** + * @return int + */ + public function getVersion(): int + { + return $this->version; + } +} diff --git a/src/SecretsEngines/KeyValueVersion2SecretsEngine.php b/src/SecretsEngines/KeyValueVersion2SecretsEngine.php new file mode 100644 index 0000000..57c911e --- /dev/null +++ b/src/SecretsEngines/KeyValueVersion2SecretsEngine.php @@ -0,0 +1,282 @@ +client->post( + parent::buildPath('config'), + json_encode($config) + ); + } + + /** + * Reads current secrets engine configuration + * + * @return Configuration + **/ + public function readConfiguration(): Configuration + { + return ReadConfigurationResponseBuilder::build( + $this->client->get( + parent::buildPath('config') + ) + ); + } + + /** + * Read specified secret version + * + * @param string $path Path of the secret + * @param int $version Version to read (0 = latest) + * @return ReadResponse + **/ + public function read(string $path, int $version = 0): ReadResponse + { + return ReadResponseBuilder::build( + $this->client->get( + parent::buildPath( + sprintf('data/%s?version=%d', $path, $version) + ) + ) + ); + } + + /** + * Creates new version of a secret + * + * @param string $path Path of the secret + * @param array $data Payload to write + * @param WriteOptions|null $options Write options + * @return VersionMetadata + **/ + public function createOrUpdate(string $path, array $data = [], ?WriteOptions $options = null): VersionMetadata + { + $payload = [ + 'data' => $data, + ]; + if ($options) { + $payload['options'] = $options; + } + return VersionMetadataResponseBuilder::build( + $this->client->post( + parent::buildPath('data/'.$path), + json_encode($payload) + ) + ); + } + + /** + * Patches existing secret + * + * @param string $path Path of the secret + * @param array $data Payload to write + * @param WriteOptions|null $options Write options + * @return VersionMetadata + **/ + public function patch(string $path, array $data = [], ?WriteOptions $options = null): VersionMetadata + { + $payload = [ + 'data' => $data, + ]; + if ($options) { + $payload['options'] = $options; + } + return VersionMetadataResponseBuilder::build( + $this->client->patch( + parent::buildPath('data/'.$path), + json_encode($payload) + ) + ); + } + + /** + * Reads subkeys within a secret + * + * @param string $path Path of the secret + * @param int $version Version to read (0 = latest) + * @param int $depth Deepest nesting level (0 = no limit) + * @return ReadSubkeysResponse + **/ + public function readSubkeys(string $path, int $version = 0, int $depth = 0): ReadSubkeysResponse + { + return ReadSubkeysResponseBuilder::build( + $this->client->get( + parent::buildPath( + sprintf('subkeys/%s?version=%d&depth=%d', $path, $version, $depth) + ) + ) + ); + } + + /** + * Deletes latest version of the secret + * + * @param string $path Path of the secret + * @return Response + **/ + public function deleteLatest(string $path): Response + { + return $this->client->delete( + parent::buildPath('data/'.$path) + ); + } + + /** + * Deletes specified secret versions + * + * @param string $path Path of the secret + * @param int[] $versions Versions to delete + * @return Response + **/ + public function deleteVersions(string $path, array $versions = []): Response + { + $payload = [ + 'versions' => $versions + ]; + return $this->client->post( + parent::buildPath('delete/'.$path), + json_encode($payload) + ); + } + + /** + * Undeletes specified secret versions + * + * @param string $path Path of the secret + * @param int[] $versions Versions to delete + * @return Response + **/ + public function undeleteVersions(string $path, array $versions = []): Response + { + $payload = [ + 'versions' => $versions + ]; + return $this->client->post( + parent::buildPath('undelete/'.$path), + json_encode($payload) + ); + } + + /** + * Destroys (hard delete) specified secret versions + * + * @param string $path Path of the secret + * @param int[] $versions Versions to delete + * @return Response + **/ + public function destroyVersions(string $path, array $versions = []): Response + { + $payload = [ + 'versions' => $versions + ]; + return $this->client->put( + parent::buildPath('destroy/'.$path), + json_encode($payload) + ); + } + + /** + * List secrets at specified path + * + * @param string $path Path to list secrets from + * @return ListResponse + **/ + public function list(string $path): ListResponse + { + return ListResponseBuilder::build( + $this->client->list( + parent::buildPath('metadata/'.$path) + ) + ); + } + + /** + * Reads specified secret metadata + * + * @param string $path Path of the secret + * @return ReadMetadataResponse + **/ + public function readMetadata(string $path): ReadMetadataResponse + { + return ReadMetadataResponseBuilder::build( + $this->client->get( + parent::buildPath('metadata/'.$path) + ) + ); + } + + /** + * Creates or updates specified secret metadata + * + * @param string $path Path of the secret + * @param SecretMetadata $metadata Metadata to set + * @return Response + **/ + public function createOrUpdateMetadata(string $path, SecretMetadata $metadata): Response + { + return $this->client->post( + parent::buildPath('metadata/'.$path), + json_encode($metadata) + ); + } + + /** + * Patches specified secret metadata + * + * @param string $path Path of the secret + * @param array $metadata Metadata to set + * @return Response + **/ + public function patchMetadata(string $path, array $metadata): Response + { + return $this->client->patch( + parent::buildPath('metadata/'.$path), + json_encode($metadata) + ); + } + + /** + * Deletes metadata and all versions + * + * @param string $path Path of the secret + * @return Response + **/ + public function deleteMetadata(string $path): Response + { + return $this->client->delete( + parent::buildPath('metadata/'.$path) + ); + } +} From 5fbd5e52aa6621927ace11a0569db1fd2f795169 Mon Sep 17 00:00:00 2001 From: Damian Kwolek Date: Sun, 2 Jul 2023 01:29:07 +0200 Subject: [PATCH 6/9] * Objectify response of kv1/list * Added missing phpdoc --- .../KeyValueVersion1/ListResponseBuilder.php | 27 ++++++++++++++ .../KeyValueVersion1/ListResponse.php | 26 ++++++++++++++ .../KeyValueVersion1SecretsEngine.php | 35 +++++++++++++++++-- 3 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 src/Builders/KeyValueVersion1/ListResponseBuilder.php create mode 100644 src/ResponseModels/KeyValueVersion1/ListResponse.php diff --git a/src/Builders/KeyValueVersion1/ListResponseBuilder.php b/src/Builders/KeyValueVersion1/ListResponseBuilder.php new file mode 100644 index 0000000..1556014 --- /dev/null +++ b/src/Builders/KeyValueVersion1/ListResponseBuilder.php @@ -0,0 +1,27 @@ +getData(); + + return new ListResponse(ModelHelper::camelize($data, false)); + } +} diff --git a/src/ResponseModels/KeyValueVersion1/ListResponse.php b/src/ResponseModels/KeyValueVersion1/ListResponse.php new file mode 100644 index 0000000..504b3c8 --- /dev/null +++ b/src/ResponseModels/KeyValueVersion1/ListResponse.php @@ -0,0 +1,26 @@ +keys; + } +} diff --git a/src/SecretsEngines/KeyValueVersion1SecretsEngine.php b/src/SecretsEngines/KeyValueVersion1SecretsEngine.php index 4efafec..6c4d315 100644 --- a/src/SecretsEngines/KeyValueVersion1SecretsEngine.php +++ b/src/SecretsEngines/KeyValueVersion1SecretsEngine.php @@ -2,6 +2,8 @@ namespace Vault\SecretsEngines; +use Vault\Builders\KeyValueVersion1\ListResponseBuilder; +use Vault\ResponseModels\KeyValueVersion1\ListResponse; use Vault\ResponseModels\Response; /** @@ -13,6 +15,12 @@ */ class KeyValueVersion1SecretsEngine extends AbstractSecretsEngine { + /** + * Read specified secret + * + * @param string $path Path of the secret + * @return Response + **/ public function read(string $path): Response { return $this->client->get( @@ -20,13 +28,28 @@ public function read(string $path): Response ); } - public function list(string $path): Response + /** + * List secrets at specified path + * + * @param string $path Path to list secrets from + * @return ListResponse + **/ + public function list(string $path): ListResponse { - return $this->client->list( - parent::buildPath($path) + return ListResponseBuilder::build( + $this->client->list( + parent::buildPath($path) + ) ); } + /** + * Create or update specified secret + * + * @param string $path Path of the secret + * @param array $data Payload to write + * @return Response + **/ public function createOrUpdate(string $path, array $data = []): Response { return $this->client->post( @@ -35,6 +58,12 @@ public function createOrUpdate(string $path, array $data = []): Response ); } + /** + * Delete secret + * + * @param string $path Path of the secret + * @return Response + **/ public function delete(string $path): Response { return $this->client->delete( From fffab7e3f20f1ef9b9f7caf89635853f1dc97826 Mon Sep 17 00:00:00 2001 From: Damian Kwolek Date: Sun, 2 Jul 2023 01:29:27 +0200 Subject: [PATCH 7/9] * PHPDoc fixes --- src/SecretsEngines/AbstractSecretsEngine.php | 9 +++++++ src/SecretsEngines/CubbyholeSecretsEngine.php | 3 +++ .../KeyValueVersion2SecretsEngine.php | 26 +++++++++---------- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/SecretsEngines/AbstractSecretsEngine.php b/src/SecretsEngines/AbstractSecretsEngine.php index f2df2fc..a544651 100644 --- a/src/SecretsEngines/AbstractSecretsEngine.php +++ b/src/SecretsEngines/AbstractSecretsEngine.php @@ -22,6 +22,10 @@ abstract class AbstractSecretsEngine */ protected $mount; + /** + * @param Client $client Authenticated Vault client + * @param string $mount Path to the secret engine (aka mount location) + */ public function __construct(Client $client, string $mount) { $this->client = $client; @@ -34,6 +38,11 @@ public function __construct(Client $client, string $mount) $this->mount = $mount; } + /** + * @param string $path Path of the secret + * + * @return string + */ public function buildPath(string $path): string { return sprintf('%s/%s', $this->client->buildPath($this->mount), $path); diff --git a/src/SecretsEngines/CubbyholeSecretsEngine.php b/src/SecretsEngines/CubbyholeSecretsEngine.php index 0f8f01e..0dc803c 100644 --- a/src/SecretsEngines/CubbyholeSecretsEngine.php +++ b/src/SecretsEngines/CubbyholeSecretsEngine.php @@ -13,6 +13,9 @@ */ class CubbyholeSecretsEngine extends KeyValueVersion1SecretsEngine { + /** + * @param Client $client Authenticated Vault client + */ public function __construct(Client $client) { parent::__construct( diff --git a/src/SecretsEngines/KeyValueVersion2SecretsEngine.php b/src/SecretsEngines/KeyValueVersion2SecretsEngine.php index 57c911e..6585543 100644 --- a/src/SecretsEngines/KeyValueVersion2SecretsEngine.php +++ b/src/SecretsEngines/KeyValueVersion2SecretsEngine.php @@ -28,7 +28,7 @@ class KeyValueVersion2SecretsEngine extends AbstractSecretsEngine { /** - * Configures secrets engine + * Configure secrets engine * * @param Configuration $config Configuration to set * @return Response @@ -42,7 +42,7 @@ public function configure(Configuration $config): Response } /** - * Reads current secrets engine configuration + * Read current secrets engine configuration * * @return Configuration **/ @@ -74,7 +74,7 @@ public function read(string $path, int $version = 0): ReadResponse } /** - * Creates new version of a secret + * Create new version of a secret * * @param string $path Path of the secret * @param array $data Payload to write @@ -98,7 +98,7 @@ public function createOrUpdate(string $path, array $data = [], ?WriteOptions $op } /** - * Patches existing secret + * Patch existing secret * * @param string $path Path of the secret * @param array $data Payload to write @@ -122,7 +122,7 @@ public function patch(string $path, array $data = [], ?WriteOptions $options = n } /** - * Reads subkeys within a secret + * Read subkeys within a secret * * @param string $path Path of the secret * @param int $version Version to read (0 = latest) @@ -141,7 +141,7 @@ public function readSubkeys(string $path, int $version = 0, int $depth = 0): Rea } /** - * Deletes latest version of the secret + * Delete latest version of the secret * * @param string $path Path of the secret * @return Response @@ -154,7 +154,7 @@ public function deleteLatest(string $path): Response } /** - * Deletes specified secret versions + * Delete specified secret versions * * @param string $path Path of the secret * @param int[] $versions Versions to delete @@ -172,7 +172,7 @@ public function deleteVersions(string $path, array $versions = []): Response } /** - * Undeletes specified secret versions + * Undelete specified secret versions * * @param string $path Path of the secret * @param int[] $versions Versions to delete @@ -190,7 +190,7 @@ public function undeleteVersions(string $path, array $versions = []): Response } /** - * Destroys (hard delete) specified secret versions + * Destroy (hard delete) specified secret versions * * @param string $path Path of the secret * @param int[] $versions Versions to delete @@ -223,7 +223,7 @@ public function list(string $path): ListResponse } /** - * Reads specified secret metadata + * Read specified secret metadata * * @param string $path Path of the secret * @return ReadMetadataResponse @@ -238,7 +238,7 @@ public function readMetadata(string $path): ReadMetadataResponse } /** - * Creates or updates specified secret metadata + * Create or update specified secret metadata * * @param string $path Path of the secret * @param SecretMetadata $metadata Metadata to set @@ -253,7 +253,7 @@ public function createOrUpdateMetadata(string $path, SecretMetadata $metadata): } /** - * Patches specified secret metadata + * Patch specified secret metadata * * @param string $path Path of the secret * @param array $metadata Metadata to set @@ -268,7 +268,7 @@ public function patchMetadata(string $path, array $metadata): Response } /** - * Deletes metadata and all versions + * Delete metadata and all versions * * @param string $path Path of the secret * @return Response From df9135600e6cdd2821929b1ad6227edf99cfb4f4 Mon Sep 17 00:00:00 2001 From: Damian Kwolek Date: Sun, 2 Jul 2023 02:04:38 +0200 Subject: [PATCH 8/9] * Added section about secret engine overlays to documentation --- docs/index.rst | 75 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index c206d01..f36c880 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -109,7 +109,8 @@ This array contains an element named ``keys``, whose value is an array of the se [ "keys": [ - "hello" + "hello", + "world" ] ] Fetching a secret @@ -149,6 +150,78 @@ Fetching a secret $data = $response->getData(); // Raw array with secret's content. + // ... +Secrets Engines overlays +================== +Each secret engine (such as Key/Value [versions 1/2], Cubbyhole, Transit, etc.) comes with a different APIs. +Overlays provide a way to use secret engine without worrying about underlaying path structure, in a more object-oriented way. + +To use one, simply create new instance while specifying authenticated Vault client and path at which it is located: + +.. code-block:: php + + setAuthenticationStrategy(new TokenAuthenticationStrategy('463763ae-0c3b-ff77-e137-af668941465c')) + ->authenticate(); + + if (!$authenticated) { + // Throw an exception or handle authentication failure. + } + + // Create an instance of KV2 secret engine overlay, mounted at path "secret" using authenticated client + $kv2 = new KeyValueVersion2SecretsEngine($client, 'secret'); + +List secret keys +---------------- + +.. code-block:: php + + // Request exception could appear here. + /** @var \Vault\ResponseModels\KeyValueVersion2\ListResponse $response */ + $response = $kv2->list(''); // Corresponds to calling $client->keys('/secret/metadata/') + $keys = $response->getKeys(); // Raw array of secret's keys + +Notice, that secret engine overlay knows structure of Vault response, so it can parse and objectify it. +It means you don't have to look for "keys" index in raw data array as before. On success, $keys would be equal to: + +.. code-block:: php + + [ + "hello", + "world" + ] + +Fetching a secret +---------------- + +.. code-block:: php + + // Request exception could appear here. + /** @var \Vault\ResponseModels\KeyValueVersion2\ReadResponse $response */ + $response = $kv2->read('database'); // Corresponds to calling $client->read('secret/data/database'); + + $data = $response->getData(); // Raw array with secret's content. + $metadata = $response->getMetadata(); // Object containing KV2 secret version metadata + // ... Indices and tables From a82555e085a25f932c6f13a697c3fa9446320d76 Mon Sep 17 00:00:00 2001 From: Damian Kwolek Date: Sun, 2 Jul 2023 02:51:26 +0200 Subject: [PATCH 9/9] * Fixed indentation --- .../KeyValueVersion2/ReadMetadataResponse.php | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/ResponseModels/KeyValueVersion2/ReadMetadataResponse.php b/src/ResponseModels/KeyValueVersion2/ReadMetadataResponse.php index 2d2eba3..37ac4b9 100644 --- a/src/ResponseModels/KeyValueVersion2/ReadMetadataResponse.php +++ b/src/ResponseModels/KeyValueVersion2/ReadMetadataResponse.php @@ -37,36 +37,36 @@ class ReadMetadataResponse extends SecretMetadata protected $versions = []; /** - * @return string - */ - public function getCreatedTime(): string + * @return string + */ + public function getCreatedTime(): string { - return $this->createdTime; - } + return $this->createdTime; + } /** - * @return int - */ - public function getCurrentVersion(): int + * @return int + */ + public function getCurrentVersion(): int { - return $this->currentVersion; - } + return $this->currentVersion; + } /** - * @return int - */ - public function getOldestVersion(): int + * @return int + */ + public function getOldestVersion(): int { - return $this->oldestVersion; - } + return $this->oldestVersion; + } /** - * @return string|null - */ - public function getUpdatedTime(): ?string + * @return string|null + */ + public function getUpdatedTime(): ?string { - return $this->updatedTime; - } + return $this->updatedTime; + } /** * @return VersionMetadata[]