You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by bh...@apache.org on 2015/03/10 11:09:24 UTC

git commit: updated refs/heads/concurrent-password-server to 3951ab0

Repository: cloudstack
Updated Branches:
  refs/heads/concurrent-password-server [created] 3951ab00e


CLOUDSTACK-8272: Python based file-lock free password server implementation

- VRs are single CPU, so Threading based implementation favoured than Forking based
- Implements a Python based password server that does not use file based locks
- Saving password mechanism is provided by using secure token only to VR (localhost)
- Old serve_password implementation is removed
- Runs with Python 2.6+ with no external dependencies
- Locks used within threads for extra safety

Signed-off-by: Rohit Yadav <ro...@shapeblue.com>


Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo
Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/3951ab00
Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/3951ab00
Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/3951ab00

Branch: refs/heads/concurrent-password-server
Commit: 3951ab00e25458c9c804b190ae7c7758f57985a3
Parents: 290938b
Author: Rohit Yadav <ro...@shapeblue.com>
Authored: Tue Mar 10 15:35:31 2015 +0530
Committer: Rohit Yadav <ro...@shapeblue.com>
Committed: Tue Mar 10 15:35:31 2015 +0530

----------------------------------------------------------------------
 .../config/opt/cloud/bin/passwd_server_ip       |  17 +-
 .../config/opt/cloud/bin/passwd_server_ip.py    | 204 +++++++++++++++++++
 .../debian/config/opt/cloud/bin/savepassword.sh |  43 +---
 .../config/opt/cloud/bin/serve_password.sh      | 103 ----------
 4 files changed, 221 insertions(+), 146 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cloudstack/blob/3951ab00/systemvm/patches/debian/config/opt/cloud/bin/passwd_server_ip
----------------------------------------------------------------------
diff --git a/systemvm/patches/debian/config/opt/cloud/bin/passwd_server_ip b/systemvm/patches/debian/config/opt/cloud/bin/passwd_server_ip
index 4622860..b76ebe7 100755
--- a/systemvm/patches/debian/config/opt/cloud/bin/passwd_server_ip
+++ b/systemvm/patches/debian/config/opt/cloud/bin/passwd_server_ip
@@ -20,13 +20,12 @@
 addr=$1;
 while [ "$ENABLED" == "1" ]
 do
