use 32B hex cookie secret

instead of large b64 secret, which doesn't make sense for sha256

Warn about deprecated base64 secrets and too-large secrets.
This commit is contained in:
Min RK
2017-02-14 14:36:50 +01:00
parent abf554f9cf
commit 9ae708b367
2 changed files with 45 additions and 15 deletions

View File

@@ -321,10 +321,11 @@ as follows:
c.JupyterHub.cookie_secret_file = '/srv/jupyterhub/cookie_secret' c.JupyterHub.cookie_secret_file = '/srv/jupyterhub/cookie_secret'
``` ```
The content of this file should be a long random string encoded in MIME Base64. An example would be to generate this file as: The content of this file should be 32 random bytes, encoded as hex.
An example would be to generate this file with:
```bash ```bash
openssl rand -base64 2048 > /srv/jupyterhub/cookie_secret openssl rand -hex 32 > /srv/jupyterhub/cookie_secret
``` ```
In most deployments of JupyterHub, you should point this to a secure location on the file In most deployments of JupyterHub, you should point this to a secure location on the file
@@ -339,7 +340,7 @@ the `JPY_COOKIE_SECRET` environment variable, which is a hex-encoded string. You
can set it this way: can set it this way:
```bash ```bash
export JPY_COOKIE_SECRET=`openssl rand -hex 1024` export JPY_COOKIE_SECRET=`openssl rand -hex 32`
``` ```
For security reasons, this environment variable should only be visible to the Hub. For security reasons, this environment variable should only be visible to the Hub.
@@ -350,7 +351,7 @@ You can also set the cookie secret in the configuration file itself,`jupyterhub_
as a binary string: as a binary string:
```python ```python
c.JupyterHub.cookie_secret = bytes.fromhex('VERY LONG SECRET HEX STRING') c.JupyterHub.cookie_secret = bytes.fromhex('64 CHAR HEX STRING')
``` ```
### Proxy authentication token ### Proxy authentication token

View File

@@ -6,16 +6,18 @@
import atexit import atexit
import binascii import binascii
from datetime import datetime
from getpass import getuser
import logging import logging
import os import os
import re
import shutil import shutil
import signal import signal
import socket import socket
import sys
import threading
from datetime import datetime
from getpass import getuser
from subprocess import Popen from subprocess import Popen
import sys
from textwrap import dedent
import threading
from urllib.parse import urlparse from urllib.parse import urlparse
if sys.version_info[:2] < (3, 3): if sys.version_info[:2] < (3, 3):
@@ -36,7 +38,7 @@ from tornado import gen, web
from traitlets import ( from traitlets import (
Unicode, Integer, Dict, TraitError, List, Bool, Any, Unicode, Integer, Dict, TraitError, List, Bool, Any,
Type, Set, Instance, Bytes, Float, Type, Set, Instance, Bytes, Float,
observe, default, observe, default, validate,
) )
from traitlets.config import Application, catch_config_error from traitlets.config import Application, catch_config_error
@@ -99,8 +101,9 @@ flags = {
), ),
} }
SECRET_BYTES = 2048 # the number of bytes to use when generating new secrets COOKIE_SECRET_BYTES = 32 # the number of bytes to use when generating new cookie secrets
HEX_RE = re.compile('([a-f0-9]{2})+', re.IGNORECASE)
class NewToken(Application): class NewToken(Application):
"""Generate and print a new API token""" """Generate and print a new API token"""
@@ -409,11 +412,20 @@ class JupyterHub(Application):
help="""The cookie secret to use to encrypt cookies. help="""The cookie secret to use to encrypt cookies.
Loaded from the JPY_COOKIE_SECRET env variable by default. Loaded from the JPY_COOKIE_SECRET env variable by default.
Should be exactly 256 bits (32 bytes).
""" """
).tag( ).tag(
config=True, config=True,
env='JPY_COOKIE_SECRET', env='JPY_COOKIE_SECRET',
) )
@validate('cookie_secret')
def _cookie_secret_check(self, proposal):
secret = proposal.value
if len(secret) > COOKIE_SECRET_BYTES:
self.log.warning("Cookie secret is %i bytes. It should be %i.",
len(secret), COOKIE_SECRET_BYTES,
)
cookie_secret_file = Unicode('jupyterhub_cookie_secret', cookie_secret_file = Unicode('jupyterhub_cookie_secret',
help="""File in which to store the cookie secret.""" help="""File in which to store the cookie secret."""
@@ -736,25 +748,42 @@ class JupyterHub(Application):
if perm & 0o07: if perm & 0o07:
raise ValueError("cookie_secret_file can be read or written by anybody") raise ValueError("cookie_secret_file can be read or written by anybody")
with open(secret_file) as f: with open(secret_file) as f:
b64_secret = f.read() text_secret = f.read().strip()
secret = binascii.a2b_base64(b64_secret) if HEX_RE.match(text_secret):
# >= 0.8, use 32B hex
secret = binascii.a2b_hex(text_secret)
else:
# old b64 secret with a bunch of ignored bytes
secret = binascii.a2b_base64(text_secret)
self.log.warning(dedent("""
Old base64 cookie-secret detected in {0}.
JupyterHub >= 0.8 expects 32B hex-encoded cookie secret
for tornado's sha256 cookie signing.
To generate a new secret:
openssl rand -hex 32 > "{0}"
""").format(secret_file))
except Exception as e: except Exception as e:
self.log.error( self.log.error(
"Refusing to run JupyterHub with invalid cookie_secret_file. " "Refusing to run JupyterHub with invalid cookie_secret_file. "
"%s error was: %s", "%s error was: %s",
secret_file, e) secret_file, e)
self.exit(1) self.exit(1)
if not secret: if not secret:
secret_from = 'new' secret_from = 'new'
self.log.debug("Generating new %s", trait_name) self.log.debug("Generating new %s", trait_name)
secret = os.urandom(SECRET_BYTES) secret = os.urandom(COOKIE_SECRET_BYTES)
if secret_file and secret_from == 'new': if secret_file and secret_from == 'new':
# if we generated a new secret, store it in the secret_file # if we generated a new secret, store it in the secret_file
self.log.info("Writing %s to %s", trait_name, secret_file) self.log.info("Writing %s to %s", trait_name, secret_file)
b64_secret = binascii.b2a_base64(secret).decode('ascii') text_secret = binascii.b2a_hex(secret).decode('ascii')
with open(secret_file, 'w') as f: with open(secret_file, 'w') as f:
f.write(b64_secret) f.write(text_secret)
f.write('\n')
try: try:
os.chmod(secret_file, 0o600) os.chmod(secret_file, 0o600)
except OSError: except OSError: