mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-17 15:03:02 +00:00
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:
@@ -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
|
||||||
|
@@ -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:
|
||||||
|
Reference in New Issue
Block a user