From a84b39fbec5f416d5294aed109ab31b2a210d246 Mon Sep 17 00:00:00 2001 From: Dave Lasley Date: Tue, 13 Dec 2016 18:48:21 -0800 Subject: [PATCH 1/9] [ADD] clouder_certificate_authority: Implement CA using CFSSL --- clouder_certificate_authority/README.rst | 52 ++++++++++++++++++ clouder_certificate_authority/__init__.py | 3 ++ clouder_certificate_authority/__openerp__.py | 30 +++++++++++ .../data/application.xml | 42 +++++++++++++++ .../data/application_link.xml | 20 +++++++ .../data/application_tag.xml | 11 ++++ .../data/application_template.xml | 13 +++++ .../data/application_type.xml | 17 ++++++ .../data/application_type_option.xml | 29 ++++++++++ clouder_certificate_authority/data/image.xml | 24 +++++++++ .../data/image_port.xml | 15 ++++++ .../data/image_template.xml | 19 +++++++ .../data/image_volume.xml | 16 ++++++ .../images/1.2.0-data/Dockerfile | 4 ++ .../images/1.2.0-data/pki/ca/csr_ca.json | 4 ++ .../images/1.2.0-exec/Dockerfile | 38 +++++++++++++ .../models/__init__.py | 3 ++ .../models/certificate_authority.py | 38 +++++++++++++ .../models/certificate_name.py | 53 +++++++++++++++++++ .../models/certificate_request.py | 29 ++++++++++ .../models/key_abstract.py | 49 +++++++++++++++++ .../models/key_private.py | 53 +++++++++++++++++++ .../models/key_public.py | 31 +++++++++++ 23 files changed, 593 insertions(+) create mode 100644 clouder_certificate_authority/README.rst create mode 100644 clouder_certificate_authority/__init__.py create mode 100644 clouder_certificate_authority/__openerp__.py create mode 100644 clouder_certificate_authority/data/application.xml create mode 100644 clouder_certificate_authority/data/application_link.xml create mode 100644 clouder_certificate_authority/data/application_tag.xml create mode 100644 clouder_certificate_authority/data/application_template.xml create mode 100644 clouder_certificate_authority/data/application_type.xml create mode 100644 clouder_certificate_authority/data/application_type_option.xml create mode 100644 clouder_certificate_authority/data/image.xml create mode 100644 clouder_certificate_authority/data/image_port.xml create mode 100644 clouder_certificate_authority/data/image_template.xml create mode 100644 clouder_certificate_authority/data/image_volume.xml create mode 100644 clouder_certificate_authority/images/1.2.0-data/Dockerfile create mode 100644 clouder_certificate_authority/images/1.2.0-data/pki/ca/csr_ca.json create mode 100644 clouder_certificate_authority/images/1.2.0-exec/Dockerfile create mode 100644 clouder_certificate_authority/models/__init__.py create mode 100644 clouder_certificate_authority/models/certificate_authority.py create mode 100644 clouder_certificate_authority/models/certificate_name.py create mode 100644 clouder_certificate_authority/models/certificate_request.py create mode 100644 clouder_certificate_authority/models/key_abstract.py create mode 100644 clouder_certificate_authority/models/key_private.py create mode 100644 clouder_certificate_authority/models/key_public.py diff --git a/clouder_certificate_authority/README.rst b/clouder_certificate_authority/README.rst new file mode 100644 index 0000000..35ad60e --- /dev/null +++ b/clouder_certificate_authority/README.rst @@ -0,0 +1,52 @@ +.. image:: https://img.shields.io/badge/licence-LGPL--3-blue.svg + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 + +================================ +Clouder Template - CFSSL +================================ + +This module provides a template for CFSSL in Clouder. + + +Configuration +============= + +Clouder configuration instructions are available at https://clouder.readthedocs.io/ + +Usage +===== + +To use this module, you need to: + +#. Create an CFSSL Service in the Clouder Control Panel + +Known issues / Roadmap +====================== + +* Add ElasticDump db backup & restore +* ElasticDump should be a separate service +* Add SSL (Requires CA - https://github.com/clouder-community/clouder/issues/167) + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smashing it by providing a detailed and welcomed feedback. + +Credits +======= + +Contributors +------------ + +* Dave Lasley + +Maintainer +---------- + +This module is maintained by Clouder Community. + +To contribute to this module, please visit https://github.com/clouder-community/clouder diff --git a/clouder_certificate_authority/__init__.py b/clouder_certificate_authority/__init__.py new file mode 100644 index 0000000..08d9d6b --- /dev/null +++ b/clouder_certificate_authority/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). diff --git a/clouder_certificate_authority/__openerp__.py b/clouder_certificate_authority/__openerp__.py new file mode 100644 index 0000000..0cbb97d --- /dev/null +++ b/clouder_certificate_authority/__openerp__.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +{ + 'name': 'Clouder Certificate Authority', + 'version': '9.0.10.0.0', + 'category': 'Clouder', + 'depends': [ + 'clouder', + 'clouder_template_proxy', + ], + 'author': 'LasLabs Inc.', + 'license': 'LGPL-3', + 'website': 'https://github.com/clouder-community/clouder', + 'data': [ + 'data/image_template.xml', + 'data/image.xml', + 'data/image_port.xml', + 'data/image_volume.xml', + 'data/application_tag.xml', + 'data/application_type.xml', + 'data/application_type_option.xml', + 'data/application_template.xml', + 'data/application.xml', + 'data/application_link.xml', + ], + 'installable': True, + 'application': False, +} diff --git a/clouder_certificate_authority/data/application.xml b/clouder_certificate_authority/data/application.xml new file mode 100644 index 0000000..8028889 --- /dev/null +++ b/clouder_certificate_authority/data/application.xml @@ -0,0 +1,42 @@ + + + + + + + CFSSL Data + data + + + + 1 + + + + + CFSSL Exec + exec + + + + 2 + + auto + + + + CFSSL + cfssl + + + + 1 + + + + diff --git a/clouder_certificate_authority/data/application_link.xml b/clouder_certificate_authority/data/application_link.xml new file mode 100644 index 0000000..039e48c --- /dev/null +++ b/clouder_certificate_authority/data/application_link.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + diff --git a/clouder_certificate_authority/data/application_tag.xml b/clouder_certificate_authority/data/application_tag.xml new file mode 100644 index 0000000..62e887a --- /dev/null +++ b/clouder_certificate_authority/data/application_tag.xml @@ -0,0 +1,11 @@ + + + + + + + cert_authority + + + diff --git a/clouder_certificate_authority/data/application_template.xml b/clouder_certificate_authority/data/application_template.xml new file mode 100644 index 0000000..30910f4 --- /dev/null +++ b/clouder_certificate_authority/data/application_template.xml @@ -0,0 +1,13 @@ + + + + + + + CFSSL + + + diff --git a/clouder_certificate_authority/data/application_type.xml b/clouder_certificate_authority/data/application_type.xml new file mode 100644 index 0000000..05a9d5a --- /dev/null +++ b/clouder_certificate_authority/data/application_type.xml @@ -0,0 +1,17 @@ + + + + + + + cfssl + root + + + + diff --git a/clouder_certificate_authority/data/application_type_option.xml b/clouder_certificate_authority/data/application_type_option.xml new file mode 100644 index 0000000..491dd8d --- /dev/null +++ b/clouder_certificate_authority/data/application_type_option.xml @@ -0,0 +1,29 @@ + + + + + + + + network + service + 172.17.0.0/16 + + + + + listen + service + * + + + diff --git a/clouder_certificate_authority/data/image.xml b/clouder_certificate_authority/data/image.xml new file mode 100644 index 0000000..ff72187 --- /dev/null +++ b/clouder_certificate_authority/data/image.xml @@ -0,0 +1,24 @@ + + + + + + + image_cfssl_data + + lasley/cfssl-data + + + + image_cfssl_exec + + lasley/cfssl-exec + data + + + diff --git a/clouder_certificate_authority/data/image_port.xml b/clouder_certificate_authority/data/image_port.xml new file mode 100644 index 0000000..f11514d --- /dev/null +++ b/clouder_certificate_authority/data/image_port.xml @@ -0,0 +1,15 @@ + + + + + + + + cfssl-http + 8888 + + + diff --git a/clouder_certificate_authority/data/image_template.xml b/clouder_certificate_authority/data/image_template.xml new file mode 100644 index 0000000..4a935e1 --- /dev/null +++ b/clouder_certificate_authority/data/image_template.xml @@ -0,0 +1,19 @@ + + + + + + + image_template_cfssl_data + + + + image_template_cfssl_exec + + + diff --git a/clouder_certificate_authority/data/image_volume.xml b/clouder_certificate_authority/data/image_volume.xml new file mode 100644 index 0000000..e882e1f --- /dev/null +++ b/clouder_certificate_authority/data/image_volume.xml @@ -0,0 +1,16 @@ + + + + + + + + cert_store + /var/pki + root + + + diff --git a/clouder_certificate_authority/images/1.2.0-data/Dockerfile b/clouder_certificate_authority/images/1.2.0-data/Dockerfile new file mode 100644 index 0000000..182a649 --- /dev/null +++ b/clouder_certificate_authority/images/1.2.0-data/Dockerfile @@ -0,0 +1,4 @@ +FROM yannickburon/clouder:base +MAINTAINER Dave Lasley + +CMD tail -f /dev/null diff --git a/clouder_certificate_authority/images/1.2.0-data/pki/ca/csr_ca.json b/clouder_certificate_authority/images/1.2.0-data/pki/ca/csr_ca.json new file mode 100644 index 0000000..182a649 --- /dev/null +++ b/clouder_certificate_authority/images/1.2.0-data/pki/ca/csr_ca.json @@ -0,0 +1,4 @@ +FROM yannickburon/clouder:base +MAINTAINER Dave Lasley + +CMD tail -f /dev/null diff --git a/clouder_certificate_authority/images/1.2.0-exec/Dockerfile b/clouder_certificate_authority/images/1.2.0-exec/Dockerfile new file mode 100644 index 0000000..abf41d0 --- /dev/null +++ b/clouder_certificate_authority/images/1.2.0-exec/Dockerfile @@ -0,0 +1,38 @@ +FROM yannickburon/clouder:base +MAINTAINER Dave Lasley + +# Install Build Dependencies + +ENV buildDeps "build-base \ + gcc \ + git \ + go \ + libtool" + +RUN apk add --no-cache $buildDeps + +# Install CFSSL + +RUN git clone --depth=1 https://github.com/cloudflare/cfssl.git /go/src/github.com/cloudflare/cfssl + +WORKDIR /go/src/github.com/cloudflare/cfssl + +RUN set -x \ + && go get github.com/GeertJohan/go.rice/rice \ + && rice embed-go -i=./cli/serve \ + && cp -R /go/src/github.com/cloudflare/cfssl/vendor/github.com/cloudflare/cfssl_trust /etc/cfssl \ + && go build -o /usr/bin/cfssl ./cmd/cfssl \ + && go build -o /usr/bin/cfssljson ./cmd/cfssljson \ + && go build -o /usr/bin/mkbundle ./cmd/mkbundle \ + && go build -o /usr/bin/multirootca ./cmd/multirootca \ + && apk del $buildDeps \ + && rm -rf /go \ + && echo "Build complete." + +# Create and Change to PKI Dir +RUN mkdir -p /var/pki +WORKDIR /var/pki + +# Enter +ENTRYPOINT ["cfssl"] +CMD ["--help"] diff --git a/clouder_certificate_authority/models/__init__.py b/clouder_certificate_authority/models/__init__.py new file mode 100644 index 0000000..08d9d6b --- /dev/null +++ b/clouder_certificate_authority/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). diff --git a/clouder_certificate_authority/models/certificate_authority.py b/clouder_certificate_authority/models/certificate_authority.py new file mode 100644 index 0000000..6b7c9b9 --- /dev/null +++ b/clouder_certificate_authority/models/certificate_authority.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from openerp import api, fields, models + + +class ClouderCertificateAuthority(models.Model): + """ It provides an interface for controlling a Cert Authority """ + + _name = 'clouder.certificate.authority' + _description = 'Clouder Certificate Authority' + _inherits = {'clouder.application': 'application_id'} + + application_id = fields.Many2one( + string='Application', + comodel_name='clouder.application', + required=True, + ondelete='cascade', + domain=lambda s: "[('tag_ids', '=', %d)]" % s.enf.ref( + 'clouder_certificate_authority.tag_cert_authority', + ), + ) + exec_service_id = fields.Many2one( + string='Executor Service', + comodel_name='clouder.service', + compute='_compute_exec_service_id', + ) + + @api.model + def _compute_exec_service_id(self): + for record in self: + service_ids = record.service_ids.filtered( + lambda s: s.code == 'exec', + ) + record.exec_service_id = service_ids[0] + + diff --git a/clouder_certificate_authority/models/certificate_name.py b/clouder_certificate_authority/models/certificate_name.py new file mode 100644 index 0000000..786eed2 --- /dev/null +++ b/clouder_certificate_authority/models/certificate_name.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from openerp import api, fields, models + + +class ClouderCertificateName(models.Model): + """ It provides the concept of a cert's SubjectInfo """ + + _name = 'clouder.certificate.name' + _description = 'Clouder Certificate Name' + + country_id = fields.Many2one( + string='Country', + model='res.country', + required=True, + default=lambda s: s.env.user.country_id, + ) + state_id = fields.Many2one( + string='State', + model='res.country.state', + domain='[(country_id, "=", country_id)]', + default=lambda s: s.env.user.state_id, + ) + city = fields.Char( + default=lambda s: s.env.user.city, + ) + company_id = fields.Many2one( + string='Company', + model='res.company', + required=True, + domain='[(company_id, "=", company_id)]', + default=lambda s: s.env.user.company_id, + ) + organization_unit = fields.Char( + required=True, + ) + computed = fields.Serialized( + compute="_compute_computed", + ) + + @api.multi + def _compute_computed(self): + """ It computes the keys required for the JSON request """ + for record in self: + record.computed = { + 'C': record.country_id.code, + 'ST': record.state_id.name, + 'L': record.city, + 'O': record.company_id.name, + 'OU': record.organization_unit, + } diff --git a/clouder_certificate_authority/models/certificate_request.py b/clouder_certificate_authority/models/certificate_request.py new file mode 100644 index 0000000..6280bcd --- /dev/null +++ b/clouder_certificate_authority/models/certificate_request.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from openerp import api, fields, models + + +class ClouderCertificateRequest(models.Model): + """ It provides the concept of a Certificate Request """ + + _name = 'clouder.certificate.request' + _description = 'Clouder Certificate Request' + + authority_id = fields.Many2one( + string='Cert Authority', + comodel_name='clouder.certificate.authority', + required=True, + ondelete='cascade', + ) + + @api.model + def _compute_exec_service_id(self): + for record in self: + service_ids = record.service_ids.filtered( + lambda s: s.code == 'exec', + ) + record.exec_service_id = service_ids[0] + + diff --git a/clouder_certificate_authority/models/key_abstract.py b/clouder_certificate_authority/models/key_abstract.py new file mode 100644 index 0000000..3714ae1 --- /dev/null +++ b/clouder_certificate_authority/models/key_abstract.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from openerp import api, fields, models + + +class ClouderKeyAbstract(models.AbstractModel): + """ It provides attributes and methods related to all keys """ + + _name = 'clouder.key.public' + _description = 'Clouder Key Public' + + name = fields.Char( + required=True, + ) + description = fields.Char() + mime_type = fields.Selection( + selection='_get_mime_types', + ) + attachment_id = fields.Many2one( + string='Key', + comodel_name='ir.attachment', + required=True, + context="""{ + 'default_type': 'binary', + 'default_res_model': _name, + 'default_res_field': 'attachment_id', + 'default_res_id': id, + 'default_name': name, + 'default_description': description, + 'default_mime_type': mime_type, + }""", + ) + data = fields.Text( + related='attachment_id.datas', + ) + + @api.model + def _get_mime_types(self): + return [ + ('application/pkcs8', 'PKCS-8'), + ('application/pkcs10', 'PKCS-10'), + ('application/x-pkcs12', 'PKCS-12'), + ('application/x-pem-file', 'PEM'), + ('application/pkcs7-mime', 'PKCS-7 MIME'), + ('application/x-x509-ca-cert', 'X.509 CA'), + ('application/x-x509-user-cert', 'X.509 User'), + ] diff --git a/clouder_certificate_authority/models/key_private.py b/clouder_certificate_authority/models/key_private.py new file mode 100644 index 0000000..786eed2 --- /dev/null +++ b/clouder_certificate_authority/models/key_private.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from openerp import api, fields, models + + +class ClouderCertificateName(models.Model): + """ It provides the concept of a cert's SubjectInfo """ + + _name = 'clouder.certificate.name' + _description = 'Clouder Certificate Name' + + country_id = fields.Many2one( + string='Country', + model='res.country', + required=True, + default=lambda s: s.env.user.country_id, + ) + state_id = fields.Many2one( + string='State', + model='res.country.state', + domain='[(country_id, "=", country_id)]', + default=lambda s: s.env.user.state_id, + ) + city = fields.Char( + default=lambda s: s.env.user.city, + ) + company_id = fields.Many2one( + string='Company', + model='res.company', + required=True, + domain='[(company_id, "=", company_id)]', + default=lambda s: s.env.user.company_id, + ) + organization_unit = fields.Char( + required=True, + ) + computed = fields.Serialized( + compute="_compute_computed", + ) + + @api.multi + def _compute_computed(self): + """ It computes the keys required for the JSON request """ + for record in self: + record.computed = { + 'C': record.country_id.code, + 'ST': record.state_id.name, + 'L': record.city, + 'O': record.company_id.name, + 'OU': record.organization_unit, + } diff --git a/clouder_certificate_authority/models/key_public.py b/clouder_certificate_authority/models/key_public.py new file mode 100644 index 0000000..3b323bd --- /dev/null +++ b/clouder_certificate_authority/models/key_public.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from openerp import api, fields, models + + +class ClouderKeyPublic(models.Model): + """ It provides attributes and methods related to handling public keys """ + + _name = 'clouder.key.public' + _description = 'Clouder Key Public' + + name = fields.Char( + required=True, + ) + attachment_id = fields.Many2one( + string='Key', + comodel_name='ir.attachment', + required=True, + context="""{ + 'default_type': 'binary', + 'default_res_model': 'clouder.key.public', + 'default_res_field': 'attachment_id', + 'default_res_id': id, + 'default_name': name, + }""", + ) + data = fields.Text( + related='attachment_id.datas', + ) From 02d333816d8933e44ddf454d3c45e4d0aeb74386 Mon Sep 17 00:00:00 2001 From: Dave Lasley Date: Wed, 14 Dec 2016 17:28:27 -0800 Subject: [PATCH 2/9] PR review changes & v10 migration --- .../{__openerp__.py => __manifest__.py} | 4 +-- .../data/application_link.xml | 20 ------------- .../data/application_type_option.xml | 29 ------------------- .../images/1.2.0-data/Dockerfile | 2 +- .../images/1.2.0-data/pki/ca/csr_ca.json | 2 +- .../images/1.2.0-exec/Dockerfile | 4 +-- .../models/certificate_authority.py | 2 +- .../models/certificate_name.py | 2 +- .../models/certificate_request.py | 2 +- .../models/key_abstract.py | 2 +- .../models/key_private.py | 2 +- .../models/key_public.py | 2 +- 12 files changed, 11 insertions(+), 62 deletions(-) rename clouder_certificate_authority/{__openerp__.py => __manifest__.py} (87%) delete mode 100644 clouder_certificate_authority/data/application_link.xml delete mode 100644 clouder_certificate_authority/data/application_type_option.xml diff --git a/clouder_certificate_authority/__openerp__.py b/clouder_certificate_authority/__manifest__.py similarity index 87% rename from clouder_certificate_authority/__openerp__.py rename to clouder_certificate_authority/__manifest__.py index 0cbb97d..88155d6 100644 --- a/clouder_certificate_authority/__openerp__.py +++ b/clouder_certificate_authority/__manifest__.py @@ -4,7 +4,7 @@ { 'name': 'Clouder Certificate Authority', - 'version': '9.0.10.0.0', + 'version': '10.0.10.0.0', 'category': 'Clouder', 'depends': [ 'clouder', @@ -20,10 +20,8 @@ 'data/image_volume.xml', 'data/application_tag.xml', 'data/application_type.xml', - 'data/application_type_option.xml', 'data/application_template.xml', 'data/application.xml', - 'data/application_link.xml', ], 'installable': True, 'application': False, diff --git a/clouder_certificate_authority/data/application_link.xml b/clouder_certificate_authority/data/application_link.xml deleted file mode 100644 index 039e48c..0000000 --- a/clouder_certificate_authority/data/application_link.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/clouder_certificate_authority/data/application_type_option.xml b/clouder_certificate_authority/data/application_type_option.xml deleted file mode 100644 index 491dd8d..0000000 --- a/clouder_certificate_authority/data/application_type_option.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - network - service - 172.17.0.0/16 - - - - - listen - service - * - - - diff --git a/clouder_certificate_authority/images/1.2.0-data/Dockerfile b/clouder_certificate_authority/images/1.2.0-data/Dockerfile index 182a649..d9d246c 100644 --- a/clouder_certificate_authority/images/1.2.0-data/Dockerfile +++ b/clouder_certificate_authority/images/1.2.0-data/Dockerfile @@ -1,4 +1,4 @@ -FROM yannickburon/clouder:base +FROM clouder/base:3.4 MAINTAINER Dave Lasley CMD tail -f /dev/null diff --git a/clouder_certificate_authority/images/1.2.0-data/pki/ca/csr_ca.json b/clouder_certificate_authority/images/1.2.0-data/pki/ca/csr_ca.json index 182a649..d9d246c 100644 --- a/clouder_certificate_authority/images/1.2.0-data/pki/ca/csr_ca.json +++ b/clouder_certificate_authority/images/1.2.0-data/pki/ca/csr_ca.json @@ -1,4 +1,4 @@ -FROM yannickburon/clouder:base +FROM clouder/base:3.4 MAINTAINER Dave Lasley CMD tail -f /dev/null diff --git a/clouder_certificate_authority/images/1.2.0-exec/Dockerfile b/clouder_certificate_authority/images/1.2.0-exec/Dockerfile index abf41d0..0255c9a 100644 --- a/clouder_certificate_authority/images/1.2.0-exec/Dockerfile +++ b/clouder_certificate_authority/images/1.2.0-exec/Dockerfile @@ -1,4 +1,4 @@ -FROM yannickburon/clouder:base +FROM clouder/base:3.4 MAINTAINER Dave Lasley # Install Build Dependencies @@ -35,4 +35,4 @@ WORKDIR /var/pki # Enter ENTRYPOINT ["cfssl"] -CMD ["--help"] +CMD ["cat"] diff --git a/clouder_certificate_authority/models/certificate_authority.py b/clouder_certificate_authority/models/certificate_authority.py index 6b7c9b9..86a1ac3 100644 --- a/clouder_certificate_authority/models/certificate_authority.py +++ b/clouder_certificate_authority/models/certificate_authority.py @@ -2,7 +2,7 @@ # Copyright 2016 LasLabs Inc. # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). -from openerp import api, fields, models +from odoo import api, fields, models class ClouderCertificateAuthority(models.Model): diff --git a/clouder_certificate_authority/models/certificate_name.py b/clouder_certificate_authority/models/certificate_name.py index 786eed2..b29e435 100644 --- a/clouder_certificate_authority/models/certificate_name.py +++ b/clouder_certificate_authority/models/certificate_name.py @@ -2,7 +2,7 @@ # Copyright 2016 LasLabs Inc. # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). -from openerp import api, fields, models +from odoo import api, fields, models class ClouderCertificateName(models.Model): diff --git a/clouder_certificate_authority/models/certificate_request.py b/clouder_certificate_authority/models/certificate_request.py index 6280bcd..779702a 100644 --- a/clouder_certificate_authority/models/certificate_request.py +++ b/clouder_certificate_authority/models/certificate_request.py @@ -2,7 +2,7 @@ # Copyright 2016 LasLabs Inc. # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). -from openerp import api, fields, models +from odoo import api, fields, models class ClouderCertificateRequest(models.Model): diff --git a/clouder_certificate_authority/models/key_abstract.py b/clouder_certificate_authority/models/key_abstract.py index 3714ae1..2d0d534 100644 --- a/clouder_certificate_authority/models/key_abstract.py +++ b/clouder_certificate_authority/models/key_abstract.py @@ -2,7 +2,7 @@ # Copyright 2016 LasLabs Inc. # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). -from openerp import api, fields, models +from odoo import api, fields, models class ClouderKeyAbstract(models.AbstractModel): diff --git a/clouder_certificate_authority/models/key_private.py b/clouder_certificate_authority/models/key_private.py index 786eed2..b29e435 100644 --- a/clouder_certificate_authority/models/key_private.py +++ b/clouder_certificate_authority/models/key_private.py @@ -2,7 +2,7 @@ # Copyright 2016 LasLabs Inc. # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). -from openerp import api, fields, models +from odoo import api, fields, models class ClouderCertificateName(models.Model): diff --git a/clouder_certificate_authority/models/key_public.py b/clouder_certificate_authority/models/key_public.py index 3b323bd..96d822c 100644 --- a/clouder_certificate_authority/models/key_public.py +++ b/clouder_certificate_authority/models/key_public.py @@ -2,7 +2,7 @@ # Copyright 2016 LasLabs Inc. # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). -from openerp import api, fields, models +from odoo import api, fields, models class ClouderKeyPublic(models.Model): From 0283c4a9a42929067627ad02ed63ef3e43d47c4a Mon Sep 17 00:00:00 2001 From: Dave Lasley Date: Wed, 14 Dec 2016 19:19:39 -0800 Subject: [PATCH 3/9] Continue data structuring including keys and cert policies. Split out a base module --- clouder_certificate_authority/README.rst | 14 +- .../data/certificate_policy_use.xml | 162 ++++++++++++++++++ .../models/certificate_authority.py | 65 ++++++- .../models/certificate_policy_auth.py | 50 ++++++ .../models/certificate_policy_sign.py | 45 +++++ .../models/certificate_policy_use.py | 29 ++++ .../models/certificate_request.py | 29 ---- .../models/key_abstract.py | 49 ------ .../models/key_private.py | 53 ------ .../models/key_public.py | 31 ---- clouder_certificate_base/README.rst | 50 ++++++ clouder_certificate_base/__init__.py | 3 + clouder_certificate_base/__manifest__.py | 17 ++ clouder_certificate_base/models/__init__.py | 3 + .../models/certificate.py | 43 +++++ .../models/certificate_abstract.py | 58 +++++++ .../models/certificate_host.py | 16 ++ .../models/certificate_name.py | 0 .../models/certificate_request.py | 56 ++++++ .../models/key_abstract.py | 31 ++++ .../models/key_private.py | 23 +++ clouder_certificate_base/models/key_public.py | 13 ++ 22 files changed, 669 insertions(+), 171 deletions(-) create mode 100644 clouder_certificate_authority/data/certificate_policy_use.xml create mode 100644 clouder_certificate_authority/models/certificate_policy_auth.py create mode 100644 clouder_certificate_authority/models/certificate_policy_sign.py create mode 100644 clouder_certificate_authority/models/certificate_policy_use.py delete mode 100644 clouder_certificate_authority/models/certificate_request.py delete mode 100644 clouder_certificate_authority/models/key_abstract.py delete mode 100644 clouder_certificate_authority/models/key_private.py delete mode 100644 clouder_certificate_authority/models/key_public.py create mode 100644 clouder_certificate_base/README.rst create mode 100644 clouder_certificate_base/__init__.py create mode 100644 clouder_certificate_base/__manifest__.py create mode 100644 clouder_certificate_base/models/__init__.py create mode 100644 clouder_certificate_base/models/certificate.py create mode 100644 clouder_certificate_base/models/certificate_abstract.py create mode 100644 clouder_certificate_base/models/certificate_host.py rename {clouder_certificate_authority => clouder_certificate_base}/models/certificate_name.py (100%) create mode 100644 clouder_certificate_base/models/certificate_request.py create mode 100644 clouder_certificate_base/models/key_abstract.py create mode 100644 clouder_certificate_base/models/key_private.py create mode 100644 clouder_certificate_base/models/key_public.py diff --git a/clouder_certificate_authority/README.rst b/clouder_certificate_authority/README.rst index 35ad60e..4456ad7 100644 --- a/clouder_certificate_authority/README.rst +++ b/clouder_certificate_authority/README.rst @@ -2,11 +2,11 @@ :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html :alt: License: LGPL-3 -================================ -Clouder Template - CFSSL -================================ +============================ +Clouder Certificte Authority +============================ -This module provides a template for CFSSL in Clouder. +This module provides a Certificate Authority using Clouder and CFSSL, Configuration @@ -19,14 +19,12 @@ Usage To use this module, you need to: -#. Create an CFSSL Service in the Clouder Control Panel +#. Create a CFSSL Service in the Clouder Control Panel Known issues / Roadmap ====================== -* Add ElasticDump db backup & restore -* ElasticDump should be a separate service -* Add SSL (Requires CA - https://github.com/clouder-community/clouder/issues/167) +* Add more Signature Profile options - https://github.com/cloudflare/cfssl/blob/86ecfbe5750ebf05565e4c80104d0a7919792fee/doc/cmd/cfssl.txt#L113 Bug Tracker =========== diff --git a/clouder_certificate_authority/data/certificate_policy_use.xml b/clouder_certificate_authority/data/certificate_policy_use.xml new file mode 100644 index 0000000..051530f --- /dev/null +++ b/clouder_certificate_authority/data/certificate_policy_use.xml @@ -0,0 +1,162 @@ + + + + + + + Certificate Signing + cert sign + + + + Signing + signing + + + + S/MIME + s/mime + + + + Server Authentication + server auth + + + + Client Authentication + client auth + + + + Digital Signatures + digital signature + + + + Email Protection + email protection + + + + Key Encipherment + key encipherment + + + + Content Commitment + content commitment + + + + Key Agreement + key agreement + + + + CRL Signing + crl sign + + + + Encipher Only + encipher only + + + + Decipher Only + decipher only + + + + Any + any + + + + Code Signing + code signing + + + + IPSEC End System + ipsec end system + + + + IPSEC Tunnel + ipsec tunnel + + + + IPSEC User + ipsec user + + + + Timestamping + timestamping + + + + OCSP Signing + ocsp signing + + + + Microsoft SGC + microsoft sgc + + + + Netscape SGC + netscape sgc + + + diff --git a/clouder_certificate_authority/models/certificate_authority.py b/clouder_certificate_authority/models/certificate_authority.py index 86a1ac3..45ea2de 100644 --- a/clouder_certificate_authority/models/certificate_authority.py +++ b/clouder_certificate_authority/models/certificate_authority.py @@ -2,6 +2,8 @@ # Copyright 2016 LasLabs Inc. # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). +import json + from odoo import api, fields, models @@ -21,13 +23,53 @@ class ClouderCertificateAuthority(models.Model): 'clouder_certificate_authority.tag_cert_authority', ), ) + private_key_id = fields.Many2one( + string='Private Key', + comodel_name='clouder.key.private', + domain="[('is_private', '=', True)]", + context="{'default_is_private': True}", + ) + certificate_id = fields.Many2one( + string='Certificate', + comodel_name='clouder.key.public', + related='certificate_request_id.public_key_id', + ) + certificate_request_id = fields.Many2one( + string='CSR', + comodel_name='clouder.certificate.request', + ) + sign_policy_default_id = fields.Many2one( + string='Default Signing Policy', + comodel_name='clouder.certificate.policy.sign', + required=True, + ) + sign_policy_profile_ids = fields.Many2many( + string='Signing Policy Profiles', + comodel_name='clouder.certificate.policy.sign', + ) + auth_policy_ids = fields.Many2many( + string='Auth Policies', + comodel_name='clouder.certificate.policy.auth', + compute='_compute_auth_policy_ids', + ) exec_service_id = fields.Many2one( string='Executor Service', comodel_name='clouder.service', compute='_compute_exec_service_id', ) + computed_config = fields.Serialized( + compute="_compute_computed_config", + ) - @api.model + @api.multi + def _compute_auth_policy_ids(self): + for record in self: + policies = sum((record.sign_policy_default_id, + record.sign_policy_profile_ids, + )) + record.auth_policy_ids = policies.mapped('auth_policy_id') + + @api.multi def _compute_exec_service_id(self): for record in self: service_ids = record.service_ids.filtered( @@ -35,4 +77,25 @@ def _compute_exec_service_id(self): ) record.exec_service_id = service_ids[0] + @api.multi + def _compute_computed_config(self): + """ It computes the keys required for the JSON request """ + for record in self: + profiles = { + p.name: p.computed for p in record.sign_policy_profile_ids + } + auth_keys = { + auth.name: auth.computed for auth in record.auth_policy_ids + } + record.computed_config = { + 'signing': record.sign_policy_default_id.computed, + 'profiles': profiles, + 'auth_keys': auth_keys, + } + @api.multi + def get_json(self, computed_type='config'): + """ It returns the JSON representation of this object """ + self.ensure_one() + attribute = get(self, 'computed_%s' % computed_type) + return json.dumps(attribute) diff --git a/clouder_certificate_authority/models/certificate_policy_auth.py b/clouder_certificate_authority/models/certificate_policy_auth.py new file mode 100644 index 0000000..16b3997 --- /dev/null +++ b/clouder_certificate_authority/models/certificate_policy_auth.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import api, fields, models + +from odoo.addons.clouder.tools import generate_random_password + + +class ClouderCertificatePolicyAuth(models.Model): + """ It provides a CA Signature Auth Policy """ + + _name = 'clouder.certificate.policy.auth' + _description = 'Clouder Certificate Auth Policy' + + name = fields.Char( + required=True, + ) + key = fields.Char( + required=True, + default=lambda s: s._default_key(), + ) + key_type = fields.Selection([ + ('standard', 'Standard'), + ], + default='standard', + required=True, + ) + computed = fields.Serialized( + compute="_compute_computed", + ) + + _sql_constraints = [ + ('name_uniq', 'UNIQUE(name)', 'Name must be unique.'), + ('key_uniq', 'UNIQUE(key)', 'Key must be unique.'), + ] + + @api.model + def _default_key(self): + passwd = generate_random_password() + return passwd.encode('hex')[:16] + + @api.multi + def _compute_computed(self): + """ It computes the keys required for the JSON request """ + for record in self: + record.computed = { + 'key': record.key, + 'type': record.key_type, + } diff --git a/clouder_certificate_authority/models/certificate_policy_sign.py b/clouder_certificate_authority/models/certificate_policy_sign.py new file mode 100644 index 0000000..dd99a5a --- /dev/null +++ b/clouder_certificate_authority/models/certificate_policy_sign.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import api, fields, models + + +class ClouderCertificatePolicySign(models.Model): + """ It provides a CA Signature Policy """ + + _name = 'clouder.certificate.policy.sign' + _description = 'Clouder Certificate Signing Policy' + + name = fields.Char( + required=True, + ) + usage_ids = fields.Many2many( + string='Usages', + comodel_name='clouder.certificate.policy.use', + required=True, + ) + auth_policy_id = fields.Many2one( + string='Auth Key', + comodel_name='clouder.certificate.policy.auth', + required=True, + ) + expire_hours = fields.Integer( + required=True, + default=(365 * 24), + ) + computed = fields.Serialized( + compute="_compute_computed", + ) + + @api.multi + def _compute_computed(self): + """ It computes the keys required for the JSON request """ + for record in self: + record.computed = { + 'auth_key': record.auth_policy_id.name, + 'expiry': '%sh' % expire_hours, + 'usages': [ + usage.code for usage in record.usage_ids + ], + } diff --git a/clouder_certificate_authority/models/certificate_policy_use.py b/clouder_certificate_authority/models/certificate_policy_use.py new file mode 100644 index 0000000..72cb2a6 --- /dev/null +++ b/clouder_certificate_authority/models/certificate_policy_use.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import api, fields, models + + +class ClouderCertificatePolicyUse(models.Model): + """ It provides a CA Signature Use Policy """ + + _name = 'clouder.certificate.policy.use' + _description = 'Clouder Certificate Usage Policy' + _order = 'sequence, name' + + name = fields.Char( + required=True, + ) + code = fields.Char( + required=True, + ) + sequence = fields.Integer( + required=True, + default=5, + ) + + _sql_constraints = [ + ('code_uniq', 'UNIQUE(code)', 'Code must be unique.'), + ('name_uniq', 'UNIQUE(name)', 'Name must be unique.'), + ] diff --git a/clouder_certificate_authority/models/certificate_request.py b/clouder_certificate_authority/models/certificate_request.py deleted file mode 100644 index 779702a..0000000 --- a/clouder_certificate_authority/models/certificate_request.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2016 LasLabs Inc. -# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). - -from odoo import api, fields, models - - -class ClouderCertificateRequest(models.Model): - """ It provides the concept of a Certificate Request """ - - _name = 'clouder.certificate.request' - _description = 'Clouder Certificate Request' - - authority_id = fields.Many2one( - string='Cert Authority', - comodel_name='clouder.certificate.authority', - required=True, - ondelete='cascade', - ) - - @api.model - def _compute_exec_service_id(self): - for record in self: - service_ids = record.service_ids.filtered( - lambda s: s.code == 'exec', - ) - record.exec_service_id = service_ids[0] - - diff --git a/clouder_certificate_authority/models/key_abstract.py b/clouder_certificate_authority/models/key_abstract.py deleted file mode 100644 index 2d0d534..0000000 --- a/clouder_certificate_authority/models/key_abstract.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2016 LasLabs Inc. -# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). - -from odoo import api, fields, models - - -class ClouderKeyAbstract(models.AbstractModel): - """ It provides attributes and methods related to all keys """ - - _name = 'clouder.key.public' - _description = 'Clouder Key Public' - - name = fields.Char( - required=True, - ) - description = fields.Char() - mime_type = fields.Selection( - selection='_get_mime_types', - ) - attachment_id = fields.Many2one( - string='Key', - comodel_name='ir.attachment', - required=True, - context="""{ - 'default_type': 'binary', - 'default_res_model': _name, - 'default_res_field': 'attachment_id', - 'default_res_id': id, - 'default_name': name, - 'default_description': description, - 'default_mime_type': mime_type, - }""", - ) - data = fields.Text( - related='attachment_id.datas', - ) - - @api.model - def _get_mime_types(self): - return [ - ('application/pkcs8', 'PKCS-8'), - ('application/pkcs10', 'PKCS-10'), - ('application/x-pkcs12', 'PKCS-12'), - ('application/x-pem-file', 'PEM'), - ('application/pkcs7-mime', 'PKCS-7 MIME'), - ('application/x-x509-ca-cert', 'X.509 CA'), - ('application/x-x509-user-cert', 'X.509 User'), - ] diff --git a/clouder_certificate_authority/models/key_private.py b/clouder_certificate_authority/models/key_private.py deleted file mode 100644 index b29e435..0000000 --- a/clouder_certificate_authority/models/key_private.py +++ /dev/null @@ -1,53 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2016 LasLabs Inc. -# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). - -from odoo import api, fields, models - - -class ClouderCertificateName(models.Model): - """ It provides the concept of a cert's SubjectInfo """ - - _name = 'clouder.certificate.name' - _description = 'Clouder Certificate Name' - - country_id = fields.Many2one( - string='Country', - model='res.country', - required=True, - default=lambda s: s.env.user.country_id, - ) - state_id = fields.Many2one( - string='State', - model='res.country.state', - domain='[(country_id, "=", country_id)]', - default=lambda s: s.env.user.state_id, - ) - city = fields.Char( - default=lambda s: s.env.user.city, - ) - company_id = fields.Many2one( - string='Company', - model='res.company', - required=True, - domain='[(company_id, "=", company_id)]', - default=lambda s: s.env.user.company_id, - ) - organization_unit = fields.Char( - required=True, - ) - computed = fields.Serialized( - compute="_compute_computed", - ) - - @api.multi - def _compute_computed(self): - """ It computes the keys required for the JSON request """ - for record in self: - record.computed = { - 'C': record.country_id.code, - 'ST': record.state_id.name, - 'L': record.city, - 'O': record.company_id.name, - 'OU': record.organization_unit, - } diff --git a/clouder_certificate_authority/models/key_public.py b/clouder_certificate_authority/models/key_public.py deleted file mode 100644 index 96d822c..0000000 --- a/clouder_certificate_authority/models/key_public.py +++ /dev/null @@ -1,31 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2016 LasLabs Inc. -# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). - -from odoo import api, fields, models - - -class ClouderKeyPublic(models.Model): - """ It provides attributes and methods related to handling public keys """ - - _name = 'clouder.key.public' - _description = 'Clouder Key Public' - - name = fields.Char( - required=True, - ) - attachment_id = fields.Many2one( - string='Key', - comodel_name='ir.attachment', - required=True, - context="""{ - 'default_type': 'binary', - 'default_res_model': 'clouder.key.public', - 'default_res_field': 'attachment_id', - 'default_res_id': id, - 'default_name': name, - }""", - ) - data = fields.Text( - related='attachment_id.datas', - ) diff --git a/clouder_certificate_base/README.rst b/clouder_certificate_base/README.rst new file mode 100644 index 0000000..f95bcaa --- /dev/null +++ b/clouder_certificate_base/README.rst @@ -0,0 +1,50 @@ +.. image:: https://img.shields.io/badge/licence-LGPL--3-blue.svg + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 + +======================== +Clouder Certificate Base +======================== + +This module provides a concept of Certificates and Keys for Clouder. + + +Configuration +============= + +Clouder configuration instructions are available at https://clouder.readthedocs.io/ + +Usage +===== + +To use this module, you need to: + +#. Create a CFSSL Service in the Clouder Control Panel + +Known issues / Roadmap +====================== + +* Add more Signature Profile options - https://github.com/cloudflare/cfssl/blob/86ecfbe5750ebf05565e4c80104d0a7919792fee/doc/cmd/cfssl.txt#L113 + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smashing it by providing a detailed and welcomed feedback. + +Credits +======= + +Contributors +------------ + +* Dave Lasley + +Maintainer +---------- + +This module is maintained by Clouder Community. + +To contribute to this module, please visit https://github.com/clouder-community/clouder diff --git a/clouder_certificate_base/__init__.py b/clouder_certificate_base/__init__.py new file mode 100644 index 0000000..08d9d6b --- /dev/null +++ b/clouder_certificate_base/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). diff --git a/clouder_certificate_base/__manifest__.py b/clouder_certificate_base/__manifest__.py new file mode 100644 index 0000000..b38345c --- /dev/null +++ b/clouder_certificate_base/__manifest__.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +{ + 'name': 'Clouder Certificate Assets', + 'version': '10.0.10.0.0', + 'category': 'Hidden', + 'depends': [ + 'clouder', + ], + 'author': 'LasLabs Inc.', + 'license': 'LGPL-3', + 'website': 'https://github.com/clouder-community/clouder', + 'installable': True, + 'application': False, +} diff --git a/clouder_certificate_base/models/__init__.py b/clouder_certificate_base/models/__init__.py new file mode 100644 index 0000000..08d9d6b --- /dev/null +++ b/clouder_certificate_base/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). diff --git a/clouder_certificate_base/models/certificate.py b/clouder_certificate_base/models/certificate.py new file mode 100644 index 0000000..ed021aa --- /dev/null +++ b/clouder_certificate_base/models/certificate.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import api, fields, models + + +class ClouderCertificate(models.Model): + """ It provides the concept of a Certificate """ + + _name = 'clouder.certificate.request' + _description = 'Clouder Certificate Request' + + authority_id = fields.Many2one( + string='Cert Authority', + comodel_name='clouder.certificate.authority', + required=True, + ondelete='cascade', + ) + common_name_id = fields.Many2one( + string='Common Name', + comodel_name='clouder.certificate.host', + required=True, + ) + subject_info_ids = fields.Many2many( + string='Names', + comodel_name='clouder.certificate.name', + required=True, + ) + computed = fields.Serialized( + compute="_compute_computed", + ) + + @api.multi + def _compute_computed(self): + """ It computes the keys required for the JSON request """ + for record in self: + record.computed = { + 'CN': record.common_name_id.name, + 'names': [ + name.computed for name in record.subject_info_ids + ], + } diff --git a/clouder_certificate_base/models/certificate_abstract.py b/clouder_certificate_base/models/certificate_abstract.py new file mode 100644 index 0000000..e577fca --- /dev/null +++ b/clouder_certificate_base/models/certificate_abstract.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import api, fields, models + + +class ClouderCertificateAbstract(models.AbstractModel): + """ It provides attributes and methods related to all cert files """ + + _name = 'clouder.certificate.abstract' + _description = 'Clouder Certificate Abstract' + + name = fields.Char( + required=True, + ) + description = fields.Char() + mime_sub_type = fields.Selection( + selection=lambda s: s._get_mime_sub_types(), + deault='x-pkcs12', + ) + mime_type = fields.Char( + readonly=True, + computed='_compute_mime_type', + ) + attachment_id = fields.Many2one( + string='Key', + comodel_name='ir.attachment', + context="""{ + 'default_type': 'binary', + 'default_res_model': _name, + 'default_res_field': 'attachment_id', + 'default_res_id': id, + 'default_name': name, + 'default_description': description, + 'default_mime_type': mime_type, + }""", + ) + data = fields.Text( + related='attachment_id.datas', + ) + + @api.model + def _get_mime_sub_types(self): + return [ + ('pkcs8', 'PKCS-8'), + ('pkcs10', 'PKCS-10'), + ('x-pkcs12', 'PKCS-12'), + ('x-pem-file', 'PEM'), + ('pkcs7-mime', 'PKCS-7 MIME'), + ('x-x509-ca-cert', 'X.509 CA'), + ('x-x509-user-cert', 'X.509 User'), + ] + + @api.multi + def _compute_mime_type(self): + for record in self: + record.mime_type = 'application/%s' % record.mime_sub_type diff --git a/clouder_certificate_base/models/certificate_host.py b/clouder_certificate_base/models/certificate_host.py new file mode 100644 index 0000000..9f0c13d --- /dev/null +++ b/clouder_certificate_base/models/certificate_host.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import fields, models + + +class ClouderCertificateHost(models.Model): + """ It provides the concept of a cert's CommonName """ + + _name = 'clouder.certificate.host' + _description = 'Clouder Certificate Host' + + name = fields.Char( + required=True, + ) diff --git a/clouder_certificate_authority/models/certificate_name.py b/clouder_certificate_base/models/certificate_name.py similarity index 100% rename from clouder_certificate_authority/models/certificate_name.py rename to clouder_certificate_base/models/certificate_name.py diff --git a/clouder_certificate_base/models/certificate_request.py b/clouder_certificate_base/models/certificate_request.py new file mode 100644 index 0000000..48a69b6 --- /dev/null +++ b/clouder_certificate_base/models/certificate_request.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +import json + +from odoo import api, fields, models + + +class ClouderCertificateRequest(models.Model): + """ It provides the concept of a Certificate Request """ + + _name = 'clouder.certificate.request' + _inherit = 'clouder.certificate.abstract' + _description = 'Clouder Certificate Request' + + authority_id = fields.Many2one( + string='Cert Authority', + comodel_name='clouder.certificate.authority', + required=True, + ondelete='cascade', + ) + host_ids = fields.Many2one( + string='Common Name', + comodel_name='clouder.certificate.host', + required=True, + ) + subject_info_ids = fields.Many2many( + string='Names', + comodel_name='clouder.certificate.name', + required=True, + ) + public_key_id = fields.Many2one( + string='Public Key', + comodel_name='clouder.key.public', + ) + computed = fields.Serialized( + compute="_compute_computed", + ) + + @api.multi + def _compute_computed(self): + """ It computes the keys required for the JSON request """ + for record in self: + record.computed = { + 'CN': record.common_name_id.name, + 'names': [ + name.computed for name in record.subject_info_ids + ], + } + + @api.multi + def get_json(self): + """ It returns the JSON representation of this object """ + self.ensure_one() + return json.dumps(self.computed) diff --git a/clouder_certificate_base/models/key_abstract.py b/clouder_certificate_base/models/key_abstract.py new file mode 100644 index 0000000..3f1341b --- /dev/null +++ b/clouder_certificate_base/models/key_abstract.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import api, fields, models + + +class ClouderKeyAbstract(models.Model): + """ It provides attributes and methods related to all keys """ + + _name = 'clouder.key.abstract' + _inherit = 'clouder.certificate.abstract' + _description = 'Clouder Key Abstract' + + strength = fields.Integer( + default=4096, + ) + algorithm = fields.Selection( + selection=lambda s: s._get_algorithms(), + ) + is_private = fields.Boolean() + active = fields.Boolean( + default=True, + ) + + @api.model + def _get_algorithms(self): + return [ + ('rsa', 'RSA'), + ('ecdsa', 'ECDSA'), + ] diff --git a/clouder_certificate_base/models/key_private.py b/clouder_certificate_base/models/key_private.py new file mode 100644 index 0000000..5c8e214 --- /dev/null +++ b/clouder_certificate_base/models/key_private.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import api, fields, models + + +class ClouderKeyPrivate(models.Model): + """ It provides the concept of a Private Key """ + + _name = 'clouder.key.private' + _inherit = 'clouder.key.abstract' + _description = 'Clouder Key Private' + + public_key_id = fields.Many2one( + string='Public Key', + comodel_name='clouder.key.public', + ) + + @api.model + def create(self, vals): + vals['is_private'] = True + return super(ClouderKeyPrivate, self).create(vals) diff --git a/clouder_certificate_base/models/key_public.py b/clouder_certificate_base/models/key_public.py new file mode 100644 index 0000000..e40713d --- /dev/null +++ b/clouder_certificate_base/models/key_public.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import api, fields, models + + +class ClouderKeyPublic(models.Model): + """ It provides attributes and methods related to handling public keys """ + + _name = 'clouder.key.public' + _inherit = 'clouder.key.abstract' + _description = 'Clouder Key Abstract' From b2175f2628dd07207be942a80e792a56a06d978c Mon Sep 17 00:00:00 2001 From: Dave Lasley Date: Thu, 15 Dec 2016 13:25:45 -0800 Subject: [PATCH 4/9] Somewhat lock down data models, move back to one module --- .../models/certificate.py | 0 .../models/certificate_abstract.py | 0 .../models/certificate_authority.py | 44 ++------------- .../models/certificate_host.py | 4 ++ .../models/certificate_name.py | 0 .../models/certificate_request.py | 26 +++++++-- .../models/config_certificate_abstract.py | 54 +++++++++++++++++++ .../models/config_certificate_client.py | 28 ++++++++++ .../models/config_certificate_server.py | 13 +++++ .../models/key_abstract.py | 1 + .../models/key_private.py | 0 .../models/key_public.py | 0 clouder_certificate_base/README.rst | 50 ----------------- clouder_certificate_base/__init__.py | 3 -- clouder_certificate_base/__manifest__.py | 17 ------ clouder_certificate_base/models/__init__.py | 3 -- 16 files changed, 126 insertions(+), 117 deletions(-) rename {clouder_certificate_base => clouder_certificate_authority}/models/certificate.py (100%) rename {clouder_certificate_base => clouder_certificate_authority}/models/certificate_abstract.py (100%) rename {clouder_certificate_base => clouder_certificate_authority}/models/certificate_host.py (83%) rename {clouder_certificate_base => clouder_certificate_authority}/models/certificate_name.py (100%) rename {clouder_certificate_base => clouder_certificate_authority}/models/certificate_request.py (72%) create mode 100644 clouder_certificate_authority/models/config_certificate_abstract.py create mode 100644 clouder_certificate_authority/models/config_certificate_client.py create mode 100644 clouder_certificate_authority/models/config_certificate_server.py rename {clouder_certificate_base => clouder_certificate_authority}/models/key_abstract.py (97%) rename {clouder_certificate_base => clouder_certificate_authority}/models/key_private.py (100%) rename {clouder_certificate_base => clouder_certificate_authority}/models/key_public.py (100%) delete mode 100644 clouder_certificate_base/README.rst delete mode 100644 clouder_certificate_base/__init__.py delete mode 100644 clouder_certificate_base/__manifest__.py delete mode 100644 clouder_certificate_base/models/__init__.py diff --git a/clouder_certificate_base/models/certificate.py b/clouder_certificate_authority/models/certificate.py similarity index 100% rename from clouder_certificate_base/models/certificate.py rename to clouder_certificate_authority/models/certificate.py diff --git a/clouder_certificate_base/models/certificate_abstract.py b/clouder_certificate_authority/models/certificate_abstract.py similarity index 100% rename from clouder_certificate_base/models/certificate_abstract.py rename to clouder_certificate_authority/models/certificate_abstract.py diff --git a/clouder_certificate_authority/models/certificate_authority.py b/clouder_certificate_authority/models/certificate_authority.py index 45ea2de..33bf488 100644 --- a/clouder_certificate_authority/models/certificate_authority.py +++ b/clouder_certificate_authority/models/certificate_authority.py @@ -8,7 +8,7 @@ class ClouderCertificateAuthority(models.Model): - """ It provides an interface for controlling a Cert Authority """ + """ It provides an interface for controlling a Cert Authority. """ _name = 'clouder.certificate.authority' _description = 'Clouder Certificate Authority' @@ -38,27 +38,14 @@ class ClouderCertificateAuthority(models.Model): string='CSR', comodel_name='clouder.certificate.request', ) - sign_policy_default_id = fields.Many2one( - string='Default Signing Policy', - comodel_name='clouder.certificate.policy.sign', - required=True, - ) - sign_policy_profile_ids = fields.Many2many( - string='Signing Policy Profiles', - comodel_name='clouder.certificate.policy.sign', - ) - auth_policy_ids = fields.Many2many( - string='Auth Policies', - comodel_name='clouder.certificate.policy.auth', - compute='_compute_auth_policy_ids', - ) exec_service_id = fields.Many2one( string='Executor Service', comodel_name='clouder.service', compute='_compute_exec_service_id', ) - computed_config = fields.Serialized( - compute="_compute_computed_config", + config_id = fields.Many2one( + string='Configuration', + comodel_name='clouder.config.certificate.server', ) @api.multi @@ -76,26 +63,3 @@ def _compute_exec_service_id(self): lambda s: s.code == 'exec', ) record.exec_service_id = service_ids[0] - - @api.multi - def _compute_computed_config(self): - """ It computes the keys required for the JSON request """ - for record in self: - profiles = { - p.name: p.computed for p in record.sign_policy_profile_ids - } - auth_keys = { - auth.name: auth.computed for auth in record.auth_policy_ids - } - record.computed_config = { - 'signing': record.sign_policy_default_id.computed, - 'profiles': profiles, - 'auth_keys': auth_keys, - } - - @api.multi - def get_json(self, computed_type='config'): - """ It returns the JSON representation of this object """ - self.ensure_one() - attribute = get(self, 'computed_%s' % computed_type) - return json.dumps(attribute) diff --git a/clouder_certificate_base/models/certificate_host.py b/clouder_certificate_authority/models/certificate_host.py similarity index 83% rename from clouder_certificate_base/models/certificate_host.py rename to clouder_certificate_authority/models/certificate_host.py index 9f0c13d..a9d30c9 100644 --- a/clouder_certificate_base/models/certificate_host.py +++ b/clouder_certificate_authority/models/certificate_host.py @@ -14,3 +14,7 @@ class ClouderCertificateHost(models.Model): name = fields.Char( required=True, ) + host = fields.Char( + required=True, + ) + port = fields.Integer() diff --git a/clouder_certificate_base/models/certificate_name.py b/clouder_certificate_authority/models/certificate_name.py similarity index 100% rename from clouder_certificate_base/models/certificate_name.py rename to clouder_certificate_authority/models/certificate_name.py diff --git a/clouder_certificate_base/models/certificate_request.py b/clouder_certificate_authority/models/certificate_request.py similarity index 72% rename from clouder_certificate_base/models/certificate_request.py rename to clouder_certificate_authority/models/certificate_request.py index 48a69b6..64d8f57 100644 --- a/clouder_certificate_base/models/certificate_request.py +++ b/clouder_certificate_authority/models/certificate_request.py @@ -14,6 +14,10 @@ class ClouderCertificateRequest(models.Model): _inherit = 'clouder.certificate.abstract' _description = 'Clouder Certificate Request' + name = fields.Char( + string='Common Name', + required=True, + ) authority_id = fields.Many2one( string='Cert Authority', comodel_name='clouder.certificate.authority', @@ -21,9 +25,8 @@ class ClouderCertificateRequest(models.Model): ondelete='cascade', ) host_ids = fields.Many2one( - string='Common Name', + string='Hosts', comodel_name='clouder.certificate.host', - required=True, ) subject_info_ids = fields.Many2many( string='Names', @@ -34,6 +37,14 @@ class ClouderCertificateRequest(models.Model): string='Public Key', comodel_name='clouder.key.public', ) + strength = fields.Integer( + default=4096, + required=True, + ) + algorithm = fields.Selection( + default='rsa', + selection=lambda s: s.env['clouder.key.abstract']._get_algorithms(), + ) computed = fields.Serialized( compute="_compute_computed", ) @@ -43,14 +54,21 @@ def _compute_computed(self): """ It computes the keys required for the JSON request """ for record in self: record.computed = { - 'CN': record.common_name_id.name, + 'CN': record.name, 'names': [ name.computed for name in record.subject_info_ids ], + 'hosts': [ + '%s:%s' % (h.host, h.port) for h in record.host_ids + ], + 'key': { + 'algo': record.algorithm, + 'size': record.strength, + }, } @api.multi - def get_json(self): + def to_json(self): """ It returns the JSON representation of this object """ self.ensure_one() return json.dumps(self.computed) diff --git a/clouder_certificate_authority/models/config_certificate_abstract.py b/clouder_certificate_authority/models/config_certificate_abstract.py new file mode 100644 index 0000000..d875701 --- /dev/null +++ b/clouder_certificate_authority/models/config_certificate_abstract.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import api, fields, models + + +class ClouderConfigCertificateAbstract(models.AbstractModel): + """ It provides data handling for certificate client + server configs. """ + + _name = 'clouder.config.certificate.abstract' + _description = 'Clouder Config Certificate Abstract' + + sign_policy_default_id = fields.Many2one( + string='Default Signing Policy', + comodel_name='clouder.certificate.policy.sign', + required=True, + ) + sign_policy_profile_ids = fields.Many2many( + string='Signing Policy Profiles', + comodel_name='clouder.certificate.policy.sign', + ) + auth_policy_ids = fields.Many2many( + string='Auth Policies', + comodel_name='clouder.certificate.policy.auth', + compute='_compute_auth_policy_ids', + ) + computed = fields.Serialized( + compute="_compute_computed", + ) + + @api.multi + def _compute_computed(self): + """ It computes the keys required for the JSON request. """ + for record in self: + profiles = { + p.name: p.computed for p in record.sign_policy_profile_ids + } + auth_keys = { + auth.name: auth.computed for auth in record.auth_policy_ids + } + record.computed_config = { + 'signing': { + 'default': record.sign_policy_default_id.computed, + 'profiles': profiles, + }, + 'auth_keys': auth_keys, + } + + @api.multi + def to_json(self): + """ It returns the JSON representation of this object """ + self.ensure_one() + return json.dumps(self.computed) diff --git a/clouder_certificate_authority/models/config_certificate_client.py b/clouder_certificate_authority/models/config_certificate_client.py new file mode 100644 index 0000000..8cf503c --- /dev/null +++ b/clouder_certificate_authority/models/config_certificate_client.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import models + + +class ClouderConfigCertificateClient(models.Model): + """ It provides data handling for certificate client configs. """ + + _name = 'clouder.config.certificate.client' + _inherit = 'clouder.config.certificate.abstract' + _description = 'Clouder Config Certificate Client' + + remote_ids = fields.Many2many( + string='Remotes', + comodel_name='clouder.certificate.host', + required=True, + ) + + @api.multi + def _compute_computed(self): + """ It computes the keys required for the JSON request. """ + for record in self: + super(ClouderConfigCertificateClient, record)._compute_computed() + record.computed['remotes'] = { + r.name: '%s:%s' % (r.host, r.port) for r in record.remote_ids + } diff --git a/clouder_certificate_authority/models/config_certificate_server.py b/clouder_certificate_authority/models/config_certificate_server.py new file mode 100644 index 0000000..330c8e1 --- /dev/null +++ b/clouder_certificate_authority/models/config_certificate_server.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import models + + +class ClouderConfigCertificateServer(models.Model): + """ It provides data handling for certificate server configs. """ + + _name = 'clouder.config.certificate.server' + _inherit = 'clouder.config.certificate.abstract' + _description = 'Clouder Config Certificate Server' diff --git a/clouder_certificate_base/models/key_abstract.py b/clouder_certificate_authority/models/key_abstract.py similarity index 97% rename from clouder_certificate_base/models/key_abstract.py rename to clouder_certificate_authority/models/key_abstract.py index 3f1341b..717169f 100644 --- a/clouder_certificate_base/models/key_abstract.py +++ b/clouder_certificate_authority/models/key_abstract.py @@ -16,6 +16,7 @@ class ClouderKeyAbstract(models.Model): default=4096, ) algorithm = fields.Selection( + default='rsa', selection=lambda s: s._get_algorithms(), ) is_private = fields.Boolean() diff --git a/clouder_certificate_base/models/key_private.py b/clouder_certificate_authority/models/key_private.py similarity index 100% rename from clouder_certificate_base/models/key_private.py rename to clouder_certificate_authority/models/key_private.py diff --git a/clouder_certificate_base/models/key_public.py b/clouder_certificate_authority/models/key_public.py similarity index 100% rename from clouder_certificate_base/models/key_public.py rename to clouder_certificate_authority/models/key_public.py diff --git a/clouder_certificate_base/README.rst b/clouder_certificate_base/README.rst deleted file mode 100644 index f95bcaa..0000000 --- a/clouder_certificate_base/README.rst +++ /dev/null @@ -1,50 +0,0 @@ -.. image:: https://img.shields.io/badge/licence-LGPL--3-blue.svg - :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html - :alt: License: LGPL-3 - -======================== -Clouder Certificate Base -======================== - -This module provides a concept of Certificates and Keys for Clouder. - - -Configuration -============= - -Clouder configuration instructions are available at https://clouder.readthedocs.io/ - -Usage -===== - -To use this module, you need to: - -#. Create a CFSSL Service in the Clouder Control Panel - -Known issues / Roadmap -====================== - -* Add more Signature Profile options - https://github.com/cloudflare/cfssl/blob/86ecfbe5750ebf05565e4c80104d0a7919792fee/doc/cmd/cfssl.txt#L113 - -Bug Tracker -=========== - -Bugs are tracked on `GitHub Issues -`_. In case of trouble, please -check there if your issue has already been reported. If you spotted it first, -help us smashing it by providing a detailed and welcomed feedback. - -Credits -======= - -Contributors ------------- - -* Dave Lasley - -Maintainer ----------- - -This module is maintained by Clouder Community. - -To contribute to this module, please visit https://github.com/clouder-community/clouder diff --git a/clouder_certificate_base/__init__.py b/clouder_certificate_base/__init__.py deleted file mode 100644 index 08d9d6b..0000000 --- a/clouder_certificate_base/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2016 LasLabs Inc. -# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). diff --git a/clouder_certificate_base/__manifest__.py b/clouder_certificate_base/__manifest__.py deleted file mode 100644 index b38345c..0000000 --- a/clouder_certificate_base/__manifest__.py +++ /dev/null @@ -1,17 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2016 LasLabs Inc. -# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). - -{ - 'name': 'Clouder Certificate Assets', - 'version': '10.0.10.0.0', - 'category': 'Hidden', - 'depends': [ - 'clouder', - ], - 'author': 'LasLabs Inc.', - 'license': 'LGPL-3', - 'website': 'https://github.com/clouder-community/clouder', - 'installable': True, - 'application': False, -} diff --git a/clouder_certificate_base/models/__init__.py b/clouder_certificate_base/models/__init__.py deleted file mode 100644 index 08d9d6b..0000000 --- a/clouder_certificate_base/models/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2016 LasLabs Inc. -# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). From be5534aeccbf4cb0e383065cc9d4590dc64317af Mon Sep 17 00:00:00 2001 From: Dave Lasley Date: Thu, 15 Dec 2016 18:26:03 -0800 Subject: [PATCH 5/9] Begin CFSSL library - I should probably make this a new python package --- clouder_certificate_authority/cfssl.py | 381 ++++++++++++++++++ clouder_certificate_authority/exceptions.py | 13 + .../models/key_abstract.py | 2 +- 3 files changed, 395 insertions(+), 1 deletion(-) create mode 100644 clouder_certificate_authority/cfssl.py create mode 100644 clouder_certificate_authority/exceptions.py diff --git a/clouder_certificate_authority/cfssl.py b/clouder_certificate_authority/cfssl.py new file mode 100644 index 0000000..1ed660c --- /dev/null +++ b/clouder_certificate_authority/cfssl.py @@ -0,0 +1,381 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +import requests + +from .exceptions import CFSSLException, CFSSLRemoteException + + +class CFSSL(object): + """ It provides Python bindings to a remote CFSSL server via HTTP(S). + + Additional documentation regarding the API endpoints is available at + https://github.com/cloudflare/cfssl/tree/master/doc/api + """ + + def __init__(self, host, port, ssl=True): + ssl = 'https' if ssl else 'http' + self.uri_base = '%s://%s:%s' % (ssl, host, port) + + def auth_sign(self, token, request, datetime=None, remote_address=None): + """ It provides returns a signed certificate. + + Args: + token: (str) The authentication token. + request: (mixed) Signing request document (e.g. as + documented in endpoint_sign.txt, but not JSON encoded). + datetime: (datetime.datetime) Authentication timestamp. + remote_address: (str) An address used in making the request. + Returns: + (str) A PEM-encoded certificate that has been signed by the + server. + """ + data = self._clean_mapping({ + 'token': token, + 'request': request, + 'datetime': datetime, + 'remote_address': remote_address, + }) + return self.call('authsign', 'POST', data=data) + + def bundle(self, certificate, private_key=None, + flavor='ubiquitous', domain=None, ip=None): + """ It builds and returns certificate bundles. + + Args: + certificate: (str) The PEM-encoded certificate to be bundled. + + If the ``certificate`` parameter is present, the following four + arguments are valid: + private_key: (str) The PEM-encoded private key to be included with + the bundle. This is valid only if the server is not running in + ``keyless`` mode. + flavor: (str) One of ``ubiquitous``, ``force``, or ``optimal``, + with a default value of ``ubiquitous``. A ubiquitous bundle is + one that has a higher probability of being verified everywhere, + even by clients using outdated or unusual trust stores. Force will + cause the endpoint to use the bundle provided in the + ``certificate`` parameter, and will only verify that the bundle + is a valid (verifiable) chain. + domain: (str) The domain name to verify as the hostname of the + certificate. + ip: (str) The IP address to verify against the certificate IP + SANs. + + If only the ``domain`` parameter is present, the following + parameter is valid: + + ip: (str) The IP address of the remote host; this will fetch the + certificate from the IP, and verify that it is valid for the + domain name. + + Returns: + (dict) Object repesenting the bundle, with the following keys: + * bundle contains the concatenated list of PEM certificates + forming the certificate chain; this forms the actual + bundle. The remaining parameters are additional metadata + supporting the bundle. + * crl_support is true if CRL information is contained in the + certificate. + * crt contains the original certificate the bundle is built + from. + * expires contains the expiration date of the certificate. + * hostnames contains the SAN hostnames for the certificate. + * issuer contains the X.509 issuer information for the + certificate. + * key contains the private key for the certificate, if one + was presented. + * key_size contains the size of the key in bits for the + certificate. It will be present even if the private key wasn't + provided because this can be determined from the public key. + * key_type contains a textual description of the key type, + e.g. '2048-bit RSA'. + * ocsp contains the OCSP URLs for the certificate, if present. + * ocsp_support will be true if the certificate supports OCSP + revocation checking. + * signature contains the signature type used in the + certificate, e.g. 'SHA1WithRSA'. + * status contains a number of elements: + * code is bit-encoded error code. 1st bit indicates whether + there is a expiring certificate in the bundle. 2nd bit indicates + whether there is a ubiquity issue with the bundle. + * expiring_SKIs contains the SKIs (subject key identifiers) + for any certificates that might expire soon (within 30 + days). + * messages is a list of human-readable warnings on bundle + ubiquity and certificate expiration. For example, an expiration + warning can be "The expiring cert is #1 in the chain", + indicating the leaf certificate is expiring. Ubiquity warnings + include SHA-1 deprecation warning (if the bundle triggers + any major browser's SHA-1 deprecation policy), SHA-2 + compatibility warning (if the bundle contains signatures using + ECDSA SHA-2 hash algorithms, it will be rejected by Windows XP + SP2), compatibility warning (if the bundle contains ECDSA + certificates, it will be rejected by Windows XP, Android 2.2 and + Android 2.3 etc) and root trust warning (if the bundle cannot be + trusted by some major OSes or browsers). + * rebundled indicates whether the server had to rebundle the + certificate. The server will rebundle the uploaded + certificate as needed; for example, if the certificate + contains none of the required intermediates or a better set + of intermediates was found. In this case, the server will + mark rebundled as true. + * untrusted_root_stores contains the names of any major + OSes and browsers that doesn't trust the bundle. The names + are used to construct the root trust warnings in the messages + list + * subject contains the X.509 subject identifier from the + certificate. + """ + data = self._clean_mapping({ + 'certificate': certificate, + 'domain': domain, + 'private_key': private_key, + 'flavor': flavor, + 'ip': ip, + }) + return self.call('bundle', 'POST', data=data) + + def info(self, label, profile=None): + """ It returns information about the CA, including the cert. + + Args: + label: (str) A string specifying the signer. + profile: (str) a string specifying the signing profile for the + signer. Signing profile specifies what key usages should be + used and how long the expiry should be set. + Returns: + (dict) Mapping with three keys: + * certificate: (str) a PEM-encoded certificate of the signer. + * usage: (list) a string array of key usages from the signing + profile. + * expiry: (str) the expiry string from the signing profile. + """ + data = self._clean_mapping({ + 'label': label, + 'profile': profile, + }) + return self.call('info', 'POST', data=data) + + def init_ca(self, hosts, names, common_name=None, key=None, ca=None): + """ It initializes a new certificate authority. + + Args: + hosts: (list) Of SANs (subject alternative names) for the + requested CA certificate. + names: (list) the certificate subject for the requested CA + certificate. + common_name: (str) the common name for the certificate subject in + the requested CA certificate. + key: the key algorithm and size for the newly generated private key, + default to ECDSA-256. + ca: the CA configuration of the requested CA, including CA pathlen + and CA default expiry. + Returns: + (dict) Mapping with two keys: + * private key: (str) a PEM-encoded CA private key. + * certificate: (str) a PEM-encoded self-signed CA certificate. + """ + data = self._clean_mapping({ + 'hosts': hosts, + 'names': names, + 'CN': common_name, + 'key': key, + 'ca': ca, + }) + return self.call('init_ca', 'POST', data=data) + + def new_key(self, hosts, names, common_name=None, key=None, ca=None): + """ It generates and returns a new private key + CSR. + + Args: + hosts: (list) Of SANs (subject alternative names) for the + requested CA certificate. + names: (list) the certificate subject for the requested CA + certificate. + CN: (str) the common name for the certificate subject in the + requestedrequested CA certificate. + key: the key algorithm and size for the newly generated private key, + default to ECDSA-256. + ca: the CA configuration of the requested CA, including CA pathlen + and CA default expiry. + Returns: + (dict) Mapping with three keys: + * private key: (str) a PEM-encoded CA private key. + * certificate: (str) a PEM-encoded self-signed CA certificate. + * sums: (dict) Mapping holding both MD5 and SHA1 digests for the + certificate request + """ + data = self._clean_mapping({ + 'hosts': hosts, + 'names': names, + 'CN': common_name, + 'key': key, + 'ca': ca, + }) + return self.call('newkey', 'POST', data=data) + + def new_cert(self, request, label=None, profile=None, bundle=None): + """ It generates and returns a new private key and certificate. + + Args: + request: (dict) Specifying the certificate request. + label: (str) Specifying which signer to be appointed to sign + the CSR, useful when interacting with cfssl server that stands + in front of a remote multi-root CA signer. + profile: (str) Specifying the signing profile for the signer. + bundle: (bool) Specifying whether to include an "optimal" + certificate bundle along with the certificate. + Returns: + (dict) mapping with these keys: + * private key: a PEM-encoded private key. + * certificate_request: a PEM-encoded certificate request. + * certificate: a PEM-encoded certificate, signed by the server. + * sums: a JSON object holding both MD5 and SHA1 digests for the + certificate request and the certificate. + * bundle: See the result of endpoint_bundle.txt (only included + if the bundle parameter was set). + """ + data = self._clean_mapping({ + 'request': request, + 'label': label, + 'profile': profile, + 'bundle': bundle, + }) + return self.call('newcert', 'POST', data=data) + + def revoke(self, serial, authority_key_id, reason): + """ It provides certificate revocation. + + Args: + serial: (str) Specifying the serial number of a certificate. + authority_key_id: (str) Specifying the authority key identifier + of the certificate to be revoked; this is used to distinguish + which private key was used to sign the certificate. + reason: (str) Identifying why the certificate was revoked; see, + for example, ReasonStringToCode in the ocsp package or section + 4.2.1.13 of RFC 5280. The "reasons" used here are the ReasonFlag + names in said RFC. + """ + data = self._clean_mapping({ + 'serial': serial, + 'authority_key_id': authority_key_id, + 'reason': reason, + }) + return self.call('revoke', 'POST', data=data) + + def scan(self, host, ip=None, timeout=None, family=None, scanner=None): + """ It scans servers to determine the quality of their TLS setup. + + Args: + host: the hostname (optionally including port) to scan. + ip: IP Address to override DNS lookup of host. + timeout: The amount of time allotted for the scan to complete + (default: 1 minute). + family: regular expression specifying scan famil(ies) to run. + scanner: regular expression specifying scanner(s) to run. + Returns: + (dict) Mapping with keys for each scan family. Each of these + objects contains keys for each scanner run in that family + pointing to objects possibly containing the following keys: + * grade: (str) Describing the exit status of the scan. Can be: + * "Good": host performing the expected state-of-the-art. + * "Warning": host with non-ideal configuration, + possibly maintaining support for legacy clients. + * "Bad": host with serious misconfiguration or vulnerability + * "Skipped": indicates that the scan was not performed for some + reason. + * error: (str) Any error encountered during the scan process. + * output: (dict) Arbitrary data retrieved during the scan. + """ + data = self._clean_mapping({ + 'host': host, + 'ip': ip, + 'timeout': timeout, + 'family': family, + 'scanner': scanner, + }) + return self.call('scan', params=data) + + def scan_info(self): + """ It lists options available for scanning. + + Returns: + (dict) Mapping with keys for each scan family. For each family, + there exists a `description` containing a string describing + the family and a `scanners` object mapping each of the family's + scanners to an object containing a `description` string. + """ + return self.call('scaninfo') + + def sign(self, certificate_request, hosts=None, subject=None, + serial_sequence=None, label=None, profile=None): + """ It signs and returns a certificate. + + Args: + certificate_request: (str) the CSR bytes to be signed in PEM. + hosts: (iter) of SAN (subject alternative .names) + which overrides the ones in the CSR + subject: (str) The certificate subject which overrides + the ones in the CSR. + serial_sequence: (str) Specify the prefix which the generated + certificate serial should have. + label: (str) Specifying which signer to be appointed to sign + the CSR, useful when interacting with a remote multi-root CA + signer. + profile: (str) Specifying the signing profile for the signer, + useful when interacting with a remote multi-root CA signer. + Returns: + (str) A PEM-encoded certificate that has been signed by the + server. + """ + data = self._clean_mapping({ + 'certificate_request': certificate_request, + 'hosts': hosts, + 'subject': subject, + 'serial_sequence': serial_sequence, + 'label': label, + 'profile': profile, + }) + result = self.call('sign', 'POST', data=data) + return result['certificate'] + + def call(self, endpoint, method='GET', params=None, data=None): + """ It calls the remote endpoint and returns the result, if success. + + Args: + endpoint: (str) CFSSL endpoint to call (e.g. ``newcert``). + method: (str) HTTP method to utilize for the Request. + params: (dict|bytes) Data to be sent in the query string + for the Request. + data: (dict|bytes|file) Data to send in the body of the + Request. + Returns: + (mixed) Data contained in ``result`` key of the API response. + Raises: + CFSSLRemoteException: In the event of a ``False`` in the + ``success`` key of the API response. + """ + endpoint = '%s/api/v1/cfssl/%s' % (self.uri_base, endpoint) + response = requests.request( + method=method, + url=endpoint, + params=params, + data=data, + ) + response = response.json() + if not response['success']: + raise CFSSLRemoteException( + '\n'.join([ + 'Errors:', + '\n'.join(response.get('errors', [])), + 'Messages:' + '\n'.join(response.get('messages', [])), + ]) + ) + return response['result'] + + def _clean_mapping(self, mapping): + """ It removes false entries from mapping """ + return {k:v for k, v in mapping.iteritems()} diff --git a/clouder_certificate_authority/exceptions.py b/clouder_certificate_authority/exceptions.py new file mode 100644 index 0000000..0abf456 --- /dev/null +++ b/clouder_certificate_authority/exceptions.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +import requests + + +class CFSSLException(EnvironmentError): + """ This exception is raised from errors in the CFSSL Library. """ + + +class CFSSLRemoteException(CFSSLException): + """ This exception is raised to indicate issues returned from API. """ diff --git a/clouder_certificate_authority/models/key_abstract.py b/clouder_certificate_authority/models/key_abstract.py index 717169f..b30d571 100644 --- a/clouder_certificate_authority/models/key_abstract.py +++ b/clouder_certificate_authority/models/key_abstract.py @@ -6,7 +6,7 @@ class ClouderKeyAbstract(models.Model): - """ It provides attributes and methods related to all keys """ + """ It provides attributes and methods related to all keys. """ _name = 'clouder.key.abstract' _inherit = 'clouder.certificate.abstract' From 505edff7c55d3bbb62789d09655a403f7956f4b0 Mon Sep 17 00:00:00 2001 From: Dave Lasley Date: Thu, 15 Dec 2016 23:09:25 -0800 Subject: [PATCH 6/9] Remove CFSSL library --- clouder_certificate_authority/cfssl.py | 381 -------------------- clouder_certificate_authority/exceptions.py | 13 - requirements.txt | 1 + 3 files changed, 1 insertion(+), 394 deletions(-) delete mode 100644 clouder_certificate_authority/cfssl.py delete mode 100644 clouder_certificate_authority/exceptions.py diff --git a/clouder_certificate_authority/cfssl.py b/clouder_certificate_authority/cfssl.py deleted file mode 100644 index 1ed660c..0000000 --- a/clouder_certificate_authority/cfssl.py +++ /dev/null @@ -1,381 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2016 LasLabs Inc. -# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). - -import requests - -from .exceptions import CFSSLException, CFSSLRemoteException - - -class CFSSL(object): - """ It provides Python bindings to a remote CFSSL server via HTTP(S). - - Additional documentation regarding the API endpoints is available at - https://github.com/cloudflare/cfssl/tree/master/doc/api - """ - - def __init__(self, host, port, ssl=True): - ssl = 'https' if ssl else 'http' - self.uri_base = '%s://%s:%s' % (ssl, host, port) - - def auth_sign(self, token, request, datetime=None, remote_address=None): - """ It provides returns a signed certificate. - - Args: - token: (str) The authentication token. - request: (mixed) Signing request document (e.g. as - documented in endpoint_sign.txt, but not JSON encoded). - datetime: (datetime.datetime) Authentication timestamp. - remote_address: (str) An address used in making the request. - Returns: - (str) A PEM-encoded certificate that has been signed by the - server. - """ - data = self._clean_mapping({ - 'token': token, - 'request': request, - 'datetime': datetime, - 'remote_address': remote_address, - }) - return self.call('authsign', 'POST', data=data) - - def bundle(self, certificate, private_key=None, - flavor='ubiquitous', domain=None, ip=None): - """ It builds and returns certificate bundles. - - Args: - certificate: (str) The PEM-encoded certificate to be bundled. - - If the ``certificate`` parameter is present, the following four - arguments are valid: - private_key: (str) The PEM-encoded private key to be included with - the bundle. This is valid only if the server is not running in - ``keyless`` mode. - flavor: (str) One of ``ubiquitous``, ``force``, or ``optimal``, - with a default value of ``ubiquitous``. A ubiquitous bundle is - one that has a higher probability of being verified everywhere, - even by clients using outdated or unusual trust stores. Force will - cause the endpoint to use the bundle provided in the - ``certificate`` parameter, and will only verify that the bundle - is a valid (verifiable) chain. - domain: (str) The domain name to verify as the hostname of the - certificate. - ip: (str) The IP address to verify against the certificate IP - SANs. - - If only the ``domain`` parameter is present, the following - parameter is valid: - - ip: (str) The IP address of the remote host; this will fetch the - certificate from the IP, and verify that it is valid for the - domain name. - - Returns: - (dict) Object repesenting the bundle, with the following keys: - * bundle contains the concatenated list of PEM certificates - forming the certificate chain; this forms the actual - bundle. The remaining parameters are additional metadata - supporting the bundle. - * crl_support is true if CRL information is contained in the - certificate. - * crt contains the original certificate the bundle is built - from. - * expires contains the expiration date of the certificate. - * hostnames contains the SAN hostnames for the certificate. - * issuer contains the X.509 issuer information for the - certificate. - * key contains the private key for the certificate, if one - was presented. - * key_size contains the size of the key in bits for the - certificate. It will be present even if the private key wasn't - provided because this can be determined from the public key. - * key_type contains a textual description of the key type, - e.g. '2048-bit RSA'. - * ocsp contains the OCSP URLs for the certificate, if present. - * ocsp_support will be true if the certificate supports OCSP - revocation checking. - * signature contains the signature type used in the - certificate, e.g. 'SHA1WithRSA'. - * status contains a number of elements: - * code is bit-encoded error code. 1st bit indicates whether - there is a expiring certificate in the bundle. 2nd bit indicates - whether there is a ubiquity issue with the bundle. - * expiring_SKIs contains the SKIs (subject key identifiers) - for any certificates that might expire soon (within 30 - days). - * messages is a list of human-readable warnings on bundle - ubiquity and certificate expiration. For example, an expiration - warning can be "The expiring cert is #1 in the chain", - indicating the leaf certificate is expiring. Ubiquity warnings - include SHA-1 deprecation warning (if the bundle triggers - any major browser's SHA-1 deprecation policy), SHA-2 - compatibility warning (if the bundle contains signatures using - ECDSA SHA-2 hash algorithms, it will be rejected by Windows XP - SP2), compatibility warning (if the bundle contains ECDSA - certificates, it will be rejected by Windows XP, Android 2.2 and - Android 2.3 etc) and root trust warning (if the bundle cannot be - trusted by some major OSes or browsers). - * rebundled indicates whether the server had to rebundle the - certificate. The server will rebundle the uploaded - certificate as needed; for example, if the certificate - contains none of the required intermediates or a better set - of intermediates was found. In this case, the server will - mark rebundled as true. - * untrusted_root_stores contains the names of any major - OSes and browsers that doesn't trust the bundle. The names - are used to construct the root trust warnings in the messages - list - * subject contains the X.509 subject identifier from the - certificate. - """ - data = self._clean_mapping({ - 'certificate': certificate, - 'domain': domain, - 'private_key': private_key, - 'flavor': flavor, - 'ip': ip, - }) - return self.call('bundle', 'POST', data=data) - - def info(self, label, profile=None): - """ It returns information about the CA, including the cert. - - Args: - label: (str) A string specifying the signer. - profile: (str) a string specifying the signing profile for the - signer. Signing profile specifies what key usages should be - used and how long the expiry should be set. - Returns: - (dict) Mapping with three keys: - * certificate: (str) a PEM-encoded certificate of the signer. - * usage: (list) a string array of key usages from the signing - profile. - * expiry: (str) the expiry string from the signing profile. - """ - data = self._clean_mapping({ - 'label': label, - 'profile': profile, - }) - return self.call('info', 'POST', data=data) - - def init_ca(self, hosts, names, common_name=None, key=None, ca=None): - """ It initializes a new certificate authority. - - Args: - hosts: (list) Of SANs (subject alternative names) for the - requested CA certificate. - names: (list) the certificate subject for the requested CA - certificate. - common_name: (str) the common name for the certificate subject in - the requested CA certificate. - key: the key algorithm and size for the newly generated private key, - default to ECDSA-256. - ca: the CA configuration of the requested CA, including CA pathlen - and CA default expiry. - Returns: - (dict) Mapping with two keys: - * private key: (str) a PEM-encoded CA private key. - * certificate: (str) a PEM-encoded self-signed CA certificate. - """ - data = self._clean_mapping({ - 'hosts': hosts, - 'names': names, - 'CN': common_name, - 'key': key, - 'ca': ca, - }) - return self.call('init_ca', 'POST', data=data) - - def new_key(self, hosts, names, common_name=None, key=None, ca=None): - """ It generates and returns a new private key + CSR. - - Args: - hosts: (list) Of SANs (subject alternative names) for the - requested CA certificate. - names: (list) the certificate subject for the requested CA - certificate. - CN: (str) the common name for the certificate subject in the - requestedrequested CA certificate. - key: the key algorithm and size for the newly generated private key, - default to ECDSA-256. - ca: the CA configuration of the requested CA, including CA pathlen - and CA default expiry. - Returns: - (dict) Mapping with three keys: - * private key: (str) a PEM-encoded CA private key. - * certificate: (str) a PEM-encoded self-signed CA certificate. - * sums: (dict) Mapping holding both MD5 and SHA1 digests for the - certificate request - """ - data = self._clean_mapping({ - 'hosts': hosts, - 'names': names, - 'CN': common_name, - 'key': key, - 'ca': ca, - }) - return self.call('newkey', 'POST', data=data) - - def new_cert(self, request, label=None, profile=None, bundle=None): - """ It generates and returns a new private key and certificate. - - Args: - request: (dict) Specifying the certificate request. - label: (str) Specifying which signer to be appointed to sign - the CSR, useful when interacting with cfssl server that stands - in front of a remote multi-root CA signer. - profile: (str) Specifying the signing profile for the signer. - bundle: (bool) Specifying whether to include an "optimal" - certificate bundle along with the certificate. - Returns: - (dict) mapping with these keys: - * private key: a PEM-encoded private key. - * certificate_request: a PEM-encoded certificate request. - * certificate: a PEM-encoded certificate, signed by the server. - * sums: a JSON object holding both MD5 and SHA1 digests for the - certificate request and the certificate. - * bundle: See the result of endpoint_bundle.txt (only included - if the bundle parameter was set). - """ - data = self._clean_mapping({ - 'request': request, - 'label': label, - 'profile': profile, - 'bundle': bundle, - }) - return self.call('newcert', 'POST', data=data) - - def revoke(self, serial, authority_key_id, reason): - """ It provides certificate revocation. - - Args: - serial: (str) Specifying the serial number of a certificate. - authority_key_id: (str) Specifying the authority key identifier - of the certificate to be revoked; this is used to distinguish - which private key was used to sign the certificate. - reason: (str) Identifying why the certificate was revoked; see, - for example, ReasonStringToCode in the ocsp package or section - 4.2.1.13 of RFC 5280. The "reasons" used here are the ReasonFlag - names in said RFC. - """ - data = self._clean_mapping({ - 'serial': serial, - 'authority_key_id': authority_key_id, - 'reason': reason, - }) - return self.call('revoke', 'POST', data=data) - - def scan(self, host, ip=None, timeout=None, family=None, scanner=None): - """ It scans servers to determine the quality of their TLS setup. - - Args: - host: the hostname (optionally including port) to scan. - ip: IP Address to override DNS lookup of host. - timeout: The amount of time allotted for the scan to complete - (default: 1 minute). - family: regular expression specifying scan famil(ies) to run. - scanner: regular expression specifying scanner(s) to run. - Returns: - (dict) Mapping with keys for each scan family. Each of these - objects contains keys for each scanner run in that family - pointing to objects possibly containing the following keys: - * grade: (str) Describing the exit status of the scan. Can be: - * "Good": host performing the expected state-of-the-art. - * "Warning": host with non-ideal configuration, - possibly maintaining support for legacy clients. - * "Bad": host with serious misconfiguration or vulnerability - * "Skipped": indicates that the scan was not performed for some - reason. - * error: (str) Any error encountered during the scan process. - * output: (dict) Arbitrary data retrieved during the scan. - """ - data = self._clean_mapping({ - 'host': host, - 'ip': ip, - 'timeout': timeout, - 'family': family, - 'scanner': scanner, - }) - return self.call('scan', params=data) - - def scan_info(self): - """ It lists options available for scanning. - - Returns: - (dict) Mapping with keys for each scan family. For each family, - there exists a `description` containing a string describing - the family and a `scanners` object mapping each of the family's - scanners to an object containing a `description` string. - """ - return self.call('scaninfo') - - def sign(self, certificate_request, hosts=None, subject=None, - serial_sequence=None, label=None, profile=None): - """ It signs and returns a certificate. - - Args: - certificate_request: (str) the CSR bytes to be signed in PEM. - hosts: (iter) of SAN (subject alternative .names) - which overrides the ones in the CSR - subject: (str) The certificate subject which overrides - the ones in the CSR. - serial_sequence: (str) Specify the prefix which the generated - certificate serial should have. - label: (str) Specifying which signer to be appointed to sign - the CSR, useful when interacting with a remote multi-root CA - signer. - profile: (str) Specifying the signing profile for the signer, - useful when interacting with a remote multi-root CA signer. - Returns: - (str) A PEM-encoded certificate that has been signed by the - server. - """ - data = self._clean_mapping({ - 'certificate_request': certificate_request, - 'hosts': hosts, - 'subject': subject, - 'serial_sequence': serial_sequence, - 'label': label, - 'profile': profile, - }) - result = self.call('sign', 'POST', data=data) - return result['certificate'] - - def call(self, endpoint, method='GET', params=None, data=None): - """ It calls the remote endpoint and returns the result, if success. - - Args: - endpoint: (str) CFSSL endpoint to call (e.g. ``newcert``). - method: (str) HTTP method to utilize for the Request. - params: (dict|bytes) Data to be sent in the query string - for the Request. - data: (dict|bytes|file) Data to send in the body of the - Request. - Returns: - (mixed) Data contained in ``result`` key of the API response. - Raises: - CFSSLRemoteException: In the event of a ``False`` in the - ``success`` key of the API response. - """ - endpoint = '%s/api/v1/cfssl/%s' % (self.uri_base, endpoint) - response = requests.request( - method=method, - url=endpoint, - params=params, - data=data, - ) - response = response.json() - if not response['success']: - raise CFSSLRemoteException( - '\n'.join([ - 'Errors:', - '\n'.join(response.get('errors', [])), - 'Messages:' - '\n'.join(response.get('messages', [])), - ]) - ) - return response['result'] - - def _clean_mapping(self, mapping): - """ It removes false entries from mapping """ - return {k:v for k, v in mapping.iteritems()} diff --git a/clouder_certificate_authority/exceptions.py b/clouder_certificate_authority/exceptions.py deleted file mode 100644 index 0abf456..0000000 --- a/clouder_certificate_authority/exceptions.py +++ /dev/null @@ -1,13 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2016 LasLabs Inc. -# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). - -import requests - - -class CFSSLException(EnvironmentError): - """ This exception is raised from errors in the CFSSL Library. """ - - -class CFSSLRemoteException(CFSSLException): - """ This exception is raised to indicate issues returned from API. """ diff --git a/requirements.txt b/requirements.txt index 0d68b60..c63f9f2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ apache-libcloud +cfssl erppeek paramiko From e3b40c6c93e953d1157b22e1fa339641bdac2bf8 Mon Sep 17 00:00:00 2001 From: Dave Lasley Date: Fri, 16 Dec 2016 16:22:45 -0800 Subject: [PATCH 7/9] Refactor for new CFSSL library --- clouder_certificate_authority/__manifest__.py | 1 + clouder_certificate_authority/api.py | 41 +++++ .../data/application.xml | 20 ++- .../data/application_tag.xml | 4 + .../data/application_template.xml | 6 + .../data/application_type.xml | 10 ++ clouder_certificate_authority/data/image.xml | 8 + .../data/image_template.xml | 6 + .../{1.2.0-data => cfssl-data}/Dockerfile | 0 .../pki/ca/csr_ca.json | 0 .../{1.2.0-exec => cfssl-exec}/Dockerfile | 0 .../images/openssl-exec/Dockerfile | 12 ++ .../images/openssl-exec/docker-entrypoint.sh | 15 ++ .../images/openssl-exec/parse_cert | 62 +++++++ .../models/certificate.py | 43 ----- .../models/certificate_abstract.py | 9 +- .../models/certificate_authority.py | 151 ++++++++++++++++-- .../models/certificate_host.py | 19 ++- .../models/certificate_name.py | 26 +-- .../models/certificate_policy_auth.py | 13 +- .../models/certificate_policy_sign.py | 18 +-- .../models/certificate_policy_use.py | 12 ++ .../models/certificate_request.py | 124 ++++++++++---- .../models/certificate_x509.py | 39 +++++ .../models/config_certificate_abstract.py | 36 ++--- .../models/config_certificate_client.py | 20 ++- .../models/config_certificate_server.py | 17 +- requirements.txt | 1 + 28 files changed, 565 insertions(+), 148 deletions(-) create mode 100644 clouder_certificate_authority/api.py rename clouder_certificate_authority/images/{1.2.0-data => cfssl-data}/Dockerfile (100%) rename clouder_certificate_authority/images/{1.2.0-data => cfssl-data}/pki/ca/csr_ca.json (100%) rename clouder_certificate_authority/images/{1.2.0-exec => cfssl-exec}/Dockerfile (100%) create mode 100644 clouder_certificate_authority/images/openssl-exec/Dockerfile create mode 100755 clouder_certificate_authority/images/openssl-exec/docker-entrypoint.sh create mode 100755 clouder_certificate_authority/images/openssl-exec/parse_cert delete mode 100644 clouder_certificate_authority/models/certificate.py create mode 100644 clouder_certificate_authority/models/certificate_x509.py diff --git a/clouder_certificate_authority/__manifest__.py b/clouder_certificate_authority/__manifest__.py index 88155d6..b2e4066 100644 --- a/clouder_certificate_authority/__manifest__.py +++ b/clouder_certificate_authority/__manifest__.py @@ -22,6 +22,7 @@ 'data/application_type.xml', 'data/application_template.xml', 'data/application.xml', + 'data/certificate_policy_use.xml', ], 'installable': True, 'application': False, diff --git a/clouder_certificate_authority/api.py b/clouder_certificate_authority/api.py new file mode 100644 index 0000000..4f1bda2 --- /dev/null +++ b/clouder_certificate_authority/api.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +import logging + +from contextlib import contextmanager + +_logger = logging.getLogger(__name__) + +try: + import cfssl +except ImportError: + _logger.info('CFSSL Python library is not installed.') + + +class API(object): + """ It provides a base for all Models requiring API functionality """ + + cfssl = cfssl + + @contextmanager + @api.model_cr_context + def get_api(self, certificate_authority=None): + """ It returns a :obj:`cfssl.CFSSL` for the cert authority. + + Args: + certificate_authority (:type:`clouder.CertificateAuthority`): + The certificate authority record singleton representing the + remote API. The CA does not have to be initialized yet. Use + :type:`None` if ``self`` is the CA that should be connected + to. + """ + try: + # @TODO: Figure out how the hell to get this host from the base + host = '000.000.000.000' + port = certificate_authority.port_id.local_port + api = cfssl.CFSSL(host, port, ssl=True) + yield api + finally: + pass diff --git a/clouder_certificate_authority/data/application.xml b/clouder_certificate_authority/data/application.xml index 8028889..752f3ac 100644 --- a/clouder_certificate_authority/data/application.xml +++ b/clouder_certificate_authority/data/application.xml @@ -4,6 +4,17 @@ + + OpenSSL + exec + + + + 1 + + auto + + CFSSL Data data @@ -11,7 +22,7 @@ 1 - + @@ -21,8 +32,11 @@ 2 - + auto + @@ -36,7 +50,7 @@ ]" /> 1 - + diff --git a/clouder_certificate_authority/data/application_tag.xml b/clouder_certificate_authority/data/application_tag.xml index 62e887a..0e07474 100644 --- a/clouder_certificate_authority/data/application_tag.xml +++ b/clouder_certificate_authority/data/application_tag.xml @@ -8,4 +8,8 @@ cert_authority + + openssl + + diff --git a/clouder_certificate_authority/data/application_template.xml b/clouder_certificate_authority/data/application_template.xml index 30910f4..fb6c5d9 100644 --- a/clouder_certificate_authority/data/application_template.xml +++ b/clouder_certificate_authority/data/application_template.xml @@ -10,4 +10,10 @@ CFSSL + + OpenSSL + + diff --git a/clouder_certificate_authority/data/application_type.xml b/clouder_certificate_authority/data/application_type.xml index 05a9d5a..2c82076 100644 --- a/clouder_certificate_authority/data/application_type.xml +++ b/clouder_certificate_authority/data/application_type.xml @@ -14,4 +14,14 @@ /> + + openssl + root + + + diff --git a/clouder_certificate_authority/data/image.xml b/clouder_certificate_authority/data/image.xml index ff72187..2693bb6 100644 --- a/clouder_certificate_authority/data/image.xml +++ b/clouder_certificate_authority/data/image.xml @@ -20,5 +20,13 @@ lasley/cfssl-exec data + + + image_openssl_exec + + lasley/openssl-exec + diff --git a/clouder_certificate_authority/data/image_template.xml b/clouder_certificate_authority/data/image_template.xml index 4a935e1..5c32650 100644 --- a/clouder_certificate_authority/data/image_template.xml +++ b/clouder_certificate_authority/data/image_template.xml @@ -16,4 +16,10 @@ image_template_cfssl_exec + + image_template_openssl_exec + + diff --git a/clouder_certificate_authority/images/1.2.0-data/Dockerfile b/clouder_certificate_authority/images/cfssl-data/Dockerfile similarity index 100% rename from clouder_certificate_authority/images/1.2.0-data/Dockerfile rename to clouder_certificate_authority/images/cfssl-data/Dockerfile diff --git a/clouder_certificate_authority/images/1.2.0-data/pki/ca/csr_ca.json b/clouder_certificate_authority/images/cfssl-data/pki/ca/csr_ca.json similarity index 100% rename from clouder_certificate_authority/images/1.2.0-data/pki/ca/csr_ca.json rename to clouder_certificate_authority/images/cfssl-data/pki/ca/csr_ca.json diff --git a/clouder_certificate_authority/images/1.2.0-exec/Dockerfile b/clouder_certificate_authority/images/cfssl-exec/Dockerfile similarity index 100% rename from clouder_certificate_authority/images/1.2.0-exec/Dockerfile rename to clouder_certificate_authority/images/cfssl-exec/Dockerfile diff --git a/clouder_certificate_authority/images/openssl-exec/Dockerfile b/clouder_certificate_authority/images/openssl-exec/Dockerfile new file mode 100644 index 0000000..f6f360a --- /dev/null +++ b/clouder_certificate_authority/images/openssl-exec/Dockerfile @@ -0,0 +1,12 @@ +FROM clouder/base:3.4 +MAINTAINER Dave Lasley + +RUN apk add --no-cache openssl \ + openssl-dev \ + pip + +RUN pip install cryptography + +# Enter +ENTRYPOINT ["openssl"] +CMD ["cat"] diff --git a/clouder_certificate_authority/images/openssl-exec/docker-entrypoint.sh b/clouder_certificate_authority/images/openssl-exec/docker-entrypoint.sh new file mode 100755 index 0000000..d180126 --- /dev/null +++ b/clouder_certificate_authority/images/openssl-exec/docker-entrypoint.sh @@ -0,0 +1,15 @@ +#!/bin/ash +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +set -e + +# Add openssl as command if needed +if [ "${1:0:1}" = '-' ]; then + set -- openssl "$@" +fi + +# As argument is not related to openssl, +# then assume that user wants to run their own process, +# for example a `bash` shell to explore this image +exec "$@" diff --git a/clouder_certificate_authority/images/openssl-exec/parse_cert b/clouder_certificate_authority/images/openssl-exec/parse_cert new file mode 100755 index 0000000..2cbaac1 --- /dev/null +++ b/clouder_certificate_authority/images/openssl-exec/parse_cert @@ -0,0 +1,62 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from __future__ import print_function + +import argparse +import pickle + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.serialization import Encoding +from cryptography.hazmat.primitives import hashes +from cryptography import x509 + + +def parse_cert(self, cert): + """ It parses a PEM encoded certificate and returns the attrs. """ + cert = x509.load_pem_x509_certificate( + cert, + default_backend(), + ) + enc_pem = Encoding('PEM') + extensions = {} + for extension in cert.extensions: + public_props = ( + n for n in dir(extension.value) if not n.startswith('_') + ) + extensions[extension.oid._name] = { + 'oid': extension.oid.dotted_string, + } + for prop in public_props: + if prop == 'oid': + continue + try: + value = getattr(extension.value, prop) + except ValueError: + continue + if callable(value): + continue + extensions[extension.oid._name][prop] = value + return { + 'serial': cert.serial, + 'fingerprint': cert.fingerprint(hashes.SHA256()), + 'public_key': cert.public_bytes(enc_pem), + 'not_valid_before': cert.not_valid_before, + 'not_valid_after': cert.not_valid_after, + 'extensions': extensions, + } + + +if __name__ == '__main__': + + parser = argparse.ArgumentParser(description='Cert Parse CLI') + parser.add_argument('cert', + help='PEM encoded certificate string to parse', + ) + args = parser.parse_args() + cert_info = parse_cert(args.cert) + print( + pickle.dumps(cert_info.encode('base64')), + ) diff --git a/clouder_certificate_authority/models/certificate.py b/clouder_certificate_authority/models/certificate.py deleted file mode 100644 index ed021aa..0000000 --- a/clouder_certificate_authority/models/certificate.py +++ /dev/null @@ -1,43 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2016 LasLabs Inc. -# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). - -from odoo import api, fields, models - - -class ClouderCertificate(models.Model): - """ It provides the concept of a Certificate """ - - _name = 'clouder.certificate.request' - _description = 'Clouder Certificate Request' - - authority_id = fields.Many2one( - string='Cert Authority', - comodel_name='clouder.certificate.authority', - required=True, - ondelete='cascade', - ) - common_name_id = fields.Many2one( - string='Common Name', - comodel_name='clouder.certificate.host', - required=True, - ) - subject_info_ids = fields.Many2many( - string='Names', - comodel_name='clouder.certificate.name', - required=True, - ) - computed = fields.Serialized( - compute="_compute_computed", - ) - - @api.multi - def _compute_computed(self): - """ It computes the keys required for the JSON request """ - for record in self: - record.computed = { - 'CN': record.common_name_id.name, - 'names': [ - name.computed for name in record.subject_info_ids - ], - } diff --git a/clouder_certificate_authority/models/certificate_abstract.py b/clouder_certificate_authority/models/certificate_abstract.py index e577fca..88e1382 100644 --- a/clouder_certificate_authority/models/certificate_abstract.py +++ b/clouder_certificate_authority/models/certificate_abstract.py @@ -6,13 +6,14 @@ class ClouderCertificateAbstract(models.AbstractModel): - """ It provides attributes and methods related to all cert files """ + """ It provides attributes and methods related to all cert files. """ _name = 'clouder.certificate.abstract' _description = 'Clouder Certificate Abstract' name = fields.Char( required=True, + help='SHA-1 Sum of Certificate', ) description = fields.Char() mime_sub_type = fields.Selection( @@ -23,7 +24,7 @@ class ClouderCertificateAbstract(models.AbstractModel): readonly=True, computed='_compute_mime_type', ) - attachment_id = fields.Many2one( + attachment_id = fields.Many2one( string='Key', comodel_name='ir.attachment', context="""{ @@ -39,6 +40,10 @@ class ClouderCertificateAbstract(models.AbstractModel): data = fields.Text( related='attachment_id.datas', ) + request_id = fields.Many2one( + string='CSR', + comodel_name='clouder.certificate.request', + ) @api.model def _get_mime_sub_types(self): diff --git a/clouder_certificate_authority/models/certificate_authority.py b/clouder_certificate_authority/models/certificate_authority.py index 33bf488..d00bae7 100644 --- a/clouder_certificate_authority/models/certificate_authority.py +++ b/clouder_certificate_authority/models/certificate_authority.py @@ -2,20 +2,23 @@ # Copyright 2016 LasLabs Inc. # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). -import json +import pickle.loads from odoo import api, fields, models +from odoo.exceptions import UserError +from ..api import API -class ClouderCertificateAuthority(models.Model): + +class ClouderCertificateAuthority(models.Model, API): """ It provides an interface for controlling a Cert Authority. """ _name = 'clouder.certificate.authority' _description = 'Clouder Certificate Authority' _inherits = {'clouder.application': 'application_id'} - application_id = fields.Many2one( - string='Application', + base_id = fields.Many2one( + string='Certificate Authority', comodel_name='clouder.application', required=True, ondelete='cascade', @@ -23,20 +26,33 @@ class ClouderCertificateAuthority(models.Model): 'clouder_certificate_authority.tag_cert_authority', ), ) - private_key_id = fields.Many2one( + api_port_id = fields.Many2one( + string='API Port', + comodel_name='clouder.image.port', + compute='_compute_api_port_id', + ) + host_id = fields.Many2one( + string='Host', + comodel_name='clouder.certificate.host', + ) + private_key_ids = fields.One2many( string='Private Key', comodel_name='clouder.key.private', - domain="[('is_private', '=', True)]", - context="{'default_is_private': True}", + related='certificate_request_id.private_key_ids', ) - certificate_id = fields.Many2one( + certificate_ids = fields.One2many( string='Certificate', comodel_name='clouder.key.public', - related='certificate_request_id.public_key_id', + related='certificate_request_id.public_key_ids', ) certificate_request_id = fields.Many2one( string='CSR', comodel_name='clouder.certificate.request', + context="""{'default_host_ids': [(4, host_id)], + 'default_authority_id': id, + 'default_public_key_id': certificate_id, + }""", + help='Certificate Signing Request.', ) exec_service_id = fields.Many2one( string='Executor Service', @@ -46,9 +62,31 @@ class ClouderCertificateAuthority(models.Model): config_id = fields.Many2one( string='Configuration', comodel_name='clouder.config.certificate.server', + help='Certificate Authority Configuration.', + ) + is_initialized = fields.Boolean( + string='Initialized', + help='Has this CA server been initialized yet?', + ) + openssl_service_id = fields.Many2one( + string='OpenSSL Service', + comodel_name='clouder.service', + compute='_compute_openssl_service_id', ) @api.multi + @api.depends('exec_service_id') + def _compute_api_port_id(self): + for record in self: + ports = record.exec_service_id.port_ids.filtered( + lambda r: r.name == 'https' + ) + record.api_port_id = ports[0] + + @api.multi + @api.depends('sign_policy_default_id', + 'sign_policy_profile_ids', + ) def _compute_auth_policy_ids(self): for record in self: policies = sum((record.sign_policy_default_id, @@ -57,9 +95,104 @@ def _compute_auth_policy_ids(self): record.auth_policy_ids = policies.mapped('auth_policy_id') @api.multi + @api.depends('service_ids', 'service_ids.code') def _compute_exec_service_id(self): for record in self: service_ids = record.service_ids.filtered( lambda s: s.code == 'exec', ) record.exec_service_id = service_ids[0] + + @api.multi + @api.depends('exec_service_id') + def _compute_openssl_service_id(self): + for record in self: + execs = record.exec_service_id.child_ids.filtered( + lambda r: r.code == 'exec' + ) + record.openssl_service_id = execs[0] + + @api.multi + def init_ca(self): + """ It initializes a new certificate authority if needed. """ + for record in self.fitered(lambda r: not r.is_initialized): + with record.get_api() as api: + csr = record.certificate_request_id + response = api.init_ca( + certificate_request=csr.to_api(), + ca=record.config_id.to_api(), + ) + certificate = record.certificate_request_id._new_cert( + response['certificate'], + ) + private_key = record.certificate_request_id._new_key( + response['private_key'], + public=False, + ) + record.write({ + 'is_initialized': True, + }) + + @api.multi + def scan(self, host, ip): + """ It scans servers to determine the quality of their TLS setup. """ + self.ensure_one() + with self.get_api() as api: + results = api.scan(host, ip) + if results['error']: + raise UserError(results['error']) + return { + 'grade': results['grade'], + 'output': results['output'], + } + + @api.multi + def sign(self, certificate_request): + """ It signs a certificate request. """ + self.ensure_one() + with self.get_api() as api: + results = api.sign( + certifcate_request=certificate_request.to_api(), + profile=self.config_id.to_api(), + ) + return certificate_request._new_cert(results) + + @api.multi + def revoke(self, certificate): + """ It revokes a certificate. """ + self.ensure_one() + with self.get_api() as api: + api.revoke(certificate.name, certificate.authority_key) + + @api.model + def _get_cert_info(self, cert): + """ It returns information about a signed certificate. + + Args: + cert (str): PEM encoded certificate. + Returns: + dict: A dictionary with the following keys: + * extensions (:class:`dict`): X.509 extensions. Some valid + keys are: + * authorityInfoAccess + * authorityKeyIdentifier + * basicConstraints + * cRLDistributionPoints + * certificatePolicies + * extendedKeyUsage + * issuerAltName + * keyUsage + * subjectAltName + * subjectKeyIdentifier + * fingerprint (:class:`str`): Certificate fingerprint. + * signature (:class:`str`): Certificate signature. + * not_valid_before (:class:`datetime`): Validity start + date for the certificate. + * not_valid_after (:class:`datetime`): Validity end date + for the certificate. + * public_key (:class:`str`): Public key associated with + the certificate. + """ + self.ensure_one() + res = self.openssl_service_id.execute(['parse_cert', cert]) + return pickle.loads(res.decode('base64')) diff --git a/clouder_certificate_authority/models/certificate_host.py b/clouder_certificate_authority/models/certificate_host.py index a9d30c9..d65815d 100644 --- a/clouder_certificate_authority/models/certificate_host.py +++ b/clouder_certificate_authority/models/certificate_host.py @@ -2,10 +2,12 @@ # Copyright 2016 LasLabs Inc. # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). -from odoo import fields, models +from odoo import api, fields, models +from ..api import API -class ClouderCertificateHost(models.Model): + +class ClouderCertificateHost(models.Model, API): """ It provides the concept of a cert's CommonName """ _name = 'clouder.certificate.host' @@ -18,3 +20,16 @@ class ClouderCertificateHost(models.Model): required=True, ) port = fields.Integer() + api_object = fields.Binary( + compute="_compute_api_object", + ) + + @api.multi + def _compute_api_object(self): + """ It computes the keys required for the JSON request """ + for record in self: + record.api_object = self.cfssl.Host( + name=record.name, + host=record.host, + port=record.port, + ) diff --git a/clouder_certificate_authority/models/certificate_name.py b/clouder_certificate_authority/models/certificate_name.py index b29e435..dd219c6 100644 --- a/clouder_certificate_authority/models/certificate_name.py +++ b/clouder_certificate_authority/models/certificate_name.py @@ -4,8 +4,10 @@ from odoo import api, fields, models +from ..api import API -class ClouderCertificateName(models.Model): + +class ClouderCertificateName(models.Model, API): """ It provides the concept of a cert's SubjectInfo """ _name = 'clouder.certificate.name' @@ -27,7 +29,7 @@ class ClouderCertificateName(models.Model): default=lambda s: s.env.user.city, ) company_id = fields.Many2one( - string='Company', + string='Organization', model='res.company', required=True, domain='[(company_id, "=", company_id)]', @@ -36,18 +38,18 @@ class ClouderCertificateName(models.Model): organization_unit = fields.Char( required=True, ) - computed = fields.Serialized( - compute="_compute_computed", + api_object = fields.Binary( + compute="_compute_api_object", ) @api.multi - def _compute_computed(self): + def _compute_api_object(self): """ It computes the keys required for the JSON request """ for record in self: - record.computed = { - 'C': record.country_id.code, - 'ST': record.state_id.name, - 'L': record.city, - 'O': record.company_id.name, - 'OU': record.organization_unit, - } + record.api_object = self.cfssl.SubjectInfo( + record.company_id.name, + record.organizational_unit, + record.city, + record.state_id.name, + record.country_id.code, + ) diff --git a/clouder_certificate_authority/models/certificate_policy_auth.py b/clouder_certificate_authority/models/certificate_policy_auth.py index 16b3997..aa443fd 100644 --- a/clouder_certificate_authority/models/certificate_policy_auth.py +++ b/clouder_certificate_authority/models/certificate_policy_auth.py @@ -26,8 +26,8 @@ class ClouderCertificatePolicyAuth(models.Model): default='standard', required=True, ) - computed = fields.Serialized( - compute="_compute_computed", + api_object = fields.Binary( + compute="_compute_api_object", ) _sql_constraints = [ @@ -41,10 +41,11 @@ def _default_key(self): return passwd.encode('hex')[:16] @api.multi - def _compute_computed(self): + def _compute_api_object(self): """ It computes the keys required for the JSON request """ for record in self: - record.computed = { + record.api_object = self.cfssl.PolicyAuth({ + 'name': record.name, 'key': record.key, - 'type': record.key_type, - } + 'key_type': record.key_type, + }) diff --git a/clouder_certificate_authority/models/certificate_policy_sign.py b/clouder_certificate_authority/models/certificate_policy_sign.py index dd99a5a..ad23330 100644 --- a/clouder_certificate_authority/models/certificate_policy_sign.py +++ b/clouder_certificate_authority/models/certificate_policy_sign.py @@ -28,18 +28,16 @@ class ClouderCertificatePolicySign(models.Model): required=True, default=(365 * 24), ) - computed = fields.Serialized( - compute="_compute_computed", + api_object = fields.Binary( + compute="_compute_api_object", ) @api.multi - def _compute_computed(self): + def _compute_api_object(self): """ It computes the keys required for the JSON request """ for record in self: - record.computed = { - 'auth_key': record.auth_policy_id.name, - 'expiry': '%sh' % expire_hours, - 'usages': [ - usage.code for usage in record.usage_ids - ], - } + record.api_object = self.cfssl.PolicySign({ + 'name': record.name, + 'usage_policies': record.usage_ids.mapped('api_object'), + 'auth_policy': record.auth_policy_id.api_object, + }) diff --git a/clouder_certificate_authority/models/certificate_policy_use.py b/clouder_certificate_authority/models/certificate_policy_use.py index 72cb2a6..489e098 100644 --- a/clouder_certificate_authority/models/certificate_policy_use.py +++ b/clouder_certificate_authority/models/certificate_policy_use.py @@ -22,8 +22,20 @@ class ClouderCertificatePolicyUse(models.Model): required=True, default=5, ) + api_object = fields.Binary( + compute="_compute_api_object", + ) _sql_constraints = [ ('code_uniq', 'UNIQUE(code)', 'Code must be unique.'), ('name_uniq', 'UNIQUE(name)', 'Name must be unique.'), ] + + @api.multi + def _compute_api_object(self): + """ It computes the keys required for the JSON request """ + for record in self: + record.api_object = self.cfssl.PolicyUse( + name=record.name, + code=record.code, + ) diff --git a/clouder_certificate_authority/models/certificate_request.py b/clouder_certificate_authority/models/certificate_request.py index 64d8f57..0fdd845 100644 --- a/clouder_certificate_authority/models/certificate_request.py +++ b/clouder_certificate_authority/models/certificate_request.py @@ -2,16 +2,20 @@ # Copyright 2016 LasLabs Inc. # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). -import json +from hashlib import sha1 from odoo import api, fields, models +from ..api import API -class ClouderCertificateRequest(models.Model): + +class ClouderCertificateRequest(models.Model, API): """ It provides the concept of a Certificate Request """ _name = 'clouder.certificate.request' - _inherit = 'clouder.certificate.abstract' + _inherit = ['clouder.certificate.abstract', + 'clouder.key.abstract', + ] _description = 'Clouder Certificate Request' name = fields.Char( @@ -33,42 +37,102 @@ class ClouderCertificateRequest(models.Model): comodel_name='clouder.certificate.name', required=True, ) - public_key_id = fields.Many2one( + public_key_ids = fields.One2many( string='Public Key', comodel_name='clouder.key.public', + inverse_name='request_id', ) - strength = fields.Integer( - default=4096, - required=True, + private_key_ids = fields.One2many( + string='Private Key', + comodel_name='clouder.key.private', + inverse_name='request_id', ) - algorithm = fields.Selection( - default='rsa', - selection=lambda s: s.env['clouder.key.abstract']._get_algorithms(), + certificate_ids = fields.One2many( + string='Signed Certificate', + comodel_name='clouder.certificate', + inverse_name='request_id', ) - computed = fields.Serialized( - compute="_compute_computed", + api_object = fields.Binary( + compute="_compute_api_object", ) @api.multi - def _compute_computed(self): - """ It computes the keys required for the JSON request """ + def create_cert(self): for record in self: - record.computed = { - 'CN': record.name, - 'names': [ - name.computed for name in record.subject_info_ids - ], - 'hosts': [ - '%s:%s' % (h.host, h.port) for h in record.host_ids - ], - 'key': { - 'algo': record.algorithm, - 'size': record.strength, - }, - } + with record.get_api(record.authority_id) as api: + response = api.new_cert( + request=record.to_api(), + ) + certificate = record._new_cert( + response['certificate'], + ) + private_key = record._new_key( + response['private_key'], + public=True, + ) + attachment = record.attachment_id.create({ + 'datas': response['certificate_request'], + }) + record.write({ + 'attachment_id': attachment.id, + }) + + @api.multi + def _new_key(self, key_data, public=False, mime='x-pem-file'): + self.ensure_one() + model = 'public' if public else 'private' + key = self.env['clouder.key.%s' % model].create({ + 'name': sha1(key_data).hexdigest(), + 'strength': self.strength, + 'algorithm': self.algorithm, + 'mime_sub_type': mime, + 'request_id': self.id, + }) + attachment = key.attachment_id.create({ + 'datas': key_data, + }) + key.attachment_id = attachment.id + return key @api.multi - def to_json(self): - """ It returns the JSON representation of this object """ + def _new_cert(self, cert_data): self.ensure_one() - return json.dumps(self.computed) + cert_info = self.authority_id._get_cert_info(cert_data) + public_key = self._new_key( + cert['public_key'], + public=True, + ) + auth_key = cert_info['authorityKeyIdentifier']['key_identifier'] + usages = self.env['clouder.policy.use'] + for usage_str in cert_info['keyUsage']: + usage = usages.search([ + ('code', '=', usage_str.replace(' ', '_')), + ]) + if not usage: + continue + usages += usage + certificate = self.env['clouder.certificate.x509'].create({ + 'subject_key': cert_info['subjectKeyIdentifier']['digest'], + 'authority_key': auth_key, + 'public_key_id': public_key.id, + 'request_id': self.id, + 'usage_ids': usages.ids, + }) + attachment = key.attachment_id.create({ + 'datas': cert_data, + }) + certificate.attachment_id = attachment.id + + @api.multi + def _compute_api_object(self): + """ It computes the keys required for the JSON request """ + for record in self: + record.api_object = self.cfssl.CertificateRequest( + common_name=record.name, + names=record.subject_info_ids.mapped('api_object'), + hosts=record.host_ids.mapped('api_object'), + key=self.cfssl.ConfigKey( + algorithm=record.algorithm, + strength=record.strength, + ), + ) diff --git a/clouder_certificate_authority/models/certificate_x509.py b/clouder_certificate_authority/models/certificate_x509.py new file mode 100644 index 0000000..b792b23 --- /dev/null +++ b/clouder_certificate_authority/models/certificate_x509.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import api, fields, models + +from ..api import API + + +class ClouderCertificateX509(models.Model, API): + """ It provides the concept of a signed certificate. """ + + _name = 'clouder.certificate.x509' + _inherit = 'clouder.certificate.abstract' + _description = 'Clouder Certificate X.509' + + subject_key = fields.Char( + required=True, + help='X509v3 Subject Key Identifier.', + ) + authority_key = fields.Char( + required=True, + help='X509v3 Authority Key Identifier.', + ) + public_key_id = fields.Many2one( + string='Public Key', + comodel_name='clouder.key.public', + required=True, + context="{'default_request_id': request_id}", + ) + request_id = fields.Many2one( + string='CSR', + comodel_name='clouder.certificate.request', + required=True, + ) + use_policy_ids = fields.Many2many( + string='Use Policies', + comodel_name='clouder.certificate.policy.use', + ) diff --git a/clouder_certificate_authority/models/config_certificate_abstract.py b/clouder_certificate_authority/models/config_certificate_abstract.py index d875701..b75c478 100644 --- a/clouder_certificate_authority/models/config_certificate_abstract.py +++ b/clouder_certificate_authority/models/config_certificate_abstract.py @@ -4,8 +4,10 @@ from odoo import api, fields, models +from ..api import API -class ClouderConfigCertificateAbstract(models.AbstractModel): + +class ClouderConfigCertificateAbstract(models.AbstractModel, API): """ It provides data handling for certificate client + server configs. """ _name = 'clouder.config.certificate.abstract' @@ -25,30 +27,18 @@ class ClouderConfigCertificateAbstract(models.AbstractModel): comodel_name='clouder.certificate.policy.auth', compute='_compute_auth_policy_ids', ) - computed = fields.Serialized( - compute="_compute_computed", + api_object = fields.Binary( + compute="_compute_api_object", ) @api.multi - def _compute_computed(self): + def _compute_api_object(self): """ It computes the keys required for the JSON request. """ for record in self: - profiles = { - p.name: p.computed for p in record.sign_policy_profile_ids - } - auth_keys = { - auth.name: auth.computed for auth in record.auth_policy_ids - } - record.computed_config = { - 'signing': { - 'default': record.sign_policy_default_id.computed, - 'profiles': profiles, - }, - 'auth_keys': auth_keys, - } - - @api.multi - def to_json(self): - """ It returns the JSON representation of this object """ - self.ensure_one() - return json.dumps(self.computed) + profiles = record.sign_policy_profile_ids.mapped('api_object') + auth_keys = record.auth_policy_ids.mapped('api_object') + record.api_object = self.cfssl.ConfigMixer( + sign_policy=record.sign_policy_default_id.api_object, + sign_policies=profiles, + auth_policies=auth_keys, + ) diff --git a/clouder_certificate_authority/models/config_certificate_client.py b/clouder_certificate_authority/models/config_certificate_client.py index 8cf503c..4b63511 100644 --- a/clouder_certificate_authority/models/config_certificate_client.py +++ b/clouder_certificate_authority/models/config_certificate_client.py @@ -2,10 +2,12 @@ # Copyright 2016 LasLabs Inc. # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). -from odoo import models +from odoo import api, fields, models +from ..api import API -class ClouderConfigCertificateClient(models.Model): + +class ClouderConfigCertificateClient(models.Model, API): """ It provides data handling for certificate client configs. """ _name = 'clouder.config.certificate.client' @@ -19,10 +21,16 @@ class ClouderConfigCertificateClient(models.Model): ) @api.multi - def _compute_computed(self): + def _compute_api_object(self): """ It computes the keys required for the JSON request. """ for record in self: - super(ClouderConfigCertificateClient, record)._compute_computed() - record.computed['remotes'] = { - r.name: '%s:%s' % (r.host, r.port) for r in record.remote_ids + super(ClouderConfigCertificateClient, record)._compute_api_object() + remotes = { + remote.name: remote.api_object for remote in record.remote_ids } + record.api_object = self.cfssl.ConfigClient( + sign_policy_default=record.api_object.sign_policy, + sign_policies_add=record.api_object.sign_policies, + auth_policies=record.api_object.auth_policies, + remotes=remotes, + ) diff --git a/clouder_certificate_authority/models/config_certificate_server.py b/clouder_certificate_authority/models/config_certificate_server.py index 330c8e1..7c835a1 100644 --- a/clouder_certificate_authority/models/config_certificate_server.py +++ b/clouder_certificate_authority/models/config_certificate_server.py @@ -2,12 +2,25 @@ # Copyright 2016 LasLabs Inc. # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). -from odoo import models +from odoo import api, models +from ..api import API -class ClouderConfigCertificateServer(models.Model): + +class ClouderConfigCertificateServer(models.Model, API): """ It provides data handling for certificate server configs. """ _name = 'clouder.config.certificate.server' _inherit = 'clouder.config.certificate.abstract' _description = 'Clouder Config Certificate Server' + + @api.multi + def _compute_api_object(self): + """ It computes the keys required for the JSON request. """ + for record in self: + super(ClouderConfigCertificateServer, record)._compute_api_object() + record.api_object = self.cfssl.ConfigServer( + sign_policy_default=record.api_object.sign_policy, + sign_policies_add=record.api_object.sign_policies, + auth_policies=record.api_object.auth_policies, + ) diff --git a/requirements.txt b/requirements.txt index c63f9f2..d8f249a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ apache-libcloud cfssl +cryptography erppeek paramiko From ea52b59d5df2392d921711dbe5575c33840aab71 Mon Sep 17 00:00:00 2001 From: Dave Lasley Date: Mon, 2 Jan 2017 10:28:37 -0800 Subject: [PATCH 8/9] PR fixes --- clouder_certificate_authority/data/image.xml | 2 +- clouder_certificate_authority/data/image_port.xml | 4 ++-- .../images/cfssl-data/Dockerfile | 4 ---- .../images/cfssl-data/pki/ca/csr_ca.json | 4 ---- .../images/cfssl-exec/Dockerfile | 13 ++++++++++--- .../images/openssl-exec/Dockerfile | 4 ---- .../models/certificate_authority.py | 12 +++--------- 7 files changed, 16 insertions(+), 27 deletions(-) delete mode 100644 clouder_certificate_authority/images/cfssl-data/Dockerfile delete mode 100644 clouder_certificate_authority/images/cfssl-data/pki/ca/csr_ca.json diff --git a/clouder_certificate_authority/data/image.xml b/clouder_certificate_authority/data/image.xml index 2693bb6..6f59217 100644 --- a/clouder_certificate_authority/data/image.xml +++ b/clouder_certificate_authority/data/image.xml @@ -9,7 +9,7 @@ - lasley/cfssl-data + clouder/base:3.4 diff --git a/clouder_certificate_authority/data/image_port.xml b/clouder_certificate_authority/data/image_port.xml index f11514d..0a90d3a 100644 --- a/clouder_certificate_authority/data/image_port.xml +++ b/clouder_certificate_authority/data/image_port.xml @@ -8,8 +8,8 @@ model="clouder.image.port" > - cfssl-http - 8888 + http + 8080 diff --git a/clouder_certificate_authority/images/cfssl-data/Dockerfile b/clouder_certificate_authority/images/cfssl-data/Dockerfile deleted file mode 100644 index d9d246c..0000000 --- a/clouder_certificate_authority/images/cfssl-data/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM clouder/base:3.4 -MAINTAINER Dave Lasley - -CMD tail -f /dev/null diff --git a/clouder_certificate_authority/images/cfssl-data/pki/ca/csr_ca.json b/clouder_certificate_authority/images/cfssl-data/pki/ca/csr_ca.json deleted file mode 100644 index d9d246c..0000000 --- a/clouder_certificate_authority/images/cfssl-data/pki/ca/csr_ca.json +++ /dev/null @@ -1,4 +0,0 @@ -FROM clouder/base:3.4 -MAINTAINER Dave Lasley - -CMD tail -f /dev/null diff --git a/clouder_certificate_authority/images/cfssl-exec/Dockerfile b/clouder_certificate_authority/images/cfssl-exec/Dockerfile index 0255c9a..630c59c 100644 --- a/clouder_certificate_authority/images/cfssl-exec/Dockerfile +++ b/clouder_certificate_authority/images/cfssl-exec/Dockerfile @@ -33,6 +33,13 @@ RUN set -x \ RUN mkdir -p /var/pki WORKDIR /var/pki -# Enter -ENTRYPOINT ["cfssl"] -CMD ["cat"] +# Setup Environment +ENV CFSSL_DATA=/var/pki \ + CFSSL_CERT=$CFSSL_DATA/ca.pem \ + CFSSL_KEY=$CFSSL_DATA/ca_key.pem + +CMD ["cfssl", \ + "serve", \ + "-address=:8080", \ + "-ca $CFFSL_CERT", \ + "-ca-key $CFSSL_KEY"] diff --git a/clouder_certificate_authority/images/openssl-exec/Dockerfile b/clouder_certificate_authority/images/openssl-exec/Dockerfile index f6f360a..23ae4a5 100644 --- a/clouder_certificate_authority/images/openssl-exec/Dockerfile +++ b/clouder_certificate_authority/images/openssl-exec/Dockerfile @@ -6,7 +6,3 @@ RUN apk add --no-cache openssl \ pip RUN pip install cryptography - -# Enter -ENTRYPOINT ["openssl"] -CMD ["cat"] diff --git a/clouder_certificate_authority/models/certificate_authority.py b/clouder_certificate_authority/models/certificate_authority.py index d00bae7..63ca6a6 100644 --- a/clouder_certificate_authority/models/certificate_authority.py +++ b/clouder_certificate_authority/models/certificate_authority.py @@ -9,17 +9,16 @@ from ..api import API - class ClouderCertificateAuthority(models.Model, API): """ It provides an interface for controlling a Cert Authority. """ _name = 'clouder.certificate.authority' _description = 'Clouder Certificate Authority' - _inherits = {'clouder.application': 'application_id'} + _inherits = {'clouder.service': 'service_id'} - base_id = fields.Many2one( + service_id = fields.Many2one( string='Certificate Authority', - comodel_name='clouder.application', + comodel_name='clouder.service', required=True, ondelete='cascade', domain=lambda s: "[('tag_ids', '=', %d)]" % s.enf.ref( @@ -54,11 +53,6 @@ class ClouderCertificateAuthority(models.Model, API): }""", help='Certificate Signing Request.', ) - exec_service_id = fields.Many2one( - string='Executor Service', - comodel_name='clouder.service', - compute='_compute_exec_service_id', - ) config_id = fields.Many2one( string='Configuration', comodel_name='clouder.config.certificate.server', From 4bbd304e0d5faf3f277cc691a8b7e8813693e2c7 Mon Sep 17 00:00:00 2001 From: Dave Lasley Date: Mon, 2 Jan 2017 23:41:02 -0800 Subject: [PATCH 9/9] Add CA views & wizards --- .../README.rst | 10 +- .../__init__.py | 3 + .../__manifest__.py | 9 +- .../api.py | 10 +- .../data/application.xml | 8 +- .../data/application_tag.xml | 4 - .../data/application_template.xml | 0 .../data/application_type.xml | 3 - .../data/cert_policy_use.xml | 90 +++---- .../data/image.xml | 4 +- .../data/image_port.xml | 0 .../data/image_template.xml | 0 .../data/image_volume.xml | 0 clouder_cert_authority/models/__init__.py | 32 +++ .../models/cert_abstract.py | 12 +- .../models/cert_authority.py | 222 ++++++++++++++++++ .../models/cert_host.py | 6 +- .../models/cert_name.py | 12 +- .../models/cert_policy_auth.py | 6 +- .../models/cert_policy_sign.py | 10 +- .../models/cert_policy_use.py | 6 +- .../models/cert_request.py | 34 +-- .../models/cert_x509.py | 14 +- .../models/config_cert_abstract.py | 22 +- .../models/config_cert_client.py | 14 +- .../models/config_cert_server.py | 12 +- clouder_cert_authority/models/key_abstract.py | 68 ++++++ .../models/key_private.py | 0 .../models/key_public.py | 0 .../views/cert_authority.xml | 103 ++++++++ clouder_cert_authority/views/menu.xml | 13 + .../wizards}/__init__.py | 4 + .../wizards/cert_authority_revoke.py | 32 +++ .../wizards/cert_authority_revoke.xml | 42 ++++ .../wizards/cert_authority_scan.py | 55 +++++ .../wizards/cert_authority_scan.xml | 52 ++++ .../wizards/cert_authority_sign.py | 52 ++++ .../wizards/cert_authority_sign.xml | 53 +++++ .../images/cfssl-exec/Dockerfile | 45 ---- .../images/openssl-exec/Dockerfile | 8 - .../images/openssl-exec/docker-entrypoint.sh | 15 -- .../images/openssl-exec/parse_cert | 62 ----- .../models/certificate_authority.py | 192 --------------- .../models/key_abstract.py | 32 --- requirements.txt | 2 +- 45 files changed, 878 insertions(+), 495 deletions(-) rename {clouder_certificate_authority => clouder_cert_authority}/README.rst (81%) rename {clouder_certificate_authority => clouder_cert_authority}/__init__.py (73%) rename {clouder_certificate_authority => clouder_cert_authority}/__manifest__.py (73%) rename {clouder_certificate_authority => clouder_cert_authority}/api.py (78%) rename {clouder_certificate_authority => clouder_cert_authority}/data/application.xml (93%) rename {clouder_certificate_authority => clouder_cert_authority}/data/application_tag.xml (71%) rename {clouder_certificate_authority => clouder_cert_authority}/data/application_template.xml (100%) rename {clouder_certificate_authority => clouder_cert_authority}/data/application_type.xml (87%) rename clouder_certificate_authority/data/certificate_policy_use.xml => clouder_cert_authority/data/cert_policy_use.xml (57%) rename {clouder_certificate_authority => clouder_cert_authority}/data/image.xml (86%) rename {clouder_certificate_authority => clouder_cert_authority}/data/image_port.xml (100%) rename {clouder_certificate_authority => clouder_cert_authority}/data/image_template.xml (100%) rename {clouder_certificate_authority => clouder_cert_authority}/data/image_volume.xml (100%) create mode 100644 clouder_cert_authority/models/__init__.py rename clouder_certificate_authority/models/certificate_abstract.py => clouder_cert_authority/models/cert_abstract.py (85%) create mode 100644 clouder_cert_authority/models/cert_authority.py rename clouder_certificate_authority/models/certificate_host.py => clouder_cert_authority/models/cert_host.py (85%) rename clouder_certificate_authority/models/certificate_name.py => clouder_cert_authority/models/cert_name.py (85%) rename clouder_certificate_authority/models/certificate_policy_auth.py => clouder_cert_authority/models/cert_policy_auth.py (89%) rename clouder_certificate_authority/models/certificate_policy_sign.py => clouder_cert_authority/models/cert_policy_sign.py (79%) rename clouder_certificate_authority/models/certificate_policy_use.py => clouder_cert_authority/models/cert_policy_use.py (86%) rename clouder_certificate_authority/models/certificate_request.py => clouder_cert_authority/models/cert_request.py (80%) rename clouder_certificate_authority/models/certificate_x509.py => clouder_cert_authority/models/cert_x509.py (68%) rename clouder_certificate_authority/models/config_certificate_abstract.py => clouder_cert_authority/models/config_cert_abstract.py (56%) rename clouder_certificate_authority/models/config_certificate_client.py => clouder_cert_authority/models/config_cert_client.py (67%) rename clouder_certificate_authority/models/config_certificate_server.py => clouder_cert_authority/models/config_cert_server.py (62%) create mode 100644 clouder_cert_authority/models/key_abstract.py rename {clouder_certificate_authority => clouder_cert_authority}/models/key_private.py (100%) rename {clouder_certificate_authority => clouder_cert_authority}/models/key_public.py (100%) create mode 100644 clouder_cert_authority/views/cert_authority.xml create mode 100644 clouder_cert_authority/views/menu.xml rename {clouder_certificate_authority/models => clouder_cert_authority/wizards}/__init__.py (53%) create mode 100644 clouder_cert_authority/wizards/cert_authority_revoke.py create mode 100644 clouder_cert_authority/wizards/cert_authority_revoke.xml create mode 100644 clouder_cert_authority/wizards/cert_authority_scan.py create mode 100644 clouder_cert_authority/wizards/cert_authority_scan.xml create mode 100644 clouder_cert_authority/wizards/cert_authority_sign.py create mode 100644 clouder_cert_authority/wizards/cert_authority_sign.xml delete mode 100644 clouder_certificate_authority/images/cfssl-exec/Dockerfile delete mode 100644 clouder_certificate_authority/images/openssl-exec/Dockerfile delete mode 100755 clouder_certificate_authority/images/openssl-exec/docker-entrypoint.sh delete mode 100755 clouder_certificate_authority/images/openssl-exec/parse_cert delete mode 100644 clouder_certificate_authority/models/certificate_authority.py delete mode 100644 clouder_certificate_authority/models/key_abstract.py diff --git a/clouder_certificate_authority/README.rst b/clouder_cert_authority/README.rst similarity index 81% rename from clouder_certificate_authority/README.rst rename to clouder_cert_authority/README.rst index 4456ad7..09e4376 100644 --- a/clouder_certificate_authority/README.rst +++ b/clouder_cert_authority/README.rst @@ -2,11 +2,11 @@ :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html :alt: License: LGPL-3 -============================ -Clouder Certificte Authority -============================ +============================= +Clouder Cert Authority +============================= -This module provides a Certificate Authority using Clouder and CFSSL, +This module provides a Cert Authority using Clouder and CFSSL, Configuration @@ -25,6 +25,8 @@ Known issues / Roadmap ====================== * Add more Signature Profile options - https://github.com/cloudflare/cfssl/blob/86ecfbe5750ebf05565e4c80104d0a7919792fee/doc/cmd/cfssl.txt#L113 +* Need to add a hook so that services dependent on revoked certs are refreshed +* Don't run as root Bug Tracker =========== diff --git a/clouder_certificate_authority/__init__.py b/clouder_cert_authority/__init__.py similarity index 73% rename from clouder_certificate_authority/__init__.py rename to clouder_cert_authority/__init__.py index 08d9d6b..ebef902 100644 --- a/clouder_certificate_authority/__init__.py +++ b/clouder_cert_authority/__init__.py @@ -1,3 +1,6 @@ # -*- coding: utf-8 -*- # Copyright 2016 LasLabs Inc. # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from . import models +from . import wizards diff --git a/clouder_certificate_authority/__manifest__.py b/clouder_cert_authority/__manifest__.py similarity index 73% rename from clouder_certificate_authority/__manifest__.py rename to clouder_cert_authority/__manifest__.py index b2e4066..973b9d7 100644 --- a/clouder_certificate_authority/__manifest__.py +++ b/clouder_cert_authority/__manifest__.py @@ -3,7 +3,7 @@ # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). { - 'name': 'Clouder Certificate Authority', + 'name': 'Clouder Cert Authority', 'version': '10.0.10.0.0', 'category': 'Clouder', 'depends': [ @@ -22,7 +22,12 @@ 'data/application_type.xml', 'data/application_template.xml', 'data/application.xml', - 'data/certificate_policy_use.xml', + 'data/cert_policy_use.xml', + 'views/cert_authority.xml', + 'views/menu.xml', + 'wizards/cert_authority_scan.xml', + 'wizards/cert_authority_sign.xml', + 'wizards/cert_authority_revoke.xml', ], 'installable': True, 'application': False, diff --git a/clouder_certificate_authority/api.py b/clouder_cert_authority/api.py similarity index 78% rename from clouder_certificate_authority/api.py rename to clouder_cert_authority/api.py index 4f1bda2..41c0867 100644 --- a/clouder_certificate_authority/api.py +++ b/clouder_cert_authority/api.py @@ -6,6 +6,8 @@ from contextlib import contextmanager +from odoo import api + _logger = logging.getLogger(__name__) try: @@ -21,12 +23,12 @@ class API(object): @contextmanager @api.model_cr_context - def get_api(self, certificate_authority=None): + def get_api(self, cert_authority=None): """ It returns a :obj:`cfssl.CFSSL` for the cert authority. Args: - certificate_authority (:type:`clouder.CertificateAuthority`): - The certificate authority record singleton representing the + cert_authority (:type:`clouder.CertAuthority`): + The cert authority record singleton representing the remote API. The CA does not have to be initialized yet. Use :type:`None` if ``self`` is the CA that should be connected to. @@ -34,7 +36,7 @@ def get_api(self, certificate_authority=None): try: # @TODO: Figure out how the hell to get this host from the base host = '000.000.000.000' - port = certificate_authority.port_id.local_port + port = cert_authority.port_id.local_port api = cfssl.CFSSL(host, port, ssl=True) yield api finally: diff --git a/clouder_certificate_authority/data/application.xml b/clouder_cert_authority/data/application.xml similarity index 93% rename from clouder_certificate_authority/data/application.xml rename to clouder_cert_authority/data/application.xml index 752f3ac..ea082fc 100644 --- a/clouder_certificate_authority/data/application.xml +++ b/clouder_cert_authority/data/application.xml @@ -42,6 +42,9 @@ CFSSL cfssl + + 1 + - 1 - + diff --git a/clouder_certificate_authority/data/application_tag.xml b/clouder_cert_authority/data/application_tag.xml similarity index 71% rename from clouder_certificate_authority/data/application_tag.xml rename to clouder_cert_authority/data/application_tag.xml index 0e07474..62e887a 100644 --- a/clouder_certificate_authority/data/application_tag.xml +++ b/clouder_cert_authority/data/application_tag.xml @@ -8,8 +8,4 @@ cert_authority - - openssl - - diff --git a/clouder_certificate_authority/data/application_template.xml b/clouder_cert_authority/data/application_template.xml similarity index 100% rename from clouder_certificate_authority/data/application_template.xml rename to clouder_cert_authority/data/application_template.xml diff --git a/clouder_certificate_authority/data/application_type.xml b/clouder_cert_authority/data/application_type.xml similarity index 87% rename from clouder_certificate_authority/data/application_type.xml rename to clouder_cert_authority/data/application_type.xml index 2c82076..b19d5c4 100644 --- a/clouder_certificate_authority/data/application_type.xml +++ b/clouder_cert_authority/data/application_type.xml @@ -19,9 +19,6 @@ > openssl root - diff --git a/clouder_certificate_authority/data/certificate_policy_use.xml b/clouder_cert_authority/data/cert_policy_use.xml similarity index 57% rename from clouder_certificate_authority/data/certificate_policy_use.xml rename to clouder_cert_authority/data/cert_policy_use.xml index 051530f..c62e284 100644 --- a/clouder_certificate_authority/data/certificate_policy_use.xml +++ b/clouder_cert_authority/data/cert_policy_use.xml @@ -5,155 +5,155 @@ - - Certificate Signing + Cert Signing cert sign - Signing signing - S/MIME s/mime - Server Authentication server auth - Client Authentication client auth - Digital Signatures digital signature - Email Protection email protection - Key Encipherment key encipherment - Content Commitment content commitment - Key Agreement key agreement - CRL Signing crl sign - Encipher Only encipher only - Decipher Only decipher only - Any any - Code Signing code signing - IPSEC End System ipsec end system - IPSEC Tunnel ipsec tunnel - IPSEC User ipsec user - Timestamping timestamping - OCSP Signing ocsp signing - Microsoft SGC microsoft sgc - Netscape SGC netscape sgc diff --git a/clouder_certificate_authority/data/image.xml b/clouder_cert_authority/data/image.xml similarity index 86% rename from clouder_certificate_authority/data/image.xml rename to clouder_cert_authority/data/image.xml index 6f59217..49ae3f5 100644 --- a/clouder_certificate_authority/data/image.xml +++ b/clouder_cert_authority/data/image.xml @@ -17,7 +17,7 @@ - lasley/cfssl-exec + laslabs/clouder-cffsl-exec:latest data @@ -26,7 +26,7 @@ - lasley/openssl-exec + laslabs/clouder-openssl-exec:latest diff --git a/clouder_certificate_authority/data/image_port.xml b/clouder_cert_authority/data/image_port.xml similarity index 100% rename from clouder_certificate_authority/data/image_port.xml rename to clouder_cert_authority/data/image_port.xml diff --git a/clouder_certificate_authority/data/image_template.xml b/clouder_cert_authority/data/image_template.xml similarity index 100% rename from clouder_certificate_authority/data/image_template.xml rename to clouder_cert_authority/data/image_template.xml diff --git a/clouder_certificate_authority/data/image_volume.xml b/clouder_cert_authority/data/image_volume.xml similarity index 100% rename from clouder_certificate_authority/data/image_volume.xml rename to clouder_cert_authority/data/image_volume.xml diff --git a/clouder_cert_authority/models/__init__.py b/clouder_cert_authority/models/__init__.py new file mode 100644 index 0000000..dc20286 --- /dev/null +++ b/clouder_cert_authority/models/__init__.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +# Abstract +from . import cert_abstract +from . import config_cert_abstract +from . import key_abstract + +# CA +from . import cert_authority + +# Cert parts +from . import cert_host +from . import cert_name + +# Cert Types +from . import cert_request +from . import cert_x509 + +# Key Types +from . import key_private +from . import key_public + +# Policies +from . import cert_policy_auth +from . import cert_policy_sign +from . import cert_policy_use + +# Configs +from . import config_cert_client +from . import config_cert_server diff --git a/clouder_certificate_authority/models/certificate_abstract.py b/clouder_cert_authority/models/cert_abstract.py similarity index 85% rename from clouder_certificate_authority/models/certificate_abstract.py rename to clouder_cert_authority/models/cert_abstract.py index 88e1382..7a7bd6e 100644 --- a/clouder_certificate_authority/models/certificate_abstract.py +++ b/clouder_cert_authority/models/cert_abstract.py @@ -5,15 +5,15 @@ from odoo import api, fields, models -class ClouderCertificateAbstract(models.AbstractModel): +class ClouderCertAbstract(models.AbstractModel): """ It provides attributes and methods related to all cert files. """ - _name = 'clouder.certificate.abstract' - _description = 'Clouder Certificate Abstract' + _name = 'clouder.cert.abstract' + _description = 'Clouder Cert Abstract' name = fields.Char( required=True, - help='SHA-1 Sum of Certificate', + help='SHA-1 Sum of Cert', ) description = fields.Char() mime_sub_type = fields.Selection( @@ -37,12 +37,12 @@ class ClouderCertificateAbstract(models.AbstractModel): 'default_mime_type': mime_type, }""", ) - data = fields.Text( + data = fields.Binary( related='attachment_id.datas', ) request_id = fields.Many2one( string='CSR', - comodel_name='clouder.certificate.request', + comodel_name='clouder.cert.request', ) @api.model diff --git a/clouder_cert_authority/models/cert_authority.py b/clouder_cert_authority/models/cert_authority.py new file mode 100644 index 0000000..d06d190 --- /dev/null +++ b/clouder_cert_authority/models/cert_authority.py @@ -0,0 +1,222 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +import pickle + +from odoo import _, api, fields, models +from odoo.exceptions import UserError, ValidationError + +from ..api import API + +class ClouderCertAuthority(models.Model, API): + """ It provides an interface for controlling a Cert Authority. """ + + _name = 'clouder.cert.authority' + _description = 'Clouder Cert Authority' + _inherits = {'clouder.service': 'service_id'} + + service_id = fields.Many2one( + string='Cert Authority', + comodel_name='clouder.service', + required=True, + ondelete='cascade', + domain=lambda s: "[('application_id.tag_ids', 'in', %s)]" % ( + s.env.ref("clouder_cert_authority.tag_cert_authority").ids, + ) + ) + environment_id = fields.Many2one( + string='Environment', + comodel_name='clouder.environment', + related='service_id.environment_id', + ) + api_port_id = fields.Many2one( + string='API Port', + comodel_name='clouder.image.port', + compute='_compute_api_port_id', + ) + private_key_ids = fields.One2many( + string='Private Keys', + comodel_name='clouder.key.private', + related='cert_request_id.private_key_ids', + ) + cert_ids = fields.One2many( + string='Certs', + comodel_name='clouder.key.public', + related='cert_request_id.public_key_ids', + ) + cert_request_id = fields.Many2one( + string='CSR', + comodel_name='clouder.cert.request', + context="""{'default_host_ids': [(4, node_id)], + 'default_authority_id': id, + 'default_public_key_id': cert_id, + }""", + help='Cert Signing Request.', + ) + config_id = fields.Many2one( + string='Configuration', + comodel_name='clouder.config.cert.server', + help='Cert Authority Configuration.', + ) + is_initialized = fields.Boolean( + string='Initialized', + readonly=True, + help='Has this CA server been initialized yet?', + ) + openssl_service_id = fields.Many2one( + string='OpenSSL Service', + comodel_name='clouder.service', + compute='_compute_openssl_service_id', + ) + + @api.multi + @api.depends('service_id') + def _compute_api_port_id(self): + for record in self: + ports = record.service_id.port_ids.filtered( + lambda r: r.name == 'https' + ) + record.api_port_id = ports[0] + + @api.multi + @api.depends('sign_default_id', + 'sign_profile_ids', + ) + def _compute_auth_policy_ids(self): + for record in self: + policies = sum((record.sign_default_id, + record.sign_profile_ids, + )) + record.auth_policy_ids = policies.mapped('auth_policy_id') + + @api.multi + @api.depends('service_id') + def _compute_openssl_service_id(self): + for record in self: + # @TODO + raise NotImplementedError() + + @api.multi + @api.constrains('environment_id', 'service_id') + def _check_environment_id(self): + for record in self: + res = self.search( + [('environment_id', '=', record.enviroment_id.id)], + ) + if len(res) > 1: + raise ValidationError(_( + 'The Environment "%s" already has an active Certificate ' + 'Authority.', + )) + + @api.multi + def name_get(self): + names = [] + for record in self: + name = record.name + if record.cert_request_id: + name += ': %s' % record.cert_request_id.name + names.append((record.id, name)) + return names + + @api.multi + def init_ca(self): + """ It initializes a new cert authority if needed. """ + for record in self.fitered(lambda r: not r.is_initialized): + if not record.cert_request_id: + raise ValidationError(_( + 'You must assign a cert request before ' + 'initializing a Cert Authority.', + )) + with record.get_api() as api: + csr = record.cert_request_id + response = api.init_ca( + cert_request=csr.api_object, + ca=record.config_id.api_object, + ) + cert = record.cert_request_id._new_cert( + response['cert'], + ) + private_key = record.cert_request_id._new_key( + response['private_key'], + public=False, + ) + record.write({ + 'is_initialized': True, + }) + + @api.multi + def scan(self, host, ip=None): + """ It scans servers to determine the quality of their TLS setup. + + Args: + host (ClouderCertHost): The host to scan. + ip (str): The IP Address to override DNS lookup of host. + """ + self.ensure_one() + with self.get_api() as api: + results = api.scan(host.api_object, ip) + if results['error']: + raise UserError(_(results['error'])) + return { + 'grade': results['grade'], + 'output': results['output'], + } + + @api.multi + def sign(self, cert_request, hosts=None, + subject=None, serial_sequence=None): + """ It signs a cert request. """ + self.ensure_one() + if hosts is None: + hosts = [] + with self.get_api() as api: + results = api.sign( + certifcate_request=cert_request.api_object, + profile=self.config_id.api_object, + hosts=[host.api_object for host in hosts], + subject=subject, + serial_sequence=serial_sequence, + ) + return cert_request._new_cert(results) + + @api.multi + def revoke(self, cert): + """ It revokes a cert. """ + self.ensure_one() + with self.get_api() as api: + api.revoke(cert.name, cert.authority_key) + + @api.model + def _get_cert_info(self, cert): + """ It returns information about a signed cert. + + Args: + cert (str): PEM encoded cert. + Returns: + dict: A dictionary with the following keys: + * extensions (:class:`dict`): X.509 extensions. Some valid + keys are: + * authorityInfoAccess + * authorityKeyIdentifier + * basicConstraints + * cRLDistributionPoints + * certPolicies + * extendedKeyUsage + * issuerAltName + * keyUsage + * subjectAltName + * subjectKeyIdentifier + * fingerprint (:class:`str`): Cert fingerprint. + * signature (:class:`str`): Cert signature. + * not_valid_before (:class:`datetime`): Validity start + date for the cert. + * not_valid_after (:class:`datetime`): Validity end date + for the cert. + * public_key (:class:`str`): Public key associated with + the cert. + """ + self.ensure_one() + res = self.openssl_service_id.execute(['parse_cert', cert]) + return pickle.loads(res.decode('base64')) diff --git a/clouder_certificate_authority/models/certificate_host.py b/clouder_cert_authority/models/cert_host.py similarity index 85% rename from clouder_certificate_authority/models/certificate_host.py rename to clouder_cert_authority/models/cert_host.py index d65815d..7d4a38a 100644 --- a/clouder_certificate_authority/models/certificate_host.py +++ b/clouder_cert_authority/models/cert_host.py @@ -7,11 +7,11 @@ from ..api import API -class ClouderCertificateHost(models.Model, API): +class ClouderCertHost(models.Model, API): """ It provides the concept of a cert's CommonName """ - _name = 'clouder.certificate.host' - _description = 'Clouder Certificate Host' + _name = 'clouder.cert.host' + _description = 'Clouder Cert Host' name = fields.Char( required=True, diff --git a/clouder_certificate_authority/models/certificate_name.py b/clouder_cert_authority/models/cert_name.py similarity index 85% rename from clouder_certificate_authority/models/certificate_name.py rename to clouder_cert_authority/models/cert_name.py index dd219c6..2e97d2b 100644 --- a/clouder_certificate_authority/models/certificate_name.py +++ b/clouder_cert_authority/models/cert_name.py @@ -7,21 +7,21 @@ from ..api import API -class ClouderCertificateName(models.Model, API): +class ClouderCertName(models.Model, API): """ It provides the concept of a cert's SubjectInfo """ - _name = 'clouder.certificate.name' - _description = 'Clouder Certificate Name' + _name = 'clouder.cert.name' + _description = 'Clouder Cert Name' country_id = fields.Many2one( string='Country', - model='res.country', + comodel_name='res.country', required=True, default=lambda s: s.env.user.country_id, ) state_id = fields.Many2one( string='State', - model='res.country.state', + comodel_name='res.country.state', domain='[(country_id, "=", country_id)]', default=lambda s: s.env.user.state_id, ) @@ -30,7 +30,7 @@ class ClouderCertificateName(models.Model, API): ) company_id = fields.Many2one( string='Organization', - model='res.company', + comodel_name='res.company', required=True, domain='[(company_id, "=", company_id)]', default=lambda s: s.env.user.company_id, diff --git a/clouder_certificate_authority/models/certificate_policy_auth.py b/clouder_cert_authority/models/cert_policy_auth.py similarity index 89% rename from clouder_certificate_authority/models/certificate_policy_auth.py rename to clouder_cert_authority/models/cert_policy_auth.py index aa443fd..1120506 100644 --- a/clouder_certificate_authority/models/certificate_policy_auth.py +++ b/clouder_cert_authority/models/cert_policy_auth.py @@ -7,11 +7,11 @@ from odoo.addons.clouder.tools import generate_random_password -class ClouderCertificatePolicyAuth(models.Model): +class ClouderCertPolicyAuth(models.Model): """ It provides a CA Signature Auth Policy """ - _name = 'clouder.certificate.policy.auth' - _description = 'Clouder Certificate Auth Policy' + _name = 'clouder.cert.policy.auth' + _description = 'Clouder Cert Auth Policy' name = fields.Char( required=True, diff --git a/clouder_certificate_authority/models/certificate_policy_sign.py b/clouder_cert_authority/models/cert_policy_sign.py similarity index 79% rename from clouder_certificate_authority/models/certificate_policy_sign.py rename to clouder_cert_authority/models/cert_policy_sign.py index ad23330..096787b 100644 --- a/clouder_certificate_authority/models/certificate_policy_sign.py +++ b/clouder_cert_authority/models/cert_policy_sign.py @@ -5,23 +5,23 @@ from odoo import api, fields, models -class ClouderCertificatePolicySign(models.Model): +class ClouderCertPolicySign(models.Model): """ It provides a CA Signature Policy """ - _name = 'clouder.certificate.policy.sign' - _description = 'Clouder Certificate Signing Policy' + _name = 'clouder.cert.policy.sign' + _description = 'Clouder Cert Signing Policy' name = fields.Char( required=True, ) usage_ids = fields.Many2many( string='Usages', - comodel_name='clouder.certificate.policy.use', + comodel_name='clouder.cert.policy.use', required=True, ) auth_policy_id = fields.Many2one( string='Auth Key', - comodel_name='clouder.certificate.policy.auth', + comodel_name='clouder.cert.policy.auth', required=True, ) expire_hours = fields.Integer( diff --git a/clouder_certificate_authority/models/certificate_policy_use.py b/clouder_cert_authority/models/cert_policy_use.py similarity index 86% rename from clouder_certificate_authority/models/certificate_policy_use.py rename to clouder_cert_authority/models/cert_policy_use.py index 489e098..1d502fc 100644 --- a/clouder_certificate_authority/models/certificate_policy_use.py +++ b/clouder_cert_authority/models/cert_policy_use.py @@ -5,11 +5,11 @@ from odoo import api, fields, models -class ClouderCertificatePolicyUse(models.Model): +class ClouderCertPolicyUse(models.Model): """ It provides a CA Signature Use Policy """ - _name = 'clouder.certificate.policy.use' - _description = 'Clouder Certificate Usage Policy' + _name = 'clouder.cert.policy.use' + _description = 'Clouder Cert Usage Policy' _order = 'sequence, name' name = fields.Char( diff --git a/clouder_certificate_authority/models/certificate_request.py b/clouder_cert_authority/models/cert_request.py similarity index 80% rename from clouder_certificate_authority/models/certificate_request.py rename to clouder_cert_authority/models/cert_request.py index 0fdd845..d7a758e 100644 --- a/clouder_certificate_authority/models/certificate_request.py +++ b/clouder_cert_authority/models/cert_request.py @@ -9,14 +9,14 @@ from ..api import API -class ClouderCertificateRequest(models.Model, API): - """ It provides the concept of a Certificate Request """ +class ClouderCertRequest(models.Model, API): + """ It provides the concept of a Cert Request """ - _name = 'clouder.certificate.request' - _inherit = ['clouder.certificate.abstract', + _name = 'clouder.cert.request' + _inherit = ['clouder.cert.abstract', 'clouder.key.abstract', ] - _description = 'Clouder Certificate Request' + _description = 'Clouder Cert Request' name = fields.Char( string='Common Name', @@ -24,17 +24,17 @@ class ClouderCertificateRequest(models.Model, API): ) authority_id = fields.Many2one( string='Cert Authority', - comodel_name='clouder.certificate.authority', + comodel_name='clouder.cert.authority', required=True, ondelete='cascade', ) host_ids = fields.Many2one( string='Hosts', - comodel_name='clouder.certificate.host', + comodel_name='clouder.cert.host', ) subject_info_ids = fields.Many2many( string='Names', - comodel_name='clouder.certificate.name', + comodel_name='clouder.cert.name', required=True, ) public_key_ids = fields.One2many( @@ -47,9 +47,9 @@ class ClouderCertificateRequest(models.Model, API): comodel_name='clouder.key.private', inverse_name='request_id', ) - certificate_ids = fields.One2many( - string='Signed Certificate', - comodel_name='clouder.certificate', + cert_ids = fields.One2many( + string='Signed Cert', + comodel_name='clouder.cert.x509', inverse_name='request_id', ) api_object = fields.Binary( @@ -63,15 +63,15 @@ def create_cert(self): response = api.new_cert( request=record.to_api(), ) - certificate = record._new_cert( - response['certificate'], + cert = record._new_cert( + response['cert'], ) private_key = record._new_key( response['private_key'], public=True, ) attachment = record.attachment_id.create({ - 'datas': response['certificate_request'], + 'datas': response['cert_request'], }) record.write({ 'attachment_id': attachment.id, @@ -111,7 +111,7 @@ def _new_cert(self, cert_data): if not usage: continue usages += usage - certificate = self.env['clouder.certificate.x509'].create({ + cert = self.env['clouder.cert.x509'].create({ 'subject_key': cert_info['subjectKeyIdentifier']['digest'], 'authority_key': auth_key, 'public_key_id': public_key.id, @@ -121,13 +121,13 @@ def _new_cert(self, cert_data): attachment = key.attachment_id.create({ 'datas': cert_data, }) - certificate.attachment_id = attachment.id + cert.attachment_id = attachment.id @api.multi def _compute_api_object(self): """ It computes the keys required for the JSON request """ for record in self: - record.api_object = self.cfssl.CertificateRequest( + record.api_object = self.cfssl.CertRequest( common_name=record.name, names=record.subject_info_ids.mapped('api_object'), hosts=record.host_ids.mapped('api_object'), diff --git a/clouder_certificate_authority/models/certificate_x509.py b/clouder_cert_authority/models/cert_x509.py similarity index 68% rename from clouder_certificate_authority/models/certificate_x509.py rename to clouder_cert_authority/models/cert_x509.py index b792b23..f3e4ea0 100644 --- a/clouder_certificate_authority/models/certificate_x509.py +++ b/clouder_cert_authority/models/cert_x509.py @@ -7,12 +7,12 @@ from ..api import API -class ClouderCertificateX509(models.Model, API): - """ It provides the concept of a signed certificate. """ +class ClouderCertX509(models.Model, API): + """ It provides the concept of a signed cert. """ - _name = 'clouder.certificate.x509' - _inherit = 'clouder.certificate.abstract' - _description = 'Clouder Certificate X.509' + _name = 'clouder.cert.x509' + _inherit = 'clouder.cert.abstract' + _description = 'Clouder Cert X.509' subject_key = fields.Char( required=True, @@ -30,10 +30,10 @@ class ClouderCertificateX509(models.Model, API): ) request_id = fields.Many2one( string='CSR', - comodel_name='clouder.certificate.request', + comodel_name='clouder.cert.request', required=True, ) use_policy_ids = fields.Many2many( string='Use Policies', - comodel_name='clouder.certificate.policy.use', + comodel_name='clouder.cert.policy.use', ) diff --git a/clouder_certificate_authority/models/config_certificate_abstract.py b/clouder_cert_authority/models/config_cert_abstract.py similarity index 56% rename from clouder_certificate_authority/models/config_certificate_abstract.py rename to clouder_cert_authority/models/config_cert_abstract.py index b75c478..35a38de 100644 --- a/clouder_certificate_authority/models/config_certificate_abstract.py +++ b/clouder_cert_authority/models/config_cert_abstract.py @@ -7,24 +7,24 @@ from ..api import API -class ClouderConfigCertificateAbstract(models.AbstractModel, API): - """ It provides data handling for certificate client + server configs. """ +class ClouderConfigCertAbstract(models.AbstractModel, API): + """ It provides data handling for cert client + server configs. """ - _name = 'clouder.config.certificate.abstract' - _description = 'Clouder Config Certificate Abstract' + _name = 'clouder.config.cert.abstract' + _description = 'Clouder Config Cert Abstract' - sign_policy_default_id = fields.Many2one( + sign_default_id = fields.Many2one( string='Default Signing Policy', - comodel_name='clouder.certificate.policy.sign', + comodel_name='clouder.cert.policy.sign', required=True, ) - sign_policy_profile_ids = fields.Many2many( + sign_profile_ids = fields.Many2many( string='Signing Policy Profiles', - comodel_name='clouder.certificate.policy.sign', + comodel_name='clouder.cert.policy.sign', ) auth_policy_ids = fields.Many2many( string='Auth Policies', - comodel_name='clouder.certificate.policy.auth', + comodel_name='clouder.cert.policy.auth', compute='_compute_auth_policy_ids', ) api_object = fields.Binary( @@ -35,10 +35,10 @@ class ClouderConfigCertificateAbstract(models.AbstractModel, API): def _compute_api_object(self): """ It computes the keys required for the JSON request. """ for record in self: - profiles = record.sign_policy_profile_ids.mapped('api_object') + profiles = record.sign_profile_ids.mapped('api_object') auth_keys = record.auth_policy_ids.mapped('api_object') record.api_object = self.cfssl.ConfigMixer( - sign_policy=record.sign_policy_default_id.api_object, + sign_policy=record.sign_default_id.api_object, sign_policies=profiles, auth_policies=auth_keys, ) diff --git a/clouder_certificate_authority/models/config_certificate_client.py b/clouder_cert_authority/models/config_cert_client.py similarity index 67% rename from clouder_certificate_authority/models/config_certificate_client.py rename to clouder_cert_authority/models/config_cert_client.py index 4b63511..57c5c4a 100644 --- a/clouder_certificate_authority/models/config_certificate_client.py +++ b/clouder_cert_authority/models/config_cert_client.py @@ -7,16 +7,16 @@ from ..api import API -class ClouderConfigCertificateClient(models.Model, API): - """ It provides data handling for certificate client configs. """ +class ClouderConfigCertClient(models.Model, API): + """ It provides data handling for cert client configs. """ - _name = 'clouder.config.certificate.client' - _inherit = 'clouder.config.certificate.abstract' - _description = 'Clouder Config Certificate Client' + _name = 'clouder.config.cert.client' + _inherit = 'clouder.config.cert.abstract' + _description = 'Clouder Config Cert Client' remote_ids = fields.Many2many( string='Remotes', - comodel_name='clouder.certificate.host', + comodel_name='clouder.cert.host', required=True, ) @@ -24,7 +24,7 @@ class ClouderConfigCertificateClient(models.Model, API): def _compute_api_object(self): """ It computes the keys required for the JSON request. """ for record in self: - super(ClouderConfigCertificateClient, record)._compute_api_object() + super(ClouderConfigCertClient, record)._compute_api_object() remotes = { remote.name: remote.api_object for remote in record.remote_ids } diff --git a/clouder_certificate_authority/models/config_certificate_server.py b/clouder_cert_authority/models/config_cert_server.py similarity index 62% rename from clouder_certificate_authority/models/config_certificate_server.py rename to clouder_cert_authority/models/config_cert_server.py index 7c835a1..958dcb4 100644 --- a/clouder_certificate_authority/models/config_certificate_server.py +++ b/clouder_cert_authority/models/config_cert_server.py @@ -7,18 +7,18 @@ from ..api import API -class ClouderConfigCertificateServer(models.Model, API): - """ It provides data handling for certificate server configs. """ +class ClouderConfigCertServer(models.Model, API): + """ It provides data handling for cert server configs. """ - _name = 'clouder.config.certificate.server' - _inherit = 'clouder.config.certificate.abstract' - _description = 'Clouder Config Certificate Server' + _name = 'clouder.config.cert.server' + _inherit = 'clouder.config.cert.abstract' + _description = 'Clouder Config Cert Server' @api.multi def _compute_api_object(self): """ It computes the keys required for the JSON request. """ for record in self: - super(ClouderConfigCertificateServer, record)._compute_api_object() + super(ClouderConfigCertServer, record)._compute_api_object() record.api_object = self.cfssl.ConfigServer( sign_policy_default=record.api_object.sign_policy, sign_policies_add=record.api_object.sign_policies, diff --git a/clouder_cert_authority/models/key_abstract.py b/clouder_cert_authority/models/key_abstract.py new file mode 100644 index 0000000..4c916bd --- /dev/null +++ b/clouder_cert_authority/models/key_abstract.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import api, fields, models + + +class ClouderKeyAbstract(models.AbstractModel): + """ It provides attributes and methods related to all keys. """ + + _name = 'clouder.key.abstract' + _description = 'Clouder Key Abstract' + + strength = fields.Integer( + default=4096, + ) + algorithm = fields.Selection( + default='rsa', + selection=lambda s: s._get_algorithms(), + ) + is_private = fields.Boolean() + active = fields.Boolean( + default=True, + ) + mime_sub_type = fields.Selection( + selection=lambda s: s._get_mime_sub_types(), + deault='x-pkcs12', + ) + mime_type = fields.Char( + readonly=True, + computed='_compute_mime_type', + ) + attachment_id = fields.Many2one( + string='Key', + comodel_name='ir.attachment', + context="""{ + 'default_type': 'binary', + 'default_res_model': _name, + 'default_res_field': 'attachment_id', + 'default_res_id': id, + 'default_name': name, + 'default_description': description, + 'default_mime_type': mime_type, + }""", + ) + data = fields.Binary( + related='attachment_id.datas', + ) + request_id = fields.Many2one( + string='CSR', + comodel_name='clouder.cert.request', + ) + + @api.model + def _get_algorithms(self): + return [ + ('rsa', 'RSA'), + ('ecdsa', 'ECDSA'), + ] + + @api.model + def _get_mime_sub_types(self): + return self.env['clouder.cert.abstract']._get_mime_sub_types() + + @api.multi + def _compute_mime_type(self): + for record in self: + record.mime_type = 'application/%s' % record.mime_sub_type diff --git a/clouder_certificate_authority/models/key_private.py b/clouder_cert_authority/models/key_private.py similarity index 100% rename from clouder_certificate_authority/models/key_private.py rename to clouder_cert_authority/models/key_private.py diff --git a/clouder_certificate_authority/models/key_public.py b/clouder_cert_authority/models/key_public.py similarity index 100% rename from clouder_certificate_authority/models/key_public.py rename to clouder_cert_authority/models/key_public.py diff --git a/clouder_cert_authority/views/cert_authority.xml b/clouder_cert_authority/views/cert_authority.xml new file mode 100644 index 0000000..de80295 --- /dev/null +++ b/clouder_cert_authority/views/cert_authority.xml @@ -0,0 +1,103 @@ + + + + + + + Cert Authority Tree + clouder.cert.authority + + + + + + + + + + + + Cert Authority Form + clouder.cert.authority + +
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + Cert Authority Search + clouder.cert.authority + + + + + + + + + + + + + Cert Authorities + clouder.cert.authority + form + tree,form + + + diff --git a/clouder_cert_authority/views/menu.xml b/clouder_cert_authority/views/menu.xml new file mode 100644 index 0000000..6e77df9 --- /dev/null +++ b/clouder_cert_authority/views/menu.xml @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/clouder_certificate_authority/models/__init__.py b/clouder_cert_authority/wizards/__init__.py similarity index 53% rename from clouder_certificate_authority/models/__init__.py rename to clouder_cert_authority/wizards/__init__.py index 08d9d6b..0c953a6 100644 --- a/clouder_certificate_authority/models/__init__.py +++ b/clouder_cert_authority/wizards/__init__.py @@ -1,3 +1,7 @@ # -*- coding: utf-8 -*- # Copyright 2016 LasLabs Inc. # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from . import cert_authority_revoke +from . import cert_authority_scan +from . import cert_authority_sign diff --git a/clouder_cert_authority/wizards/cert_authority_revoke.py b/clouder_cert_authority/wizards/cert_authority_revoke.py new file mode 100644 index 0000000..857b712 --- /dev/null +++ b/clouder_cert_authority/wizards/cert_authority_revoke.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import api, fields, models + + +class ClouderCertAuthorityRevoke(models.TransientModel): + """ It revokes a Cert Request. """ + + _name = 'clouder.cert.authority.revoke' + _description = 'Clouder Cert Authority Revoke' + + cert_authority_id = fields.Many2one( + string='Cert Authority', + comodel_name='clouder.cert.authority', + required=True, + readonly=True, + ) + cert_id = fields.Many2one( + string='Signed Cert', + comodel_name='clouder.cert.x509', + required=True, + ) + + @api.multi + def action_revoke(self): + self.ensure_one() + self.cert_authority_id.revoke( + self.cert_id, + ) + return True diff --git a/clouder_cert_authority/wizards/cert_authority_revoke.xml b/clouder_cert_authority/wizards/cert_authority_revoke.xml new file mode 100644 index 0000000..e1c3657 --- /dev/null +++ b/clouder_cert_authority/wizards/cert_authority_revoke.xml @@ -0,0 +1,42 @@ + + + + + + + Cert Authority Revoke Form + clouder.cert.authority.revoke + +
+ +

