Merge "Add fixups configuration / processing"

This commit is contained in:
Jenkins 2015-09-19 11:50:03 +00:00 committed by Gerrit Code Review
commit af129c1340
11 changed files with 212 additions and 2 deletions

View File

@ -235,6 +235,14 @@ Sample configuration for the default backend:
For more information, please refer to the documentation.
Fixups
======
Anchor can modify the submitted CSRs in order to enforce some rules, remove
deprecated elements, or just add information. Submitted CSR may be modified or
entirely redone. Fixup are loaded from "anchor.fixups" namespace and can take
parameters just like validators.
Reporting bugs and contributing
===============================

View File

@ -172,6 +172,18 @@ def validate_registration_authority_config(ra_name, conf):
config_check_domains(ra_validators)
logger.info("Validators OK for registration authority: %s", ra_name)
ra_fixups = ra_conf.get('fixups', {})
for step in ra_fixups.keys():
try:
jsonloader.conf.get_fixup(step)
except KeyError:
raise ConfigValidationException(
"Unknown fixup <{}> found (for registration "
"authority {})".format(step, ra_name))
logger.info("Fixups OK for registration authority: %s", ra_name)
def load_config():
"""Attempt to find and load a JSON configuration file.

View File

@ -176,6 +176,63 @@ def dispatch_sign(ra_name, csr):
return cert_pem
def _run_fixup(name, body, args):
"""Parse the fixup tuple, call the fixup, and return the new csr.
:param name: the fixup name
:param body: fixup body, directly from config
:param args: additional arguments to pass to the fixup function
:return: the fixed csr
"""
# careful to not modify the master copy of args with local params
new_kwargs = args.copy()
new_kwargs.update(body)
# perform the actual check
logger.debug("_run_fixup: fixup <%s> with arguments: %s", name, body)
try:
fixup = jsonloader.conf.get_fixup(name)
new_csr = fixup(**new_kwargs)
logger.debug("_run_fixup: success: <%s> ", name)
return new_csr
except Exception:
logger.exception("_run_fixup: FAILED: <%s>", name)
return None
def fixup_csr(ra_name, csr, request):
"""Apply configured changes to the certificate.
:param ra_name: registration authority name
:param csr: X509 certificate signing request
:param request: pecan request
"""
ra_conf = jsonloader.config_for_registration_authority(ra_name)
args = {'csr': csr,
'conf': ra_conf,
'request': request}
fixups = ra_conf.get('fixups', {})
try:
for fixup_name, fixup in fixups.items():
new_csr = _run_fixup(fixup_name, fixup, args)
if new_csr is None:
pecan.abort(500, "Could not finish all required modifications")
if not isinstance(new_csr, signing_request.X509Csr):
logger.error("Fixup %s returned incorrect object", fixup_name)
pecan.abort(500, "Could not finish all required modifications")
args['csr'] = new_csr
except http_status.HTTPInternalServerError:
raise
except Exception:
logger.exception("Failed to execute fixups")
pecan.abort(500, "Could not finish all required modifications")
return args['csr']
def sign(csr, ca_conf):
"""Generate an X.509 certificate and sign it.

View File

@ -52,6 +52,7 @@ class SignInstanceController(GenericInstanceController):
csr = certificate_ops.parse_csr(pecan.request.POST.get('csr'),
pecan.request.POST.get('encoding'))
certificate_ops.validate_csr(ra_name, auth_result, csr, pecan.request)
csr = certificate_ops.fixup_csr(ra_name, csr, pecan.request)
return certificate_ops.dispatch_sign(ra_name, csr)

View File

