s3: Make s3 support configurable

Amazon S3 compatibility:
Due to security concerns raised, this change makes S3 support tunable
using a config option and is turned off by default.

Change-Id: I077f78946983f5d6b3b725dd6aa3ed178dc5604e
Signed-off-by: Prashanth Pai <ppai@redhat.com>
This commit is contained in:
Prashanth Pai 2016-03-14 22:49:49 +05:30
parent 5d15daaab6
commit 26cf5aa107
4 changed files with 90 additions and 9 deletions

View File

@ -80,6 +80,8 @@ swauth when `auth_type` in swauth is configured to be *Plaintext* (default).
[pipeline:main]
pipeline = catch_errors cache swift3 swauth proxy-server
It can be used with `auth_type` set to Sha1/Sha512 too but with certain caveats.
It can be used with `auth_type` set to Sha1/Sha512 too but with certain caveats
and security concern. Hence, s3 support is disabled by default and you have to
explicitly enable it in your configuration.
Refer to swift3 compatibility [section](https://swauth.readthedocs.io/en/latest/#swift3-middleware-compatibility)
in documentation for further details

View File

@ -124,12 +124,21 @@ Web Admin Install
Swift3 Middleware Compatibility
-------------------------------
`Swift3 middleware <https://github.com/openstack/swift3>`_ can be used with
swauth when `auth_type` in swauth is configured to be *Plaintext* (default)::
`Swift3 middleware <https://github.com/openstack/swift3>`_ support has to be
explicitly turned on in conf file using `s3_support` config option. It can
easily be used with swauth when `auth_type` in swauth is configured to be
*Plaintext* (default)::
[pipeline:main]
pipeline = catch_errors cache swift3 swauth proxy-server
[filter:swauth]
use = egg:swauth#swauth
super_admin_key = swauthkey
s3_support = on
The AWS S3 client uses password in plaintext to
`compute HMAC signature <https://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html>`_
When `auth_type` in swauth is configured to be *Sha1* or *Sha512*, swauth
@ -139,7 +148,22 @@ in signature mismatch although the user credentials are correct.
When `auth_type` is **not** *Plaintext*, the only way for S3 clients to
authenticate is by giving SHA1/SHA512 of password as input to it's HMAC
function. In this case, the S3 clients will have to know `auth_type` and
`salt` beforehand.
`auth_type_salt` beforehand. Here is a sample configuration::
[pipeline:main]
pipeline = catch_errors cache swift3 swauth proxy-server
[filter:swauth]
use = egg:swauth#swauth
super_admin_key = swauthkey
s3_support = on
auth_type = Sha512
auth_type_salt = mysalt
**Security Concern**: Swauth stores user information (username, password hash,
salt etc) as objects in the Swift cluster. If these backend objects which
contain password hashes gets stolen, the intruder will be able to authenticate
using the hash directly when S3 API is used.
Contents

View File

@ -168,6 +168,16 @@ class Swauth(object):
# If auth_type_salt is not set in conf file, a random salt will be
# generated for each new password to be encoded.
self.auth_encoder.salt = conf.get('auth_type_salt', None)
# Due to security concerns, S3 support is disabled by default.
self.s3_support = conf.get('s3_support', 'off').lower() in TRUE_VALUES
if self.s3_support and self.auth_type != 'Plaintext' \
and not self.auth_encoder.salt:
msg = _('S3 support requires salt to be manually set in conf '
'file using auth_type_salt config option.')
self.logger.warning(msg)
self.s3_support = False
self.allow_overrides = \
conf.get('allow_overrides', 't').lower() in TRUE_VALUES
self.agent = '%(orig)s Swauth'
@ -232,6 +242,9 @@ class Swauth(object):
elif env.get('PATH_INFO', '').startswith(self.auth_prefix):
return self.handle(env, start_response)
s3 = env.get('HTTP_AUTHORIZATION')
if s3 and not self.s3_support:
msg = 'S3 support is disabled in swauth.'
return HTTPBadRequest(body=msg)(env, start_response)
token = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN'))
if token and len(token) > swauth.authtypes.MAX_TOKEN_LENGTH:
return HTTPBadRequest(body='Token exceeds maximum length.')(env,
@ -310,6 +323,9 @@ class Swauth(object):
groups = None
if env.get('HTTP_AUTHORIZATION'):
if not self.s3_support:
self.logger.warning('S3 support is disabled in swauth.')
return None
if self.swauth_remote:
# TODO(gholt): Support S3-style authorization with
# swauth_remote mode

View File

@ -4033,13 +4033,50 @@ class TestAuth(unittest.TestCase):
self.assertEqual(resp.status_int, 400)
self.assertEqual(resp.body, 'Token exceeds maximum length.')
def test_crazy_authorization(self):
def test_s3_enabled_when_conditions_are_met(self):
# auth_type_salt needs to be set
for atype in ('Sha1', 'Sha512'):
test_auth = \
auth.filter_factory({
'super_admin_key': 'supertest',
's3_support': 'on',
'auth_type_salt': 'blah',
'auth_type': atype})(FakeApp())
self.assertTrue(test_auth.s3_support)
# auth_type_salt need not be set for Plaintext
test_auth = \
auth.filter_factory({
'super_admin_key': 'supertest',
's3_support': 'on',
'auth_type': 'Plaintext'})(FakeApp())
self.assertTrue(test_auth.s3_support)
def test_s3_disabled_when_conditions_not_met(self):
# Conf says that it wants s3 support but other conditions are not met
# In that case s3 support should be disabled.
for atype in ('Sha1', 'Sha512'):
# auth_type_salt is not set
test_auth = \
auth.filter_factory({
'super_admin_key': 'supertest',
's3_support': 'on',
'auth_type': atype})(FakeApp())
self.assertFalse(test_auth.s3_support)
def test_s3_authorization_default_off(self):
self.assertFalse(self.test_auth.s3_support)
req = self._make_request('/v1/AUTH_account', headers={
'authorization': 'somebody elses header value'})
'authorization': 's3_header'})
resp = req.get_response(self.test_auth)
self.assertEqual(resp.status_int, 401)
self.assertEqual(resp.environ['swift.authorize'],
self.test_auth.denied_response)
self.assertEqual(resp.status_int, 400) # HTTPBadRequest
self.assertTrue(resp.environ.get('swift.authorize') is None)
def test_s3_turned_off_get_groups(self):
env = \
{'HTTP_AUTHORIZATION': 's3 header'}
token = 'whatever'
self.test_auth.logger = mock.Mock()
self.assertEqual(self.test_auth.get_groups(env, token), None)
def test_default_storage_policy(self):
ath = auth.filter_factory({})(FakeApp())
@ -4050,6 +4087,7 @@ class TestAuth(unittest.TestCase):
self.assertEqual(ath.default_storage_policy, 'ssd')
def test_s3_creds_unicode(self):
self.test_auth.s3_support = True
self.test_auth.app = FakeApp(iter([
('200 Ok', {},
json.dumps({"auth": unicode("plaintext:key)"),
@ -4064,6 +4102,7 @@ class TestAuth(unittest.TestCase):
self.assertEqual(self.test_auth.get_groups(env, token), None)
def test_s3_only_hash_passed_to_hmac(self):
self.test_auth.s3_support = True
key = 'dadada'
salt = 'zuck'
key_hash = hashlib.sha1('%s%s' % (salt, key)).hexdigest()