X.509 client authentication with Keystone. Implements blueprint 2-way-ssl

Change-Id: I5648acb8980782d2f1f932ee4947dd2deb490de5
This commit is contained in:
Liem Nguyen 2011-10-20 15:54:58 -07:00
parent 37ff5b6883
commit 96f2fc18ee
23 changed files with 511 additions and 67 deletions

View File

@ -70,19 +70,35 @@ if __name__ == '__main__':
print "Using config file:", config_file
# Load Service API server
server = wsgi.Server()
server.start(app, int(conf['service_port']), conf['service_host'])
if conf['service_ssl'] == 'True':
server = wsgi.SslServer()
server.start(app, int(conf['service_port']), conf['service_host'],
certfile=conf['certfile'], keyfile=conf['keyfile'],
ca_certs=conf['ca_certs'],
cert_required=conf['cert_required'])
else:
server = wsgi.Server()
server.start(app, int(conf['service_port']), conf['service_host'])
print "Service API listening on %s:%s" % (
conf['service_host'], conf['service_port'])
print "Service API (ssl=%s) listening on %s:%s" % (
conf['service_ssl'], conf['service_host'], conf['service_port'])
# Load Admin API server
admin_server = wsgi.Server()
admin_server.start(admin_app,
int(conf['admin_port']), conf['admin_host'])
if conf['admin_ssl'] == 'True':
admin_server = wsgi.SslServer()
admin_server.start(admin_app,
int(conf['admin_port']), conf['admin_host'],
certfile=conf['certfile'], keyfile=conf['keyfile'],
ca_certs=conf['ca_certs'],
cert_required=conf['cert_required'])
else:
admin_server = wsgi.Server()
admin_server.start(admin_app,
int(conf['admin_port']), conf['admin_host'])
print "Admin API listening on %s:%s" % (
conf['admin_host'], conf['admin_port'])
print "Admin API (ssl=%s) listening on %s:%s" % (
conf['admin_ssl'], conf['admin_host'], conf['admin_port'])
# Wait until done
server.wait()

86
doc/source/ssl.rst Normal file
View File

@ -0,0 +1,86 @@
..
Copyright 2011 OpenStack, LLC
All Rights Reserved.
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.
=====================================================
Instructions for Keystone x.509 client authentication
=====================================================
Purpose
-------
Allows the Keystone middleware to authenticate itself with the Keystone server
via an x.509 client certificate. Both Service API and Admin API may be secured
with this feature.
Certificates
------------
The following types of certificates are required. A set of 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:
1. ca.pem : Certificate Authority chain to validate against.
2. keystone.pem : Public certificate for Keystone server.
3. middleware-key.pem: Public and private certificate for Keystone middleware.
4. cakey.pem : Private key for the CA.
5. 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.
Keystone server
---------------
By default, the Keystone server does not use SSL. To enable SSL with client authentication,
modify the etc/keystone.conf file accordingly:
1. To enable SSL for Service API:
service_ssl = True
2. To enable SSL for Admin API:
admin_ssl = True
3. To enable SSL client authentication:
cert_required = True
4. Set the location of the Keystone certificate file (example):
certfile = /etc/keystone/ca/certs/keystone.pem
5. Set the location of the Keystone private file (example):
keyfile = /etc/keystone/ca/private/keystonekey.pem
6. Set the location of the CA chain:
ca_certs = /etc/keystone/ca/certs/ca.pem
Middleware
----------
Add the following to your middleware configuration to support x.509 client authentication.
If cert_required is set to False on the keystone server, the certfile and keyfile parameters
in steps 3) and 4) may be commented out.
1. Specify 'https' as the auth_protocol:
auth_protocol = https
2. Modify the protocol in 'auth_uri' to be 'https' as well, if the service API is configured
for SSL:
auth_uri = https://localhost:5000/
3. Set the location of the middleware certificate file (example):
certfile = /etc/keystone/ca/certs/middleware-key.pem
4. Set the location of the Keystone private file (example):
keyfile = /etc/keystone/ca/certs/middleware-key.pem
For an example, take a look at the 'echo.ini' middleware configuration for the 'echo' example
service in the examples/echo directory.
Testing
-------
You can test out how it works by using the 'echo' example service in the examples/echo directory
and the certficates included in the examples/ssl directory. Invoke the echo_client.py with
the path to the client certificate:
python echo_client.py -s <path to client certificate>

View File

