Skip to content

Commit

Permalink
Added Azure Key Vault certificate provider
Browse files Browse the repository at this point in the history
  • Loading branch information
rimi-itk committed May 17, 2024
1 parent e2b0eb2 commit 1795f8e
Show file tree
Hide file tree
Showing 3 changed files with 208 additions and 1 deletion.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

* [PR-3](https://github.com/OS2web/os2web_key/pull/3)
Added Azure Key Vault certificate provider
* [PR-2](https://github.com/OS2web/os2web_key/pull/2)
Updated documentation
* [PR-1](https://github.com/OS2web/os2web_key/pull/1)
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"php": "^8.1",
"ext-openssl": "*",
"drupal/core": "^9 || ^10",
"drupal/key": "^1.17"
"drupal/key": "^1.17",
"itk-dev/serviceplatformen": "dev-feature/guzzle6-adapter"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "^1.0",
Expand Down
204 changes: 204 additions & 0 deletions src/Plugin/KeyProvider/AzureKeyVaultKeyProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
<?php

namespace Drupal\os2web_key\Plugin\KeyProvider;

use Drupal\Core\Form\FormStateInterface;
use Drupal\key\KeyInterface;
use Drupal\key\Plugin\KeyPluginFormInterface;
use Drupal\key\Plugin\KeyProviderBase;
use GuzzleHttp\Client;
use Http\Adapter\Guzzle6\Client as GuzzleAdapter;
use Http\Factory\Guzzle\RequestFactory;
use ItkDev\AzureKeyVault\Authorisation\VaultToken;
use ItkDev\AzureKeyVault\KeyVault\VaultSecret;
use ItkDev\Serviceplatformen\Certificate\AzureKeyVaultCertificateLocator;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
* Adds a key provider for Azure Key Vault.
*
* @KeyProvider(
* id = "os2web_azure_key_vault",
* label = @Translation("Azure Key Vault"),
* description = @Translation("Azure Key Vault"),
* storage_method = "remote",
* key_value = {
* "accepted" = FALSE,
* "required" = FALSE
* }
* )
*/
final class AzureKeyVaultKeyProvider extends KeyProviderBase implements KeyPluginFormInterface {

private const TENANT_ID = 'tenant_id';
private const APPLICATION_ID = 'application_id';
private const CLIENT_SECRET = 'client_secret';
private const NAME = 'name';
private const SECRET = 'secret';
private const VERSION = 'version';

/**
* Constructor.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The unique ID of this plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Psr\Log\LoggerInterface $logger
* The logger for this module.
*/
public function __construct(
array $configuration,
string $plugin_id,
$plugin_definition,
private readonly LoggerInterface $logger,
) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
}

/**
* {@inheritdoc}
*/
public static function create(
ContainerInterface $container,
array $configuration,
$plugin_id,
$plugin_definition,
) {
/** @var \Psr\Log\LoggerInterface $logger */
$logger = $container->get('logger.channel.os2web_key');

return new static(
$configuration,
$plugin_id,
$plugin_definition,
$logger
);
}

/**
* {@inheritdoc}
*/
public function defaultConfiguration(): array {
return [
self::TENANT_ID => '',
self::APPLICATION_ID => '',
self::CLIENT_SECRET => '',
self::NAME => '',
self::SECRET => '',
self::VERSION => '',
];
}

/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state): array {
$configuration = $this->getConfiguration();

$settings = [
self::TENANT_ID => ['title' => $this->t('Tenant id')],
self::APPLICATION_ID => ['title' => $this->t('Application id')],
self::CLIENT_SECRET => ['title' => $this->t('Client secret')],
self::NAME => ['title' => $this->t('Name')],
self::SECRET => ['title' => $this->t('Secret')],
self::VERSION => ['title' => $this->t('Version')],
];

foreach ($settings as $key => $info) {
$form[$key] = [
'#type' => 'textfield',
'#title' => $info['title'],
'#default_value' => $configuration[$key] ?? NULL,
'#required' => TRUE,
];
}

return $form;
}

/**
* {@inheritdoc}
*/
public function validateConfigurationForm(array &$form, FormStateInterface $form_state): void {
try {
return $this->getCertificate();

Check failure on line 128 in src/Plugin/KeyProvider/AzureKeyVaultKeyProvider.php

View workflow job for this annotation

GitHub Actions / PHP code analysis (8.1)

Method Drupal\os2web_key\Plugin\KeyProvider\AzureKeyVaultKeyProvider::validateConfigurationForm() with return type void returns string but should not return anything.
}
catch (\Throwable $throwable) {
$form_state->setError($form, $this->t('Error getting certificate: %message', ['%message' => $throwable->getMessage()]));
}
}

/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state): void {
$this->setConfiguration($form_state->getValues());
}

/**
* {@inheritdoc}
*/
public function getKeyValue(KeyInterface $key): ?string {
try {
return $this->getCertificate();
}
catch (\Throwable $throwable) {
}

return NULL;
}

/**
* Get certificate.
*
* @return string
* The certificate.
*
* @throws \ItkDev\AzureKeyVault\Exception\TokenException
* @throws \ItkDev\Serviceplatformen\Certificate\Exception\AzureKeyVaultCertificateLocatorException
*/
private function getCertificate(): string {
try {
$httpClient = new GuzzleAdapter(new Client());
$requestFactory = new RequestFactory();

$vaultToken = new VaultToken($httpClient, $requestFactory);

$options = $this->getConfiguration();

$token = $vaultToken->getToken(
$options[self::TENANT_ID],
$options[self::APPLICATION_ID],
$options[self::CLIENT_SECRET],
);

$vault = new VaultSecret(
$httpClient,
$requestFactory,
$options[self::NAME],
$token->getAccessToken()
);

$locator = new AzureKeyVaultCertificateLocator(
$vault,
$options[self::SECRET],
$options[self::VERSION],
);

return $locator->getCertificate();
}
catch (\Exception $exception) {
// Log the exception and re-throw it.
$this->logger->error('Error getting certificate: @message', [
'@message' => $exception->getMessage(),
'throwable' => $exception,
]);
throw $exception;
}
}

}

0 comments on commit 1795f8e

Please sign in to comment.