File horizon-secure_SECRET_KEY.patch of Package openstack-dashboard
diff -ruN a/.gitignore b/.gitignore
--- a/.gitignore 2012-06-05 15:43:17.000000000 +0200
+++ b/.gitignore 2012-06-25 15:36:16.029765418 +0200
@@ -10,6 +10,7 @@
reports
horizon.egg-info
openstack_dashboard/local/local_settings.py
+openstack_dashboard/test/.secret_key_store
docs/build/
docs/source/sourcecode
.venv
diff -ruN a/horizon/tests/utils_tests.py b/horizon/tests/utils_tests.py
--- a/horizon/tests/utils_tests.py 2012-06-05 15:43:17.000000000 +0200
+++ b/horizon/tests/utils_tests.py 2012-06-25 15:36:32.677765799 +0200
@@ -15,8 +15,11 @@
# under the License.
+import os
+
from horizon import test
from horizon.utils import validators
+from horizon.utils import secret_key
class ValidatorsTests(test.TestCase):
@@ -40,3 +43,26 @@
self.assertTrue(validators.ipv4_cidr_re.match(cidr))
for cidr in BAD_CIDRS:
self.assertFalse(validators.ipv4_cidr_re.match(cidr))
+
+
+class SecretKeyTests(test.TestCase):
+ def test_generate_secret_key(self):
+ key = secret_key.generate_key(32)
+ self.assertEqual(len(key), 32)
+ self.assertNotEqual(key, secret_key.generate_key(32))
+
+ def test_generate_or_read_key_from_file(self):
+ key_file = ".test_secret_key_store"
+ key = secret_key.generate_or_read_from_file(key_file)
+
+ # Consecutive reads should come from the already existing file:
+ self.assertEqual(key, secret_key.generate_or_read_from_file(key_file))
+
+ # Key file only be read/writable by user:
+ self.assertEqual(oct(os.stat(key_file).st_mode & 0777), "0600")
+ os.chmod(key_file, 0777)
+ self.assertRaises(secret_key.FilePermissionError,
+ secret_key.generate_or_read_from_file, key_file)
+ os.chmod(key_file, 0600)
+
+ os.remove(key_file) # Cleanup
diff -ruN a/horizon/utils/secret_key.py b/horizon/utils/secret_key.py
--- a/horizon/utils/secret_key.py 1970-01-01 01:00:00.000000000 +0100
+++ b/horizon/utils/secret_key.py 2012-06-25 15:37:13.443766746 +0200
@@ -0,0 +1,68 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 Nebula, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+from __future__ import with_statement # Python 2.5 compliance
+
+import lockfile
+import random
+import string
+import tempfile
+import os
+
+
+class FilePermissionError(Exception):
+ """The key file permissions are insecure."""
+ pass
+
+
+def generate_key(key_length=64):
+ """Secret key generator.
+
+ The quality of randomness depends on operating system support,
+ see http://docs.python.org/library/random.html#random.SystemRandom.
+ """
+ if hasattr(random, 'SystemRandom'):
+ choice = random.SystemRandom().choice
+ else:
+ choice = random.choice
+ return ''.join(map(lambda x: choice(string.digits + string.letters),
+ range(key_length)))
+
+
+def generate_or_read_from_file(key_file='.secret_key', key_length=64):
+ """Multiprocess-safe secret key file generator.
+
+ Useful to replace the default (and thus unsafe) SECRET_KEY in settings.py
+ upon first start. Save to use, i.e. when multiple Python interpreters
+ serve the dashboard Django application (e.g. in a mod_wsgi + daemonized
+ environment). Also checks if file permissions are set correctly and
+ throws an exception if not.
+ """
+ lock = lockfile.FileLock(key_file)
+ with lock:
+ if not os.path.exists(key_file):
+ key = generate_key(key_length)
+ old_umask = os.umask(0177) # Use '0600' file permissions
+ with open(key_file, 'w') as f:
+ f.write(key)
+ os.umask(old_umask)
+ else:
+ if oct(os.stat(key_file).st_mode & 0777) != '0600':
+ raise FilePermissionError("Insecure key file permissions!")
+ with open(key_file, 'r') as f:
+ key = f.readline()
+ return key
diff -ruN a/openstack_dashboard/local/local_settings.py.example b/openstack_dashboard/local/local_settings.py.example
--- a/openstack_dashboard/local/local_settings.py.example 2012-06-05 15:43:17.000000000 +0200
+++ b/openstack_dashboard/local/local_settings.py.example 2012-06-25 15:38:21.185768316 +0200
@@ -7,9 +7,6 @@
PROD = False
USE_SSL = False
-# Note: You should change this value
-SECRET_KEY = 'elj1IWiLoWHgcyYxFVLj7cM5rGOOxWl0'
-
# Specify a regular expression to validate user passwords.
# HORIZON_CONFIG = {
# "password_validator": {
@@ -20,6 +17,18 @@
LOCAL_PATH = os.path.dirname(os.path.abspath(__file__))
+# Set custom secret key:
+# You can either set it to a specific value or you can let horizion generate a
+# default secret key that is unique on this machine, e.i. regardless of the
+# amount of Python WSGI workers (if used behind Apache+mod_wsgi): However, there
+# may be situations where you would want to set this explicitly, e.g. when
+# multiple dashboard instances are distributed on different machines (usually
+# behind a load-balancer). Either you have to make sure that a session gets all
+# requests routed to the same dashboard instance or you set the same SECRET_KEY
+# for all of them.
+# from horizon.utils import secret_key
+# SECRET_KEY = secret_key.generate_or_read_from_file(os.path.join(LOCAL_PATH, '.secret_key_store'))
+
# We recommend you use memcached for development; otherwise after every reload
# of the django development server, you will have to login again. To use
# memcached set CACHE_BACKED to something like 'memcached://127.0.0.1:11211/'
diff -ruN a/tools/pip-requires b/tools/pip-requires
--- a/tools/pip-requires 2012-06-05 15:43:17.000000000 +0200
+++ b/tools/pip-requires 2012-06-25 15:38:34.263768622 +0200
@@ -17,6 +17,9 @@
xattr
iso8601
+# Horizon Utility Requirements
+lockfile # for SECURE_KEY generation
+
# Horizon Non-pip Requirements
-e git+https://github.com/openstack/python-novaclient.git#egg=python-novaclient
-e git+https://github.com/openstack/python-keystoneclient.git#egg=python-keystoneclient