mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
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.yadav@shapeblue.com>
This commit is contained in:
parent
cfd4573335
commit
4b45d25152
@ -20,12 +20,11 @@
|
||||
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\""
|
||||
|
||||
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 socat..."
|
||||
logger -t cloud "Password server failed with error code $rc. Restarting it..."
|
||||
sleep 3
|
||||
fi
|
||||
. /etc/default/cloud-passwd-srvr
|
||||
|
||||
187
systemvm/patches/debian/config/opt/cloud/bin/passwd_server_ip.py
Executable file
187
systemvm/patches/debian/config/opt/cloud/bin/passwd_server_ip.py
Executable file
@ -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()
|
||||
@ -16,25 +16,9 @@
|
||||
# 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
|
||||
|
||||
while getopts 'v:p:' OPTION
|
||||
do
|
||||
case $OPTION in
|
||||
@ -43,21 +27,16 @@ do
|
||||
p) PASSWORD="$OPTARG"
|
||||
;;
|
||||
?) echo "Incorrect usage"
|
||||
unlock_exit 1 $lock $locked
|
||||
;;
|
||||
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
|
||||
|
||||
@ -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
|
||||
Loading…
x
Reference in New Issue
Block a user