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