diff --git a/README.md b/README.md index 761dd49..cd29bbb 100644 --- a/README.md +++ b/README.md @@ -48,16 +48,18 @@ You may alternatively define values yourself: ```php $config = new \DCarbone\PHPConsulAPI\Config([ - 'HttpClient' => $client // REQUIRED Client conforming to GuzzleHttp\ClientInterface - - 'Address' => 'address of server', // REQUIRED - 'Scheme' => 'http or https', // REQUIRED - 'Datacenter' => 'name of datacenter', // OPTIONAL - 'HttpAuth' => 'user:pass', // OPTIONAL, - 'WaitTime' => 30, // OPTIONAL, not used yet - 'Token' => 'auth token', // OPTIONAL - 'TokenInHeader' => false // OPTIONAL - 'InsecureSkipVerify' => false, // OPTIONAL + 'HttpClient' => $client // [required] Client conforming to GuzzleHttp\ClientInterface + 'Address' => 'address of server', // [required] + + 'Scheme' => 'http or https', // [optional] defaults to "http" + 'Datacenter' => 'name of datacenter', // [optional] + 'HttpAuth' => 'user:pass', // [optional] + 'Token' => 'auth token', // [optional] default auth token to use + 'TokenInHeader' => false, // [optional] specifies whether to send the token in the header or query string + 'InsecureSkipVerify' => false, // [optional] if set to true, ignores all SSL validation + 'CAFile' => '', // [optional] path to ca cert file, see http://docs.guzzlephp.org/en/latest/request-options.html#verify + 'CertFile' => '', // [optional] path to client pem. if set, requires KeyFile also be set + 'KeyFile' => '', // [optional] path to client ]); ``` diff --git a/src/AbstractClient.php b/src/AbstractClient.php index 0551902..35b19e5 100644 --- a/src/AbstractClient.php +++ b/src/AbstractClient.php @@ -101,11 +101,7 @@ protected function doRequest(Request $r) { try { // If we actually have a client defined... if (isset($this->c->HttpClient) && $this->c->HttpClient instanceof ClientInterface) { - $response = $this->c->HttpClient->send($r->toPsrRequest(), [ - 'http_errors' => false, - 'verify' => $this->c->isInsecureSkipVerify(), - 'decode_content' => false, - ]); + $response = $this->c->HttpClient->send($r->toPsrRequest(), $this->c->getGuzzleRequestOptions()); } // Otherwise, throw error to be caught below else { throw new \RuntimeException('Unable to execute query as no HttpClient has been defined.'); diff --git a/src/Config.php b/src/Config.php index 9c544e4..6c73702 100644 --- a/src/Config.php +++ b/src/Config.php @@ -17,6 +17,7 @@ */ use GuzzleHttp\Client; use GuzzleHttp\ClientInterface; +use GuzzleHttp\RequestOptions; /** * Class Config @@ -65,6 +66,27 @@ class Config { */ public $Token = ''; + /** + * Optional path to CA certificate + * + * @var string + */ + public $CAFile = ''; + + /** + * Optional path to certificate. If set, KeyFile must also be set + * + * @var string + */ + public $CertFile = ''; + + /** + * Optional path to private key. If set, CertFile must also be set + * + * @var string + */ + public $KeyFile = ''; + /** * Whether to skip SSL validation. This does nothing unless you use it within your HttpClient of choice. * @@ -91,13 +113,30 @@ class Config { * @param array $config */ public function __construct(array $config = []) { - foreach ($config as $k => $v) { + foreach ($config + self::getDefaultConfig() as $k => $v) { $this->{"set{$k}"}($v); } if (null !== $this->HttpAuth && !isset($this->HttpAuth)) { $this->HttpAuth = new HttpAuth(); } + + // quick validation on key/cert combo + $c = $this->getCertFile(); + $k = $this->getKeyFile(); + if (('' !== $k && '' === $c) || ('' !== $c && '' === $k)) { + throw new \InvalidArgumentException(sprintf( + '%s - CertFile and KeyFile must be both either empty or populated. Key: %s; Cert: %s', + get_class($this), + $k, + $c + )); + } + + // if client hasn't been constructed, construct. + if (null === $this->HttpClient) { + $this->HttpClient = new Client(); + } } /** @@ -106,34 +145,52 @@ public function __construct(array $config = []) { * @return \DCarbone\PHPConsulAPI\Config */ public static function newDefaultConfig() { - $conf = new static([ + return new static(self::getDefaultConfig()); + } + + /** + * @return array + */ + private static function getDefaultConfig() { + $conf = [ 'Address' => '127.0.0.1:8500', 'Scheme' => 'http', - ]); - - $envParams = static::getEnvironmentConfig(); - if (isset($envParams[Consul::HTTPAddrEnvName])) { - $conf->setAddress($envParams[Consul::HTTPAddrEnvName]); - } - - if (isset($envParams[Consul::HTTPTokenEnvName])) { - $conf->setToken($envParams[Consul::HTTPTokenEnvName]); - } - - if (isset($envParams[Consul::HTTPAuthEnvName])) { - $conf->setHttpAuth($envParams[Consul::HTTPAuthEnvName]); - } - - if (isset($envParams[Consul::HTTPSSLEnvName]) && $envParams[Consul::HTTPSSLEnvName]) { - $conf->setScheme('https'); - } - - if (isset($envParams[Consul::HTTPSSLVerifyEnvName]) && !$envParams[Consul::HTTPSSLVerifyEnvName]) { - $conf->setInsecureSkipVerify(false); + ]; + + // parse env vars + foreach (static::getEnvironmentConfig() as $k => $v) { + switch ($k) { + case Consul::HTTPAddrEnvName: + $conf['Address'] = $v; + break; + case Consul::HTTPTokenEnvName: + $conf['Token'] = $v; + break; + case Consul::HTTPAuthEnvName: + $conf['HttpAuth'] = $v; + break; + case Consul::HTTPCAFileEnvName: + $conf['CAFile'] = $v; + break; + case Consul::HTTPClientCertEnvName: + $conf['CertFile'] = $v; + break; + case Consul::HTTPClientKeyEnvName: + $conf['KeyFile'] = $v; + break; + case Consul::HTTPSSLEnvName: + if ((bool)$v) { + $conf['Scheme'] = 'https'; + } + break; + case Consul::HTTPSSLVerifyEnvName: + if ((bool)$v) { + $conf['InsecureSkipVerify'] = true; + } + break; + } } - $conf->setHttpClient(new Client()); - return $conf; } @@ -269,6 +326,54 @@ public function setHttpAuth($HttpAuth) { )); } + /** + * @return string + */ + public function getCAFile() { + return $this->CAFile; + } + + /** + * @param string $CAFile + * @return \DCarbone\PHPConsulAPI\Config + */ + public function setCAFile($CAFile) { + $this->CAFile = $CAFile; + return $this; + } + + /** + * @return string + */ + public function getCertFile() { + return $this->CertFile; + } + + /** + * @param string $CertFile + * @return \DCarbone\PHPConsulAPI\Config + */ + public function setCertFile($CertFile) { + $this->CertFile = $CertFile; + return $this; + } + + /** + * @return string + */ + public function getKeyFile() { + return $this->KeyFile; + } + + /** + * @param string $KeyFile + * @return \DCarbone\PHPConsulAPI\Config + */ + public function setKeyFile($KeyFile) { + $this->KeyFile = $KeyFile; + return $this; + } + /** * @return \GuzzleHttp\ClientInterface */ @@ -319,15 +424,42 @@ public function intToMillisecond($in) { return sprintf('%dms', $ms); } + /** + * @return array + */ + public function getGuzzleRequestOptions() { + // TODO: Define once? + $opts = [ + RequestOptions::HTTP_ERRORS => false, + RequestOptions::DECODE_CONTENT => false, + ]; + + if (!$this->isInsecureSkipVerify()) { + $opts[RequestOptions::VERIFY] = false; + } else if ('' !== ($b = $this->getCAFile())) { + $opts[RequestOptions::VERIFY] = $b; + } + + if ('' !== ($c = $this->getCertFile())) { + $opts[RequestOptions::CERT] = $c; + $opts[RequestOptions::SSL_KEY] = $this->getKeyFile(); + } + + return $opts; + } + /** * @return array */ public static function getEnvironmentConfig() { return array_filter([ - 'CONSUL_HTTP_ADDR' => static::_tryGetEnvParam('CONSUL_HTTP_ADDR'), - 'CONSUL_HTTP_AUTH' => static::_tryGetEnvParam('CONSUL_HTTP_AUTH'), - 'CONSUL_HTTP_SSL' => static::_tryGetEnvParam('CONSUL_HTTP_SSL'), - 'CONSUL_HTTP_SSL_VERIFY' => static::_tryGetEnvParam('CONSUL_HTTP_SSL_VERIFY') + Consul::HTTPAddrEnvName => static::_tryGetEnvParam(Consul::HTTPAddrEnvName), + Consul::HTTPAuthEnvName => static::_tryGetEnvParam(Consul::HTTPAuthEnvName), + Consul::HTTPCAFileEnvName => static::_tryGetEnvParam(Consul::HTTPCAFileEnvName), + Consul::HTTPClientCertEnvName => static::_tryGetEnvParam(Consul::HTTPClientCertEnvName), + Consul::HTTPClientKeyEnvName => static::_tryGetEnvParam(Consul::HTTPClientKeyEnvName), + Consul::HTTPSSLEnvName => static::_tryGetEnvParam(Consul::HTTPSSLEnvName), + Consul::HTTPSSLVerifyEnvName => static::_tryGetEnvParam(Consul::HTTPSSLVerifyEnvName), ], function ($val) { return null !== $val; @@ -339,6 +471,10 @@ function ($val) { * @return string|null */ protected static function _tryGetEnvParam($param) { + if (isset($_ENV[$param])) { + return $_ENV[$param]; + } + if (false !== ($value = getenv($param))) { return $value; } diff --git a/src/Consul.php b/src/Consul.php index b3b7890..4a3e470 100644 --- a/src/Consul.php +++ b/src/Consul.php @@ -36,6 +36,9 @@ class Consul { const HTTPAddrEnvName = 'CONSUL_HTTP_ADDR'; const HTTPTokenEnvName = 'CONSUL_HTTP_TOKEN'; const HTTPAuthEnvName = 'CONSUL_HTTP_AUTH'; + const HTTPCAFileEnvName = "CONSUL_CACERT"; + const HTTPClientCertEnvName = "CONSUL_CLIENT_CERT"; + const HTTPClientKeyEnvName = "CONSUL_CLIENT_KEY"; const HTTPSSLEnvName = 'CONSUL_HTTP_SSL'; const HTTPSSLVerifyEnvName = 'CONSUL_HTTP_SSL_VERIFY'; diff --git a/tests/Definition/ConfigDefinitionTest.php b/tests/Definition/ConfigDefinitionTest.php index b4b520f..f69c42c 100644 --- a/tests/Definition/ConfigDefinitionTest.php +++ b/tests/Definition/ConfigDefinitionTest.php @@ -27,8 +27,14 @@ class ConfigDefinitionTest extends AbstractDefinitionTestCases /** * @inheritDoc */ - protected function getSubjectClassName() - { + protected function getSubjectClassName() { return Config::class; } + + /** + * @return object + */ + protected function getEmptyInstance() { + return $this->createMock($this->getSubjectClassName()); + } }