Skip to content

Commit 1795f8e

Browse files
committed
Added Azure Key Vault certificate provider
1 parent e2b0eb2 commit 1795f8e

File tree

3 files changed

+208
-1
lines changed

3 files changed

+208
-1
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
* [PR-3](https://github.com/OS2web/os2web_key/pull/3)
11+
Added Azure Key Vault certificate provider
1012
* [PR-2](https://github.com/OS2web/os2web_key/pull/2)
1113
Updated documentation
1214
* [PR-1](https://github.com/OS2web/os2web_key/pull/1)

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
"php": "^8.1",
1414
"ext-openssl": "*",
1515
"drupal/core": "^9 || ^10",
16-
"drupal/key": "^1.17"
16+
"drupal/key": "^1.17",
17+
"itk-dev/serviceplatformen": "dev-feature/guzzle6-adapter"
1718
},
1819
"require-dev": {
1920
"dealerdirect/phpcodesniffer-composer-installer": "^1.0",
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
<?php
2+
3+
namespace Drupal\os2web_key\Plugin\KeyProvider;
4+
5+
use Drupal\Core\Form\FormStateInterface;
6+
use Drupal\key\KeyInterface;
7+
use Drupal\key\Plugin\KeyPluginFormInterface;
8+
use Drupal\key\Plugin\KeyProviderBase;
9+
use GuzzleHttp\Client;
10+
use Http\Adapter\Guzzle6\Client as GuzzleAdapter;
11+
use Http\Factory\Guzzle\RequestFactory;
12+
use ItkDev\AzureKeyVault\Authorisation\VaultToken;
13+
use ItkDev\AzureKeyVault\KeyVault\VaultSecret;
14+
use ItkDev\Serviceplatformen\Certificate\AzureKeyVaultCertificateLocator;
15+
use Psr\Log\LoggerInterface;
16+
use Symfony\Component\DependencyInjection\ContainerInterface;
17+
18+
/**
19+
* Adds a key provider for Azure Key Vault.
20+
*
21+
* @KeyProvider(
22+
* id = "os2web_azure_key_vault",
23+
* label = @Translation("Azure Key Vault"),
24+
* description = @Translation("Azure Key Vault"),
25+
* storage_method = "remote",
26+
* key_value = {
27+
* "accepted" = FALSE,
28+
* "required" = FALSE
29+
* }
30+
* )
31+
*/
32+
final class AzureKeyVaultKeyProvider extends KeyProviderBase implements KeyPluginFormInterface {
33+
34+
private const TENANT_ID = 'tenant_id';
35+
private const APPLICATION_ID = 'application_id';
36+
private const CLIENT_SECRET = 'client_secret';
37+
private const NAME = 'name';
38+
private const SECRET = 'secret';
39+
private const VERSION = 'version';
40+
41+
/**
42+
* Constructor.
43+
*
44+
* @param array $configuration
45+
* A configuration array containing information about the plugin instance.
46+
* @param string $plugin_id
47+
* The unique ID of this plugin instance.
48+
* @param mixed $plugin_definition
49+
* The plugin implementation definition.
50+
* @param \Psr\Log\LoggerInterface $logger
51+
* The logger for this module.
52+
*/
53+
public function __construct(
54+
array $configuration,
55+
string $plugin_id,
56+
$plugin_definition,
57+
private readonly LoggerInterface $logger,
58+
) {
59+
parent::__construct($configuration, $plugin_id, $plugin_definition);
60+
}
61+
62+
/**
63+
* {@inheritdoc}
64+
*/
65+
public static function create(
66+
ContainerInterface $container,
67+
array $configuration,
68+
$plugin_id,
69+
$plugin_definition,
70+
) {
71+
/** @var \Psr\Log\LoggerInterface $logger */
72+
$logger = $container->get('logger.channel.os2web_key');
73+
74+
return new static(
75+
$configuration,
76+
$plugin_id,
77+
$plugin_definition,
78+
$logger
79+
);
80+
}
81+
82+
/**
83+
* {@inheritdoc}
84+
*/
85+
public function defaultConfiguration(): array {
86+
return [
87+
self::TENANT_ID => '',
88+
self::APPLICATION_ID => '',
89+
self::CLIENT_SECRET => '',
90+
self::NAME => '',
91+
self::SECRET => '',
92+
self::VERSION => '',
93+
];
94+
}
95+
96+
/**
97+
* {@inheritdoc}
98+
*/
99+
public function buildConfigurationForm(array $form, FormStateInterface $form_state): array {
100+
$configuration = $this->getConfiguration();
101+
102+
$settings = [
103+
self::TENANT_ID => ['title' => $this->t('Tenant id')],
104+
self::APPLICATION_ID => ['title' => $this->t('Application id')],
105+
self::CLIENT_SECRET => ['title' => $this->t('Client secret')],
106+
self::NAME => ['title' => $this->t('Name')],
107+
self::SECRET => ['title' => $this->t('Secret')],
108+
self::VERSION => ['title' => $this->t('Version')],
109+
];
110+
111+
foreach ($settings as $key => $info) {
112+
$form[$key] = [
113+
'#type' => 'textfield',
114+
'#title' => $info['title'],
115+
'#default_value' => $configuration[$key] ?? NULL,
116+
'#required' => TRUE,
117+
];
118+
}
119+
120+
return $form;
121+
}
122+
123+
/**
124+
* {@inheritdoc}
125+
*/
126+
public function validateConfigurationForm(array &$form, FormStateInterface $form_state): void {
127+
try {
128+
return $this->getCertificate();
129+
}
130+
catch (\Throwable $throwable) {
131+
$form_state->setError($form, $this->t('Error getting certificate: %message', ['%message' => $throwable->getMessage()]));
132+
}
133+
}
134+
135+
/**
136+
* {@inheritdoc}
137+
*/
138+
public function submitConfigurationForm(array &$form, FormStateInterface $form_state): void {
139+
$this->setConfiguration($form_state->getValues());
140+
}
141+
142+
/**
143+
* {@inheritdoc}
144+
*/
145+
public function getKeyValue(KeyInterface $key): ?string {
146+
try {
147+
return $this->getCertificate();
148+
}
149+
catch (\Throwable $throwable) {
150+
}
151+
152+
return NULL;
153+
}
154+
155+
/**
156+
* Get certificate.
157+
*
158+
* @return string
159+
* The certificate.
160+
*
161+
* @throws \ItkDev\AzureKeyVault\Exception\TokenException
162+
* @throws \ItkDev\Serviceplatformen\Certificate\Exception\AzureKeyVaultCertificateLocatorException
163+
*/
164+
private function getCertificate(): string {
165+
try {
166+
$httpClient = new GuzzleAdapter(new Client());
167+
$requestFactory = new RequestFactory();
168+
169+
$vaultToken = new VaultToken($httpClient, $requestFactory);
170+
171+
$options = $this->getConfiguration();
172+
173+
$token = $vaultToken->getToken(
174+
$options[self::TENANT_ID],
175+
$options[self::APPLICATION_ID],
176+
$options[self::CLIENT_SECRET],
177+
);
178+
179+
$vault = new VaultSecret(
180+
$httpClient,
181+
$requestFactory,
182+
$options[self::NAME],
183+
$token->getAccessToken()
184+
);
185+
186+
$locator = new AzureKeyVaultCertificateLocator(
187+
$vault,
188+
$options[self::SECRET],
189+
$options[self::VERSION],
190+
);
191+
192+
return $locator->getCertificate();
193+
}
194+
catch (\Exception $exception) {
195+
// Log the exception and re-throw it.
196+
$this->logger->error('Error getting certificate: @message', [
197+
'@message' => $exception->getMessage(),
198+
'throwable' => $exception,
199+
]);
200+
throw $exception;
201+
}
202+
}
203+
204+
}

0 commit comments

Comments
 (0)