diff --git a/bin/confetty b/bin/confetty index e3936a1b..e27230b9 100755 --- a/bin/confetty +++ b/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() diff --git a/confluent/auth.py b/confluent/auth.py index 035e23a4..aaf27b31 100644 --- a/confluent/auth.py +++ b/confluent/auth.py @@ -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. diff --git a/confluent/common/__init__.py b/confluent/common/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/confluent/common/tlv.py b/confluent/common/tlv.py new file mode 100644 index 00000000..c58a787e --- /dev/null +++ b/confluent/common/tlv.py @@ -0,0 +1,4 @@ +# Define types for TLV use in logs and other + +class Types(object): + text, json = range(2) diff --git a/confluent/common/tlvdata.py b/confluent/common/tlvdata.py index f9be3c42..1fc7327f 100644 --- a/confluent/common/tlvdata.py +++ b/confluent/common/tlvdata.py @@ -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) diff --git a/confluent/log.py b/confluent/log.py index a9e378c9..eff02eb0 100644 --- a/confluent/log.py +++ b/confluent/log.py @@ -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) diff --git a/confluent/sockapi.py b/confluent/sockapi.py index 26f8818a..6c708c72 100644 --- a/confluent/sockapi.py +++ b/confluent/sockapi.py @@ -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