+ +

+ + + +
+
+
+
+
+
+ + + Revoke A Cert + clouder.cert.authority + clouder.cert.authority.revoke + form + form + client_action_multi + new + + +
diff --git a/clouder_cert_authority/wizards/cert_authority_scan.py b/clouder_cert_authority/wizards/cert_authority_scan.py new file mode 100644 index 0000000..51bf139 --- /dev/null +++ b/clouder_cert_authority/wizards/cert_authority_scan.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import api, fields, models + + +class ClouderCertAuthorityScan(models.TransientModel): + """ It scans a host and displays the results. """ + + _name = 'clouder.cert.authority.scan' + _description = 'Clouder Cert Authority Scan' + + cert_authority_id = fields.Many2one( + string='Cert Authority', + comodel_name='clouder.cert.authority', + required=True, + readonly=True, + ) + host_id = fields.Many2one( + string='Host', + comodel_name='clouder.cert.host', + required=True, + help='The host to scan.' + ) + ip = fields.Char( + string='IP', + help='IP Address to override DNS lookup of host.', + ) + state = fields.Selection([ + ('New', 'Not Scanned'), + ('Good', 'Good'), + ('Warning', 'Warning'), + ('Bad', 'Bad'), + ('Skipped', 'Skipped'), + ], + default='New', + readonly=True, + required=True, + ) + error = fields.Text() + results = fields.Text() + + @api.multi + def action_scan(self): + self.ensure_one() + results = self.cert_authority_id.scan( + self.host_id, self.ip, + ) + self.write({ + 'state': results['grade'], + 'error': results['error'], + 'results': results['output'], + }) + return True diff --git a/clouder_cert_authority/wizards/cert_authority_scan.xml b/clouder_cert_authority/wizards/cert_authority_scan.xml new file mode 100644 index 0000000..056ea36 --- /dev/null +++ b/clouder_cert_authority/wizards/cert_authority_scan.xml @@ -0,0 +1,52 @@ + + + + + + + Cert Authority Scan Form + clouder.cert.authority.scan + +
+
+ +
+ +

