/usr/lib/python2.7/dist-packages/etcd/auth.py is in python-etcd 0.4.3-2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 | import json
import logging
import etcd
_log = logging.getLogger(__name__)
class EtcdAuthBase(object):
entity = 'example'
def __init__(self, client, name):
self.client = client
self.name = name
self.uri = "{}/auth/{}s/{}".format(self.client.version_prefix,
self.entity, self.name)
@property
def names(self):
key = "{}s".format(self.entity)
uri = "{}/auth/{}".format(self.client.version_prefix, key)
response = self.client.api_execute(uri, self.client._MGET)
return json.loads(response.data.decode('utf-8'))[key]
def read(self):
try:
response = self.client.api_execute(self.uri, self.client._MGET)
except etcd.EtcdInsufficientPermissions as e:
_log.error("Any action on the authorization requires the root role")
raise
except etcd.EtcdKeyNotFound:
_log.info("%s '%s' not found", self.entity, self.name)
raise
except Exception as e:
_log.error("Failed to fetch %s in %s%s: %r",
self.entity, self.client._base_uri,
self.client.version_prefix, e)
raise etcd.EtcdException(
"Could not fetch {} '{}'".format(self.entity, self.name))
self._from_net(response.data)
def write(self):
try:
r = self.__class__(self.client, self.name)
r.read()
except etcd.EtcdKeyNotFound:
r = None
try:
for payload in self._to_net(r):
response = self.client.api_execute_json(self.uri,
self.client._MPUT,
params=payload)
# This will fail if the response is an error
self._from_net(response.data)
except etcd.EtcdInsufficientPermissions as e:
_log.error("Any action on the authorization requires the root role")
raise
except Exception as e:
_log.error("Failed to write %s '%s'", self.entity, self.name)
# TODO: fine-grained exception handling
raise etcd.EtcdException(
"Could not write {} '{}': {}".format(self.entity,
self.name, e))
def delete(self):
try:
_ = self.client.api_execute(self.uri, self.client._MDELETE)
except etcd.EtcdInsufficientPermissions as e:
_log.error("Any action on the authorization requires the root role")
raise
except etcd.EtcdKeyNotFound:
_log.info("%s '%s' not found", self.entity, self.name)
raise
except Exception as e:
_log.error("Failed to delete %s in %s%s: %r",
self.entity, self._base_uri, self.version_prefix, e)
raise etcd.EtcdException(
"Could not delete {} '{}'".format(self.entity, self.name))
def _from_net(self, data):
raise NotImplementedError()
def _to_net(self, old=None):
raise NotImplementedError()
@classmethod
def new(cls, client, data):
c = cls(client, data[cls.entity])
c._from_net(data)
return c
class EtcdUser(EtcdAuthBase):
"""Class to manage in a orm-like way etcd users"""
entity = 'user'
def __init__(self, client, name):
super(EtcdUser, self).__init__(client, name)
self._roles = set()
self._password = None
def _from_net(self, data):
d = json.loads(data.decode('utf-8'))
self.roles = d.get('roles', [])
self.name = d.get('user')
def _to_net(self, prevobj=None):
if prevobj is None:
retval = [{"user": self.name, "password": self._password,
"roles": list(self.roles)}]
else:
retval = []
if self._password:
retval.append({"user": self.name, "password": self._password})
to_grant = list(self.roles - prevobj.roles)
to_revoke = list(prevobj.roles - self.roles)
if to_grant:
retval.append({"user": self.name, "grant": to_grant})
if to_revoke:
retval.append({"user": self.name, "revoke": to_revoke})
# Let's blank the password now
# Even if the user can't be written we don't want it to leak anymore.
self._password = None
return retval
@property
def roles(self):
return self._roles
@roles.setter
def roles(self, val):
self._roles = set(val)
@property
def password(self):
"""Empty property for password."""
return None
@password.setter
def password(self, new_password):
"""Change user's password."""
self._password = new_password
def __str__(self):
return json.dumps(self._to_net()[0])
class EtcdRole(EtcdAuthBase):
entity = 'role'
def __init__(self, client, name):
super(EtcdRole, self).__init__(client, name)
self._read_paths = set()
self._write_paths = set()
def _from_net(self, data):
d = json.loads(data.decode('utf-8'))
self.name = d.get('role')
try:
kv = d["permissions"]["kv"]
except:
self._read_paths = set()
self._write_paths = set()
return
self._read_paths = set(kv.get('read', []))
self._write_paths = set(kv.get('write', []))
def _to_net(self, prevobj=None):
retval = []
if prevobj is None:
retval.append({
"role": self.name,
"permissions":
{
"kv":
{
"read": list(self._read_paths),
"write": list(self._write_paths)
}
}
})
else:
to_grant = {
'read': list(self._read_paths - prevobj._read_paths),
'write': list(self._write_paths - prevobj._write_paths)
}
to_revoke = {
'read': list(prevobj._read_paths - self._read_paths),
'write': list(prevobj._write_paths - self._write_paths)
}
if [path for sublist in to_revoke.values() for path in sublist]:
retval.append({'role': self.name, 'revoke': {'kv': to_revoke}})
if [path for sublist in to_grant.values() for path in sublist]:
retval.append({'role': self.name, 'grant': {'kv': to_grant}})
return retval
def grant(self, path, permission):
if permission.upper().find('R') >= 0:
self._read_paths.add(path)
if permission.upper().find('W') >= 0:
self._write_paths.add(path)
def revoke(self, path, permission):
if permission.upper().find('R') >= 0 and \
path in self._read_paths:
self._read_paths.remove(path)
if permission.upper().find('W') >= 0 and \
path in self._write_paths:
self._write_paths.remove(path)
@property
def acls(self):
perms = {}
try:
for path in self._read_paths:
perms[path] = 'R'
for path in self._write_paths:
if path in perms:
perms[path] += 'W'
else:
perms[path] = 'W'
except:
pass
return perms
@acls.setter
def acls(self, acls):
self._read_paths = set()
self._write_paths = set()
for path, permission in acls.items():
self.grant(path, permission)
def __str__(self):
return json.dumps({"role": self.name, 'acls': self.acls})
class Auth(object):
def __init__(self, client):
self.client = client
self.uri = "{}/auth/enable".format(self.client.version_prefix)
@property
def active(self):
resp = self.client.api_execute(self.uri, self.client._MGET)
return json.loads(resp.data.decode('utf-8'))['enabled']
@active.setter
def active(self, value):
if value != self.active:
method = value and self.client._MPUT or self.client._MDELETE
self.client.api_execute(self.uri, method)
|