Repository navigation
Expand file tree
/
Copy pathhdb.sql
More file actions
1537 lines (1409 loc) · 64.1 KB
/
Copy pathhdb.sql
File metadata and controls
1537 lines (1409 loc) · 64.1 KB
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
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
-- SET client_min_messages TO 'debug';
\unset ON_ERROR_STOP
CREATE EXTENSION IF NOT EXISTS pgcrypto;
/*
* Data tables go in the schema "heimdal".
*
* XXX If we want to do something like AD's global catalog, we could have a
* separate schema to hold that. For group memberships in that scheme we would
* indeed need two-column foreign keys or else single-column with the realm
* included in the value. This argues for preparing to have multiple realms in
* one DB, even if in actuality we wouldn't usually do that.
*
* XXX Rethink namespacing. Perhaps implement something akin to UName*It
* container rules.
*/
CREATE SCHEMA IF NOT EXISTS heimdal;
/*
* Views for libhdb support go in the schema "hdb".
*
* These will permit generation of HDB entries as JSON which can then be
* transcoded to the hdb_entry ASN.1 type using DER.
*
* These will also map INSERT/UPDATE/DELETE operations on the main hdb view
* into corresponding INSERT/UPDATE/DELETE operations on heimdal tables.
*/
CREATE SCHEMA IF NOT EXISTS hdb;
/*
* Views and functions for PostgREST APIs go in the schema "pgt".
*/
CREATE SCHEMA IF NOT EXISTS pgt;
CREATE OR REPLACE FUNCTION heimdal.split_name(name TEXT)
RETURNS TEXT[]
LANGUAGE SQL AS $$
SELECT CASE WHEN name !~ '' THEN ARRAY[name,'']
WHEN name ~ '^[@]' THEN ARRAY['', substring(name FROM 2)]
WHEN name ~ '[@]' THEN ARRAY[trim(TRAILING '@' FROM substring(name FROM '^.*[@]')),
substring(substring(name FROM '[@].*$') FROM 2)]
ELSE ARRAY[name,'']
END;
$$ IMMUTABLE;
/* XXX Add allowed-to-delegate-to (not implemented in Heimdal anyways) */
CREATE SEQUENCE IF NOT EXISTS heimdal.ids;
CREATE TYPE heimdal.enc_type AS ENUM (
'aes128-cts-hmac-sha1-96',
'aes256-cts-hmac-sha1-96',
'aes128-cts-hmac-sha256',
'aes256-cts-hmac-sha512'
/* non-standard enc_type names will also be included here */
/* XXX populate moar */
);
CREATE TYPE heimdal.digest_type AS ENUM (
'sha1',
'sha256',
'sha512'
/* XXX add moar */
);
CREATE TYPE heimdal.key_type AS ENUM (
'SYMMETRIC', /* usable with SPAKE2 */
'MAC',
'SPAKE2', /* symmetric reply key usable only with SPAKE2; enc_type will be enc_type */
'SPAKE2+', /* asymmetric verifier for SPAKE2+; enc_type will be enc_type */
'PUBLIC', /* public key; enc_type will be pubkey alg name and params */
'PRIVATE', /* private key to a public key cryptosystem; ditto */
'PASSWORD' /* enc_type will be codeset name (e.g., 'UTF-8') */,
'CERT', /* enc_type will be 'OPAQUE' */
'CERT-HASH' /* enc_type will be digest name; enc_type will be digest alg */
);
CREATE TYPE heimdal.containers AS ENUM (
/*
* So, what we're going for here is that we want to be able to support
* distinct containers for several things for backwards compatibility
* reasons. But we could, in a better world, have just one container, and
* then get rid of this ENUM type and all uses of it.
*
* XXX For now use only 'PRINCIPAL' -Nico
*/
'PRINCIPAL', 'USER', 'GROUP', 'ACL', 'HOST', 'ROLE', 'LABEL', 'VERB'
);
CREATE TYPE heimdal.entity_types AS ENUM (
/*
* This is different from containers only because the latter are about
* uniqueness, while this is about what kind of thing something is.
*
* If each kind of thing had a distinct container, then we'd not need this
* ENUM type at all either.
*
* What we really want is to lose this ENUM, and for namespacing we should
* build an extension that implements UName*It-style container rules.
*
* XXX For now use only 'USER'.
*/
'PRINCIPAL', 'USER', 'ROLE', 'GROUP', 'CLUSTER', 'ACL', 'LABEL', 'VERB'
);
CREATE TYPE heimdal.princ_flags AS ENUM (
'INITIAL', 'FORWARDABLE', 'PROXIABLE', 'RENEWABLE', 'POSTDATE', 'SERVER',
'CLIENT', 'INVALID', 'REQUIRE-PREAUTH', 'CHANGE-PW', 'REQUIRE-HWAUTH',
'OK-AS-DELEGATE', 'USER-TO-USER', 'IMMUTABLE', 'TRUSTED-FOR-DELEGATION',
'ALLOW-KERBEROS4', 'ALLOW-DIGEST', 'LOCKED-OUT', 'REQUIRE-PWCHANGE',
'DO-NOT-STORE'
);
CREATE TYPE heimdal.kerberos_name_type AS ENUM (
-- Principal name-types.
'UNKNOWN', 'USER', 'HOST-BASED-SERVICE', 'DOMAIN-BASED-SERVICE'
);
CREATE TYPE heimdal.pkix_name_type AS ENUM (
'General', 'RFC822-SAN', 'PKINIT-SAN'
);
CREATE TABLE IF NOT EXISTS heimdal.common (
valid_start TIMESTAMP WITHOUT TIME ZONE DEFAULT (current_timestamp),
valid_end TIMESTAMP WITHOUT TIME ZONE DEFAULT (current_timestamp + '100 years'::interval),
created_by TEXT DEFAULT (current_user),
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT (current_timestamp),
modified_by TEXT DEFAULT (current_user),
modified_at TIMESTAMP WITHOUT TIME ZONE DEFAULT (current_timestamp),
/* XXX Use an origin name or something better than IP:port */
origin_addr INET DEFAULT (inet_server_addr()),
origin_port INTEGER DEFAULT (inet_server_port()),
origin_txid BIGINT DEFAULT (txid_current())
);
/* Which enctypes are enabled or disabled globally */
CREATE TABLE IF NOT EXISTS heimdal.enc_types (
ktype heimdal.key_type,
etype heimdal.enc_type,
enabled BOOLEAN DEFAULT (TRUE),
LIKE heimdal.common INCLUDING ALL,
CONSTRAINT hecpk PRIMARY KEY (ktype, etype)
);
INSERT INTO heimdal.enc_types (ktype, etype)
VALUES ('SYMMETRIC','aes128-cts-hmac-sha1-96'),
('SYMMETRIC','aes256-cts-hmac-sha1-96'),
('SYMMETRIC','aes128-cts-hmac-sha256'),
('SYMMETRIC','aes256-cts-hmac-sha512')
/* XXX populate moar */
ON CONFLICT DO NOTHING;
/* Which digestypes are enabled or disabled globally */
CREATE TABLE IF NOT EXISTS heimdal.digest_types (
ktype heimdal.key_type,
dtype heimdal.digest_type,
enabled BOOLEAN DEFAULT (TRUE),
LIKE heimdal.common INCLUDING ALL,
CONSTRAINT hdtpk PRIMARY KEY (ktype, dtype),
CONSTRAINT hdtpk2 UNIQUE (dtype)
);
/* Duplicate inserts -L */
/* Remedied with digest types -L */
INSERT INTO heimdal.digest_types (ktype, dtype)
VALUES ('SYMMETRIC','sha1'),
('SYMMETRIC','sha256'),
('SYMMETRIC','sha512')
/* XXX populate moar */
ON CONFLICT DO NOTHING;
/*
* All non-principal entities and all principals will share a container via
* trigger-driven double-entry in this table. Among other things this allows
* us to have an entity type in here.
*/
CREATE TABLE IF NOT EXISTS heimdal.policies (
name TEXT,
/* XXX Add policy content */
LIKE heimdal.common INCLUDING ALL,
CONSTRAINT hpolpk PRIMARY KEY (name)
);
INSERT INTO heimdal.policies (name)
VALUES ('default')
ON CONFLICT DO NOTHING;
CREATE TABLE IF NOT EXISTS heimdal.entities (
display_name TEXT,
name TEXT,
realm TEXT,
container heimdal.containers,
entity_type heimdal.entity_types NOT NULL,
id BIGINT DEFAULT (nextval('heimdal.ids')),
policy TEXT,
owner_name TEXT,
owner_container heimdal.containers,
owner_realm TEXT,
owner_entity_type heimdal.entity_types,
LIKE heimdal.common INCLUDING ALL,
CONSTRAINT hepk PRIMARY KEY (name, realm, container),
CONSTRAINT hepk2 UNIQUE (id),
/* hepk3 is just for denormalzation of entity_type via FKs */
CONSTRAINT heofk FOREIGN KEY (owner_name, owner_realm, owner_container)
REFERENCES heimdal.entities (name, realm, container),
CONSTRAINT hefkp FOREIGN KEY (policy)
REFERENCES heimdal.policies (name)
ON DELETE SET NULL
ON UPDATE CASCADE
);
CREATE TABLE IF NOT EXISTS heimdal.entity_labels (
name TEXT,
realm TEXT,
container heimdal.containers,
label_name TEXT,
label_realm TEXT,
label_container heimdal.containers
DEFAULT ('LABEL')
CHECK (label_container = 'LABEL'),
LIKE heimdal.common INCLUDING ALL,
CONSTRAINT helpk PRIMARY KEY (name, realm, container,
label_name, label_realm, label_container),
CONSTRAINT helefk FOREIGN KEY (name, realm, container)
REFERENCES heimdal.entities (name, realm, container)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT hellfk FOREIGN KEY (label_name, label_realm, label_container)
REFERENCES heimdal.entities (name, realm, container)
ON DELETE CASCADE
ON UPDATE CASCADE
);
CREATE TABLE IF NOT EXISTS heimdal.principals (
display_name TEXT,
name TEXT,
realm TEXT,
container heimdal.containers
DEFAULT ('PRINCIPAL')
CHECK (container = 'PRINCIPAL'),
name_type heimdal.kerberos_name_type
DEFAULT ('UNKNOWN'),
/* flags and etypes are stored separately */
kvno BIGINT DEFAULT (1),
pw_life INTERVAL DEFAULT ('90 days'::interval),
pw_end TIMESTAMP WITHOUT TIME ZONE
DEFAULT (current_timestamp + '90 days'::interval),
last_pw_change TIMESTAMP WITHOUT TIME ZONE
DEFAULT ('1970-01-01T00:00:00Z'::timestamp without time zone),
max_life INTERVAL DEFAULT ('10 hours'::interval),
max_renew INTERVAL DEFAUlT ('7 days'::interval),
password TEXT, /* very much optional, mostly unused XXX make binary, encrypted */
LIKE heimdal.common INCLUDING ALL,
CONSTRAINT hppk PRIMARY KEY (name, realm, container),
CONSTRAINT hpfka FOREIGN KEY (name, realm, container)
REFERENCES heimdal.entities (name, realm, container)
ON DELETE CASCADE
ON UPDATE CASCADE
);
CREATE TABLE IF NOT EXISTS heimdal.principal_etypes(
name TEXT,
realm TEXT,
container heimdal.containers
DEFAULT ('PRINCIPAL')
CHECK (container = 'PRINCIPAL'),
etype heimdal.enc_type NOT NULL,
LIKE heimdal.common INCLUDING ALL,
CONSTRAINT hpepk PRIMARY KEY (name, realm, container, etype),
CONSTRAINT hpefk FOREIGN KEY (name, realm, container)
REFERENCES heimdal.entities (name, realm, container)
ON DELETE CASCADE
ON UPDATE CASCADE
);
/*
* Let's have some consistency here. If enc_type and digest_type are both types contained in tables enc_typeS and digest_typeS
* then the table principal_flags should contain the type principal_flag, NOT princ_flags. -L
*/
CREATE TABLE IF NOT EXISTS heimdal.principal_flags(
name TEXT,
realm TEXT,
container heimdal.containers
DEFAULT ('PRINCIPAL')
CHECK (container = 'PRINCIPAL'),
flag heimdal.princ_flags NOT NULL,
LIKE heimdal.common INCLUDING ALL,
CONSTRAINT hpfpk PRIMARY KEY (name, realm, container, flag),
CONSTRAINT hpffk FOREIGN KEY (name, realm, container)
REFERENCES heimdal.entities (name, realm, container)
ON DELETE CASCADE
ON UPDATE CASCADE
);
CREATE TABLE IF NOT EXISTS heimdal.members (
name TEXT,
realm TEXT,
container heimdal.containers,
member_name TEXT,
member_realm TEXT,
member_container heimdal.containers,
LIKE heimdal.common INCLUDING ALL,
CONSTRAINT hmpk PRIMARY KEY (name, realm, container, member_name, member_realm, member_container),
CONSTRAINT hmfkp FOREIGN KEY (name, realm, container)
REFERENCES heimdal.entities (name, realm, container)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT hmfkm FOREIGN KEY (member_name, member_realm, member_container)
REFERENCES heimdal.entities (name, realm, container)
ON DELETE CASCADE
ON UPDATE CASCADE
);
CREATE TYPE heimdal.salt AS (
salttype BIGINT,
value BYTEA,
opaque BYTEA
);
CREATE TABLE IF NOT EXISTS heimdal.keys (
name TEXT,
realm TEXT,
container heimdal.containers
DEFAULT ('PRINCIPAL')
CHECK (container = 'PRINCIPAL'),
kvno BIGINT,
ktype heimdal.key_type,
etype heimdal.enc_type, /* varies according to heimdal.key_type */
key BYTEA,
salt heimdal.salt,
mkvno BIGINT,
/* keys can be disabled separately from enc_types */
enabled BOOLEAN DEFAULT (TRUE),
LIKE heimdal.common INCLUDING ALL,
CONSTRAINT hkpk PRIMARY KEY (name, realm, container, ktype, etype, kvno, key),
CONSTRAINT hkfk1 FOREIGN KEY (name, realm, container)
REFERENCES heimdal.entities (name, realm, container)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT hkfk2 FOREIGN KEY (ktype,etype)
REFERENCES heimdal.enc_types (ktype,etype)
ON DELETE RESTRICT
ON UPDATE CASCADE
);
CREATE TABLE IF NOT EXISTS heimdal.aliases (
name TEXT,
realm TEXT,
container heimdal.containers
DEFAULT ('PRINCIPAL')
CHECK (container = 'PRINCIPAL'),
alias_name TEXT,
alias_realm TEXT,
alias_container heimdal.containers
DEFAULT ('PRINCIPAL')
CHECK (container = 'PRINCIPAL'),
LIKE heimdal.common INCLUDING ALL,
CONSTRAINT hapk1 PRIMARY KEY (name, realm, container, alias_name, alias_realm, alias_container),
CONSTRAINT hafk1 FOREIGN KEY (alias_name, alias_realm, alias_container)
REFERENCES heimdal.entities (name, realm, container)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT hafk2 FOREIGN KEY (name, realm, container)
REFERENCES heimdal.entities (name, realm, container)
ON DELETE CASCADE
ON UPDATE CASCADE
);
CREATE TABLE IF NOT EXISTS heimdal.password_history (
name TEXT,
realm TEXT,
container heimdal.containers
DEFAULT ('PRINCIPAL')
CHECK (container = 'PRINCIPAL'),
etype heimdal.enc_type,
/* XXX Should be MAC, not digest */
digest_alg heimdal.digest_type, /* why not just dtype like table digest_types? -L */
digest BYTEA,
mkvno BIGINT,
LIKE heimdal.common INCLUDING ALL,
CONSTRAINT hphpk PRIMARY KEY (name, realm, container, etype, digest_alg, mkvno),
CONSTRAINT hpfk FOREIGN KEY (name, realm, container)
REFERENCES heimdal.entities (name, realm, container)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT hkfk2 FOREIGN KEY (digest_alg)
REFERENCES heimdal.digest_types (dtype)
ON DELETE RESTRICT
ON UPDATE CASCADE
);
CREATE TYPE heimdal.pkix_name AS (
display TEXT, /* display form of name */
name_type heimdal.pkix_name_type,
name BYTEA
);
CREATE TABLE IF NOT EXISTS heimdal.pkinit_cert_names (
name TEXT,
realm TEXT,
container heimdal.containers
DEFAULT ('PRINCIPAL')
CHECK (container = 'PRINCIPAL'),
subject heimdal.pkix_name,
issuer heimdal.pkix_name,
serial BYTEA,
anchor heimdal.pkix_name,
LIKE heimdal.common INCLUDING ALL,
CONSTRAINT hpcnpk PRIMARY KEY (name, realm, container, subject, issuer, serial),
CONSTRAINT hpcfk FOREIGN KEY (name, realm, container)
REFERENCES heimdal.entities (name, realm, container)
ON DELETE CASCADE
ON UPDATE CASCADE
);
CREATE TABLE IF NOT EXISTS heimdal.roles2verbs (
name TEXT,
realm TEXT,
container heimdal.containers
DEFAULT ('ROLE')
CHECK (container = 'ROLE'),
verb_name TEXT,
verb_realm TEXT,
verb_container heimdal.containers
DEFAULT ('VERB')
CHECK (verb_container = 'VERB'),
LIKE heimdal.common INCLUDING ALL,
CONSTRAINT hrpk PRIMARY KEY (name, realm, container, verb_name, verb_realm, verb_container),
CONSTRAINT hrfkr FOREIGN KEY (name, realm, container)
REFERENCES heimdal.entities (name, realm, container)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT hrfkv FOREIGN KEY (verb_name, verb_realm, verb_container)
REFERENCES heimdal.entities (name, realm, container)
ON DELETE CASCADE
ON UPDATE CASCADE
);
CREATE TABLE IF NOT EXISTS heimdal.grants (
name TEXT,
realm TEXT,
container heimdal.containers
CHECK (container = 'GROUP' OR container = 'USER'),
label_name TEXT,
label_realm TEXT,
label_container heimdal.containers
DEFAULT ('LABEL')
CHECK (label_container = 'LABEL'),
role_name TEXT,
role_realm TEXT,
role_container heimdal.containers
DEFAULT ('ROLE')
CHECK (role_container = 'ROLE'),
LIKE heimdal.common INCLUDING ALL,
CONSTRAINT hgpk PRIMARY KEY (name, realm, container,
label_name, label_realm, label_container,
role_name, role_realm, role_container),
CONSTRAINT hgfks FOREIGN KEY (name, realm, container)
REFERENCES heimdal.entities (name, realm, container)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT hgfkl FOREIGN KEY (label_name, label_realm, label_container)
REFERENCES heimdal.entities (name, realm, container)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT hgfkr FOREIGN KEY (role_name, role_realm, role_container)
REFERENCES heimdal.entities (name, realm, container)
ON DELETE CASCADE
ON UPDATE CASCADE
);
CREATE OR REPLACE VIEW heimdal.tc_view AS
WITH RECURSIVE groups AS (
/* Seed with every group includes itself -- this is important */
SELECT name AS name, realm AS realm, container AS container,
name AS member_name, realm AS member_realm, container AS member_container
FROM heimdal.entities WHERE entity_type = 'GROUP'
UNION
/* Get the parents of all groups */
SELECT m.name, m.realm, m.container,
g.member_name, g.member_realm, g.member_container
FROM heimdal.members m JOIN groups g ON (m.member_name = g.name AND m.member_realm = g.realm AND m.member_container = g.container)
)
SELECT * FROM groups;
/* Materialize the view -- we'll have triggers to keep it up to date */
SELECT mat_views.create_view('heimdal','tc', 'heimdal', 'tc_view');
/* Populate it (creating it doesn't do this) */
SELECT mat_views.refresh_view('heimdal','tc');
/* Look 'ma! PG's MAT VIEWs do not allow this: */
ALTER TABLE heimdal.tc
ADD CONSTRAINT htcpk1
PRIMARY KEY (name, realm, container,
member_name, member_realm, member_container);
/* nor this! */
CREATE INDEX tc2 ON heimdal.tc
(member_name, member_realm, member_container,
name, realm, container);
/* Now a view for all the users' group memerships */
CREATE OR REPLACE VIEW heimdal.tcu_view AS
SELECT tc.name AS name, tc.realm AS realm,
tc.container AS container, e.name AS member_name,
e.realm AS member_realm, e.container AS member_container
FROM heimdal.entities e
/* get direct memberships */
JOIN heimdal.members m ON (e.name = m.member_name AND e.realm = m.member_realm AND e.container = m.member_container)
/* get all remaining indirect memberships */
JOIN heimdal.tc tc ON (tc.member_name = m.name AND tc.member_realm = m.realm AND tc.member_container = m.container)
WHERE e.entity_type = 'USER'
UNION
SELECT e.name, e.realm, e.container, e.name, e.realm, e.container
FROM heimdal.entities e
WHERE e.entity_type = 'USER';
SELECT mat_views.create_view('heimdal','tcu','heimdal','tcu_view');
SELECT mat_views.refresh_view('heimdal','tcu');
ALTER TABLE heimdal.tcu
ADD CONSTRAINT htcupk1
PRIMARY KEY (name, realm, container,
member_name, member_realm, member_container);
CREATE INDEX tcu2 ON heimdal.tcu
(member_name, member_realm, member_container,
name, realm, container);
CREATE OR REPLACE VIEW heimdal.grants2direct_grantees AS
SELECT g.name AS name, g.realm AS realm,
g.container AS container, g.label_name AS label_name,
g.label_realm AS label_realm, g.label_container AS label_container,
rv.verb_name AS verb_name, rv.verb_realm AS verb_realm,
rv.verb_container AS verb_container
FROM heimdal.roles2verbs rv
JOIN heimdal.grants g ON rv.name = g.role_name AND
rv.realm = g.role_realm AND
rv.container = g.role_container;
SELECT mat_views.create_view('heimdal','g2dg','heimdal','grants2direct_grantees');
SELECT mat_views.refresh_view('heimdal','g2dg');
ALTER TABLE heimdal.g2dg
ADD CONSTRAINT hgdgpk1
PRIMARY KEY (label_name, label_realm, label_container,
name, realm, container,
verb_name, verb_realm, verb_container);
CREATE INDEX g2dg2 ON heimdal.g2dg
(name, realm, container,
label_name, label_realm, label_container,
verb_name, verb_realm, verb_container);
/*
* HDB VIEWs and INSTEAD OF triggers for interfacing libhdb to HDBs hosted on
* PG with the above schema.
*
* Many of these VIEWs and associated TRIGGERs could be auto-generated from the
* schema. We might need to enrich the schema with JSON-encoded COMMENTary.
*/
CREATE OR REPLACE VIEW hdb.modified_info AS
SELECT name AS name, realm AS realm, container AS container,
modified_by AS modified_by, modified_at AS modified_at
FROM heimdal.principals;
CREATE OR REPLACE VIEW hdb.key AS
SELECT
k.name AS name, k.realm AS realm, k.kvno AS kvno,
jsonb_build_object('ktype',k.ktype::text,
'etype',k.etype::text,
'set_at',
CASE coalesce(current_setting('hdb.test',true), 'false')
WHEN 'true' THEN k.created_at::text
ELSE '1970-01-01 00:00:00'::timestamp without time zone::text END,
'kvno',k.kvno::bigint,
'mkvno',k.mkvno::bigint,
'salt',k.salt::text,
'key',encode(k.key, 'base64')) AS key
FROM heimdal.keys k
WHERE k.enabled AND k.valid_start <= current_timestamp AND
k.valid_end > current_timestamp;
CREATE OR REPLACE VIEW hdb.keyset AS
SELECT ks.name AS name, ks.realm AS realm, ks.kvno AS kvno, jsonb_agg(ks.key ORDER BY ks.key) AS keys
FROM hdb.key ks
GROUP BY ks.name, ks.realm, ks.kvno;
CREATE OR REPLACE VIEW hdb.keysets AS
SELECT ks.name AS name, ks.realm AS realm, 'keysets' AS extname,
jsonb_agg(ks.keys ORDER BY ks.keys) AS ext
FROM hdb.keyset ks
WHERE NOT EXISTS (SELECT 1 FROM heimdal.principals p WHERE p.name = ks.name AND p.realm = ks.realm AND p.kvno = ks.kvno)
GROUP BY ks.name, ks.realm;
CREATE OR REPLACE VIEW hdb.aliases AS
SELECT a.name AS name, a.realm AS realm, 'aliases' AS extname,
jsonb_agg(jsonb_build_object('alias_name',a.alias_name,
'alias_realm',a.alias_realm) ORDER BY a.alias_name, a.alias_realm) AS ext
FROM heimdal.aliases a
WHERE container = 'PRINCIPAL'
GROUP BY a.name, a.realm;
CREATE OR REPLACE VIEW hdb.pwh1 AS
SELECT p.name AS name, p.realm AS realm,
jsonb_build_object('mkvno',p.mkvno,
'etype',p.etype::text,
'digest_alg',p.digest_alg::text,
'digest',encode(p.digest, 'base64'),
'set_at',
CASE coalesce(current_setting('hdb.test',true), 'false')
WHEN 'true' THEN p.created_at::text
ELSE '1970-01-01 00:00:00'::timestamp without time zone::text END
) AS old_password
FROM heimdal.password_history p;
CREATE OR REPLACE VIEW hdb.pwh AS
SELECT p1.name AS name, p1.realm AS realm, 'password_history' AS extname,
jsonb_agg(p1.old_password ORDER BY p1.old_password) AS ext
FROM hdb.pwh1 p1
GROUP BY p1.name, p1.realm;
/*
* XXX Finish, add all remaining hdb entry extensions here:
*
* - PKINIT cert hashes
* - PKINIT cert names
* - PKINIT certs
* - S4U constrained delegation ACLs
*/
CREATE OR REPLACE VIEW hdb.flags AS
SELECT p.name AS name, p.realm AS realm, jsonb_agg(p.flag::text ORDER BY p.flag) AS flags
FROM heimdal.principal_flags p
WHERE valid_end > current_timestamp
GROUP BY p.name, p.realm;
CREATE OR REPLACE VIEW hdb.etypes AS
SELECT p.name AS name, p.realm AS realm, jsonb_agg(p.etype::text ORDER BY p.etype) AS etypes
FROM heimdal.principal_etypes p
WHERE valid_end > current_timestamp
GROUP BY p.name, p.realm;
CREATE OR REPLACE VIEW hdb.hdb AS
/* Principals */
SELECT e.display_name AS display_name, e.name AS name, e.realm AS realm,
jsonb_build_object(
'name',e.name,
'realm',e.realm,
'kvno',p.kvno,
'keys',keys.keys,
'name_type',p.name_type,
'created_by',e.created_by,
'created_at',e.created_at::text,
'modified_by',modinfo.modified_by,
'modified_at',modinfo.modified_at::text,
'password',p.password::text,
'valid_start',p.valid_start::text,
'valid_end',p.valid_end::text,
'pw_life',p.pw_life::text,
'pw_end',p.pw_end::text,
'last_pw_change',p.last_pw_change::text,
'max_life',coalesce(p.max_life::text,''),
'max_renew',coalesce(p.max_renew::text,''),
'flags',coalesce(flags.flags,'[]'::jsonb),
'etypes',coalesce(etypes.etypes,jsonb_build_array()),
'aliases',a.ext,
'keysets',keysets.ext,
'password_history',pwh.ext) AS entry
FROM heimdal.entities e
JOIN hdb.modified_info modinfo USING (name, realm, container)
JOIN heimdal.principals p USING (name, realm, container)
JOIN hdb.flags flags USING (name, realm)
LEFT JOIN hdb.aliases a USING (name, realm)
LEFT JOIN hdb.keysets keysets USING (name, realm)
LEFT JOIN hdb.pwh pwh USING (name, realm)
LEFT JOIN hdb.etypes etypes ON e.name = etypes.name AND e.realm = etypes.realm
LEFT JOIN hdb.keyset keys ON p.name = keys.name AND p.realm = keys.realm AND p.kvno = keys.kvno
WHERE e.container = 'PRINCIPAL' AND
p.valid_start <= current_timestamp AND p.valid_end > current_timestamp
UNION ALL
/* Aliases */
SELECT a.alias_name || '@' || a.alias_realm AS display_name,
a.alias_name AS name, a.alias_realm AS realm,
jsonb_build_object(
'name',a.alias_name,
'realm',a.alias_realm,
'canon_name',p.name,
'canon_realm',p.realm, /* Return to this -L */
'created_by',a.created_by,
'created_at',a.created_at::text,
'modified_by',a.modified_by,
'modified_at',a.modified_at::text) AS entry
FROM heimdal.aliases a
JOIN heimdal.principals p ON a.name = p.name AND a.realm = p.realm AND
a.container = p.container
WHERE a.container = 'PRINCIPAL' AND
a.valid_start <= current_timestamp AND a.valid_end > current_timestamp AND
p.valid_start <= current_timestamp AND p.valid_end > current_timestamp;
/* Create check function -L */
CREATE OR REPLACE FUNCTION heimdal.chk(
_name TEXT, _realm TEXT, _container heimdal.containers,
_object_name TEXT, _object_realm TEXT, _object_container heimdal.containers)
RETURNS BOOLEAN AS $$
SELECT count(*) <> 0
FROM (
SELECT name, realm, container
FROM heimdal.tcu
WHERE member_name = _name AND member_realm = _realm AND
member_container = _container
INTERSECT
SELECT owner_name, owner_realm, owner_container
FROM heimdal.entities
WHERE name = _object_name AND realm = _object_realm AND
container = _object_container
) q;
; $$ LANGUAGE SQL;
CREATE OR REPLACE FUNCTION heimdal.chk(
_name TEXT, _realm TEXT, _container heimdal.containers,
_verb_name TEXT, _verb_realm TEXT, _verb_container heimdal.containers,
_label_name TEXT, _label_realm TEXT, _label_container heimdal.containers)
RETURNS BOOLEAN AS $$
SELECT count(*) <> 0
FROM (
SELECT name, realm, container
FROM heimdal.tcu
WHERE member_name = _name AND member_realm = _realm AND
member_container = _container
INTERSECT
SELECT name, realm, container
FROM heimdal.g2dg
WHERE label_name = _label_name AND label_realm = _label_realm AND
label_container = _label_container AND
verb_name = _verb_name AND verb_realm = _verb_realm AND
verb_container = _verb_container
) q;
; $$ LANGUAGE SQL;
/* Create triggers on heimdal inserts -L */
/* XXX This function is kinda sloppy and should be redone -L */
/* Reverse cascade from non-entities to entities */
CREATE OR REPLACE FUNCTION heimdal.trigger_on_entities_func()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'INSERT' AND NEW.entity_type = 'GROUP' THEN
INSERT INTO heimdal.tc (name, realm, container,
member_name, member_realm, member_container)
SELECT NEW.name, NEW.realm, NEW.container,
NEW.name, NEW.realm, NEW.container;
END IF;
IF TG_TABLE_NAME = 'entities' THEN
NEW.display_name :=
CASE NEW.entity_type
WHEN 'PRINCIPAL' THEN NEW.name || '@' || NEW.realm
ELSE lower(NEW.entity_type::TEXT) || ': ' || NEW.name || '@' || lower(NEW.realm)
END;
END IF;
IF TG_OP = 'UPDATE' AND TG_WHEN = 'AFTER' THEN
UPDATE heimdal.principals SET display_name = NULL WHERE name = NEW.name AND realm = NEW.realm;
END IF;
RETURN NEW;
END; $$ LANGUAGE PLPGSQL;
CREATE TRIGGER before_on_heimdal_entities_set_display_name
BEFORE INSERT OR UPDATE
ON heimdal.entities
FOR EACH ROW
EXECUTE FUNCTION heimdal.trigger_on_entities_func();
CREATE TRIGGER after_on_heimdal_entities_set_display_name
AFTER UPDATE
ON heimdal.entities
FOR EACH ROW
EXECUTE FUNCTION heimdal.trigger_on_entities_func();
CREATE OR REPLACE FUNCTION heimdal.trigger_on_principals_func()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO heimdal.entities
(name, realm, container, entity_type)
SELECT NEW.name, NEW.realm, 'PRINCIPAL', 'PRINCIPAL'
ON CONFLICT DO NOTHING;
NEW.display_name := (
SELECT e.display_name FROM heimdal.entities e WHERE e.name = NEW.name AND
e.realm = NEW.realm AND
e.container = 'PRINCIPAL');
RETURN NEW;
END; $$ LANGUAGE PLPGSQL;
CREATE TRIGGER before_on_heimdal_principals_set_display_name
BEFORE INSERT
ON heimdal.principals
FOR EACH ROW
EXECUTE FUNCTION heimdal.trigger_on_principals_func();
CREATE OR REPLACE FUNCTION heimdal.trigger_on_members_func()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'UPDATE' THEN
RETURN NULL; /* XXX Raise instead */
END IF;
/* DELETE GOES HERE */
IF TG_OP = 'DELETE' THEN
WITH RECURSIVE parents AS (
SELECT OLD.name AS name, OLD.realm AS realm,
OLD.container AS container
UNION
SELECT m.name, m.realm, m.container
FROM heimdal.members m
JOIN parents p ON m.member_name = p.name AND
m.member_realm = p.realm AND
m.member_container = p.container
), rmembers AS (
SELECT OLD.member_name AS name, OLD.member_realm AS realm,
OLD.member_container AS container
UNION
SELECT m.member_name, m.member_realm, m.member_container
FROM heimdal.members m
JOIN rmembers mem ON m.name = mem.name AND
m.realm = mem.realm AND
m.container = mem.container
), exceptions AS (
SELECT mem.name AS member_name,
mem.realm AS member_realm,
mem.container AS member_container,
mem.name AS name,
mem.realm AS realm,
mem.container AS container
FROM rmembers mem
UNION
SELECT exc.member_name,
exc.member_realm,
exc.member_container,
m.name,
m.realm,
m.container
FROM heimdal.members m
JOIN exceptions exc ON m.member_name = exc.name AND
m.member_realm = exc.realm AND
m.member_container = exc.container
), deletions AS (
SELECT p.name AS name,
p.realm AS realm,
p.container AS container,
m.name AS member_name,
m.realm AS member_realm,
m.container AS member_container
FROM
parents p
CROSS JOIN
rmembers m
EXCEPT
SELECT exc.name,
exc.realm,
exc.container,
exc.member_name,
exc.member_realm,
exc.member_container
FROM exceptions exc
)
DELETE FROM heimdal.tc AS tc
USING deletions d
WHERE tc.name = d.name AND
tc.realm = d.realm AND
tc.container = d.container AND
tc.member_name = d.member_name AND
tc.member_realm = d.member_realm AND
tc.member_container = d.member_container;
PERFORM mat_views.set_needs_refresh('heimdal','tcu');
RETURN OLD;
END IF;
IF NOT EXISTS
(SELECT 1 FROM heimdal.entities e
WHERE e.name = NEW.name AND e.realm = NEW.realm AND
e.container = NEW.container AND e.entity_type = 'GROUP') OR
NOT EXISTS
(SELECT 1 FROM heimdal.entities e
WHERE e.name = NEW.member_name AND e.realm = NEW.member_realm AND
e.container = NEW.member_container AND
(e.entity_type = 'GROUP' OR e.entity_type = 'USER')) THEN
RETURN NULL; /* XXX Raise instead */
END IF;
WITH RECURSIVE parents AS (
SELECT NEW.name AS name, NEW.realm AS realm, NEW.container AS container
UNION
SELECT m.name, m.realm, m.container
FROM heimdal.members m
JOIN parents p ON (m.member_name = p.name AND
m.member_realm = p.realm AND
m.member_container = p.container)
), rmembers AS (
SELECT NEW.member_name AS name, NEW.member_realm AS realm,
NEW.member_container AS container
UNION
SELECT m.member_name, m.member_realm, m.member_container
FROM heimdal.members m
JOIN rmembers mem ON (m.name = mem.name AND
m.realm = mem.realm AND
m.container = mem.container)
)
INSERT INTO heimdal.tc (name, realm, container,
member_name, member_realm, member_container)
SELECT p.name, p.realm, p.container,
m.name, m.realm, m.container
FROM
parents p /* All related parents */
CROSS JOIN
rmembers m /* All related members */
JOIN heimdal.entities e ON (m.name = e.name AND
m.realm = e.realm AND
m.container = e.container)
WHERE e.entity_type = 'GROUP'
ON CONFLICT DO NOTHING;
PERFORM mat_views.set_needs_refresh('heimdal','tcu');
RETURN NEW;
END; $$ LANGUAGE PLPGSQL;
CREATE TRIGGER trigger_on_heimdal_members_transitive_closure_before
BEFORE UPDATE
ON heimdal.members
FOR EACH ROW
EXECUTE FUNCTION heimdal.trigger_on_members_func();
CREATE TRIGGER trigger_on_heimdal_members_transitive_closure_after
AFTER INSERT OR DELETE
ON heimdal.members
FOR EACH ROW
EXECUTE FUNCTION heimdal.trigger_on_members_func();
CREATE OR REPLACE FUNCTION heimdal.trigger_on_grants_func()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'DELETE' THEN
WITH deletions AS (
SELECT OLD.label_name AS label_name, OLD.label_realm AS label_realm,
OLD.label_container AS label_container, rv.verb_name AS verb_name,
rv.verb_realm AS verb_realm, rv.verb_container AS verb_container,
OLD.name AS name, OLD.realm AS realm,
OLD.container AS container
FROM
heimdal.roles2verbs rv
EXCEPT
SELECT gt.label_name, gt.label_realm, gt.label_container,
rv.verb_name, rv.verb_realm, rv.verb_container,
gt.name, gt.realm, gt.container
FROM
heimdal.grants gt
JOIN
heimdal.roles2verbs rv
ON rv.name = gt.role_name AND rv.realm = gt.role_realm AND
rv.container = gt.role_container
WHERE gt.label_name = OLD.label_name AND gt.label_realm = OLD.label_realm AND
gt.label_container = OLD.label_container AND gt.name = OLD.name AND
gt.realm = OLD.realm AND gt.container = OLD.container
)
DELETE FROM heimdal.g2dg g2dg
USING deletions d
WHERE g2dg.label_name = d.label_name AND g2dg.label_realm = d.label_realm AND
g2dg.label_container = d.label_container AND g2dg.verb_name = d.verb_name AND
g2dg.verb_realm = d.verb_realm AND g2dg.verb_container = d.verb_container AND
g2dg.name = d.name AND g2dg.realm = d.realm AND
g2dg.container = d.container;
RETURN OLD;
END IF;
IF NOT EXISTS
(SELECT 1 FROM heimdal.entities e
WHERE e.name = NEW.label_name AND e.realm = NEW.label_realm AND
e.container = NEW.label_container AND e.entity_type = 'LABEL') OR
NOT EXISTS
(SELECT 1 FROM heimdal.entities e
WHERE e.name = NEW.role_name AND e.realm = NEW.role_realm AND
e.container = NEW.role_container AND e.entity_type = 'ROLE') OR
NOT EXISTS
(SELECT 1 FROM heimdal.entities e
WHERE e.name = NEW.name AND e.realm = NEW.realm AND
e.container = NEW.container AND
(e.entity_type = 'GROUP' OR e.entity_type = 'USER')) THEN
RETURN NULL; /* XXX Raise instead */
END IF;
INSERT INTO heimdal.g2dg /* XXX name of view */ (label_name, label_realm, label_container,
verb_name, verb_realm, verb_container,
name, realm, container)
SELECT NEW.label_name, NEW.label_realm, NEW.label_container,
rv.verb_name, rv.verb_realm, rv.verb_container,
NEW.name, NEW.realm, NEW.container
FROM
heimdal.roles2verbs rv
WHERE rv.name = NEW.role_name AND
rv.realm = NEW.role_realm AND rv.container = NEW.role_container