@ -32,12 +32,34 @@ service_host = 0.0.0.0
# Port the bind the API server to
service_port = 5000
# SSL for API server
service_ssl = False
# Address to bind the Admin API server
admin_host = 0.0.0.0
# Port the bind the Admin API server to
admin_port = 35357
# SSL for API Admin server
admin_ssl = False
# Keystone certificate file (modify as needed)
# Only required if *_ssl is set to True
certfile = /etc/keystone/ssl/certs/keystone.pem
# Keystone private key file (modify as needed)
# Only required if *_ssl is set to True
keyfile = /etc/keystone/ssl/private/keystonekey.pem
# Keystone trusted CA certificates (modify as needed)
# Only required if *_ssl is set to True
ca_certs = /etc/keystone/ssl/certs/ca.pem
# Client certificate required
# Only relevant if *_ssl is set to True
cert_required = True
#Role that allows to perform admin operations.
keystone-admin-role = Admin

View File

@ -10,6 +10,9 @@ service_port = 35357
;used to verify this component with the OpenStack service (or PAPIAuth)
service_pass = dTpw
;where to find x.509 client certificates
certfile = ../../ssl/certs/middleware-key.pem
keyfile = ../../ssl/certs/middleware-key.pem
[app:echo]
paste.app_factory = echo:app_factory
@ -26,6 +29,10 @@ auth_host = 127.0.0.1
auth_port = 35357
auth_protocol = http
auth_uri = http://localhost:5000/
;Uncomment the following out for SSL connections
;auth_protocol = https
;auth_uri = https://localhost:5000/
;how to authenticate to the auth service for priviledged operations
;like validate token
admin_token = 999888777666

View File

@ -20,15 +20,29 @@ Implement a client for Echo service using Identity service
import httplib
import json
import sys
def keystone_conn():
""" Get a connection. If it is SSL, needs the '-s' option, optionally
followed by the location of the cert_file with private key. """
if '-s' in sys.argv:
cert_file = None
if len(sys.argv) > sys.argv.index('-s') + 1:
cert_file = sys.argv[sys.argv.index('-s') + 1]
conn = httplib.HTTPSConnection("localhost:5000", cert_file=cert_file)
else:
conn = httplib.HTTPConnection("localhost:5000")
return conn
def get_auth_token(username, password, tenant):
headers = {"Content-type": "application/json",
"Accept": "application/json"}
params = {"passwordCredentials": {"username": username,
"password": password,
"tenantId": tenant}}
conn = httplib.HTTPConnection("localhost:5000")
params = {"auth": {"passwordCredentials":
{"username": username, "password": password},
"tenantName": tenant}}
conn = keystone_conn()
conn.request("POST", "/v2.0/tokens", json.dumps(params), headers=headers)
response = conn.getresponse()
data = response.read()
@ -72,7 +86,7 @@ if __name__ == '__main__':
print "\033[91mTrying with valid test credentials...\033[0m"
auth = get_auth_token("joeuser", "secrete", "customer-x")
obj = json.loads(auth)
token = obj["auth"]["token"]["id"]
token = obj["access"]["token"]["id"]
print "Token obtained:", token
# Use that token to call an OpenStack service (echo)
@ -94,5 +108,5 @@ if __name__ == '__main__':
#Supply bad credentials
print "\033[91mTrying with bad credentials...\033[0m"
auth = get_auth_token("joeuser", "wrongpass", "1")
auth = get_auth_token("joeuser", "wrongpass", "customer-x")
print "Response:", auth

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

@ -30,7 +30,7 @@ class ServiceClient(object):
_default_port = 5000
def __init__(self, host, port=None):
def __init__(self, host, port=None, is_ssl=False, cert_file=None):
"""Initialize client.
:param host: The hostname or IP of the Keystone service to use
@ -39,6 +39,8 @@ class ServiceClient(object):
"""
self.host = host
self.port = port or self._default_port
self.is_ssl = is_ssl
self.cert_file = cert_file
def _http_request(self, verb, path, body=None, headers=None):
"""Perform an HTTP request and return the HTTP response.
@ -50,7 +52,11 @@ class ServiceClient(object):
:returns: httplib.HTTPResponse object
"""
connection = httplib.HTTPConnection(self.auth_address)
if (self.is_ssl):
connection = httplib.HTTPSConnection(self.auth_address,
cert_file=self.cert_file)
else:
connection = httplib.HTTPConnection(self.auth_address)
connection.request(verb, path, body=body, headers=headers)
response = connection.getresponse()
@ -109,7 +115,8 @@ class AdminClient(ServiceClient):
_default_admin_name = "admin"
_default_admin_pass = "password"
def __init__(self, host, port=None, admin_name=None, admin_pass=None):
def __init__(self, host, port=None, is_ssl=False, cert_file=None,
admin_name=None, admin_pass=None):
"""Initialize client.
:param host: The hostname or IP of the Keystone service to use
@ -118,7 +125,8 @@ class AdminClient(ServiceClient):
:param admin_pass: The password to use for the admin account
"""
super(AdminClient, self).__init__(host, port=port)
super(AdminClient, self).__init__(host, port=port, is_ssl=is_ssl,
cert_file=cert_file)
self.admin_name = admin_name or self._default_admin_name
self.admin_pass = admin_pass or self._default_admin_pass
self._admin_token = None

