Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added exceptions handling #3

Merged
merged 2 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .env
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
COMPOSE_PROJECT_NAME=itkdev-vault
COMPOSE_DOMAIN=php-vault.local.itkdev.dk
COMPOSE_PROJECT_NAME=vault-library
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ See [keep a changelog] for information about writing changes to this log.

## [Unreleased]

* Added version support to secrets.
* Added test cases.
* Added cache of token and possibility to cache secrets.
* Added get secrets.
Expand Down
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,4 @@ composer require itkdev/vault

## Usage

@TODO: Add links to the symfony bundel and drupal Keys provider module as
examples on how to use this library.
See https://github.com/itk-dev/vault-bundle
7 changes: 7 additions & 0 deletions src/Vault/Exception/NotFoundException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace ItkDev\Vault\Exception;

class NotFoundException extends VaultException
{
}
13 changes: 13 additions & 0 deletions src/Vault/Exception/VaultException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace ItkDev\Vault\Exception;

/**
* Main library exception.
*
* All other exceptions extend this exception, making it a catch-all for
* handling errors.
*/
class VaultException extends \Exception
{
}
65 changes: 47 additions & 18 deletions src/Vault/Vault.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@

namespace ItkDev\Vault;

use ItkDev\Vault\Exception\NotFoundException;
use ItkDev\Vault\Exception\VaultException;
use ItkDev\Vault\Model\Secret;
use ItkDev\Vault\Model\Token;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\SimpleCache\CacheInterface;
use Psr\SimpleCache\InvalidArgumentException;

