swift/swift/common/middleware/read_only.py
Matthew Oliver 589ac355f3 Move *_swift_info functions into a new registry module
The *_swift_info functions use in module global dicts to provide a
registry mechanism for registering and getting swift info.

This is an abnormal pattern and doesn't quite fit into utils. Further
we looking at following this pattern for sensitive info to trim in the
future.
So this patch does some house cleaning and moves this registry to a new
module swift.common.registry. And updates all the references to it.

For backwards compat we still import the *_swift_info methods into utils
for any 3rd party tools or middleware.

Change-Id: I71fd7f50d1aafc001d6905438f42de4e58af8421
2022-02-03 14:41:13 +00:00

124 lines
4.4 KiB
Python

# Copyright (c) 2010-2015 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from swift.common.constraints import check_account_format
from swift.common.swob import HTTPMethodNotAllowed, Request
from swift.common.utils import get_logger, config_true_value
from swift.common.registry import register_swift_info
from swift.proxy.controllers.base import get_info
"""
=========
Read Only
=========
The ability to make an entire cluster or individual accounts read only is
implemented as pluggable middleware. When a cluster or an account is in read
only mode, requests that would result in writes to the cluser are not allowed.
A 405 is returned on such requests. "COPY", "DELETE", "POST", and
"PUT" are the HTTP methods that are considered writes.
-------------
Configuration
-------------
All configuration is optional.
============= ======= ====================================================
Option Default Description
------------- ------- ----------------------------------------------------
read_only false Set to 'true' to put the entire cluster in read only
mode.
allow_deletes false Set to 'true' to allow deletes.
============= ======= ====================================================
---------------------------
Marking Individual Accounts
---------------------------
If a system administrator wants to mark individual accounts as read only,
he/she can set X-Account-Sysmeta-Read-Only on an account to 'true'.
If a system administrator wants to allow writes to individual accounts,
when a cluster is in read only mode, he/she can set
X-Account-Sysmeta-Read-Only on an account to 'false'.
This header will be hidden from the user, because of the gatekeeper middleware,
and can only be set using a direct client to the account nodes.
"""
class ReadOnlyMiddleware(object):
"""
Middleware that make an entire cluster or individual accounts read only.
"""
def __init__(self, app, conf, logger=None):
self.app = app
self.logger = logger or get_logger(conf, log_route='read_only')
self.read_only = config_true_value(conf.get('read_only'))
self.write_methods = {'COPY', 'POST', 'PUT'}
if not config_true_value(conf.get('allow_deletes')):
self.write_methods.add('DELETE')
def __call__(self, env, start_response):
req = Request(env)
if req.method not in self.write_methods:
return self.app(env, start_response)
try:
version, account, container, obj = req.split_path(1, 4, True)
except ValueError:
return self.app(env, start_response)
if req.method == 'COPY' and 'Destination-Account' in req.headers:
dest_account = req.headers.get('Destination-Account')
account = check_account_format(req, dest_account)
if self.account_read_only(req, account):
msg = 'Writes are disabled for this account.'
return HTTPMethodNotAllowed(body=msg)(env, start_response)
return self.app(env, start_response)
def account_read_only(self, req, account):
"""
Check whether an account should be read-only.
This considers both the cluster-wide config value as well as the
per-account override in X-Account-Sysmeta-Read-Only.
"""
info = get_info(self.app, req.environ, account, swift_source='RO')
read_only = info.get('sysmeta', {}).get('read-only', '')
if not read_only:
return self.read_only
return config_true_value(read_only)
def filter_factory(global_conf, **local_conf):
"""
paste.deploy app factory for creating WSGI proxy apps.
"""
conf = global_conf.copy()
conf.update(local_conf)
if config_true_value(conf.get('read_only')):
register_swift_info('read_only')
def read_only_filter(app):
return ReadOnlyMiddleware(app, conf)
return read_only_filter