-	socat -lf /var/log/cloud.log TCP4-LISTEN:8080,reuseaddr,fork,crnl,bind=$addr SYSTEM:"/opt/cloud/bin/serve_password.sh \"\$SOCAT_PEERADDR\""
-
-	rc=$?
-	if [ $rc -ne 0 ]
-	then
-		logger -t cloud "Password server failed with error code $rc. Restarting socat..."
-		sleep 3
-	fi
-        . /etc/default/cloud-passwd-srvr
+    python /opt/cloud/bin/password_server_ip.py >/dev/null 2>/dev/null
+    rc=$?
+    if [ $rc -ne 0 ]
+    then
+        logger -t cloud "Password server failed with error code $rc. Restarting it..."
+        sleep 3
+    fi
+    . /etc/default/cloud-passwd-srvr
 done

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/3951ab00/systemvm/patches/debian/config/opt/cloud/bin/passwd_server_ip.py
----------------------------------------------------------------------
diff --git a/systemvm/patches/debian/config/opt/cloud/bin/passwd_server_ip.py b/systemvm/patches/debian/config/opt/cloud/bin/passwd_server_ip.py
new file mode 100755
index 0000000..75c4444
--- /dev/null
+++ b/systemvm/patches/debian/config/opt/cloud/bin/passwd_server_ip.py
@@ -0,0 +1,204 @@
+#!/usr/bin/env python
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you 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.
+#
+# Client usage examples;
+# Getting password:
+#   wget -q -t 3 -T 20 -O - --header "DomU_Request: send_my_password" <routerIP>:8080
+# Send ack:
+#   wget -t 3 -T 20 -O - --header "DomU_Request: saved_password" localhost:8080
+# Save password only from within router:
+#   /opt/cloud/bin/savepassword.sh -v <IP> -p <password>
+#   curl --header "DomU_Request: save_password" http://localhost:8080/ -F ip=<IP> -F password=<passwd>
+
+import binascii
+import cgi
+import os
+import sys
+import syslog
+import threading
+import time
+import urlparse
+
+from BaseHTTPServer   import BaseHTTPRequestHandler, HTTPServer
+from SocketServer     import ThreadingMixIn, ForkingMixIn
+
+# Global password map
+passMap = {}
+secureToken = None
+
+# Global passMap lock
+lock = threading.Lock()
+
+def getTokenFile():
+    return "/tmp/passwdsrvrtoken"
+
+def getPasswordFile():
+    return "/var/cache/cloud/passwords"
+
+def initToken():
+    global secureToken
+    f = open(getTokenFile(), "w")
+    secureToken = binascii.hexlify(os.urandom(16))
+    f.write(secureToken)
+    f.close()
+
+def checkToken(token):
+    global secureToken
+    return token == secureToken
+
+def loadPasswordFile():
+    global passMap
+    if os.path.exists(getPasswordFile()):
+        f = open(getPasswordFile(), "r")
+        data = f.read()
+        f.close()
+        for line in data.split("\n"):
+            splitLine = line.split("=")
+            if len(splitLine) == 2:
+                passMap[splitLine[0]] = splitLine[1]
+
+def savePasswordFile():
+    global passMap, lock
+    lock.acquire()
+    try:
+        f = open(getPasswordFile(), "w")
+        for ip in passMap.keys():
+            f.write("%s=%s\n" % (ip, passMap[ip]))
+        f.close()
+    except IOError, e:
+        syslog.syslog("serve_password: Unable to save to password file %s" % e)
+    finally:
+        lock.release()
+
+def getPassword(ip):
+    global passMap
+    if ip in passMap:
+        return passMap[ip]
+    return None
+
+def setPassword(ip, password):
+    global passMap, lock
+    if ip == None or ip == "" or password == None or password == "":
+        return
+    lock.acquire()
+    try:
+        passMap[ip] = password
+    finally:
+        lock.release()
+
+def removePassword(ip):
+    global passMap, lock
+    lock.acquire()
+    try:
+        if ip in passMap:
+            del passMap[ip]
+    finally:
+        lock.release()
+
+
+class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
+    pass
+
+
+class PasswordRequestHandler(BaseHTTPRequestHandler):
+    server_version = "CloudStack Password Server"
+    sys_version = "4.x"
+    def do_GET(self):
+        self.send_response(200)
+        self.send_header("Content-type", "text/plain")
+        self.send_header("Server", "CloudStack Password Server")
+        self.end_headers()
+        requestType = self.headers.get('DomU_Request')
+        clientAddress = self.client_address[0]
+        if requestType == "send_my_password":
+            password = getPassword(clientAddress)
+            if not password:
+                syslog.syslog("serve_password: requested password not found for %s" % clientAddress)
+            else:
+                self.wfile.write(password)
+                syslog.syslog("serve_password: password sent to %s" % clientAddress)
+        elif requestType == "saved_password":
+            removePassword(clientAddress)
+            savePasswordFile()
+            self.wfile.write("saved_password")
+            syslog.syslog("serve_password: saved_password ack received from %s" % clientAddress)
+        else:
+            self.send_response(400)
+            self.wfile.write("bad_request")
+            syslog.syslog("serve_password: bad_request from IP %s" % clientAddress)
+        return
+
+    def do_POST(self):
+        form = cgi.FieldStorage(
+                    fp=self.rfile,
+                    headers=self.headers,
+                    environ={'REQUEST_METHOD':'POST',
+                             'CONTENT_TYPE':self.headers['Content-Type'],
+                    })
+        self.send_response(200)
+        self.end_headers()
+        clientAddress = self.client_address[0]
+        if not (clientAddress == "localhost" or clientAddress == "127.0.0.1"):
+            syslog.syslog("serve_password: non-localhost IP trying to save password: %s" % clientAddress)
+            self.send_response(403)
+            return
+        if "ip" not in form.keys() or "password" not in form.keys() or "token" not in form.keys() or self.headers.get('DomU_Request') != "save_password":
+            syslog.syslog("serve_password: request trying to save password does not contain both ip and password")
+            self.send_response(403)
+            return
+        token = form["token"].value
+        if not checkToken(token):
+            syslog.syslog("serve_password: invalid save_password token received from %s" % clientAddress)
+            self.send_response(403)
+            return
+        ip = form["ip"].value
+        password = form["password"].value
+        setPassword(ip, password)
+        savePasswordFile()
+        return
+
+    def log_message(self, format, *args):
+            return
+
+
+def serve(HandlerClass = PasswordRequestHandler,
+          ServerClass = ThreadedHTTPServer):
+
+    listeningAddress = '127.0.0.1'
+    if len(sys.argv) > 1:
+        listeningAddress = sys.argv[1]
+
+    server_address = (listeningAddress, 8080)
+    passwordServer = ServerClass(server_address, HandlerClass)
+    passwordServer.allow_reuse_address = True
+    sa = passwordServer.socket.getsockname()
+    initToken()
+    loadPasswordFile()
+    syslog.syslog("serve_password running on %s:%s" % (sa[0], sa[1]))
+    try:
+        passwordServer.serve_forever()
+    except KeyboardInterrupt:
+        syslog.syslog("serve_password shutting down")
+        passwordServer.socket.close()
+    except Exception, e:
+        syslog.syslog("serve_password hit exception %s -- died" % e)
+        passwordServer.socket.close()
+
+
+if __name__ == '__main__':
+    serve()

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/3951ab00/systemvm/patches/debian/config/opt/cloud/bin/savepassword.sh
----------------------------------------------------------------------
diff --git a/systemvm/patches/debian/config/opt/cloud/bin/savepassword.sh b/systemvm/patches/debian/config/opt/cloud/bin/savepassword.sh
index ab027b6..1c384e6 100755
--- a/systemvm/patches/debian/config/opt/cloud/bin/savepassword.sh
+++ b/systemvm/patches/debian/config/opt/cloud/bin/savepassword.sh
@@ -16,48 +16,23 @@
 # specific language governing permissions and limitations
 # under the License.
 
