blueprint 2-way-ssl

Implemented bp/2-way-ssl using eventlet-based SSL.

Change-Id: I5aeb622aded13b406e01c78a2d8c245543306180
This commit is contained in:
Liem Nguyen 2012-05-23 18:05:11 +00:00
parent 33d107aa1d
commit f537a8259b
17 changed files with 442 additions and 31 deletions

View File

@ -15,5 +15,6 @@ graft bin
graft doc graft doc
graft tests graft tests
graft tools graft tools
recursive-include keystone *.json *.xml *.cfg README graft examples
recursive-include keystone *.json *.xml *.cfg *.pem README
global-exclude *.pyc *.sdx *.log *.db *.swp global-exclude *.pyc *.sdx *.log *.db *.swp

View File

@ -28,7 +28,11 @@ CONF = config.CONF
def create_server(conf, name, host, port): def create_server(conf, name, host, port):
app = deploy.loadapp('config:%s' % conf, name=name) app = deploy.loadapp('config:%s' % conf, name=name)
return wsgi.Server(app, host=host, port=port) server = wsgi.Server(app, host=host, port=port)
if CONF.ssl.enable:
server.set_ssl(CONF.ssl.certfile, CONF.ssl.keyfile,
CONF.ssl.ca_certs, CONF.ssl.cert_required)
return server
def serve(*servers): def serve(*servers):

View File

@ -60,6 +60,7 @@ values are organized into the following sections:
* ``[catalog]`` - service catalog driver configuration * ``[catalog]`` - service catalog driver configuration
* ``[token]`` - token driver configuration * ``[token]`` - token driver configuration
* ``[policy]`` - policy system driver configuration for RBAC * ``[policy]`` - policy system driver configuration for RBAC
* ``[ssl]`` - SSL configuration
The Keystone configuration file is expected to be named ``keystone.conf``. The Keystone configuration file is expected to be named ``keystone.conf``.
When starting keystone, you can specify a different configuration file to When starting keystone, you can specify a different configuration file to
@ -149,6 +150,58 @@ choosing the output levels and formats.
.. _Paste: http://pythonpaste.org/ .. _Paste: http://pythonpaste.org/
.. _`python logging module`: http://docs.python.org/library/logging.html .. _`python logging module`: http://docs.python.org/library/logging.html
SSL
---
Keystone may be configured to support 2-way SSL out-of-the-box. The x509
certificates used by Keystone must be obtained externally and configured for use
with Keystone as described in this section. However, a set of sample certficates
is provided in the examples/ssl directory with the Keystone distribution for testing.
Here is the description of each of them and their purpose:
Types of certificates
^^^^^^^^^^^^^^^^^^^^^
ca.pem
Certificate Authority chain to validate against.
keystone.pem
Public certificate for Keystone server.
middleware.pem
Public and private certificate for Keystone middleware/client.
cakey.pem
Private key for the CA.
keystonekey.pem
Private key for the Keystone server.
Note that you may choose whatever names you want for these certificates, or combine
the public/private keys in the same file if you wish. These certificates are just
provided as an example.
Configuration
^^^^^^^^^^^^^
To enable SSL with client authentication, modify the etc/keystone.conf file accordingly
under the [ssl] section. SSL configuration example using the included sample
certificates::
[ssl]
enable = True
certfile = <path to keystone.pem>
keyfile = <path to keystonekey.pem>
ca_certs = <path to ca.pem>
cert_required = True
* ``enable``: True enables SSL. Defaults to False.
* ``certfile``: Path to Keystone public certificate file.
* ``keyfile``: Path to Keystone private certificate file. If the private key is included in the certfile, the keyfile maybe omitted.
* ``ca_certs``: Path to CA trust chain.
* ``cert_required``: Requires client certificate. Defaults to False.
Sample Configuration Files Sample Configuration Files
-------------------------- --------------------------

View File

