Skip to content

Commit f02a8c5

Browse files
authored
Merge pull request #3 from UrosPurtic/develop
Implement Hashicorp Vault for config and secrets
2 parents eb91a9d + 1678afb commit f02a8c5

2 files changed

Lines changed: 163 additions & 9 deletions

File tree

src/Processor.php

Lines changed: 114 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88

99
class Processor
1010
{
11+
const VAULT_KEYWORD = 'vault';
12+
const VAULT_URL_KEYWORD = 'url';
13+
const VAULT_API_TOKEN_KEYWORD = 'token';
1114

1215
/**
1316
* @var array
@@ -31,16 +34,27 @@ class Processor
3134

3235
private $useAggregator;
3336

37+
/**
38+
* @var VaultRepository
39+
*/
40+
private $vaultRepository;
41+
/**
42+
* @var array
43+
*/
44+
private $vaultVariables;
45+
46+
3447
/**
3548
* Processor constructor.
3649
* @param $path
3750
* @param $section
51+
* @param $useAggregator
3852
*/
3953
public function __construct($path, $section, $useAggregator)
4054
{
41-
$this->path = $path;
42-
$this->section = $section;
43-
$this->useAggregator = $useAggregator;
55+
$this->path = $path;
56+
$this->section = $section;
57+
$this->useAggregator = $useAggregator;
4458
}
4559

4660
public function process()
@@ -53,21 +67,31 @@ public function process()
5367
? $this->getSection($this->section)
5468
: $this->mergeSections();
5569

70+
$this->getVaultVariables();
71+
72+
if (empty($this->vaultVariables)) {
73+
return $this->data;
74+
}
75+
76+
$this->setVaultRepo();
77+
78+
$this->replaceVaultVariablesWithData($this->data, $this->getValuesFromVault());
79+
5680
return $this->data;
5781
}
5882

