anchor/tests/test_functional.py
Stanisław Pitucha f1ed12e2cf Implement new API format
Signing requests are expected to arrive at

/v1/sign/<registration_authority>

now. Virtual registration authority is a new concept which right now includes
everything the original configuration included. That means for example each
registration authority available within Anchor deployment can configure its own
CA, auth, and validators. Clients request a specific registration authority via
the URL. This does not mean they can just choose who signs the CSR - they still
need to pass all checks. Only the guesswork of "which validation set applies to
them" is gone because of this change.

The previous concept of validator sets is gone. Each registration authority
configures its own validators and all of them need to pass.

Previous endpoint /sign will not work anymore. It's incompatible with
the new design.

The configuration file changes in the following way:
1. Registration authorities need to be defined in the main config.
2. Validator sets are not available anymore.
3. CA and auth settings at the top level need to be named. They can be referred
   to in the registration authority block.
4. Old names are removed. Any use of "auth", "ca", or "validators" at the top
   level will result in an error and explanation regarding the upgrade path.

Further documentation and a sample config with the new layout can be found in
the docs/configuration.rst file.

Closes-bug: 1463752
Change-Id: I5a949e0c79a2d56eadadf5ece62bb8b8eea89e78
2015-08-20 11:32:28 +10:00

168 lines
6.8 KiB
Python

