2
0
mirror of https://github.com/xcat2/confluent.git synced 2024-11-26 03:19:48 +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:
Jarrod Johnson 2014-02-09 10:43:26 -05:00
parent 2f62e9e6bb
commit 70fad4335a
7 changed files with 124 additions and 53 deletions

View File

@ -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()

View File

@ -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.

View File

4
confluent/common/tlv.py Normal file
View File

@ -0,0 +1,4 @@
# Define types for TLV use in logs and other
class Types(object):
text, json = range(2)

View File

@ -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)

View File

@ -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)

View File

@ -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