"""Kazoo Security"""
from base64 import b64encode
from collections import namedtuple
import hashlib
# Represents a Zookeeper ID and ACL object
Id = namedtuple("Id", "scheme id")
[docs]
class ACL(namedtuple("ACL", "perms id")):
"""An ACL for a Zookeeper Node
An ACL object is created by using an :class:`Id` object along with
a :class:`Permissions` setting. For convenience,
:meth:`make_digest_acl` should be used to create an ACL object with
the desired scheme, id, and permissions.
"""
@property
def acl_list(self):
perms = []
if self.perms & Permissions.ALL == Permissions.ALL:
perms.append("ALL")
return perms
if self.perms & Permissions.READ == Permissions.READ:
perms.append("READ")
if self.perms & Permissions.WRITE == Permissions.WRITE:
perms.append("WRITE")
if self.perms & Permissions.CREATE == Permissions.CREATE:
perms.append("CREATE")
if self.perms & Permissions.DELETE == Permissions.DELETE:
perms.append("DELETE")
if self.perms & Permissions.ADMIN == Permissions.ADMIN:
perms.append("ADMIN")
return perms
def __repr__(self):
return "ACL(perms=%r, acl_list=%s, id=%r)" % (
self.perms,
self.acl_list,
self.id,
)
class Permissions(object):
READ = 1
WRITE = 2
CREATE = 4
DELETE = 8
ADMIN = 16
ALL = 31
# Shortcuts for common Ids
ANYONE_ID_UNSAFE = Id("world", "anyone")
AUTH_IDS = Id("auth", "")
# Shortcuts for common ACLs
OPEN_ACL_UNSAFE = [ACL(Permissions.ALL, ANYONE_ID_UNSAFE)]
CREATOR_ALL_ACL = [ACL(Permissions.ALL, AUTH_IDS)]
READ_ACL_UNSAFE = [ACL(Permissions.READ, ANYONE_ID_UNSAFE)]
[docs]
def make_digest_acl_credential(username, password):
"""Create a SHA1 digest credential.
.. note::
This function uses UTF-8 to encode non-ASCII codepoints,
whereas ZooKeeper uses the "default locale" for decoding. It
may be a good idea to start the JVM with `-Dfile.encoding=UTF-8`
in non-UTF-8 locales.
See: https://github.com/python-zk/kazoo/pull/584
"""
credential = username.encode("utf-8") + b":" + password.encode("utf-8")
cred_hash = b64encode(hashlib.sha1(credential).digest()).strip()
return username + ":" + cred_hash.decode("utf-8")
[docs]
def make_acl(
scheme,
credential,
read=False,
write=False,
create=False,
delete=False,
admin=False,
all=False,
):
"""Given a scheme and credential, return an :class:`ACL` object
appropriate for use with Kazoo.
:param scheme: The scheme to use. I.e. `digest`.
:param credential:
A colon separated username, password. The password should be
hashed with the `scheme` specified. The
:meth:`make_digest_acl_credential` method will create and
return a credential appropriate for use with the `digest`
scheme.
:param write: Write permission.
:type write: bool
:param create: Create permission.
:type create: bool
:param delete: Delete permission.
:type delete: bool
:param admin: Admin permission.
:type admin: bool
:param all: All permissions.
:type all: bool
:rtype: :class:`ACL`
"""
if all:
permissions = Permissions.ALL
else:
permissions = 0
if read:
permissions |= Permissions.READ
if write:
permissions |= Permissions.WRITE
if create:
permissions |= Permissions.CREATE
if delete:
permissions |= Permissions.DELETE
if admin:
permissions |= Permissions.ADMIN
return ACL(permissions, Id(scheme, credential))
[docs]
def make_digest_acl(
username,
password,
read=False,
write=False,
create=False,
delete=False,
admin=False,
all=False,
):
"""Create a digest ACL for Zookeeper with the given permissions
This method combines :meth:`make_digest_acl_credential` and
:meth:`make_acl` to create an :class:`ACL` object appropriate for
use with Kazoo's ACL methods.
:param username: Username to use for the ACL.
:param password: A plain-text password to hash.
:param write: Write permission.
:type write: bool
:param create: Create permission.
:type create: bool
:param delete: Delete permission.
:type delete: bool
:param admin: Admin permission.
:type admin: bool
:param all: All permissions.
:type all: bool
:rtype: :class:`ACL`
"""
cred = make_digest_acl_credential(username, password)
return make_acl(
"digest",
cred,
read=read,
write=write,
create=create,
delete=delete,
admin=admin,
all=all,
)