]> git.proxmox.com Git - ceph.git/blame - ceph/qa/workunits/mon/caps.py
update sources to v12.1.2
[ceph.git] / ceph / qa / workunits / mon / caps.py
CommitLineData
7c673cae
FG
1#!/usr/bin/python
2
3import json
4import subprocess
5import shlex
6from StringIO import StringIO
7import errno
8import sys
9import os
10import io
11import re
12
13
14import rados
15from ceph_argparse import *
16
17keyring_base = '/tmp/cephtest-caps.keyring'
18
19class UnexpectedReturn(Exception):
20 def __init__(self, cmd, ret, expected, msg):
21 if isinstance(cmd, list):
22 self.cmd = ' '.join(cmd)
23 else:
24 assert isinstance(cmd, str) or isinstance(cmd, unicode), \
25 'cmd needs to be either a list or a str'
26 self.cmd = cmd
27 self.cmd = str(self.cmd)
28 self.ret = int(ret)
29 self.expected = int(expected)
30 self.msg = str(msg)
31
32 def __str__(self):
33 return repr('{c}: expected return {e}, got {r} ({o})'.format(
34 c=self.cmd, e=self.expected, r=self.ret, o=self.msg))
35
36def call(cmd):
37 if isinstance(cmd, list):
38 args = cmd
39 elif isinstance(cmd, str) or isinstance(cmd, unicode):
40 args = shlex.split(cmd)
41 else:
42 assert False, 'cmd is not a string/unicode nor a list!'
43
44 print 'call: {0}'.format(args)
45 proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
46 ret = proc.wait()
47
48 return (ret, proc)
49
50def expect(cmd, expected_ret):
51
52 try:
53 (r, p) = call(cmd)
54 except ValueError as e:
55 print >> sys.stderr, \
56 'unable to run {c}: {err}'.format(c=repr(cmd), err=e.message)
57 return errno.EINVAL
58
59 assert r == p.returncode, \
60 'wth? r was supposed to match returncode!'
61
62 if r != expected_ret:
63 raise UnexpectedReturn(repr(cmd), r, expected_ret, str(p.stderr.read()))
64
65 return p
66
67def expect_to_file(cmd, expected_ret, out_file, mode='a'):
68
69 # Let the exception be propagated to the caller
70 p = expect(cmd, expected_ret)
71 assert p.returncode == expected_ret, \
72 'expected result doesn\'t match and no exception was thrown!'
73
74 with io.open(out_file, mode) as file:
75 file.write(unicode(p.stdout.read()))
76
77 return p
78
79class Command:
80 def __init__(self, cid, j):
81 self.cid = cid[3:]
82 self.perms = j['perm']
83 self.module = j['module']
84
85 self.sig = ''
86 self.args = []
87 for s in j['sig']:
88 if not isinstance(s, dict):
89 assert isinstance(s, str) or isinstance(s,unicode), \
90 'malformatted signature cid {0}: {1}\n{2}'.format(cid,s,j)
91 if len(self.sig) > 0:
92 self.sig += ' '
93 self.sig += s
94 else:
95 self.args.append(s)
96
97 def __str__(self):
98 return repr('command {0}: {1} (requires \'{2}\')'.format(self.cid,\
99 self.sig, self.perms))
100
101
102def destroy_keyring(path):
103 if not os.path.exists(path):
104 raise Exception('oops! cannot remove inexistent keyring {0}'.format(path))
105
106 # grab all client entities from the keyring
107 entities = [m.group(1) for m in [re.match(r'\[client\.(.*)\]', l)
108 for l in [str(line.strip())
109 for line in io.open(path,'r')]] if m is not None]
110
111 # clean up and make sure each entity is gone
112 for e in entities:
113 expect('ceph auth del client.{0}'.format(e), 0)
114 expect('ceph auth get client.{0}'.format(e), errno.ENOENT)
115
116 # remove keyring
117 os.unlink(path)
118
119 return True
120
121def test_basic_auth():
122 # make sure we can successfully add/del entities, change their caps
123 # and import/export keyrings.
124
125 expect('ceph auth add client.basicauth', 0)
126 expect('ceph auth caps client.basicauth mon \'allow *\'', 0)
127 # entity exists and caps do not match
128 expect('ceph auth add client.basicauth', errno.EINVAL)
129 # this command attempts to change an existing state and will fail
130 expect('ceph auth add client.basicauth mon \'allow w\'', errno.EINVAL)
131 expect('ceph auth get-or-create client.basicauth', 0)
132 expect('ceph auth get-key client.basicauth', 0)
133 expect('ceph auth get-or-create client.basicauth2', 0)
134 # cleanup
135 expect('ceph auth del client.basicauth', 0)
136 expect('ceph auth del client.basicauth2', 0)
137
138 return True
139
140def gen_module_keyring(module):
141 module_caps = [
142 ('all', '{t} \'allow service {s} rwx\'', 0),
143 ('none', '', errno.EACCES),
144 ('wrong', '{t} \'allow service foobar rwx\'', errno.EACCES),
145 ('right', '{t} \'allow service {s} {p}\'', 0),
146 ('no-execute', '{t} \'allow service {s} x\'', errno.EACCES)
147 ]
148
149 keyring = '{0}.service-{1}'.format(keyring_base,module)
150 for perms in 'r rw x'.split():
151 for (n,p,r) in module_caps:
152 c = p.format(t='mon', s=module, p=perms)
153 expect_to_file(
154 'ceph auth get-or-create client.{cn}-{cp} {caps}'.format(
155 cn=n,cp=perms,caps=c), 0, keyring)
156
157 return keyring
158
159
160def test_all():
161
162
163 perms = {
164 'good': {
165 'broad':[
166 ('rwx', 'allow *'),
167 ('r', 'allow r'),
168 ('rw', 'allow rw'),
169 ('x', 'allow x'),
170 ],
171 'service':[
172 ('rwx', 'allow service {s} rwx'),
173 ('r', 'allow service {s} r'),
174 ('rw', 'allow service {s} rw'),
175 ('x', 'allow service {s} x'),
176 ],
177 'command':[
178 ('rwx', 'allow command "{c}"'),
179 ],
180 'command-with':[
181 ('rwx', 'allow command "{c}" with {kv}')
182 ],
183 'command-with-prefix':[
184 ('rwx', 'allow command "{c}" with {key} prefix {val}')
185 ]
186 },
187 'bad': {
188 'broad':[
189 ('none', ''),
190 ],
191 'service':[
192 ('none1', 'allow service foo rwx'),
193 ('none2', 'allow service foo r'),
194 ('none3', 'allow service foo rw'),
195 ('none4', 'allow service foo x'),
196 ],
197 'command':[
198 ('none', 'allow command foo'),
199 ],
200 'command-with':[
201 ('none', 'allow command "{c}" with foo=bar'),
202 ],
203 'command-with-prefix':[
204 ('none', 'allow command "{c}" with foo prefix bar'),
205 ],
206 }
207 }
208
209 cmds = {
210 '':[
211 {
212 'cmd':('status', '', 'r')
213 },
214 {
215 'pre':'heap start_profiler',
216 'cmd':('heap', 'heapcmd=stats', 'rw'),
217 'post':'heap stop_profiler'
218 }
219 ],
220 'auth':[
221 {
222 'pre':'',
c07f9fc5 223 'cmd':('auth ls', '', 'r'),
7c673cae
FG
224 'post':''
225 },
226 {
227 'pre':'auth get-or-create client.foo mon \'allow *\'',
228 'cmd':('auth caps', 'entity="client.foo"', 'rw'),
229 'post':'auth del client.foo'
230 }
231 ],
232 'pg':[
233 {
234 'cmd':('pg getmap', '', 'r'),
235 },
236 ],
237 'mds':[
238 {
239 'cmd':('mds getmap', '', 'r'),
240 },
241 {
242 'cmd':('mds cluster_down', '', 'rw'),
243 'post':'mds cluster_up'
244 },
245 ],
246 'mon':[
247 {
248 'cmd':('mon getmap', '', 'r')
249 },
250 {
251 'cmd':('mon remove', 'name=a', 'rw')
252 }
253 ],
254 'osd':[
255 {
256 'cmd':('osd getmap', '', 'r'),
257 },
258 {
259 'cmd':('osd pause', '', 'rw'),
260 'post':'osd unpause'
261 },
262 {
263 'cmd':('osd crush dump', '', 'r')
264 },
265 ],
266 'config-key':[
267 {
c07f9fc5 268 'pre':'config-key set foo bar',
7c673cae
FG
269 'cmd':('config-key get', 'key=foo', 'r')
270 },
271 {
c07f9fc5 272 'pre':'config-key set foo bar',
7c673cae
FG
273 'cmd':('config-key del', 'key=foo', 'rw')
274 }
275 ]
276 }
277
278 for (module,cmd_lst) in cmds.iteritems():
279 k = keyring_base + '.' + module
280 for cmd in cmd_lst:
281
282 (cmd_cmd, cmd_args, cmd_perm) = cmd['cmd']
283 cmd_args_key = ''
284 cmd_args_val = ''
285 if len(cmd_args) > 0:
286 (cmd_args_key, cmd_args_val) = cmd_args.split('=')
287
288 print 'generating keyring for {m}/{c}'.format(m=module,c=cmd_cmd)
289 # gen keyring
290 for (good_or_bad,kind_map) in perms.iteritems():
291 for (kind,lst) in kind_map.iteritems():
292 for (perm, cap) in lst:
293 cap_formatted = cap.format(
294 s=module,
295 c=cmd_cmd,
296 kv=cmd_args,
297 key=cmd_args_key,
298 val=cmd_args_val)
299
300 if len(cap_formatted) == 0:
301 run_cap = ''
302 else:
303 run_cap = 'mon \'{fc}\''.format(fc=cap_formatted)
304
305 cname = 'client.{gb}-{kind}-{p}'.format(
306 gb=good_or_bad,kind=kind,p=perm)
307 expect_to_file(
308 'ceph auth get-or-create {n} {c}'.format(
309 n=cname,c=run_cap), 0, k)
310 # keyring generated
311 print 'testing {m}/{c}'.format(m=module,c=cmd_cmd)
312
313 # test
314 for good_bad in perms.iterkeys():
315 for (kind,lst) in perms[good_bad].iteritems():
316 for (perm,_) in lst:
317 cname = 'client.{gb}-{k}-{p}'.format(gb=good_bad,k=kind,p=perm)
318
319 if good_bad == 'good':
320 expect_ret = 0
321 else:
322 expect_ret = errno.EACCES
323
324 if ( cmd_perm not in perm ):
325 expect_ret = errno.EACCES
326 if 'with' in kind and len(cmd_args) == 0:
327 expect_ret = errno.EACCES
328 if 'service' in kind and len(module) == 0:
329 expect_ret = errno.EACCES
330
331 if 'pre' in cmd and len(cmd['pre']) > 0:
332 expect('ceph {0}'.format(cmd['pre']), 0)
333 expect('ceph -n {cn} -k {k} {c} {arg_val}'.format(
334 cn=cname,k=k,c=cmd_cmd,arg_val=cmd_args_val), expect_ret)
335 if 'post' in cmd and len(cmd['post']) > 0:
336 expect('ceph {0}'.format(cmd['post']), 0)
337 # finish testing
338 destroy_keyring(k)
339
340
341 return True
342
343
344def test_misc():
345
346 k = keyring_base + '.misc'
347 expect_to_file(
348 'ceph auth get-or-create client.caps mon \'allow command "auth caps"' \
349 ' with entity="client.caps"\'', 0, k)
350 expect('ceph -n client.caps -k {kf} mon_status'.format(kf=k), errno.EACCES)
351 expect('ceph -n client.caps -k {kf} auth caps client.caps mon \'allow *\''.format(kf=k), 0)
352 expect('ceph -n client.caps -k {kf} mon_status'.format(kf=k), 0)
353 destroy_keyring(k)
354
355def main():
356
357 test_basic_auth()
358 test_all()
359 test_misc()
360
361 print 'OK'
362
363 return 0
364
365if __name__ == '__main__':
366 main()