Implement a https:// in REST alarm notification

This change adds schemes https:// to the alarm notifier.

By default, the server certificate is verified like a browser does
but with the system CA.

A client certificate can be set in the configuration file.

 [alarm]
 rest_notifier_certificate_file = /path/certificate.pem
 rest_notifier_certificate_key = /path/private_key.pem

Change-Id: Id06e0a45ef88c15674052faeb941d87b70c7b99b
Blueprint: alarm-notifier
This commit is contained in:
Mehdi Abaakouk 2013-07-16 12:15:32 +02:00
parent 0b50ac2f59
commit fc9b416a47
5 changed files with 102 additions and 19 deletions

View File

@ -20,12 +20,27 @@
import eventlet import eventlet
import requests import requests
from oslo.config import cfg
from ceilometer.alarm import notifier from ceilometer.alarm import notifier
from ceilometer.openstack.common import jsonutils from ceilometer.openstack.common import jsonutils
from ceilometer.openstack.common import log from ceilometer.openstack.common import log
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
REST_NOTIFIER_OPTS = [
cfg.StrOpt('rest_notifier_certificate_file',
default='',
help='SSL Client certificate for REST notifier'
),
cfg.StrOpt('rest_notifier_certificate_key',
default='',
help='SSL Client private key for REST notifier'
),
]
cfg.CONF.register_opts(REST_NOTIFIER_OPTS, group="alarm")
class RestAlarmNotifier(notifier.AlarmNotifier): class RestAlarmNotifier(notifier.AlarmNotifier):
"""Rest alarm notifier.""" """Rest alarm notifier."""
@ -33,9 +48,13 @@ class RestAlarmNotifier(notifier.AlarmNotifier):
def notify(self, action, alarm, state, reason): def notify(self, action, alarm, state, reason):
LOG.info("Notifying alarm %s in state %s with action %s because %s", LOG.info("Notifying alarm %s in state %s with action %s because %s",
alarm, state, action, reason) alarm, state, action, reason)
data = {
'state': state, body = {'state': state, 'reason': reason}
'reason': reason, kwargs = {'data': jsonutils.dumps(body)}
}
eventlet.spawn_n(requests.post, action, cert = cfg.CONF.alarm.rest_notifier_certificate_file
data=jsonutils.dumps(data)) key = cfg.CONF.alarm.rest_notifier_certificate_key
if action.scheme == 'https' and cert:
kwargs['cert'] = (cert, key) if key else cert
eventlet.spawn_n(requests.post, action, **kwargs)

View File

@ -64,4 +64,5 @@ class TestCase(testtools.TestCase):
self.stubs.UnsetAll() self.stubs.UnsetAll()
self.stubs.SmartUnsetAll() self.stubs.SmartUnsetAll()
self.mox.VerifyAll() self.mox.VerifyAll()
cfg.CONF.reset()
super(TestCase, self).tearDown() super(TestCase, self).tearDown()

View File

@ -562,6 +562,17 @@
[alarm] [alarm]
#
# Options defined in ceilometer.alarm.notifier.rest
#
# SSL Client certificate for REST notifier (string value)
#rest_notifier_certificate_file=
# SSL Client private key for REST notifier (string value)
#rest_notifier_certificate_key=
# #
# Options defined in ceilometer.alarm.service # Options defined in ceilometer.alarm.service
# #
@ -689,4 +700,4 @@
#password=<None> #password=<None>
# Total option count: 130 # Total option count: 132

View File

@ -104,6 +104,7 @@ ceilometer.alarm.notifier =
log = ceilometer.alarm.notifier.log:LogAlarmNotifier log = ceilometer.alarm.notifier.log:LogAlarmNotifier
test = ceilometer.alarm.notifier.test:TestAlarmNotifier test = ceilometer.alarm.notifier.test:TestAlarmNotifier
http = ceilometer.alarm.notifier.rest:RestAlarmNotifier http = ceilometer.alarm.notifier.rest:RestAlarmNotifier
https = ceilometer.alarm.notifier.rest:RestAlarmNotifier
paste.filter_factory = paste.filter_factory =
swift = ceilometer.objectstore.swift_middleware:filter_factory swift = ceilometer.objectstore.swift_middleware:filter_factory