View File

@ -101,7 +101,8 @@ class BufferedHTTPConnection(HTTPConnection):
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,
HTTPSConnection will be used. However, if ssl=False, BufferedHTTPConnection
@ -116,26 +117,18 @@ def http_connect(ipaddr, port, device, partition, method, path,
:param headers: dictionary of headers
:param query_string: request query string
: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
"""
if ssl:
conn = HTTPSConnection('%s:%s' % (ipaddr, port))
else:
conn = BufferedHTTPConnection('%s:%s' % (ipaddr, port))
path = quote('/' + device + '/' + str(partition) + path)
if query_string:
path += '?' + query_string
conn.path = path
conn.putrequest(method, path)
if headers:
for header, value in headers.iteritems():
conn.putheader(header, value)
conn.endheaders()
return conn
return http_connect_raw(ipaddr, port, device, partition, method, path,
headers, query_string, ssl, key_file, cert_file)
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,
HTTPSConnection will be used. However, if ssl=False, BufferedHTTPConnection
@ -148,10 +141,13 @@ def http_connect_raw(ipaddr, port, method, path, headers=None,
:param headers: dictionary of headers
:param query_string: request query string
: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
"""
if ssl:
conn = HTTPSConnection('%s:%s' % (ipaddr, port))
conn = HTTPSConnection('%s:%s' % (ipaddr, port), key_file=key_file,
cert_file=cert_file)
else:
conn = BufferedHTTPConnection('%s:%s' % (ipaddr, port))
if query_string:

View File

@ -25,6 +25,7 @@ import json
import logging
import sys
import datetime
import ssl
import eventlet.wsgi
eventlet.patcher.monkey_patch(all=False, socket=True)
@ -111,6 +112,24 @@ class Server(object):
log=WritableLogger(logger, logging.root.level))
class SslServer(Server):
"""SSL Server class to manage multiple WSGI sockets and applications."""
def start(self, application, port, host='0.0.0.0', backlog=128,
certfile=None, keyfile=None, ca_certs=None,
cert_required='True'):
"""Run a 2-way SSL WSGI server with the given application."""
socket = eventlet.listen((host, port), backlog=backlog)
if cert_required == 'True':
cert_reqs = ssl.CERT_REQUIRED
else:
cert_reqs = ssl.CERT_NONE
sslsocket = eventlet.wrap_ssl(socket, certfile=certfile,
keyfile=keyfile,
server_side=True, cert_reqs=cert_reqs,
ca_certs=ca_certs)
self.pool.spawn_n(self._run, application, sslsocket)
class Middleware(object):
"""
Base WSGI middleware wrapper. These classes require an application to be

View File

@ -127,6 +127,10 @@ class AuthProtocol(object):
# Credentials used to verify this component with the Auth service since
# validating tokens is a privileged call
self.admin_token = conf.get('admin_token')
# Certificate file and key file used to authenticate with Keystone
# server
self.cert_file = conf.get('certfile', None)
self.key_file = conf.get('keyfile', None)
def __init__(self, app, conf):
""" Common initialization code """
@ -204,26 +208,6 @@ class AuthProtocol(object):
#Send request downstream
return self._forward_request(env, start_response, proxy_headers)
# NOTE(todd): unused
def get_admin_auth_token(self, username, password):
"""
This function gets an admin auth token to be used by this service to
validate a user's token. Validate_token is a priviledged call so
it needs to be authenticated by a service that is calling it
"""
headers = {"Content-type": "application/json",
"Accept": "application/json"}
params = {"passwordCredentials": {"username": username,
"password": password,
"tenantId": "1"}}
conn = httplib.HTTPConnection("%s:%s" \
% (self.auth_host, self.auth_port))
conn.request("POST", "/v2.0/tokens", json.dumps(params), \
headers=headers)
response = conn.getresponse()
data = response.read()
return data
def _get_claims(self, env):
"""Get claims from request"""
claims = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN'))
@ -262,8 +246,10 @@ class AuthProtocol(object):
#Khaled's version uses creds to get a token
# "X-Auth-Token": admin_token}
# we're using a test token from the ini file for now
conn = http_connect(self.auth_host, self.auth_port, 'GET',
'/v2.0/tokens/%s' % claims, headers=headers)
conn = http_connect(self.auth_host, self.auth_port, 'HEAD',
'/v2.0/tokens/%s' % claims, headers=headers,
ssl=(self.auth_protocol == 'https'),
key_file=self.key_file, cert_file=self.cert_file)
resp = conn.getresponse()
# data = resp.read()
conn.close()
@ -289,7 +275,9 @@ class AuthProtocol(object):
# "X-Auth-Token": admin_token}
# we're using a test token from the ini file for now
conn = http_connect(self.auth_host, self.auth_port, 'GET',
'/v2.0/tokens/%s' % claims, headers=headers)
'/v2.0/tokens/%s' % claims, headers=headers,
ssl=(self.auth_protocol == 'https'),
key_file=self.key_file, cert_file=self.cert_file)
resp = conn.getresponse()
data = resp.read()
conn.close()

View File

@ -5,9 +5,12 @@ import tempfile
import time
import unittest2 as unittest
from functional.common import HttpTestCase
TEST_DIR = os.path.abspath(os.path.dirname(__file__))
BASE_DIR = os.path.abspath(os.path.join(TEST_DIR, '..', '..'))
TEST_CERT = os.path.join(BASE_DIR, 'examples/ssl/certs/middleware-key.pem')
def execute(cmd, raise_error=True):
@ -26,7 +29,6 @@ def execute(cmd, raise_error=True):
# Make sure that we use the programs in the
# current source directory's bin/ directory.
env['PATH'] = os.path.join(BASE_DIR, 'bin') + ':' + env['PATH']
process = subprocess.Popen(cmd,
shell=True,
stdin=subprocess.PIPE,
@ -58,7 +60,8 @@ class KeystoneTest(object):
end of test execution from the temporary space used to run these
tests).
"""
CONF_PARAMS = {'test_dir': TEST_DIR}
CONF_PARAMS = {'test_dir': TEST_DIR, 'base_dir': BASE_DIR}
isSsl = False
def clear_database(self):
"""Remove any test databases or files generated by previous tests."""
@ -81,6 +84,10 @@ class KeystoneTest(object):
self.clear_database()
self.construct_temp_conf_file()
# Set client certificate for test client
if (self.isSsl == True):
os.environ['cert_file'] = TEST_CERT
# run the keystone server
print "Starting the keystone server..."
self.server = subprocess.Popen(
@ -115,7 +122,6 @@ class KeystoneTest(object):
execute('coverage run %s discover -t %s -s %s' %
('/usr/bin/unit2', BASE_DIR, TEST_DIR))
else:
execute('unit2 discover -f -t %s -s %s' %
(BASE_DIR, TEST_DIR))
execute('unit2 discover -f -t %s -s %s' % (BASE_DIR, TEST_DIR))
finally:
self.tearDown()

View File

@ -10,8 +10,10 @@ service-header-mappings = {
'cdn' : 'X-CDN-Manageent-Url'}
service_host = 0.0.0.0
service_port = 5000
service_ssl = False
admin_host = 0.0.0.0
admin_port = 35357
admin_ssl = False
keystone-admin-role = Admin
keystone-service-admin-role = KeystoneServiceAdmin
hash-password = True

View File

@ -10,8 +10,10 @@ service-header-mappings = {
'cdn' : 'X-CDN-Manageent-Url'}
service_host = 0.0.0.0
service_port = 5000
service_ssl = False
admin_host = 0.0.0.0
admin_port = 35357
admin_ssl = False
keystone-admin-role = Admin
keystone-service-admin-role = KeystoneServiceAdmin

View File

@ -10,8 +10,10 @@ service-header-mappings = {
'cdn' : 'X-CDN-Manageent-Url'}
service_host = 0.0.0.0
service_port = 5000
service_ssl = False
admin_host = 0.0.0.0
admin_port = 35357
admin_ssl = False
keystone-admin-role = Admin
keystone-service-admin-role = KeystoneServiceAdmin
hash-password = True

View File

@ -0,0 +1,55 @@
[DEFAULT]
verbose = False
debug = False
default_store = sqlite
log_file = %(test_dir)s/keystone.ssl.log
backends = keystone.backends.sqlalchemy
service-header-mappings = {
'nova' : 'X-Server-Management-Url',
'swift' : 'X-Storage-Url',
'cdn' : 'X-CDN-Manageent-Url'}
service_host = 0.0.0.0
service_port = 5000
service_ssl = True
admin_host = 0.0.0.0
admin_port = 35357
admin_ssl = True
keystone-admin-role = Admin
keystone-service-admin-role = KeystoneServiceAdmin
hash-password = True
certfile = %(base_dir)s/examples/ssl/certs/keystone.pem
keyfile = %(base_dir)s/examples/ssl/private/keystonekey.pem
ca_certs = %(base_dir)s/examples/ssl/certs/ca.pem
cert_required = True
[keystone.backends.sqlalchemy]
sql_connection = for_testing_only
sql_idle_timeout = 30
backend_entities = ['Endpoints', 'Credentials', 'EndpointTemplates', 'Tenant', 'User', 'UserRoleAssociation', 'Role', 'Token', 'Service']
[pipeline:admin]
pipeline =
urlrewritefilter
admin_api
[pipeline:keystone-legacy-auth]
pipeline =
urlrewritefilter
legacy_auth
RAX-KEY-extension
service_api
[app:service_api]
paste.app_factory = keystone.server:service_app_factory
[app:admin_api]
paste.app_factory = keystone.server:admin_app_factory
[filter:urlrewritefilter]
paste.filter_factory = keystone.middleware.url:filter_factory
[filter:legacy_auth]
paste.filter_factory = keystone.frontends.legacy_token_auth:filter_factory
[filter:RAX-KEY-extension]
paste.filter_factory = keystone.contrib.extensions.service.raxkey.frontend:filter_factory

View File

@ -2,9 +2,17 @@ import unittest2 as unittest
import httplib
import uuid
import json
import os
from xml.etree import ElementTree
def isSsl():
""" See if we are testing with SSL. If cert is non-empty, we are! """
if 'cert_file' in os.environ:
return os.environ['cert_file']
return None
class HttpTestCase(unittest.TestCase):
"""Performs generic HTTP request testing.
@ -23,7 +31,13 @@ class HttpTestCase(unittest.TestCase):
headers = {} if not headers else headers
# Initialize a connection
connection = httplib.HTTPConnection(host, port, timeout=20)
cert_file = isSsl()
if (cert_file != None):
connection = httplib.HTTPSConnection(host, port,
cert_file=cert_file,
timeout=20)
else:
connection = httplib.HTTPConnection(host, port, timeout=20)
# Perform the request
connection.request(method, path, body, headers)
@ -906,8 +920,7 @@ class FunctionalTestCase(ApiTestCase):
"global": is_global,
"versionId": version_id,
"versionInfo": version_info,
"versionList": version_list
}}
"versionList": version_list}}
return self.post_endpoint_template(as_json=data, **kwargs)
def remove_endpoint_template(self, endpoint_template_id=None, **kwargs):

View File

@ -2,6 +2,7 @@ import unittest
import keystone.common.exception
import keystone.client
from common import isSsl
class TestAdminClient(unittest.TestCase):
@ -13,7 +14,10 @@ class TestAdminClient(unittest.TestCase):
"""
Run before each test.
"""
cert_file = isSsl()
self.client = keystone.client.AdminClient("127.0.0.1",
is_ssl=(cert_file != None),
cert_file=cert_file,
admin_name="admin",
admin_pass="secrete")
@ -71,7 +75,10 @@ class TestServiceClient(unittest.TestCase):
"""
Run before each test.
"""
self.client = keystone.client.ServiceClient("127.0.0.1")
cert_file = isSsl()
self.client = keystone.client.ServiceClient("127.0.0.1",
is_ssl=(cert_file != None),
cert_file=cert_file)
def test_admin_get_token(self):
"""

View File

@ -10,6 +10,12 @@ class SQLTest(KeystoneTest):
test_files = ('keystone.db',)
class SSLTest(KeystoneTest):
config_name = 'ssl.conf.template'
test_files = ('keystone.db',)
isSsl = True
class MemcacheTest(KeystoneTest):
"""Test defined using only SQLAlchemy and Memcache back-end"""
config_name = 'memcache.conf.template'
@ -25,6 +31,7 @@ TESTS = [
SQLTest,
# currently failing, and has yet to pass in jenkins: MemcacheTest,
LDAPTest,
SSLTest,
]
if __name__ == '__main__':

View File

@ -42,11 +42,11 @@ addlargs=
wrapper=""
just_pep8=0
just_pylint=0
RUNTESTS="python run_tests.py $addlargs"
for arg in "$@"; do
process_option $arg
done
RUNTESTS="python run_tests.py $addlargs"
function run_tests {
# Just run the test suites in current environment