diff --git a/docs/ENVIRONMENT.rst b/docs/ENVIRONMENT.rst index 41613787..dd78d35e 100644 --- a/docs/ENVIRONMENT.rst +++ b/docs/ENVIRONMENT.rst @@ -84,6 +84,7 @@ ZooKeeper - **PATRONI\_ZOOKEEPER\_KEY**: (optional) File with the client key. - **PATRONI\_ZOOKEEPER\_KEY\_PASSWORD**: (optional) The client key password. - **PATRONI\_ZOOKEEPER\_VERIFY**: (optional) Whether to verify certificate or not. Defaults to ``true``. +- **PATRONI\_ZOOKEEPER\_SET\_ACLS**: (optional) If set, configure Kazoo to apply a default ACL to each ZNode that it creates. ACLs will assume 'x509' schema and should be specified as a dictionary with the principal as the key and one or more permissions as a list in the value. Permissions may be one of ``CREATE``, ``READ``, ``WRITE``, ``DELETE`` or ``ADMIN``. For example, ``set_acls: {CN=principal1: [CREATE, READ], CN=principal2: [ALL]}``. .. note:: It is required to install ``kazoo>=2.6.0`` to support SSL. diff --git a/docs/SETTINGS.rst b/docs/SETTINGS.rst index d304b04c..53227c8b 100644 --- a/docs/SETTINGS.rst +++ b/docs/SETTINGS.rst @@ -182,6 +182,7 @@ ZooKeeper - **key**: (optional) File with the client key. - **key_password**: (optional) The client key password. - **verify**: (optional) Whether to verify certificate or not. Defaults to ``true``. +- **set_acls**: (optional) If set, configure Kazoo to apply a default ACL to each ZNode that it creates. ACLs will assume 'x509' schema and should be specified as a dictionary with the principal as the key and one or more permissions as a list in the value. Permissions may be one of ``CREATE``, ``READ``, ``WRITE``, ``DELETE`` or ``ADMIN``. For example, ``set_acls: {CN=principal1: [CREATE, READ], CN=principal2: [ALL]}``. .. note:: It is required to install ``kazoo>=2.6.0`` to support SSL. diff --git a/patroni/config.py b/patroni/config.py index d265c511..2161a7c6 100644 --- a/patroni/config.py +++ b/patroni/config.py @@ -352,13 +352,13 @@ class Config(object): 'CACERT', 'CERT', 'KEY', 'VERIFY', 'TOKEN', 'CHECKS', 'DC', 'CONSISTENCY', 'REGISTER_SERVICE', 'SERVICE_CHECK_INTERVAL', 'NAMESPACE', 'CONTEXT', 'USE_ENDPOINTS', 'SCOPE_LABEL', 'ROLE_LABEL', 'POD_IP', 'PORTS', 'LABELS', - 'BYPASS_API_SERVICE', 'KEY_PASSWORD', 'USE_SSL') and name: + 'BYPASS_API_SERVICE', 'KEY_PASSWORD', 'USE_SSL', 'SET_ACLS') and name: value = os.environ.pop(param) if suffix == 'PORT': value = value and parse_int(value) elif suffix in ('HOSTS', 'PORTS', 'CHECKS'): value = value and _parse_list(value) - elif suffix == 'LABELS': + elif suffix in ('LABELS', 'SET_ACLS'): value = _parse_dict(value) elif suffix in ('USE_PROXIES', 'REGISTER_SERVICE', 'USE_ENDPOINTS', 'BYPASS_API_SERVICE', 'VERIFY'): value = parse_bool(value) diff --git a/patroni/dcs/zookeeper.py b/patroni/dcs/zookeeper.py index f3b9ac38..9cbff65e 100644 --- a/patroni/dcs/zookeeper.py +++ b/patroni/dcs/zookeeper.py @@ -7,6 +7,7 @@ from kazoo.client import KazooClient, KazooState, KazooRetry from kazoo.exceptions import NoNodeError, NodeExistsError, SessionExpiredError from kazoo.handlers.threading import SequentialThreadingHandler from kazoo.protocol.states import KeeperState +from kazoo.security import make_acl from . import AbstractDCS, ClusterConfig, Cluster, Failover, Leader, Member, SyncState, TimelineHistory from ..exceptions import DCSError @@ -83,6 +84,19 @@ class ZooKeeper(AbstractDCS): 'cert': 'certfile', 'key': 'keyfile', 'key_password': 'keyfile_password'} kwargs = {v: config[k] for k, v in mapping.items() if k in config} + if 'set_acls' in config: + kwargs['default_acl'] = [] + for principal, permissions in config['set_acls'].items(): + normalizedPermissions = [p.upper() for p in permissions] + kwargs['default_acl'].append(make_acl(scheme='x509', + credential=principal, + read='READ' in normalizedPermissions, + write='WRITE' in normalizedPermissions, + create='CREATE' in normalizedPermissions, + delete='DELETE' in normalizedPermissions, + admin='ADMIN' in normalizedPermissions, + all='ALL' in normalizedPermissions)) + self._client = PatroniKazooClient(hosts, handler=PatroniSequentialThreadingHandler(config['retry_timeout']), timeout=config['ttl'], connection_retry=KazooRetry(max_delay=1, max_tries=-1, sleep_func=time.sleep), command_retry=KazooRetry(max_delay=1, max_tries=-1,