@ -133,6 +133,9 @@ a WSGI component. Example for the auth_token middleware::
admin_tenant_name = service admin_tenant_name = service
;Uncomment next line and check ip:port to use memcached to cache tokens ;Uncomment next line and check ip:port to use memcached to cache tokens
;memcache_servers = 127.0.0.1:11211 ;memcache_servers = 127.0.0.1:11211
;Uncomment next 2 lines if Keystone server is validating client cert
certfile = <path to middleware public cert>
keyfile = <path to middleware private cert>
Configuration Options Configuration Options
--------------------- ---------------------
@ -153,6 +156,9 @@ Configuration Options
* ``auth_port``: (optional, default `35357`) the port used to validate tokens * ``auth_port``: (optional, default `35357`) the port used to validate tokens
* ``auth_protocol``: (optional, default `https`) * ``auth_protocol``: (optional, default `https`)
* ``auth_uri``: (optional, defaults to `auth_protocol`://`auth_host`:`auth_port`) * ``auth_uri``: (optional, defaults to `auth_protocol`://`auth_host`:`auth_port`)
* ``certfile``: (required, if Keystone server requires client cert)
* ``keyfile``: (required, if Keystone server requires client cert) This can be
the same as the certfile if the certfile includes the private key.
Caching for improved response Caching for improved response
----------------------------- -----------------------------

View File

@ -81,6 +81,14 @@
[ec2] [ec2]
# driver = keystone.contrib.ec2.backends.kvs.Ec2 # driver = keystone.contrib.ec2.backends.kvs.Ec2
[ssl]
#enable = True
#certfile = /etc/keystone/ssl/certs/keystone.pem
#keyfile = /etc/keystone/ssl/private/keystonekey.pem
#ca_certs = /etc/keystone/ssl/certs/ca.pem
#cert_required = True
[ldap] [ldap]
# url = ldap://localhost # url = ldap://localhost
# user = dc=Manager,dc=example,dc=com # user = dc=Manager,dc=example,dc=com

22
examples/ssl/certs/ca.pem Normal file
View File

@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDmTCCAwKgAwIBAgIJALMGu1g0q5GjMA0GCSqGSIb3DQEBBQUAMIGQMQswCQYD
VQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVJvc2V2aWxsZTESMBAGA1UE
ChMJT3BlbnN0YWNrMREwDwYDVQQLEwhLZXlzdG9uZTESMBAGA1UEAxMJbG9jYWxo
b3N0MSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBvcGVuc3RhY2sub3JnMB4XDTEx
MTAyMDE2MDQ0MloXDTIxMTAxNzE2MDQ0MlowgZAxCzAJBgNVBAYTAlVTMQswCQYD
VQQIEwJDQTESMBAGA1UEBxMJUm9zZXZpbGxlMRIwEAYDVQQKEwlPcGVuc3RhY2sx
ETAPBgNVBAsTCEtleXN0b25lMRIwEAYDVQQDEwlsb2NhbGhvc3QxJTAjBgkqhkiG
9w0BCQEWFmtleXN0b25lQG9wZW5zdGFjay5vcmcwgZ8wDQYJKoZIhvcNAQEBBQAD
gY0AMIGJAoGBAMfYcS0Fs7DRqdGSMVyrLk91vdzs+K6a6NOgppxhETqrOMAjW5yL
ajE2Ly48qfO/BRZR0kgTGSpnv7oiFzWLCvPf63nUnCalkE+uBpksY7BpphnTCJ8F
IsZ6aggAGKto9mmADpiKxt1uSQ6DDpPm8quXbMdSZTFOOVQNPYhwPMYvAgMBAAGj
gfgwgfUwHQYDVR0OBBYEFGA/MhYYUnjIdH9FWFVVo/YODkZBMIHFBgNVHSMEgb0w
gbqAFGA/MhYYUnjIdH9FWFVVo/YODkZBoYGWpIGTMIGQMQswCQYDVQQGEwJVUzEL
MAkGA1UECBMCQ0ExEjAQBgNVBAcTCVJvc2V2aWxsZTESMBAGA1UEChMJT3BlbnN0
YWNrMREwDwYDVQQLEwhLZXlzdG9uZTESMBAGA1UEAxMJbG9jYWxob3N0MSUwIwYJ
KoZIhvcNAQkBFhZrZXlzdG9uZUBvcGVuc3RhY2sub3JnggkAswa7WDSrkaMwDAYD
VR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQBoeuR/pRznAtStj4Axe8Xq1ivL
jXFt2G9Pj+MwLs2wokcUBYz6/rJdSTjW21s4/FQCHiw9K7HA63c4mbjkRRgtJlXo
F5PiQqv4F1KqZmWeIDGxOGStQbgc77unsYYXILI27pSqQLKc9xlli77LekY+BzTK
tr5JYtKMaby4lJTg3A==
-----END CERTIFICATE-----

View File

@ -0,0 +1,62 @@
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 1 (0x1)
Signature Algorithm: sha1WithRSAEncryption
Issuer: C=US, ST=CA, L=Roseville, O=Openstack, OU=Keystone, CN=localhost/emailAddress=keystone@openstack.org
Validity
Not Before: Oct 20 16:34:17 2011 GMT
Not After : Oct 19 16:34:17 2012 GMT
Subject: C=US, ST=CA, L=Roseville, O=Openstack, OU=Keystone, CN=localhost/emailAddress=keystone@openstack.org
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public Key: (1024 bit)
Modulus (1024 bit):
00:9e:5a:5c:be:dc:20:d4:af:36:5c:33:6d:72:44:
94:59:c6:a9:24:ed:fa:8b:2c:53:ab:24:7d:79:46:
cc:a6:45:05:b0:57:b4:0d:d6:8f:f4:d9:a5:11:64:
e4:78:b1:26:30:de:fb:4a:72:c8:97:e7:31:4f:55:
bb:5b:16:d7:22:1b:13:ca:fc:6b:04:bd:15:9c:09:
51:d6:f9:14:51:67:a3:42:4a:81:ce:98:0f:6e:5c:
ac:7f:36:be:0f:79:ad:07:81:75:a2:21:a8:5f:e5:
9c:22:71:4c:db:63:b6:44:29:65:22:76:6e:07:98:
de:be:58:3f:b2:fe:cd:27:f7
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
Netscape Comment:
OpenSSL Generated Certificate
X509v3 Subject Key Identifier:
C1:E3:A1:36:45:3F:B5:3B:11:A1:23:A4:7E:3A:A0:F9:BC:F6:93:A3
X509v3 Authority Key Identifier:
keyid:60:3F:32:16:18:52:78:C8:74:7F:45:58:55:55:A3:F6:0E:0E:46:41
Signature Algorithm: sha1WithRSAEncryption
06:86:d7:5d:93:11:94:ce:23:ae:74:b2:16:09:99:32:63:3d:
d9:be:8f:99:87:43:7c:0d:27:25:5c:08:c2:d6:18:37:3c:4e:
b9:06:51:53:a9:d7:93:da:14:a1:25:96:2b:eb:8d:81:9d:68:
8d:ec:b8:1f:9e:09:80:25:fb:be:f8:20:5b:fc:ca:6c:3d:38:
c7:09:36:aa:dd:f8:0c:01:35:3e:c5:c5:3b:60:24:8c:5f:c5:
44:e7:7f:9b:ce:b6:d5:85:b7:93:e4:8a:a5:a9:90:ff:2d:09:
56:8c:e6:17:1f:07:33:0a:46:73:b1:65:13:d8:6f:39:76:3a:
93:87
-----BEGIN CERTIFICATE-----
MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQUFADCBkDELMAkGA1UEBhMCVVMx
CzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlSb3NldmlsbGUxEjAQBgNVBAoTCU9wZW5z
dGFjazERMA8GA1UECxMIS2V5c3RvbmUxEjAQBgNVBAMTCWxvY2FsaG9zdDElMCMG
CSqGSIb3DQEJARYWa2V5c3RvbmVAb3BlbnN0YWNrLm9yZzAeFw0xMTEwMjAxNjM0
MTdaFw0xMjEwMTkxNjM0MTdaMIGQMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0Ex
EjAQBgNVBAcTCVJvc2V2aWxsZTESMBAGA1UEChMJT3BlbnN0YWNrMREwDwYDVQQL
EwhLZXlzdG9uZTESMBAGA1UEAxMJbG9jYWxob3N0MSUwIwYJKoZIhvcNAQkBFhZr
ZXlzdG9uZUBvcGVuc3RhY2sub3JnMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
gQCeWly+3CDUrzZcM21yRJRZxqkk7fqLLFOrJH15RsymRQWwV7QN1o/02aURZOR4
sSYw3vtKcsiX5zFPVbtbFtciGxPK/GsEvRWcCVHW+RRRZ6NCSoHOmA9uXKx/Nr4P
ea0HgXWiIahf5ZwicUzbY7ZEKWUidm4HmN6+WD+y/s0n9wIDAQABo3sweTAJBgNV
HRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZp
Y2F0ZTAdBgNVHQ4EFgQUweOhNkU/tTsRoSOkfjqg+bz2k6MwHwYDVR0jBBgwFoAU
YD8yFhhSeMh0f0VYVVWj9g4ORkEwDQYJKoZIhvcNAQEFBQADgYEABobXXZMRlM4j
rnSyFgmZMmM92b6PmYdDfA0nJVwIwtYYNzxOuQZRU6nXk9oUoSWWK+uNgZ1ojey4
H54JgCX7vvggW/zKbD04xwk2qt34DAE1PsXFO2AkjF/FROd/m8621YW3k+SKpamQ
/y0JVozmFx8HMwpGc7FlE9hvOXY6k4c=
-----END CERTIFICATE-----

View File

@ -0,0 +1,77 @@
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 1 (0x1)
Signature Algorithm: sha1WithRSAEncryption
Issuer: C=US, ST=CA, L=Roseville, O=Openstack, OU=Keystone, CN=localhost/emailAddress=keystone@openstack.org
Validity
Not Before: Oct 20 17:22:02 2011 GMT
Not After : Oct 19 17:22:02 2012 GMT
Subject: C=US, ST=CA, O=Openstack, OU=Middleware, CN=localhost/emailAddress=middleware@openstack.org
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public Key: (1024 bit)
Modulus (1024 bit):
00:cb:8d:ff:0a:f8:1f:da:0b:65:d9:15:86:e7:4a:
89:07:81:26:7a:2e:ef:67:30:bb:5b:88:3e:73:31:
0e:c9:d9:eb:84:55:7c:57:1b:07:8a:29:7f:41:ed:
1a:47:b2:c4:74:3c:dc:52:81:81:ba:6c:43:b8:44:
bd:83:20:28:4a:82:03:34:f2:1e:88:89:1c:f3:d6:
ef:02:27:9f:7b:4b:dc:ed:50:91:7a:13:a0:8f:5f:
44:10:a6:17:01:6f:7d:7a:3a:a2:1a:28:4e:6e:c5:
b6:06:0b:ba:5c:c9:e9:15:39:95:54:63:bb:40:90:
5d:5d:76:f6:ae:ed:ee:ed:85
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
Netscape Comment:
OpenSSL Generated Certificate
X509v3 Subject Key Identifier:
5A:34:DE:19:11:FF:77:19:2E:E5:6C:36:FA:42:17:6B:46:AF:6A:61
X509v3 Authority Key Identifier:
keyid:60:3F:32:16:18:52:78:C8:74:7F:45:58:55:55:A3:F6:0E:0E:46:41
Signature Algorithm: sha1WithRSAEncryption
a2:1b:e0:d3:e5:c5:35:ad:18:cb:79:a4:fc:f3:d6:7b:53:1e:
dd:28:95:e0:6c:b0:db:fe:aa:30:04:19:c8:99:7a:eb:cb:ed:
dd:74:29:ad:f8:89:6a:ed:d0:10:35:b3:62:36:a2:b0:cc:9f:
86:e8:96:fd:d7:1b:5e:2c:64:b5:5d:f3:bf:1a:1a:07:8b:01:
1f:5f:09:c3:e1:62:cd:30:35:1a:08:e1:cd:71:be:8c:87:de:
f6:7d:40:1b:c6:5f:f0:80:a0:68:55:01:00:74:86:08:52:7e:
c7:fd:62:f9:e3:d0:f8:0b:b0:64:d9:20:70:80:ec:95:11:74:
fb:0b
-----BEGIN CERTIFICATE-----
MIIDAzCCAmygAwIBAgIBATANBgkqhkiG9w0BAQUFADCBkDELMAkGA1UEBhMCVVMx
CzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlSb3NldmlsbGUxEjAQBgNVBAoTCU9wZW5z
dGFjazERMA8GA1UECxMIS2V5c3RvbmUxEjAQBgNVBAMTCWxvY2FsaG9zdDElMCMG
CSqGSIb3DQEJARYWa2V5c3RvbmVAb3BlbnN0YWNrLm9yZzAeFw0xMTEwMjAxNzIy
MDJaFw0xMjEwMTkxNzIyMDJaMIGAMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0Ex
EjAQBgNVBAoTCU9wZW5zdGFjazETMBEGA1UECxMKTWlkZGxld2FyZTESMBAGA1UE
AxMJbG9jYWxob3N0MScwJQYJKoZIhvcNAQkBFhhtaWRkbGV3YXJlQG9wZW5zdGFj
ay5vcmcwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMuN/wr4H9oLZdkVhudK
iQeBJnou72cwu1uIPnMxDsnZ64RVfFcbB4opf0HtGkeyxHQ83FKBgbpsQ7hEvYMg
KEqCAzTyHoiJHPPW7wInn3tL3O1QkXoToI9fRBCmFwFvfXo6ohooTm7FtgYLulzJ
6RU5lVRju0CQXV129q7t7u2FAgMBAAGjezB5MAkGA1UdEwQCMAAwLAYJYIZIAYb4
QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBRa
NN4ZEf93GS7lbDb6QhdrRq9qYTAfBgNVHSMEGDAWgBRgPzIWGFJ4yHR/RVhVVaP2
Dg5GQTANBgkqhkiG9w0BAQUFAAOBgQCiG+DT5cU1rRjLeaT889Z7Ux7dKJXgbLDb
/qowBBnImXrry+3ddCmt+Ilq7dAQNbNiNqKwzJ+G6Jb91xteLGS1XfO/GhoHiwEf
XwnD4WLNMDUaCOHNcb6Mh972fUAbxl/wgKBoVQEAdIYIUn7H/WL549D4C7Bk2SBw
gOyVEXT7Cw==
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDLjf8K+B/aC2XZFYbnSokHgSZ6Lu9nMLtbiD5zMQ7J2euEVXxX
GweKKX9B7RpHssR0PNxSgYG6bEO4RL2DIChKggM08h6IiRzz1u8CJ597S9ztUJF6
E6CPX0QQphcBb316OqIaKE5uxbYGC7pcyekVOZVUY7tAkF1ddvau7e7thQIDAQAB
AoGAITSpzV1KvOQtGiuz1RlIn0vHPhlX/opplfX00g/HrM/65pyXaxJCuZwpYVTP
e7DC8X9YJbFwuzucFHxKOhDN4YbnW145bgfHbI9KLXtZiDvXvHg2MGKjpL/S3Lp3
zzWBo8gknmFGLK41WbYCCWKcvikEb3/KowcooznY5X5BjWECQQD6NC9Bi2EUUyPR
B2ZT3C3h2Hj53yqLkJzP0PaxTC+j7rsycy5r7UiOK8+8aC1T9EsaJrmEKlYBmlbd
lVdhohpNAkEA0EUphaVGURlNmXZgYdSZ1rrpJTvKbFtXmUCowi7Ml2h/oTuHDFHf
i4P8//79YB1uJ4Ll9edjJsZqtAErUTnMGQJBAJcKp7hutqU5Z3bJe8mGMqCTOLzH
LvzfyPpfkH0Jm/zfolxbUhvPO6yv4BFB5pM295uK4xVZJWCEVoofnIeQ/0UCQHuK
ex3esv5KTyCX+oYtkW+xgbjnZaSu7iBnHXPKROwPPZ4LbIlfS4Y7rejAfdX0vzHK
0NP0BHmsuwC5rNNKwIkCQBZqTnLVcisz1FRM2g/OKfWMx+lhVf5fIng+jUEJCdNE
fGjCUu4BRs+nXq6EzoijLvtrmRmFL7VYAKdabSVeLRc=
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,18 @@
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,116D8984CC1AC50A
PnDGqu3+5ITsGtOwCucdQBs7UmPpJKk3x+UuBdpJuygMEgGM70P+eN+RLTH/vaAl
GFWV9wvlL7j+azrEYlbiKhHn4+6SmDSWjjSVM5wGclzH/UYhyhVe/GZsJ8axW278
6EdwzmrbvIjuPTN/dJjyXdeOlFFpoCST8TI03+qYo9T0L86560Y3SjTr/hHhlVyL
PgwfcN3wdarhPloJvFoV10kNH3MBpgGeclNQcNVRH7+Z2DwzHgV3bW28w1h4dOI7
RrPpa1YaAi0lTltuiZYLUtTBI/+xEDf3kFkeSNSdl3sLp9faHUoosVObdFfLCmV1
+66MdqgesPFipkfGPlTGuUX9CmYMooCn+hs7+tVZUqCl/fcErFWeW8iS5+nrat7f
HBiAsOTZ96AEvy/FksYPymrdaK085aODgPqSfR2pvMuF66iKS1xRZiTpMnDApTVN
A6BOZdJgTqGX4yny7ORxQ90xkv39oZYS9cc10Hqec1DG1LWy9dfvavEPk7/GejiT
Z5SMbIHHiNe5tNTomGqtgLIhjfoRXH14zbPGbJ5bI0REJ+sdUM3ItH75tTHYQUIb
S8UQBkHzU+ExK4q5E3BvKR7UH0KD5z6B6QhAyCB6mQp+63nsIP5cImXuAY9u0s1a
3tOmvUpXWDpqJLeAShb3DAPz5+FMx4mbT5oZq1Y8q5RDqMSSrB7XilAroCqasUIb
LoLNMri7WKcrCT1dKjN4y17ucwU8wLPo7Lpo+x5/XWqQA5qSB83YG9nh6nuzYNyo
aUsLH4cfAj3vCPU+KQux5jJfpcma9fyxVfCfa55dmakmGM8ww6ZXXQ==
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCeWly+3CDUrzZcM21yRJRZxqkk7fqLLFOrJH15RsymRQWwV7QN
1o/02aURZOR4sSYw3vtKcsiX5zFPVbtbFtciGxPK/GsEvRWcCVHW+RRRZ6NCSoHO
mA9uXKx/Nr4Pea0HgXWiIahf5ZwicUzbY7ZEKWUidm4HmN6+WD+y/s0n9wIDAQAB
AoGAf6eY3MPYM5yL1ggfUt62OSlNcdfnAgrZ6D2iaQIKOH+r9ly9aepuYpSR3VPY
WvN0NjGLopil3M8jkTEruGLRSgin8+v+qlcRFsoXamegc3NV4XtxJhSmSIocKIIK
14w5YxcDz1QGqoati4LxQ1D6V5eNhiO65YhdcUDarGnlcAECQQDK4vcBGLY7H91f
lGT/oFJ0crqF4V+bLxMO28NhtS0G+GoM0MKrPfIu+nZDlKQzzHUlEZMNXSLz1T+T
po92UVe3AkEAx87ZKDK4xZZRNz0dAe29a3gQ6PmVkav1+NIxr0MP7Ff4tH6K/uoz
96OZpZg+TxdaoxSeNltuUelt3/xPs9AxwQJBAI01t1FuD7fLD9ssf7djsMAX8jao
jFCITS10S+K/pR1K3RUaX8OsE9oavSGAXWEoFwi72KvefStU6zErJoLlTrUCQCG5
wmHMne+L/c1rHVhT/qMDMyd/6UUbV3tWT1ib4zYraylcKq34bikgjjCrT+kdsgjQ
1BustyRQWGF0PyfEvoECQGhVOY2byAOEau+GeTC0c3LIDoErx6WaW3d9ty3Tmx3G
Y81XHlbO4Lw2q8fWZ8Ah2ptjv2IpKj0GAGRiJ5NnPTM=
-----END RSA PRIVATE KEY-----

View File

@ -107,7 +107,8 @@ class BufferedHTTPConnection(HTTPConnection):
def http_connect(ipaddr, port, device, partition, method, path, def http_connect(ipaddr, port, device, partition, method, path,
headers=None, query_string=None, ssl=False): headers=None, query_string=None, ssl=False, key_file=None,
cert_file=None):
""" """
Helper function to create an HTTPConnection object. If ssl is set True, Helper function to create an HTTPConnection object. If ssl is set True,
HTTPSConnection will be used. However, if ssl=False, BufferedHTTPConnection HTTPSConnection will be used. However, if ssl=False, BufferedHTTPConnection
@ -122,26 +123,18 @@ def http_connect(ipaddr, port, device, partition, method, path,
:param headers: dictionary of headers :param headers: dictionary of headers
:param query_string: request query string :param query_string: request query string
:param ssl: set True if SSL should be used (default: False) :param ssl: set True if SSL should be used (default: False)
:param key_file Private key file (not needed if cert_file has private key)
:param cert_file Certificate file (Keystore)
:returns: HTTPConnection object :returns: HTTPConnection object
""" """
if ssl:
conn = HTTPSConnection('%s:%s' % (ipaddr, port))
else:
conn = BufferedHTTPConnection('%s:%s' % (ipaddr, port))
path = quote('/' + device + '/' + str(partition) + path) path = quote('/' + device + '/' + str(partition) + path)
if query_string: return http_connect_raw(ipaddr, port, device, partition, method, path,
path += '?' + query_string headers, query_string, ssl, key_file, cert_file)
conn.path = path
conn.putrequest(method, path)
if headers:
for header, value in headers.iteritems():
conn.putheader(header, value)
conn.endheaders()
return conn
def http_connect_raw(ipaddr, port, method, path, headers=None, def http_connect_raw(ipaddr, port, method, path, headers=None,
query_string=None, ssl=False): query_string=None, ssl=False, key_file=None,
cert_file=None):
""" """
Helper function to create an HTTPConnection object. If ssl is set True, Helper function to create an HTTPConnection object. If ssl is set True,
HTTPSConnection will be used. However, if ssl=False, BufferedHTTPConnection HTTPSConnection will be used. However, if ssl=False, BufferedHTTPConnection
@ -154,10 +147,13 @@ def http_connect_raw(ipaddr, port, method, path, headers=None,
:param headers: dictionary of headers :param headers: dictionary of headers
:param query_string: request query string :param query_string: request query string
:param ssl: set True if SSL should be used (default: False) :param ssl: set True if SSL should be used (default: False)
:param key_file Private key file (not needed if cert_file has private key)
:param cert_file Certificate file (Keystore)
:returns: HTTPConnection object :returns: HTTPConnection object
""" """
if ssl: if ssl:
conn = HTTPSConnection('%s:%s' % (ipaddr, port)) conn = HTTPSConnection('%s:%s' % (ipaddr, port), key_file=key_file,
cert_file=cert_file)
else: else:
conn = BufferedHTTPConnection('%s:%s' % (ipaddr, port)) conn = BufferedHTTPConnection('%s:%s' % (ipaddr, port))
if query_string: if query_string:

View File

@ -23,12 +23,10 @@
import json import json
import sys import sys
import eventlet
import eventlet.wsgi import eventlet.wsgi
eventlet.patcher.monkey_patch(all=False, socket=True, time=True) eventlet.patcher.monkey_patch(all=False, socket=True, time=True)
import routes
import routes.middleware import routes.middleware
import webob import ssl
import webob.dec import webob.dec
import webob.exc import webob.exc
@ -61,6 +59,8 @@ class Server(object):
self.pool = eventlet.GreenPool(threads) self.pool = eventlet.GreenPool(threads)
self.socket_info = {} self.socket_info = {}
self.greenthread = None self.greenthread = None
self.do_ssl = False
self.cert_required = False
def start(self, key=None, backlog=128): def start(self, key=None, backlog=128):
"""Run a WSGI server with the given application.""" """Run a WSGI server with the given application."""
@ -69,9 +69,30 @@ class Server(object):
'host': self.host, 'host': self.host,
'port': self.port}) 'port': self.port})
socket = eventlet.listen((self.host, self.port), backlog=backlog) socket = eventlet.listen((self.host, self.port), backlog=backlog)
self.greenthread = self.pool.spawn(self._run, self.application, socket)
if key: if key:
self.socket_info[key] = socket.getsockname() self.socket_info[key] = socket.getsockname()
# SSL is enabled
if self.do_ssl:
if self.cert_required:
cert_reqs = ssl.CERT_REQUIRED
else:
cert_reqs = ssl.CERT_NONE
sslsocket = eventlet.wrap_ssl(socket, certfile=self.certfile,
keyfile=self.keyfile,
server_side=True,
cert_reqs=cert_reqs,
ca_certs=self.ca_certs)
socket = sslsocket
self.greenthread = self.pool.spawn(self._run, self.application, socket)
def set_ssl(self, certfile, keyfile=None, ca_certs=None,
cert_required=True):
self.certfile = certfile
self.keyfile = keyfile
self.ca_certs = ca_certs
self.cert_required = cert_required
self.do_ssl = True
def kill(self): def kill(self):
if self.greenthread: if self.greenthread:

