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/12 09:28:10 UTC

git commit: updated refs/heads/master to 375e998

Repository: cloudstack
Updated Branches:
  refs/heads/master 74f9adbe3 -> 375e998ea


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

This closes #106

(cherry picked from commit 4b45d25152a6661fcd1796670f0fb65bb4a32df5)
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/375e998e
Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/375e998e
Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/375e998e

Branch: refs/heads/master
Commit: 375e998eafdb5a02385f5330fea89942ebb2d02a
Parents: 74f9adb
Author: Rohit Yadav <ro...@shapeblue.com>
Authored: Tue Mar 10 15:35:31 2015 +0530
Committer: Rohit Yadav <ro...@shapeblue.com>
Committed: Thu Mar 12 13:57:49 2015 +0530

----------------------------------------------------------------------
 .../config/opt/cloud/bin/passwd_server_ip       |  17 +-
 .../config/opt/cloud/bin/passwd_server_ip.py    | 187 +++++++++++++++++++
 .../debian/config/opt/cloud/bin/savepassword.sh |  47 ++---
 .../config/opt/cloud/bin/serve_password.sh      | 101 ----------
 4 files changed, 208 insertions(+), 144 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cloudstack/blob/375e998e/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..5e15a19 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/passwd_server_ip.py $addr >/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/375e998e/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..097d4fb
--- /dev/null
+++ b/systemvm/patches/debian/config/opt/cloud/bin/passwd_server_ip.py
@@ -0,0 +1,187 @@
+#!/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 urlparse
+
+from BaseHTTPServer   import BaseHTTPRequestHandler, HTTPServer
+from SocketServer     import ThreadingMixIn #, ForkingMixIn
+
+
+passMap = {}
+secureToken = None
+lock = threading.RLock()
+
+def getTokenFile():
+    return '/tmp/passwdsrvrtoken'
+
+def getPasswordFile():
+    return '/var/cache/cloud/passwords'
+
+def initToken():
+    global secureToken
+    secureToken = binascii.hexlify(os.urandom(16))
+    with open(getTokenFile(), 'w') as f:
+        f.write(secureToken)
+
+def checkToken(token):
+    return token == secureToken
+
+def loadPasswordFile():
+    try:
+        with file(getPasswordFile()) as f:
+            for line in f:
+                if '=' not in line: continue
+                key, value = line.strip().split('=', 1)
+                passMap[key] = value
+    except IOError:
+        pass
+
+def savePasswordFile():
+    with lock:
+        try:
+            with file(getPasswordFile(), 'w') as f:
+                for ip in passMap:
+                    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)
+
+def getPassword(ip):
+    return passMap.get(ip, None)
+
+def setPassword(ip, password):
+    if not ip or not password:
+        return
+    with lock:
+        passMap[ip] = password
+
+def removePassword(ip):
+    with lock:
+        if ip in passMap:
+            del passMap[ip]
+
+
+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 clientAddress not in ['localhost', '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 or 'password' not in form or 'token' not in form 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
+        if not ip or not password:
+            syslog.syslog('serve_password: empty ip/password[%s/%s] received from savepassword' % (ip, password))
+            return
+        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/375e998e/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..5b1f5e6 100755
--- a/systemvm/patches/debian/config/opt/cloud/bin/savepassword.sh
+++ b/systemvm/patches/debian/config/opt/cloud/bin/savepassword.sh
@@ -16,48 +16,27 @@
 # 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_FILE="/tmp/passwdsrvrtoken"
+TOKEN=""
+if [ -f $TOKEN_FILE ]; then
+    TOKEN=$(cat $TOKEN_FILE)
+fi
+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/375e998e/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 294eab0..0000000
--- a/systemvm/patches/debian/config/opt/cloud/bin/serve_password.sh
+++ /dev/null
@@ -1,101 +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 -ne "HTTP/1.0 200 OK\r\nDocumentType: text/plain\r\n\r\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
-
-unlock_exit 0 $lock $locked