Block ssl_ca certificates with same subject
Block the addition of ssl_ca certificates with same subject name Test Plan: PASS: Attempted to install another certificate with same subject, and verified that it fails with an error. PASS: Generate and install a full iso and verified that columns subject and hash_subject were added to certificate table. PASS: Verified that when there is a subject name clash the command system certificate-install returns an error and the certificate that has the same subject PASS: Verified that the system shows an error when the subject field is emtpy for ssl_ca PASS: Verified that a new column subject shows up for command system certificate-list PASS: Verified that a new column subject shows up as a return to a successful system certificate-install command Depends-on: https://review.opendev.org/c/starlingx/ansible-playbooks/+/851894 Closes-bug: 1981100 Signed-off-by: Karla Felix <karla.karolinenogueirafelix@windriver.com> Change-Id: I7ce11cc5dab6f686d360d01594ba100d07d2c2db
This commit is contained in:
parent
7403087c98
commit
7bd617b2fb
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2018 Wind River Systems, Inc.
|
# Copyright (c) 2022 Wind River Systems, Inc.
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
#
|
#
|
||||||
|
@ -22,7 +22,7 @@ PRIVATE_KEY_PATTERN = \
|
||||||
|
|
||||||
|
|
||||||
def _print_certificate_show(certificate):
|
def _print_certificate_show(certificate):
|
||||||
fields = ['uuid', 'certtype', 'signature', 'start_date', 'expiry_date']
|
fields = ['uuid', 'certtype', 'signature', 'start_date', 'expiry_date', 'subject']
|
||||||
if isinstance(certificate, dict):
|
if isinstance(certificate, dict):
|
||||||
data = [(f, certificate.get(f, '')) for f in fields]
|
data = [(f, certificate.get(f, '')) for f in fields]
|
||||||
details = ('details', certificate.get('details', ''))
|
details = ('details', certificate.get('details', ''))
|
||||||
|
@ -31,7 +31,6 @@ def _print_certificate_show(certificate):
|
||||||
details = ('details', getattr(certificate, 'details', ''))
|
details = ('details', getattr(certificate, 'details', ''))
|
||||||
if details[1]:
|
if details[1]:
|
||||||
data.append(details)
|
data.append(details)
|
||||||
|
|
||||||
utils.print_tuple_list(data)
|
utils.print_tuple_list(data)
|
||||||
|
|
||||||
|
|
||||||
|
@ -49,8 +48,11 @@ def do_certificate_show(cc, args):
|
||||||
def do_certificate_list(cc, args):
|
def do_certificate_list(cc, args):
|
||||||
"""List certificates."""
|
"""List certificates."""
|
||||||
certificates = cc.certificate.list()
|
certificates = cc.certificate.list()
|
||||||
fields = ['uuid', 'certtype', 'expiry_date']
|
fields = ['uuid', 'certtype', 'expiry_date', 'subject']
|
||||||
field_labels = fields
|
field_labels = fields
|
||||||
|
for certificate in certificates:
|
||||||
|
if len(certificate.subject) > 20:
|
||||||
|
certificate.subject = certificate.subject[:20] + "..."
|
||||||
utils.print_list(certificates, fields, field_labels, sortby=0)
|
utils.print_list(certificates, fields, field_labels, sortby=0)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
#
|
#
|
||||||
# Copyright (c) 2018 Wind River Systems, Inc.
|
# Copyright (c) 2018-2022 Wind River Systems, Inc.
|
||||||
#
|
#
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
@ -104,6 +104,12 @@ class Certificate(base.APIBase):
|
||||||
|
|
||||||
updated_at = wtypes.datetime.datetime
|
updated_at = wtypes.datetime.datetime
|
||||||
|
|
||||||
|
subject = wtypes.text
|
||||||
|
"Represents the subject of the certificate"
|
||||||
|
|
||||||
|
hash_subject = wtypes.text
|
||||||
|
"Represents the hash of the certificate's subject"
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
self.fields = list(objects.certificate.fields.keys())
|
self.fields = list(objects.certificate.fields.keys())
|
||||||
for k in self.fields:
|
for k in self.fields:
|
||||||
|
@ -126,7 +132,9 @@ class Certificate(base.APIBase):
|
||||||
'signature',
|
'signature',
|
||||||
'details',
|
'details',
|
||||||
'start_date',
|
'start_date',
|
||||||
'expiry_date'])
|
'expiry_date',
|
||||||
|
'subject',
|
||||||
|
'hash_subject'])
|
||||||
|
|
||||||
certificate.links = \
|
certificate.links = \
|
||||||
[link.Link.make_link('self', pecan.request.host_url,
|
[link.Link.make_link('self', pecan.request.host_url,
|
||||||
|
@ -323,6 +331,9 @@ class CertificateController(rest.RestController):
|
||||||
|
|
||||||
hash_issuers = []
|
hash_issuers = []
|
||||||
cert_validity_error = None
|
cert_validity_error = None
|
||||||
|
|
||||||
|
existing_certificates = pecan.request.dbapi.certificate_get_list()
|
||||||
|
|
||||||
for index, cert in enumerate(certs):
|
for index, cert in enumerate(certs):
|
||||||
msg = self._check_cert_validity(cert)
|
msg = self._check_cert_validity(cert)
|
||||||
if msg is not True:
|
if msg is not True:
|
||||||
|
@ -384,6 +395,24 @@ class CertificateController(rest.RestController):
|
||||||
msg = "Cannot install non-CA type certificate as SSL " \
|
msg = "Cannot install non-CA type certificate as SSL " \
|
||||||
"CA certificate"
|
"CA certificate"
|
||||||
return dict(success="", error=msg)
|
return dict(success="", error=msg)
|
||||||
|
if cert.subject:
|
||||||
|
hash_subject = cutils.get_cert_subject_hash(cert)
|
||||||
|
duplicate_certificates = [certificate.uuid
|
||||||
|
for certificate in existing_certificates
|
||||||
|
if certificate.hash_subject
|
||||||
|
if hash_subject == int(certificate.hash_subject)]
|
||||||
|
if duplicate_certificates:
|
||||||
|
msg = "Cannot install certificate with same subject" \
|
||||||
|
"\nPlease uninstall the following CA certs that have " \
|
||||||
|
"the same subject first"
|
||||||
|
for uuid in duplicate_certificates:
|
||||||
|
msg += "\nUUID : %s" % uuid
|
||||||
|
LOG.error(msg)
|
||||||
|
return dict(success="", error=msg)
|
||||||
|
else:
|
||||||
|
msg = "Cannot install CA certificate without subject"
|
||||||
|
LOG.error(msg)
|
||||||
|
return dict(success="", error=msg)
|
||||||
|
|
||||||
if mode == constants.CERT_MODE_OPENSTACK and index == 0:
|
if mode == constants.CERT_MODE_OPENSTACK and index == 0:
|
||||||
domain, msg = _check_endpoint_domain_exists()
|
domain, msg = _check_endpoint_domain_exists()
|
||||||
|
@ -408,8 +437,6 @@ class CertificateController(rest.RestController):
|
||||||
LOG.warn(msg)
|
LOG.warn(msg)
|
||||||
return dict(success="", error=str(e.value), body="", certificates={})
|
return dict(success="", error=str(e.value), body="", certificates={})
|
||||||
|
|
||||||
certificates = pecan.request.dbapi.certificate_get_list()
|
|
||||||
|
|
||||||
# Create new or update existing certificates in sysinv with the
|
# Create new or update existing certificates in sysinv with the
|
||||||
# information returned from conductor manager.
|
# information returned from conductor manager.
|
||||||
certificate_dicts = []
|
certificate_dicts = []
|
||||||
|
@ -429,12 +456,14 @@ class CertificateController(rest.RestController):
|
||||||
'signature': inv_cert.get('signature'),
|
'signature': inv_cert.get('signature'),
|
||||||
'start_date': inv_cert.get('not_valid_before'),
|
'start_date': inv_cert.get('not_valid_before'),
|
||||||
'expiry_date': inv_cert.get('not_valid_after'),
|
'expiry_date': inv_cert.get('not_valid_after'),
|
||||||
|
'hash_subject': inv_cert.get('hash_subject'),
|
||||||
|
'subject': inv_cert.get('subject'),
|
||||||
}
|
}
|
||||||
LOG.info("config_certificate values=%s" % values)
|
LOG.info("config_certificate values=%s" % values)
|
||||||
|
|
||||||
# check to see if the installed cert exist in sysinv
|
# check to see if the installed cert exist in sysinv
|
||||||
uuid = None
|
uuid = None
|
||||||
for certificate in certificates:
|
for certificate in existing_certificates:
|
||||||
if mode == constants.CERT_MODE_SSL_CA:
|
if mode == constants.CERT_MODE_SSL_CA:
|
||||||
if inv_cert.get('signature') == certificate.signature:
|
if inv_cert.get('signature') == certificate.signature:
|
||||||
uuid = certificate.uuid
|
uuid = certificate.uuid
|
||||||
|
|
|
@ -12983,6 +12983,8 @@ class ConductorManager(service.PeriodicService):
|
||||||
# check if the cert is a CA cert
|
# check if the cert is a CA cert
|
||||||
is_ca = cutils.is_ca_cert(cert)
|
is_ca = cutils.is_ca_cert(cert)
|
||||||
|
|
||||||
|
hash_subject = cutils.get_cert_subject_hash(cert)
|
||||||
|
|
||||||
signature = mode + '_' + str(cert.serial_number)
|
signature = mode + '_' + str(cert.serial_number)
|
||||||
if len(signature) > 255:
|
if len(signature) > 255:
|
||||||
LOG.info("Truncating certificate serial no %s" % signature)
|
LOG.info("Truncating certificate serial no %s" % signature)
|
||||||
|
@ -12992,7 +12994,8 @@ class ConductorManager(service.PeriodicService):
|
||||||
cert_list.append({'cert': cert,
|
cert_list.append({'cert': cert,
|
||||||
'is_ca': is_ca,
|
'is_ca': is_ca,
|
||||||
'public_bytes': public_bytes,
|
'public_bytes': public_bytes,
|
||||||
'signature': signature})
|
'signature': signature,
|
||||||
|
'hash_subject': hash_subject})
|
||||||
|
|
||||||
return cert_list, private_bytes
|
return cert_list, private_bytes
|
||||||
|
|
||||||
|
@ -13327,7 +13330,10 @@ class ConductorManager(service.PeriodicService):
|
||||||
inv_cert = {'signature': cert.get('signature'),
|
inv_cert = {'signature': cert.get('signature'),
|
||||||
'is_ca': cert.get('is_ca'),
|
'is_ca': cert.get('is_ca'),
|
||||||
'not_valid_before': cert.get('cert').not_valid_before,
|
'not_valid_before': cert.get('cert').not_valid_before,
|
||||||
'not_valid_after': cert.get('cert').not_valid_after}
|
'not_valid_after': cert.get('cert').not_valid_after,
|
||||||
|
'hash_subject': cert.get('hash_subject'),
|
||||||
|
'subject': cert.get('cert').subject.rfc4514_string()
|
||||||
|
}
|
||||||
inv_certs.append(inv_cert)
|
inv_certs.append(inv_cert)
|
||||||
|
|
||||||
return inv_certs
|
return inv_certs
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
#
|
||||||
|
# Copyright (c) 2022 Wind River Systems, Inc.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
|
from sqlalchemy import Column
|
||||||
|
from sqlalchemy import MetaData
|
||||||
|
from sqlalchemy import String
|
||||||
|
from sqlalchemy import Table
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade(migrate_engine):
|
||||||
|
meta = MetaData()
|
||||||
|
meta.bind = migrate_engine
|
||||||
|
|
||||||
|
# add column to certificate table
|
||||||
|
certificate = Table('certificate', meta, autoload=True)
|
||||||
|
|
||||||
|
col_subject = Column('subject', String(255), nullable=True)
|
||||||
|
col_subject.create(certificate)
|
||||||
|
|
||||||
|
col_hash_subject = Column('hash_subject', String(64), nullable=True)
|
||||||
|
col_hash_subject.create(certificate)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade(migrate_engine):
|
||||||
|
# As per other openstack components, downgrade is
|
||||||
|
# unsupported in this release.
|
||||||
|
raise NotImplementedError('SysInv database downgrade is unsupported.')
|
|
@ -15,7 +15,7 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
#
|
#
|
||||||
# Copyright (c) 2013-2021 Wind River Systems, Inc.
|
# Copyright (c) 2013-2022 Wind River Systems, Inc.
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
#
|
#
|
||||||
|
@ -2018,6 +2018,8 @@ class certificate(Base):
|
||||||
start_date = Column(DateTime(timezone=False))
|
start_date = Column(DateTime(timezone=False))
|
||||||
expiry_date = Column(DateTime(timezone=False))
|
expiry_date = Column(DateTime(timezone=False))
|
||||||
capabilities = Column(JSONEncodedDict)
|
capabilities = Column(JSONEncodedDict)
|
||||||
|
subject = Column(String(255))
|
||||||
|
hash_subject = Column(String(64))
|
||||||
|
|
||||||
|
|
||||||
class HelmOverrides(Base):
|
class HelmOverrides(Base):
|
||||||
|
|
|
@ -24,6 +24,8 @@ class Certificate(base.SysinvObject):
|
||||||
'signature': utils.str_or_none,
|
'signature': utils.str_or_none,
|
||||||
'start_date': utils.datetime_or_str_or_none,
|
'start_date': utils.datetime_or_str_or_none,
|
||||||
'expiry_date': utils.datetime_or_str_or_none,
|
'expiry_date': utils.datetime_or_str_or_none,
|
||||||
|
'subject': utils.str_or_none,
|
||||||
|
'hash_subject': utils.str_or_none,
|
||||||
}
|
}
|
||||||
|
|
||||||
@base.remotable_classmethod
|
@base.remotable_classmethod
|
||||||
|
|
Loading…
Reference in New Issue