Skip to content

Commit

Permalink
NAS-129859 / 25.04 / Allow adding certificates to system's trusted st…
Browse files Browse the repository at this point in the history
…ore (by sonicaj) (#14430)

* Add db migration to add trusted store column for certs

(cherry picked from commit b988d5d)

* Account for trusted store key in cert service

(cherry picked from commit 7674f9f)

* Update generate ssl certs so that we add certs to trusted store

(cherry picked from commit f1820b2)

* Add merge migration

* Add ca/cert prefix when writing to trusted ca's path dir

---------

Co-authored-by: Waqar Ahmed <[email protected]>
  • Loading branch information
bugclerk and sonicaj authored Sep 6, 2024
1 parent a06c5f6 commit fa423b8
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""
Add add_to_trust_store field for certifacates
Revision ID: c31881e67797
Revises: 98c1ebde0079
Create Date: 2024-09-04 19:40:16.801832+00:00
"""
from alembic import op
import sqlalchemy as sa


revision = 'c31881e67797'
down_revision = '98c1ebde0079'
branch_labels = None
depends_on = None


def upgrade():
with op.batch_alter_table('system_certificate', schema=None) as batch_op:
batch_op.add_column(sa.Column('cert_add_to_trusted_store', sa.Boolean(), nullable=False, server_default='0'))


def downgrade():
pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""Merge
Revision ID: 7b618b9ca77d
Revises: 9f51d0be7b07, c31881e67797
Create Date: 2024-09-04 00:35:59.547731+00:00
"""

revision = '7b618b9ca77d'
down_revision = ('9f51d0be7b07', 'c31881e67797')
branch_labels = None
depends_on = None


def upgrade():
pass


def downgrade():
pass
11 changes: 6 additions & 5 deletions src/middlewared/middlewared/etc_files/generate_ssl_certs.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from middlewared.service import CallError, Service


def write_certificates(certs: list, cacerts: list) -> set:
def write_certificates(certs: list) -> set:
expected_files = set()
for cert in certs:
if cert['chain_list']:
Expand All @@ -31,9 +31,10 @@ def write_certificates(certs: list, cacerts: list) -> set:
# to forcibly remove all locally-added CAs.
trusted_cas_path = '/var/local/ca-certificates'
shutil.rmtree(trusted_cas_path, ignore_errors=True)
for ca in filter(lambda c: c['chain_list'] and c['add_to_trusted_store'], cacerts):
with open(os.path.join(trusted_cas_path, f'{ca["name"]}.crt'), 'w') as f:
f.write('\n'.join(ca['chain_list']))
for cert in filter(lambda c: c['chain_list'] and c['add_to_trusted_store'], certs):
cert_type = 'ca' if cert['cert_type'] == 'CA' else 'cert'
with open(os.path.join(trusted_cas_path, f'{cert_type}_{cert["name"]}.crt'), 'w') as f:
f.write('\n'.join(cert['chain_list']))

cp = subprocess.Popen('update-ca-certificates', stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
err = cp.communicate()[1]
Expand Down Expand Up @@ -73,7 +74,7 @@ def render(service: Service, middleware: Middleware) -> None:
certs = middleware.call_sync('certificate.query')
cas = middleware.call_sync('certificateauthority.query')

expected_files |= write_certificates(certs + cas, cas)
expected_files |= write_certificates(certs + cas)
expected_files |= write_crls(cas, middleware)

# We would like to remove certificates which have been deleted
Expand Down
2 changes: 1 addition & 1 deletion src/middlewared/middlewared/plugins/crypto_/cert_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
Int('lifetime', null=True),
Int('serial', null=True),
Int('key_length', null=True),
Bool('add_to_trusted_store', default=False),
Bool('chain', null=True),
Bool('CA_type_existing'),
Bool('CA_type_internal'),
Expand All @@ -68,5 +69,4 @@
def get_ca_result_entry():
entry = copy.deepcopy(CERT_ENTRY)
entry.name = 'certificateauthority_entry'
entry.attrs['add_to_trusted_store'] = Bool('add_to_trusted_store')
return entry
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,6 @@ def set_defaults(attr):
('edit', _set_enum('create_type')),
('edit', _set_cert_extensions_defaults('cert_extensions')),
('rm', {'name': 'dns_mapping'}),
('add', Bool('add_to_trusted_store', default=False)),
register=True
),
)
Expand Down
15 changes: 12 additions & 3 deletions src/middlewared/middlewared/plugins/crypto_/certificates.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class CertificateModel(sa.Model):
cert_renew_days = sa.Column(sa.Integer(), nullable=True, default=10)
cert_acme_id = sa.Column(sa.ForeignKey('system_acmeregistration.id'), index=True, nullable=True)
cert_revoked_date = sa.Column(sa.DateTime(), nullable=True)
cert_add_to_trusted_store = sa.Column(sa.Boolean(), default=False, nullable=False)


class CertificateService(CRUDService):
Expand Down Expand Up @@ -200,6 +201,7 @@ async def validate_common_attributes(self, data, schema_name):
Str('digest_algorithm', enum=['SHA224', 'SHA256', 'SHA384', 'SHA512']),
List('san', items=[Str('san')]),
Ref('cert_extensions'),
Bool('add_to_trusted_store', default=False),
register=True
),
)
Expand Down Expand Up @@ -328,7 +330,7 @@ async def do_create(self, job, data):
).items()
if k in [
'name', 'certificate', 'CSR', 'privatekey', 'type', 'signedby', 'acme', 'acme_uri',
'domains_authenticators', 'renew_days'
'domains_authenticators', 'renew_days', 'add_to_trusted_store',
]
}

Expand Down Expand Up @@ -555,6 +557,7 @@ async def create_internal(self, job, data):
'certificate_update',
Bool('revoked'),
Int('renew_days', validators=[Range(min_=1, max_=30)]),
Bool('add_to_trusted_store'),
Str('name'),
),
)
Expand Down Expand Up @@ -595,7 +598,7 @@ async def do_update(self, job, id_, data):

new.update(data)

if any(new.get(k) != old.get(k) for k in ('name', 'revoked', 'renew_days')):
if any(new.get(k) != old.get(k) for k in ('name', 'revoked', 'renew_days', 'add_to_trusted_store')):

verrors = ValidationErrors()

Expand Down Expand Up @@ -627,6 +630,12 @@ async def do_update(self, job, id_, data):
'Certificate has already been revoked and this cannot be reversed'
)

if not verrors and new['revoked'] and new['add_to_trusted_store']:
verrors.add(
'certificate_update.add_to_trusted_store',
'Revoked certificates cannot be added to system\'s trusted store'
)

verrors.check()

to_update = {'renew_days': new['renew_days']} if data.get('renew_days') else {}
Expand All @@ -637,7 +646,7 @@ async def do_update(self, job, id_, data):
'datastore.update',
self._config.datastore,
id_,
{'name': new['name'], **to_update},
{'name': new['name'], 'add_to_trusted_store': new['add_to_trusted_store'], **to_update},
{'prefix': self._config.datastore_prefix}
)

Expand Down

0 comments on commit fa423b8

Please sign in to comment.