View File

@ -145,6 +145,12 @@ register_str('admin_port', default=35357)
register_str('public_port', default=5000) register_str('public_port', default=5000)
register_str('onready') register_str('onready')
#ssl options
register_bool('enable', group='ssl', default=False)
register_str('certfile', group='ssl', default=None)
register_str('keyfile', group='ssl', default=None)
register_str('ca_certs', group='ssl', default=None)
register_bool('cert_required', group='ssl', default=False)
# sql options # sql options
register_str('connection', group='sql', default='sqlite:///keystone.db') register_str('connection', group='sql', default='sqlite:///keystone.db')

View File

@ -125,17 +125,21 @@ class AuthProtocol(object):
# where to find the auth service (we use this to validate tokens) # where to find the auth service (we use this to validate tokens)
self.auth_host = conf.get('auth_host') self.auth_host = conf.get('auth_host')
self.auth_port = int(conf.get('auth_port', 35357)) self.auth_port = int(conf.get('auth_port', 35357))
auth_protocol = conf.get('auth_protocol', 'https') self.auth_protocol = conf.get('auth_protocol', 'https')
if auth_protocol == 'http': if self.auth_protocol == 'http':
self.http_client_class = httplib.HTTPConnection self.http_client_class = httplib.HTTPConnection
else: else:
self.http_client_class = httplib.HTTPSConnection self.http_client_class = httplib.HTTPSConnection
default_auth_uri = '%s://%s:%s' % (auth_protocol, default_auth_uri = '%s://%s:%s' % (self.auth_protocol,
self.auth_host, self.auth_host,
self.auth_port) self.auth_port)
self.auth_uri = conf.get('auth_uri', default_auth_uri) self.auth_uri = conf.get('auth_uri', default_auth_uri)
# SSL
self.cert_file = conf.get('certfile')
self.key_file = conf.get('keyfile')
# Credentials used to verify this component with the Auth service since # Credentials used to verify this component with the Auth service since
# validating tokens is a privileged call # validating tokens is a privileged call
self.admin_token = conf.get('admin_token') self.admin_token = conf.get('admin_token')
@ -252,7 +256,11 @@ class AuthProtocol(object):
return self.admin_token return self.admin_token
def _get_http_connection(self): def _get_http_connection(self):
if self.auth_protocol == 'http':
return self.http_client_class(self.auth_host, self.auth_port) return self.http_client_class(self.auth_host, self.auth_port)
else:
return self.http_client_class(self.auth_host, self.auth_port,
self.key_file, self.cert_file)
def _json_request(self, method, path, body=None, additional_headers=None): def _json_request(self, method, path, body=None, additional_headers=None):
"""HTTP request helper used to make json requests. """HTTP request helper used to make json requests.