-
- 
-
 # Usage
-#	save_password -v <user VM IP> -p <password>
-
-source /root/func.sh
-
-lock="passwdlock"
-#default timeout value is 30 mins as password reset command is not synchronized on agent side any more,
-#and multiple commands can be sent to the same VR at a time
-locked=$(getLockFile $lock 1800)
-if [ "$locked" != "1" ]
-then
-    exit 1
-fi
-
-PASSWD_FILE=/var/cache/cloud/passwords
+#   save_password -v <user VM IP> -p <password>
 
 while getopts 'v:p:' OPTION
 do
   case $OPTION in
-  v)	VM_IP="$OPTARG"
-		;;
+  v)    VM_IP="$OPTARG"
+        ;;
   p)    PASSWORD="$OPTARG"
-		;;
-  ?)	echo "Incorrect usage"
-                unlock_exit 1 $lock $locked
-		;;
+        ;;
+  ?)    echo "Incorrect usage"
+        ;;
   esac
 done
-
-[ -f $PASSWD_FILE ] ||  touch $PASSWD_FILE
-
-sed -i /$VM_IP=/d $PASSWD_FILE
-
-ps aux | grep serve_password.sh |grep -v grep 2>&1 > /dev/null
+TOKEN=$(cat /tmp/passwdsrvrtoken)
+ps aux | grep passwd_server_ip.py |grep -v grep 2>&1 > /dev/null
 if [ $? -eq 0 ]
 then
-    echo "$VM_IP=$PASSWORD" >> $PASSWD_FILE
-else
-    echo "$VM_IP=saved_password" >> $PASSWD_FILE
+    curl --header "DomU_Request: save_password" http://127.0.0.1:8080/ -F "ip=$VM_IP" -F "password=$PASSWORD" -F "token=$TOKEN"
 fi
-
-unlock_exit $? $lock $locked

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/3951ab00/systemvm/patches/debian/config/opt/cloud/bin/serve_password.sh
----------------------------------------------------------------------
diff --git a/systemvm/patches/debian/config/opt/cloud/bin/serve_password.sh b/systemvm/patches/debian/config/opt/cloud/bin/serve_password.sh
deleted file mode 100755
index a3a2732..0000000
--- a/systemvm/patches/debian/config/opt/cloud/bin/serve_password.sh
+++ /dev/null
@@ -1,103 +0,0 @@
-#!/bin/bash
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you 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.
- 
-
-
-# set -x 
-
-source /root/func.sh
-
-lock="passwdlock"
-locked=$(getLockFile $lock)
-if [ "$locked" != "1" ]
-then
-    exit 1
-fi
-
-PASSWD_FILE=/var/cache/cloud/passwords
-
-#   $1 filename
-#   $2 keyname
-#   $3 value
-replace_in_file() {
-  local filename=$1
-  local keyname=$2
-  local value=$3
-  sed -i /$keyname=/d $filename
-  echo "$keyname=$value" >> $filename
-  return $?
-}
-
-#   $1 filename
-#   $2 keyname
-get_value() {
-  local filename=$1
-  local keyname=$2
-  grep -i $keyname= $filename | cut -d= -f2
-}
-
-ip=$1
-
-logger -t cloud "serve_password called to service a request for $ip."
-
-while read input
-do
-	if [ "$input" == "" ]
-	then
-		break
-	fi
-
-	request=$(echo "$input" | grep "DomU_Request:" | cut -d: -f2 | sed 's/^[ \t]*//')
-
-	if [ "$request" != "" ]
-	then
-		break
-	fi
-done
-
-# echo -e \"\\\"HTTP/1.0 200 OK\\\nDocumentType: text/plain\\\n\\\n\\\"\"; 
-
-if [ "$request" == "send_my_password" ]
-then
-	password=$(get_value $PASSWD_FILE $ip)
-	if [ "$password" == "" ]
-	then
-		logger -t cloud "serve_password sent bad_request to $ip."
-		# echo "bad_request"
-                # Return "saved_password" for non-existed entry, to make it
-                # work if domR was once destroyed.
-		echo "saved_password"
-	else
-		logger -t cloud "serve_password sent a password to $ip."
-		echo $password
-	fi
-else
-	if [ "$request" == "saved_password" ]
-	then
-		replace_in_file $PASSWD_FILE $ip "saved_password"
-		logger -t cloud "serve_password sent saved_password to $ip."
-		echo "saved_password"
-	else
-		logger -t cloud "serve_password sent bad_request to $ip."
-		echo "bad_request"
-	fi
-fi
-
-# echo -e \"\\\"\\\n\\\"\"
-
-unlock_exit 0 $lock $locked