+ +

+ + + + + + + + +
+
+
+
+
+
+ + + Scan TLS/SSL Setup + clouder.cert.authority + clouder.cert.authority.scan + form + form + client_action_multi + new + + +
diff --git a/clouder_cert_authority/wizards/cert_authority_sign.py b/clouder_cert_authority/wizards/cert_authority_sign.py new file mode 100644 index 0000000..97e77f0 --- /dev/null +++ b/clouder_cert_authority/wizards/cert_authority_sign.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import api, fields, models + + +class ClouderCertAuthoritySign(models.TransientModel): + """ It signs a Cert Request. """ + + _name = 'clouder.cert.authority.sign' + _description = 'Clouder Cert Authority Sign' + + cert_authority_id = fields.Many2one( + string='Cert Authority', + comodel_name='clouder.cert.authority', + required=True, + readonly=True, + ) + cert_request_id = fields.Many2one( + string='Cert Request', + comodel_name='clouder.cert.request', + required=True, + ) + host_ids = fields.Many2many( + string='Hosts', + comodel_name='clouder.cert.host', + help='Hosts to use as overrides for the CSR.', + ) + subject = fields.Char( + help='Subject to use as override for the CSR.', + ) + serial_sequence = fields.Char( + help='Prefix for the generated cert\'s serial number.', + ) + cert_id = fields.Many2one( + string='Signed Cert', + comodel_name='clouder.cert.x509', + ) + + @api.multi + def action_sign(self): + self.ensure_one() + self.write({ + 'cert_id': self.cert_authority_id.sign( + cert_request=self.cert_request_id, + hosts=self.host_ids, + subject=self.subject, + serial_sequence=self.serial_sequence, + ) + }) + return True diff --git a/clouder_cert_authority/wizards/cert_authority_sign.xml b/clouder_cert_authority/wizards/cert_authority_sign.xml new file mode 100644 index 0000000..f36eec9 --- /dev/null +++ b/clouder_cert_authority/wizards/cert_authority_sign.xml @@ -0,0 +1,53 @@ + + + + + + + Cert Authority Sign Form + clouder.cert.authority.sign + +
+ +

