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'
```
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
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
@@ -339,7 +340,7 @@ the `JPY_COOKIE_SECRET` environment variable, which is a hex-encoded string. You
can set it this way:
```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.
@@ -350,7 +351,7 @@ You can also set the cookie secret in the configuration file itself,`jupyterhub_
as a binary string:
```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

View File

@@ -6,16 +6,18 @@
import atexit
import binascii
from datetime import datetime
from getpass import getuser
import logging
import os
import re
import shutil
import signal
import socket
import sys
import threading
from datetime import datetime
from getpass import getuser
from subprocess import Popen
import sys
from textwrap import dedent
import threading
from urllib.parse import urlparse
if sys.version_info[:2] < (3, 3):
@@ -36,7 +38,7 @@ from tornado import gen, web
from traitlets import (
Unicode, Integer, Dict, TraitError, List, Bool, Any,
Type, Set, Instance, Bytes, Float,
observe, default,
observe, default, validate,
)
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):
"""Generate and print a new API token"""
@@ -409,11 +412,20 @@ class JupyterHub(Application):
help="""The cookie secret to use to encrypt cookies.
Loaded from the JPY_COOKIE_SECRET env variable by default.
Should be exactly 256 bits (32 bytes).
"""
).tag(
config=True,
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',
help="""File in which to store the cookie secret."""
@@ -736,25 +748,42 @@ class JupyterHub(Application):
if perm & 0o07:
raise ValueError("cookie_secret_file can be read or written by anybody")
with open(secret_file) as f:
b64_secret = f.read()
secret = binascii.a2b_base64(b64_secret)
text_secret = f.read().strip()
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:
self.log.error(
"Refusing to run JupyterHub with invalid cookie_secret_file. "
"%s error was: %s",
secret_file, e)
self.exit(1)
if not secret:
secret_from = 'new'
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 we generated a new secret, store it in the 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:
f.write(b64_secret)
f.write(text_secret)
f.write('\n')
try:
os.chmod(secret_file, 0o600)
except OSError: