mirror of
https://github.com/xcat2/confluent.git
synced 2024-11-22 17:43:14 +00:00
Advance state of the socket interface
Properly implement authentication and switch the protocol over to the tlv based protocol. Abandon all thought of the socket being directly accessible. Any CLI semantics will be in confetty and an appliance wishing to expose that CLI directly should use standard ssh stuff with a shell of confetty. The unix domain authentication support makes this feasible (requires user creation push name into confluent repository at the moment..)
This commit is contained in:
parent
2f62e9e6bb
commit
70fad4335a
22
bin/confetty
22
bin/confetty
@ -25,6 +25,7 @@
|
||||
# ~ I will not use for now...
|
||||
|
||||
import fcntl
|
||||
import getpass
|
||||
import optparse
|
||||
import os
|
||||
import select
|
||||
@ -34,6 +35,12 @@ import sys
|
||||
import termios
|
||||
import tty
|
||||
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..'))
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.common.tlvdata as tlvdata
|
||||
|
||||
SO_PASSCRED = 16
|
||||
conserversequence = '\x05c' # ctrl-e, c
|
||||
|
||||
@ -136,9 +143,14 @@ elif opts.unixsock:
|
||||
#Next stop, reading and writing from whichever of stdin and server goes first.
|
||||
#see pyghmi code for solconnect.py
|
||||
|
||||
banner = server.recv(128)
|
||||
while "\n" not in banner:
|
||||
banner += server.recv(128)
|
||||
banner = tlvdata.recv_tlvdata(server)
|
||||
authinfo = tlvdata.recv_tlvdata(server)
|
||||
while authinfo['authpassed'] != 1:
|
||||
username = raw_input("Name: ")
|
||||
passphrase = getpass.getpass("Passphrase: ")
|
||||
tlvdata.send_tlvdata(server,
|
||||
{'username': username, 'passphrase': passphrase})
|
||||
authinfo = tlvdata.recv_tlvdata(server)
|
||||
tty.setraw(sys.stdin.fileno())
|
||||
fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, os.O_NONBLOCK)
|
||||
# clear on start can help with readable of TUI, but it
|
||||
@ -151,7 +163,7 @@ while not doexit:
|
||||
for fh in rdylist:
|
||||
if fh == server:
|
||||
#fh.read()
|
||||
data = fh.recv(16384)
|
||||
data = tlvdata.recv_tlvdata(fh)
|
||||
if data:
|
||||
sys.stdout.write(data)
|
||||
sys.stdout.flush()
|
||||
@ -163,5 +175,5 @@ while not doexit:
|
||||
input = fh.read()
|
||||
input = check_escape_seq(input, fh)
|
||||
if input:
|
||||
server.sendall(input)
|
||||
tlvdata.send_tlvdata(server, input)
|
||||
exit()
|
||||
|
@ -113,6 +113,9 @@ def check_user_passphrase(name, passphrase, element=None, tenant=False):
|
||||
:param tenant: Optional explicit indication of tenant (defaults to
|
||||
embedded in name)
|
||||
"""
|
||||
# The reason why tenant is 'False' instead of 'None':
|
||||
# None means explictly not a tenant. False means check
|
||||
# the username for signs of being a tenant
|
||||
# If there is any sign of guessing on a user, all valid and
|
||||
# invalid attempts are equally slowed to no more than 20 per second
|
||||
# for that particular user.
|
||||
|
0
confluent/common/__init__.py
Normal file
0
confluent/common/__init__.py
Normal file
4
confluent/common/tlv.py
Normal file
4
confluent/common/tlv.py
Normal file
@ -0,0 +1,4 @@
|
||||
# Define types for TLV use in logs and other
|
||||
|
||||
class Types(object):
|
||||
text, json = range(2)
|
@ -1,10 +1,11 @@
|
||||
|
||||
import confluent.common.tlv as tlv
|
||||
import json
|
||||
import struct
|
||||
|
||||
def send_tlvdata(handle, data):
|
||||
if not isintance(type, int):
|
||||
raise Exception()
|
||||
if isinstance(data, str):
|
||||
# plain text, e.g. console data
|
||||
tl = len(data)
|
||||
if tl < 16777216:
|
||||
#type for string is '0', so we don't need
|
||||
@ -13,8 +14,8 @@ def send_tlvdata(handle, data):
|
||||
else:
|
||||
raise Exception("String data length exceeds protocol")
|
||||
handle.sendall(data)
|
||||
handle.flush()
|
||||
elif isinstance(data, dict): # JSON currently only goes to 4 bytes
|
||||
# Some structured message, like what would be seen in http responses
|
||||
sdata = json.dumps(data, separators=(',',':'))
|
||||
tl = len(sdata)
|
||||
if tl > 16777215:
|
||||
@ -22,23 +23,23 @@ def send_tlvdata(handle, data):
|
||||
# xor in the type (0b1 << 24)
|
||||
tl |= 16777216
|
||||
handle.sendall(struct.pack("!I", tl))
|
||||
handle.write(sdata)
|
||||
handle.flush()
|
||||
handle.sendall(sdata)
|
||||
|
||||
def recv_tlvdata(handle):
|
||||
tl = handle.recv(4)
|
||||
tl = struct.unpack("!B", tl)[0]
|
||||
if tl & 0b10000000:
|
||||
tl = struct.unpack("!I", tl)[0]
|
||||
if tl & 0b10000000000000000000000000000000:
|
||||
raise Exception("Protocol Violation, reserved bit set")
|
||||
# 4 byte tlv
|
||||
dlen = tl & 16777215 # 24 ones
|
||||
type = (tl & 2130706432) >> 24 # 7 ones, followed by 24 zeroes
|
||||
if type == 0:
|
||||
data = handle.recv(dlen)
|
||||
while len(data) < dlen:
|
||||
ndata = handle.recv(dlen - len(data))
|
||||
if not ndata:
|
||||
raise Exception("Error reading data")
|
||||
elif type == 1:
|
||||
sdata = handle.recv(dlen)
|
||||
return json.loads(sdata)
|
||||
dlen = tl & 16777215 # grab lower 24 bits
|
||||
type = (tl & 2130706432) >> 24 # grab 7 bits from near beginning
|
||||
data = handle.recv(dlen)
|
||||
while len(data) < dlen:
|
||||
ndata = handle.recv(dlen - len(data))
|
||||
if not ndata:
|
||||
raise Exception("Error reading data")
|
||||
data += ndata
|
||||
if type == tlv.Types.text:
|
||||
return data
|
||||
elif type == tlv.Types.json:
|
||||
return json.loads(data)
|
||||
|
@ -19,3 +19,45 @@
|
||||
# can always be manipulated....
|
||||
# - TPM PCRs. Understand better what PCRs may be used/extended perhaps
|
||||
# per-indexed event..
|
||||
|
||||
# On the plaintext half of a log:
|
||||
# Strategy is that console log shall have just the payload logged, sans
|
||||
# timestamp.
|
||||
# Other events (e.g. SEL or other actions) will get timestamps
|
||||
# preceding '[]' to denote them. Timestamps will be in local
|
||||
# time in the text output
|
||||
# If a log is set to not be primarily console type data, then '[]' are not
|
||||
# used, timestamp still precedes record, and records get '\n' appended
|
||||
# If binary data is really called for, base64 format shall be used to
|
||||
# avoid messing up text reads.
|
||||
|
||||
# On the binary half of a log (.jnl):
|
||||
# The specific format can be whatever we decide since there is a text format.
|
||||
# The information to store:
|
||||
# - leading bit reserved, 0 for now
|
||||
# - length of metadata record 7 bits
|
||||
# - type of data referenced by this entry (one byte)
|
||||
# - offset into the text log to begin (4 bytes)
|
||||
# - length of data referenced by this entry (2 bytes)
|
||||
# - UTC timestamp of this entry in seconds since epoch (unsigned 32 bit?)
|
||||
# - CRC32 over the record
|
||||
# (a future extended version might include suport for Forward Secure Sealing
|
||||
# or other fields)
|
||||
|
||||
import os
|
||||
|
||||
# on conserving filehandles:
|
||||
# upon write, if file not open, open it for append
|
||||
# upon write, schedule/reschedule closing filehandle in 15 seconds
|
||||
# this way, idle log files get closed, mitigating risk of running afoul
|
||||
# of uname type limts, but the filehandle stays open under load and
|
||||
# buffering and such work when things are busy
|
||||
# perhaps test with very low ulimit and detect shortage and
|
||||
# switch to aggressive handle reclaim, tanking performance
|
||||
# if that happens, warn to have user increase ulimit for optimal
|
||||
# performance
|
||||
|
||||
class Logger(object):
|
||||
def __init__(self, location, console=True, configmanager):
|
||||
self.location = location
|
||||
os.path.isdir(location)
|
||||
|
@ -5,6 +5,8 @@
|
||||
# It implement unix and tls sockets
|
||||
#
|
||||
# TODO: SO_PEERCRED for unix socket
|
||||
import confluent.auth as auth
|
||||
import confluent.common.tlvdata as tlvdata
|
||||
import confluent.consoleserver as consoleserver
|
||||
import confluent.config.configmanager as configmanager
|
||||
import eventlet.green.socket as socket
|
||||
@ -15,39 +17,46 @@ import struct
|
||||
|
||||
SO_PEERCRED = 17
|
||||
|
||||
class ClientConsole(object):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
|
||||
def sendall(self, data):
|
||||
tlvdata.send_tlvdata(self.client, data)
|
||||
|
||||
def sessionhdl(connection, authname):
|
||||
#TODO: authenticate and authorize peer
|
||||
# For now, trying to test the console stuff, so let's just do n1.
|
||||
skipauth = False
|
||||
# For now, trying to test the console stuff, so let's just do n4.
|
||||
authenticated = False
|
||||
if authname and isinstance(authname, bool):
|
||||
skipauth = True
|
||||
connection.sendall("Confluent -- v0 --\r\n")
|
||||
if authname is None: # prompt for name and passphrase
|
||||
connection.sendall("Name: ")
|
||||
username = connection.recv(4096)
|
||||
connection.sendall(username)
|
||||
while "\r" not in username:
|
||||
ddata = connection.recv(4096)
|
||||
if not ddata:
|
||||
return
|
||||
connection.sendall(ddata)
|
||||
username += ddata
|
||||
username, _, passphrase = username.partition("\r")
|
||||
connection.sendall("\nPassphrase: ")
|
||||
while "\r" not in passphrase:
|
||||
pdata = connection.recv(4096)
|
||||
if not pdata:
|
||||
return
|
||||
passphrase += pdata
|
||||
connection.sendall("\r\n")
|
||||
print username
|
||||
print passphrase
|
||||
connection.sendall("Confluent -- v0 -- Session Granted\r\n/->")
|
||||
cfm = configmanager.ConfigManager(tenant=0)
|
||||
consession = consoleserver.ConsoleSession(node='n1', configmanager=cfm,
|
||||
datacallback=connection.sendall)
|
||||
authenticated = True
|
||||
cfm = configmanager.ConfigManager(tenant=None)
|
||||
elif authname:
|
||||
authenticated = True
|
||||
authdata = auth.authorize(authname, element=None)
|
||||
cfm = authdata[1]
|
||||
authenticated = True
|
||||
tlvdata.send_tlvdata(connection,"Confluent -- v0 --")
|
||||
while not authenticated: # prompt for name and passphrase
|
||||
tlvdata.send_tlvdata(connection, {'authpassed': 0})
|
||||
response = tlvdata.recv_tlvdata(connection)
|
||||
username = response['username']
|
||||
passphrase = response['passphrase']
|
||||
# NOTE(jbjohnso): Here, we need to authenticate, but not
|
||||
# authorize a user. When authorization starts understanding
|
||||
# element path, that authorization will need to be called
|
||||
# per request the user makes
|
||||
authdata = auth.check_user_passphrase(username, passphrase)
|
||||
if authdata is None:
|
||||
tlvdata.send_tlvdata(connection, {'authpassed': 0})
|
||||
else:
|
||||
authenticated = True
|
||||
cfm = authdata[1]
|
||||
tlvdata.send_tlvdata(connection, {'authpassed': 1})
|
||||
ccons = ClientConsole(connection)
|
||||
consession = consoleserver.ConsoleSession(node='n4', configmanager=cfm,
|
||||
datacallback=ccons.sendall)
|
||||
while (1):
|
||||
data = connection.recv(4096)
|
||||
data = tlvdata.recv_tlvdata(connection)
|
||||
if not data:
|
||||
consession.destroy()
|
||||
return
|
||||
|
Loading…
Reference in New Issue
Block a user