Skip to content

Commit

Permalink
Merge "Add option for cloud admin access only for analytics REST API"
Browse files Browse the repository at this point in the history
  • Loading branch information
Zuul authored and opencontrail-ci-admin committed Jun 15, 2016
2 parents 7c863c8 + 5492f71 commit 8e4e211
Show file tree
Hide file tree
Showing 27 changed files with 555 additions and 171 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#
import requests, json
from requests.exceptions import ConnectionError
from requests.auth import HTTPBasicAuth

class AnalyticApiClient(object):
def __init__(self, cfg):
Expand All @@ -23,7 +24,8 @@ def init_client(self):
def _get_url_json(self, url):
if url is None:
return {}
page = self.client.get(url)
page = self.client.get(url, auth=HTTPBasicAuth(
self.config.admin_user(), self.config.admin_password()))
if page.status_code == 200:
return json.loads(page.text)
raise ConnectionError, "bad request " + url
Expand Down
11 changes: 9 additions & 2 deletions src/analytics/contrail-topology/contrail_topology/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
import argparse, os, ConfigParser, sys, re
from pysandesh.sandesh_base import *
from pysandesh.gen_py.sandesh.ttypes import SandeshLevel
from sandesh_common.vns.constants import ModuleNames, HttpPortTopology, API_SERVER_DISCOVERY_SERVICE_NAME
from sandesh_common.vns.constants import ModuleNames, HttpPortTopology, \
API_SERVER_DISCOVERY_SERVICE_NAME, OpServerAdminPort
from sandesh_common.vns.ttypes import Module
import discoveryclient.client as discovery_client
import traceback
Expand Down Expand Up @@ -69,7 +70,7 @@ def parse(self):

