Repository: cloudstack Updated Branches: refs/heads/4.5 cfd457333 -> 4b45d2515
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 <rohit.ya...@shapeblue.com> Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/4b45d251 Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/4b45d251 Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/4b45d251 Branch: refs/heads/4.5 Commit: 4b45d25152a6661fcd1796670f0fb65bb4a32df5 Parents: cfd4573 Author: Rohit Yadav <rohit.ya...@shapeblue.com> Authored: Tue Mar 10 15:35:31 2015 +0530 Committer: Rohit Yadav <rohit.ya...@shapeblue.com> Committed: Thu Mar 12 13:56:28 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 | 103 ---------- 4 files changed, 208 insertions(+), 146 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cloudstack/blob/4b45d251/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/4b45d251/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/4b45d251/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/4b45d251/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