tempauth: Support fernet tokens

Tempauth fernet tokens use a secret shared among all proxies to encrypt
user group information. Because they are encrypted, clients can neither
view nor edit this information; it is an opaque bearer token similar to
the existing memcached-backed tokens (just much longer). Note that
tokens still expire after the configured token_life.

Add a new set of config options of the form

   fernet_key_<keyid> = <32 url-safe base64-encoded bytes>

Any of the configured keys will be used to attempt to decrypt tokens
starting with "ftk" and extract group information.

Another new config option

   active_fernet_key_id = <keyid>

dictates which key should be used when minting tokens. Such tokens
will start with "ftk" to distinguish them from memcached-backed tokens
(which continue to start with "tk"). If active_fernet_key_id is not
configured, memcached-backed tokens continue to be used.

Together, these allow seamless transitions from memcached-backed tokens
to fernet tokens, as well as transitions from one fernet key to another:

   1. Add a new fernet_key_<keyid> entry.
   2. Ensure all proxies have the new config with fernet_key_<keyid>.
   3. Set active_fernet_key_id = <keyid>.
   4. Ensure all proxies have the new config with the new
      active_fernet_key_id.

This is similar to the key-rotation process for the encryption feature,
except that old keys may be pruned following a token_life period.

Additionally, opportunistically compress groups before minting tokens.
Compressed tokens will begin with "zftk" but otherwise behave just like
"ftk" tokens.

Change-Id: I0bdc98765d05e91f872ef39d4722f91711a5641f
This commit is contained in:
Tim Burke
2022-10-13 15:29:19 -07:00
parent 64bd1acb9e
commit 74030236ad
8 changed files with 273 additions and 88 deletions

View File

@@ -459,6 +459,19 @@ use = egg:swift#tempauth
# This can be useful with an SSL load balancer in front of a non-SSL server. # This can be useful with an SSL load balancer in front of a non-SSL server.
# storage_url_scheme = default # storage_url_scheme = default
# #
# Fernet keys may be used for storage, rather than relying on memcached.
# Multiple keys may be configured using options named 'fernet_key_<key_id>'
# where 'key_id' is a unique identifier. The value should be 32 url-safe
# base64-encoded bytes, such as may be generated using
# `openssl rand -base64 32 | tr '+/' '-_'`
# Any of these keys may be used for decryption. Only one key may be used
# for encryption by a proxy at any given time; configure it with the
# 'active_fernet_key_id' option. All proxies in the cluster should know
# about a key before it is activated. If blank (the default),
# memcached-backed tokens will be issued.
# fernet_key_myid = <32 url-safe base64-encoded bytes>
# active_fernet_key_id = myid
#
# Lastly, you need to list all the accounts/users you want here. The format is: # Lastly, you need to list all the accounts/users you want here. The format is:
# user_<account>_<user> = <key> [group] [group] [...] [storage_url] # user_<account>_<user> = <key> [group] [group] [...] [storage_url]
# or if you want underscores in <account> or <user>, you can base64 encode them # or if you want underscores in <account> or <user>, you can base64 encode them

View File

@@ -19,7 +19,7 @@ from swift.common.exceptions import UnknownSecretIdError
from swift.common.middleware.crypto.crypto_utils import CRYPTO_KEY_CALLBACK from swift.common.middleware.crypto.crypto_utils import CRYPTO_KEY_CALLBACK
from swift.common.swob import Request, HTTPException, wsgi_to_str, str_to_wsgi from swift.common.swob import Request, HTTPException, wsgi_to_str, str_to_wsgi
from swift.common.utils import readconf, strict_b64decode, get_logger, \ from swift.common.utils import readconf, strict_b64decode, get_logger, \
split_path split_path, load_multikey_opts
from swift.common.wsgi import WSGIContext from swift.common.wsgi import WSGIContext
@@ -282,17 +282,6 @@ class BaseKeyMaster(object):
return readconf(self.keymaster_config_path, return readconf(self.keymaster_config_path,
self.keymaster_conf_section) self.keymaster_conf_section)
def _load_multikey_opts(self, conf, prefix):
result = []
for k, v in conf.items():
if not k.startswith(prefix):
continue
suffix = k[len(prefix):]
if suffix and (suffix[0] != '_' or len(suffix) < 2):
raise ValueError('Malformed root secret option name %s' % k)
result.append((k, suffix[1:] or None, v))
return sorted(result)
def __call__(self, env, start_response): def __call__(self, env, start_response):
req = Request(env) req = Request(env)
@@ -366,8 +355,8 @@ class KeyMaster(BaseKeyMaster):
:rtype: dict :rtype: dict
""" """
root_secrets = {} root_secrets = {}
for opt, secret_id, value in self._load_multikey_opts( for opt, secret_id, value in load_multikey_opts(
conf, 'encryption_root_secret'): conf, 'encryption_root_secret', allow_none_key=True):
try: try:
secret = self._decode_root_secret(value) secret = self._decode_root_secret(value)
except ValueError: except ValueError:

