9a934e57eb
This change breaks several gates, including RDO's package promotion.
This reverts commit f06ba48195
.
Change-Id: I0524b7057016daa59ea0a506bdc50a71e9fc8f6a
1479 lines
51 KiB
Python
1479 lines
51 KiB
Python
# 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),
|
|
sa.ForeignKey('certificate_authorities.id'),
|
|
index=True,
|
|
nullable=False)
|
|
|
|
ca = orm.relationship("CertificateAuthority", backref="project_cas")
|
|
|
|
__table_args__ = (sa.UniqueConstraint(
|
|
'project_id', 'ca_id', name='_project_certificate_authority_uc'),)
|
|
|
|
def __init__(self, project_id, ca_id):
|
|
"""Registers a Consumer to a Container."""
|
|
super(ProjectCertificateAuthority, self).__init__()
|
|
|
|
msg = u._("Must supply non-None {0} argument "
|
|
"for ProjectCertificateAuthority entry.")
|
|
|
|
if project_id is None:
|
|
raise exception.MissingArgumentError(msg.format("project_id"))
|
|
self.project_id = project_id
|
|
|
|
if ca_id is None:
|
|
raise exception.MissingArgumentError(msg.format("ca_id"))
|
|
self.ca_id = ca_id
|
|
|
|
self.status = States.ACTIVE
|
|
|
|
def _do_extra_dict_fields(self):
|
|
"""Sub-class hook method: return dict of fields."""
|
|
return {'project_id': self.project_id,
|
|
'ca_id': self.ca_id}
|
|
|
|
|
|
class PreferredCertificateAuthority(BASE, ModelBase):
|
|
"""Stores preferred CAs for any project.
|
|
|
|
Admins can define a set of CAs available for issuance requests for
|
|
any project in the ProjectCertificateAuthority table..
|
|
"""
|
|
|
|
__tablename__ = 'preferred_certificate_authorities'
|
|
|
|
project_id = sa.Column(sa.String(36),
|
|
sa.ForeignKey('projects.id'),
|
|
index=True,
|
|
unique=True,
|
|
nullable=False)
|
|
|
|
ca_id = sa.Column(sa.String(36),
|
|
sa.ForeignKey(
|
|
'certificate_authorities.id',
|
|
name='preferred_certificate_authorities_fk'),
|
|
index=True,
|
|
nullable=False)
|
|
|
|
project = orm.relationship('Project',
|
|
backref=orm.backref('preferred_ca'),
|
|
uselist=False)
|
|
|
|
ca = orm.relationship('CertificateAuthority',
|
|
backref=orm.backref('preferred_ca'))
|
|
|
|
def __init__(self, project_id, ca_id):
|
|
"""Registers a Consumer to a Container."""
|
|
super(PreferredCertificateAuthority, self).__init__()
|
|
|
|
msg = u._("Must supply non-None {0} argument "
|
|
"for PreferredCertificateAuthority entry.")
|
|
|
|
if project_id is None:
|
|
raise exception.MissingArgumentError(msg.format("project_id"))
|
|
self.project_id = project_id
|
|
|
|
if ca_id is None:
|
|
raise exception.MissingArgumentError(msg.format("ca_id"))
|
|
self.ca_id = ca_id
|
|
|
|
self.status = States.ACTIVE
|
|
|
|
def _do_extra_dict_fields(self):
|
|
"""Sub-class hook method: return dict of fields."""
|
|
return {'project_id': self.project_id,
|
|
'ca_id': self.ca_id}
|
|
|
|
|
|
class SecretACL(BASE, ModelBase):
|
|
"""Stores Access Control List (ACL) for a secret.
|
|
|
|
Class to define whitelist of user ids who are allowed specific operation
|
|
on a secret. List of user ids is defined via SecretACLUser via
|
|
acl_users association.
|
|
Creator_only flag helps in making a secret private for
|
|
non-admin project users who may have access otherwise.
|
|
|
|
SecretACL deletes are not soft-deletes.
|
|
"""
|
|
|
|
__tablename__ = 'secret_acls'
|
|
|
|
secret_id = sa.Column(sa.String(36), sa.ForeignKey('secrets.id'),
|
|
index=True, nullable=False)
|
|
|
|
operation = sa.Column(sa.String(255), nullable=False)
|
|
|
|
project_access = sa.Column(sa.Boolean, nullable=False, default=True)
|
|
|
|
secret = orm.relationship(
|
|
'Secret', backref=orm.backref('secret_acls', lazy=False))
|
|
|
|
acl_users = orm.relationship(
|
|
'SecretACLUser', backref=orm.backref('secret_acl', lazy=False),
|
|
cascade="all, delete-orphan")
|
|
|
|
__table_args__ = (sa.UniqueConstraint(
|
|
'secret_id', 'operation', name='_secret_acl_operation_uc'),)
|
|
|
|
def __init__(self, secret_id, operation, project_access=None,
|
|
user_ids=None):
|
|
"""Creates secret ACL entity."""
|
|
super(SecretACL, self).__init__()
|
|
|
|
msg = u._("Must supply non-None {0} argument for SecretACL entry.")
|
|
|
|
if secret_id is None:
|
|
raise exception.MissingArgumentError(msg.format("secret_id"))
|
|
self.secret_id = secret_id
|
|
|
|
if operation is None:
|
|
raise exception.MissingArgumentError(msg.format("operation"))
|
|
self.operation = operation
|
|
|
|
if project_access is not None:
|
|
self.project_access = project_access
|
|
self.status = States.ACTIVE
|
|
if user_ids is not None and isinstance(user_ids, list):
|
|
userids = set(user_ids) # remove duplicate if any
|
|
for user_id in userids:
|
|
acl_user = SecretACLUser(self.id, user_id)
|
|
self.acl_users.append(acl_user)
|
|
|
|
def _do_delete_children(self, session):
|
|
"""Sub-class hook: delete children relationships."""
|
|
for acl_user in self.acl_users:
|
|
acl_user.delete(session)
|
|
|
|
def _do_extra_dict_fields(self):
|
|
"""Sub-class hook method: return dict of fields.
|
|
|
|
Adds non-deleted acl related users from relationship if there.
|
|
"""
|
|
users = [acl_user.user_id for acl_user in self.acl_users
|
|
if not acl_user.deleted]
|
|
fields = {'acl_id': self.id,
|
|
'secret_id': self.secret_id,
|
|
'operation': self.operation,
|
|
'project_access': self.project_access}
|
|
if users:
|
|
fields['users'] = users
|
|
return fields
|
|
|
|
|
|
class ContainerACL(BASE, ModelBase):
|
|
"""Stores Access Control List (ACL) for a container.
|
|
|
|
Class to define whitelist of user ids who are allowed specific operation
|
|
on a container. List of user ids is defined in ContainerACLUser via
|
|
acl_users association.
|
|
Creator_only flag helps in making a container private for
|
|
non-admin project users who may have access otherwise.
|
|
|
|
ContainerACL deletes are not soft-deletes.
|
|
"""
|
|
|
|
__tablename__ = 'container_acls'
|
|
|
|
container_id = sa.Column(sa.String(36), sa.ForeignKey('containers.id'),
|
|
index=True, nullable=False)
|
|
|
|
operation = sa.Column(sa.String(255), nullable=False)
|
|
|
|
project_access = sa.Column(sa.Boolean, nullable=False, default=True)
|
|
|
|
container = orm.relationship(
|
|
'Container', backref=orm.backref('container_acls', lazy=False))
|
|
|
|
acl_users = orm.relationship(
|
|
'ContainerACLUser', backref=orm.backref('container_acl', lazy=False),
|
|
cascade="all, delete-orphan")
|
|
|
|
__table_args__ = (sa.UniqueConstraint(
|
|
'container_id', 'operation', name='_container_acl_operation_uc'),)
|
|
|
|
def __init__(self, container_id, operation, project_access=None,
|
|
user_ids=None):
|
|
"""Creates container ACL entity."""
|
|
super(ContainerACL, self).__init__()
|
|
|
|
msg = u._("Must supply non-None {0} argument for ContainerACL entry.")
|
|
|
|
if container_id is None:
|
|
raise exception.MissingArgumentError(msg.format("container_id"))
|
|
self.container_id = container_id
|
|
|
|
if operation is None:
|
|
raise exception.MissingArgumentError(msg.format("operation"))
|
|
self.operation = operation
|
|
|
|
if project_access is not None:
|
|
self.project_access = project_access
|
|
self.status = States.ACTIVE
|
|
|
|
if user_ids is not None and isinstance(user_ids, list):
|
|
userids = set(user_ids) # remove duplicate if any
|
|
for user_id in userids:
|
|
acl_user = ContainerACLUser(self.id, user_id)
|
|
self.acl_users.append(acl_user)
|
|
|
|
def _do_delete_children(self, session):
|
|
"""Sub-class hook: delete children relationships."""
|
|
for acl_user in self.acl_users:
|
|
acl_user.delete(session)
|
|
|
|
def _do_extra_dict_fields(self):
|
|
"""Sub-class hook method: return dict of fields.
|
|
|
|
Adds non-deleted acl related users from relationship if there.
|
|
"""
|
|
users = [acl_user.user_id for acl_user in self.acl_users
|
|
if not acl_user.deleted]
|
|
fields = {'acl_id': self.id,
|
|
'container_id': self.container_id,
|
|
'operation': self.operation,
|
|
'project_access': self.project_access}
|
|
if users:
|
|
fields['users'] = users
|
|
return fields
|
|
|
|
|
|
class SecretACLUser(BASE, ModelBase):
|
|
"""Stores user id for a secret ACL.
|
|
|
|
This class provides way to store list of users associated with a
|
|
specific ACL operation.
|
|
|
|
SecretACLUser deletes are not soft-deletes.
|
|
"""
|
|
|
|
__tablename__ = 'secret_acl_users'
|
|
|
|
acl_id = sa.Column(sa.String(36), sa.ForeignKey('secret_acls.id'),
|
|
index=True, nullable=False)
|
|
|
|
user_id = sa.Column(sa.String(255), nullable=False)
|
|
|
|
__table_args__ = (sa.UniqueConstraint(
|
|
'acl_id', 'user_id', name='_secret_acl_user_uc'),)
|
|
|
|
def __init__(self, acl_id, user_id):
|
|
"""Creates secret ACL user entity."""
|
|
super(SecretACLUser, self).__init__()
|
|
|
|
msg = u._("Must supply non-None {0} argument for SecretACLUser entry.")
|
|
|
|
self.acl_id = acl_id
|
|
if user_id is None:
|
|
raise exception.MissingArgumentError(msg.format("user_id"))
|
|
self.user_id = user_id
|
|
self.status = States.ACTIVE
|
|
|
|
def _do_extra_dict_fields(self):
|
|
"""Sub-class hook method: return dict of fields."""
|
|
return {'acl_id': self.acl_id,
|
|
'user_id': self.user_id}
|
|
|
|
|
|
class ContainerACLUser(BASE, ModelBase):
|
|
"""Stores user id for a container ACL.
|
|
|
|
This class provides way to store list of users associated with a
|
|
specific ACL operation.
|
|
|
|
ContainerACLUser deletes are not soft-deletes.
|
|
"""
|
|
|
|
__tablename__ = 'container_acl_users'
|
|
|
|
acl_id = sa.Column(sa.String(36), sa.ForeignKey('container_acls.id'),
|
|
index=True, nullable=False)
|
|
|
|
user_id = sa.Column(sa.String(255), nullable=False)
|
|
|
|
__table_args__ = (sa.UniqueConstraint(
|
|
'acl_id', 'user_id', name='_container_acl_user_uc'),)
|
|
|
|
def __init__(self, acl_id, user_id):
|
|
"""Creates container ACL user entity."""
|
|
super(ContainerACLUser, self).__init__()
|
|
|
|
msg = u._("Must supply non-None {0} argument for ContainerACLUser "
|
|
"entry.")
|
|
|
|
self.acl_id = acl_id
|
|
if user_id is None:
|
|
raise exception.MissingArgumentError(msg.format("user_id"))
|
|
self.user_id = user_id
|
|
self.status = States.ACTIVE
|
|
|
|
def _do_extra_dict_fields(self):
|
|
"""Sub-class hook method: return dict of fields."""
|
|
return {'acl_id': self.acl_id,
|
|
'user_id': self.user_id}
|
|
|
|
|
|
class ProjectQuotas(BASE, ModelBase):
|
|
"""Stores Project Quotas.
|
|
|
|
Class to define project specific resource quotas.
|
|
|
|
Project quota deletes are not soft-deletes.
|
|
"""
|
|
|
|
__tablename__ = 'project_quotas'
|
|
|
|
project_id = sa.Column(
|
|
sa.String(36),
|
|
sa.ForeignKey('projects.id', name='project_quotas_fk'),
|
|
index=True,
|
|
nullable=False)
|
|
secrets = sa.Column(sa.Integer, nullable=True)
|
|
orders = sa.Column(sa.Integer, nullable=True)
|
|
containers = sa.Column(sa.Integer, nullable=True)
|
|
consumers = sa.Column(sa.Integer, nullable=True)
|
|
cas = sa.Column(sa.Integer, nullable=True)
|
|
|
|
def __init__(self, project_id=None, parsed_project_quotas=None):
|
|
"""Creates Project Quotas entity from a project and a dict.
|
|
|
|
:param project_id: the internal id of the project with quotas
|
|
:param parsed_project_quotas: a dict with the keys matching the
|
|
resources for which quotas are to be set, and the values containing
|
|
the quota value to be set for this project and that resource.
|
|
:return: None
|
|
"""
|
|
super(ProjectQuotas, self).__init__()
|
|
|
|
msg = u._("Must supply non-None {0} argument for ProjectQuotas entry.")
|
|
|
|
if project_id is None:
|
|
raise exception.MissingArgumentError(msg.format("project_id"))
|
|
self.project_id = project_id
|
|
|
|
if parsed_project_quotas is None:
|
|
self.secrets = None
|
|
self.orders = None
|
|
self.containers = None
|
|
self.consumers = None
|
|
self.cas = None
|
|
else:
|
|
self.secrets = parsed_project_quotas.get('secrets')
|
|
self.orders = parsed_project_quotas.get('orders')
|
|
self.containers = parsed_project_quotas.get('containers')
|
|
self.consumers = parsed_project_quotas.get('consumers')
|
|
self.cas = parsed_project_quotas.get('cas')
|
|
|
|
def _do_extra_dict_fields(self):
|
|
"""Sub-class hook method: return dict of fields."""
|
|
ret = {
|
|
'project_id': self.project_id,
|
|
}
|
|
if self.secrets:
|
|
ret['secrets'] = self.secrets
|
|
if self.orders:
|
|
ret['orders'] = self.orders
|
|
if self.containers:
|
|
ret['containers'] = self.containers
|
|
if self.consumers:
|
|
ret['consumers'] = self.consumers
|
|
if self.cas:
|
|
ret['cas'] = self.cas
|
|
return ret
|
|
|
|
|
|
class SecretStores(BASE, ModelBase):
|
|
"""List of secret stores defined via service configuration.
|
|
|
|
This class provides a list of secret stores entities with their respective
|
|
secret store plugin and crypto plugin names.
|
|
|
|
SecretStores deletes are NOT soft-deletes.
|
|
"""
|
|
|
|
__tablename__ = 'secret_stores'
|
|
|
|
store_plugin = sa.Column(sa.String(255), nullable=False)
|
|
crypto_plugin = sa.Column(sa.String(255), nullable=True)
|
|
global_default = sa.Column(sa.Boolean, nullable=False, default=False)
|
|
name = sa.Column(sa.String(255), nullable=False)
|
|
|
|
__table_args__ = (sa.UniqueConstraint(
|
|
'store_plugin', 'crypto_plugin',
|
|
name='_secret_stores_plugin_names_uc'),
|
|
sa.UniqueConstraint('name', name='_secret_stores_name_uc'),)
|
|
|
|
def __init__(self, name, store_plugin, crypto_plugin=None,
|
|
global_default=None):
|
|
"""Creates secret store entity."""
|
|
super(SecretStores, self).__init__()
|
|
|
|
msg = u._("Must supply non-Blank {0} argument for SecretStores entry.")
|
|
|
|
if not name:
|
|
raise exception.MissingArgumentError(msg.format("name"))
|
|
if not store_plugin:
|
|
raise exception.MissingArgumentError(msg.format("store_plugin"))
|
|
|
|
self.store_plugin = store_plugin
|
|
self.name = name
|
|
self.crypto_plugin = crypto_plugin
|
|
if global_default is not None:
|
|
self.global_default = global_default
|
|
|
|
self.status = States.ACTIVE
|
|
|
|
def _do_extra_dict_fields(self):
|
|
"""Sub-class hook method: return dict of fields."""
|
|
return {'secret_store_id': self.id,
|
|
'store_plugin': self.store_plugin,
|
|
'crypto_plugin': self.crypto_plugin,
|
|
'global_default': self.global_default,
|
|
'name': self.name}
|
|
|
|
|
|
class ProjectSecretStore(BASE, ModelBase):
|
|
"""Stores secret store to be used for new project secrets.
|
|
|
|
This class maintains secret store and project mapping so that new project
|
|
secret entries uses it as plugin backend.
|
|
|
|
ProjectSecretStores deletes are NOT soft-deletes.
|
|
"""
|
|
|
|
__tablename__ = 'project_secret_store'
|
|
|
|
secret_store_id = sa.Column(sa.String(36),
|
|
sa.ForeignKey('secret_stores.id'),
|
|
index=True,
|
|
nullable=False)
|
|
project_id = sa.Column(sa.String(36),
|
|
sa.ForeignKey('projects.id'),
|
|
index=True,
|
|
nullable=False)
|
|
|
|
secret_store = orm.relationship("SecretStores", backref="project_store")
|
|
project = orm.relationship('Project',
|
|
backref=orm.backref('preferred_secret_store'))
|
|
|
|
__table_args__ = (sa.UniqueConstraint(
|
|
'project_id', name='_project_secret_store_project_uc'),)
|
|
|
|
def __init__(self, project_id, secret_store_id):
|
|
"""Creates project secret store mapping entity."""
|
|
super(ProjectSecretStore, self).__init__()
|
|
|
|
msg = u._("Must supply non-None {0} argument for ProjectSecretStore "
|
|
" entry.")
|
|
|
|
if not project_id:
|
|
raise exception.MissingArgumentError(msg.format("project_id"))
|
|
self.project_id = project_id
|
|
if not secret_store_id:
|
|
raise exception.MissingArgumentError(msg.format("secret_store_id"))
|
|
self.secret_store_id = secret_store_id
|
|
|
|
self.status = States.ACTIVE
|
|
|
|
def _do_extra_dict_fields(self):
|
|
"""Sub-class hook method: return dict of fields."""
|
|
return {'secret_store_id': self.secret_store_id,
|
|
'project_id': self.project_id}
|