bae0874faf
An invalid URI should return a BadRequest error rather than making the metadata proxy bomb out. Closes-bug: #1371160 Change-Id: Ifb495f9e8929062a0c24d090c3e702109a38803a
183 lines
6.2 KiB
Python
183 lines
6.2 KiB
Python
# Copyright 2012 New Dream Network, LLC (DreamHost)
|
|
#
|
|
# 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 httplib
|
|
import socket
|
|
|
|
import eventlet
|
|
eventlet.monkey_patch()
|
|
|
|
import httplib2
|
|
from oslo.config import cfg
|
|
import six.moves.urllib.parse as urlparse
|
|
import webob
|
|
|
|
from neutron.agent.linux import daemon
|
|
from neutron.common import config
|
|
from neutron.common import utils
|
|
from neutron.openstack.common import log as logging
|
|
from neutron import wsgi
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class UnixDomainHTTPConnection(httplib.HTTPConnection):
|
|
"""Connection class for HTTP over UNIX domain socket."""
|
|
def __init__(self, host, port=None, strict=None, timeout=None,
|
|
proxy_info=None):
|
|
httplib.HTTPConnection.__init__(self, host, port, strict)
|
|
self.timeout = timeout
|
|
|
|
def connect(self):
|
|
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
if self.timeout:
|
|
self.sock.settimeout(self.timeout)
|
|
self.sock.connect(cfg.CONF.metadata_proxy_socket)
|
|
|
|
|
|
class NetworkMetadataProxyHandler(object):
|
|
"""Proxy AF_INET metadata request through Unix Domain socket.
|
|
|
|
The Unix domain socket allows the proxy access resource that are not
|
|
accessible within the isolated tenant context.
|
|
"""
|
|
|
|
def __init__(self, network_id=None, router_id=None):
|
|
self.network_id = network_id
|
|
self.router_id = router_id
|
|
|
|
if network_id is None and router_id is None:
|
|
msg = _('network_id and router_id are None. One must be provided.')
|
|
raise ValueError(msg)
|
|
|
|
@webob.dec.wsgify(RequestClass=webob.Request)
|
|
def __call__(self, req):
|
|
LOG.debug(_("Request: %s"), req)
|
|
try:
|
|
return self._proxy_request(req.remote_addr,
|
|
req.method,
|
|
req.path_info,
|
|
req.query_string,
|
|
req.body)
|
|
except Exception:
|
|
LOG.exception(_("Unexpected error."))
|
|
msg = _('An unknown error has occurred. '
|
|
'Please try your request again.')
|
|
return webob.exc.HTTPInternalServerError(explanation=unicode(msg))
|
|
|
|
def _proxy_request(self, remote_address, method, path_info,
|
|
query_string, body):
|
|
headers = {
|
|
'X-Forwarded-For': remote_address,
|
|
}
|
|
|
|
if self.router_id:
|
|
headers['X-Neutron-Router-ID'] = self.router_id
|
|
else:
|
|
headers['X-Neutron-Network-ID'] = self.network_id
|
|
|
|
url = urlparse.urlunsplit((
|
|
'http',
|
|
'169.254.169.254', # a dummy value to make the request proper
|
|
path_info,
|
|
query_string,
|
|
''))
|
|
|
|
h = httplib2.Http()
|
|
resp, content = h.request(
|
|
url,
|
|
method=method,
|
|
headers=headers,
|
|
body=body,
|
|
connection_type=UnixDomainHTTPConnection)
|
|
|
|
if resp.status == 200:
|
|
LOG.debug(resp)
|
|
LOG.debug(content)
|
|
response = webob.Response()
|
|
response.status = resp.status
|
|
response.headers['Content-Type'] = resp['content-type']
|
|
response.body = content
|
|
return response
|
|
elif resp.status == 400:
|
|
return webob.exc.HTTPBadRequest()
|
|
elif resp.status == 404:
|
|
return webob.exc.HTTPNotFound()
|
|
elif resp.status == 409:
|
|
return webob.exc.HTTPConflict()
|
|
elif resp.status == 500:
|
|
msg = _(
|
|
'Remote metadata server experienced an internal server error.'
|
|
)
|
|
LOG.debug(msg)
|
|
return webob.exc.HTTPInternalServerError(explanation=unicode(msg))
|
|
else:
|
|
raise Exception(_('Unexpected response code: %s') % resp.status)
|
|
|
|
|
|
class ProxyDaemon(daemon.Daemon):
|
|
def __init__(self, pidfile, port, network_id=None, router_id=None):
|
|
uuid = network_id or router_id
|
|
super(ProxyDaemon, self).__init__(pidfile, uuid=uuid)
|
|
self.network_id = network_id
|
|
self.router_id = router_id
|
|
self.port = port
|
|
|
|
def run(self):
|
|
handler = NetworkMetadataProxyHandler(
|
|
self.network_id,
|
|
self.router_id)
|
|
proxy = wsgi.Server('neutron-network-metadata-proxy')
|
|
proxy.start(handler, self.port)
|
|
proxy.wait()
|
|
|
|
|
|
def main():
|
|
opts = [
|
|
cfg.StrOpt('network_id',
|
|
help=_('Network that will have instance metadata '
|
|
'proxied.')),
|
|
cfg.StrOpt('router_id',
|
|
help=_('Router that will have connected instances\' '
|
|
'metadata proxied.')),
|
|
cfg.StrOpt('pid_file',
|
|
help=_('Location of pid file of this process.')),
|
|
cfg.BoolOpt('daemonize',
|
|
default=True,
|
|
help=_('Run as daemon.')),
|
|
cfg.IntOpt('metadata_port',
|
|
default=9697,
|
|
help=_("TCP Port to listen for metadata server "
|
|
"requests.")),
|
|
cfg.StrOpt('metadata_proxy_socket',
|
|
default='$state_path/metadata_proxy',
|
|
help=_('Location of Metadata Proxy UNIX domain '
|
|
'socket'))
|
|
]
|
|
|
|
cfg.CONF.register_cli_opts(opts)
|
|
# Don't get the default configuration file
|
|
cfg.CONF(project='neutron', default_config_files=[])
|
|
config.setup_logging()
|
|
utils.log_opt_values(LOG)
|
|
proxy = ProxyDaemon(cfg.CONF.pid_file,
|
|
cfg.CONF.metadata_port,
|
|
network_id=cfg.CONF.network_id,
|
|
router_id=cfg.CONF.router_id)
|
|
|
|
if cfg.CONF.daemonize:
|
|
proxy.start()
|
|
else:
|
|
proxy.run()
|