View File

@@ -17,7 +17,7 @@ import logging
import os import os
from swift.common.middleware.crypto import keymaster from swift.common.middleware.crypto import keymaster
from swift.common.utils import LogLevelFilter from swift.common.utils import LogLevelFilter, load_multikey_opts
from kmip.pie.client import ProxyKmipClient from kmip.pie.client import ProxyKmipClient
@@ -145,7 +145,7 @@ class KmipKeyMaster(keymaster.BaseKeyMaster):
return conf return conf
def _get_root_secret(self, conf): def _get_root_secret(self, conf):
multikey_opts = self._load_multikey_opts(conf, 'key_id') multikey_opts = load_multikey_opts(conf, 'key_id', allow_none_key=True)
kmip_to_secret = {} kmip_to_secret = {}
root_secrets = {} root_secrets = {}
with self.proxy_kmip_client as client: with self.proxy_kmip_client as client:

View File

@@ -16,6 +16,7 @@ from castellan import key_manager, options
from castellan.common.credentials import keystone_password from castellan.common.credentials import keystone_password
from oslo_config import cfg from oslo_config import cfg
from swift.common.middleware.crypto.keymaster import BaseKeyMaster from swift.common.middleware.crypto.keymaster import BaseKeyMaster
from swift.common.utils import load_multikey_opts
class KmsKeyMaster(BaseKeyMaster): class KmsKeyMaster(BaseKeyMaster):
@@ -74,8 +75,8 @@ class KmsKeyMaster(BaseKeyMaster):
manager = key_manager.API(oslo_conf) manager = key_manager.API(oslo_conf)
root_secrets = {} root_secrets = {}
for opt, secret_id, key_id in self._load_multikey_opts( for opt, secret_id, key_id in load_multikey_opts(
conf, 'key_id'): conf, 'key_id', allow_none_key=True):
key = manager.get(ctxt, key_id) key = manager.get(ctxt, key_id)
if key is None: if key is None:
raise ValueError("Retrieval of encryption root secret with " raise ValueError("Retrieval of encryption root secret with "

View File

@@ -179,7 +179,9 @@ from time import time
from traceback import format_exc from traceback import format_exc
from uuid import uuid4 from uuid import uuid4
import base64 import base64
import zlib
from cryptography import fernet
from eventlet import Timeout from eventlet import Timeout
from swift.common.memcached import MemcacheConnectionError from swift.common.memcached import MemcacheConnectionError
from swift.common.swob import ( from swift.common.swob import (
@@ -192,7 +194,7 @@ from swift.common.request_helpers import get_sys_meta_prefix
from swift.common.middleware.acl import ( from swift.common.middleware.acl import (
clean_acl, parse_acl, referrer_allowed, acls_from_account_info) clean_acl, parse_acl, referrer_allowed, acls_from_account_info)
from swift.common.utils import cache_from_env, get_logger, \ from swift.common.utils import cache_from_env, get_logger, \
split_path, config_true_value split_path, config_true_value, load_multikey_opts
from swift.common.registry import register_swift_info from swift.common.registry import register_swift_info
from swift.common.utils import config_read_reseller_options, quote from swift.common.utils import config_read_reseller_options, quote
from swift.proxy.controllers.base import get_account_info from swift.proxy.controllers.base import get_account_info
@@ -229,6 +231,17 @@ class TempAuth(object):
if not self.auth_prefix.endswith('/'): if not self.auth_prefix.endswith('/'):
self.auth_prefix += '/' self.auth_prefix += '/'
self.token_life = int(conf.get('token_life', DEFAULT_TOKEN_LIFE)) self.token_life = int(conf.get('token_life', DEFAULT_TOKEN_LIFE))
self.fernet_keys = {
key_id: fernet.Fernet(key)
for _, key_id, key in load_multikey_opts(conf, 'fernet_key')
}
self.fernet = (fernet.MultiFernet(self.fernet_keys.values())
if self.fernet_keys else None)
self.active_fernet_key_id = conf.get('active_fernet_key_id')
if self.active_fernet_key_id and \
self.active_fernet_key_id not in self.fernet_keys:
raise ValueError("key_id %r not found; %r are available" % (
self.active_fernet_key_id, sorted(self.fernet_keys.keys())))
self.allow_overrides = config_true_value( self.allow_overrides = config_true_value(
conf.get('allow_overrides', 't')) conf.get('allow_overrides', 't'))
self.storage_url_scheme = conf.get('storage_url_scheme', 'default') self.storage_url_scheme = conf.get('storage_url_scheme', 'default')
@@ -425,6 +438,40 @@ class TempAuth(object):
groups = ','.join(groups) groups = ','.join(groups)
return groups return groups
def groups_from_fernet(self, env, token):
try:
if self.fernet:
return self.fernet.decrypt(
token.encode('ascii'),
ttl=self.token_life).decode('utf8')
except (ValueError, fernet.InvalidToken):
pass
return None
def groups_from_compressed_fernet(self, env, token):
try:
if self.fernet:
return zlib.decompress(self.fernet.decrypt(
token.encode('ascii'),
ttl=self.token_life)).decode('utf8')
except (ValueError, fernet.InvalidToken):
pass
return None
def groups_from_memcache(self, env, token):
memcache_client = cache_from_env(env)
if not memcache_client:
raise Exception('Memcache required')
memcache_token_key = '%s/token/%stk%s' % (
self.reseller_prefix, self.reseller_prefix, token)
cached_auth_data = memcache_client.get(memcache_token_key)
groups = None
if cached_auth_data:
expires, groups = cached_auth_data
if expires < time():
groups = None
return groups
def get_groups(self, env, token): def get_groups(self, env, token):
""" """
Get groups for the given token. Get groups for the given token.
@@ -436,20 +483,24 @@ class TempAuth(object):
of. The first group in the list is also considered a unique of. The first group in the list is also considered a unique
identifier for that user. identifier for that user.
""" """
groups = None handlers = [
memcache_client = cache_from_env(env) ('zftk', self.groups_from_compressed_fernet),
if not memcache_client: ('ftk', self.groups_from_fernet),
raise Exception('Memcache required') ('tk', self.groups_from_memcache),
memcache_token_key = '%s/token/%s' % (self.reseller_prefix, token) ]
cached_auth_data = memcache_client.get(memcache_token_key) if token:
if cached_auth_data: for prefix, handler in handlers:
expires, groups = cached_auth_data prefix = self.reseller_prefix + prefix
if expires < time(): if token.startswith(prefix):
groups = None groups = handler(env, token[len(prefix):])
if groups:
return groups
s3_auth_details = env.get('s3api.auth_details') or\ s3_auth_details = env.get('s3api.auth_details') or\
env.get('swift3.auth_details') env.get('swift3.auth_details')
if s3_auth_details: if not s3_auth_details:
return None
if 'check_signature' not in s3_auth_details: if 'check_signature' not in s3_auth_details:
self.logger.warning( self.logger.warning(
'Swift3 did not provide a check_signature function; ' 'Swift3 did not provide a check_signature function; '
@@ -465,9 +516,7 @@ class TempAuth(object):
return None return None
env['PATH_INFO'] = env['PATH_INFO'].replace( env['PATH_INFO'] = env['PATH_INFO'].replace(
str_to_wsgi(account_user), wsgi_unquote(account_id), 1) str_to_wsgi(account_user), wsgi_unquote(account_id), 1)
groups = self._get_user_groups(account, account_user, account_id) return self._get_user_groups(account, account_user, account_id)
return groups
def account_acls(self, req): def account_acls(self, req):
""" """
@@ -718,6 +767,26 @@ class TempAuth(object):
def _create_new_token(self, memcache_client, def _create_new_token(self, memcache_client,
account, account_user, account_id): account, account_user, account_id):
if self.active_fernet_key_id:
expires = time() + self.token_life
token_prefix = 'ftk' # nosec: B105
groups = self._get_user_groups(
account,
account_user,
account_id,
).encode('utf8')
compressed = zlib.compress(groups)
if len(compressed) < len(groups):
token_prefix = 'zftk' # nosec: B105
groups = compressed
token = ''.join([
self.reseller_prefix,
token_prefix,
self.fernet_keys[self.active_fernet_key_id].encrypt(
groups).decode('ascii'),
])
return token, expires
# Generate new token # Generate new token
token = '%stk%s' % (self.reseller_prefix, uuid4().hex) token = '%stk%s' % (self.reseller_prefix, uuid4().hex)
expires = time() + self.token_life expires = time() + self.token_life
@@ -817,13 +886,15 @@ class TempAuth(object):
self.logger.increment('token_denied') self.logger.increment('token_denied')
return HTTPUnauthorized(request=req, headers=unauthed_headers) return HTTPUnauthorized(request=req, headers=unauthed_headers)
account_id = self.users[account_user]['url'].rsplit('/', 1)[-1] account_id = self.users[account_user]['url'].rsplit('/', 1)[-1]
# Get memcache client # Try to get memcache client
memcache_client = cache_from_env(req.environ) memcache_client = cache_from_env(req.environ)
if not memcache_client: if not (memcache_client or self.active_fernet_key_id):
raise Exception('Memcache required') raise Exception('Memcache required')
# See if a token already exists and hasn't expired # See if a token already exists and hasn't expired
token = None token = None
memcache_user_key = '%s/user/%s' % (self.reseller_prefix, account_user) if memcache_client:
memcache_user_key = '%s/user/%s' % (
self.reseller_prefix, account_user)
candidate_token = memcache_client.get(memcache_user_key) candidate_token = memcache_client.get(memcache_user_key)
if candidate_token: if candidate_token:
memcache_token_key = \ memcache_token_key = \

View File

@@ -1501,6 +1501,32 @@ def cache_from_env(env, allow_none=False):
return item_from_env(env, 'swift.cache', allow_none) return item_from_env(env, 'swift.cache', allow_none)
def load_multikey_opts(conf, prefix, allow_none_key=False):
"""
Read multi-key options of the form "<prefix>_<key> = <value>"
:param conf: a config dict
:param prefix: the prefix for which to search
:param allow_none_key: if True, also parse "<prefix> = <value>" and
include it in the result as ``(None, value)``
:returns: a sorted list of (<key>, <value>) tuples
:raises ValueError: if an option starts with prefix but cannot be parsed
"""
result = []
for k, v in conf.items():
if not k.startswith(prefix):
continue
suffix = k[len(prefix):]
if not suffix and allow_none_key:
result.append((k, None, v))
continue
if len(suffix) >= 2 and suffix[0] == '_':
result.append((k, suffix[1:], v))
continue
raise ValueError('Malformed multi-key option name %s' % k)
return sorted(result)
def write_pickle(obj, dest, tmp=None, pickle_protocol=0): def write_pickle(obj, dest, tmp=None, pickle_protocol=0):
""" """
Ensure that a pickle file gets written to disk. The file Ensure that a pickle file gets written to disk. The file

View File

@@ -399,7 +399,7 @@ class TestKeymaster(unittest.TestCase):
with self.assertRaises(ValueError) as err: with self.assertRaises(ValueError) as err:
keymaster.KeyMaster(self.swift, conf) keymaster.KeyMaster(self.swift, conf)
self.assertEqual( self.assertEqual(
'Malformed root secret option name %s' % bad_option, 'Malformed multi-key option name %s' % bad_option,
str(err.exception)) str(err.exception))
do_test('encryption_root_secret1') do_test('encryption_root_secret1')
do_test('encryption_root_secret123') do_test('encryption_root_secret123')

View File

@@ -22,10 +22,11 @@ from time import time
from urllib.parse import quote, urlparse from urllib.parse import quote, urlparse
from swift.common.middleware import tempauth as auth from swift.common.middleware import tempauth as auth
from swift.common.middleware.acl import format_acl from swift.common.middleware.acl import format_acl
from swift.common.swob import Request, Response, bytes_to_wsgi from swift.common.swob import Request, Response, bytes_to_wsgi, HTTPOk
from swift.common.statsd_client import StatsdClient from swift.common.statsd_client import StatsdClient
from swift.common.utils import split_path from swift.common.utils import split_path
from test.unit import FakeMemcache from test.unit import FakeMemcache
from test.unit.common.middleware.helpers import FakeSwift
NO_CONTENT_RESP = (('204 No Content', {}, ''),) # mock server response NO_CONTENT_RESP = (('204 No Content', {}, ''),) # mock server response
@@ -537,8 +538,8 @@ class TestAuth(unittest.TestCase):
def test_detect_reseller_request(self): def test_detect_reseller_request(self):
req = self._make_request('/v1/AUTH_admin', req = self._make_request('/v1/AUTH_admin',
headers={'X-Auth-Token': 'AUTH_t'}) headers={'X-Auth-Token': 'AUTH_tk'})
cache_key = 'AUTH_/token/AUTH_t' cache_key = 'AUTH_/token/AUTH_tk'
cache_entry = (time() + 3600, '.reseller_admin') cache_entry = (time() + 3600, '.reseller_admin')
req.environ['swift.cache'].set(cache_key, cache_entry) req.environ['swift.cache'].set(cache_key, cache_entry)
req.get_response(self.test_auth) req.get_response(self.test_auth)
@@ -670,8 +671,8 @@ class TestAuth(unittest.TestCase):
test_auth = auth.filter_factory({'user_acct_user': 'testing'})( test_auth = auth.filter_factory({'user_acct_user': 'testing'})(
FakeApp(iter(NO_CONTENT_RESP * 1))) FakeApp(iter(NO_CONTENT_RESP * 1)))
req = self._make_request('/v1/AUTH_acct', req = self._make_request('/v1/AUTH_acct',
headers={'X-Auth-Token': 'AUTH_t'}) headers={'X-Auth-Token': 'AUTH_tk'})
cache_key = 'AUTH_/token/AUTH_t' cache_key = 'AUTH_/token/AUTH_tk'
cache_entry = (time() + 3600, 'AUTH_acct') cache_entry = (time() + 3600, 'AUTH_acct')
req.environ['swift.cache'].set(cache_key, cache_entry) req.environ['swift.cache'].set(cache_key, cache_entry)
resp = req.get_response(test_auth) resp = req.get_response(test_auth)
@@ -724,12 +725,96 @@ class TestAuth(unittest.TestCase):
self.assertEqual(resp.headers.get('Www-Authenticate'), self.assertEqual(resp.headers.get('Www-Authenticate'),
'Swift realm="act"') 'Swift realm="act"')
def test_fernet_token_no_memcache(self):
swift = FakeSwift()
swift.register('GET', '/v1/AUTH_ac', HTTPOk, {})
test_auth = auth.filter_factory({
'user_ac_user': 'testing .admin',
'fernet_key_2024': 'esipv1wC03xLGPb3cydid0uPINl6g8sydhlPh6iwJxk=',
'active_fernet_key_id': '2024',
})(swift)
req = Request.blank(
'/auth/v1.0',
headers={'X-Auth-User': 'ac:user', 'X-Auth-Key': 'testing'})
# no memcache!
resp = req.get_response(test_auth)
self.assertEqual(resp.status_int, 200)
token = resp.headers['X-Auth-Token']
self.assertEqual(token[:8], 'AUTH_ftk')
req = Request.blank('/v1/AUTH_ac', headers={'X-Auth-Token': token})
# again, no memcache!
resp = req.get_response(test_auth)
self.assertEqual(resp.status_int, 200)
# key rotation time
test_auth = auth.filter_factory({
'user_ac_user': 'testing .admin',
'fernet_key_2024': 'esipv1wC03xLGPb3cydid0uPINl6g8sydhlPh6iwJxk=',
'fernet_key_2025': 'gRXHeKlt5h1nMDZL_QA7UfVIJ5z3ZP3v351cvmiRZD4=',
'active_fernet_key_id': '2025',
})(swift)
# old token still good
req = Request.blank('/v1/AUTH_ac', headers={'X-Auth-Token': token})
resp = req.get_response(test_auth)
self.assertEqual(resp.status_int, 200)
req = Request.blank(
'/auth/v1.0',
headers={'X-Auth-User': 'ac:user', 'X-Auth-Key': 'testing'})
resp = req.get_response(test_auth)
self.assertEqual(resp.status_int, 200)
new_token = resp.headers['X-Auth-Token']
self.assertEqual(new_token[:8], 'AUTH_ftk')
# drop old key
test_auth = auth.filter_factory({
'user_ac_user': 'testing .admin',
'fernet_key_2025': 'gRXHeKlt5h1nMDZL_QA7UfVIJ5z3ZP3v351cvmiRZD4=',
'active_fernet_key_id': '2025',
})(swift)
# old token now bad
req = Request.blank('/v1/AUTH_ac', headers={'X-Auth-Token': token})
resp = req.get_response(test_auth)
self.assertEqual(resp.status_int, 401)
# new token still good
req = Request.blank('/v1/AUTH_ac', headers={'X-Auth-Token': new_token})
resp = req.get_response(test_auth)
self.assertEqual(resp.status_int, 200)
def test_compressed_fernet_token_no_memcache(self):
swift = FakeSwift()
swift.register('GET', '/v1/AUTH_ac', HTTPOk, {})
test_auth = auth.filter_factory({
'user_ac_user': 'testing .admin ' + ' '.join(
'similar-group-name-%d' % i for i in range(20)),
'fernet_key_2024': 'esipv1wC03xLGPb3cydid0uPINl6g8sydhlPh6iwJxk=',
'active_fernet_key_id': '2024',
})(swift)
req = Request.blank(
'/auth/v1.0',
headers={'X-Auth-User': 'ac:user', 'X-Auth-Key': 'testing'})
resp = req.get_response(test_auth)
self.assertEqual(resp.status_int, 200)
token = resp.headers['X-Auth-Token']
self.assertEqual(token[:9], 'AUTH_zftk')
# token's good
req = Request.blank('/v1/AUTH_ac', headers={'X-Auth-Token': token})
resp = req.get_response(test_auth)
self.assertEqual(resp.status_int, 200)
def test_object_name_containing_slash(self): def test_object_name_containing_slash(self):
test_auth = auth.filter_factory({'user_acct_user': 'testing'})( test_auth = auth.filter_factory({'user_acct_user': 'testing'})(
FakeApp(iter(NO_CONTENT_RESP * 1))) FakeApp(iter(NO_CONTENT_RESP * 1)))
req = self._make_request('/v1/AUTH_acct/cont/obj/name/with/slash', req = self._make_request('/v1/AUTH_acct/cont/obj/name/with/slash',
headers={'X-Auth-Token': 'AUTH_t'}) headers={'X-Auth-Token': 'AUTH_tk'})
cache_key = 'AUTH_/token/AUTH_t' cache_key = 'AUTH_/token/AUTH_tk'
cache_entry = (time() + 3600, 'AUTH_acct') cache_entry = (time() + 3600, 'AUTH_acct')
req.environ['swift.cache'].set(cache_key, cache_entry) req.environ['swift.cache'].set(cache_key, cache_entry)
resp = req.get_response(test_auth) resp = req.get_response(test_auth)
@@ -1284,7 +1369,7 @@ class TestAccountAcls(unittest.TestCase):
def _make_request(self, path, **kwargs): def _make_request(self, path, **kwargs):
# Our TestAccountAcls default request will have a valid auth token # Our TestAccountAcls default request will have a valid auth token
version, acct, _ = split_path(path, 1, 3, True) version, acct, _ = split_path(path, 1, 3, True)
headers = kwargs.pop('headers', {'X-Auth-Token': 'AUTH_t'}) headers = kwargs.pop('headers', {'X-Auth-Token': 'AUTH_tk'})
user_groups = kwargs.pop('user_groups', 'AUTH_firstacct') user_groups = kwargs.pop('user_groups', 'AUTH_firstacct')
# The account being accessed will have account ACLs # The account being accessed will have account ACLs
@@ -1298,7 +1383,7 @@ class TestAccountAcls(unittest.TestCase):
# Authorize the token by populating the request's cache # Authorize the token by populating the request's cache
req.environ['swift.cache'] = FakeMemcache() req.environ['swift.cache'] = FakeMemcache()
cache_key = 'AUTH_/token/AUTH_t' cache_key = 'AUTH_/token/AUTH_tk'
cache_entry = (time() + 3600, user_groups) cache_entry = (time() + 3600, user_groups)
req.environ['swift.cache'].set(cache_key, cache_entry) req.environ['swift.cache'].set(cache_key, cache_entry)
@@ -1451,7 +1536,7 @@ class TestAccountAcls(unittest.TestCase):
FakeApp(iter(NO_CONTENT_RESP * 5))) FakeApp(iter(NO_CONTENT_RESP * 5)))
user_groups = test_auth._get_user_groups('admin', 'admin:user', user_groups = test_auth._get_user_groups('admin', 'admin:user',
'AUTH_admin') 'AUTH_admin')
good_headers = {'X-Auth-Token': 'AUTH_t'} good_headers = {'X-Auth-Token': 'AUTH_tk'}
good_acl = json.dumps({"read-only": [u"á", "b"]}) good_acl = json.dumps({"read-only": [u"á", "b"]})
bad_list_types = '{"read-only": ["a", 99]}' bad_list_types = '{"read-only": ["a", 99]}'
bad_acl = 'syntactically invalid acl -- this does not parse as JSON' bad_acl = 'syntactically invalid acl -- this does not parse as JSON'
@@ -1546,7 +1631,7 @@ class TestAccountAcls(unittest.TestCase):
sysmeta_hdr = 'x-account-sysmeta-core-access-control' sysmeta_hdr = 'x-account-sysmeta-core-access-control'
target = '/v1/AUTH_firstacct' target = '/v1/AUTH_firstacct'
good_headers = {'X-Auth-Token': 'AUTH_t'} good_headers = {'X-Auth-Token': 'AUTH_tk'}
good_acl = '{"read-only":["a","b"]}' good_acl = '{"read-only":["a","b"]}'
# no acls -- no problem! # no acls -- no problem!
@@ -1567,7 +1652,7 @@ class TestAccountAcls(unittest.TestCase):
FakeApp(iter(NO_CONTENT_RESP * 3))) FakeApp(iter(NO_CONTENT_RESP * 3)))
target = '/v1/AUTH_firstacct' target = '/v1/AUTH_firstacct'
good_headers = {'X-Auth-Token': 'AUTH_t'} good_headers = {'X-Auth-Token': 'AUTH_tk'}
bad_acls = ( bad_acls = (
'syntax error', 'syntax error',
'{"bad_key":"should_fail"}', '{"bad_key":"should_fail"}',
@@ -1824,9 +1909,9 @@ class TestTokenHandling(unittest.TestCase):
self.req = Request.blank(path, headers=headers) self.req = Request.blank(path, headers=headers)
self.req.method = method self.req.method = method
self.req.environ['swift.cache'] = FakeMemcache() self.req.environ['swift.cache'] = FakeMemcache()
self._setup_user_and_token('AUTH_t', 'acct', 'acct:joe', self._setup_user_and_token('AUTH_tk', 'acct', 'acct:joe',
'.admin') '.admin')
self._setup_user_and_token('AUTH_s', 'admin', 'admin:glance', self._setup_user_and_token('AUTH_tks', 'admin', 'admin:glance',
'.service') '.service')
resp = self.req.get_response(self.test_auth) resp = self.req.get_response(self.test_auth)
return resp return resp
@@ -1852,21 +1937,21 @@ class TestTokenHandling(unittest.TestCase):
def test_tokens_set_remote_user(self): def test_tokens_set_remote_user(self):
conf = {} # Default conf conf = {} # Default conf
resp = self._make_request(conf, '/v1/AUTH_acct', resp = self._make_request(conf, '/v1/AUTH_acct',
{'x-auth-token': 'AUTH_t'}) {'x-auth-token': 'AUTH_tk'})
self.assertEqual(self.req.environ['REMOTE_USER'], self.assertEqual(self.req.environ['REMOTE_USER'],
'acct,acct:joe,AUTH_acct') 'acct,acct:joe,AUTH_acct')
self.assertEqual(resp.status_int, 200) self.assertEqual(resp.status_int, 200)
# Add x-service-token # Add x-service-token
resp = self._make_request(conf, '/v1/AUTH_acct', resp = self._make_request(conf, '/v1/AUTH_acct',
{'x-auth-token': 'AUTH_t', {'x-auth-token': 'AUTH_tk',
'x-service-token': 'AUTH_s'}) 'x-service-token': 'AUTH_tks'})
self.assertEqual(self.req.environ['REMOTE_USER'], self.assertEqual(self.req.environ['REMOTE_USER'],
'acct,acct:joe,AUTH_acct,admin,admin:glance,.service') 'acct,acct:joe,AUTH_acct,admin,admin:glance,.service')
self.assertEqual(resp.status_int, 200) self.assertEqual(resp.status_int, 200)
# Put x-auth-token value into x-service-token # Put x-auth-token value into x-service-token
resp = self._make_request(conf, '/v1/AUTH_acct', resp = self._make_request(conf, '/v1/AUTH_acct',
{'x-auth-token': 'AUTH_t', {'x-auth-token': 'AUTH_tk',
'x-service-token': 'AUTH_t'}) 'x-service-token': 'AUTH_tk'})
self.assertEqual(self.req.environ['REMOTE_USER'], self.assertEqual(self.req.environ['REMOTE_USER'],
'acct,acct:joe,AUTH_acct,acct,acct:joe,AUTH_acct') 'acct,acct:joe,AUTH_acct,acct,acct:joe,AUTH_acct')
self.assertEqual(resp.status_int, 200) self.assertEqual(resp.status_int, 200)
@@ -1875,15 +1960,15 @@ class TestTokenHandling(unittest.TestCase):
conf = {'reseller_prefix': 'AUTH, PRE2', conf = {'reseller_prefix': 'AUTH, PRE2',
'PRE2_require_group': '.service'} 'PRE2_require_group': '.service'}
resp = self._make_request(conf, '/v1/PRE2_acct', resp = self._make_request(conf, '/v1/PRE2_acct',
{'x-auth-token': 'AUTH_t', {'x-auth-token': 'AUTH_tk',
'x-service-token': 'AUTH_s'}) 'x-service-token': 'AUTH_tks'})
self.assertEqual(resp.status_int, 200) self.assertEqual(resp.status_int, 200)
def test_service_token_omitted(self): def test_service_token_omitted(self):
conf = {'reseller_prefix': 'AUTH, PRE2', conf = {'reseller_prefix': 'AUTH, PRE2',
'PRE2_require_group': '.service'} 'PRE2_require_group': '.service'}
resp = self._make_request(conf, '/v1/PRE2_acct', resp = self._make_request(conf, '/v1/PRE2_acct',
{'x-auth-token': 'AUTH_t'}) {'x-auth-token': 'AUTH_tk'})
self.assertEqual(resp.status_int, 403) self.assertEqual(resp.status_int, 403)
def test_invalid_tokens(self): def test_invalid_tokens(self):
@@ -1893,12 +1978,12 @@ class TestTokenHandling(unittest.TestCase):
{'x-auth-token': 'AUTH_junk'}) {'x-auth-token': 'AUTH_junk'})
self.assertEqual(resp.status_int, 401) self.assertEqual(resp.status_int, 401)
resp = self._make_request(conf, '/v1/PRE2_acct', resp = self._make_request(conf, '/v1/PRE2_acct',
{'x-auth-token': 'AUTH_t', {'x-auth-token': 'AUTH_tk',
'x-service-token': 'AUTH_junk'}) 'x-service-token': 'AUTH_junk'})
self.assertEqual(resp.status_int, 403) self.assertEqual(resp.status_int, 403)
resp = self._make_request(conf, '/v1/PRE2_acct', resp = self._make_request(conf, '/v1/PRE2_acct',
{'x-auth-token': 'AUTH_junk', {'x-auth-token': 'AUTH_junk',
'x-service-token': 'AUTH_s'}) 'x-service-token': 'AUTH_tks'})
self.assertEqual(resp.status_int, 401) self.assertEqual(resp.status_int, 401)