Restrict hosts that can be targets/sources of container syncing
This commit is contained in:
@@ -7,6 +7,9 @@
|
|||||||
# swift_dir = /etc/swift
|
# swift_dir = /etc/swift
|
||||||
# devices = /srv/node
|
# devices = /srv/node
|
||||||
# mount_check = true
|
# 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:
|
# You can specify default log routing here if you want:
|
||||||
# log_name = swift
|
# log_name = swift
|
||||||
# log_facility = LOG_LOCAL0
|
# log_facility = LOG_LOCAL0
|
||||||
|
@@ -63,6 +63,9 @@ use = egg:swift#auth
|
|||||||
# ssl = false
|
# ssl = false
|
||||||
# prefix = /
|
# prefix = /
|
||||||
# node_timeout = 10
|
# 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
|
# Only needed for Swauth
|
||||||
[filter: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
|
# default_swift_cluster = local#https://public.com:8080/v1#http://private.com:8080/v1
|
||||||
# token_life = 86400
|
# token_life = 86400
|
||||||
# node_timeout = 10
|
# 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.
|
# Highly recommended to change this.
|
||||||
super_admin_key = swauthkey
|
super_admin_key = swauthkey
|
||||||
|
|
||||||
|
@@ -37,6 +37,9 @@ class DevAuth(object):
|
|||||||
self.ssl = conf.get('ssl', 'false').lower() in TRUE_VALUES
|
self.ssl = conf.get('ssl', 'false').lower() in TRUE_VALUES
|
||||||
self.auth_prefix = conf.get('prefix', '/')
|
self.auth_prefix = conf.get('prefix', '/')
|
||||||
self.timeout = int(conf.get('node_timeout', 10))
|
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):
|
def __call__(self, env, start_response):
|
||||||
"""
|
"""
|
||||||
@@ -184,12 +187,11 @@ class DevAuth(object):
|
|||||||
# account DELETE or PUT...
|
# account DELETE or PUT...
|
||||||
req.environ['swift_owner'] = True
|
req.environ['swift_owner'] = True
|
||||||
return None
|
return None
|
||||||
# TODO: Restrict this further to only authenticated folks in the .sync
|
if ('swift_sync_key' in req.environ and
|
||||||
# group. Currently, anybody with the x-container-sync-key can do a
|
req.environ['swift_sync_key'] ==
|
||||||
# sync.
|
req.headers.get('x-container-sync-key', None) and
|
||||||
if 'swift_sync_key' in req.environ and \
|
(req.remote_addr in self.allowed_sync_hosts or
|
||||||
req.environ['swift_sync_key'] == \
|
get_remote_client(req) in self.allowed_sync_hosts)):
|
||||||
req.headers.get('x-container-sync-key', None):
|
|
||||||
return None
|
return None
|
||||||
referrers, groups = parse_acl(getattr(req, 'acl', None))
|
referrers, groups = parse_acl(getattr(req, 'acl', None))
|
||||||
if referrer_allowed(req.referer, referrers):
|
if referrer_allowed(req.referer, referrers):
|
||||||
|
@@ -101,6 +101,9 @@ class Swauth(object):
|
|||||||
self.timeout = int(conf.get('node_timeout', 10))
|
self.timeout = int(conf.get('node_timeout', 10))
|
||||||
self.itoken = None
|
self.itoken = None
|
||||||
self.itoken_expires = 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):
|
def __call__(self, env, start_response):
|
||||||
"""
|
"""
|
||||||
@@ -277,12 +280,11 @@ class Swauth(object):
|
|||||||
# account DELETE or PUT...
|
# account DELETE or PUT...
|
||||||
req.environ['swift_owner'] = True
|
req.environ['swift_owner'] = True
|
||||||
return None
|
return None
|
||||||
# TODO: Restrict this further to only authenticated folks in the .sync
|
if ('swift_sync_key' in req.environ and
|
||||||
# group. Currently, anybody with the x-container-sync-key can do a
|
req.environ['swift_sync_key'] ==
|
||||||
# sync.
|
req.headers.get('x-container-sync-key', None) and
|
||||||
if 'swift_sync_key' in req.environ and \
|
(req.remote_addr in self.allowed_sync_hosts or
|
||||||
req.environ['swift_sync_key'] == \
|
get_remote_client(req) in self.allowed_sync_hosts)):
|
||||||
req.headers.get('x-container-sync-key', None):
|
|
||||||
return None
|
return None
|
||||||
referrers, groups = parse_acl(getattr(req, 'acl', None))
|
referrers, groups = parse_acl(getattr(req, 'acl', None))
|
||||||
if referrer_allowed(req.referer, referrers):
|
if referrer_allowed(req.referer, referrers):
|
||||||
|
@@ -952,3 +952,26 @@ def urlparse(url):
|
|||||||
:param url: URL to parse.
|
:param url: URL to parse.
|
||||||
"""
|
"""
|
||||||
return ModifiedParseResult(*stdlib_urlparse(url))
|
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
|
||||||
|
@@ -32,7 +32,8 @@ from webob.exc import HTTPAccepted, HTTPBadRequest, HTTPConflict, \
|
|||||||
|
|
||||||
from swift.common.db import ContainerBroker
|
from swift.common.db import ContainerBroker
|
||||||
from swift.common.utils import get_logger, get_param, hash_path, \
|
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, \
|
from swift.common.constraints import CONTAINER_LISTING_LIMIT, \
|
||||||
check_mount, check_float, check_utf8
|
check_mount, check_float, check_utf8
|
||||||
from swift.common.bufferedhttp import http_connect
|
from swift.common.bufferedhttp import http_connect
|
||||||
@@ -56,6 +57,9 @@ class ContainerController(object):
|
|||||||
('true', 't', '1', 'on', 'yes', 'y')
|
('true', 't', '1', 'on', 'yes', 'y')
|
||||||
self.node_timeout = int(conf.get('node_timeout', 3))
|
self.node_timeout = int(conf.get('node_timeout', 3))
|
||||||
self.conn_timeout = float(conf.get('conn_timeout', 0.5))
|
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,
|
self.replicator_rpc = ReplicatorRpc(self.root, DATADIR,
|
||||||
ContainerBroker, self.mount_check)
|
ContainerBroker, self.mount_check)
|
||||||
|
|
||||||
@@ -175,6 +179,11 @@ class ContainerController(object):
|
|||||||
not check_float(req.headers['x-timestamp']):
|
not check_float(req.headers['x-timestamp']):
|
||||||
return HTTPBadRequest(body='Missing timestamp', request=req,
|
return HTTPBadRequest(body='Missing timestamp', request=req,
|
||||||
content_type='text/plain')
|
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):
|
if self.mount_check and not check_mount(self.root, drive):
|
||||||
return Response(status='507 %s is not mounted' % drive)
|
return Response(status='507 %s is not mounted' % drive)
|
||||||
timestamp = normalize_timestamp(req.headers['x-timestamp'])
|
timestamp = normalize_timestamp(req.headers['x-timestamp'])
|
||||||
@@ -370,6 +379,11 @@ class ContainerController(object):
|
|||||||
not check_float(req.headers['x-timestamp']):
|
not check_float(req.headers['x-timestamp']):
|
||||||
return HTTPBadRequest(body='Missing or bad timestamp',
|
return HTTPBadRequest(body='Missing or bad timestamp',
|
||||||
request=req, content_type='text/plain')
|
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):
|
if self.mount_check and not check_mount(self.root, drive):
|
||||||
return Response(status='507 %s is not mounted' % drive)
|
return Response(status='507 %s is not mounted' % drive)
|
||||||
broker = self._get_container_broker(drive, part, account, container)
|
broker = self._get_container_broker(drive, part, account, container)
|
||||||
|
@@ -22,7 +22,7 @@ from swift.common import client, direct_client
|
|||||||
from swift.common.ring import Ring
|
from swift.common.ring import Ring
|
||||||
from swift.common.db import ContainerBroker
|
from swift.common.db import ContainerBroker
|
||||||
from swift.common.utils import audit_location_generator, get_logger, \
|
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
|
from swift.common.daemon import Daemon
|
||||||
|
|
||||||
|
|
||||||
@@ -59,13 +59,16 @@ class ContainerSync(Daemon):
|
|||||||
conf.get('mount_check', 'true').lower() in TRUE_VALUES
|
conf.get('mount_check', 'true').lower() in TRUE_VALUES
|
||||||
self.interval = int(conf.get('interval', 300))
|
self.interval = int(conf.get('interval', 300))
|
||||||
self.container_time = int(conf.get('container_time', 60))
|
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_syncs = 0
|
||||||
self.container_deletes = 0
|
self.container_deletes = 0
|
||||||
self.container_puts = 0
|
self.container_puts = 0
|
||||||
self.container_skips = 0
|
self.container_skips = 0
|
||||||
self.container_failures = 0
|
self.container_failures = 0
|
||||||
self.reported = time.time()
|
self.reported = time.time()
|
||||||
|
swift_dir = conf.get('swift_dir', '/etc/swift')
|
||||||
self.object_ring = object_ring or \
|
self.object_ring = object_ring or \
|
||||||
Ring(os.path.join(swift_dir, 'object.ring.gz'))
|
Ring(os.path.join(swift_dir, 'object.ring.gz'))
|
||||||
|
|
||||||
@@ -151,6 +154,14 @@ class ContainerSync(Daemon):
|
|||||||
self.container_skips += 1
|
self.container_skips += 1
|
||||||
return
|
return
|
||||||
sync_to = sync_to.rstrip('/')
|
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
|
stop_at = time.time() + self.container_time
|
||||||
while time.time() < stop_at:
|
while time.time() < stop_at:
|
||||||
rows = broker.get_items_since(sync_row, 1)
|
rows = broker.get_items_since(sync_row, 1)
|
||||||
|
@@ -42,7 +42,7 @@ from webob import Request, Response
|
|||||||
|
|
||||||
from swift.common.ring import Ring
|
from swift.common.ring import Ring
|
||||||
from swift.common.utils import get_logger, normalize_timestamp, split_path, \
|
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.bufferedhttp import http_connect
|
||||||
from swift.common.constraints import check_metadata, check_object_creation, \
|
from swift.common.constraints import check_metadata, check_object_creation, \
|
||||||
check_utf8, CONTAINER_LISTING_LIMIT, MAX_ACCOUNT_NAME_LENGTH, \
|
check_utf8, CONTAINER_LISTING_LIMIT, MAX_ACCOUNT_NAME_LENGTH, \
|
||||||
@@ -1834,11 +1834,7 @@ class Application(BaseApplication):
|
|||||||
the_request = quote(unquote(req.path))
|
the_request = quote(unquote(req.path))
|
||||||
if req.query_string:
|
if req.query_string:
|
||||||
the_request = the_request + '?' + req.query_string
|
the_request = the_request + '?' + req.query_string
|
||||||
# remote user for zeus
|
client = get_remote_client(req)
|
||||||
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()
|
|
||||||
logged_headers = None
|
logged_headers = None
|
||||||
if self.log_headers:
|
if self.log_headers:
|
||||||
logged_headers = '\n'.join('%s: %s' % (k, v)
|
logged_headers = '\n'.join('%s: %s' % (k, v)
|
||||||
|
Reference in New Issue
Block a user