From e11c3516e98c451cbc98dbb240456b687a339676 Mon Sep 17 00:00:00 2001 From: Markus Hilger Date: Tue, 3 Sep 2024 18:25:15 +0200 Subject: [PATCH 01/11] Create README.md (#162) --- README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..9be6cc60 --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +# Confluent + +![Python 3](https://img.shields.io/badge/python-3-blue.svg) [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/xcat2/confluent/blob/master/LICENSE) + +Confluent is a software package to handle essential bootstrap and operation of scale-out server configurations. +It supports stateful and stateless deployments for various operating systems. + +Check [this page](https://hpc.lenovo.com/users/documentation/whatisconfluent.html +) for a more detailed list of features. + +Confluent is the modern successor of [xCAT](https://github.com/xcat2/xcat-core). +If you're coming from xCAT, check out [this comparison](https://hpc.lenovo.com/users/documentation/confluentvxcat.html). + +# Documentation + +Confluent documentation is hosted on hpc.lenovo.com: https://hpc.lenovo.com/users/documentation/ + +# Download + +Get the latest version from: https://hpc.lenovo.com/users/downloads/ + +Check release notes on: https://hpc.lenovo.com/users/news/ + +# Open Source License + +Confluent is made available under the Apache 2.0 license: https://opensource.org/license/apache-2-0 + +# Developers + +Want to help? Submit a [Pull Request](https://github.com/xcat2/confluent/pulls). From cb67c8328751736ff3e234305a64aad537d7ba3e Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Wed, 4 Sep 2024 08:11:42 -0400 Subject: [PATCH 02/11] iAttempt to be consistent with ~ prerelease versioning We may not know where we are going, but at least bump the minor number. Ultimately we may not be building toward that number, or that number will be used in a different branch, but it can at least handle more cases --- confluent_server/makesetup | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/confluent_server/makesetup b/confluent_server/makesetup index de39ea18..012fe1d4 100755 --- a/confluent_server/makesetup +++ b/confluent_server/makesetup @@ -2,7 +2,11 @@ cd `dirname $0` VERSION=`git describe|cut -d- -f 1` NUMCOMMITS=`git describe|cut -d- -f 2` if [ "$NUMCOMMITS" != "$VERSION" ]; then - VERSION=$VERSION.dev$NUMCOMMITS+g`git describe|cut -d- -f 3` + LASTNUM=$(echo $VERSION|rev|cut -d . -f 1|rev) + LASTNUM=$((LASTNUM+1)) + FIRSTPART=$(echo $VERSION|rev|cut -d . -f 2- |rev) + VERSION=${FIRSTPART}.${LASTNUM} + VERSION=$VERSION~dev$NUMCOMMITS+g`git describe|cut -d- -f 3` fi echo $VERSION > VERSION sed -e "s/#VERSION#/$VERSION/" setup.py.tmpl > setup.py From 97e29a5655ad876d8360d5f8ec075166fe5629fd Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Wed, 4 Sep 2024 10:11:47 -0400 Subject: [PATCH 03/11] Change versioning to consistently produce prerelease consistent with rpm and deb --- confluent_client/confluent_client.spec.tmpl | 2 +- confluent_server/confluent_server.spec.tmpl | 2 +- confluent_server/makesetup | 2 +- confluent_vtbufferd/builddeb | 6 +++++- confluent_vtbufferd/buildrpm | 6 +++++- imgutil/builddeb | 6 +++++- imgutil/buildrpm | 6 +++++- 7 files changed, 23 insertions(+), 7 deletions(-) diff --git a/confluent_client/confluent_client.spec.tmpl b/confluent_client/confluent_client.spec.tmpl index 820b0bbb..b26155ea 100644 --- a/confluent_client/confluent_client.spec.tmpl +++ b/confluent_client/confluent_client.spec.tmpl @@ -21,7 +21,7 @@ This package enables python development and command line access to a confluent server. %prep -%setup -n %{name}-%{version} -n %{name}-%{version} +%setup -n %{name}-%{lua: print(string.gsub("#VERSION#", "[~+]", "-"))} %build %if "%{dist}" == ".el7" diff --git a/confluent_server/confluent_server.spec.tmpl b/confluent_server/confluent_server.spec.tmpl index acaf9fc4..284a487d 100644 --- a/confluent_server/confluent_server.spec.tmpl +++ b/confluent_server/confluent_server.spec.tmpl @@ -33,7 +33,7 @@ Url: https://github.com/lenovo/confluent Server for console management and systems management aggregation %prep -%setup -n %{name}-%{version} -n %{name}-%{version} +%setup -n %{name}-%{lua: print(string.gsub("#VERSION#", "[~+]", "-"))} %build %if "%{dist}" == ".el7" diff --git a/confluent_server/makesetup b/confluent_server/makesetup index 012fe1d4..a34438d3 100755 --- a/confluent_server/makesetup +++ b/confluent_server/makesetup @@ -6,7 +6,7 @@ if [ "$NUMCOMMITS" != "$VERSION" ]; then LASTNUM=$((LASTNUM+1)) FIRSTPART=$(echo $VERSION|rev|cut -d . -f 2- |rev) VERSION=${FIRSTPART}.${LASTNUM} - VERSION=$VERSION~dev$NUMCOMMITS+g`git describe|cut -d- -f 3` + VERSION=$VERSION~dev$NUMCOMMITS+`git describe|cut -d- -f 3` fi echo $VERSION > VERSION sed -e "s/#VERSION#/$VERSION/" setup.py.tmpl > setup.py diff --git a/confluent_vtbufferd/builddeb b/confluent_vtbufferd/builddeb index 36f41218..3a98315a 100755 --- a/confluent_vtbufferd/builddeb +++ b/confluent_vtbufferd/builddeb @@ -8,7 +8,11 @@ DSCARGS="--with-python3=True --with-python2=False" VERSION=`git describe|cut -d- -f 1` NUMCOMMITS=`git describe|cut -d- -f 2` if [ "$NUMCOMMITS" != "$VERSION" ]; then - VERSION=$VERSION.dev$NUMCOMMITS.g`git describe|cut -d- -f 3` + LASTNUM=$(echo $VERSION|rev|cut -d . -f 1|rev) + LASTNUM=$((LASTNUM+1)) + FIRSTPART=$(echo $VERSION|rev|cut -d . -f 2- |rev) + VERSION=${FIRSTPART}.${LASTNUM} + VERSION=$VERSION~dev$NUMCOMMITS+`git describe|cut -d- -f 3` fi cd .. rm -rf /tmp/confluent diff --git a/confluent_vtbufferd/buildrpm b/confluent_vtbufferd/buildrpm index 35feec59..9a20844d 100755 --- a/confluent_vtbufferd/buildrpm +++ b/confluent_vtbufferd/buildrpm @@ -1,7 +1,11 @@ VERSION=`git describe|cut -d- -f 1` NUMCOMMITS=`git describe|cut -d- -f 2` if [ "$NUMCOMMITS" != "$VERSION" ]; then - VERSION=$VERSION.dev$NUMCOMMITS.g`git describe|cut -d- -f 3` + LASTNUM=$(echo $VERSION|rev|cut -d . -f 1|rev) + LASTNUM=$((LASTNUM+1)) + FIRSTPART=$(echo $VERSION|rev|cut -d . -f 2- |rev) + VERSION=${FIRSTPART}.${LASTNUM} + VERSION=$VERSION~dev$NUMCOMMITS+`git describe|cut -d- -f 3` fi mkdir -p dist/confluent_vtbufferd-$VERSION cp ../LICENSE NOTICE *.c *.h Makefile dist/confluent_vtbufferd-$VERSION diff --git a/imgutil/builddeb b/imgutil/builddeb index 7e12a6e6..a7cee375 100755 --- a/imgutil/builddeb +++ b/imgutil/builddeb @@ -2,7 +2,11 @@ VERSION=`git describe|cut -d- -f 1` NUMCOMMITS=`git describe|cut -d- -f 2` if [ "$NUMCOMMITS" != "$VERSION" ]; then - VERSION=$VERSION.dev$NUMCOMMITS.g`git describe|cut -d- -f 3` + LASTNUM=$(echo $VERSION|rev|cut -d . -f 1|rev) + LASTNUM=$((LASTNUM+1)) + FIRSTPART=$(echo $VERSION|rev|cut -d . -f 2- |rev) + VERSION=${FIRSTPART}.${LASTNUM} + VERSION=$VERSION~dev$NUMCOMMITS+`git describe|cut -d- -f 3` fi mkdir -p /tmp/confluent-imgutil cp -a * /tmp/confluent-imgutil diff --git a/imgutil/buildrpm b/imgutil/buildrpm index 87631ac0..4439b7ed 100755 --- a/imgutil/buildrpm +++ b/imgutil/buildrpm @@ -2,7 +2,11 @@ VERSION=`git describe|cut -d- -f 1` NUMCOMMITS=`git describe|cut -d- -f 2` if [ "$NUMCOMMITS" != "$VERSION" ]; then - VERSION=$VERSION.dev$NUMCOMMITS.g`git describe|cut -d- -f 3` + LASTNUM=$(echo $VERSION|rev|cut -d . -f 1|rev) + LASTNUM=$((LASTNUM+1)) + FIRSTPART=$(echo $VERSION|rev|cut -d . -f 2- |rev) + VERSION=${FIRSTPART}.${LASTNUM} + VERSION=$VERSION~dev$NUMCOMMITS+`git describe|cut -d- -f 3` fi sed -e "s/#VERSION#/$VERSION/" confluent_imgutil.spec.tmpl > confluent_imgutil.spec cp ../LICENSE . From 4a2e943f8432a9531253c4a7e3ac60fc7616778e Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Wed, 4 Sep 2024 10:19:11 -0400 Subject: [PATCH 04/11] Update osdeploy rpms to new version scheme for snapshots --- confluent_osdeploy/buildrpm | 6 +++++- confluent_osdeploy/buildrpm-aarch64 | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/confluent_osdeploy/buildrpm b/confluent_osdeploy/buildrpm index 6bd1d419..9e9cb582 100755 --- a/confluent_osdeploy/buildrpm +++ b/confluent_osdeploy/buildrpm @@ -1,7 +1,11 @@ VERSION=`git describe|cut -d- -f 1` NUMCOMMITS=`git describe|cut -d- -f 2` if [ "$NUMCOMMITS" != "$VERSION" ]; then - VERSION=$VERSION.dev$NUMCOMMITS.g`git describe|cut -d- -f 3` + LASTNUM=$(echo $VERSION|rev|cut -d . -f 1|rev) + LASTNUM=$((LASTNUM+1)) + FIRSTPART=$(echo $VERSION|rev|cut -d . -f 2- |rev) + VERSION=${FIRSTPART}.${LASTNUM} + VERSION=$VERSION~dev$NUMCOMMITS+`git describe|cut -d- -f 3` fi sed -e "s/#VERSION#/$VERSION/" confluent_osdeploy.spec.tmpl > confluent_osdeploy.spec cd .. diff --git a/confluent_osdeploy/buildrpm-aarch64 b/confluent_osdeploy/buildrpm-aarch64 index 83ffc519..c269284b 100644 --- a/confluent_osdeploy/buildrpm-aarch64 +++ b/confluent_osdeploy/buildrpm-aarch64 @@ -2,7 +2,11 @@ cd $(dirname $0) VERSION=`git describe|cut -d- -f 1` NUMCOMMITS=`git describe|cut -d- -f 2` if [ "$NUMCOMMITS" != "$VERSION" ]; then - VERSION=$VERSION.dev$NUMCOMMITS.g`git describe|cut -d- -f 3` + LASTNUM=$(echo $VERSION|rev|cut -d . -f 1|rev) + LASTNUM=$((LASTNUM+1)) + FIRSTPART=$(echo $VERSION|rev|cut -d . -f 2- |rev) + VERSION=${FIRSTPART}.${LASTNUM} + VERSION=$VERSION~dev$NUMCOMMITS+`git describe|cut -d- -f 3` fi sed -e "s/#VERSION#/$VERSION/" confluent_osdeploy-aarch64.spec.tmpl > confluent_osdeploy-aarch64.spec cd .. From ee067fa3c065a62ac8c2c3d66ed5eeea2de0b787 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Wed, 4 Sep 2024 10:27:00 -0400 Subject: [PATCH 05/11] Cleanup stray debian content in apt --- confluent_server/builddeb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/confluent_server/builddeb b/confluent_server/builddeb index 71080110..9aca0d0a 100755 --- a/confluent_server/builddeb +++ b/confluent_server/builddeb @@ -74,7 +74,7 @@ else rm -rf $PKGNAME.egg-info dist setup.py rm -rf $(find deb_dist -mindepth 1 -maxdepth 1 -type d) if [ ! -z "$1" ]; then - mv deb_dist/* $1/ + mv deb_dist/*.deb $1/ fi fi exit 0 From 7c8f85eb065db88066a041a20d32d8db8aac8f01 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Wed, 4 Sep 2024 10:53:05 -0400 Subject: [PATCH 06/11] Handle python mangling of filename consistently for rpm build --- confluent_client/confluent_client.spec.tmpl | 8 ++++++-- confluent_server/confluent_server.spec.tmpl | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/confluent_client/confluent_client.spec.tmpl b/confluent_client/confluent_client.spec.tmpl index b26155ea..ee786175 100644 --- a/confluent_client/confluent_client.spec.tmpl +++ b/confluent_client/confluent_client.spec.tmpl @@ -1,12 +1,16 @@ %define name confluent_client %define version #VERSION# +%define fversion %{lua: +sv, _ = string.gsub("#VERSION#", "[~+]", "-") +print(sv) +} %define release 1 Summary: Client libraries and utilities for confluent Name: %{name} Version: %{version} Release: %{release} -Source0: %{name}-%{version}.tar.gz +Source0: %{name}-%{fversion}.tar.gz License: Apache2 Group: Development/Libraries BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot @@ -21,7 +25,7 @@ This package enables python development and command line access to a confluent server. %prep -%setup -n %{name}-%{lua: print(string.gsub("#VERSION#", "[~+]", "-"))} +%setup -n %{name}-%{fversion} %build %if "%{dist}" == ".el7" diff --git a/confluent_server/confluent_server.spec.tmpl b/confluent_server/confluent_server.spec.tmpl index 284a487d..0a36390c 100644 --- a/confluent_server/confluent_server.spec.tmpl +++ b/confluent_server/confluent_server.spec.tmpl @@ -1,12 +1,16 @@ %define name confluent_server %define version #VERSION# +%define fversion %{lua: +sv, _ = string.gsub("#VERSION#", "[~+]", "-") +print(sv) +} %define release 1 Summary: confluent systems management server Name: %{name} Version: %{version} Release: %{release} -Source0: %{name}-%{version}.tar.gz +Source0: %{name}-%{fversion}.tar.gz License: Apache2 Group: Development/Libraries BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot @@ -33,7 +37,7 @@ Url: https://github.com/lenovo/confluent Server for console management and systems management aggregation %prep -%setup -n %{name}-%{lua: print(string.gsub("#VERSION#", "[~+]", "-"))} +%setup -n %{name}-%{fversion} %build %if "%{dist}" == ".el7" From 2c76d94a6debf504ff4c78161be2de60f9b4f359 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Wed, 4 Sep 2024 12:13:40 -0400 Subject: [PATCH 07/11] Vary version for debian build process --- confluent_server/builddeb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/confluent_server/builddeb b/confluent_server/builddeb index 9aca0d0a..3a983694 100755 --- a/confluent_server/builddeb +++ b/confluent_server/builddeb @@ -17,7 +17,9 @@ cd /tmp/confluent/$PKGNAME if [ -x ./makeman ]; then ./makeman fi -./makesetup +sed -e 's/~/./' ./makesetup > ./makesetup.deb +chmod +x ./makesetup.deb +./makesetup.deb VERSION=`cat VERSION` cat > setup.cfg << EOF [install] From d17d5341f11325141a62a03070d3e12e65859072 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Wed, 4 Sep 2024 19:16:41 -0400 Subject: [PATCH 08/11] Imprement basic vinz management --- confluent_server/confluent/core.py | 1 + confluent_server/confluent/messages.py | 13 +- confluent_server/confluent/vinzmanager.py | 166 ++++++++++++++++++++++ 3 files changed, 176 insertions(+), 4 deletions(-) create mode 100644 confluent_server/confluent/vinzmanager.py diff --git a/confluent_server/confluent/core.py b/confluent_server/confluent/core.py index a4e311c4..0ab6b647 100644 --- a/confluent_server/confluent/core.py +++ b/confluent_server/confluent/core.py @@ -430,6 +430,7 @@ def _init_core(): 'pluginattrs': ['hardwaremanagement.method'], 'default': 'ipmi', }), + 'ikvm': PluginRoute({'handler': 'ikvm'}), }, 'description': PluginRoute({ 'pluginattrs': ['hardwaremanagement.method'], diff --git a/confluent_server/confluent/messages.py b/confluent_server/confluent/messages.py index ce36344d..fa8dbbcb 100644 --- a/confluent_server/confluent/messages.py +++ b/confluent_server/confluent/messages.py @@ -262,10 +262,10 @@ class Generic(ConfluentMessage): def json(self): return json.dumps(self.data) - + def raw(self): return self.data - + def html(self): return json.dumps(self.data) @@ -344,10 +344,10 @@ class ConfluentResourceCount(ConfluentMessage): self.myargs = [count] self.desc = 'Resource Count' self.kvpairs = {'count': count} - + def strip_node(self, node): pass - + class CreatedResource(ConfluentMessage): notnode = True readonly = True @@ -569,6 +569,8 @@ def get_input_message(path, operation, inputdata, nodes=None, multinode=False, return InputLicense(path, nodes, inputdata, configmanager) elif path == ['deployment', 'ident_image']: return InputIdentImage(path, nodes, inputdata) + elif path == ['console', 'ikvm']: + return InputIkvmParams(path, nodes, inputdata) elif inputdata: raise exc.InvalidArgumentException( 'No known input handler for request') @@ -936,6 +938,9 @@ class InputIdentImage(ConfluentInputMessage): keyname = 'ident_image' valid_values = ['create'] +class InputIkvmParams(ConfluentInputMessage): + keyname = 'method' + valid_values = ['unix', 'wss'] class InputIdentifyMessage(ConfluentInputMessage): valid_values = set([ diff --git a/confluent_server/confluent/vinzmanager.py b/confluent_server/confluent/vinzmanager.py new file mode 100644 index 00000000..0eb2314a --- /dev/null +++ b/confluent_server/confluent/vinzmanager.py @@ -0,0 +1,166 @@ + +import confluent.auth as auth +import eventlet +import confluent.messages as msg +import confluent.exceptions as exc +import confluent.util as util +import confluent.config.configmanager as configmanager +import struct +import eventlet.green.socket as socket +import eventlet.green.subprocess as subprocess +import base64 +import os +import pwd +mountsbyuser = {} +_vinzfd = None +_vinztoken = None +webclient = eventlet.import_patched('pyghmi.util.webclient') + + +# Handle the vinz VNC session +def assure_vinz(): + global _vinzfd + global _vinztoken + if _vinzfd is None: + _vinztoken = base64.b64encode(os.urandom(33), altchars=b'_-').decode() + os.environ['VINZ_TOKEN'] = _vinztoken + os.makedirs('/var/run/confluent/vinz/sessions', exist_ok=True) + + _vinzfd = subprocess.Popen( + ['/opt/confluent/bin/vinz', + '-c', '/var/run/confluent/vinz/control', + '-w', '127.0.0.1:4007', + '-a', '/var/run/confluent/vinz/approval', + # vinz supports unix domain websocket, however apache reverse proxy is dicey that way in some versions + '-d', '/var/run/confluent/vinz/sessions']) + while not os.path.exists('/var/run/confluent/vinz/control'): + eventlet.sleep(0.5) + eventlet.spawn(monitor_requests) + + +def get_url(nodename, inputdata): + method = inputdata.inputbynode[nodename] + assure_vinz() + if method == 'wss': + return f'/vinz/kvmsession/{nodename}' + elif method == 'unix': + return request_session(nodename) + + +def send_grant(conn, nodename): + cfg = configmanager.ConfigManager(None) + c = cfg.get_node_attributes( + nodename, + ['secret.hardwaremanagementuser', + 'secret.hardwaremanagementpassword', + 'hardwaremanagement.manager'], decrypt=True) + bmcuser = c.get(nodename, {}).get( + 'secret.hardwaremanagementuser', {}).get('value', None) + bmcpass = c.get(nodename, {}).get( + 'secret.hardwaremanagementpassword', {}).get('value', None) + bmc = c.get(nodename, {}).get( + 'hardwaremanagement.manager', {}).get('value', None) + if bmcuser and bmcpass and bmc: + kv = util.TLSCertVerifier(cfg, nodename, + 'pubkeys.tls_hardwaremanager').verify_cert + wc = webclient.SecureHTTPConnection(bmc, 443, verifycallback=kv) + if not isinstance(bmcuser, str): + bmcuser = bmcuser.decode() + if not isinstance(bmcpass, str): + bmcpass = bmcpass.decode() + rsp = wc.grab_json_response_with_status( + '/login', {'data': [bmcuser, bmcpass]}, + headers={'Content-Type': 'application/json', + 'Accept': 'application/json'}) + sessionid = wc.cookies['SESSION'] + sessiontok = wc.cookies['XSRF-TOKEN'] + url = '/kvm/0' + fprintinfo = cfg.get_node_attributes(nodename, 'pubkeys.tls_hardwaremanager') + fprint = fprintinfo.get( + nodename, {}).get('pubkeys.tls_hardwaremanager', {}).get('value', None) + if not fprint: + return + fprint = fprint.split('$', 1)[1] + fprint = bytes.fromhex(fprint) + conn.send(struct.pack('!BI', 1, len(bmc))) + conn.send(bmc.encode()) + conn.send(struct.pack('!I', len(sessionid))) + conn.send(sessionid.encode()) + conn.send(struct.pack('!I', len(sessiontok))) + conn.send(sessiontok.encode()) + conn.send(struct.pack('!I', len(fprint))) + conn.send(fprint) + conn.send(struct.pack('!I', len(url))) + conn.send(url.encode()) + conn.send(b'\xff') + +def evaluate_request(conn): + try: + creds = conn.getsockopt(socket.SOL_SOCKET, socket.SO_PEERCRED, + struct.calcsize('iII')) + pid, uid, gid = struct.unpack('iII', creds) + if uid != os.getuid(): + return + rqcode, fieldlen = struct.unpack('!BI', conn.recv(5)) + if rqcode != 1: + return + authtoken = conn.recv(fieldlen).decode() + if authtoken != _vinztoken: + return + fieldlen = struct.unpack('!I', conn.recv(4))[0] + nodename = conn.recv(fieldlen).decode() + idtype = struct.unpack('!B', conn.recv(1))[0] + if idtype == 1: + usernum = struct.unpack('!I', conn.recv(4))[0] + if usernum == 0: # root is a special guy + send_grant(conn, nodename) + return + try: + authname = pwd.getpwuid(usernum).pw_name + except Exception: + return + allow = auth.authorize(authname, f'/nodes/{nodename}/console/ikvm') + if not allow: + return + send_grant(conn, nodename) + else: + return + if conn.recv(1) != b'\xff': + return + + finally: + conn.close() + +def monitor_requests(): + a = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + try: + os.remove('/var/run/confluent/vinz/approval') + except Exception: + pass + a.bind('/var/run/confluent/vinz/approval') + os.chmod('/var/run/confluent/vinz/approval', 0o600) + a.listen(8) + while True: + conn, addr = a.accept() + eventlet.spawn_n(evaluate_request, conn) + +def request_session(nodename): + assure_vinz() + a = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + a.connect('/var/run/confluent/vinz/control') + nodename = nodename.encode() + a.send(struct.pack('!BI', 1, len(nodename))) + a.send(nodename) + a.send(b'\xff') + rsp = a.recv(1) + retcode = struct.unpack('!B', rsp)[0] + if retcode != 1: + raise Exception("Bad return code") + rsp = a.recv(4) + nlen = struct.unpack('!I', rsp)[0] + sockname = a.recv(nlen).decode('utf8') + retcode = a.recv(1) + if retcode != b'\xff': + raise Exception("Unrecognized response") + return os.path.join('/var/run/confluent/vinz/sessions', sockname) + From f8715f4cb19a67cf520a9fb444ce7114c86496cd Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Thu, 5 Sep 2024 11:42:52 -0400 Subject: [PATCH 09/11] Implement logout on disconnect notification for vinz --- confluent_server/confluent/vinzmanager.py | 78 +++++++++++++++++------ 1 file changed, 57 insertions(+), 21 deletions(-) diff --git a/confluent_server/confluent/vinzmanager.py b/confluent_server/confluent/vinzmanager.py index 0eb2314a..991ba0be 100644 --- a/confluent_server/confluent/vinzmanager.py +++ b/confluent_server/confluent/vinzmanager.py @@ -46,6 +46,36 @@ def get_url(nodename, inputdata): elif method == 'unix': return request_session(nodename) +_usersessions = {} +def close_session(sessionid): + sessioninfo = _usersessions.get(sessionid, None) + if not sessioninfo: + return + del _usersessions[sessionid] + nodename = sessioninfo['nodename'] + wc = sessioninfo['webclient'] + cfg = configmanager.ConfigManager(None) + c = cfg.get_node_attributes( + nodename, + ['secret.hardwaremanagementuser', + 'secret.hardwaremanagementpassword', + ], decrypt=True) + bmcuser = c.get(nodename, {}).get( + 'secret.hardwaremanagementuser', {}).get('value', None) + bmcpass = c.get(nodename, {}).get( + 'secret.hardwaremanagementpassword', {}).get('value', None) + if not isinstance(bmcuser, str): + bmcuser = bmcuser.decode() + if not isinstance(bmcpass, str): + bmcpass = bmcpass.decode() + if bmcuser and bmcpass: + wc.grab_json_response_with_status( + '/logout', {'data': [bmcuser, bmcpass]}, + headers={ + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'X-XSRF-TOKEN': wc.cookies['XSRF-TOKEN']}) + def send_grant(conn, nodename): cfg = configmanager.ConfigManager(None) @@ -74,6 +104,10 @@ def send_grant(conn, nodename): 'Accept': 'application/json'}) sessionid = wc.cookies['SESSION'] sessiontok = wc.cookies['XSRF-TOKEN'] + _usersessions[sessionid] = { + 'webclient': wc, + 'nodename': nodename, + } url = '/kvm/0' fprintinfo = cfg.get_node_attributes(nodename, 'pubkeys.tls_hardwaremanager') fprint = fprintinfo.get( @@ -102,32 +136,34 @@ def evaluate_request(conn): if uid != os.getuid(): return rqcode, fieldlen = struct.unpack('!BI', conn.recv(5)) - if rqcode != 1: - return authtoken = conn.recv(fieldlen).decode() if authtoken != _vinztoken: return - fieldlen = struct.unpack('!I', conn.recv(4))[0] - nodename = conn.recv(fieldlen).decode() - idtype = struct.unpack('!B', conn.recv(1))[0] - if idtype == 1: - usernum = struct.unpack('!I', conn.recv(4))[0] - if usernum == 0: # root is a special guy + if rqcode == 2: # disconnect notification + fieldlen = struct.unpack('!I', conn.recv(4))[0] + sessionid = conn.recv(fieldlen).decode() + close_session(sessionid) + conn.recv(1) # digest 0xff + if rqcode == 1: # request for new connection + fieldlen = struct.unpack('!I', conn.recv(4))[0] + nodename = conn.recv(fieldlen).decode() + idtype = struct.unpack('!B', conn.recv(1))[0] + if idtype == 1: + usernum = struct.unpack('!I', conn.recv(4))[0] + if usernum == 0: # root is a special guy + send_grant(conn, nodename) + return + try: + authname = pwd.getpwuid(usernum).pw_name + except Exception: + return + allow = auth.authorize(authname, f'/nodes/{nodename}/console/ikvm') + if not allow: + return send_grant(conn, nodename) + else: return - try: - authname = pwd.getpwuid(usernum).pw_name - except Exception: - return - allow = auth.authorize(authname, f'/nodes/{nodename}/console/ikvm') - if not allow: - return - send_grant(conn, nodename) - else: - return - if conn.recv(1) != b'\xff': - return - + conn.recv(1) # should be 0xff finally: conn.close() From c048439849198f039b0fb6d607118172697327ff Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Thu, 5 Sep 2024 11:44:29 -0400 Subject: [PATCH 10/11] Reuse existing vinz unix session for a node --- confluent_server/confluent/vinzmanager.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/confluent_server/confluent/vinzmanager.py b/confluent_server/confluent/vinzmanager.py index 991ba0be..75b51294 100644 --- a/confluent_server/confluent/vinzmanager.py +++ b/confluent_server/confluent/vinzmanager.py @@ -37,14 +37,17 @@ def assure_vinz(): eventlet.sleep(0.5) eventlet.spawn(monitor_requests) - +_unix_by_nodename = {} def get_url(nodename, inputdata): method = inputdata.inputbynode[nodename] assure_vinz() if method == 'wss': return f'/vinz/kvmsession/{nodename}' elif method == 'unix': - return request_session(nodename) + if nodename not in _unix_by_nodename: + _unix_by_nodename[nodename] = request_session(nodename) + return _unix_by_nodename[nodename] + _usersessions = {} def close_session(sessionid): From 0fb19ce26360dbf22a358212b798647009370ed8 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Fri, 6 Sep 2024 13:41:50 -0400 Subject: [PATCH 11/11] Wire up http sessions to vinzmanager --- confluent_server/confluent/httpapi.py | 14 ++++++++++++++ confluent_server/confluent/vinzmanager.py | 20 ++++++++++++++++---- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/confluent_server/confluent/httpapi.py b/confluent_server/confluent/httpapi.py index 10ab8b86..cf6a42ee 100644 --- a/confluent_server/confluent/httpapi.py +++ b/confluent_server/confluent/httpapi.py @@ -72,6 +72,20 @@ opmap = { } +def get_user_for_session(sessionid, sessiontok): + if not isinstance(sessionid, str): + sessionid = sessionid.decode() + if not isinstance(sessiontok, str): + sessiontok = sessiontok.decode() + if not sessiontok or not sessionid: + raise Exception("invalid session id or token") + if sessiontok != httpsessions.get(sessionid, {}).get('csrftoken', None): + raise Exception("Invalid csrf token for session") + user = httpsessions[sessionid]['name'] + if not isinstance(user, str): + user = user.decode() + return user + def group_creation_resources(): yield confluent.messages.Attributes( kv={'name': None}, desc="Name of the group").html() + '
' diff --git a/confluent_server/confluent/vinzmanager.py b/confluent_server/confluent/vinzmanager.py index 75b51294..308acb59 100644 --- a/confluent_server/confluent/vinzmanager.py +++ b/confluent_server/confluent/vinzmanager.py @@ -11,6 +11,7 @@ import eventlet.green.subprocess as subprocess import base64 import os import pwd +import confluent.httpapi as httpapi mountsbyuser = {} _vinzfd = None _vinztoken = None @@ -44,7 +45,7 @@ def get_url(nodename, inputdata): if method == 'wss': return f'/vinz/kvmsession/{nodename}' elif method == 'unix': - if nodename not in _unix_by_nodename: + if nodename not in _unix_by_nodename or not os.path.exists(_unix_by_nodename[nodename]): _unix_by_nodename[nodename] = request_session(nodename) return _unix_by_nodename[nodename] @@ -132,6 +133,8 @@ def send_grant(conn, nodename): conn.send(b'\xff') def evaluate_request(conn): + allow = False + authname = None try: creds = conn.getsockopt(socket.SOL_SOCKET, socket.SO_PEERCRED, struct.calcsize('iII')) @@ -160,13 +163,22 @@ def evaluate_request(conn): authname = pwd.getpwuid(usernum).pw_name except Exception: return - allow = auth.authorize(authname, f'/nodes/{nodename}/console/ikvm') - if not allow: + elif idtype == 2: + fieldlen = struct.unpack('!I', conn.recv(4))[0] + sessionid = conn.recv(fieldlen) + fieldlen = struct.unpack('!I', conn.recv(4))[0] + sessiontok = conn.recv(fieldlen) + try: + authname = httpapi.get_user_for_session(sessionid, sessiontok) + except Exception: return - send_grant(conn, nodename) else: return conn.recv(1) # should be 0xff + if authname: + allow = auth.authorize(authname, f'/nodes/{nodename}/console/ikvm') + if allow: + send_grant(conn, nodename) finally: conn.close()