# -*- 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 copy
import json
import os
import stat
import tempfile
import textwrap
import unittest
import pecan
from pecan import testing as pecan_testing
from anchor import jsonloader
from anchor import validators
from anchor.X509 import certificate as X509_cert
import config
import tests
class TestFunctional(tests.DefaultConfigMixin, unittest.TestCase):
csr_good = textwrap.dedent(u"""
-----BEGIN CERTIFICATE REQUEST-----
MIIEDzCCAncCAQAwcjELMAkGA1UEBhMCR0IxEzARBgNVBAgTCkNhbGlmb3JuaWEx
FjAUBgNVBAcTDVNhbiBGcmFuY3NpY28xDTALBgNVBAoTBE9TU0cxDTALBgNVBAsT
BE9TU0cxGDAWBgNVBAMTD21hc3Rlci50ZXN0LmNvbTCCAaIwDQYJKoZIhvcNAQEB
BQADggGPADCCAYoCggGBALnhCRvwMoaZa4car663lwcwn86PO3BS90X8b2wIZjkf
rq/eePz2J3Ox8/BbsYiYICHn8oSd/VVXUnqHMFU9xTeJwsDLbyc+0P4S9Fj+RkbM
W+YQZsG8Wy9M8aKi9hNtIGiqknyzcOfCQcGPpcKqXRXAW1afqLmifBcFqN1qcpT8
OooGNtgo4Ix/fA7omZaKkIXSi5FovC8mFPUm2VqDyvctxBGq0EngIOB9rczloun0
nO8PpWBsX2rg3uIs6GIejVrx1ZkcHxJbrze/Nt9vt4C11hJAiAUlHDl0cf50/Pck
g0T3ehEqr0zdzCx+wXr3AzStcoOow+REb8CbTt2QaUbZ5izrZFX0JC73mRtqDhuc
UxUaguLK9ufhUfA0I1j++w/pQkBEu5PGNX7YpRLImEp636lD8RJ9Ced7oii+gjY0
OXlVPRv9MMPvkCWnjNjLapz8kzypJr94BQz1AffHxVfmGGQh60vq4KINm+etuI0Q
kfI9NRa/ficRhsuh7yxQRwIDAQABoFgwVgYJKoZIhvcNAQkOMUkwRzAJBgNVHRME
AjAAMAsGA1UdDwQEAwIF4DAtBgNVHREEJjAkghBzZXJ2ZXIxLnRlc3QuY29tghBz
ZXJ2ZXIyLnRlc3QuY29tMA0GCSqGSIb3DQEBCwUAA4IBgQBdyATuNnfVIeQL2odc
zV7f9c/tvN5/Mn4AmGt5S457FGO/s3J7hWX9L02VYPWwORbtkBvZZKtQWLjHbMzU
oGsfxeo6vUv+dSP6bjqKibFyMArdaRIobFMvM/5N6g9zcP4sQEnpUyIeV2g6b0Os
FoKGsLPIMiS69mAVdfKrgXnmXApXu5zjAoPnSzcc+wKTCbzVIRLZIopEtet84atN
7Tf9xokgrDZppJE76w3zXYWPkUDbVuWTuO4afQxujHbJYiZblxJz/gRbMgugAt4V
ftlI3EGnGaBQHcZfmyZz1F8ti1jteWMMQZHtWr32cF9Lw/jd2adYFYVTez3BXtQW
pULCxdq8G2CFdrV/atIL8Vadf2dOzn2tZIFFihzuilWbcmTP7+8UI8MOKkrqfWN+
Q6yV3I896rSprU7WAmWSq+jXkOOwNGDEbmaWsxu4AjvfGty5v2lZqdYJRkbjerXD
tR7XqQGqJKca/vRTfJ+zIAxMEeH1N9Lx7YBO6VdVja+yG1E=
-----END CERTIFICATE REQUEST-----""")
csr_bad = textwrap.dedent(u"""
-----BEGIN CERTIFICATE REQUEST-----
MIIBWTCCARMCAQAwgZQxCzAJBgNVBAYTAlVLMQ8wDQYDVQQIEwZOYXJuaWExEjAQ
BgNVBAcTCUZ1bmt5dG93bjEXMBUGA1UEChMOQW5jaG9yIFRlc3RpbmcxEDAOBgNV
BAsTB3Rlc3RpbmcxFDASBgNVBAMTC2FuY2hvci50ZXN0MR8wHQYJKoZIhvcNAQkB
FhB0ZXN0QGFuY2hvci50ZXN0MEwwDQYJKoZIhvcNAQEBBQADOwAwOAIxAOpvxkCx
NNTc86GVnP4rWvaniOnHaemXbhBOoFxhMwaghiq7u5V9ZKkUZfbu+L+ZSQIDAQAB
oCkwJwYJKoZIhvcNAQkOMRowGDAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DANBgkq
hkiG9w0BAQUFAAMxALaK8/HR73ZSvHiWo7Mduin0S519aJBm+gO8d9iliUkK00gQ
VMs9DuTAxljX7t7Eug==
-----END CERTIFICATE REQUEST-----""")
def setUp(self):
super(TestFunctional, self).setUp()
# Load config from json test config
jsonloader.conf.load_str_data(json.dumps(self.sample_conf))
self.conf = jsonloader.conf._config
ca_conf = self.conf["signing_ca"]["default_ca"]
ca_conf["output_path"] = tempfile.mkdtemp()
# Set CA file permissions
os.chmod(ca_conf["cert_path"], stat.S_IRUSR | stat.S_IFREG)
os.chmod(ca_conf["key_path"], stat.S_IRUSR | stat.S_IFREG)
app_conf = {"app": copy.deepcopy(config.app),
"logging": copy.deepcopy(config.logging)}
self.app = pecan_testing.load_test_app(app_conf)
def tearDown(self):
pecan.set_config({}, overwrite=True)
self.app.reset()
def test_check_unauthorised(self):
resp = self.app.post('/v1/sign/default_ra', expect_errors=True)
self.assertEqual(401, resp.status_int)
def test_robots(self):
resp = self.app.get('/robots.txt')
self.assertEqual(200, resp.status_int)
self.assertEqual("User-agent: *\nDisallow: /\n", resp.text)
def test_check_missing_csr(self):
data = {'user': 'myusername',
'secret': 'simplepassword',
'encoding': 'pem'}
resp = self.app.post('/v1/sign/default_ra', data, expect_errors=True)
self.assertEqual(400, resp.status_int)
def test_check_unknown_instance(self):
data = {'user': 'myusername',
'secret': 'simplepassword',
'encoding': 'pem',
'csr': TestFunctional.csr_good}
resp = self.app.post('/v1/sign/unknown', data, expect_errors=True)
self.assertEqual(404, resp.status_int)
def test_check_bad_csr(self):
data = {'user': 'myusername',
'secret': 'simplepassword',
'encoding': 'pem',
'csr': TestFunctional.csr_bad}
resp = self.app.post('/v1/sign/default_ra', data, expect_errors=True)
self.assertEqual(400, resp.status_int)
def test_check_good_csr(self):
data = {'user': 'myusername',
'secret': 'simplepassword',
'encoding': 'pem',
'csr': TestFunctional.csr_good}
resp = self.app.post('/v1/sign/default_ra', data, expect_errors=False)
self.assertEqual(200, resp.status_int)
cert = X509_cert.X509Certificate.from_buffer(resp.text)
# make sure the cert is what we asked for
self.assertEqual(("/C=GB/ST=California/L=San Francsico/O=OSSG"
"/OU=OSSG/CN=master.test.com"),
str(cert.get_subject()))
# make sure the cert was issued by anchor
self.assertEqual("/C=UK/ST=Some-State/O=OSSG/CN=anchor.example.com",
str(cert.get_issuer()))
def test_check_broken_validator(self):
data = {'user': 'myusername',
'secret': 'simplepassword',
'encoding': 'pem',
'csr': TestFunctional.csr_good}
def derp(**kwdargs):
raise Exception("BOOM")
validators.broken_validator = derp
ra = jsonloader.conf.registration_authority['default_ra']
ra['validators']["broken_validator"] = {}
resp = self.app.post('/v1/sign/default_ra', data, expect_errors=True)
self.assertEqual(500, resp.status_int)
self.assertTrue(("Internal Validation Error running validator "
"'broken_validator' for registration authority "
"'default_ra'") in str(resp))