Skip to content

MQE-1600: MFTF Vault integration #382

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

Merged
merged 8 commits into from
Jul 24, 2019
Merged
Show file tree
Hide file tree
Changes from 4 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
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
language: php
php:
- 7.0
- 7.1
- 7.2
- 7.3
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"allure-framework/allure-codeception": "~1.3.0",
"codeception/codeception": "~2.3.4 || ~2.4.0 ",
"consolidation/robo": "^1.0.0",
"csharpru/vault-php": "^3.6",
"csharpru/vault-php": "~3.5.3",
"csharpru/vault-php-guzzle6-transport": "^2.0",
"flow/jsonpath": ">0.2",
"fzaninotto/faker": "^1.6",
Expand Down
34 changes: 17 additions & 17 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -12,31 +12,35 @@

class CredentialStore
{
const ARRAY_KEY_FOR_VAULT = 'vault';
const ARRAY_KEY_FOR_FILE = 'file';

/**
* Singleton instance
* Numeric indexed array that defines the access precedence of credential storage
*
* @var CredentialStore
* @var array
*/
private static $INSTANCE = null;
private static $credStoragePrecedence = [self::ARRAY_KEY_FOR_FILE, self::ARRAY_KEY_FOR_VAULT];

/**
* File storage for credentials
* Credential storage array
*
* @var FileStorage
* @var array
*/
private $credFile = null;
private $credStorage = [];

/**
* Vault storage for credentials
* Singleton instance
*
* @var VaultStorage
* @var CredentialStore
*/
private $credVault = null;
private static $INSTANCE = null;

/**
* Static singleton getter for CredentialStore Instance
*
* @return CredentialStore
* @throws TestFrameworkException
*/
public static function getInstance()
{
Expand All @@ -48,7 +52,9 @@ public static function getInstance()
}

/**
* CredentialStore constructor.
* CredentialStore constructor
*
* @throws TestFrameworkException
*/
private function __construct()
{
Expand All @@ -57,16 +63,28 @@ private function __construct()
$csToken = getenv('CREDENTIAL_VAULT_TOKEN');
if ($csBaseUrl !== false && $csToken !== false) {
try {
$this->credVault = new VaultStorage(rtrim($csBaseUrl, '/'), $csToken);
$this->credStorage[self::ARRAY_KEY_FOR_VAULT] = new VaultStorage(
rtrim($csBaseUrl, '/'),
$csToken
);
} catch (TestFrameworkException $e) {
}
}

// Initialize file storage
try {
$this->credFile = new FileStorage();
$this->credStorage[self::ARRAY_KEY_FOR_FILE] = new FileStorage();
} catch (TestFrameworkException $e) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Log the exception so that we can see that FileStorage failed to initialize: <message>. Do the same for Vault.

That way, if it fails in a build or something we can refer to the logs to see what did and didn't initialize properly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the exceptions are already logged in FileStorage and VaultStorage.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, but human readable statements will make the log more useful. Instead of just seeing a failure message, you see that the credential store is not using a specific method.

}

foreach ($this->credStorage as $cred) {
if (null !== $cred) {
return;
}
}
throw new TestFrameworkException(
"No credential storage is properly configured. Please configure vault or .credentials file."
);
}

/**
Expand All @@ -78,24 +96,20 @@ private function __construct()
*/
public function getSecret($key)
{
// Get secret data from vault storage first
if (null !== $this->credVault) {
$value = $this->credVault->getEncryptedValue($key);
if (!empty($value)) {
return $value;
}
}

// Get secret data from file when not found in vault
if (null !== $this->credFile) {
$value = $this->credFile->getEncryptedValue($key);
if (!empty($value)) {
return $value;
// Get secret data from storage according to defined precedence
// File storage is preferred over vault storage to allow local secret value overriding remote secret value
foreach (self::$credStoragePrecedence as $credType) {
if (null !== $this->credStorage[$credType]) {
$value = $this->credStorage[$credType]->getEncryptedValue($key);
if (null !== $value) {
return $value;
}
}
}

throw new TestFrameworkException(
"value for key \"$key\" not found in credential storage."
"\"{$key}\" not defined in vault or .credentials file, "
. "please provide a value in order to use this secret in a test."
);
}

Expand All @@ -107,12 +121,11 @@ public function getSecret($key)
*/
public function decryptSecretValue($value)
{
if (null !== $this->credVault) {
return $this->credVault->getDecryptedValue($value);
}

if (null !== $this->credFile) {
return $this->credFile->getDecryptedValue($value);
// Loop through storage to decrypt value
foreach (self::$credStoragePrecedence as $credType) {
if (null !== $this->credStorage[$credType]) {
return $this->credStorage[$credType]->getDecryptedValue($value);
}
}
}

Expand All @@ -124,12 +137,11 @@ public function decryptSecretValue($value)
*/
public function decryptAllSecretsInString($string)
{
if (null !== $this->credVault) {
return $this->credVault->getAllDecryptedValues($string);
}

if (null !== $this->credFile) {
return $this->credFile->getAllDecryptedValues($string);
// Loop through storage to decrypt all occurrences from input string
foreach (self::$credStoragePrecedence as $credType) {
if (null !== $this->credStorage[$credType]) {
return $this->credStorage[$credType]->getAllDecryptedValuesInString($string);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public function getDecryptedValue($value)
* @param string $string
* @return mixed
*/
public function getAllDecryptedValues($string)
public function getAllDecryptedValuesInString($string)
{
$newString = $string;
foreach (self::$cachedSecretData as $key => $secretValue) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@

namespace Magento\FunctionalTestingFramework\DataGenerator\Handlers\SecretStorage;

use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig;
use Magento\FunctionalTestingFramework\Console\BuildProjectCommand;
use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException;
use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig;
use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil;

class FileStorage extends BaseStorage
Expand All @@ -22,7 +22,6 @@ class FileStorage extends BaseStorage

/**
* FileStorage constructor
*
* @throws TestFrameworkException
*/
public function __construct()
Expand All @@ -40,27 +39,24 @@ public function __construct()
*/
public function getEncryptedValue($key)
{
$value = null;
// Check if secret is in cached array
if (null !== ($value = parent::getEncryptedValue($key))) {
return $value;
}

try {
// log here for verbose config
if (MftfApplicationConfig::getConfig()->verboseEnabled()) {
LoggingUtil::getInstance()->getLogger(FileStorage::class)->debug(
"retrieving secret for key name {$key} from file"
);
}
} catch (\Exception $e) {
// log here for verbose config
if (MftfApplicationConfig::getConfig()->verboseEnabled()) {
LoggingUtil::getInstance()->getLogger(FileStorage::class)->debug(
"retrieving secret for key name {$key} from file"
);
}

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

parent::$cachedSecretData[$key] = $value;
return $value;
}

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

if (!file_exists($credsFilePath)) {
throw new TestFrameworkException(
"Cannot find .credentials file, please create in "
. TESTS_BP . " in order to reference sensitive information"
"Credential file is not used: .credentials file not found in " . TESTS_BP
);
}

Expand Down
Loading