Skip to content

Commit d50dd5b

Browse files
author
Matthew Fisher
committed
feat(controller): support SAN certificates
This feature allows a user to specify a list of SubjectAltNames (SANs) for a specified certificate. This has the effect of creating multiple certificate entries in the database (one for each SAN), but it gives the user the benefit to revoke the certficate for each custom domain. By default, SANs are **not** added as new entries with `certs:add`. The user will need to explicitly give each subject alt name they wish to add as a certificate. Usage: ``` $ deis certs:add ~/.openssl/star.fishworks.io.cert ~/.openssl/private.key.nopass --subject-alt-name foo.fishworks.io --subject-alt-name bar.fishworks.io Adding SSL endpoint... done *.fishworks.io Adding SSL endpoint foo.fishworks.io...done Adding SSL endpoint bar.fishworks.io...done $ deis certs Common Name Expires ---------------- ---------------------- *.fishworks.io 2015-08-29T08:17:48UTC foo.fishworks.io 2015-08-29T08:17:48UTC bar.fishworks.io 2015-08-29T08:17:48UTC ``` In order for users to update their certificate, they will need to run `deis certs:remove` for each entry, then re-run `deis certs:add`. Docopt does not respect ellipsis when '[options]' is present, so I had to explicitly write out the option in the usage in order for the flag to work. Additionally, a new --common-name flag has also been introduced. This is a temporary workaround for users to add their wildcard certificates to their custom domain endpoints. This acts the same way where a database entry is created for each call to `certs:add`. If users want to update their wildcard certificate, they'll have to update each entry they've added. This is not the optimal solution, but it provides a way for us to support wildcard certificates for custom domains. Usage: ``` $ deis certs:add ~/.openssl/star.fishworks.io.cert ~/.openssl/private.key.nopass --common-name foo.fishworks.io Adding SSL endpoint foo.fishworks.io...done $ deis certs Common Name Expires ---------------- ---------------------- foo.fishworks.io 2015-08-29T08:17:48UTC ```
1 parent e8ec005 commit d50dd5b

4 files changed

Lines changed: 47 additions & 5 deletions

File tree

client/deis.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,18 +1043,35 @@ def certs_add(self, args):
10431043
"""
10441044
Binds a certificate/key pair to an application.
10451045
1046-
Usage: deis certs:add <cert> <key>
1046+
Usage: deis certs:add <cert> <key> [--subject-alt-name=<san>...] [options]
10471047
10481048
Arguments:
10491049
<cert>
10501050
The public key of the SSL certificate.
10511051
<key>
10521052
The private key of the SSL certificate.
1053+
1054+
Options:
1055+
--common-name=<cname>
1056+
The common name of the certificate. If none is provided, the controller will
1057+
interpret the common name from the certificate.
1058+
--subject-alt-name=<san>...
1059+
The subject alternate names (SAN) of the certificate. This will create multiple
1060+
Certificate objects in the controller, one for each SAN.
10531061
"""
10541062
cert = args.get('<cert>')
10551063
key = args.get('<key>')
1064+
self._certs_add(cert, key, args.get('--common-name'))
1065+
for san in args.get('--subject-alt-name'):
1066+
self._certs_add(cert, key, san)
1067+
1068+
def _certs_add(self, cert, key, common_name=None):
10561069
body = {'certificate': file(cert).read().strip(), 'key': file(key).read().strip()}
1057-
sys.stdout.write("Adding SSL endpoint... ")
1070+
if common_name:
1071+
body['common_name'] = common_name
1072+
sys.stdout.write("Adding SSL endpoint {}...".format(common_name))
1073+
else:
1074+
sys.stdout.write("Adding SSL endpoint... ")
10581075
sys.stdout.flush()
10591076
try:
10601077
progress = TextProgress()
@@ -1066,7 +1083,8 @@ def certs_add(self, args):
10661083
if response.status_code == requests.codes.created:
10671084
self._logger.info("done")
10681085
data = response.json()
1069-
self._logger.info("{common_name}".format(**data))
1086+
if not common_name:
1087+
self._logger.info("{common_name}".format(**data))
10701088
else:
10711089
raise ResponseError(response)
10721090

controller/api/serializers.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,8 +275,9 @@ class Meta:
275275
"""Metadata options for a DomainCertSerializer."""
276276
model = models.Certificate
277277
extra_kwargs = {'certificate': {'write_only': True},
278-
'key': {'write_only': True}}
279-
read_only_fields = ['common_name', 'expires', 'created', 'updated']
278+
'key': {'write_only': True},
279+
'common_name': {'required': False}}
280+
read_only_fields = ['expires', 'created', 'updated']
280281

281282

282283
class PushSerializer(ModelSerializer):

controller/api/tests/test_certificate.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,20 @@ def test_create_certificate_with_domain(self):
8080
HTTP_AUTHORIZATION='token {}'.format(self.token))
8181
self.assertEqual(response.status_code, 201)
8282

83+
def test_create_certificate_with_different_common_name(self):
84+
"""
85+
In some cases such as with SAN certificates, the certificate can cover more
86+
than a single domain. In that case, we want to be able to specify the common
87+
name for the certificate/key.
88+
"""
89+
body = {'certificate': self.autotest_example_com_cert,
90+
'key': self.key,
91+
'common_name': 'foo.example.com'}
92+
response = self.client.post(self.url, json.dumps(body), content_type='application/json',
93+
HTTP_AUTHORIZATION='token {}'.format(self.token))
94+
self.assertEqual(response.status_code, 201)
95+
self.assertEqual(response.data['common_name'], 'foo.example.com')
96+
8397
def test_get_certificate_screens_data(self):
8498
"""
8599
When a user retrieves a certificate, only the common name and expiry date should be

docs/reference/api-v1.3.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ What's New
1414

1515
**New!** ``/users`` endpoint for listing users
1616
**New!** ``/logs`` endpoint now has a option for limiting the number of log lines returned
17+
**New!** ``/certs`` endpoint now has an optional parameter to set the common name for a certificate
1718

1819

1920
Authentication
@@ -385,6 +386,14 @@ Example Request:
385386
"key": "-----BEGIN RSA PRIVATE KEY-----"
386387
}
387388
389+
Optional Parameters:
390+
391+
.. code-block:: console
392+
393+
{
394+
"common_name": "test.example.com"
395+
}
396+
388397
389398
Example Response:
390399

0 commit comments

Comments
 (0)