/
models.py
1478 lines (1129 loc) · 50.8 KB
/
models.py
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
# Copyright (c) 2013-2014 Rackspace, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Defines database models for Barbican
"""
import hashlib
from oslo_serialization import jsonutils as json
from oslo_utils import timeutils
import six
import sqlalchemy as sa
from sqlalchemy.ext import compiler
from sqlalchemy.ext import declarative
from sqlalchemy import orm
from sqlalchemy.orm import collections as col
from sqlalchemy import types as sql_types
from barbican.common import exception
from barbican.common import utils
from barbican import i18n as u
BASE = declarative.declarative_base()
ERROR_REASON_LENGTH = 255
SUB_STATUS_LENGTH = 36
SUB_STATUS_MESSAGE_LENGTH = 255
# Allowed entity states
class States(object):
PENDING = 'PENDING'
ACTIVE = 'ACTIVE'
ERROR = 'ERROR'
@classmethod
def is_valid(cls, state_to_test):
"""Tests if a state is a valid one."""
return state_to_test in cls.__dict__
class OrderType(object):
KEY = 'key'
ASYMMETRIC = 'asymmetric'
CERTIFICATE = 'certificate'
@classmethod
def is_valid(cls, order_type):
"""Tests if a order type is a valid one."""
return order_type in cls.__dict__
class OrderStatus(object):
def __init__(self, id, message):
self.id = id
self.message = message
@compiler.compiles(sa.BigInteger, 'sqlite')
def compile_big_int_sqlite(type_, compiler, **kw):
return 'INTEGER'
class JsonBlob(sql_types.TypeDecorator):
"""JsonBlob is custom type for fields which need to store JSON text."""
impl = sa.Text
def process_bind_param(self, value, dialect):
if value is not None:
return json.dumps(value)
return value
def process_result_value(self, value, dialect):
if value is not None:
return json.loads(value)
return value
class ModelBase(object):
"""Base class for Nova and Barbican Models."""
__table_args__ = {'mysql_engine': 'InnoDB'}
__table_initialized__ = False
__protected_attributes__ = {
"created_at", "updated_at", "deleted_at", "deleted"}
id = sa.Column(sa.String(36), primary_key=True,
default=utils.generate_uuid)
created_at = sa.Column(sa.DateTime, default=timeutils.utcnow,
nullable=False)
updated_at = sa.Column(sa.DateTime, default=timeutils.utcnow,
nullable=False, onupdate=timeutils.utcnow)
deleted_at = sa.Column(sa.DateTime)
deleted = sa.Column(sa.Boolean, nullable=False, default=False)
status = sa.Column(sa.String(20), nullable=False, default=States.PENDING)
def save(self, session=None):
"""Save this object."""
# import api here to prevent circular dependency problem
import barbican.model.repositories
session = session or barbican.model.repositories.get_session()
# if model is being created ensure that created/updated are the same
if self.id is None:
self.created_at = timeutils.utcnow()
self.updated_at = self.created_at
session.add(self)
session.flush()
def delete(self, session=None):
"""Delete this object."""
import barbican.model.repositories
session = session or barbican.model.repositories.get_session()
self._do_delete_children(session)
session.delete(self)
def _do_delete_children(self, session):
"""Sub-class hook: delete children relationships."""
pass
def update(self, values):
"""dict.update() behaviour."""
for k, v in values.items():
self[k] = v
def __setitem__(self, key, value):
setattr(self, key, value)
def __getitem__(self, key):
return getattr(self, key)
def __iter__(self):
self._i = iter(orm.object_mapper(self).sa.Columns)
return self
def next(self):
n = next(self._i).name
return n, getattr(self, n)
def keys(self):
return self.__dict__.keys()
def values(self):
return self.__dict__.values()
def items(self):
return self.__dict__.items()
def to_dict(self):
return self.__dict__.copy()
def to_dict_fields(self):
"""Returns a dictionary of just the db fields of this entity."""
if self.created_at:
created_at = self.created_at.isoformat()
else:
created_at = self.created_at
if self.updated_at:
updated_at = self.updated_at.isoformat()
else:
updated_at = self.updated_at
dict_fields = {
'created': created_at,
'updated': updated_at,
'status': self.status
}
if self.deleted_at:
dict_fields['deleted_at'] = self.deleted_at.isoformat()
if self.deleted:
dict_fields['deleted'] = True
dict_fields.update(self._do_extra_dict_fields())
return dict_fields
def _do_extra_dict_fields(self):
"""Sub-class hook method: return dict of fields."""
return {}
def _iso_to_datetime(self, expiration):
"""Convert ISO formatted string to datetime."""
if isinstance(expiration, six.string_types):
expiration_iso = timeutils.parse_isotime(expiration.strip())
expiration = timeutils.normalize_time(expiration_iso)
return expiration
class SoftDeleteMixIn(object):
"""Mix-in class that adds soft delete functionality."""
def delete(self, session=None):
"""Delete this object."""
import barbican.model.repositories
session = session or barbican.model.repositories.get_session()
self.deleted = True
self.deleted_at = timeutils.utcnow()
self.save(session=session)
self._do_delete_children(session)
class ContainerSecret(BASE, SoftDeleteMixIn, ModelBase):
"""Represents an association between a Container and a Secret."""
__tablename__ = 'container_secret'
name = sa.Column(sa.String(255), nullable=True)
container_id = sa.Column(
sa.String(36), sa.ForeignKey('containers.id'), index=True,
nullable=False)
secret_id = sa.Column(
sa.String(36), sa.ForeignKey('secrets.id'), index=True, nullable=False)
# Eager load this relationship via 'lazy=False'.
container = orm.relationship(
'Container',
backref=orm.backref('container_secrets', lazy=False,
primaryjoin="and_(ContainerSecret.container_id == "
"Container.id, ContainerSecret.deleted!=True)"))
secrets = orm.relationship(
'Secret',
backref=orm.backref('container_secrets',
primaryjoin="and_(ContainerSecret.secret_id == "
"Secret.id, ContainerSecret.deleted!=True)"))
__table_args__ = (sa.UniqueConstraint('container_id', 'secret_id', 'name',
name='_container_secret_name_uc'),)
class Project(BASE, SoftDeleteMixIn, ModelBase):
"""Represents a Project in the datastore.
Projects are users that wish to store secret information within Barbican.
"""
__tablename__ = 'projects'
external_id = sa.Column(sa.String(255), unique=True)
orders = orm.relationship("Order", backref="project")
secrets = orm.relationship("Secret", backref="project")
keks = orm.relationship("KEKDatum", backref="project")
containers = orm.relationship("Container", backref="project")
cas = orm.relationship("ProjectCertificateAuthority", backref="project")
project_quotas = orm.relationship("ProjectQuotas", backref="project")
def _do_extra_dict_fields(self):
"""Sub-class hook method: return dict of fields."""
return {'external_id': self.external_id}
class Secret(BASE, SoftDeleteMixIn, ModelBase):
"""Represents a Secret in the datastore.
Secrets are any information Projects wish to store within
Barbican, though the actual encrypted data is stored in one
or more EncryptedData entities on behalf of a Secret.
"""
__tablename__ = 'secrets'
name = sa.Column(sa.String(255))
secret_type = sa.Column(sa.String(255),
server_default=utils.SECRET_TYPE_OPAQUE)
expiration = sa.Column(sa.DateTime, default=None)
algorithm = sa.Column(sa.String(255))
bit_length = sa.Column(sa.Integer)
mode = sa.Column(sa.String(255))
creator_id = sa.Column(sa.String(255))
project_id = sa.Column(
sa.String(36),
sa.ForeignKey('projects.id', name='secrets_project_fk'),
index=True,
nullable=False)
# TODO(jwood): Performance - Consider avoiding full load of all
# datum attributes here. This is only being done to support the
# building of the list of supported content types when secret
# metadata is retrieved.
# See barbican.api.resources.py::SecretsResource.on_get()
# Eager load this relationship via 'lazy=False'.
encrypted_data = orm.relationship("EncryptedDatum", lazy=False)
secret_store_metadata = orm.relationship(
"SecretStoreMetadatum",
collection_class=col.attribute_mapped_collection('key'),
backref="secret",
cascade="all, delete-orphan")
secret_user_metadata = orm.relationship(
"SecretUserMetadatum",
collection_class=col.attribute_mapped_collection('key'),
backref="secret",
cascade="all, delete-orphan")
def __init__(self, parsed_request=None):
"""Creates secret from a dict."""
super(Secret, self).__init__()
if parsed_request:
self.name = parsed_request.get('name')
self.secret_type = parsed_request.get(
'secret_type',
utils.SECRET_TYPE_OPAQUE)
expiration = self._iso_to_datetime(parsed_request.get
('expiration'))
self.expiration = expiration
self.algorithm = parsed_request.get('algorithm')
self.bit_length = parsed_request.get('bit_length')
self.mode = parsed_request.get('mode')
self.creator_id = parsed_request.get('creator_id')
self.project_id = parsed_request.get('project_id')
self.status = States.ACTIVE
def _do_delete_children(self, session):
"""Sub-class hook: delete children relationships."""
for k, v in self.secret_store_metadata.items():
v.delete(session)
for k, v in self.secret_user_metadata.items():
v.delete(session)
for datum in self.encrypted_data:
datum.delete(session)
for secret_ref in self.container_secrets:
session.delete(secret_ref)
for secret_acl in self.secret_acls:
session.delete(secret_acl)
def _do_extra_dict_fields(self):
"""Sub-class hook method: return dict of fields."""
if self.expiration:
expiration = self.expiration.isoformat()
else:
expiration = self.expiration
return {
'secret_id': self.id,
'name': self.name,
'secret_type': self.secret_type,
'expiration': expiration,
'algorithm': self.algorithm,
'bit_length': self.bit_length,
'mode': self.mode,
'creator_id': self.creator_id,
}
class SecretStoreMetadatum(BASE, SoftDeleteMixIn, ModelBase):
"""Represents Secret Store metadatum for a single key-value pair."""
__tablename__ = "secret_store_metadata"
key = sa.Column(sa.String(255), nullable=False)
value = sa.Column(sa.String(255), nullable=False)
secret_id = sa.Column(
sa.String(36), sa.ForeignKey('secrets.id'), index=True, nullable=False)
def __init__(self, key, value):
super(SecretStoreMetadatum, self).__init__()
msg = u._("Must supply non-None {0} argument "
"for SecretStoreMetadatum entry.")
if key is None:
raise exception.MissingArgumentError(msg.format("key"))
self.key = key
if value is None:
raise exception.MissingArgumentError(msg.format("value"))
self.value = value
def _do_extra_dict_fields(self):
"""Sub-class hook method: return dict of fields."""
return {
'key': self.key,
'value': self.value
}
class SecretUserMetadatum(BASE, SoftDeleteMixIn, ModelBase):
"""Represents Secret user metadatum for a single key-value pair."""
__tablename__ = "secret_user_metadata"
key = sa.Column(sa.String(255), nullable=False)
value = sa.Column(sa.String(255), nullable=False)
secret_id = sa.Column(
sa.String(36), sa.ForeignKey('secrets.id'), index=True, nullable=False)
__table_args__ = (sa.UniqueConstraint('secret_id', 'key',
name='_secret_key_uc'),)
def __init__(self, key, value):
super(SecretUserMetadatum, self).__init__()
msg = u._("Must supply non-None {0} argument "
"for SecretUserMetadatum entry.")
if key is None:
raise exception.MissingArgumentError(msg.format("key"))
self.key = key
if value is None:
raise exception.MissingArgumentError(msg.format("value"))
self.value = value
def _do_extra_dict_fields(self):
"""Sub-class hook method: return dict of fields."""
return {
'key': self.key,
'value': self.value
}
class EncryptedDatum(BASE, SoftDeleteMixIn, ModelBase):
"""Represents the encrypted data for a Secret."""
__tablename__ = 'encrypted_data'
content_type = sa.Column(sa.String(255))
secret_id = sa.Column(
sa.String(36), sa.ForeignKey('secrets.id'), index=True, nullable=False)
kek_id = sa.Column(
sa.String(36), sa.ForeignKey('kek_data.id'), index=True,
nullable=False)
# TODO(jwood) Why LargeBinary on Postgres (BYTEA) not work correctly?
cypher_text = sa.Column(sa.Text)
kek_meta_extended = sa.Column(sa.Text)
# Eager load this relationship via 'lazy=False'.
kek_meta_project = orm.relationship("KEKDatum", lazy=False)
def __init__(self, secret=None, kek_datum=None):
"""Creates encrypted datum from a secret and KEK metadata."""
super(EncryptedDatum, self).__init__()
if secret:
self.secret_id = secret.id
if kek_datum:
self.kek_id = kek_datum.id
self.kek_meta_project = kek_datum
self.status = States.ACTIVE
def _do_extra_dict_fields(self):
"""Sub-class hook method: return dict of fields."""
return {'content_type': self.content_type}
class KEKDatum(BASE, SoftDeleteMixIn, ModelBase):
"""Key encryption key (KEK) metadata model.
Represents the key encryption key (KEK) metadata associated with a process
used to encrypt/decrypt secret information.
When a secret is encrypted, in addition to the cypher text, the Barbican
encryption process produces a KEK metadata object. The cypher text is
stored via the EncryptedDatum model above, whereas the metadata is stored
within this model. Decryption processes utilize this KEK metadata
to decrypt the associated cypher text.
Note that this model is intended to be agnostic to the specific means used
to encrypt/decrypt the secret information, so please do not place vendor-
specific attributes here.
Note as well that each Project will have at most one 'active=True' KEKDatum
instance at a time, representing the most recent KEK metadata instance
to use for encryption processes performed on behalf of the Project.
KEKDatum instances that are 'active=False' are associated to previously
used encryption processes for the Project, that eventually should be
rotated and deleted with the Project's active KEKDatum.
"""
__tablename__ = 'kek_data'
plugin_name = sa.Column(sa.String(255), nullable=False)
kek_label = sa.Column(sa.String(255))
project_id = sa.Column(
sa.String(36),
sa.ForeignKey('projects.id', name='kek_data_project_fk'),
index=True,
nullable=False)
active = sa.Column(sa.Boolean, nullable=False, default=True)
bind_completed = sa.Column(sa.Boolean, nullable=False, default=False)
algorithm = sa.Column(sa.String(255))
bit_length = sa.Column(sa.Integer)
mode = sa.Column(sa.String(255))
plugin_meta = sa.Column(sa.Text)
def _do_extra_dict_fields(self):
"""Sub-class hook method: return dict of fields."""
return {'algorithm': self.algorithm}
class Order(BASE, SoftDeleteMixIn, ModelBase):
"""Represents an Order in the datastore.
Orders are requests for Barbican to generate secrets,
ranging from symmetric, asymmetric keys to automated
requests to Certificate Authorities to generate SSL
certificates.
"""
__tablename__ = 'orders'
type = sa.Column(sa.String(255), nullable=False, default='key')
project_id = sa.Column(
sa.String(36),
sa.ForeignKey('projects.id', name='orders_project_fk'),
index=True,
nullable=False)
error_status_code = sa.Column(sa.String(16))
error_reason = sa.Column(sa.String(ERROR_REASON_LENGTH))
meta = sa.Column(JsonBlob(), nullable=True)
secret_id = sa.Column(sa.String(36), sa.ForeignKey('secrets.id'),
index=True, nullable=True)
container_id = sa.Column(sa.String(36), sa.ForeignKey('containers.id'),
index=True, nullable=True)
sub_status = sa.Column(sa.String(SUB_STATUS_LENGTH), nullable=True)
sub_status_message = sa.Column(sa.String(SUB_STATUS_MESSAGE_LENGTH),
nullable=True)
creator_id = sa.Column(sa.String(255))
order_plugin_metadata = orm.relationship(
"OrderPluginMetadatum",
collection_class=col.attribute_mapped_collection('key'),
backref="order",
cascade="all, delete-orphan")
order_barbican_metadata = orm.relationship(
"OrderBarbicanMetadatum",
collection_class=col.attribute_mapped_collection('key'),
backref="order",
cascade="all, delete-orphan")
def __init__(self, parsed_request=None):
"""Creates a Order entity from a dict."""
super(Order, self).__init__()
if parsed_request:
self.type = parsed_request.get('type')
self.meta = parsed_request.get('meta')
self.status = States.ACTIVE
self.sub_status = parsed_request.get('sub_status')
self.sub_status_message = parsed_request.get(
'sub_status_message')
self.creator_id = parsed_request.get('creator_id')
def set_error_reason_safely(self, error_reason_raw):
"""Ensure error reason does not raise database attribute exceptions."""
self.error_reason = error_reason_raw[:ERROR_REASON_LENGTH]
def set_sub_status_safely(self, sub_status_raw):
"""Ensure sub-status does not raise database attribute exceptions."""
self.sub_status = sub_status_raw[:SUB_STATUS_LENGTH]
def set_sub_status_message_safely(self, sub_status_message_raw):
"""Ensure status message doesn't raise database attrib. exceptions."""
self.sub_status_message = sub_status_message_raw[
:SUB_STATUS_MESSAGE_LENGTH
]
def _do_delete_children(self, session):
"""Sub-class hook: delete children relationships."""
for k, v in self.order_plugin_metadata.items():
v.delete(session)
for k, v in self.order_barbican_metadata.items():
v.delete(session)
def _do_extra_dict_fields(self):
"""Sub-class hook method: return dict of fields."""
ret = {
'type': self.type,
'meta': self.meta,
'order_id': self.id
}
if self.secret_id:
ret['secret_id'] = self.secret_id
if self.container_id:
ret['container_id'] = self.container_id
if self.error_status_code:
ret['error_status_code'] = self.error_status_code
if self.error_reason:
ret['error_reason'] = self.error_reason
if self.sub_status:
ret['sub_status'] = self.sub_status
if self.sub_status_message:
ret['sub_status_message'] = self.sub_status_message
if self.creator_id:
ret['creator_id'] = self.creator_id
return ret
class OrderPluginMetadatum(BASE, SoftDeleteMixIn, ModelBase):
"""Represents Order plugin metadatum for a single key-value pair.
This entity is used to store plugin-specific metadata on behalf of an
Order instance.
"""
__tablename__ = "order_plugin_metadata"
order_id = sa.Column(sa.String(36), sa.ForeignKey('orders.id'),
index=True, nullable=False)
key = sa.Column(sa.String(255), nullable=False)
value = sa.Column(sa.String(255), nullable=False)
def __init__(self, key, value):
super(OrderPluginMetadatum, self).__init__()
msg = u._("Must supply non-None {0} argument "
"for OrderPluginMetadatum entry.")
if key is None:
raise exception.MissingArgumentError(msg.format("key"))
self.key = key
if value is None:
raise exception.MissingArgumentError(msg.format("value"))
self.value = value
def _do_extra_dict_fields(self):
"""Sub-class hook method: return dict of fields."""
return {'key': self.key,
'value': self.value}
class OrderBarbicanMetadatum(BASE, SoftDeleteMixIn, ModelBase):
"""Represents Order barbican metadatum for a single key-value pair.
This entity is used to store barbican-specific metadata on behalf of an
Order instance. This is data that is stored by the server to help
process the order through its life cycle, but which is not in the original
request.
"""
__tablename__ = "order_barbican_metadata"
order_id = sa.Column(sa.String(36), sa.ForeignKey('orders.id'),
index=True, nullable=False)
key = sa.Column(sa.String(255), nullable=False)
value = sa.Column(sa.Text, nullable=False)
def __init__(self, key, value):
super(OrderBarbicanMetadatum, self).__init__()
msg = u._("Must supply non-None {0} argument "
"for OrderBarbicanMetadatum entry.")
if key is None:
raise exception.MissingArgumentError(msg.format("key"))
self.key = key
if value is None:
raise exception.MissingArgumentError(msg.format("value"))
self.value = value
def _do_extra_dict_fields(self):
"""Sub-class hook method: return dict of fields."""
return {'key': self.key,
'value': self.value}
class OrderRetryTask(BASE, SoftDeleteMixIn, ModelBase):
__tablename__ = "order_retry_tasks"
__table_args__ = {"mysql_engine": "InnoDB"}
__table_initialized__ = False
id = sa.Column(
sa.String(36), primary_key=True, default=utils.generate_uuid,
)
order_id = sa.Column(
sa.String(36), sa.ForeignKey("orders.id"), index=True, nullable=False,
)
retry_task = sa.Column(sa.Text, nullable=False)
retry_at = sa.Column(sa.DateTime, default=None, nullable=False)
retry_args = sa.Column(JsonBlob(), nullable=False)
retry_kwargs = sa.Column(JsonBlob(), nullable=False)
retry_count = sa.Column(sa.Integer, nullable=False, default=0)
class Container(BASE, SoftDeleteMixIn, ModelBase):
"""Represents a Container for Secrets in the datastore.
Containers store secret references. Containers are owned by Projects.
Containers can be generic or have a predefined type. Predefined typed
containers allow users to store structured key relationship
inside Barbican.
"""
__tablename__ = 'containers'
name = sa.Column(sa.String(255))
type = sa.Column(sa.Enum('generic', 'rsa', 'dsa', 'certificate',
name='container_types'))
project_id = sa.Column(
sa.String(36),
sa.ForeignKey('projects.id', name='containers_project_fk'),
index=True,
nullable=False)
consumers = sa.orm.relationship("ContainerConsumerMetadatum")
creator_id = sa.Column(sa.String(255))
def __init__(self, parsed_request=None):
"""Creates a Container entity from a dict."""
super(Container, self).__init__()
if parsed_request:
self.name = parsed_request.get('name')
self.type = parsed_request.get('type')
self.status = States.ACTIVE
self.creator_id = parsed_request.get('creator_id')
secret_refs = parsed_request.get('secret_refs')
if secret_refs:
for secret_ref in parsed_request.get('secret_refs'):
container_secret = ContainerSecret()
container_secret.name = secret_ref.get('name')
# TODO(hgedikli) move this into a common location
# TODO(hgedikli) validate provided url
# TODO(hgedikli) parse out secret_id with regex
secret_id = secret_ref.get('secret_ref')
if secret_id.endswith('/'):
secret_id = secret_id.rsplit('/', 2)[1]
elif '/' in secret_id:
secret_id = secret_id.rsplit('/', 1)[1]
else:
secret_id = secret_id
container_secret.secret_id = secret_id
self.container_secrets.append(container_secret)
def _do_delete_children(self, session):
"""Sub-class hook: delete children relationships."""
for container_secret in self.container_secrets:
session.delete(container_secret)
for container_acl in self.container_acls:
session.delete(container_acl)
def _do_extra_dict_fields(self):
"""Sub-class hook method: return dict of fields."""
return {'container_id': self.id,
'name': self.name,
'type': self.type,
'creator_id': self.creator_id,
'secret_refs': [
{
'secret_id': container_secret.secret_id,
'name': container_secret.name
if hasattr(container_secret, 'name') else None
} for container_secret in self.container_secrets],
'consumers': [
{
'name': consumer.name,
'URL': consumer.URL
} for consumer in self.consumers if not consumer.deleted
]}
class ContainerConsumerMetadatum(BASE, SoftDeleteMixIn, ModelBase):
"""Stores Consumer Registrations for Containers in the datastore.
Services can register interest in Containers. Services will provide a type
and a URL for the object that is using the Container.
"""
__tablename__ = 'container_consumer_metadata'
container_id = sa.Column(sa.String(36), sa.ForeignKey('containers.id'),
index=True, nullable=False)
project_id = sa.Column(sa.String(36), sa.ForeignKey('projects.id'),
index=True, nullable=True)
name = sa.Column(sa.String(36))
URL = sa.Column(sa.String(255))
data_hash = sa.Column(sa.CHAR(64))
__table_args__ = (
sa.UniqueConstraint('data_hash',
name='_consumer_hashed_container_name_url_uc'),
sa.Index('values_index', 'container_id', 'name', 'URL')
)
def __init__(self, container_id, project_id, parsed_request):
"""Registers a Consumer to a Container."""
super(ContainerConsumerMetadatum, self).__init__()
# TODO(john-wood-w) This class should really be immutable due to the
# data_hash attribute.
if container_id and parsed_request:
self.container_id = container_id
self.project_id = project_id
self.name = parsed_request.get('name')
self.URL = parsed_request.get('URL')
hash_text = ''.join((self.container_id, self.name, self.URL))
self.data_hash = hashlib.sha256(hash_text.
encode('utf-8')).hexdigest()
self.status = States.ACTIVE
def _do_extra_dict_fields(self):
"""Sub-class hook method: return dict of fields."""
return {'name': self.name,
'URL': self.URL}
class TransportKey(BASE, SoftDeleteMixIn, ModelBase):
"""Transport Key model for wrapping secrets in transit
Represents the transport key used for wrapping secrets in transit
to/from clients when storing/retrieving secrets.
"""
__tablename__ = 'transport_keys'
plugin_name = sa.Column(sa.String(255), nullable=False)
transport_key = sa.Column(sa.Text, nullable=False)
def __init__(self, plugin_name, transport_key):
"""Creates transport key entity."""
super(TransportKey, self).__init__()
msg = u._("Must supply non-None {0} argument for TransportKey entry.")
if plugin_name is None:
raise exception.MissingArgumentError(msg.format("plugin_name"))
self.plugin_name = plugin_name
if transport_key is None:
raise exception.MissingArgumentError(msg.format("transport_key"))
self.transport_key = transport_key
self.status = States.ACTIVE
def _do_extra_dict_fields(self):
"""Sub-class hook method: return dict of fields."""
return {'transport_key_id': self.id,
'plugin_name': self.plugin_name}
class CertificateAuthority(BASE, ModelBase):
"""CertificateAuthority model to specify the CAs available to Barbican
Represents the CAs available for certificate issuance to Barbican.
"""
__tablename__ = 'certificate_authorities'
plugin_name = sa.Column(sa.String(255), nullable=False)
plugin_ca_id = sa.Column(sa.Text, nullable=False)
expiration = sa.Column(sa.DateTime, default=None)
creator_id = sa.Column(sa.String(255), nullable=True)
project_id = sa.Column(
sa.String(36),
sa.ForeignKey('projects.id', name='cas_project_fk'),
nullable=True)
ca_meta = orm.relationship(
'CertificateAuthorityMetadatum',
collection_class=col.attribute_mapped_collection('key'),
backref="ca",
cascade="all, delete-orphan"
)
def __init__(self, parsed_ca_in):
"""Creates certificate authority entity."""
super(CertificateAuthority, self).__init__()
msg = u._("Must supply Non-None {0} argument "
"for CertificateAuthority entry.")
parsed_ca = dict(parsed_ca_in)
plugin_name = parsed_ca.pop('plugin_name', None)
if plugin_name is None:
raise exception.MissingArgumentError(msg.format("plugin_name"))
self.plugin_name = plugin_name
plugin_ca_id = parsed_ca.pop('plugin_ca_id', None)
if plugin_ca_id is None:
raise exception.MissingArgumentError(msg.format("plugin_ca_id"))
self.plugin_ca_id = plugin_ca_id
expiration = parsed_ca.pop('expiration', None)
self.expiration = self._iso_to_datetime(expiration)
creator_id = parsed_ca.pop('creator_id', None)
if creator_id is not None:
self.creator_id = creator_id
project_id = parsed_ca.pop('project_id', None)
if project_id is not None:
self.project_id = project_id
for key in parsed_ca:
meta = CertificateAuthorityMetadatum(key, parsed_ca[key])
self.ca_meta[key] = meta
self.status = States.ACTIVE
def _do_delete_children(self, session):
"""Sub-class hook: delete children relationships."""
for k, v in self.ca_meta.items():
v.delete(session)
def _do_extra_dict_fields(self):
"""Sub-class hook method: return dict of fields."""
if self.expiration:
expiration = self.expiration.isoformat()
else:
expiration = None
return {
'ca_id': self.id,
'plugin_name': self.plugin_name,
'plugin_ca_id': self.plugin_ca_id,
'expiration': expiration,
'meta': [
{
meta['key']: meta['value']
} for key, meta in self.ca_meta.items()
]
}
class CertificateAuthorityMetadatum(BASE, ModelBase):
"""Represents CA metadatum for a single key-value pair."""
__tablename__ = "certificate_authority_metadata"
key = sa.Column(sa.String(255), index=True, nullable=False)
value = sa.Column(sa.Text, nullable=False)
ca_id = sa.Column(
sa.String(36), sa.ForeignKey('certificate_authorities.id'),
index=True, nullable=False)
__table_args__ = (sa.UniqueConstraint(
'ca_id', 'key', name='_certificate_authority_metadatum_uc'),)
def __init__(self, key, value):
super(CertificateAuthorityMetadatum, self).__init__()
msg = u._("Must supply non-None {0} argument "
"for CertificateAuthorityMetadatum entry.")
if key is None:
raise exception.MissingArgumentError(msg.format("key"))
self.key = key
if value is None:
raise exception.MissingArgumentError(msg.format("value"))
self.value = value
def _do_extra_dict_fields(self):
"""Sub-class hook method: return dict of fields."""
return {
'key': self.key,
'value': self.value
}
class ProjectCertificateAuthority(BASE, ModelBase):
"""Stores CAs available for a project.
Admins can define a set of CAs that are available for use in a particular
project. There can be multiple entries for any given project.
"""
__tablename__ = 'project_certificate_authorities'
project_id = sa.Column(sa.String(36),
sa.ForeignKey('projects.id'),
index=True,
nullable=False)
ca_id = sa.Column(sa.String(36),