@ -63,6 +63,7 @@ class AnchorConf():
self._validators = stevedore.ExtensionManager("anchor.validators")
self._authentication = stevedore.ExtensionManager(
"anchor.authentication")
self._fixups = stevedore.ExtensionManager("anchor.fixups")
def get_signing_backend(self, name):
return self._signing_backends[name].plugin
@ -73,6 +74,9 @@ class AnchorConf():
def get_authentication(self, name):
return self._authentication[name].plugin
def get_fixup(self, name):
return self._fixups[name].plugin
@property
def config(self):
'''Property to return the config dictionary

View File

@ -151,6 +151,8 @@ and the list of validators applied to each request.
"source_cidrs": {
"cidrs": [ "127.0.0.0/8" ]
}
},
"fixups": {
}
}
}
@ -162,7 +164,8 @@ against two validators (``ca_status`` and ``source_cidrs``) and if they pass,
the CSR will be signed by the previously defined signing ca called ``local``.
Each validator has its own set of parameters described separately in the
:doc:`validators section </validators>`.
:doc:`validators section </validators>`. Same for fixups described in
:doc:`fixups section </fixups>`
Example configuration
@ -200,6 +203,8 @@ Example configuration
"source_cidrs": {
"cidrs": [ "127.0.0.0/8" ]
}
},
"fixups": {
}
}
}

10
doc/source/fixups.rst Normal file
View File

@ -0,0 +1,10 @@
Fixups
======
Fixups can be used to modify submitted CSRs before sigining. That means for
example adding extra name elements, or extensions. Each fixup is loaded from
the "anchor.fixups" namespace using stevedore and gets access to the parsed CSR
and the configuration.
Unlike validators, each fixup has to return either a new CSR structure or the
modified original.

View File

@ -17,6 +17,7 @@ Contents:
signing_backends
ephemeralPKI
validators
fixups
Indices and tables

View File

@ -55,11 +55,14 @@ class DefaultConfigMixin(object):
"allowed_domains": [".test.com"]
}
}
self.sample_conf_fixups = {
}
self.sample_conf_ra = {
"default_ra": {
"authentication": "default_auth",
"signing_ca": "default_ca",
"validators": self.sample_conf_validators
"validators": self.sample_conf_validators,
"fixups": self.sample_conf_fixups,
}
}
self.sample_conf = {

0
tests/fixups/__init__.py Normal file
View File

View File

@ -0,0 +1,109 @@
# -*- coding:utf-8 -*-
#
# Copyright 2014 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import textwrap
import unittest
import mock
import webob
from anchor import certificate_ops
from anchor import jsonloader
from anchor.X509 import signing_request
import tests
class TestFixupFunctionality(tests.DefaultConfigMixin, unittest.TestCase):
csr_data_with_cn = textwrap.dedent(u"""
-----BEGIN CERTIFICATE REQUEST-----
MIIDBTCCAe0CAQAwgb8xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlh
MRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMSEwHwYDVQQKExhPcGVuU3RhY2sgU2Vj
dXJpdHkgR3JvdXAxETAPBgNVBAsTCFNlY3VyaXR5MRYwFAYDVQQDEw1vc3NnLnRl
c3QuY29tMTUwMwYJKoZIhvcNAQkBFiZvcGVuc3RhY2stc2VjdXJpdHlAbGlzdHMu
b3BlbnN0YWNrLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJCw
hIh3kwHGrGff7bHpY0x7ebXS8CfnwDx/wFSqlBeARL9f4riN172P4hkk7F+QQ2R9
88osQX4dmbQZDX18y85TTQv9jmtzvTZtJM2UQ80XMIVLZjpK5966cmJKqn/s+IaL
zh+kqyb7S6xV0590VarEFZ6JsXdxU9TtVHOWCfn/P8swr5DCTzsE/LUIuVdqgkGh
g63E9iLYtAOUcQv6lpmrI8NHOMK2F7XnP64IEshpZ4POzc7m8nTEHHb0+xxxiive
mwLTp6pyZ5wBx/Dvk2Dc7SF6x51wOxAxdWc3vxwA5Q2nbFK2RlBHCiIi+ZK3i5S/
tOkcQydQ0Cl9escDrv0CAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQA1dpxxTGFF
TGFenVJlT2uecvXK4UePeaslRx2P1k3xwJK9ZEvKY297cqhK5Y8kWyzNUjGFLHPr
RlgjFMYlUICNgCdcWD2b0avZ9q648+F3b9CWKg0kNMhxyQpXdSeLZOzpDVUyr6TN
GcCZqcQQclixruXsIGQoZFIXazGju2UTtxwK/J87u2S0yR2bR48dPlNXAWKV+e4o
Ua0RaDUUBypZNMMbY6KSB6C7oXGzA/WOnvNz9PzhXlqgWhOv5M6iG3sYDtKllXJT
7lcLhUzNVdWaPveTqX/V8QX//53IkyNa+IBm+H84UE5M0GFunqFBYqrWw8S46tMQ
JQxgjf65ujnn
-----END CERTIFICATE REQUEST-----""")
"""
Subject:
C=US, ST=California, L=San Francisco,
O=OpenStack Security Group, OU=Security,
CN=ossg.test.com/emailAddress=openstack-security@lists.openstack.org
"""
def setUp(self):
super(TestFixupFunctionality, self).setUp()
jsonloader.conf.load_extensions()
self.csr = signing_request.X509Csr.from_buffer(
TestFixupFunctionality.csr_data_with_cn)
def test_with_noop(self):
"""Ensure single fixup is processed."""
self.sample_conf_ra['default_ra']['fixups'] = {'noop': {}}
data = self.sample_conf
config = "anchor.jsonloader.conf._config"
mock_noop = mock.MagicMock()
mock_noop.name = "noop"
mock_noop.plugin.return_value = self.csr
jsonloader.conf._fixups = jsonloader.conf._fixups.make_test_instance(
[mock_noop], 'anchor.fixups')
with mock.patch.dict(config, data):
certificate_ops.fixup_csr('default_ra', self.csr, None)
mock_noop.plugin.assert_called_with(
csr=self.csr, conf=self.sample_conf_ra['default_ra'], request=None)
def test_with_no_fixups(self):
"""Ensure no fixups is ok."""
self.sample_conf_ra['default_ra']['fixups'] = {}
data = self.sample_conf
config = "anchor.jsonloader.conf._config"
with mock.patch.dict(config, data):
res = certificate_ops.fixup_csr('default_ra', self.csr, None)
self.assertIs(res, self.csr)
def test_with_broken_fixup(self):
"""Ensure broken fixups stop processing."""
self.sample_conf_ra['default_ra']['fixups'] = {'broken': {}}
data = self.sample_conf
config = "anchor.jsonloader.conf._config"
mock_noop = mock.MagicMock()
mock_noop.name = "broken"
mock_noop.plugin.side_effects = Exception("BOOM")
jsonloader.conf._fixups = jsonloader.conf._fixups.make_test_instance(
[mock_noop], 'anchor.fixups')
with mock.patch.dict(config, data):
with self.assertRaises(webob.exc.WSGIHTTPException):
certificate_ops.fixup_csr('default_ra', self.csr, None)