Add support for Consul ACL token parameter.

When running Consul with ACLs enabled, the best practice is to
default deny all. In order to support applying a policy to the
tooz coordination KV store path, passing through an ACL is needed.

Closes-Bug: #1752205
Change-Id: I98fc96468b21368ce66365e3fc38c495b1f2918a
This commit is contained in:
JP Bourget 2020-04-08 11:19:34 -04:00
parent 977c7b9ff9
commit 08b2d93226
3 changed files with 37 additions and 10 deletions

View File

@ -0,0 +1,12 @@
---
features:
- |
Added support for Consul ACL tokens. Consul ACL tokens can be passed in as
a parameterized argument. Requires python-consul2
upgrade:
- |
Updated the python-consul library to python-consul2. The original library is
no longer maintained while python-consul2 is reasonably maintained. Consul 1.4
has a new ACL implementation, and Consul 1.7 includes some standards enforcement
which break python-consul.

View File

@ -44,7 +44,7 @@ tooz.backends =
[extras] [extras]
consul = consul =
python-consul>=0.4.7 # MIT License python-consul2>=0.0.16 # MIT License
etcd = etcd =
requests>=2.10.0 # Apache-2.0 requests>=2.10.0 # Apache-2.0
etcd3 = etcd3 =

View File

@ -27,11 +27,12 @@ from tooz import utils
class ConsulLock(locking.Lock): class ConsulLock(locking.Lock):
def __init__(self, name, node, address, session_id, client): def __init__(self, name, node, address, session_id, client, token=None):
super(ConsulLock, self).__init__(name) super(ConsulLock, self).__init__(name)
self._name = name self._name = name
self._node = node self._node = node
self._address = address self._address = address
self._acl_token = token
self._session_id = session_id self._session_id = session_id
self._client = client self._client = client
self.acquired = False self.acquired = False
@ -45,7 +46,8 @@ class ConsulLock(locking.Lock):
# Check if we are the owner and if we are simulate # Check if we are the owner and if we are simulate
# blocking (because consul will not block a second # blocking (because consul will not block a second
# acquisition attempt by the same owner). # acquisition attempt by the same owner).
_index, value = self._client.kv.get(key=self._name) _index, value = self._client.kv.get(key=self._name,
token=self._acl_token)
if value and value.get('Session') == self._session_id: if value and value.get('Session') == self._session_id:
if blocking is False: if blocking is False:
return False return False
@ -55,7 +57,8 @@ class ConsulLock(locking.Lock):
# The value can be anything. # The value can be anything.
gotten = self._client.kv.put(key=self._name, gotten = self._client.kv.put(key=self._name,
value=u"I got it!", value=u"I got it!",
acquire=self._session_id) acquire=self._session_id,
token=self._acl_token)
if gotten: if gotten:
self.acquired = True self.acquired = True
return True return True
@ -70,14 +73,16 @@ class ConsulLock(locking.Lock):
if not self.acquired: if not self.acquired:
return False return False
# Get the lock to verify the session ID's are same # Get the lock to verify the session ID's are same
_index, contents = self._client.kv.get(key=self._name) _index, contents = self._client.kv.get(key=self._name,
token=self._acl_token)
if not contents: if not contents:
return False return False
owner = contents.get('Session') owner = contents.get('Session')
if owner == self._session_id: if owner == self._session_id:
removed = self._client.kv.put(key=self._name, removed = self._client.kv.put(key=self._name,
value=self._session_id, value=self._session_id,
release=self._session_id) release=self._session_id,
token=self._acl_token)
if removed: if removed:
self.acquired = False self.acquired = False
return True return True
@ -103,6 +108,7 @@ class ConsulDriver(coordination.CoordinationDriver):
================== ======= ================== =======
ttl 15 ttl 15
namespace tooz namespace tooz
acl_token None
================== ======= ================== =======
For details on the available options, refer to For details on the available options, refer to
@ -121,6 +127,9 @@ class ConsulDriver(coordination.CoordinationDriver):
#: Default consul port if not provided. #: Default consul port if not provided.
DEFAULT_PORT = 8500 DEFAULT_PORT = 8500
#: Consul ACL Token if not provided
ACL_TOKEN = None
def __init__(self, member_id, parsed_url, options): def __init__(self, member_id, parsed_url, options):
super(ConsulDriver, self).__init__(member_id, parsed_url, options) super(ConsulDriver, self).__init__(member_id, parsed_url, options)
options = utils.collapse(options) options = utils.collapse(options)
@ -131,13 +140,15 @@ class ConsulDriver(coordination.CoordinationDriver):
self._ttl = int(options.get('ttl', self.DEFAULT_TTL)) self._ttl = int(options.get('ttl', self.DEFAULT_TTL))
namespace = options.get('namespace', self.TOOZ_NAMESPACE) namespace = options.get('namespace', self.TOOZ_NAMESPACE)
self._namespace = encodeutils.safe_decode(namespace) self._namespace = encodeutils.safe_decode(namespace)
self._acl_token = options.get('acl_token', self.ACL_TOKEN)
self._client = None self._client = None
def _start(self): def _start(self):
"""Create a client, register a node and create a session.""" """Create a client, register a node and create a session."""
# Create a consul client # Create a consul client
if self._client is None: if self._client is None:
self._client = consul.Consul(host=self._host, port=self._port) self._client = consul.Consul(host=self._host, port=self._port,
token=self._acl_token)
local_agent = self._client.agent.self() local_agent = self._client.agent.self()
self._node = local_agent['Member']['Name'] self._node = local_agent['Member']['Name']
@ -145,11 +156,15 @@ class ConsulDriver(coordination.CoordinationDriver):
# Register a Node # Register a Node
self._client.catalog.register(node=self._node, self._client.catalog.register(node=self._node,
address=self._address) address=self._address,
token=self._acl_token)
# Create a session # Create a session
self._session_id = self._client.session.create( self._session_id = self._client.session.create(
name=self._session_name, node=self._node, ttl=self._ttl) name=self._session_name,
node=self._node,
ttl=self._ttl,
token=self._acl_token)
def _stop(self): def _stop(self):
if self._client is not None: if self._client is not None:
@ -175,7 +190,7 @@ class ConsulDriver(coordination.CoordinationDriver):
real_name = self._paths_join(self._namespace, u"locks", name) real_name = self._paths_join(self._namespace, u"locks", name)
return ConsulLock(real_name, self._node, self._address, return ConsulLock(real_name, self._node, self._address,
session_id=self._session_id, session_id=self._session_id,
client=self._client) client=self._client, token=self._acl_token)
@staticmethod @staticmethod
def _paths_join(*args): def _paths_join(*args):