Restrict hosts that can be targets/sources of container syncing

This commit is contained in:
gholt 2011-02-24 10:50:00 -08:00
parent 305e4b41f5
commit adb45bc871
8 changed files with 78 additions and 21 deletions

View File

@ -7,6 +7,9 @@
# swift_dir = /etc/swift
# devices = /srv/node
# mount_check = true
# This is a comma separated list of hosts allowed in the X-Container-Sync-To
# field for containers.
# allowed_sync_hosts = 127.0.0.1
# You can specify default log routing here if you want:
# log_name = swift
# log_facility = LOG_LOCAL0

View File

@ -63,6 +63,9 @@ use = egg:swift#auth
# ssl = false
# prefix = /
# node_timeout = 10
# This is a comma separated list of hosts allowed to send X-Container-Sync-Key
# requests.
# allowed_sync_hosts = 127.0.0.1
# Only needed for Swauth
[filter:swauth]
@ -91,6 +94,9 @@ use = egg:swift#swauth
# default_swift_cluster = local#https://public.com:8080/v1#http://private.com:8080/v1
# token_life = 86400
# node_timeout = 10
# This is a comma separated list of hosts allowed to send X-Container-Sync-Key
# requests.
# allowed_sync_hosts = 127.0.0.1
# Highly recommended to change this.
super_admin_key = swauthkey

View File

@ -37,6 +37,9 @@ class DevAuth(object):
self.ssl = conf.get('ssl', 'false').lower() in TRUE_VALUES
self.auth_prefix = conf.get('prefix', '/')
self.timeout = int(conf.get('node_timeout', 10))
self.allowed_sync_hosts = [h.strip()
for h in conf.get('allowed_sync_hosts', '127.0.0.1').split(',')
if h.strip()]
def __call__(self, env, start_response):
"""
@ -184,12 +187,11 @@ class DevAuth(object):
# account DELETE or PUT...
req.environ['swift_owner'] = True
return None
# TODO: Restrict this further to only authenticated folks in the .sync
# group. Currently, anybody with the x-container-sync-key can do a
# sync.
if 'swift_sync_key' in req.environ and \
req.environ['swift_sync_key'] == \
req.headers.get('x-container-sync-key', None):
if ('swift_sync_key' in req.environ and
req.environ['swift_sync_key'] ==
req.headers.get('x-container-sync-key', None) and
(req.remote_addr in self.allowed_sync_hosts or
get_remote_client(req) in self.allowed_sync_hosts)):
return None
referrers, groups = parse_acl(getattr(req, 'acl', None))
if referrer_allowed(req.referer, referrers):

View File

@ -101,6 +101,9 @@ class Swauth(object):
self.timeout = int(conf.get('node_timeout', 10))
self.itoken = None
self.itoken_expires = None
self.allowed_sync_hosts = [h.strip()
for h in conf.get('allowed_sync_hosts', '127.0.0.1').split(',')
if h.strip()]
def __call__(self, env, start_response):
"""
@ -277,12 +280,11 @@ class Swauth(object):
# account DELETE or PUT...
req.environ['swift_owner'] = True
return None
# TODO: Restrict this further to only authenticated folks in the .sync
# group. Currently, anybody with the x-container-sync-key can do a
# sync.
if 'swift_sync_key' in req.environ and \
req.environ['swift_sync_key'] == \
req.headers.get('x-container-sync-key', None):
if ('swift_sync_key' in req.environ and
req.environ['swift_sync_key'] ==
req.headers.get('x-container-sync-key', None) and
(req.remote_addr in self.allowed_sync_hosts or
get_remote_client(req) in self.allowed_sync_hosts)):
return None
referrers, groups = parse_acl(getattr(req, 'acl', None))
if referrer_allowed(req.referer, referrers):

View File