+ +

+ + + + + + + + + + + + +
+
+
+
+
+
+ + + Sign A Cert + clouder.cert.authority + clouder.cert.authority.sign + form + form + client_action_multi + new + + +
diff --git a/clouder_certificate_authority/images/cfssl-exec/Dockerfile b/clouder_certificate_authority/images/cfssl-exec/Dockerfile deleted file mode 100644 index 630c59c..0000000 --- a/clouder_certificate_authority/images/cfssl-exec/Dockerfile +++ /dev/null @@ -1,45 +0,0 @@ -FROM clouder/base:3.4 -MAINTAINER Dave Lasley - -# Install Build Dependencies - -ENV buildDeps "build-base \ - gcc \ - git \ - go \ - libtool" - -RUN apk add --no-cache $buildDeps - -# Install CFSSL - -RUN git clone --depth=1 https://github.com/cloudflare/cfssl.git /go/src/github.com/cloudflare/cfssl - -WORKDIR /go/src/github.com/cloudflare/cfssl - -RUN set -x \ - && go get github.com/GeertJohan/go.rice/rice \ - && rice embed-go -i=./cli/serve \ - && cp -R /go/src/github.com/cloudflare/cfssl/vendor/github.com/cloudflare/cfssl_trust /etc/cfssl \ - && go build -o /usr/bin/cfssl ./cmd/cfssl \ - && go build -o /usr/bin/cfssljson ./cmd/cfssljson \ - && go build -o /usr/bin/mkbundle ./cmd/mkbundle \ - && go build -o /usr/bin/multirootca ./cmd/multirootca \ - && apk del $buildDeps \ - && rm -rf /go \ - && echo "Build complete." - -# Create and Change to PKI Dir -RUN mkdir -p /var/pki -WORKDIR /var/pki - -# Setup Environment -ENV CFSSL_DATA=/var/pki \ - CFSSL_CERT=$CFSSL_DATA/ca.pem \ - CFSSL_KEY=$CFSSL_DATA/ca_key.pem - -CMD ["cfssl", \ - "serve", \ - "-address=:8080", \ - "-ca $CFFSL_CERT", \ - "-ca-key $CFSSL_KEY"] diff --git a/clouder_certificate_authority/images/openssl-exec/Dockerfile b/clouder_certificate_authority/images/openssl-exec/Dockerfile deleted file mode 100644 index 23ae4a5..0000000 --- a/clouder_certificate_authority/images/openssl-exec/Dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -FROM clouder/base:3.4 -MAINTAINER Dave Lasley - -RUN apk add --no-cache openssl \ - openssl-dev \ - pip - -RUN pip install cryptography diff --git a/clouder_certificate_authority/images/openssl-exec/docker-entrypoint.sh b/clouder_certificate_authority/images/openssl-exec/docker-entrypoint.sh deleted file mode 100755 index d180126..0000000 --- a/clouder_certificate_authority/images/openssl-exec/docker-entrypoint.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/ash -# Copyright 2016 LasLabs Inc. -# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). - -set -e - -# Add openssl as command if needed -if [ "${1:0:1}" = '-' ]; then - set -- openssl "$@" -fi - -# As argument is not related to openssl, -# then assume that user wants to run their own process, -# for example a `bash` shell to explore this image -exec "$@" diff --git a/clouder_certificate_authority/images/openssl-exec/parse_cert b/clouder_certificate_authority/images/openssl-exec/parse_cert deleted file mode 100755 index 2cbaac1..0000000 --- a/clouder_certificate_authority/images/openssl-exec/parse_cert +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# Copyright 2016 LasLabs Inc. -# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). - -from __future__ import print_function - -import argparse -import pickle - -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives.serialization import Encoding -from cryptography.hazmat.primitives import hashes -from cryptography import x509 - - -def parse_cert(self, cert): - """ It parses a PEM encoded certificate and returns the attrs. """ - cert = x509.load_pem_x509_certificate( - cert, - default_backend(), - ) - enc_pem = Encoding('PEM') - extensions = {} - for extension in cert.extensions: - public_props = ( - n for n in dir(extension.value) if not n.startswith('_') - ) - extensions[extension.oid._name] = { - 'oid': extension.oid.dotted_string, - } - for prop in public_props: - if prop == 'oid': - continue - try: - value = getattr(extension.value, prop) - except ValueError: - continue - if callable(value): - continue - extensions[extension.oid._name][prop] = value - return { - 'serial': cert.serial, - 'fingerprint': cert.fingerprint(hashes.SHA256()), - 'public_key': cert.public_bytes(enc_pem), - 'not_valid_before': cert.not_valid_before, - 'not_valid_after': cert.not_valid_after, - 'extensions': extensions, - } - - -if __name__ == '__main__': - - parser = argparse.ArgumentParser(description='Cert Parse CLI') - parser.add_argument('cert', - help='PEM encoded certificate string to parse', - ) - args = parser.parse_args() - cert_info = parse_cert(args.cert) - print( - pickle.dumps(cert_info.encode('base64')), - ) diff --git a/clouder_certificate_authority/models/certificate_authority.py b/clouder_certificate_authority/models/certificate_authority.py deleted file mode 100644 index 63ca6a6..0000000 --- a/clouder_certificate_authority/models/certificate_authority.py +++ /dev/null @@ -1,192 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2016 LasLabs Inc. -# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). - -import pickle.loads - -from odoo import api, fields, models -from odoo.exceptions import UserError - -from ..api import API - -class ClouderCertificateAuthority(models.Model, API): - """ It provides an interface for controlling a Cert Authority. """ - - _name = 'clouder.certificate.authority' - _description = 'Clouder Certificate Authority' - _inherits = {'clouder.service': 'service_id'} - - service_id = fields.Many2one( - string='Certificate Authority', - comodel_name='clouder.service', - required=True, - ondelete='cascade', - domain=lambda s: "[('tag_ids', '=', %d)]" % s.enf.ref( - 'clouder_certificate_authority.tag_cert_authority', - ), - ) - api_port_id = fields.Many2one( - string='API Port', - comodel_name='clouder.image.port', - compute='_compute_api_port_id', - ) - host_id = fields.Many2one( - string='Host', - comodel_name='clouder.certificate.host', - ) - private_key_ids = fields.One2many( - string='Private Key', - comodel_name='clouder.key.private', - related='certificate_request_id.private_key_ids', - ) - certificate_ids = fields.One2many( - string='Certificate', - comodel_name='clouder.key.public', - related='certificate_request_id.public_key_ids', - ) - certificate_request_id = fields.Many2one( - string='CSR', - comodel_name='clouder.certificate.request', - context="""{'default_host_ids': [(4, host_id)], - 'default_authority_id': id, - 'default_public_key_id': certificate_id, - }""", - help='Certificate Signing Request.', - ) - config_id = fields.Many2one( - string='Configuration', - comodel_name='clouder.config.certificate.server', - help='Certificate Authority Configuration.', - ) - is_initialized = fields.Boolean( - string='Initialized', - help='Has this CA server been initialized yet?', - ) - openssl_service_id = fields.Many2one( - string='OpenSSL Service', - comodel_name='clouder.service', - compute='_compute_openssl_service_id', - ) - - @api.multi - @api.depends('exec_service_id') - def _compute_api_port_id(self): - for record in self: - ports = record.exec_service_id.port_ids.filtered( - lambda r: r.name == 'https' - ) - record.api_port_id = ports[0] - - @api.multi - @api.depends('sign_policy_default_id', - 'sign_policy_profile_ids', - ) - def _compute_auth_policy_ids(self): - for record in self: - policies = sum((record.sign_policy_default_id, - record.sign_policy_profile_ids, - )) - record.auth_policy_ids = policies.mapped('auth_policy_id') - - @api.multi - @api.depends('service_ids', 'service_ids.code') - def _compute_exec_service_id(self): - for record in self: - service_ids = record.service_ids.filtered( - lambda s: s.code == 'exec', - ) - record.exec_service_id = service_ids[0] - - @api.multi - @api.depends('exec_service_id') - def _compute_openssl_service_id(self): - for record in self: - execs = record.exec_service_id.child_ids.filtered( - lambda r: r.code == 'exec' - ) - record.openssl_service_id = execs[0] - - @api.multi - def init_ca(self): - """ It initializes a new certificate authority if needed. """ - for record in self.fitered(lambda r: not r.is_initialized): - with record.get_api() as api: - csr = record.certificate_request_id - response = api.init_ca( - certificate_request=csr.to_api(), - ca=record.config_id.to_api(), - ) - certificate = record.certificate_request_id._new_cert( - response['certificate'], - ) - private_key = record.certificate_request_id._new_key( - response['private_key'], - public=False, - ) - record.write({ - 'is_initialized': True, - }) - - @api.multi - def scan(self, host, ip): - """ It scans servers to determine the quality of their TLS setup. """ - self.ensure_one() - with self.get_api() as api: - results = api.scan(host, ip) - if results['error']: - raise UserError(results['error']) - return { - 'grade': results['grade'], - 'output': results['output'], - } - - @api.multi - def sign(self, certificate_request): - """ It signs a certificate request. """ - self.ensure_one() - with self.get_api() as api: - results = api.sign( - certifcate_request=certificate_request.to_api(), - profile=self.config_id.to_api(), - ) - return certificate_request._new_cert(results) - - @api.multi - def revoke(self, certificate): - """ It revokes a certificate. """ - self.ensure_one() - with self.get_api() as api: - api.revoke(certificate.name, certificate.authority_key) - - @api.model - def _get_cert_info(self, cert): - """ It returns information about a signed certificate. - - Args: - cert (str): PEM encoded certificate. - Returns: - dict: A dictionary with the following keys: - * extensions (:class:`dict`): X.509 extensions. Some valid - keys are: - * authorityInfoAccess - * authorityKeyIdentifier - * basicConstraints - * cRLDistributionPoints - * certificatePolicies - * extendedKeyUsage - * issuerAltName - * keyUsage - * subjectAltName - * subjectKeyIdentifier - * fingerprint (:class:`str`): Certificate fingerprint. - * signature (:class:`str`): Certificate signature. - * not_valid_before (:class:`datetime`): Validity start - date for the certificate. - * not_valid_after (:class:`datetime`): Validity end date - for the certificate. - * public_key (:class:`str`): Public key associated with - the certificate. - """ - self.ensure_one() - res = self.openssl_service_id.execute(['parse_cert', cert]) - return pickle.loads(res.decode('base64')) diff --git a/clouder_certificate_authority/models/key_abstract.py b/clouder_certificate_authority/models/key_abstract.py deleted file mode 100644 index b30d571..0000000 --- a/clouder_certificate_authority/models/key_abstract.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2016 LasLabs Inc. -# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). - -from odoo import api, fields, models - - -class ClouderKeyAbstract(models.Model): - """ It provides attributes and methods related to all keys. """ - - _name = 'clouder.key.abstract' - _inherit = 'clouder.certificate.abstract' - _description = 'Clouder Key Abstract' - - strength = fields.Integer( - default=4096, - ) - algorithm = fields.Selection( - default='rsa', - selection=lambda s: s._get_algorithms(), - ) - is_private = fields.Boolean() - active = fields.Boolean( - default=True, - ) - - @api.model - def _get_algorithms(self): - return [ - ('rsa', 'RSA'), - ('ecdsa', 'ECDSA'), - ] diff --git a/requirements.txt b/requirements.txt index d8f249a..978f55e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ apache-libcloud -cfssl +cfssl==0.0.2b215 cryptography erppeek paramiko