mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-17 15:03:02 +00:00
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:
@@ -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
|
||||||
|
@@ -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
|
||||||
|
|
||||||
|
@@ -4,7 +4,6 @@ jinja2
|
|||||||
simplepam
|
simplepam
|
||||||
sqlalchemy
|
sqlalchemy
|
||||||
sqlalchemy-utils
|
sqlalchemy-utils
|
||||||
cryptography
|
|
||||||
passlib
|
passlib
|
||||||
requests
|
requests
|
||||||
six
|
six
|
||||||
|
Reference in New Issue
Block a user