Enable Basic and https certificate authentication for http publisher

Change-Id: I3735db7a37eff9e822e5180349eb8f86002fd0ef
This commit is contained in:
Darren Hague 2016-11-09 18:47:24 +00:00
parent d3caa24565
commit 191748a403
4 changed files with 87 additions and 9 deletions

View File

@ -35,9 +35,20 @@ class HttpPublisher(publisher.ConfigPublisherBase):
`timeout` and `max_retries` will be set to 5 and 2 respectively. Additional
parameters are:
- ssl can be enabled by setting `verify_ssl`
- ssl certificate verification can be disabled by setting `verify_ssl`
to False
- batching can be configured by `batch`
- connection pool size configured using `poolsize`
- Basic authentication can be configured using the URL authentication
scheme: http://username:password@example.com
- For certificate authentication, `clientcert` and `clientkey` are the
paths to the certificate and key files respectively. `clientkey` is
only required if the clientcert file doesn't already contain the key.
All of the parameters mentioned above get removed during processing,
with the remaining portion of the URL being used as the actual endpoint.
e.g. https://username:password@example.com/path?verify_ssl=False&q=foo
will result in a call to https://example.com/path?q=foo
To use this publisher for samples, add the following section to the
/etc/ceilometer/pipeline.yaml file or simply add it to an existing
@ -63,7 +74,6 @@ class HttpPublisher(publisher.ConfigPublisherBase):
def __init__(self, conf, parsed_url):
super(HttpPublisher, self).__init__(conf, parsed_url)
self.target = parsed_url.geturl()
if not parsed_url.hostname:
raise ValueError('The hostname of an endpoint for '
@ -83,12 +93,32 @@ class HttpPublisher(publisher.ConfigPublisherBase):
self.poster = (
self._do_post if strutils.bool_from_string(self._get_param(
params, 'batch', True)) else self._individual_post)
verify_ssl = self._get_param(params, 'verify_ssl', True)
try:
self.verify_ssl = strutils.bool_from_string(
self._get_param(params, 'verify_ssl', None), strict=True)
self.verify_ssl = strutils.bool_from_string(verify_ssl,
strict=True)
except ValueError:
self.verify_ssl = (self._get_param(params, 'verify_ssl', None)
or True)
self.verify_ssl = (verify_ssl or True)
username = parsed_url.username
password = parsed_url.password
if username:
self.client_auth = (username, password)
netloc = parsed_url.netloc.replace(username+':'+password+'@', '')
else:
self.client_auth = None
netloc = parsed_url.netloc
clientcert = self._get_param(params, 'clientcert', None)
clientkey = self._get_param(params, 'clientkey', None)
if clientcert:
if clientkey:
self.client_cert = (clientcert, clientkey)
else:
self.client_cert = clientcert
else:
self.client_cert = None
self.raw_only = strutils.bool_from_string(
self._get_param(params, 'raw_only', False))
@ -96,7 +126,16 @@ class HttpPublisher(publisher.ConfigPublisherBase):
kwargs = {'max_retries': self.max_retries,
'pool_connections': pool_size, 'pool_maxsize': pool_size}
self.session = requests.Session()
# FIXME(gordc): support https in addition to http
# authentication & config params have been removed, so use URL with
# updated query string
self.target = urlparse.urlunsplit([
parsed_url.scheme,
netloc,
parsed_url.path,
urlparse.urlencode(params),
parsed_url.fragment])
self.session.mount(self.target, adapters.HTTPAdapter(**kwargs))
LOG.debug('HttpPublisher for endpoint %s is initialized!' %
@ -105,8 +144,8 @@ class HttpPublisher(publisher.ConfigPublisherBase):
@staticmethod
def _get_param(params, name, default_value, cast=None):
try:
return cast(params.get(name)[-1]) if cast else params.get(name)[-1]
except (ValueError, TypeError):
return cast(params.pop(name)[-1]) if cast else params.pop(name)[-1]
except (ValueError, TypeError, KeyError):
LOG.debug('Default value %(value)s is used for %(name)s' %
{'value': default_value, 'name': name})
return default_value
@ -124,6 +163,8 @@ class HttpPublisher(publisher.ConfigPublisherBase):
try:
res = self.session.post(self.target, data=data,
headers=self.headers, timeout=self.timeout,
auth=self.client_auth,
cert=self.client_cert,
verify=self.verify_ssl)
res.raise_for_status()
LOG.debug('Message posting to %s: status code %d.',

View File

@ -232,6 +232,27 @@ class TestHttpPublisher(base.BaseTestCase):
publisher.publish_samples(self.sample_data)
self.assertEqual('/path/to/cert.crt', post.call_args[1]['verify'])
def test_post_basic_auth(self):
parsed_url = urlparse.urlparse(
'http://alice:l00kingGla$$@localhost:90/path1?')
publisher = http.HttpPublisher(self.CONF, parsed_url)
with mock.patch.object(requests.Session, 'post') as post:
publisher.publish_samples(self.sample_data)
self.assertEqual(('alice', 'l00kingGla$$'),
post.call_args[1]['auth'])
def test_post_client_cert_auth(self):
parsed_url = urlparse.urlparse('http://localhost:90/path1?'
'clientcert=/path/to/cert.crt&'
'clientkey=/path/to/cert.key')
publisher = http.HttpPublisher(self.CONF, parsed_url)
with mock.patch.object(requests.Session, 'post') as post:
publisher.publish_samples(self.sample_data)
self.assertEqual(('/path/to/cert.crt', '/path/to/cert.key'),
post.call_args[1]['cert'])
def test_post_raw_only(self):
parsed_url = urlparse.urlparse('http://localhost:90/path1?raw_only=1')
publisher = http.HttpPublisher(self.CONF, parsed_url)

View File

@ -0,0 +1,14 @@
---
features:
- In the 'publishers' section of a meter/event pipeline definition, https://
can now be used in addition to http://. Furthermore, either Basic or
client-certificate authentication can be used (obviously, client cert only
makes sense in the https case).
For Basic authentication, use the form http://username:password@hostname/.
For client certificate authentication pass the client certificate's path
(and the key file path, if the key is not in the certificate file) using
the parameters 'clientcert' and 'clientkey', e.g.
https://hostname/path?clientcert=/path/to/cert&clientkey=/path/to/key.
Any parameters or credentials used for http(s) publishers are removed from
the URL before the actual HTTP request is made.

View File

@ -231,6 +231,7 @@ ceilometer.sample.publisher =
direct = ceilometer.publisher.direct:DirectPublisher
kafka = ceilometer.publisher.kafka_broker:KafkaBrokerPublisher
http = ceilometer.publisher.http:HttpPublisher
https = ceilometer.publisher.http:HttpPublisher
gnocchi = ceilometer.publisher.direct:DirectPublisher
database = ceilometer.publisher.direct:DirectPublisher
file_alt = ceilometer.publisher.direct:DirectPublisher
@ -242,6 +243,7 @@ ceilometer.event.publisher =
notifier = ceilometer.publisher.messaging:EventNotifierPublisher
kafka = ceilometer.publisher.kafka_broker:KafkaBrokerPublisher
http = ceilometer.publisher.http:HttpPublisher
https = ceilometer.publisher.http:HttpPublisher
gnocchi = ceilometer.publisher.direct:DirectPublisher
database = ceilometer.publisher.direct:DirectPublisher
file_alt = ceilometer.publisher.direct:DirectPublisher