Skip to content

Commit c3f67ea

Browse files
committed
MQE-1600: MQE-1600: MFTF Vault integration
- prefer credentials from local file over vault - address review comments
1 parent 3889f9a commit c3f67ea

File tree

5 files changed

+96
-94
lines changed

5 files changed

+96
-94
lines changed

src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/CredentialStore.php

Lines changed: 50 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -12,31 +12,35 @@
1212

1313
class CredentialStore
1414
{
15+
const ARRAY_KEY_FOR_VAULT = 'vault';
16+
const ARRAY_KEY_FOR_FILE = 'file';
17+
1518
/**
16-
* Singleton instance
19+
* Numeric indexed array that defines the access precedence of credential storage
1720
*
18-
* @var CredentialStore
21+
* @var array
1922
*/
20-
private static $INSTANCE = null;
23+
private static $credStoragePrecedence = [self::ARRAY_KEY_FOR_FILE, self::ARRAY_KEY_FOR_VAULT];
2124

2225
/**
23-
* File storage for credentials
26+
* Credential storage array
2427
*
25-
* @var FileStorage
28+
* @var array
2629
*/
27-
private $credFile = null;
30+
private $credStorage = [];
2831

2932
/**
30-
* Vault storage for credentials
33+
* Singleton instance
3134
*
32-
* @var VaultStorage
35+
* @var CredentialStore
3336
*/
34-
private $credVault = null;
37+
private static $INSTANCE = null;
3538

3639
/**
3740
* Static singleton getter for CredentialStore Instance
3841
*
3942
* @return CredentialStore
43+
* @throws TestFrameworkException
4044
*/
4145
public static function getInstance()
4246
{
@@ -48,7 +52,9 @@ public static function getInstance()
4852
}
4953

5054
/**
51-
* CredentialStore constructor.
55+
* CredentialStore constructor
56+
*
57+
* @throws TestFrameworkException
5258
*/
5359
private function __construct()
5460
{
@@ -57,16 +63,28 @@ private function __construct()
5763
$csToken = getenv('CREDENTIAL_VAULT_TOKEN');
5864
if ($csBaseUrl !== false && $csToken !== false) {
5965
try {
60-
$this->credVault = new VaultStorage(rtrim($csBaseUrl, '/'), $csToken);
66+
$this->credStorage[self::ARRAY_KEY_FOR_VAULT] = new VaultStorage(
67+
rtrim($csBaseUrl, '/'),
68+
$csToken
69+
);
6170
} catch (TestFrameworkException $e) {
6271
}
6372
}
6473

6574
// Initialize file storage
6675
try {
67-
$this->credFile = new FileStorage();
76+
$this->credStorage[self::ARRAY_KEY_FOR_FILE] = new FileStorage();
6877
} catch (TestFrameworkException $e) {
6978
}
79+
80+
foreach ($this->credStorage as $cred) {
81+
if (null !== $cred) {
82+
return;
83+
}
84+
}
85+
throw new TestFrameworkException(
86+
"No credential storage is properly configured. Please configure vault or .credentials file."
87+
);
7088
}
7189

7290
/**
@@ -78,24 +96,20 @@ private function __construct()
7896
*/
7997
public function getSecret($key)
8098
{
81-
// Get secret data from vault storage first
82-
if (null !== $this->credVault) {
83-
$value = $this->credVault->getEncryptedValue($key);
84-
if (!empty($value)) {
85-
return $value;
86-
}
87-
}
88-
89-
// Get secret data from file when not found in vault
90-
if (null !== $this->credFile) {
91-
$value = $this->credFile->getEncryptedValue($key);
92-
if (!empty($value)) {
93-
return $value;
99+
// Get secret data from storage according to defined precedence
100+
// File storage is preferred over vault storage to allow local secret value overriding remote secret value
101+
foreach (self::$credStoragePrecedence as $credType) {
102+
if (null !== $this->credStorage[$credType]) {
103+
$value = $this->credStorage[$credType]->getEncryptedValue($key);
104+
if (null !== $value) {
105+
return $value;
106+
}
94107
}
95108
}
96109

97110
throw new TestFrameworkException(
98-
"value for key \"$key\" not found in credential storage."
111+
"{$key} not defined in vault or .credentials file, "
112+
. "please provide a value in order to use this secret in a test.\"."
99113
);
100114
}
101115

@@ -107,12 +121,11 @@ public function getSecret($key)
107121
*/
108122
public function decryptSecretValue($value)
109123
{
110-
if (null !== $this->credVault) {
111-
return $this->credVault->getDecryptedValue($value);
112-
}
113-
114-
if (null !== $this->credFile) {
115-
return $this->credFile->getDecryptedValue($value);
124+
// Loop through storage to decrypt value
125+
foreach (self::$credStoragePrecedence as $credType) {
126+
if (null !== $this->credStorage[$credType]) {
127+
return $this->credStorage[$credType]->getDecryptedValue($value);
128+
}
116129
}
117130
}
118131

@@ -124,12 +137,11 @@ public function decryptSecretValue($value)
124137
*/
125138
public function decryptAllSecretsInString($string)
126139
{
127-
if (null !== $this->credVault) {
128-
return $this->credVault->getAllDecryptedValues($string);
129-
}
130-
131-
if (null !== $this->credFile) {
132-
return $this->credFile->getAllDecryptedValues($string);
140+
// Loop through storage to decrypt all occurrences from input string
141+
foreach (self::$credStoragePrecedence as $credType) {
142+
if (null !== $this->credStorage[$credType]) {
143+
return $this->credStorage[$credType]->getAllDecryptedValues($string);
144+
}
133145
}
134146
}
135147
}

src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/FileStorage.php

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66

77
namespace Magento\FunctionalTestingFramework\DataGenerator\Handlers\SecretStorage;
88

9-
use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig;
109
use Magento\FunctionalTestingFramework\Console\BuildProjectCommand;
1110
use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException;
11+
use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig;
1212
use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil;
1313

1414
class FileStorage extends BaseStorage
@@ -22,7 +22,6 @@ class FileStorage extends BaseStorage
2222

2323
/**
2424
* FileStorage constructor
25-
*
2625
* @throws TestFrameworkException
2726
*/
2827
public function __construct()
@@ -40,27 +39,24 @@ public function __construct()
4039
*/
4140
public function getEncryptedValue($key)
4241
{
42+
$value = null;
4343
// Check if secret is in cached array
4444
if (null !== ($value = parent::getEncryptedValue($key))) {
4545
return $value;
4646
}
4747

48-
try {
49-
// log here for verbose config
50-
if (MftfApplicationConfig::getConfig()->verboseEnabled()) {
51-
LoggingUtil::getInstance()->getLogger(FileStorage::class)->debug(
52-
"retrieving secret for key name {$key} from file"
53-
);
54-
}
55-
} catch (\Exception $e) {
48+
// log here for verbose config
49+
if (MftfApplicationConfig::getConfig()->verboseEnabled()) {
50+
LoggingUtil::getInstance()->getLogger(FileStorage::class)->debug(
51+
"retrieving secret for key name {$key} from file"
52+
);
5653
}
5754

5855
// Retrieve from file storage
59-
if (!array_key_exists($key, $this->secretData) || empty($value = $this->secretData[$key])) {
60-
return null;
56+
if (array_key_exists($key, $this->secretData) && (null !== ($value = $this->secretData[$key]))) {
57+
parent::$cachedSecretData[$key] = $value;
6158
}
6259

63-
parent::$cachedSecretData[$key] = $value;
6460
return $value;
6561
}
6662

@@ -80,8 +76,7 @@ private function readInCredentialsFile()
8076

8177
if (!file_exists($credsFilePath)) {
8278
throw new TestFrameworkException(
83-
"Cannot find .credentials file, please create in "
84-
. TESTS_BP . " in order to reference sensitive information"
79+
"Credential file is not used: .credentials file not found in " . TESTS_BP
8580
);
8681
}
8782

src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/VaultStorage.php

Lines changed: 32 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,14 @@ class VaultStorage extends BaseStorage
2020
* Adobe Vault
2121
*/
2222
const BASE_PATH = '/dx_magento_qe';
23-
const KV_DATA = '/data';
24-
/**
25-
* Local Vault
26-
*/
27-
//const BASE_PATH = '/secret';
28-
//const KV_DATA = '/data';
23+
const KV_DATA = 'data';
2924

3025
/**
3126
* Vault client
3227
*
3328
* @var Client
3429
*/
35-
private static $client = null;
30+
private $client = null;
3631

3732
/**
3833
* Vault token
@@ -51,13 +46,13 @@ class VaultStorage extends BaseStorage
5146
public function __construct($baseUrl, $token)
5247
{
5348
parent::__construct();
54-
if (null === self::$client) {
49+
if (null === $this->client) {
5550
// Creating the client using Guzzle6 Transport and passing a custom url
56-
self::$client = new Client(new Guzzle6Transport(['base_uri' => $baseUrl]));
51+
$this->client = new Client(new Guzzle6Transport(['base_uri' => $baseUrl]));
5752
}
5853
$this->token = $token;
5954
if (!$this->authenticated()) {
60-
throw new TestFrameworkException("Credential Vault: Cannot Authenticate");
55+
throw new TestFrameworkException("Credential vault is not used: cannot authenticate");
6156
}
6257
}
6358

@@ -74,50 +69,48 @@ public function getEncryptedValue($key)
7469
return $value;
7570
}
7671

72+
if (MftfApplicationConfig::getConfig()->verboseEnabled()) {
73+
LoggingUtil::getInstance()->getLogger(VaultStorage::class)->debug(
74+
"Retrieving secret for key name {$key} from vault"
75+
);
76+
}
77+
78+
$reValue = null;
7779
try {
78-
// Log here for verbose config
80+
// Split vendor/key to construct secret path
81+
list($vendor, $key) = explode('/', trim($key, '/'), 2);
82+
$url = self::BASE_PATH
83+
. (empty(self::KV_DATA) ? '' : '/' . self::KV_DATA)
84+
. self::MFTF_PATH
85+
. '/'
86+
. $vendor
87+
. '/'
88+
. $key;
89+
// Read value by key from vault
90+
$value = $this->client->read($url)->getData()[self::KV_DATA][$key];
91+
// Encrypt value for return
92+
$reValue = openssl_encrypt($value, parent::ENCRYPTION_ALGO, parent::$encodedKey, 0, parent::$iv);
93+
parent::$cachedSecretData[$key] = $reValue;
94+
} catch (\Exception $e) {
7995
if (MftfApplicationConfig::getConfig()->verboseEnabled()) {
8096
LoggingUtil::getInstance()->getLogger(VaultStorage::class)->debug(
81-
"retrieving secret for key name {$key} from vault"
97+
"Unable to read secret for key name {$key} from vault"
8298
);
8399
}
84-
} catch (\Exception $e) {
85-
}
86-
87-
// Retrieve from vault storage
88-
if (!$this->authenticated()) {
89-
return null;
90-
}
91-
92-
// Read value by key from vault
93-
list($vendor, $key) = explode('/', trim($key, '/'), 2);
94-
$url = self::BASE_PATH
95-
. (empty(self::KV_DATA) ? '' : self::KV_DATA)
96-
. self::MFTF_PATH
97-
. '/'
98-
. $vendor
99-
. '/'
100-
. $key;
101-
$value = self::$client->read($url)->getData()['data'][$key];
102-
103-
if (empty($value)) {
104-
return null;
105100
}
106-
$eValue = openssl_encrypt($value, parent::ENCRYPTION_ALGO, parent::$encodedKey, 0, parent::$iv);
107-
parent::$cachedSecretData[$key] = $eValue;
108-
return $eValue;
101+
return $reValue;
109102
}
110103

111104
/**
112-
* Check if vault token is still valid.
105+
* Check if vault token is valid
113106
*
114107
* @return boolean
115108
*/
116109
private function authenticated()
117110
{
118111
try {
119-
// Authenticating using token auth backend.
120-
$authenticated = self::$client
112+
// Authenticating using token auth backend
113+
$authenticated = $this->client
121114
->setAuthenticationStrategy(new TokenAuthenticationStrategy($this->token))
122115
->authenticate();
123116

src/Magento/FunctionalTestingFramework/Test/Util/ActionMergeUtil.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class ActionMergeUtil
3131
const DEFAULT_WAIT_ORDER = 'after';
3232
const APPROVED_ACTIONS = ['fillField', 'magentoCLI', 'field'];
3333
const SECRET_MAPPING = ['fillField' => 'fillSecretField', 'magentoCLI' => 'magentoCLISecret'];
34+
const CREDS_REGEX = "/{{_CREDS\.([\w|\/]+)}}/";
3435

3536
/**
3637
* Array holding final resulting steps
@@ -149,7 +150,7 @@ private function actionAttributeContainsSecretRef($actionAttributes)
149150
return $this->actionAttributeContainsSecretRef($actionAttribute);
150151
}
151152

152-
preg_match_all("/{{_CREDS\.([\w|\/]+)}}/", $actionAttribute, $matches);
153+
preg_match_all(self::CREDS_REGEX, $actionAttribute, $matches);
153154

154155
if (!empty($matches[0])) {
155156
return true;

src/Magento/FunctionalTestingFramework/Util/TestGenerator.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use Magento\FunctionalTestingFramework\Test\Util\ActionObjectExtractor;
2525
use Magento\FunctionalTestingFramework\Test\Util\TestObjectExtractor;
2626
use Magento\FunctionalTestingFramework\Util\Filesystem\DirSetupUtil;
27+
use Magento\FunctionalTestingFramework\Test\Util\ActionMergeUtil;
2728

2829
/**
2930
* Class TestGenerator
@@ -1901,7 +1902,7 @@ private function resolveAllRuntimeReferences($args)
19011902
{
19021903
$runtimeReferenceRegex = [
19031904
"/{{_ENV\.([\w]+)}}/" => 'getenv',
1904-
"/{{_CREDS\.([\w|\/]+)}}/" => 'CredentialStore::getInstance()->getSecret'
1905+
ActionMergeUtil::CREDS_REGEX => 'CredentialStore::getInstance()->getSecret'
19051906
];
19061907

19071908
$argResult = $args;

0 commit comments

Comments
 (0)