diff --git a/websockify/encode_url.py b/websockify/encode_url.py index 6871b3a..38e0f0b 100644 --- a/websockify/encode_url.py +++ b/websockify/encode_url.py @@ -5,7 +5,9 @@ import sys import hashlib import base64 import datetime +import group # Please change the salt for your own project. +#SALT = "Some salt for security. liqun@ncl.sg" SALT = "Some salt for security. Please change it in your project. liqun@ncl.sg" def qencode(str, create_date=datetime.date.today(), salt=SALT): ''' The func encode str, hash it and base64 it.''' @@ -20,42 +22,57 @@ def qencode(str, create_date=datetime.date.today(), salt=SALT): def qdecode(str, valid_daynum=3, salt=SALT): ''' The func decode str, validate with hash and base64.decode it. If valid_daynum =0, only today is valid.''' - str1 = base64.urlsafe_b64decode(str) + if str[-1] == '/': + str1 = base64.urlsafe_b64decode(str[0:-1]) + else: + str1 = base64.urlsafe_b64decode(str) pos = str1.find(':') if pos == -1: - return '' + return '','Err:not find :' hash = str1[0:pos-8] tstr = str1[pos-8:pos] url_date = datetime.date(int(str1[pos-8:pos-4]), int(str1[pos-4:pos-2]), int(str1[pos-2:pos])) today = datetime.date.today() if (today - url_date) > datetime.timedelta(valid_daynum): - return '' + return '', 'Err: Timeout' alg = hashlib.sha256() alg.update(str1[pos-8:]) alg.update(salt) hash1 = alg.hexdigest() if hash != hash1: - return '' - return str1[pos+1:] -def get_server_from_path(path, is_encoded, valid_daynum=3, salt=SALT): + return '', 'Err: Wrong hash' + return str1[pos+1:], '' +def get_server_from_path(path, is_encoded, valid_daynum=3, salt=SALT, gpolicy=group.gpolicy): '''The func decode host port from path parameter. path looks like [/qencode(n1.soc.cloud.ncl.sg:5901)] ''' + phost = '' + if path[-1] == '/': + path = path[0:-1] + try: pos = path.rfind('/') if pos == -1: return '', 0 if is_encoded: - str = qdecode(path[pos+1:], valid_daynum, salt) + (str,err) = qdecode(path[pos+1:], valid_daynum, salt) + if err != '': + phost = err else: str = path[pos+1:] if str == '': - return '', 0 - phost = '' - phost = str.split(':')[0] - pport = int(str.split(':')[1]) + return phost, 0 + part_list = str.split(':') + phost = part_list[0] + pport = int(part_list[1]) + if len(part_list) > 2: + username = part_list[2] + # check the access ability. + if not group.can_access(username, phost, gpolicy): + return 'Err: group policy block', 0 except: - return phost, 0 + raise + #return phost, 0 return phost, pport def test_basic(): @@ -66,23 +83,45 @@ def test_basic(): enc = qencode(str) print enc print base64.urlsafe_b64decode(enc) - dec = qdecode(enc) + (dec,err) = qdecode(enc) assert str == dec assert get_server_from_path('/'+enc, True) == ('n1.soc.cloud.ncl.sg', 5901) enc = qencode(str, datetime.date(2018, 2, 24)) - assert (get_server_from_path('/'+enc, True)) == ('', 0) + assert (get_server_from_path('/'+enc, True)) == ('Err: Timeout', 0) enc = qencode(str, datetime.date.today(), 'test salt') - dec = qdecode(enc, 3, 'test salt') + (dec,err) = qdecode(enc, 3, 'test salt') assert str == dec - dec = qdecode(enc, 3, 'test salt1') + (dec,err) = qdecode(enc, 3, 'test salt1') assert dec == '' str = "n1.soc.cloud.ncl.sg:5901:ntechni3" enc = qencode(str) print enc print base64.urlsafe_b64decode(enc) - dec = qdecode(enc) + (dec,err) = qdecode(enc) assert str == dec assert get_server_from_path('/'+enc, True) == ('n1.soc.cloud.ncl.sg', 5901) + (dec,err) = qdecode(enc + '/') + assert str == dec + gpolicy = { + "ExperimentDomainName":"soc.cloud.ncl.sg", + 'Groups':[{ + 'Name':'Red', + 'Users': ["user1", "ntechni3"], + 'Hosts': ["n1"] + }, + { + 'Name':'Blue', + 'Users': ['user1'], + 'Hosts': ['n2','n3'] + }] + } + str = "n1.soc.cloud.ncl.sg:5901:ntechni3" + enc = qencode(str) + assert get_server_from_path('/'+enc, True, 3, SALT, gpolicy) == ('n1.soc.cloud.ncl.sg', 5901) + str = "n2.soc.cloud.ncl.sg:5901:ntechni3" + enc = qencode(str) + assert get_server_from_path('/'+enc, True, 3, SALT, gpolicy) == ('Err: group policy block', 0) + def main(): '''The func is the main func.''' import sys @@ -93,11 +132,12 @@ def main(): elif (len(sys.argv) == 2): print qencode(sys.argv[1]) elif (len(sys.argv) == 3) and (sys.argv[1] == "decode"): - print qdecode(sys.argv[2]) + print get_server_from_path(sys.argv[2], True) + #print qdecode(sys.argv[2]) elif (len(sys.argv) == 5) and (sys.argv[1] == "decode"): - print qdecode(sys.argv[2],sys.argv[3],sys.argv[4]) + print qdecode(sys.argv[2],int(sys.argv[3]),sys.argv[4]) elif (len(sys.argv) == 5) and (sys.argv[1] == "encode"): - print qencode(sys.argv[2],sys.argv[3],sys.argv[4]) + print qencode(sys.argv[2],int(sys.argv[3]),sys.argv[4]) elif (len(sys.argv) == 3) and (sys.argv[1] == "debase"): str1 = base64.urlsafe_b64decode(sys.argv[2]) print str1 diff --git a/websockify/group.py b/websockify/group.py new file mode 100644 index 0000000..3800ee1 --- /dev/null +++ b/websockify/group.py @@ -0,0 +1,221 @@ +#!/usr/bin/env python +import subprocess + +gpolicy = { } + +def group(users, hosts, ousers, ohosts): + '''The func handle group policy for one group. Users can access hosts.''' + pass +def get_users_hosts(gpolicy): + '''The func Get the all user list, hosts, hosts_access''' + users = set() + hosts = {} + hosts_access = {} + # Get the all user list, hosts, hosts_access + for group in gpolicy['Groups']: + if not group.has_key('Name'): + print "The group do not have key [Name]" + continue; + for user in group['Users']: + users.add(user) + if not hosts_access.has_key(user): + hosts_access[user] = set() + if group.has_key('Hosts'): + hosts_access[user] = hosts_access[user].union(group['Hosts']) + # get all host list. + if group.has_key('Hosts'): + for host in group['Hosts']: + hosts[host] = '' + return users, hosts, hosts_access +def get_super_users(gpolicy, users, hosts_access): + '''The func Get all super user list. User in super user list need be + deleted in user list and hosts_access.''' + super_users = set() + # Get all super user list. + for group in gpolicy['Groups']: + if not group.has_key('Name'): + print "The group do not have key [Name]" + continue; + if not group.has_key('Hosts'): + for user in group['Users']: + # Delete super user from normal user list. + if user in users: + users.remove(user) + del hosts_access[user] + super_users.add(user) + return (super_users, users, hosts_access) +def get_unaccess_hosts(hosts_access, hosts): + hosts_unaccess = {} + for user in hosts_access.keys(): + hosts_unaccess[user] = set() + for host in hosts: + if host not in hosts_access[user]: + hosts_unaccess[user].add(host) + return hosts_unaccess +def can_access(username, node_url, gpolicy): + '''The func return true or False, based on gpolicy allowing username to node_url or not.''' + if not gpolicy.has_key('ExperimentDomainName'): + print 'Err: no ExperimentDomainName' + return True + ename = gpolicy['ExperimentDomainName'] + exp_name = ename + dnode = node_url[:node_url.find('.')] + dexp = node_url[node_url.find('.')+1:] + if dexp != exp_name : + # not the experience group return true. + return True + for group in gpolicy['Groups']: + find_host = False + find_user = False + if not group.has_key('Name'): + print "The group do not have key [Name]" + continue; + if group.has_key('Hosts'): + for host in group['Hosts']: + if host == dnode: + find_host = True + else: + #super user group, can access all nodes. + find_host = True + if group.has_key('Users'): + for user in group['Users']: + if username == user: + find_user = True + if find_host and find_user : + return True + return False + +def group_exp(gpolicy): + ename = gpolicy['ExperimentDomainName'] + users = () + super_users = () + hosts = {} + hosts_access = {} + hosts_unaccess = {} + # Get the all user list, hosts, hosts_access + (users, hosts, hosts_access) = get_users_hosts(gpolicy) + # Get all super user list. + (super_users, users, hosts_access) = get_super_users(gpolicy, users, hosts_access) + # produce unaccess hosts for every user. + hosts_unaccess = get_unaccess_hosts(hosts_access, hosts) + + # For every normal user, get all accessable host list and give access right, other give no right. + for user in users: + for host in hosts_access[user]: + hosts[host] += 'sudo usermod -e "" %s\n' % user + for user in users: + for host in hosts_unaccess[user]: + hosts[host] += 'sudo usermod -e 1 %s\n' % user + # For every super user, give all access. + for user in super_users: + for host in hosts.keys(): + hosts[host] += 'sudo usermod -e "" %s\n' % user + return hosts +def test(): + gpolicy = { + "ExperimentDomainName":"EnterpriseNetwork.NYPSOC.ncl.sg", + 'Groups':[] + } + assert get_users_hosts(gpolicy) == (set([]), {}, {}) + + gpolicy = { + "ExperimentDomainName":"EnterpriseNetwork.NYPSOC.ncl.sg", + 'Groups':[{ + 'Name':'Red', + 'Users': ["user1", "user2"], + 'Hosts': ["n1"] + }, + { + 'Name':'Blue', + 'Users': ['user3'], + 'Hosts': ['n2','n3'] + }, + { + 'Name':'Grey', + 'Users': ['user4'], + 'Hosts': [] + }, + { + 'Name':'Super', + 'Users': ['user3'] + }] + } + (users, hosts, hosts_access) = get_users_hosts(gpolicy) + assert (users, hosts, hosts_access) == (set(['user4', 'user2', 'user3', 'user1']),\ + {'n1': '', 'n2': '', 'n3': ''}, \ + {'user4': set([]), 'user2': set(['n1']), 'user3': set(['n2', 'n3']), 'user1': set(['n1'])}) + assert get_unaccess_hosts(hosts_access, hosts) == { + 'user4': set(['n1', 'n2', 'n3']), 'user2': set(['n2', 'n3']), + 'user3': set(['n1']), 'user1': set(['n2', 'n3'])} + (super_users, users, hosts_access) = get_super_users(gpolicy, users, hosts_access) + assert (super_users, users, hosts_access) == (set(['user3']), + set(['user4', 'user2', 'user1']), \ + {'user4': set([]), 'user2': set(['n1']), 'user1': set(['n1'])}) + assert get_unaccess_hosts(hosts_access, hosts) == { + 'user4': set(['n1', 'n2', 'n3']), 'user2': set(['n2', 'n3']), + 'user1': set(['n2', 'n3'])} + print group_exp(gpolicy) + group_exp(gpolicy) == {'n1': 'sudo usermod -e "" user2\nsudo usermod -e "" user1\nsudo usermod -e 1 user4\nsudo usermod -e "" user3\n', 'n2': 'sudo usermod -e 1 user4\nsudo usermod -e 1 user2\nsudo usermod -e 1 user1\nsudo usermod -e "" user3\n', 'n3': 'sudo usermod -e 1 user4\nsudo usermod -e 1 user2\nsudo usermod -e 1 user1\nsudo usermod -e "" user3\n'} + + assert get_unaccess_hosts({'user1': set(['n1'])}, {'n1': ''}) == {'user1': set([])} + assert get_unaccess_hosts({'user1': set([])}, {'n1': ''}) == {'user1': set(['n1'])} + assert get_unaccess_hosts({'user1': set(['n1', 'n2'])}, {'n1': '', 'n2': ''}) == {'user1': set([])} + + gpolicy = { + "ExperimentDomainName":"EnterpriseNetwork.NYPSOC.ncl.sg", + 'Groups':[{ + 'Name':'Red', + 'Users': ["user1", "user2"], + 'Hosts': ["n1"] + }, + { + 'Name':'Blue', + 'Users': ['user1'], + 'Hosts': ['n2','n3'] + } ] + } + (users, hosts, hosts_access) = get_users_hosts(gpolicy) + #print (users, hosts, hosts_access) + assert (users, hosts, hosts_access) == (set(['user2', 'user1']), \ + {'n1': '', 'n2': '', 'n3': ''}, {'user2': set(['n1']), 'user1': set(['n1', 'n2', 'n3'])}) + gpolicy = { + "ExperimentDomainName":"EnterpriseNetwork.NYPSOC.ncl.sg", + 'Groups':[{ + 'Name':'Red', + 'Users': ["user1", "user2"], + 'Hosts': ["n1"] + }, + { + 'Name':'Blue', + 'Users': ['user1'], + 'Hosts': ['n2','n3'] + }, + { 'Name':'superusers', 'Users':['suser']} + ] + } + assert can_access('user1', 'n1.EnterpriseNetwork.NYPSOC.ncl.sg', gpolicy) == True + assert can_access('user1', 'n1.another.NYPSOC.ncl.sg', gpolicy) == True + assert can_access('user1', 'n2.EnterpriseNetwork.NYPSOC.ncl.sg', gpolicy) == True + assert can_access('user2', 'n2.EnterpriseNetwork.NYPSOC.ncl.sg', gpolicy) == False + assert can_access('user3', 'n2.EnterpriseNetwork.NYPSOC.ncl.sg', gpolicy) == False + assert can_access('suser', 'n2.EnterpriseNetwork.NYPSOC.ncl.sg', gpolicy) == True + + +def main(): + '''The func is the main func.''' + import sys + if (len(sys.argv) == 2) and (sys.argv[1] == "test"): + test() + print "Pass all test" + exit() + if (len(sys.argv) == 2) and (sys.argv[1] == "do"): + gp1 = { } + node_cmd_list = group_exp(gp1) + ename = gp1['ExperimentDomainName'] + + for (node, cmd) in node_cmd_list.items(): + cmdline = "echo '%s' | ssh %s.%s" % (cmd, node, ename) + subprocess.call(cmdline, shell = True) + +if __name__ == "__main__": + main() diff --git a/websockify/websocketproxy.py b/websockify/websocketproxy.py index 7adcd1f..3c72287 100644 --- a/websockify/websocketproxy.py +++ b/websockify/websocketproxy.py @@ -36,9 +36,9 @@ URL_PATH_DEF_VNCSERVER = True # Whether is URL path encoded URL_PATH_ENCODED = False # URL valid day number after creating. -URL_VALID_DAYNUM = 3 +URL_VALID_DAYNUM = 1 # Salt used to valide the hash of the URL. -URL_SALT = 'Some salt used to hash the URL for integrity validation. liqun@ncl.sg' +URL_SALT = "Some salt for security. Please change it in your project. liqun@ncl.sg" class ProxyRequestHandler(websockifyserver.WebSockifyRequestHandler): @@ -114,7 +114,9 @@ Traffic Legend: (ptarget_host, ptarget_port) = encode_url.get_server_from_path(\ self.path, URL_PATH_ENCODED, URL_VALID_DAYNUM, URL_SALT) if ptarget_port == 0: - raise self.server.EClose('Cannot decode path.') + print 'Error: URL encode error[%s]' % ptarget_host + return + #raise self.server.EClose('Cannot decode path.') msg = "connecting to: %s:%s" % (ptarget_host, ptarget_port) else: msg = "connecting to: %s:%s" % (