Skip to content
This repository was archived by the owner on Sep 26, 2019. It is now read-only.

Commit b626a3c

Browse files
tempusfrangittipabuKota Tsuyuzaki
committed
Add s3token middleware to the swift3 project
This patch moves (as discussed at the Newton design summit) the s3_token middleware from keystonemiddleware to swift3. The git history is not included based upon the agreement between the Keystone team and the Swift3 team. This is based on s3_token.py from openstack/keystonemiddleware@234913e Note that the egg entrypoint has changed from "s3_token" to "s3token" for consistency between entrypoint and recommended pipeline names. Additionally, keystone functional tests now use the in-tree s3token middleware. Upgrade Impact ============== Deployers currently using keystone for authentication should change their s3token filter definition to use the middleware provided by swift3 rather than the one provided by keystonemiddleware. Note that keystonemiddleware will still need to be installed, and its auth_token middleware configured. UpgradeImpact Co-Authored-By: Tim Burke <tim.burke@gmail.com> Co-Authored-By: Kota Tsuyuzaki <tsuyuzaki.kota@lab.ntt.co.jp> Change-Id: I1c0e68a5276dd3dee97d7569e477c784db8ccb8a
1 parent ca08743 commit b626a3c

File tree

8 files changed

+538
-15
lines changed

8 files changed

+538
-15
lines changed

README.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ If you use keystone:
5757
pipeline = catch_errors cache swift3 s3token authtoken keystoneauth slo proxy-server
5858

5959
Note:
60-
* The s3token and authtoken filters require the keystonemiddleware package.
60+
* The authtoken filter requires the keystonemiddleware package.
6161
* Swift3 explicitly checks that keystoneauth is in the pipeline. You must use this name
6262
in the pipeline statement and in [filter:keystoneauth] section header.
6363

@@ -69,10 +69,8 @@ Note:
6969
You also need to add the following if you use keystone (adjust port, host, protocol configurations for your environment):
7070

7171
[filter:s3token]
72-
paste.filter_factory = keystonemiddleware.s3_token:filter_factory
73-
auth_port = 35357
74-
auth_host = 127.0.0.1
75-
auth_protocol = http
72+
use = egg:swift3#s3token
73+
auth_uri = http://127.0.0.1:35357/
7674

7775

7876
4) Swift3 config options:

etc/proxy-server.conf-sample

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,15 +141,13 @@ use = egg:swift#memcache
141141

142142
[filter:s3token]
143143
# See swift manual for more details.
144-
paste.filter_factory = keystonemiddleware.s3_token:filter_factory
144+
use = egg:swift3#s3token
145145

146146
# Prefix that will be prepended to the tenant to form the account
147147
reseller_prefix = AUTH_
148148

149149
# Keystone server details
150-
auth_host = keystonehost
151-
auth_port = 35357
152-
auth_protocol = http
150+
auth_uri = http://keystonehost:35357/
153151

154152
# SSL-related options
155153
#insecure = False

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
swift>=2.1.0
22
lxml
3+
requests!=2.9.0,>=2.8.1 # Apache-2.0
4+
six>=1.9.0

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ packages =
2929
[entry_points]
3030
paste.filter_factory =
3131
swift3 = swift3.middleware:filter_factory
32+
s3token = swift3.s3_token_middleware:filter_factory
3233

3334
[nosetests]
3435
exe = 1