5983
private function getSection($name)
6084
{
6185
$this->processSections();
6286

63-
if(!array_key_exists($name, $this->sections)) {
87+
if (!array_key_exists($name, $this->sections)) {
6488
throw new \Exception("Section '{$name}' missing");
6589
}
6690

6791
$extends = false;
6892
$parentData = array();
6993

70-
if($this->sections[$name] !== null) {
94+
if ($this->sections[$name] !== null) {
7195
$extends = true;
7296
$func = __FUNCTION__;
7397
$parentData = $this->$func($this->sections[$name]);
@@ -96,7 +120,7 @@ private function getSectionName($item)
96120
private function mergeSections()
97121
{
98122
$tmpData = [];
99-
foreach($this->data as $sectionName => $data){
123+
foreach ($this->data as $sectionName => $data) {
100124
$name = $this->getSectionName($sectionName);
101125
$tmpData[$name] = $this->getSection($name);
102126
}
@@ -107,8 +131,9 @@ private function processSections()
107131
{
108132
$tmp = array_keys($this->data);
109133

110-
foreach($tmp as $item) {
111-
if(substr_count($item, ":") > 1) { }
134+
foreach ($tmp as $item) {
135+
if (substr_count($item, ":") > 1) {
136+
}
112137

113138
$segments = explode(':', $item);
114139

@@ -136,11 +161,91 @@ private function readFromFile()
136161
{
137162
$path = realpath($this->path);
138163

139-
if(false === $path || !is_readable($path)) {
164+
if (false === $path || !is_readable($path)) {
140165
throw new \Exception('Configuration file is not readable');
141166
}
142167

143168
$reader = new Reader();
144169
$this->data = $reader->fromFile($path);
145170
}
171+
172+
private function getVaultVariables()
173+
{
174+
$this->vaultVariables = $this->filterMultiArray($this->data);
175+
}
176+
177+
public function filterMultiArray(array $data)
178+
{
179+
$filteredArray = [];
180+
foreach ($data as $value) {
181+
if (is_array($value)) {
182+
$filteredSubArray = $this->filterMultiArray($value);
183+
$filteredArray = array_merge($filteredArray, $filteredSubArray);
184+
} elseif (substr($value, 0, strlen(self::VAULT_KEYWORD)) === self::VAULT_KEYWORD) {
185+
$filteredArray[] = $value;
186+
}
187+
}
188+
189+
return $filteredArray;
190+
}
191+
192+
private function sortVariablesByRouteAndKey()
193+
{
194+
//todo maybe remove strtolower when variables are stored in apper case in vault.
195+
$sorted = [];
196+
foreach ($this->vaultVariables as $vaultVariable) {
197+
$array = explode('/', strtolower($vaultVariable));
198+
$secretKey = array_pop($array);
199+
array_shift($array);
200+
$secretRoute = implode('/', $array);
201+
$sorted[$secretRoute][] = $secretKey;
202+
}
203+
204+
return $sorted;
205+
}
206+
207+
private function getValuesFromVault()
208+
{
209+
$sorted = $this->sortVariablesByRouteAndKey();
210+
211+
$data = [];
212+
213+
foreach ($sorted as $section => $secretKeys) {
214+
$valuesForSection = $this->vaultRepository->getValueBySection($section);
215+
foreach ($secretKeys as $secretKey) {
216+
$data[self::VAULT_KEYWORD . '/' . $section . '/' . $secretKey] = $valuesForSection[$secretKey] ?? '';
217+
}
218+
}
219+
220+
return $data;
221+
}
222+
223+
public function replaceVaultVariablesWithData(&$array, $searchArray)
224+
{
225+
foreach ($array as &$value) {
226+
if (is_array($value)) {
227+
$this->replaceVaultVariablesWithData($value, $searchArray);
228+
} else {
229+
if (array_key_exists($value, $searchArray)) {
230+
$value = $searchArray[$value];
231+
}
232+
}
233+
}
234+
}
235+
236+
private function setVaultRepo()
237+
{
238+
$message = 'No config found for Vault %s ';
239+
240+
if(!isset($this->data[self::VAULT_KEYWORD][self::VAULT_URL_KEYWORD])){
241+
throw new \RuntimeException(sprintf($message, self::VAULT_URL_KEYWORD));
242+
}
243+
if(!isset($this->data[self::VAULT_KEYWORD][self::VAULT_API_TOKEN_KEYWORD])){
244+
throw new \RuntimeException(sprintf($message, self::VAULT_API_TOKEN_KEYWORD));
245+
}
246+
247+
$this->vaultRepository = new VaultRepository(
248+
$this->data[self::VAULT_KEYWORD][self::VAULT_URL_KEYWORD],
249+
$this->data[self::VAULT_KEYWORD][self::VAULT_API_TOKEN_KEYWORD]);
250+
}
146251
}

src/VaultRepository.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
namespace G4\Config;
4+
5+
class VaultRepository
6+
{
7+
/**
8+
* @var mixed
9+
*/
10+
private $vaultUrl;
11+
/**
12+
* @var mixed
13+
*/
14+
private $vaultApiToken;
15+
16+
/**
17+
* @param string $vaultUrl
18+
* @param string $vaultApiToken
19+
*/
20+
public function __construct(string $vaultUrl, string $vaultApiToken)
21+
{
22+
$this->vaultUrl = $vaultUrl;
23+
$this->vaultApiToken = $vaultApiToken;
24+
}
25+
26+
public function getValueBySection($section)
27+
{
28+
$ch = curl_init();
29+
curl_setopt($ch, CURLOPT_URL, $this->vaultUrl . '/v1/' . $section);
30+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
31+
curl_setopt($ch, CURLOPT_HTTPHEADER, [
32+
"X-Vault-Token: " . $this->vaultApiToken,
33+
"X-Vault-Namespace: " . $this->vaultUrl
34+
]);
35+
36+
$response = curl_exec($ch);
37+
38+
if (curl_errno($ch)) {
39+
echo 'Error:' . curl_error($ch);
40+
} else {
41+
$json = json_decode($response, true);
42+
$param = $json['data'];
43+
}
44+
45+
curl_close($ch);
46+
47+
return $param;
48+
}
49+
}

0 commit comments

Comments
 (0)