readonly class Vault implements VaultInterface
{
Expand All @@ -21,11 +25,10 @@ public function __construct(
}

/**
* @throws \DateMalformedIntervalStringException
* @throws VaultException
* @throws \DateMalformedStringException
* @throws \JsonException
* @throws \Psr\Http\Client\ClientExceptionInterface
* @throws \Psr\SimpleCache\InvalidArgumentException
* @throws InvalidArgumentException
* @throws \DateMalformedIntervalStringException
*/
public function login(string $roleId, string $secretId, string $enginePath = 'approle', bool $refreshCache = false): Token
{
Expand All @@ -43,9 +46,18 @@ public function login(string $roleId, string $secretId, string $enginePath = 'ap
->withHeader('Content-Type', 'application/json')
->withBody($body);

$response = $this->httpClient->sendRequest($request);
try {
$response = $this->httpClient->sendRequest($request);
$data = json_decode($response->getBody(), associative: true, flags: JSON_THROW_ON_ERROR);
} catch (ClientExceptionInterface $e) {
throw new VaultException(sprintf('Vault login failed: %s', $e->getMessage()), previous: $e);
} catch (\JsonException $e) {
throw new VaultException(sprintf('Vault data decode failed: %s', $e->getMessage()), previous: $e);
}

$data = json_decode($response->getBody(), associative: true, flags: JSON_THROW_ON_ERROR);
if (isset($data['errors'])) {
throw new VaultException(sprintf('Vault login failed: %s', reset($data['errors'])));
}

$ttl = (int) $data['auth']['lease_duration'];
$now = new \DateTimeImmutable(timezone: new \DateTimeZone('UTC'));
Expand All @@ -64,18 +76,19 @@ public function login(string $roleId, string $secretId, string $enginePath = 'ap
}

/**
* @throws VaultException
* @throws \DateMalformedStringException
* @throws \JsonException
* @throws \Psr\Http\Client\ClientExceptionInterface
* @throws \Psr\SimpleCache\InvalidArgumentException
* @throws InvalidArgumentException
* @throws NotFoundException
*/
public function getSecret(Token $token, string $path, string $secret, string $id, bool $useCache = false, bool $refreshCache = false, int $expire = 0): Secret
public function getSecret(Token $token, string $path, string $secret, string $id, ?int $version = null, bool $useCache = false, bool $refreshCache = false, int $expire = 0): Secret
{
$secret = $this->getSecrets(
token: $token,
path: $path,
secret: $secret,
ids: [$id],
version: $version,
useCache: $useCache,
refreshCache: $refreshCache,
expire: $expire
Expand All @@ -85,29 +98,45 @@ public function getSecret(Token $token, string $path, string $secret, string $id
}

/**
* @throws VaultException
* @throws NotFoundException
* @throws \DateMalformedStringException
* @throws \JsonException
* @throws \Psr\Http\Client\ClientExceptionInterface
* @throws \Psr\SimpleCache\InvalidArgumentException
* @throws InvalidArgumentException
*/
public function getSecrets(Token $token, string $path, string $secret, array $ids, bool $useCache = false, bool $refreshCache = false, int $expire = 0): array
public function getSecrets(Token $token, string $path, string $secret, array $ids, ?int $version = null, bool $useCache = false, bool $refreshCache = false, int $expire = 0): array
{
$cacheKey = 'itkdev_vault_secret_'.$secret;
$data = $this->cache->get($cacheKey);

if (!$useCache || is_null($data) || $refreshCache) {
$url = sprintf('%s/v1/%s/data/%s', $this->vaultUrl, $path, $secret);
if (!is_null($version)) {
$url .= '?version='.$version;
}

$request = $this->requestFactory->createRequest('GET', $url)
->withHeader('Content-Type', 'application/json')
->withHeader('Authorization', 'Bearer '.$token->token);
$response = $this->httpClient->sendRequest($request);

$res = json_decode($response->getBody(), associative: true, flags: JSON_THROW_ON_ERROR);
try {
$response = $this->httpClient->sendRequest($request);
$res = json_decode($response->getBody(), associative: true, flags: JSON_THROW_ON_ERROR);
} catch (ClientExceptionInterface $e) {
throw new VaultException(sprintf('Vault fetch failed: %s', $e->getMessage()), previous: $e);
} catch (\JsonException $e) {
throw new VaultException(sprintf('Vault data decode failed: %s', $e->getMessage()), previous: $e);
}

if (isset($res['errors'])) {
if (empty($res['errors'])) {
throw new NotFoundException('Secrets not found.');
}
preg_match('/.*:\n\t\* (.+)\n\n$/', reset($res['errors']), $matches);
throw new VaultException(sprintf('Vault failed: %s', $matches[1] ?? ''));
}

$created = new \DateTimeImmutable($res['data']['metadata']['created_time'], new \DateTimeZone('UTC'));
$version = $res['data']['metadata']['version'];

$data = [];
if (!empty($ids)) {
$secrets = $res['data']['data'];
Expand All @@ -120,7 +149,7 @@ public function getSecrets(Token $token, string $path, string $secret, array $id
createdAt: $created
);
} else {
throw new \InvalidArgumentException(sprintf('Secret with ID "%s" not found.', $id));
throw new NotFoundException(sprintf('Secret with ID "%s" not found.', $id));
}
}
}
Expand Down
15 changes: 13 additions & 2 deletions src/Vault/VaultInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace ItkDev\Vault;

use ItkDev\Vault\Exception\VaultException;
use ItkDev\Vault\Model\Secret;
use ItkDev\Vault\Model\Token;

Expand All @@ -21,6 +22,8 @@ interface VaultInterface
*
* @return Token
* Token that can be used to communicate with the vault
*
* @throws VaultException
*/
public function login(string $roleId, string $secretId, string $enginePath = 'approle', bool $refreshCache = false): Token;

Expand All @@ -35,6 +38,8 @@ public function login(string $roleId, string $secretId, string $enginePath = 'ap
* The type of secret being requested
* @param string $id
* An array of identifiers specifying which secrets to retrieve
* @param int|null $version
* The version of the secret
* @param bool $useCache
* Optional parameter to indicate whether to use cached secrets. Defaults to false.
* @param bool $refreshCache
Expand All @@ -44,8 +49,10 @@ public function login(string $roleId, string $secretId, string $enginePath = 'ap
*
* @return Secret
* The secret found
*
* @throws VaultException
*/
public function getSecret(Token $token, string $path, string $secret, string $id, bool $useCache = false, bool $refreshCache = false, int $expire = 0): Secret;
public function getSecret(Token $token, string $path, string $secret, string $id, ?int $version = null, bool $useCache = false, bool $refreshCache = false, int $expire = 0): Secret;

/**
* Retrieves secrets from the specified secret engine path.
Expand All @@ -58,6 +65,8 @@ public function getSecret(Token $token, string $path, string $secret, string $id
* The type of secret being requested
* @param array<string> $ids
* An array of identifiers specifying which secrets to retrieve
* @param int|null $version
* The version of the secrets
* @param bool $useCache
* Optional parameter to indicate whether to use cached secrets. Defaults to false.
* @param bool $refreshCache
Expand All @@ -67,6 +76,8 @@ public function getSecret(Token $token, string $path, string $secret, string $id
*
* @return array
* An array containing the requested secrets
*
* @throws VaultException
*/
public function getSecrets(Token $token, string $path, string $secret, array $ids, bool $useCache = false, bool $refreshCache = false, int $expire = 0): array;
public function getSecrets(Token $token, string $path, string $secret, array $ids, ?int $version = null, bool $useCache = false, bool $refreshCache = false, int $expire = 0): array;
}
Loading