Create a vault for secret tokens and move some into it.

This is intended to reduce the number of critical secrets stored in the
INI file.  An initial subset of secrets is moved into the vault to test
things out.
This commit is contained in:
Neil Williams
2013-11-15 10:34:57 -08:00
parent 342ad24409
commit 3366083663
6 changed files with 208 additions and 13 deletions

View File

@@ -7,6 +7,15 @@
# any name will do - e.g., 'foo.update' will create
# 'foo.ini')
[secrets]
# the tokens in this section are base64 encoded
# general purpose secret
SECRET = YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU2Nzg5
# secret for /prefs/feeds
FEEDSECRET = YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU2Nzg5
# used for authenticating admin API calls w/o cookie
ADMINSECRET = YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU2Nzg5
#
# r2 - Pylons development environment configuration
@@ -43,14 +52,6 @@ error_reporters =
# the site's tagline, used in the title and description
short_description = open source is awesome
# -- SECRETS! <-- update these first! --
# global secret
SECRET = abcdefghijklmnopqrstuvwxyz0123456789
# secret for /prefs/feeds
FEEDSECRET = abcdefghijklmnopqrstuvwxyz0123456789
# used for authenticating admin API calls w/o cookie
ADMINSECRET = abcdefghijklmnopqrstuvwxyz0123456789
CLOUDSEARCH_SEARCH_API =
CLOUDSEARCH_DOC_API =
CLOUDSEARCH_SUBREDDIT_SEARCH_API =

View File

@@ -22,6 +22,8 @@
from datetime import datetime
from urlparse import urlparse
import base64
import ConfigParser
import locale
import json
@@ -64,7 +66,9 @@ from r2.lib.stats import Stats, CacheStats, StatsCollectingConnectionPool
from r2.lib.translation import get_active_langs, I18N_PATH
from r2.lib.utils import config_gold_price, thread_dump
LIVE_CONFIG_NODE = "/config/live"
SECRETS_NODE = "/config/secrets"
def extract_live_config(config, plugins):
@@ -84,6 +88,24 @@ def extract_live_config(config, plugins):
return parsed
def _decode_secrets(secrets):
return {key: base64.b64decode(value) for key, value in secrets.iteritems()}
def extract_secrets(config):
# similarly to the live_config one above, if we just did
# .options("secrets") we'd get back all the junk from DEFAULT too. bleh.
secrets = config._sections["secrets"].copy()
del secrets["__name__"] # magic value used by ConfigParser
return _decode_secrets(secrets)
def fetch_secrets(zk_client):
node_data = zk_client.get(SECRETS_NODE)[0]
secrets = json.loads(node_data)
return _decode_secrets(secrets)
class Globals(object):
spec = {
@@ -434,14 +456,17 @@ class Globals(object):
self.zookeeper = connect_to_zookeeper(zk_hosts, (zk_username,
zk_password))
self.live_config = LiveConfig(self.zookeeper, LIVE_CONFIG_NODE)
self.secrets = fetch_secrets(self.zookeeper)
self.throttles = LiveList(self.zookeeper, "/throttles",
map_fn=ipaddress.ip_network,
reduce_fn=ipaddress.collapse_addresses)
else:
self.zookeeper = None
parser = ConfigParser.RawConfigParser()
parser.optionxform = str
parser.read([self.config["__file__"]])
self.live_config = extract_live_config(parser, self.plugins)
self.secrets = extract_secrets(parser)
self.throttles = tuple() # immutable since it's not real
self.startup_timer.intermediate("zookeeper")

View File

@@ -879,7 +879,8 @@ def make_or_admin_secret_cls(base_cls):
def run(self, secret=None):
'''If validation succeeds, return True if the secret was used,
False otherwise'''
if secret and constant_time_compare(secret, g.ADMINSECRET):
if secret and constant_time_compare(secret,
g.secrets["ADMINSECRET"]):
return True
super(VOrAdminSecret, self).run()
return False

View File

@@ -239,7 +239,7 @@ class Account(Thing):
self._load()
timestr = timestr or time.strftime(COOKIE_TIMESTAMP_FORMAT)
id_time = str(self._id) + ',' + timestr
to_hash = ','.join((id_time, self.password, g.SECRET))
to_hash = ','.join((id_time, self.password, g.secrets["SECRET"]))
return id_time + ',' + hashlib.sha1(to_hash).hexdigest()
def make_admin_cookie(self, first_login=None, last_request=None):
@@ -248,7 +248,7 @@ class Account(Thing):
first_login = first_login or datetime.utcnow().strftime(COOKIE_TIMESTAMP_FORMAT)
last_request = last_request or datetime.utcnow().strftime(COOKIE_TIMESTAMP_FORMAT)
hashable = ','.join((first_login, last_request, request.ip, request.user_agent, self.password))
mac = hmac.new(g.SECRET, hashable, hashlib.sha1).hexdigest()
mac = hmac.new(g.secrets["SECRET"], hashable, hashlib.sha1).hexdigest()
return ','.join((first_login, last_request, mac))
def make_otp_cookie(self, timestamp=None):
@@ -257,7 +257,7 @@ class Account(Thing):
timestamp = timestamp or datetime.utcnow().strftime(COOKIE_TIMESTAMP_FORMAT)
secrets = [request.user_agent, self.otp_secret, self.password]
signature = hmac.new(g.SECRET, ','.join([timestamp] + secrets), hashlib.sha1).hexdigest()
signature = hmac.new(g.secrets["SECRET"], ','.join([timestamp] + secrets), hashlib.sha1).hexdigest()
return ",".join((timestamp, signature))
@@ -694,7 +694,8 @@ def valid_feed(name, feedhash, path):
pass
def make_feedhash(user, path):
return hashlib.sha1("".join([user.name, user.password, g.FEEDSECRET])
return hashlib.sha1("".join([user.name, user.password,
g.secrets["FEEDSECRET"]])
).hexdigest()
def make_feedurl(user, path, ext = "rss"):