Don't persist proxy auth token to db

removes last need for encrypted database fields, so db_secret is removed as well.
This commit is contained in:
MinRK
2014-10-27 16:16:21 -07:00
parent 536b9b5e17
commit 548c404265
3 changed files with 80 additions and 78 deletions

View File

@@ -8,6 +8,7 @@ import binascii
import io import io
import logging import logging
import os import os
import socket
from datetime import datetime from datetime import datetime
from subprocess import Popen from subprocess import Popen
@@ -24,6 +25,7 @@ from sqlalchemy.exc import OperationalError
import tornado.httpserver import tornado.httpserver
import tornado.options import tornado.options
from tornado.httpclient import HTTPError
from tornado.ioloop import IOLoop, PeriodicCallback from tornado.ioloop import IOLoop, PeriodicCallback
from tornado.log import LogFormatter, app_log, access_log, gen_log from tornado.log import LogFormatter, app_log, access_log, gen_log
from tornado import gen, web from tornado import gen, web
@@ -170,7 +172,16 @@ class JupyterHubApp(Application):
""" """
) )
def _proxy_auth_token_default(self): def _proxy_auth_token_default(self):
return os.environ.get('CONFIGPROXY_AUTH_TOKEN', orm.new_token()) token = os.environ.get('CONFIGPROXY_AUTH_TOKEN', None)
if not token:
self.log.warn('\n'.join([
"",
"Generating CONFIGPROXY_AUTH_TOKEN. Restarting the Hub will require restarting the proxy.",
"Set CONFIGPROXY_AUTH_TOKEN env or JupyterHubApp.proxy_auth_token config to avoid this message.",
"",
]))
token = orm.new_token()
return token
proxy_api_ip = Unicode('localhost', config=True, proxy_api_ip = Unicode('localhost', config=True,
help="The ip for the proxy API handlers" help="The ip for the proxy API handlers"
@@ -218,17 +229,6 @@ class JupyterHubApp(Application):
help="""File in which to store the cookie secret.""" help="""File in which to store the cookie secret."""
) )
db_secret = Bytes(config=True, env='JPY_DB_SECRET',
help="""The database secret to use to encrypt sensitive information in the database.
Loaded from the JPY_DB_SECRET env variable by default.
"""
)
db_secret_file = Unicode('jupyterhub_db_secret', config=True,
help="""File in which to store the database secret."""
)
authenticator_class = Type(PAMAuthenticator, Authenticator, authenticator_class = Type(PAMAuthenticator, Authenticator,
config=True, config=True,
help="""Class for authenticating users. help="""Class for authenticating users.
@@ -378,15 +378,13 @@ class JupyterHubApp(Application):
self.log.error("%s cannot edit %s", user, path) self.log.error("%s cannot edit %s", user, path)
def init_secrets(self): def init_secrets(self):
traits = self.traits() trait_name = 'cookie_secret'
for key in ('cookie', 'db'): trait = self.traits()[trait_name]
trait_name = '{}_secret'.format(key) env_name = trait.get_metadata('env')
env_name = traits[trait_name].get_metadata('env')
file_attr_name = '{}_secret_file'.format(key)
secret_file = os.path.abspath( secret_file = os.path.abspath(
os.path.expanduser(getattr(self, file_attr_name)) os.path.expanduser(self.cookie_secret_file)
) )
secret = getattr(self, trait_name) secret = self.cookie_secret
secret_from = 'config' secret_from = 'config'
# load priority: 1. config, 2. env, 3. file # load priority: 1. config, 2. env, 3. file
if not secret and os.environ.get(env_name): if not secret and os.environ.get(env_name):
@@ -422,14 +420,13 @@ class JupyterHubApp(Application):
except OSError: except OSError:
self.log.warn("Failed to set permissions on %s", secret_file) self.log.warn("Failed to set permissions on %s", secret_file)
# store the loaded trait value # store the loaded trait value
setattr(self, trait_name, secret) self.cookie_secret = secret
def init_db(self): def init_db(self):
"""Create the database connection""" """Create the database connection"""
self.log.debug("Connecting to db: %s", self.db_url) self.log.debug("Connecting to db: %s", self.db_url)
try: try:
self.db = orm.new_session(self.db_url, reset=self.reset_db, echo=self.debug_db, self.db = orm.new_session(self.db_url, reset=self.reset_db, echo=self.debug_db,
crypto_key=self.db_secret,
**self.db_kwargs **self.db_kwargs
) )
except OperationalError as e: except OperationalError as e:
@@ -565,26 +562,37 @@ class JupyterHubApp(Application):
self.proxy = orm.Proxy( self.proxy = orm.Proxy(
public_server=orm.Server(), public_server=orm.Server(),
api_server=orm.Server(), api_server=orm.Server(),
auth_token = self.proxy_auth_token,
) )
self.db.add(self.proxy) self.db.add(self.proxy)
self.db.commit() self.db.commit()
self.proxy.auth_token = self.proxy_auth_token # not persisted
self.proxy.log = self.log self.proxy.log = self.log
self.proxy.public_server.ip = self.ip self.proxy.public_server.ip = self.ip
self.proxy.public_server.port = self.port self.proxy.public_server.port = self.port
self.proxy.api_server.ip = self.proxy_api_ip self.proxy.api_server.ip = self.proxy_api_ip
self.proxy.api_server.port = self.proxy_api_port self.proxy.api_server.port = self.proxy_api_port
self.proxy.api_server.base_url = u'/api/routes/' self.proxy.api_server.base_url = u'/api/routes/'
if self.proxy.auth_token is None:
self.proxy.auth_token = self.proxy_auth_token
self.db.commit() self.db.commit()
@gen.coroutine @gen.coroutine
def start_proxy(self): def start_proxy(self):
"""Actually start the configurable-http-proxy""" """Actually start the configurable-http-proxy"""
if self.proxy.public_server.is_up() and \ # check for proxy
self.proxy.api_server.is_up(): if self.proxy.public_server.is_up() or self.proxy.api_server.is_up():
self.log.warn("Proxy already running at: %s", self.proxy.public_server.url) # check for *authenticated* access to the proxy (auth token can change)
try:
yield self.proxy.get_routes()
except (HTTPError, OSError, socket.error) as e:
if isinstance(e, HTTPError) and e.code == 403:
msg = "Did CONFIGPROXY_AUTH_TOKEN change?"
else:
msg = "Is something else using %s?" % self.proxy.public_server.url
self.log.error("Proxy appears to be running at %s, but I can't access it (%s)\n%s",
self.proxy.public_server.url, e, msg)
self.exit(1)
return
else:
self.log.info("Proxy already running at: %s", self.proxy.public_server.url)
self.proxy_process = None self.proxy_process = None
return return
@@ -604,7 +612,8 @@ class JupyterHubApp(Application):
cmd.extend(['--ssl-key', self.ssl_key]) cmd.extend(['--ssl-key', self.ssl_key])
if self.ssl_cert: if self.ssl_cert:
cmd.extend(['--ssl-cert', self.ssl_cert]) cmd.extend(['--ssl-cert', self.ssl_cert])
self.log.info("Starting proxy: %s", cmd) self.log.info("Starting proxy @ %s", self.proxy.public_server.url)
self.log.debug("Proxy cmd: %s", cmd)
self.proxy_process = Popen(cmd, env=env) self.proxy_process = Popen(cmd, env=env)
def _check(): def _check():
status = self.proxy_process.poll() status = self.proxy_process.poll()
@@ -763,7 +772,7 @@ class JupyterHubApp(Application):
@gen.coroutine @gen.coroutine
def update_last_activity(self): def update_last_activity(self):
"""Update User.last_activity timestamps from the proxy""" """Update User.last_activity timestamps from the proxy"""
routes = yield self.proxy.fetch_routes() routes = yield self.proxy.get_routes()
for prefix, route in routes.items(): for prefix, route in routes.items():
if 'user' not in route: if 'user' not in route:
# not a user route, ignore it # not a user route, ignore it

View File

@@ -25,7 +25,7 @@ from sqlalchemy.ext.declarative import declarative_base, declared_attr
from sqlalchemy.orm import sessionmaker, relationship, backref from sqlalchemy.orm import sessionmaker, relationship, backref
from sqlalchemy.pool import StaticPool from sqlalchemy.pool import StaticPool
from sqlalchemy import create_engine from sqlalchemy import create_engine
from sqlalchemy_utils.types import EncryptedType, PasswordType from sqlalchemy_utils.types import PasswordType
from .utils import random_port, url_path_join, wait_for_server, wait_for_http_server from .utils import random_port, url_path_join, wait_for_server, wait_for_http_server
@@ -125,7 +125,7 @@ class Proxy(Base):
""" """
__tablename__ = 'proxies' __tablename__ = 'proxies'
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
auth_token = Column(EncryptedType(Unicode, key=b''), default=new_token) auth_token = None
_public_server_id = Column(Integer, ForeignKey('servers.id')) _public_server_id = Column(Integer, ForeignKey('servers.id'))
public_server = relationship(Server, primaryjoin=_public_server_id == Server.id) public_server = relationship(Server, primaryjoin=_public_server_id == Server.id)
_api_server_id = Column(Integer, ForeignKey('servers.id')) _api_server_id = Column(Integer, ForeignKey('servers.id'))
@@ -197,7 +197,7 @@ class Proxy(Base):
yield f yield f
@gen.coroutine @gen.coroutine
def fetch_routes(self, client=None): def get_routes(self, client=None):
"""Fetch the proxy's routes""" """Fetch the proxy's routes"""
resp = yield self.api_request('', client=client) resp = yield self.api_request('', client=client)
raise gen.Return(json.loads(resp.body.decode('utf8', 'replace'))) raise gen.Return(json.loads(resp.body.decode('utf8', 'replace')))
@@ -411,7 +411,7 @@ class CookieToken(Token, Base):
__tablename__ = 'cookie_tokens' __tablename__ = 'cookie_tokens'
def new_session(url="sqlite:///:memory:", reset=False, crypto_key=None, **kwargs): def new_session(url="sqlite:///:memory:", reset=False, **kwargs):
"""Create a new session at url""" """Create a new session at url"""
if url.startswith('sqlite'): if url.startswith('sqlite'):
kwargs.setdefault('connect_args', {'check_same_thread': False}) kwargs.setdefault('connect_args', {'check_same_thread': False})
@@ -421,12 +421,6 @@ def new_session(url="sqlite:///:memory:", reset=False, crypto_key=None, **kwargs
session = Session() session = Session()
if reset: if reset:
Base.metadata.drop_all(engine) Base.metadata.drop_all(engine)
# configure encryption key
if crypto_key:
for table in Base.metadata.tables.values():
for column in table.columns.values():
if isinstance(column.type, EncryptedType):
column.type.key = crypto_key
Base.metadata.create_all(engine) Base.metadata.create_all(engine)
return session return session

View File

@@ -4,7 +4,6 @@ jinja2
simplepam simplepam
sqlalchemy sqlalchemy
sqlalchemy-utils sqlalchemy-utils
cryptography
passlib passlib
requests requests
six six