@ -952,3 +952,26 @@ def urlparse(url):
:param url: URL to parse.
"""
return ModifiedParseResult(*stdlib_urlparse(url))
def validate_sync_to(value, allowed_sync_hosts):
p = urlparse(value)
if p.scheme not in ('http', 'https'):
return _('Invalid scheme %r in X-Container-Sync-To, must be "http" '
'or "https".') % p.scheme
if not p.path:
return _('Path required in X-Container-Sync-To')
if p.params or p.query or p.fragment:
return _('Params, queries, and fragments not allowed in '
'X-Container-Sync-To')
if p.hostname not in allowed_sync_hosts:
return _('Invalid host %r in X-Container-Sync-To') % p.hostname
def get_remote_client(req):
# remote host for zeus
client = req.headers.get('x-cluster-client-ip')
if not client and 'x-forwarded-for' in req.headers:
# remote host for other lbs
client = req.headers['x-forwarded-for'].split(',')[0].strip()
return client

View File

@ -32,7 +32,8 @@ from webob.exc import HTTPAccepted, HTTPBadRequest, HTTPConflict, \
from swift.common.db import ContainerBroker
from swift.common.utils import get_logger, get_param, hash_path, \
normalize_timestamp, storage_directory, split_path
normalize_timestamp, storage_directory, split_path, urlparse, \
validate_sync_to
from swift.common.constraints import CONTAINER_LISTING_LIMIT, \
check_mount, check_float, check_utf8
from swift.common.bufferedhttp import http_connect
@ -56,6 +57,9 @@ class ContainerController(object):
('true', 't', '1', 'on', 'yes', 'y')
self.node_timeout = int(conf.get('node_timeout', 3))
self.conn_timeout = float(conf.get('conn_timeout', 0.5))
self.allowed_sync_hosts = [h.strip()
for h in conf.get('allowed_sync_hosts', '127.0.0.1').split(',')
if h.strip()]
self.replicator_rpc = ReplicatorRpc(self.root, DATADIR,
ContainerBroker, self.mount_check)
@ -175,6 +179,11 @@ class ContainerController(object):
not check_float(req.headers['x-timestamp']):
return HTTPBadRequest(body='Missing timestamp', request=req,
content_type='text/plain')
if 'x-container-sync-to' in req.headers:
err = validate_sync_to(req.headers['x-container-sync-to'],
self.allowed_sync_hosts)
if err:
return HTTPBadRequest(err)
if self.mount_check and not check_mount(self.root, drive):
return Response(status='507 %s is not mounted' % drive)
timestamp = normalize_timestamp(req.headers['x-timestamp'])
@ -370,6 +379,11 @@ class ContainerController(object):
not check_float(req.headers['x-timestamp']):
return HTTPBadRequest(body='Missing or bad timestamp',
request=req, content_type='text/plain')
if 'x-container-sync-to' in req.headers:
err = validate_sync_to(req.headers['x-container-sync-to'],
self.allowed_sync_hosts)
if err:
return HTTPBadRequest(err)
if self.mount_check and not check_mount(self.root, drive):
return Response(status='507 %s is not mounted' % drive)
broker = self._get_container_broker(drive, part, account, container)

View File

@ -22,7 +22,7 @@ from swift.common import client, direct_client
from swift.common.ring import Ring
from swift.common.db import ContainerBroker
from swift.common.utils import audit_location_generator, get_logger, \
normalize_timestamp, TRUE_VALUES
normalize_timestamp, TRUE_VALUES, validate_sync_to
from swift.common.daemon import Daemon
@ -59,13 +59,16 @@ class ContainerSync(Daemon):
conf.get('mount_check', 'true').lower() in TRUE_VALUES
self.interval = int(conf.get('interval', 300))
self.container_time = int(conf.get('container_time', 60))
swift_dir = conf.get('swift_dir', '/etc/swift')
self.allowed_sync_hosts = [h.strip()
for h in conf.get('allowed_sync_hosts', '127.0.0.1').split(',')
if h.strip()]
self.container_syncs = 0
self.container_deletes = 0
self.container_puts = 0
self.container_skips = 0
self.container_failures = 0
self.reported = time.time()
swift_dir = conf.get('swift_dir', '/etc/swift')
self.object_ring = object_ring or \
Ring(os.path.join(swift_dir, 'object.ring.gz'))
@ -151,6 +154,14 @@ class ContainerSync(Daemon):
self.container_skips += 1
return
sync_to = sync_to.rstrip('/')
err = validate_sync_to(sync_to, self.allowed_sync_hosts)
if err:
self.logger.info(
_('ERROR %(db_file)s: %(validate_sync_to_err)s'),
{'db_file': broker.db_file,
'validate_sync_to_err': err})
self.container_failures += 1
return
stop_at = time.time() + self.container_time
while time.time() < stop_at:
rows = broker.get_items_since(sync_row, 1)

View File

@ -42,7 +42,7 @@ from webob import Request, Response
from swift.common.ring import Ring
from swift.common.utils import get_logger, normalize_timestamp, split_path, \
cache_from_env
cache_from_env, get_remote_client
from swift.common.bufferedhttp import http_connect
from swift.common.constraints import check_metadata, check_object_creation, \
check_utf8, CONTAINER_LISTING_LIMIT, MAX_ACCOUNT_NAME_LENGTH, \
@ -1834,11 +1834,7 @@ class Application(BaseApplication):
the_request = quote(unquote(req.path))
if req.query_string:
the_request = the_request + '?' + req.query_string
# remote user for zeus
client = req.headers.get('x-cluster-client-ip')
if not client and 'x-forwarded-for' in req.headers:
# remote user for other lbs
client = req.headers['x-forwarded-for'].split(',')[0].strip()
client = get_remote_client(req)
logged_headers = None
if self.log_headers:
logged_headers = '\n'.join('%s: %s' % (k, v)