Merge branch 'webhook-configure-auth' into develop

This commit is contained in:
Seth House 2014-04-08 13:51:50 -06:00
commit b961aea447
2 changed files with 62 additions and 9 deletions

View file

@ -54,6 +54,17 @@ A REST API for Salt
will be sent in the clear!
.. versionadded:: 0.8.3
webhook_disable_auth : False
The :py:class:`Webhook` URL requires authentication by default but
external services cannot always be configured to send authentication.
See the Webhook documentation for suggestions on securing this
interface.
.. versionadded:: 0.8.4.1
webhook_url : /hook
Configure the URL endpoint for the :py:class:`Webhook` entry point.
.. versionadded:: 0.8.4.1
thread_pool : ``100``
The number of worker threads to start up in the pool.
@ -1243,18 +1254,24 @@ class Webhook(object):
A generic web hook entry point that fires an event on Salt's event bus
External services can POST data to this URL to trigger an event in Salt.
For example, Jenkins-CI or Travis-CI, or GitHub web hooks.
This entry point does not require authentication. The event data is taken
from the request body.
For example, Amazon SNS, Jenkins-CI or Travis-CI, or GitHub web hooks.
.. note:: Be mindful of security
Salt's Reactor can run any code. If you write a Reactor SLS that
responds to a hook event be sure to validate that the event came from a
trusted source and contains valid data! Pass a secret key and use SSL.
Salt's Reactor can run any code. A Reactor SLS that responds to a hook
event is responsible for validating that the event came from a trusted
source and contains valid data.
This is a generic interface and securing it is up to you!
**This is a generic interface and securing it is up to you!**
This URL requires authentication however not all external services can
be configured to authenticate. For this reason authentication can be
selectively disabled for this URL. Follow best practices -- always use
SSL, pass a secret key, configure the firewall to only allow traffic
from a known source, etc.
The event data is taken from the request body. The
:mailheader:`Content-Type` header is respected for the payload.
The event tag is prefixed with ``salt/netapi/hook`` and the URL path is
appended to the end. For example, a ``POST`` request sent to
@ -1280,6 +1297,9 @@ class Webhook(object):
# Don't do any lowdata processing on the POST data
'tools.lowdata_fmt.on': True,
# Auth can be overridden in __init__().
'tools.salt_token.on': True,
'tools.salt_auth.on': True,
})
def __init__(self):
@ -1287,6 +1307,10 @@ class Webhook(object):
self.event = salt.utils.event.SaltEvent('master',
self.opts.get('sock_dir', ''))
if cherrypy.config['apiopts'].get('webhook_disable_auth'):
self._cp_config['tools.salt_token.on'] = False
self._cp_config['tools.salt_auth.on'] = False
def POST(self, *args, **kwargs):
'''
Fire an event in Salt with a custom event tag and data
@ -1422,7 +1446,6 @@ class API(object):
'run': Run,
'jobs': Jobs,
'events': Events,
'hook': Webhook,
'stats': Stats,
}
@ -1433,6 +1456,9 @@ class API(object):
for url, cls in self.url_map.items():
setattr(self, url, cls())
# Allow the Webhook URL to be overridden from the conf.
setattr(self, self.apiopts.get('webhook_url', 'hook').lstrip('/'), Webhook())
if 'app' in self.apiopts:
setattr(self, self.apiopts.get('app_path', 'app').lstrip('/'), App())

View file

@ -30,6 +30,13 @@ class TestAuth(BaseRestCherryPyTest):
request, response = self.request('/login')
self.assertEqual(response.status, '200 OK')
def test_webhook_auth(self):
'''
Requests to the webhook URL require auth by default
'''
self.assertRaisesRegexp(cherrypy.InternalRedirect, '\/login',
self.request, '/hook', method='POST', data={})
class TestLogin(BaseRestCherryPyTest):
auth_creds = (
('username', 'saltdev'),
@ -69,3 +76,23 @@ class TestLogin(BaseRestCherryPyTest):
'content-type': 'application/x-www-form-urlencoded'
})
self.assertEqual(response.status, '401 Unauthorized')
class TestWebhookDisableAuth(BaseRestCherryPyTest):
__opts__ = {
'rest_cherrypy': {
'port': 8000,
'debug': True,
'webhook_disable_auth': True,
},
}
def test_webhook_noauth(self):
'''
Auth can be disabled for requests to the webhook URL
'''
body = urllib.urlencode({'foo': 'Foo!'})
request, response = self.request('/hook', method='POST', body=body,
headers={
'content-type': 'application/x-www-form-urlencoded'
})
self.assertEqual(response.status, '200 OK')