View File

@ -15,11 +15,12 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import eventlet
import urlparse import urlparse
import mock import mock
import requests import requests
from oslo.config import cfg
from ceilometer.alarm import service from ceilometer.alarm import service
from ceilometer.openstack.common import context from ceilometer.openstack.common import context
from ceilometer.openstack.common import network_utils from ceilometer.openstack.common import network_utils
@ -59,6 +60,10 @@ class TestAlarmNotifier(base.TestCase):
'condition': {'threshold': 42}, 'condition': {'threshold': 42},
}) })
@staticmethod
def _fake_spawn_n(func, *args, **kwargs):
func(*args, **kwargs)
def test_notify_alarm_rest_action(self): def test_notify_alarm_rest_action(self):
action = 'http://host/action' action = 'http://host/action'
data_json = '{"state": "ALARM", "reason": "what ?"}' data_json = '{"state": "ALARM", "reason": "what ?"}'
@ -66,17 +71,63 @@ class TestAlarmNotifier(base.TestCase):
self.mox.StubOutWithMock(requests, "post") self.mox.StubOutWithMock(requests, "post")
requests.post(network_utils.urlsplit(action), data=data_json) requests.post(network_utils.urlsplit(action), data=data_json)
self.mox.ReplayAll() self.mox.ReplayAll()
self.service.notify_alarm(context.get_admin_context(),
{ with mock.patch('eventlet.spawn_n', self._fake_spawn_n):
'actions': [action], self.service.notify_alarm(context.get_admin_context(),
'alarm': {'name': 'foobar'}, {
'condition': {'threshold': 42}, 'actions': [action],
'reason': 'what ?', 'alarm': {'name': 'foobar'},
'state': 'ALARM', 'condition': {'threshold': 42},
}) 'reason': 'what ?',
eventlet.sleep(1) 'state': 'ALARM',
self.mox.UnsetStubs() })
self.mox.VerifyAll()
def test_notify_alarm_rest_action_with_ssl_client_cert(self):
action = 'https://host/action'
certificate = "/etc/ssl/cert/whatever.pem"
data_json = '{"state": "ALARM", "reason": "what ?"}'
cfg.CONF.set_override("rest_notifier_certificate_file", certificate,
group='alarm')
self.mox.StubOutWithMock(requests, "post")
requests.post(network_utils.urlsplit(action), data=data_json,
cert=certificate)
self.mox.ReplayAll()
with mock.patch('eventlet.spawn_n', self._fake_spawn_n):
self.service.notify_alarm(context.get_admin_context(),
{
'actions': [action],
'alarm': {'name': 'foobar'},
'condition': {'threshold': 42},
'reason': 'what ?',
'state': 'ALARM',
})
def test_notify_alarm_rest_action_with_ssl_client_cert_and_key(self):
action = 'https://host/action'
certificate = "/etc/ssl/cert/whatever.pem"
key = "/etc/ssl/cert/whatever.key"
data_json = '{"state": "ALARM", "reason": "what ?"}'
cfg.CONF.set_override("rest_notifier_certificate_file", certificate,
group='alarm')
cfg.CONF.set_override("rest_notifier_certificate_key", key,
group='alarm')
self.mox.StubOutWithMock(requests, "post")
requests.post(network_utils.urlsplit(action), data=data_json,
cert=(certificate, key))
self.mox.ReplayAll()
with mock.patch('eventlet.spawn_n', self._fake_spawn_n):
self.service.notify_alarm(context.get_admin_context(),
{
'actions': [action],
'alarm': {'name': 'foobar'},
'condition': {'threshold': 42},
'reason': 'what ?',
'state': 'ALARM',
})
@staticmethod @staticmethod
def _fake_urlsplit(*args, **kwargs): def _fake_urlsplit(*args, **kwargs):