View File

@ -60,11 +60,14 @@ class S3Token(object):
# where to find the auth service (we use this to validate tokens) # where to find the auth service (we use this to validate tokens)
self.auth_host = conf.get('auth_host') self.auth_host = conf.get('auth_host')
self.auth_port = int(conf.get('auth_port', 35357)) self.auth_port = int(conf.get('auth_port', 35357))
auth_protocol = conf.get('auth_protocol', 'https') self.auth_protocol = conf.get('auth_protocol', 'https')
if auth_protocol == 'http': if self.auth_protocol == 'http':
self.http_client_class = httplib.HTTPConnection self.http_client_class = httplib.HTTPConnection
else: else:
self.http_client_class = httplib.HTTPSConnection self.http_client_class = httplib.HTTPSConnection
# SSL
self.cert_file = conf.get('certfile')
self.key_file = conf.get('keyfile')
def deny_request(self, code): def deny_request(self, code):
error_table = { error_table = {
@ -86,7 +89,11 @@ class S3Token(object):
headers = {'Content-Type': 'application/json'} headers = {'Content-Type': 'application/json'}
try: try:
if self.auth_protocol == 'http':
conn = self.http_client_class(self.auth_host, self.auth_port) conn = self.http_client_class(self.auth_host, self.auth_port)
else:
conn = self.http_client_class(self.auth_host, self.auth_port,
self.key_file, self.cert_file)
conn.request('POST', '/v2.0/s3tokens', conn.request('POST', '/v2.0/s3tokens',
body=creds_json, body=creds_json,
headers=headers) headers=headers)

View File

@ -32,7 +32,7 @@ from keystone.common import wsgi
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
ROOTDIR = os.path.dirname(os.path.dirname(__file__)) ROOTDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
VENDOR = os.path.join(ROOTDIR, 'vendor') VENDOR = os.path.join(ROOTDIR, 'vendor')
TESTSDIR = os.path.join(ROOTDIR, 'tests') TESTSDIR = os.path.join(ROOTDIR, 'tests')
ETCDIR = os.path.join(ROOTDIR, 'etc') ETCDIR = os.path.join(ROOTDIR, 'etc')
@ -236,9 +236,13 @@ class TestCase(unittest.TestCase):
def appconfig(self, config): def appconfig(self, config):
return deploy.appconfig(self._paste_config(config)) return deploy.appconfig(self._paste_config(config))
def serveapp(self, config, name=None): def serveapp(self, config, name=None, cert=None, key=None, ca=None,
cert_required=None):
app = self.loadapp(config, name=name) app = self.loadapp(config, name=name)
server = wsgi.Server(app, host="127.0.0.1", port=0) server = wsgi.Server(app, host="127.0.0.1", port=0)
if cert is not None and ca is not None and key is not None:
server.set_ssl(certfile=cert, keyfile=key, ca_certs=ca,
cert_required=cert_required)
server.start(key='socket') server.start(key='socket')
# Service catalog tests need to know the port we ran on. # Service catalog tests need to know the port we ran on.

103
tests/test_ssl.py Normal file
View File

@ -0,0 +1,103 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 OpenStack LLC
#
# 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 os
import httplib
import ssl
from keystone import test
from keystone import config
CONF = config.CONF
CERTDIR = test.rootdir("examples/ssl/certs")
KEYDIR = test.rootdir("examples/ssl/private")
CERT = os.path.join(CERTDIR, 'keystone.pem')
KEY = os.path.join(KEYDIR, 'keystonekey.pem')
CA = os.path.join(CERTDIR, 'ca.pem')
CLIENT = os.path.join(CERTDIR, 'middleware.pem')
class SSLTestCase(test.TestCase):
def setUp(self):
super(SSLTestCase, self).setUp()
self.load_backends()
def test_1way_ssl_ok(self):
"""
Make sure both public and admin API work with 1-way SSL.
"""
self.public_server = self.serveapp('keystone', name='main',
cert=CERT, key=KEY, ca=CA)
self.admin_server = self.serveapp('keystone', name='admin',
cert=CERT, key=KEY, ca=CA)
# Verify Admin
conn = httplib.HTTPSConnection('127.0.0.1', CONF.admin_port)
conn.request('GET', '/')
resp = conn.getresponse()
self.assertEqual(resp.status, 300)
# Verify Public
conn = httplib.HTTPSConnection('127.0.0.1', CONF.public_port)
conn.request('GET', '/')
resp = conn.getresponse()
self.assertEqual(resp.status, 300)
def test_2way_ssl_ok(self):
"""
Make sure both public and admin API work with 2-way SSL. Requires
client certificate.
"""
self.public_server = self.serveapp('keystone', name='main',
cert=CERT, key=KEY, ca=CA, cert_required=True)
self.admin_server = self.serveapp('keystone', name='admin',
cert=CERT, key=KEY, ca=CA, cert_required=True)
# Verify Admin
conn = httplib.HTTPSConnection(
'127.0.0.1', CONF.admin_port, CLIENT, CLIENT)
conn.request('GET', '/')
resp = conn.getresponse()
self.assertEqual(resp.status, 300)
# Verify Public
conn = httplib.HTTPSConnection(
'127.0.0.1', CONF.public_port, CLIENT, CLIENT)
conn.request('GET', '/')
resp = conn.getresponse()
self.assertEqual(resp.status, 300)
def test_2way_ssl_fail(self):
"""
Expect to fail when client does not present proper certificate.
"""
self.public_server = self.serveapp('keystone', name='main',
cert=CERT, key=KEY, ca=CA, cert_required=True)
self.admin_server = self.serveapp('keystone', name='admin',
cert=CERT, key=KEY, ca=CA, cert_required=True)
# Verify Admin
conn = httplib.HTTPSConnection('127.0.0.1', CONF.admin_port)
try:
conn.request('GET', '/')
self.fail('Admin API shoulda failed with SSL handshake!')
except ssl.SSLError:
pass
# Verify Public
conn = httplib.HTTPSConnection('127.0.0.1', CONF.public_port)
try:
conn.request('GET', '/')
self.fail('Public API shoulda failed with SSL handshake!')
except ssl.SSLError:
pass