defaults = {
'collectors' : None,
'analytics_api' : ['127.0.0.1:8081'],
'analytics_api' : ['127.0.0.1:' + str(OpServerAdminPort)],
'log_local' : False,
'log_level' : SandeshLevel.SYS_DEBUG,
'log_category' : '',
Expand Down Expand Up @@ -224,6 +225,12 @@ def frequency(self):
def http_port(self):
return self._args.http_server_port

def admin_user(self):
return self._args.admin_user

def admin_password(self):
return self._args.admin_password

def sandesh_send_rate_limit(self):
return self._args.sandesh_send_rate_limit

Expand Down
14 changes: 14 additions & 0 deletions src/api-lib/vnc_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1148,4 +1148,18 @@ def set_user_roles(self, roles):
self._headers['X-API-ROLE'] = (',').join(roles)
#end set_user_roles

"""
validate user token. Optionally, check token authorization for an object.
rv {'token_info': <token-info>, 'permissions': 'RWX'}
"""
def obj_perms(self, token, obj_uuid=None):
query = 'token=%s' % token
if obj_uuid:
query += '&uuid=%s' % obj_uuid
try:
rv = self._request_server(rest.OP_GET, "/obj-perms", data=query)
return json.loads(rv)
except PermissionDenied:
return None

#end class VncApi
3 changes: 2 additions & 1 deletion src/config/api-server/vnc_auth_keystone.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,8 @@ def verify_signed_token(self, user_token):
# gets called from keystone middleware after token check
def token_valid(self, env, start_response):
status = env.get('HTTP_X_IDENTITY_STATUS')
return True if status != 'Invalid' else False
token_info = env.get('keystone.token_info')
return token_info if status != 'Invalid' else None

def validate_user_token(self, request):
# following config forces keystone middleware to always return the result
Expand Down
17 changes: 9 additions & 8 deletions src/config/api-server/vnc_cfg_api_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -1713,14 +1713,8 @@ def obj_perms_http_get(self):
if 'token' not in get_request().query:
raise cfgm_common.exceptions.HttpError(
400, 'User token needed for validation')
if 'uuid' not in get_request().query:
raise cfgm_common.exceptions.HttpError(
400, 'Object uuid needed for validation')
obj_uuid = get_request().query.uuid
user_token = get_request().query.token

result = {'permissions' : ''}

# get permissions in internal context
try:
orig_context = get_context()
Expand All @@ -1734,11 +1728,18 @@ def obj_perms_http_get(self):
i_req = context.ApiInternalRequest(
b_req.url, b_req.urlparts, b_req.environ, b_req.headers, None, None)
set_context(context.ApiContext(internal_req=i_req))
if self._auth_svc.validate_user_token(get_request()):
result['permissions']= self._permissions.obj_perms(get_request(), obj_uuid)
token_info = self._auth_svc.validate_user_token(get_request())
finally:
set_context(orig_context)

# roles in result['token_info']['access']['user']['roles']
if token_info:
result = {'token_info' : token_info}
if 'uuid' in get_request().query:
obj_uuid = get_request().query.uuid
result['permissions'] = self._permissions.obj_perms(get_request(), obj_uuid)
else:
raise cfgm_common.exceptions.HttpError(403, " Permission denied")
return result
#end check_obj_perms_http_get

Expand Down
9 changes: 6 additions & 3 deletions src/config/common/analytics_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,13 @@ def __init__(self, endpoint, data={}):
self.endpoint = endpoint
self.data = data

def request(self, path, fqdn_uuid, data=None):
def request(self, path, fqdn_uuid, user_token=None,
data=None):
req_data = dict(self.data)
if data:
req_data.update(data)

req_params = self._get_req_params(data=req_data)
req_params = self._get_req_params(user_token, data=req_data)

url = urlparse.urljoin(self.endpoint, path + fqdn_uuid)
resp = requests.get(url, **req_params)
Expand All @@ -51,13 +52,15 @@ def request(self, path, fqdn_uuid, data=None):

return resp.json()

def _get_req_params(self, data=None):
def _get_req_params(self, user_token, data=None):
req_params = {
'headers': {
'Accept': 'application/json'
},
'data': data,
'allow_redirects': False,
}
if user_token:
req_params['headers']['X-AUTH-TOKEN'] = user_token

return req_params
2 changes: 1 addition & 1 deletion src/config/common/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1111,7 +1111,7 @@ def __call__(self, env, start_response):
return self._reject_request(env, start_response)

token_info = self._validate_user_token(user_token, env)
# env['keystone.token_info'] = token_info
env['keystone.token_info'] = token_info
user_headers = self._build_user_headers(token_info)
self._add_headers(env, user_headers)
return self.app(env, start_response)
Expand Down
4 changes: 3 additions & 1 deletion src/config/common/tests/tools/install_venv_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,9 @@ def pip_install(self, find_links, *args):
find_links_str = ' '.join('--find-links file://'+x for x in find_links)
cmd_array = ['%stools/with_venv.sh' %(os.environ.get('tools_path', '')),
'python', '.venv/bin/pip', 'install',
'--upgrade', '--no-cache-dir']
'--upgrade']
if not args[0].startswith('pip'):
cmd_array.extend(['--no-cache-dir'])
for link in find_links:
cmd_array.extend(['--find-links', 'file://'+link])
self.run_command(cmd_array + list(args),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ def query_uve(self, filter_string):
path = "/analytics/uves/vrouter/"
response_dict = {}
try:
response = self._analytics.request(path, filter_string)
response = self._analytics.request(path, filter_string,
user_token=self._vnc_lib.get_auth_token())
for values in response['value']:
response_dict[values['name']] = values['value']
except analytics_client.OpenContrailAPIFailed:
Expand Down
5 changes: 4 additions & 1 deletion src/opserver/SConscript
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ OpEnv = BuildEnv.Clone()
setup_sources = [
'setup.py',
'MANIFEST.in',
'requirements.txt',
]

setup_sources_rules = []
Expand Down Expand Up @@ -43,7 +44,9 @@ local_sources = [
'gendb_move_tables.py',
'alarm_notify.py',
'config_handler.py',
'alarmgen_config_handler.py'
'alarmgen_config_handler.py',
'vnc_cfg_api_client.py',
'opserver_local.py',
]

plugins_sources = [
Expand Down
15 changes: 11 additions & 4 deletions src/opserver/flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def run(self):
def parse_args(self):
"""
Eg. python flow.py --analytics-api-ip 127.0.0.1
--analytics-api-port 8081
--analytics-api-port 8181
--vrouter a6s23
--source-vn default-domain:default-project:vn1
--destination-vn default-domain:default-project:vn2
Expand All @@ -94,7 +94,7 @@ def parse_args(self):
"""
defaults = {
'analytics_api_ip': '127.0.0.1',
'analytics_api_port': '8081',
'analytics_api_port': '8181',
'start_time': 'now-10m',
'end_time': 'now',
'direction' : 'ingress',
Expand Down Expand Up @@ -139,6 +139,11 @@ def parse_args(self):
help="Show vmi uuid information")
parser.add_argument(
"--verbose", action="store_true", help="Show internal information")
parser.add_argument(
"--admin-user", help="Name of admin user", default="admin")
parser.add_argument(
"--admin-password", help="Password of admin user",
default="contrail123")
self._args = parser.parse_args()

try:
Expand Down Expand Up @@ -332,13 +337,15 @@ def query(self):
json.dumps(flow_query.__dict__))
print ''
resp = OpServerUtils.post_url_http(
flow_url, json.dumps(flow_query.__dict__))
flow_url, json.dumps(flow_query.__dict__), self._args.admin_user,
self._args.admin_password)
result = {}
if resp is not None:
resp = json.loads(resp)
qid = resp['href'].rsplit('/', 1)[1]
result = OpServerUtils.get_query_result(
self._args.analytics_api_ip, self._args.analytics_api_port, qid)
self._args.analytics_api_ip, self._args.analytics_api_port, qid,
self._args.admin_user, self._args.admin_password)
return result
# end query