swift3/s3_token_middleware.py

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
# Copyright 2012 OpenStack Foundation
2+
# Copyright 2010 United States Government as represented by the
3+
# Administrator of the National Aeronautics and Space Administration.
4+
# Copyright 2011,2012 Akira YOSHIYAMA <akirayoshiyama@gmail.com>
5+
# All Rights Reserved.
6+
#
7+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
8+
# not use this file except in compliance with the License. You may obtain
9+
# a copy of the License at
10+
#
11+
# http://www.apache.org/licenses/LICENSE-2.0
12+
#
13+
# Unless required by applicable law or agreed to in writing, software
14+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16+
# License for the specific language governing permissions and limitations
17+
# under the License.
18+
19+
# This source code is based ./auth_token.py and ./ec2_token.py.
20+
# See them for their copyright.
21+
22+
"""
23+
S3 Token Middleware
24+
25+
This WSGI component:
26+
27+
* Gets a request from the swift3 middleware with an S3 Authorization
28+
access key.
29+
* Validates s3 token in Keystone.
30+
* Transforms the account name to AUTH_%(tenant_name).
31+
32+
"""
33+
34+
import json
35+
import logging
36+
37+
import requests
38+
import six
39+
40+
from swift.common.swob import Request, Response
41+
from swift.common.utils import config_true_value, split_path
42+
43+
44+
PROTOCOL_NAME = 'S3 Token Authentication'
45+
46+
47+
class ServiceError(Exception):
48+
pass
49+
50+
51+
class S3Token(object):
52+
"""Middleware that handles S3 authentication."""
53+
54+
def __init__(self, app, conf):
55+
"""Common initialization code."""
56+
self._app = app
57+
self._logger = logging.getLogger(conf.get('log_name', __name__))
58+
self._logger.debug('Starting the %s component', PROTOCOL_NAME)
59+
self._reseller_prefix = conf.get('reseller_prefix', 'AUTH_')
60+
# where to find the auth service (we use this to validate tokens)
61+
62+
self._request_uri = conf.get('auth_uri')
63+
if not self._request_uri:
64+
self._logger.warning(
65+
"Use of the auth_host, auth_port, and auth_protocol "
66+
"configuration options was deprecated in the Newton release "
67+
"in favor of auth_uri. These options may be removed in a "
68+
"future release.")
69+
auth_host = conf.get('auth_host')
70+
auth_port = int(conf.get('auth_port', 35357))
71+
auth_protocol = conf.get('auth_protocol', 'https')
72+
73+
self._request_uri = '%s://%s:%s' % (auth_protocol, auth_host,
74+
auth_port)
75+
self._request_uri = self._request_uri.rstrip('/')
76+
77+
# SSL
78+
insecure = config_true_value(conf.get('insecure'))
79+
cert_file = conf.get('certfile')
80+
key_file = conf.get('keyfile')
81+
82+
if insecure:
83+
self._verify = False
84+
elif cert_file and key_file:
85+
self._verify = (cert_file, key_file)
86+
elif cert_file:
87+
self._verify = cert_file
88+
else:
89+
self._verify = None
90+
91+
def _deny_request(self, code):
92+
error_table = {
93+
'AccessDenied': (401, 'Access denied'),
94+
'InvalidURI': (400, 'Could not parse the specified URI'),
95+
}
96+
resp = Response(content_type='text/xml')
97+
resp.status = error_table[code][0]
98+
error_msg = ('<?xml version="1.0" encoding="UTF-8"?>\r\n'
99+
'<Error>\r\n <Code>%s</Code>\r\n '
100+
'<Message>%s</Message>\r\n</Error>\r\n' %
101+
(code, error_table[code][1]))
102+
if six.PY3:
103+
error_msg = error_msg.encode()
104+
resp.body = error_msg
105+
return resp
106+
107+
def _json_request(self, creds_json):
108+
headers = {'Content-Type': 'application/json'}
109+
try:
110+
response = requests.post('%s/v2.0/s3tokens' % self._request_uri,
111+
headers=headers, data=creds_json,
112+
verify=self._verify)
113+
except requests.exceptions.RequestException as e:
114+
self._logger.info('HTTP connection exception: %s', e)
115+
resp = self._deny_request('InvalidURI')
116+
raise ServiceError(resp)
117+
118+
if response.status_code < 200 or response.status_code >= 300:
119+
self._logger.debug('Keystone reply error: status=%s reason=%s',
120+
response.status_code, response.reason)
121+
resp = self._deny_request('AccessDenied')
122+
raise ServiceError(resp)
123+
124+
return response
125+
126+
def __call__(self, environ, start_response):
127+
"""Handle incoming request. authenticate and send downstream."""
128+
req = Request(environ)
129+
self._logger.debug('Calling S3Token middleware.')
130+
131+
try:
132+
parts = split_path(req.path, 1, 4, True)
133+
version, account, container, obj = parts
134+
except ValueError:
135+
msg = 'Not a path query, skipping.'
136+
self._logger.debug(msg)
137+
return self._app(environ, start_response)
138+
139+
# Read request signature and access id.
140+
if 'Authorization' not in req.headers:
141+
msg = 'No Authorization header. skipping.'
142+
self._logger.debug(msg)
143+
return self._app(environ, start_response)
144+
145+
token = req.headers.get('X-Auth-Token',
146+
req.headers.get('X-Storage-Token'))
147+
if not token:
148+
msg = 'You did not specify an auth or a storage token. skipping.'
149+
self._logger.debug(msg)
150+
return self._app(environ, start_response)
151+
152+
auth_header = req.headers['Authorization']
153+
try:
154+
access, signature = auth_header.split(' ')[-1].rsplit(':', 1)
155+
except ValueError:
156+
msg = 'You have an invalid Authorization header: %s'
157+
self._logger.debug(msg, auth_header)
158+
return self._deny_request('InvalidURI')(environ, start_response)
159+
160+
# NOTE(chmou): This is to handle the special case with nova
161+
# when we have the option s3_affix_tenant. We will force it to
162+
# connect to another account than the one
163+
# authenticated. Before people start getting worried about
164+
# security, I should point that we are connecting with
165+
# username/token specified by the user but instead of
166+
# connecting to its own account we will force it to go to an
167+
# another account. In a normal scenario if that user don't
168+
# have the reseller right it will just fail but since the
169+
# reseller account can connect to every account it is allowed
170+
# by the swift_auth middleware.
171+
force_tenant = None
172+
if ':' in access:
173+
access, force_tenant = access.split(':')
174+
175+
# Authenticate request.
176+
creds = {'credentials': {'access': access,
177+
'token': token,
178+
'signature': signature}}
179+
creds_json = json.dumps(creds)
180+
self._logger.debug('Connecting to Keystone sending this JSON: %s',
181+
creds_json)
182+
# NOTE(vish): We could save a call to keystone by having
183+
# keystone return token, tenant, user, and roles
184+
# from this call.
185+
#
186+
# NOTE(chmou): We still have the same problem we would need to
187+
# change token_auth to detect if we already
188+
# identified and not doing a second query and just
189+
# pass it through to swiftauth in this case.
190+
try:
191+
resp = self._json_request(creds_json)
192+
except ServiceError as e:
193+
resp = e.args[0] # NB: swob.Response, not requests.Response
194+
msg = 'Received error, exiting middleware with error: %s'
195+
self._logger.debug(msg, resp.status_int)
196+
return resp(environ, start_response)
197+
198+
self._logger.debug('Keystone Reply: Status: %d, Output: %s',
199+
resp.status_code, resp.content)
200+
201+
try:
202+
identity_info = resp.json()
203+
token_id = str(identity_info['access']['token']['id'])
204+
tenant = identity_info['access']['token']['tenant']
205+
except (ValueError, KeyError):
206+
error = 'Error on keystone reply: %d %s'
207+
self._logger.debug(error, resp.status_code, resp.content)
208+
return self._deny_request('InvalidURI')(environ, start_response)
209+
210+
req.headers['X-Auth-Token'] = token_id
211+
tenant_to_connect = force_tenant or tenant['id']
212+
if six.PY2 and isinstance(tenant_to_connect, six.text_type):
213+
tenant_to_connect = tenant_to_connect.encode('utf-8')
214+
self._logger.debug('Connecting with tenant: %s', tenant_to_connect)
215+
new_tenant_name = '%s%s' % (self._reseller_prefix, tenant_to_connect)
216+
environ['PATH_INFO'] = environ['PATH_INFO'].replace(account,
217+
new_tenant_name)
218+
return self._app(environ, start_response)
219+
220+
221+
def filter_factory(global_conf, **local_conf):
222+
"""Returns a WSGI filter app for use with paste.deploy."""
223+
conf = global_conf.copy()
224+
conf.update(local_conf)
225+
226+
def auth_filter(app):
227+
return S3Token(app, conf)
228+
return auth_filter

swift3/test/functional/conf/proxy-server.conf.in

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,8 @@ use = egg:swift#gatekeeper
4747
use = egg:swift#memcache
4848

4949
[filter:s3token]
50-
paste.filter_factory = keystonemiddleware.s3_token:filter_factory
51-
auth_host = localhost
52-
auth_port = 35357
53-
auth_protocol = http
54-
auth_uri = http://localhost:5000/
50+
use = egg:swift3#s3token
51+
auth_uri = http://localhost:35357/
5552

5653
[filter:authtoken]
5754
paste.filter_factory = keystonemiddleware.auth_token:filter_factory

0 commit comments

Comments
 (0)