cloudstack/systemvm/debian/opt/cloud/bin/passwd_server_ip.py
Wei Zhou 94a47ac778
VR: fix password server exception when no password is found (#9699)
see errors in /var/log/daemon.log below
```
Sep 13 12:36:58 systemvm passwd_server_ip.py[2154]: ----------------------------------------
Sep 13 12:36:58 systemvm passwd_server_ip.py[2154]: Exception occurred during processing of request from ('192.168.20.8', 51108)
Sep 13 12:36:58 systemvm passwd_server_ip.py[2154]: Traceback (most recent call last):
Sep 13 12:36:58 systemvm passwd_server_ip.py[2154]:   File "/usr/lib/python3.11/socketserver.py", line 691, in process_request_thread
Sep 13 12:36:58 systemvm passwd_server_ip.py[2154]:     self.finish_request(request, client_address)
Sep 13 12:36:58 systemvm passwd_server_ip.py[2154]:   File "/usr/lib/python3.11/socketserver.py", line 361, in finish_request
Sep 13 12:36:58 systemvm passwd_server_ip.py[2154]:     self.RequestHandlerClass(request, client_address, self)
Sep 13 12:36:58 systemvm passwd_server_ip.py[2154]:   File "/usr/lib/python3.11/socketserver.py", line 755, in __init__
Sep 13 12:36:58 systemvm passwd_server_ip.py[2154]:     self.handle()
Sep 13 12:36:58 systemvm passwd_server_ip.py[2154]:   File "/usr/lib/python3.11/http/server.py", line 432, in handle
Sep 13 12:36:58 systemvm passwd_server_ip.py[2154]:     self.handle_one_request()
Sep 13 12:36:58 systemvm passwd_server_ip.py[2154]:   File "/usr/lib/python3.11/http/server.py", line 420, in handle_one_request
Sep 13 12:36:58 systemvm passwd_server_ip.py[2154]:     method()
Sep 13 12:36:58 systemvm passwd_server_ip.py[2154]:   File "/opt/cloud/bin/passwd_server_ip.py", line 117, in do_GET
Sep 13 12:36:58 systemvm passwd_server_ip.py[2154]:     self.wfile.write('saved_password')
Sep 13 12:36:58 systemvm passwd_server_ip.py[2154]:   File "/usr/lib/python3.11/socketserver.py", line 834, in write
Sep 13 12:36:58 systemvm passwd_server_ip.py[2154]:     self._sock.sendall(b)
Sep 13 12:36:58 systemvm passwd_server_ip.py[2154]: TypeError: a bytes-like object is required, not 'str'
Sep 13 12:36:58 systemvm passwd_server_ip.py[2154]: ----------------------------------------
```
2024-09-19 10:58:57 -03:00

202 lines
6.9 KiB
Python
Executable File

#!/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 urllib.parse
from http.server import BaseHTTPRequestHandler, HTTPServer
from socketserver import ThreadingMixIn
passMap = {}
secureToken = None
listeningAddress = '127.0.0.1'
allowAddresses = ['localhost', '127.0.0.1']
lock = threading.RLock()
def getTokenFile():
return '/tmp/passwdsrvrtoken'
def getPasswordFile():
return '/var/cache/cloud/passwords-%s' % listeningAddress
def initToken():
global secureToken
if os.path.exists(getTokenFile()):
with open(getTokenFile(), 'r') as f:
secureToken = f.read()
if not secureToken:
secureToken = binascii.hexlify(os.urandom(16)).decode()
with open(getTokenFile(), 'w') as f:
f.write(secureToken)
def checkToken(token):
return token == secureToken
def loadPasswordFile():
try:
with open(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 open(getPasswordFile(), 'w') as f:
for ip in passMap:
f.write('%s=%s\n' % (ip, passMap[ip]))
f.close()
except IOError as 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:
self.wfile.write('saved_password'.encode())
syslog.syslog('serve_password: requested password not found for %s' % clientAddress)
else:
self.wfile.write(password.encode())
syslog.syslog('serve_password: password sent to %s' % clientAddress)
elif requestType == 'saved_password':
removePassword(clientAddress)
savePasswordFile()
self.wfile.write('saved_password'.encode())
syslog.syslog('serve_password: saved_password ack received from %s' % clientAddress)
else:
self.send_response(400)
self.wfile.write('bad_request'.encode())
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 allowAddresses:
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
syslog.syslog('serve_password: password saved for VM IP %s' % ip)
setPassword(ip, password)
savePasswordFile()
return
def log_message(self, format, *args):
return
def serve(HandlerClass = PasswordRequestHandler,
ServerClass = ThreadedHTTPServer):
global listeningAddress
global allowAddresses
if len(sys.argv) > 1:
addresses = sys.argv[1].split(",")
if len(addresses) > 0:
listeningAddress = addresses[0]
allowAddresses.append(addresses[0])
if len(addresses) > 1:
allowAddresses.append(addresses[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 as e:
syslog.syslog('serve_password hit exception %s -- died' % e)
passwordServer.socket.close()
if __name__ == '__main__':
serve()