Expand Down
45 changes: 27 additions & 18 deletions src/opserver/introspect_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,37 @@
# Copyright (c) 2013 Juniper Networks, Inc. All rights reserved.
#

import urllib, urllib2
import urllib
import xmltodict
import json
import requests
from lxml import etree
import socket

from requests.auth import HTTPBasicAuth

class JsonDrv (object):

def _http_con(self, url):
return urllib2.urlopen(url)

def load(self, url):
return json.load(self._http_con(url))

def load(self, url, user, password):
try:
if user and password:
auth=HTTPBasicAuth(user, password)
else:
auth=None
resp = requests.get(url, auth=auth)
return json.loads(resp.text)
except requests.ConnectionError, e:
print "Socket Connection error : " + str(e)
return None

class XmlDrv (object):

def load(self, url):
def load(self, url, user, password):
try:
resp = requests.get(url)
if user and password:
auth=HTTPBasicAuth(user, password)
else:
auth=None
resp = requests.get(url, auth=auth)
return etree.fromstring(resp.text)
except requests.ConnectionError, e:
print "Socket Connection error : " + str(e)
Expand Down Expand Up @@ -54,14 +63,14 @@ def _mk_url_str(self, path, query):
return path+query_str
return "http://%s:%d/%s%s" % (self._ip, self._port, path, query_str)

def dict_get(self, path='', query=None, drv=None):
try:
if path:
if drv is not None:
return drv().load(self._mk_url_str(path, query))
return self._drv.load(self._mk_url_str(path, query))
except urllib2.HTTPError:
return None
def dict_get(self, path='', query=None, drv=None, user=None,
password=None):
if path:
if drv is not None:
return drv().load(self._mk_url_str(path, query), user,
password)
return self._drv.load(self._mk_url_str(path, query), user,
password)
# end dict_get


Expand Down
13 changes: 9 additions & 4 deletions src/opserver/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def run(self):
def parse_args(self):
"""
Eg. python log.py --analytics-api-ip 127.0.0.1
--analytics-api-port 8081
--analytics-api-port 8181
--source 127.0.0.1
--node-type Control
--module bgp | cfgm | vnswad
Expand All @@ -105,7 +105,7 @@ def parse_args(self):
"""
defaults = {
'analytics_api_ip': '127.0.0.1',
'analytics_api_port': '8081',
'analytics_api_port': '8181',
}

parser = argparse.ArgumentParser(
Expand Down Expand Up @@ -158,6 +158,9 @@ def parse_args(self):
parser.add_argument("--output-file", "-o", help="redirect output to file")
parser.add_argument("--json", help="Dump output as json", action="store_true")
parser.add_argument("--all", action="store_true", help=argparse.SUPPRESS)
parser.add_argument("--admin-user", help="Name of admin user", default="admin")
parser.add_argument("--admin-password", help="Password of admin user",
default="contrail123")
self._args = parser.parse_args()
return 0
# end parse_args
Expand Down Expand Up @@ -457,13 +460,15 @@ def query(self):
print 'Performing query: {0}'.format(
json.dumps(messages_query.__dict__))
resp = OpServerUtils.post_url_http(
messages_url, json.dumps(messages_query.__dict__))
messages_url, json.dumps(messages_query.__dict__),
self._args.admin_user, self._args.admin_password)
result = {}
if resp is not None:
resp = json.loads(resp)
qid = resp['href'].rsplit('/', 1)[1]
result = OpServerUtils.get_query_result(
self._args.analytics_api_ip, self._args.analytics_api_port, qid)
self._args.analytics_api_ip, self._args.analytics_api_port, qid,
self._args.admin_user, self._args.admin_password)
return result
# end query

Expand Down

0 comments on commit 8e4e211

Please sign in to comment.