1 # Unix SMB/CIFS implementation.
3 # Tests for samba-tool commands for Key Distribution Services
5 # Copyright © Catalyst.Net Ltd. 2024
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
23 from datetime
import datetime
, timezone
25 from .base
import SambaToolCmdTest
26 from samba
.dcerpc
import misc
28 from samba
.nt_time
import (nt_now
,
33 from ldb
import SCOPE_SUBTREE
, Dn
35 from samba
.tests
.gkdi
import create_root_key
38 HOST
= "ldap://{DC_SERVER}".format(**os
.environ
)
39 CREDS
= "-U{DC_USERNAME}%{DC_PASSWORD}".format(**os
.environ
)
40 SMBCONF
= os
.environ
['SERVERCONFFILE']
43 NON_ADMIN_CREDS
= "-U{DOMAIN_USER}%{DOMAIN_USER_PASSWORD}".format(**os
.environ
)
45 TIMESTAMP_RE
= r
'\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}\+00:00'
50 class KdsRootKeyTestsBase(SambaToolCmdTest
):
53 cls
.samdb
= cls
.getSamDB("-H", HOST
, CREDS
)
54 dn
= cls
.samdb
.get_config_basedn()
55 dn
.add_child("CN=Master Root Keys,CN=Group Key Distribution Service,CN=Services")
56 cls
.root_key_base_dn
= dn
58 # we'll add one for all tests to rely on -- but most will add
63 def _create_root_key_timediff(cls
, create_diff
=0, use_diff
=0):
65 nt_create
= now
+ create_diff
* NT_TICKS_PER_SEC
66 nt_use
= now
+ use_diff
* NT_TICKS_PER_SEC
67 guid
, dn
= create_root_key(cls
.samdb
,
69 current_nt_time
=nt_create
,
70 use_start_time
=nt_use
)
72 return guid
, dn
, nt_create
, nt_use
74 def _create_root_key_timediff_cleanup(self
, create_diff
=0, use_diff
=0):
75 """create a root key that will disappear when the test ends."""
76 guid
, dn
, nt_create
, nt_use
= self
._create
_root
_key
_timediff
(
79 self
.addCleanup(self
.samdb
.delete
, dn
)
80 return guid
, dn
, nt_create
, nt_use
82 def _check_timestamp(self
, isotimestamp
, expected
, range=10000):
83 """Check that a timestamp string matches an nt-time.
85 By default we give a millisecond of leeway, because the ISO
86 timestamp has less resolution than NT time (at most 6 decimal
90 t
= nt_time_from_string(isotimestamp
)
93 # we don't know what we want, but at least it's a time!
96 if expected
is NOWISH
:
98 range = 2.0 * NT_TICKS_PER_SEC
100 self
.assertGreaterEqual(t
, expected
- range)
101 self
.assertLessEqual(t
, expected
+ range)
103 def _test_list_output_snippet(self
, output
,
104 guid
=r
'\b[0-9a-fA-F-]{36}\b',
108 # name 1146a853-b604-75ac-5acc-4ef4f0530584
109 # created 2024-02-15T22:55:47.865576+00:00 (about 4 days ago)
110 # usable from 2024-02-15T22:55:47.865576+00:00 (about 4 days ago)
111 self
.assertRegex(output
, f
"(?m)^name {guid}$")
113 m
= re
.search(f
' created +({TIMESTAMP_RE})', output
)
114 self
.assertIsNotNone(m
, "create timestamp not found")
115 create_timestamp
= m
.group(1)
116 self
._check
_timestamp
(create_timestamp
, created
)
118 m
= re
.search(f
' usable from +({TIMESTAMP_RE})', output
)
119 self
.assertIsNotNone(m
, "usable from timestamp not found")
120 used_from_timestamp
= m
.group(1)
121 self
._check
_timestamp
(used_from_timestamp
, used_from
)
124 dn
= f
"CN={guid},{self.root_key_base_dn}"
125 self
.assertRegex(output
, f
"(?m)^ +dn +{dn}$")
126 self
.assertRegex(output
, r
"(?m)^ +whenCreated +\d{14}.0Z$")
127 self
.assertRegex(output
, r
"(?m)^ +whenChanged +\d{14}.0Z$")
128 self
.assertRegex(output
, r
"(?m)^ +objectGUID +[0-9a-fA-F-]{36}$")
129 self
.assertRegex(output
, r
"(?m)^ +msKds-KDFAlgorithmID \w+$")
130 self
.assertRegex(output
, r
"(?m)^ +msKds-KDFParam \w+$")
131 self
.assertRegex(output
, r
"(?m)^ +msKds-SecretAgreementAlgorithmID \w+$")
132 self
.assertRegex(output
, r
"(?m)^ +msKds-PublicKeyLength \d+$")
133 self
.assertRegex(output
, r
"(?m)^ +msKds-PrivateKeyLength \d+$")
134 self
.assertRegex(output
, r
"(?m)^ +msKds-Version 1$")
135 self
.assertRegex(output
, rf
"(?m)^ +msKds-DomainID [\w=, ]+{self.samdb.domain_dn()}$",
137 self
.assertRegex(output
, f
"(?m)^ +cn +{guid}$") # same guid as name
139 def _test_list_output_json_snippet(self
, snippet
,
140 guid
=r
'\b[0-9a-fA-F-]{36}\b',
145 _guid
= lambda x
: re
.fullmatch(str(guid
), x
)
146 _hexstr
= lambda x
: re
.fullmatch('[0-9a-fA-F]+', x
)
147 _str
= lambda x
: isinstance(x
, str)
148 _int
= lambda x
: isinstance(x
, int)
150 # these next 2 will raise an assertion error on failure
152 self
._check
_timestamp
(x
, used_from
)
156 self
._check
_timestamp
(x
, used_from
)
162 "msKds-CreateTime": _created
,
163 "msKds-DomainID": _str
,
164 "msKds-KDFAlgorithmID": _str
,
165 "msKds-KDFParam": _hexstr
,
166 "msKds-PrivateKeyLength": _int
,
167 "msKds-PublicKeyLength": _int
,
168 "msKds-SecretAgreementAlgorithmID": _str
,
169 "msKds-UseStartTime": _used_from
,
170 "msKds-Version": _int
,
179 keys
= ["name", "msKds-UseStartTime", "msKds-CreateTime", "dn"]
181 self
.assertEqual(len(keys
), len(snippet
), f
"keys: {keys}, json: {snippet}")
184 f
= validators
.get(k
)
186 self
.assertTrue(f(v
), f
"{k} value {v} is wrong or malformed")
188 def _get_root_key_guids(self
):
189 """Get the current list of GUIDs."""
190 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "list", "--json",
192 return [x
['name'] for x
in json
.loads(out
)]
194 def _delete_root_key(self
, guid
):
195 dn
= Dn(self
.samdb
, str(self
.root_key_base_dn
))
196 dn
.add_child(f
"CN={guid}")
197 self
.samdb
.delete(dn
)
199 class KdsRootKeyTests(KdsRootKeyTestsBase
):
204 # we'll add one for all tests to rely on.
205 cls
.common_guid
, cls
.common_dn
, cls
.common_time
, _
= cls
._create
_root
_key
_timediff
()
206 cls
.addClassCleanup(cls
.samdb
.delete
, cls
.common_dn
)
210 """Do we list root keys with the expected info?"""
211 # For this test we also need to create some root keys.
212 guid
, dn
, _created
, _used
= self
._create
_root
_key
_timediff
_cleanup
()
214 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "list",
216 self
.assertCmdSuccess(result
, out
, err
)
217 self
.assertEqual(err
, "", "not expecting error messages")
219 # the output looks something like
221 #------------------------------------------------------------------------
224 # name d58e85d7-ffc4-d118-9c43-46fac38dea05
225 # created 2024-02-27T09:09:21.065486+00:00 (about 1 seconds ago)
226 # usable from 2024-02-27T09:09:21.065486+00:00 (about 1 seconds ago)
228 # name 8f3e6557-3ec9-cb84-2ecd-9e258df68e79
229 # created 2024-02-27T09:09:10.853494+00:00 (about 12 seconds ago)
230 # usable from 2024-02-27T09:09:10.853494+00:00 (about 12 seconds ago)
231 #-------------------------------------------------------------------------
233 # we want to check the various bits.
235 parts
= out
.rstrip().split("\n\n")
237 self
.assertEqual(parts
[0], f
"{len(parts) - 1} root keys found.")
239 self
._test
_list
_output
_snippet
(parts
[1], guid
,
243 guid2
, dn2
, _created2
, _used2
= self
._create
_root
_key
_timediff
_cleanup
()
245 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "list",
247 self
.assertCmdSuccess(result
, out
, err
)
248 self
.assertEqual(err
, "", "not expecting error messages")
250 parts2
= out
.rstrip().split("\n\n")
251 self
.assertEqual(parts2
[0], f
"{len(parts)} root keys found.")
252 self
.assertEqual(len(parts2
), len(parts
) + 1)
254 # we want to check that both of them are still there, in the
255 # right order, which is newest first.
256 self
._test
_list
_output
_snippet
(parts2
[1], guid2
,
259 self
._test
_list
_output
_snippet
(parts2
[2], guid
,
263 def test_list_verbose(self
):
264 """Do we list root keys with the expected info?"""
265 guid
, dn
, _created
, _used
= self
._create
_root
_key
_timediff
_cleanup
()
267 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "list", "-v",
270 self
.assertCmdSuccess(result
, out
, err
)
271 self
.assertEqual(err
, "", "not expecting error messages")
273 self
._test
_list
_output
_snippet
(out
, guid
, verbose
=True)
275 guid2
, dn2
, _created2
, _used2
= self
._create
_root
_key
_timediff
_cleanup
()
277 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "list", "-v",
279 self
.assertCmdSuccess(result
, out
, err
)
280 self
.assertEqual(err
, "", "not expecting error messages")
282 self
._test
_list
_output
_snippet
(out
, guid2
, verbose
=True)
284 # in case there are other root keys, we will test each piece
285 # using the default '[0-9a-fA-F-]{36}' guid-ish assertion.
287 pieces
= out
.rstrip().split('\n\n')
288 self
.assertRegex(pieces
[0], f
'{len(pieces) - 1} root keys found.')
290 for piece
in pieces
[1:]:
291 self
._test
_list
_output
_snippet
(piece
, verbose
=True)
293 def test_list_json(self
):
294 """The JSON should be a list of dicts, containing the right things"""
295 guid
, dn
, _created
, _used
= self
._create
_root
_key
_timediff
_cleanup
()
297 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "list", "-v", "--json",
299 self
.assertCmdSuccess(result
, out
, err
)
300 self
.assertEqual(err
, "", "not expecting error messages")
301 data
= json
.loads(out
)
303 self
._test
_list
_output
_json
_snippet
(snippet
, verbose
=True)
306 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "list", "--json",
308 self
.assertCmdSuccess(result
, out
, err
)
309 self
.assertEqual(err
, "", "not expecting error messages")
310 data
= json
.loads(out
)
312 self
._test
_list
_output
_json
_snippet
(snippet
)
314 def test_view_key_that_exists(self
):
315 guid
, dn
, _created
, _used
= self
._create
_root
_key
_timediff
_cleanup
()
316 cmd
= ["domain", "kds", "root-key", "view",
320 result
, out
, err
= self
.runcmd(*cmd
)
321 self
.assertCmdSuccess(result
, out
, err
)
322 self
.assertEqual(err
, "", "not expecting error messages")
324 self
._test
_list
_output
_snippet
(out
, guid
,
329 def test_view_key_that_exists_json(self
):
330 guid
, dn
, _created
, _used
= self
._create
_root
_key
_timediff
_cleanup
()
332 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "view",
336 self
.assertCmdSuccess(result
, out
, err
)
337 self
.assertEqual(err
, "", "not expecting error messages")
338 data
= json
.loads(out
)
339 self
._test
_list
_output
_json
_snippet
(data
, guid
,
345 def test_view_key_latest_json(self
):
346 guid
, dn
, _created
, _used
= self
._create
_root
_key
_timediff
_cleanup
()
348 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "view",
352 self
.assertCmdSuccess(result
, out
, err
)
353 self
.assertEqual(err
, "", "not expecting error messages")
354 data
= json
.loads(out
)
355 self
._test
_list
_output
_json
_snippet
(data
, guid
,
360 # if we make a new now-ish key, it will be shown with
361 # --latest, forgetting the old one.
362 guid2
, dn2
, _created2
, _used2
= self
._create
_root
_key
_timediff
_cleanup
()
364 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "view",
368 self
.assertCmdSuccess(result
, out
, err
)
369 self
.assertEqual(err
, "", "not expecting error messages")
370 data
= json
.loads(out
)
371 self
._test
_list
_output
_json
_snippet
(data
, guid2
,
376 # if we make a new backdated key, it will not be shown as
377 # latest, even though it was the most recently created.
379 self
._create
_root
_key
_timediff
_cleanup
(use_diff
=-600)
381 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "view",
385 self
.assertCmdSuccess(result
, out
, err
)
386 self
.assertEqual(err
, "", "not expecting error messages")
387 data
= json
.loads(out
)
388 self
._test
_list
_output
_json
_snippet
(data
, guid2
,
393 # if we make a future-dated key, it will be shown as
394 # latest, even though it doesn't work yet.
396 guid3
, dn3
, _created3
, _used3
= self
._create
_root
_key
_timediff
_cleanup
()
398 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "view",
402 self
.assertCmdSuccess(result
, out
, err
)
403 self
.assertEqual(err
, "", "not expecting error messages")
404 data
= json
.loads(out
)
405 self
._test
_list
_output
_json
_snippet
(data
, guid3
,
410 def test_view_non_existent(self
):
411 """Viewing a non-existent GUID should fail, regardless of what exists."""
412 guid
= misc
.GUID(b
'a' * 16)
414 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "view",
417 self
.assertCmdFail(result
)
419 self
.assertIn("ERROR: no such root key: 61616161-6161-6161-6161-616161616161",
422 def test_view_non_existent_json(self
):
423 guid
= misc
.GUID(b
'a' * 16)
425 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "view",
429 self
.assertCmdFail(result
)
430 data
= json
.loads(out
)
434 "message": f
"no such root key: {guid}",
438 def test_delete_non_existent(self
):
439 """Deletion of non-existent guid should fail"""
440 guid
= 'eeeeeeee-1111-eeee-1111-000000000000'
441 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "delete",
444 self
.assertCmdFail(result
)
445 self
.assertIn(f
"ERROR: no such root key: {guid}", err
)
447 def test_delete_non_existent_json(self
):
448 """Deletion of non-existent guids should fail"""
449 for guid
in ('eeeeeeee-1111-eeee-1111-000000000000',
452 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "delete",
456 self
.assertCmdFail(result
)
457 data
= json
.loads(out
)
461 "message": f
"no such root key: {guid}",
465 def test_create(self
):
466 """does create work?"""
467 pre_create
= self
._get
_root
_key
_guids
()
469 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "create",
471 self
.assertCmdSuccess(result
, out
, err
)
472 self
.assertEqual(err
, "", "not expecting error messages")
474 post_create
= self
._get
_root
_key
_guids
()
476 new_guids
= list(set(post_create
) - set(pre_create
))
477 gone_guids
= set(pre_create
) - set(post_create
)
478 self
.assertEqual(len(gone_guids
), 0)
479 self
.assertEqual(len(new_guids
), 1)
480 self
.assertRegex(out
,
481 f
"created root key {new_guids[0]}, usable from {TIMESTAMP_RE}")
482 self
._delete
_root
_key
(new_guids
[0])
484 def test_create_json(self
):
485 """does create work?"""
486 pre_create
= self
._get
_root
_key
_guids
()
488 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "create",
489 "-H", HOST
, CREDS
, "--json")
490 self
.assertCmdSuccess(result
, out
, err
)
491 self
.assertEqual(err
, "", "not expecting error messages")
493 post_create
= self
._get
_root
_key
_guids
()
495 new_guids
= list(set(post_create
) - set(pre_create
))
496 gone_guids
= set(pre_create
) - set(post_create
)
497 self
.assertEqual(len(gone_guids
), 0)
498 self
.assertEqual(len(new_guids
), 1)
499 data
= json
.loads(out
)
500 self
.assertEqual(data
['dn'], f
"CN={new_guids[0]},{self.root_key_base_dn}")
501 self
.assertEqual(data
['status'], 'OK')
502 self
.assertRegex(data
['message'],
503 f
"created root key {new_guids[0]}, usable from {TIMESTAMP_RE}")
504 self
._delete
_root
_key
(new_guids
[0])
506 def test_create_json_non_admin(self
):
507 """can you create a root-key without being admin?"""
508 pre_create
= self
._get
_root
_key
_guids
()
510 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "create",
511 "-H", HOST
, NON_ADMIN_CREDS
, "--json")
512 self
.assertCmdFail(result
)
514 post_create
= self
._get
_root
_key
_guids
()
516 self
.assertEqual(set(pre_create
), set(post_create
))
517 data
= json
.loads(out
)
518 self
.assertEqual(data
['status'], 'error')
519 self
.assertEqual(data
['message'], 'User has insufficient access rights')
520 self
.assertEqual(err
, "", "not expecting stderr messages")
522 def test_create_json_1997(self
):
523 """does create work?"""
524 pre_create
= self
._get
_root
_key
_guids
()
526 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "create",
527 "-H", HOST
, CREDS
, "--json",
529 "1997-11-11T23:18:00.259810+00:00")
530 self
.assertCmdSuccess(result
, out
, err
)
531 self
.assertEqual(err
, "", "not expecting error messages")
533 post_create
= self
._get
_root
_key
_guids
()
535 new_guids
= list(set(post_create
) - set(pre_create
))
536 gone_guids
= set(pre_create
) - set(post_create
)
537 self
.assertEqual(len(gone_guids
), 0)
538 self
.assertEqual(len(new_guids
), 1)
539 data
= json
.loads(out
)
540 self
.assertEqual(data
['dn'], f
"CN={new_guids[0]},{self.root_key_base_dn}")
541 self
.assertEqual(data
['status'], 'OK')
542 self
.assertRegex(data
['message'],
543 f
"created root key {new_guids[0]}, usable from 1997-11-1")
544 self
._delete
_root
_key
(new_guids
[0])
546 def test_create_json_2197(self
):
547 """does create work?"""
548 pre_create
= self
._get
_root
_key
_guids
()
550 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "create",
551 "-H", HOST
, CREDS
, "--json",
553 "2197-11-11T23:18:00")
554 self
.assertCmdSuccess(result
, out
, err
)
555 self
.assertEqual(err
, "", "not expecting error messages")
557 post_create
= self
._get
_root
_key
_guids
()
559 new_guids
= list(set(post_create
) - set(pre_create
))
560 gone_guids
= set(pre_create
) - set(post_create
)
561 self
.assertEqual(len(gone_guids
), 0)
562 self
.assertEqual(len(new_guids
), 1)
563 data
= json
.loads(out
)
564 self
.assertEqual(data
['dn'], f
"CN={new_guids[0]},{self.root_key_base_dn}")
565 self
.assertEqual(data
['status'], 'OK')
566 self
.assertRegex(data
['message'],
567 f
"created root key {new_guids[0]}, usable from 2197-11-1")
568 self
._delete
_root
_key
(new_guids
[0])
570 def test_create_future(self
):
571 """does create work, with a use-start-time 500 seconds in the
573 pre_create
= self
._get
_root
_key
_guids
()
575 later
= now
+ 500 * NT_TICKS_PER_SEC
576 timestamp
= string_from_nt_time(later
)
578 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "create",
579 "-H", HOST
, CREDS
, "--json",
580 "--use-start-time", timestamp
)
582 self
.assertCmdSuccess(result
, out
, err
)
583 self
.assertEqual(err
, "", "not expecting error messages")
585 post_create
= self
._get
_root
_key
_guids
()
587 new_guids
= list(set(post_create
) - set(pre_create
))
588 gone_guids
= set(pre_create
) - set(post_create
)
589 self
.assertEqual(len(gone_guids
), 0)
590 self
.assertEqual(len(new_guids
), 1)
591 data
= json
.loads(out
)
592 self
.assertEqual(data
['dn'], f
"CN={new_guids[0]},{self.root_key_base_dn}")
593 self
.assertEqual(data
['status'], 'OK')
594 self
.assertRegex(data
['message'],
595 f
"created root key {new_guids[0]}, usable from {timestamp[:-10]}")
596 self
._delete
_root
_key
(new_guids
[0])
598 def test_delete(self
):
599 """does delete work?"""
600 # make one to delete, and get the list as JSON
601 _guid
, dn
, _created
, _used
= self
._create
_root
_key
_timediff
()
604 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "list", "--json",
606 pre_delete
= json
.loads(out
)
608 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "delete",
611 self
.assertCmdSuccess(result
, out
, err
)
612 self
.assertEqual(err
, "", "not expecting error messages")
613 self
.assertEqual(out
, f
"deleted root key {guid}\n")
615 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "list", "--json",
617 post_delete
= json
.loads(out
)
619 self
.assertEqual(len(pre_delete
), len(post_delete
) + 1)
621 post_names
= [x
['name'] for x
in post_delete
]
622 pre_names
= [x
['name'] for x
in pre_delete
]
624 self
.assertIn(guid
, pre_names
)
625 self
.assertNotIn(guid
, post_names
)
627 def test_delete_json(self
):
628 """does delete --json work?"""
629 _guid
, dn
, _created
, _used
= self
._create
_root
_key
_timediff
()
632 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "list", "--json",
634 pre_delete
= json
.loads(out
)
636 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "delete",
637 "-H", HOST
, CREDS
, "--json",
640 self
.assertCmdSuccess(result
, out
, err
)
641 self
.assertEqual(err
, "", "not expecting error messages")
642 data
= json
.loads(out
)
646 "message": f
"deleted root key {guid}",
650 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "list", "--json",
652 post_delete
= json
.loads(out
)
654 self
.assertEqual(len(pre_delete
), len(post_delete
) + 1)
656 post_names
= [x
['name'] for x
in post_delete
]
657 pre_names
= [x
['name'] for x
in pre_delete
]
659 self
.assertIn(guid
, pre_names
)
660 self
.assertNotIn(guid
, post_names
)
662 def test_delete_non_admin(self
):
663 """does delete as non-admin fail?"""
664 # make one to delete, and get the list as JSON
665 _guid
, dn
, _created
, _used
= self
._create
_root
_key
_timediff
()
668 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "list", "--json",
670 pre_delete
= json
.loads(out
)
672 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "delete",
673 "-H", HOST
, NON_ADMIN_CREDS
,
675 self
.assertCmdFail(result
)
676 self
.assertIn(f
"ERROR: no such root key: {guid}", err
)
678 # a bad guid should be just like a good guid
679 guid2
= 'eeeeeeee-1111-eeee-1111-000000000000'
680 result
, out2
, err2
= self
.runcmd("domain", "kds", "root-key", "delete",
681 "-H", HOST
, NON_ADMIN_CREDS
,
683 self
.assertCmdFail(result
)
684 self
.assertIn(f
"ERROR: no such root key: {guid2}", err2
)
686 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "list", "--json",
688 post_delete
= json
.loads(out
)
690 self
.assertEqual(len(pre_delete
), len(post_delete
))
692 post_names
= [x
['name'] for x
in post_delete
]
693 pre_names
= [x
['name'] for x
in pre_delete
]
695 self
.assertIn(guid
, pre_names
)
696 self
.assertIn(guid
, post_names
)
698 def test_list_non_admin(self
):
699 """There are root keys, but non-admins can't see them"""
700 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "list",
701 "-H", HOST
, NON_ADMIN_CREDS
)
702 self
.assertCmdSuccess(result
, out
, err
)
703 self
.assertEqual(err
, "", "not expecting error messages")
704 self
.assertEqual(out
, "no root keys found.\n")
706 def test_list_json_non_admin(self
):
707 """Insufficient rights should look like an empty list."""
708 # this is a copy of the KdsNoRootKeyTests test below --
709 # non-admin should look exactly like an empty list.
710 for extra
in ([], ["-v"]):
711 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "list",
712 "-H", HOST
, NON_ADMIN_CREDS
, "--json", *extra
)
713 self
.assertCmdSuccess(result
, out
, err
)
714 self
.assertEqual(err
, "", "not expecting error messages")
715 data
= json
.loads(out
)
716 self
.assertEqual(data
, [])
718 def test_view_key_non_admin(self
):
719 """should not appear to non-admin"""
720 guid
, dn
, _created
, _used
= self
._create
_root
_key
_timediff
_cleanup
()
722 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "view",
725 "-H", HOST
, NON_ADMIN_CREDS
)
726 self
.assertCmdFail(result
)
727 self
.assertEqual(err
, "", "not expecting error messages")
728 data
= json
.loads(out
)
729 data
= json
.loads(out
)
733 "message": f
"no such root key: {guid}",
738 class KdsNoRootKeyTests(KdsRootKeyTestsBase
):
739 """Here we test the case were there are no root keys, which we need to
740 ensure by deleting any that are there.
746 # We delete all the root keys, and add one back at the end,
747 # in case other tests want there to be one.
748 res
= cls
.samdb
.search(cls
.root_key_base_dn
,
750 expression
="(objectClass = msKds-ProvRootKey)")
753 cls
.samdb
.delete(msg
.dn
)
755 cls
.addClassCleanup(cls
.samdb
.new_gkdi_root_key
)
757 def test_list_empty(self
):
758 """Check the message when there are no root keys"""
759 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "list",
761 self
.assertCmdSuccess(result
, out
, err
)
762 self
.assertEqual(err
, "", "not expecting error messages")
763 self
.assertEqual(out
, "no root keys found.\n")
765 def test_list_empty_json(self
):
766 """The JSON should be an empty list when there are no root keys"""
767 # verbose flag makes no difference here.
768 for extra
in ([], ["-v"]):
769 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "list",
770 "-H", HOST
, CREDS
, "--json", *extra
)
771 self
.assertCmdSuccess(result
, out
, err
)
772 self
.assertEqual(err
, "", "not expecting error messages")
773 data
= json
.loads(out
)
774 self
.assertEqual(data
, [])
776 def test_list_empty_json_non_admin(self
):
777 """Insufficient rights should look like an empty list."""
778 # verbose flag makes no difference here.
779 for extra
in ([], ["-v"]):
780 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "list",
781 "-H", HOST
, NON_ADMIN_CREDS
, "--json", *extra
)
782 self
.assertCmdSuccess(result
, out
, err
)
783 self
.assertEqual(err
, "", "not expecting error messages")
784 data
= json
.loads(out
)
785 self
.assertEqual(data
, [])
787 def test_view_latest_non_existent(self
):
788 """With no root keys, --latest should return an error"""
790 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "view",
794 self
.assertEqual(err
, "ERROR: no root keys found\n")
795 self
.assertCmdFail(result
)
797 def test_view_latest_non_existent_json(self
):
798 """With no root keys, --latest should return an error"""
800 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "view",
802 "--json", "--latest")
803 self
.assertCmdFail(result
)
804 data
= json
.loads(out
)
808 "message": "no root keys found",
812 def test_view_non_existent(self
):
813 """Viewing a non-existent GUID should fail, regardless of what exists."""
814 guid
= misc
.GUID(b
'b' * 16)
816 result
, out
, err
= self
.runcmd("domain", "kds", "root-key", "view",
819 self
.assertCmdFail(result
)
821 self
.assertIn("ERROR: no such root key: 62626262-6262-6262-6262-626262626262",