ToDo | 1
scripts/README.ssl | 84 +++++++++++++++++++++++++++++++
scripts/cfs_add_directory.py | 17 +-----
scripts/cfs_add_tenant.py | 18 +-----
scripts/cfs_add_volume.py | 18 +-----
scripts/cfs_delete_tenant.py | 16 +-----
scripts/cfs_enable_tenant.py | 18 +-----
scripts/cfs_mount.py | 20 ++++++-
scripts/cfs_paths.py | 1
scripts/cfs_rm_volume.py | 16 +-----
scripts/cfs_start_volume.py | 16 +-----
scripts/cfs_stop_volume.py | 16 +-----
scripts/cfs_utils.py | 49 ++++++++++++++++++
scripts/cloudfsd.py | 106 ++++++++++++++++++++++++++++++++++++++--
scripts/ssl_examples/admin.key | 15 +++++
scripts/ssl_examples/admin.pem | 16 ++++++
scripts/ssl_examples/admin.pfx |binary
scripts/ssl_examples/root.pem | 48 ++++++++++++++++++
scripts/ssl_examples/server.key | 15 +++++
scripts/ssl_examples/server.pem | 16 ++++++
scripts/ssl_examples/tenant.key | 15 +++++
scripts/ssl_examples/tenant.pem | 16 ++++++
22 files changed, 419 insertions(+), 118 deletions(-)
New commits:
commit 095f7664f5731ca79cebace7e7959fbbb6701202
Author: Jeff Darcy <jdarcy(a)redhat.com>
Date: Mon May 16 15:20:02 2011 -0400
Added SSL support (disabled for now).
See scripts/README.ssl for more information.
diff --git a/ToDo b/ToDo
index 2fbbcb9..48a0dc9 100644
--- a/ToDo
+++ b/ToDo
@@ -2,7 +2,6 @@
(nothing left)
= Medium Priority =
-SSL
Sanitize volume/tenant names etc. to avoid XSS/injection
Add/document code to generate brick list for volume creation
Handle IPv6, multi-homed hosts, localhost in scan_gfs_volfiles
diff --git a/scripts/README.ssl b/scripts/README.ssl
new file mode 100644
index 0000000..3547f6b
--- /dev/null
+++ b/scripts/README.ssl
@@ -0,0 +1,84 @@
+= How to set up management SSL =
+
+The CloudFS management system uses three roles, each identified by a separate
+certificate. Only the commonName field of each certificate is checked, as
+folows.
+
+ * The servers themselves (commonName="The Server") represented by
+ PEM format certificate and key files.
+
+ * An administrator (commonName="Super User") represented by a PKCS#12
+ format file. This file is intended for loading into browsers; it
+ includes both certificate and key.
+
+ * A generic tenant (commonName="A Tenant") represented by PEM format
+ certificate and key. This is used only for management operations,
+ and is separate from certificates/keys used for the low-level SSL
+ transport (though for convenience this certificate might be used as
+ the signing key for those certificates).
+
+Here is an example of how to create the administrator certificate. The others
+are similar except that they don't require the last step.
+
+ # Generate key.
+ openssl genrsa 1024 > admin.key
+
+ # Generate PEM certificate.
+ openssl req -new -x509 -key admin.key -out admin.pem
+
+ # Convert to a PKCS#12 certificate.
+ openssl pkcs12 -export -in admin.pem -inkey admin.key \
+ -name "Super User" > admin.pfx
+
+There is a directory of sample certificates/keys included with the code, in
+.../scripts/ssl_examples. Note that the file names do matter, as they are
+embedded in the code.
+
+ * server.key, server.pem: servers' private key and certificate (PEM)
+
+ * admin.key, admin.pem: admin's private key and certificate (PEM)
+
+ * admin.pfx: admin's combined certificate (PKCS#12, export pw = "fubar")
+
+ * tenant.key, tenant.pem: tenants' private key and certificate (PEM)
+
+ * root.pem: "certificate authority" list for servers (multiple PEM).
+
+The last issue is who trusts whom. This is represented by which certificates
+are loaded into an endpoint's "certificate authority" file (which in our case
+actually includes the certificates themselves rather than the authorities).
+The servers need to trust each other for internal calls, admins for external
+ones, and tenants for the specific operations used when mounting. Thus, the
+easiest thing to do is:
+
+ cat server.pem admin.pem tenant.pem > root.pem
+
+Clients don't particularly need to trust anyone, so no action is needed there.
+
+== Appendix A: configuration for Firefox ==
+
+To load the admin certificate in Firefox, do the following:
+
+ * Select the "Edit/Preferences" menu item.
+
+ * Go to the "Advanced" section, "Encryption" tab.
+
+ * Click the "View Certificates" button.
+
+ * Click the "Import..." button and select the .pfx file.
+
+== Appendix B: configuration for Chrome ==
+
+To load the admin certificate in Chrome, do the following:
+
+ * Select the "Preferences" item from the configuration menu (the little
+ spanner at the top right).
+
+ * Click on "Under the Hood"
+
+ * Scroll down to "Security" and click on the "Manage Certificates..."
+ button.
+
+ * Click on the "Import..." button and select the .pfx file.
+
+
diff --git a/scripts/cfs_add_directory.py b/scripts/cfs_add_directory.py
index 1c9f0b9..a1b6585 100755
--- a/scripts/cfs_add_directory.py
+++ b/scripts/cfs_add_directory.py
@@ -6,7 +6,6 @@ import socket
import string
import sys
import urllib
-import urllib2
from bottle import request, template
import cfs_utils
@@ -101,19 +100,9 @@ def add_local (path):
def run_common (path):
node_list = cfs_utils.get_members()
blob = []
- for node in node_list:
- scratch = [node, []]
- if socket.gethostbyname(node) in cfs_utils.local_addrs:
- url_obj = [add_local(path)]
- else:
- url = "http://%s:%d/volumes/add_dir_local" % (
- node, cfs_paths.CLOUDFSD_PORT)
- data = urllib.urlencode([("path",path)])
- url_obj = urllib2.urlopen(url,data=data)
- for line in url_obj:
- scratch[1].append(line)
- blob.append(scratch)
- return blob
+ data = urllib.urlencode([("path",path)])
+ return cfs_utils.do_all(blob,node_list,"volumes/add_dir_local",data,
+ add_local, path)
def run_www ():
path = request.forms.get("path")
diff --git a/scripts/cfs_add_tenant.py b/scripts/cfs_add_tenant.py
index d460367..83982ce 100755
--- a/scripts/cfs_add_tenant.py
+++ b/scripts/cfs_add_tenant.py
@@ -6,7 +6,6 @@ import socket
import string
import sys
import urllib
-import urllib2
from bottle import request, template
import cfs_utils
@@ -22,20 +21,9 @@ def add_local (tn_name, tn_pw):
def run_common (tn_name, tn_pw):
node_list = cfs_utils.get_members()
blob = []
- for node in node_list:
- scratch = [node, []]
- if socket.gethostbyname(node) in cfs_utils.local_addrs:
- url_obj = [add_local(tn_name,tn_pw)]
- else:
- url = "http://%s:%d/tenants/add_local" % (
- node, cfs_paths.CLOUDFSD_PORT)
- data = urllib.urlencode([("tn_name",tn_name),
- ("tn_pw",tn_pw)])
- url_obj = urllib2.urlopen(url,data=data)
- for line in url_obj:
- scratch[1].append(line)
- blob.append(scratch)
- return blob
+ data = urllib.urlencode([("tn_name",tn_name), ("tn_pw",tn_pw)])
+ return cfs_utils.do_all(blob,node_list,"tenants/add_local",data,
+ add_local, tn_name, tn_pw)
def run_www ():
tn_name = request.forms.get("tn_name")
diff --git a/scripts/cfs_add_volume.py b/scripts/cfs_add_volume.py
index 32bdc60..a00e2bc 100755
--- a/scripts/cfs_add_volume.py
+++ b/scripts/cfs_add_volume.py
@@ -6,7 +6,6 @@ import socket
import string
import sys
import urllib
-import urllib2
from bottle import request, template
import cfs_paths
@@ -29,19 +28,10 @@ def run_common (vname, vtype, vcount, bricks):
if sts:
return [["gluster",["command failed with %d"%sts]]]
blob = []
- for node in cfs_utils.get_nodes_for_vol(vname):
- scratch = [node,[]]
- if socket.gethostbyname(node) in cfs_utils.local_addrs:
- url_obj = [add_local(vname)]
- else:
- url = "http://%s:%d/volumes/add_vol_local" % (
- node, cfs_paths.CLOUDFSD_PORT)
- data = urllib.urlencode([("vname",vname)])
- url_obj = urllib2.urlopen(url,data=data)
- for line in url_obj:
- scratch[1].append(line)
- blob.append(scratch)
- return blob
+ node_list = cfs_utils.get_nodes_for_vol(vname)
+ data = urllib.urlencode([("vname",vname)])
+ return cfs_utils.do_all(blob,node_list,"volumes/add_vol_local",data,
+ add_local, vname)
def run_www ():
volume_id = request.forms.get("VOLUMEID")
diff --git a/scripts/cfs_delete_tenant.py b/scripts/cfs_delete_tenant.py
index 92c11d7..e921783 100755
--- a/scripts/cfs_delete_tenant.py
+++ b/scripts/cfs_delete_tenant.py
@@ -5,7 +5,6 @@ import re
import socket
import string
import sys
-import urllib2
from bottle import request, template
import cfs_utils
@@ -21,18 +20,9 @@ def delete_local (tn_name):
def run_common (tn_name):
node_list = cfs_utils.get_members()
blob = []
- for node in node_list:
- scratch = [node, []]
- if socket.gethostbyname(node) in cfs_utils.local_addrs:
- url_obj = [delete_local(tn_name)]
- else:
- url = "http://%s:%d/tenants/%s/delete_local" % (
- node, cfs_paths.CLOUDFSD_PORT, tn_name)
- url_obj = urllib2.urlopen(url)
- for line in url_obj:
- scratch[1].append(line)
- blob.append(scratch)
- return blob
+ return cfs_utils.do_all(blob,node_list,
+ "tenants/%s/delete_local" % tn_name, None,
+ delete_local, tn_name)
def run_www (tn_name):
blob = run_common(tn_name)
diff --git a/scripts/cfs_enable_tenant.py b/scripts/cfs_enable_tenant.py
index 146c785..30eb848 100755
--- a/scripts/cfs_enable_tenant.py
+++ b/scripts/cfs_enable_tenant.py
@@ -6,7 +6,6 @@ import socket
import string
import sys
import urllib
-import urllib2
from bottle import request, template
import cfs_paths
@@ -36,20 +35,9 @@ def run_common (tn_name, vol_list):
vol_list = string.join(vol_list,",")
node_list = cfs_utils.get_members()
blob = []
- for node in node_list:
- scratch = [node, []]
- if socket.gethostbyname(node) in cfs_utils.local_addrs:
- url_obj = [enable_local(tn_name,vol_list)]
- else:
- url = "http://%s:%d/tenants/enable_local" % (
- node, cfs_paths.CLOUDFSD_PORT)
- data = urllib.urlencode([("tn_name",tn_name),
- ("vol_list",vol_list)])
- url_obj = urllib2.urlopen(url,data=data)
- for line in url_obj:
- scratch[1].append(line)
- blob.append(scratch)
- return blob
+ data = urllib.urlencode([("tn_name",tn_name), ("vol_list",vol_list)])
+ return cfs_utils.do_all(blob,node_list,"tenants/enable_local",data,
+ enable_local, tn_name, vol_list)
def run_www (tn_name):
vol_list = []
diff --git a/scripts/cfs_mount.py b/scripts/cfs_mount.py
index af4ad06..b3226e0 100755
--- a/scripts/cfs_mount.py
+++ b/scripts/cfs_mount.py
@@ -1,5 +1,6 @@
#!/usr/bin/env python
+import httplib
import json
import os
import sys
@@ -9,6 +10,18 @@ import cfs_paths
import cfs_utils
import volfilter
+if cfs_utils.use_ssl:
+ # Copied from cfs_utils (see long "monkey patch" comment there,
+ # modified to add tenant certs instead of server.
+ def my_hacked_init (self, *args, **kwargs):
+ apply(httplib.HTTPConnection.__init__,(self,)+args,kwargs)
+ self.key_file = "tenant.key"
+ self.cert_file = "tenant.pem"
+ httplib.HTTPSConnection.__init__ = my_hacked_init
+ proto = "https"
+else:
+ proto = "http"
+
# Simple cache of brick-to-port mappings, so we don't have to keep re-fetching
# the maps from the same host if it has multiple bricks.
class mapper:
@@ -18,8 +31,8 @@ class mapper:
if self.cache.has_key(host):
mydict = self.cache[host]
else:
- url = "http://%s:%d/%s/map" % \
- (host, cfs_paths.CLOUDFSD_PORT, volume)
+ url = "%s://%s:%d/%s/map" % \
+ (proto, host, cfs_paths.CLOUDFSD_PORT, volume)
mydict = json.load(urllib2.urlopen(url))
self.cache[host] = mydict
if mydict.has_key(subv):
@@ -37,7 +50,8 @@ if __name__ == "__main__":
(host, volume, username, password, mount) = sys.argv[1:6]
# Fetch the GlusterFS client-side volfile.
- url = "http://%s:%d/%s/fetch" % (host, cfs_paths.CLOUDFSD_PORT, volume)
+ url = "%s://%s:%d/%s/fetch" % \
+ (proto, host, cfs_paths.CLOUDFSD_PORT, volume)
vol_file = urllib2.urlopen(url)
# Load the volfile and clean out some of the crud.
diff --git a/scripts/cfs_paths.py b/scripts/cfs_paths.py
index bf47ebd..2f9085b 100644
--- a/scripts/cfs_paths.py
+++ b/scripts/cfs_paths.py
@@ -10,4 +10,3 @@ idle_subdir = os.path.join(pid_dir,".idle_ports")
used_subdir = os.path.join(pid_dir,".used_ports")
volfile_re = re.compile("(?P<vol>[^.]+)\.(?P<node>.+)\.(?P<path>[^.]+)\.vol")
CLOUDFSD_PORT = 8080
-
diff --git a/scripts/cfs_rm_volume.py b/scripts/cfs_rm_volume.py
index bfab9b2..b99cc2a 100755
--- a/scripts/cfs_rm_volume.py
+++ b/scripts/cfs_rm_volume.py
@@ -6,7 +6,6 @@ import socket
import string
import sys
import urllib
-import urllib2
from bottle import request, template
import cfs_paths
@@ -27,18 +26,9 @@ def run_common (vname):
sts = kid.wait()
if sts:
return template("rm_vol_done.html", name=vname, blob=blob)
- for node in nodes_for_vol:
- scratch = [node,[]]
- if socket.gethostbyname(node) in cfs_utils.local_addrs:
- url_obj = [rm_local(vname)]
- else:
- url = "http://%s:%d/volumes/%s/rm_local" % (
- node, cfs_paths.CLOUDFSD_PORT, vname)
- url_obj = urllib2.urlopen(url)
- for line in url_obj:
- scratch[1].append(line)
- blob.append(scratch)
- return blob
+ return cfs_utils.do_all(blob,nodes_for_vol,
+ "volumes/%s/rm_local" % vname, None,
+ rm_local, vname)
def run_www (vname):
blob = run_common(vname)
diff --git a/scripts/cfs_start_volume.py b/scripts/cfs_start_volume.py
index 11c992b..a9112e6 100755
--- a/scripts/cfs_start_volume.py
+++ b/scripts/cfs_start_volume.py
@@ -7,7 +7,6 @@ import socket
import string
import subprocess
import sys
-import urllib2
from bottle import template
@@ -171,18 +170,9 @@ def start_local (vol_name):
def run_common (vol_name):
node_list = cfs_utils.get_nodes_for_vol(vol_name)
blob = []
- for node in node_list:
- scratch = [node, []]
- if socket.gethostbyname(node) in cfs_utils.local_addrs:
- url_obj = [start_local(vol_name)]
- else:
- url = "http://%s:%d/volumes/%s/start_local" % (
- node, cfs_paths.CLOUDFSD_PORT, vol_name)
- url_obj = urllib2.urlopen(url)
- for line in url_obj:
- scratch[1].append(line)
- blob.append(scratch)
- return blob
+ return cfs_utils.do_all(blob,node_list,
+ "volumes/%s/start_local" % vol_name, None,
+ start_local, vol_name)
def run_www (vol_name):
blob = run_common(vol_name)
diff --git a/scripts/cfs_stop_volume.py b/scripts/cfs_stop_volume.py
index b93b438..ec8126d 100755
--- a/scripts/cfs_stop_volume.py
+++ b/scripts/cfs_stop_volume.py
@@ -6,7 +6,6 @@ import os
import socket
import subprocess
import sys
-import urllib2
from bottle import template
@@ -46,18 +45,9 @@ def stop_local (vol_name):
def run_common (vol_name):
node_list = cfs_utils.get_nodes_for_vol(vol_name)
blob = []
- for node in node_list:
- scratch = [node, []]
- if socket.gethostbyname(node) in cfs_utils.local_addrs:
- url_obj = [stop_local(vol_name)]
- else:
- url = "http://%s:%d/volumes/%s/stop_local" % (
- node, cfs_paths.CLOUDFSD_PORT, vol_name)
- url_obj = urllib2.urlopen(url)
- for line in url_obj:
- scratch[1].append(line)
- blob.append(scratch)
- return blob
+ return cfs_utils.do_all(blob,node_list,
+ "volumes/%s/stop_local" % vol_name, None,
+ stop_local, vol_name)
def run_www (vol_name):
blob = run_common(vol_name)
diff --git a/scripts/cfs_utils.py b/scripts/cfs_utils.py
index 39a36e0..21db9ec 100644
--- a/scripts/cfs_utils.py
+++ b/scripts/cfs_utils.py
@@ -1,8 +1,10 @@
import dbm
import glob
+import httplib
import os
import socket
import subprocess
+import urllib2
import cfs_paths
@@ -15,6 +17,9 @@ good_fs_types = [ "ext2", "ext3", "ext4", "xfs", "btrfs" ]
# Sudo is kind of pointless if we're already running as root.
use_sudo = False
+# Disabled for developer convenience; turn on for production.
+use_ssl = False
+
# We use the class cache to avoid having to call through the shell to find
# executables on $PATH every single time. To do this, we use the shell *once*
# to find the local path and assume all nodes are configured similarly.
@@ -162,6 +167,50 @@ def open_db ():
db_path = os.path.join(cfs_paths.info_dir,"config.db")
return dbm.open(db_path,"c",0600)
+# Yes, this is nasty. My first choice would have been to pass the key/cert as
+# arguments to urllib2.urlopen, but it doesn't give me that option. My second
+# choice would have been to use a clean registration interface to have urllib2
+# create my httplib.HTTPSConnection the way I want, but that's created in
+# AbtractHTTPHandler.do_open with fixed arguments, and there's no "hook" to
+# add the needed values to the object before that very same code starts using
+# it. Subclassing HTTPSConnection would mean also subclassing HTTPSHandler to
+# use it, building a new OpenerDirector to use *that* subclass, and generally
+# reinventing half of urllib2 to get one little bit of functionality. A pox
+# on developers who use terms like "modular" and "abstract" in code that is
+# in fact marked by bad linkage and hidden dependencies, to the point where
+# even a monkey-patch like this is cleaner than their alternative.
+
+if use_ssl:
+ def my_hacked_init (self, *args, **kwargs):
+ apply(httplib.HTTPConnection.__init__,(self,)+args,kwargs)
+ self.key_file = "server.key"
+ self.cert_file = "server.pem"
+ httplib.HTTPSConnection.__init__ = my_hacked_init
+
+# Loop over a set of nodes, issuing a command either directly or via HTTP(S).
+# "Blob" should be a list of [node,text...] and hence mutable, but it's also
+# returned for convenience.
+def do_all (blob, node_list, rmt_cmd, data, lcl_cmd, *args):
+ for node in node_list:
+ scratch = [node, []]
+ if socket.gethostbyname(node) in local_addrs:
+ url_obj = [apply(lcl_cmd,args)]
+ else:
+ if use_ssl:
+ url = "https://%s:%d/%s" % (
+ node, cfs_paths.CLOUDFSD_PORT,rmt_cmd)
+ else:
+ url = "http://%s:%d/%s" % (
+ node, cfs_paths.CLOUDFSD_PORT,rmt_cmd)
+ if data != None:
+ url_obj = urllib2.urlopen(url,data=data)
+ else:
+ url_obj = urllib2.urlopen(url)
+ for line in url_obj:
+ scratch[1].append(line)
+ blob.append(scratch)
+ return blob
+
# Print a "blob" of [node, [line1, line2]] tuples/lists for CLI/debugging.
def print_blob (blob):
for node, text in blob:
diff --git a/scripts/cloudfsd.py b/scripts/cloudfsd.py
index 8255a44..d081aed 100755
--- a/scripts/cloudfsd.py
+++ b/scripts/cloudfsd.py
@@ -1,11 +1,14 @@
#!/usr/bin/python
-from bottle import route, post, run, view, debug, request
import os
import socket
+import ssl
import string
+import wsgiref.simple_server as ss
+from bottle import route, post, run, view, debug, request, ServerAdapter, abort
+
import cfs_paths
import cfs_utils
import volmap
@@ -20,16 +23,34 @@ import cfs_add_tenant
import cfs_delete_tenant
import cfs_enable_tenant
+def authorized (request, *users):
+ if not cfs_utils.use_ssl:
+ return True
+ path = request.environ["PATH_INFO"]
+ cert = request.environ["wsgi.input"]._sock._sslobj.peer_certificate()
+ for part in cert["subject"]:
+ if part[0][0] == "commonName":
+ print "got %s from %s" % (path, part[0][1])
+ return (part[0][1] in users)
+ return False
+
+server_id = "The Server"
+admin_id = "Super User"
+
@route("/")
@route("/cfg")
@route("/cfgmain")
@view("cfgmain.html")
def cfg_main():
+ if not authorized(request,admin_id):
+ abort(401,"Forbidden")
return dict()
@route("/cluster")
@view("cluster.html")
def show_cluster():
+ if not authorized(request,admin_id):
+ abort(401,"Forbidden")
# TBD: handle glusterd presence/startup check sanely
cfs_utils.run_cmd("chkconfig","--add glusterd")
cfs_utils.run_cmd("chkconfig","glusterd on")
@@ -39,6 +60,8 @@ def show_cluster():
@post("/cluster/add_node")
def add_node():
+ if not authorized(request,admin_id):
+ abort(401,"Forbidden")
return cfs_add_node.run_www()
# TBD: implement remove_node
@@ -46,6 +69,8 @@ def add_node():
@route("/volumes")
@view("volumes.html")
def show_volumes():
+ if not authorized(request,admin_id):
+ abort(401,"Forbidden")
brick_list = cfs_utils.get_bricks()
mount_list = cfs_utils.get_mounts(brick_list)
# TBD: allow adding arbitrary directories instead of just mountpoints
@@ -53,53 +78,77 @@ def show_volumes():
@post("/volumes/add_directory")
def add_directory ():
+ if not authorized(request,admin_id):
+ abort(401,"Forbidden")
return cfs_add_directory.run_www()
@post("/volumes/add_dir_local")
def add_dir_local ():
+ if not authorized(request,server_id):
+ abort(401,"Forbidden")
path = request.forms.get("path")
return cfs_add_directory.add_local(path)
@post("/volumes/add_volume")
def add_volume():
+ if not authorized(request,admin_id):
+ abort(401,"Forbidden")
return cfs_add_volume.run_www()
@post("/volumes/add_vol_local")
def add_vol_local ():
+ if not authorized(request,server_id):
+ abort(401,"Forbidden")
vname = request.forms.get("vname")
return cfs_add_volume.add_local(vname)
@route("volumes/:name/remove")
def rm_volume (name):
+ if not authorized(request,admin_id):
+ abort(401,"Forbidden")
return cfs_rm_volume.run_www(name)
@route("/volumes/:name/rm_local")
def rm_vol_local (name):
+ if not authorized(request,server_id):
+ abort(401,"Forbidden")
return cfs_rm_volume.rm_local(name)
@route("/volumes/:vol_name/start")
def start_volume(vol_name):
+ if not authorized(request,admin_id):
+ abort(401,"Forbidden")
return cfs_start_volume.run_www(vol_name)
@route("/volumes/:vol_name/start_local")
def start_local (vol_name):
+ if not authorized(request,server_id):
+ abort(401,"Forbidden")
return cfs_start_volume.start_local(vol_name)
@route("/volumes/:vol_name/stop")
def stop_volume(vol_name):
+ if not authorized(request,admin_id):
+ abort(401,"Forbidden")
return cfs_stop_volume.run_www(vol_name)
@route("/volumes/:vol_name/stop_local")
def stop_local (vol_name):
+ if not authorized(request,server_id):
+ abort(401,"Forbidden")
return cfs_stop_volume.stop_local(vol_name)
# Used by mount.cloudfs
@route("/:vol_name/fetch")
def fetch_client_vf(vol_name):
+ if not authorized(request,"A Tenant"):
+ return "crap"
vf_path = "%s/vols/%s/%s-fuse.vol" % (cfs_paths.gfs_dir, vol_name, vol_name)
return open(vf_path,"r")
-# Used by mount.cloudfs
+# Used by mount.cloudfs; for these two we don't really care who they are, so
+# long as they do provide an identity that's part of this cluster. Merely
+# getting past the SSL handshake using the tenant ID is therefore sufficient.
@route("/:vol_name/map")
def map_paths(vol_name):
return volmap.vol_map(vol_name)
@@ -107,30 +156,42 @@ def map_paths(vol_name):
@route("/tenants")
@view("tenants.html")
def show_tenants ():
+ if not authorized(request,admin_id):
+ abort(401,"Forbidden")
db_obj = cfs_utils.open_db()
return dict(tenants=db_obj)
@post("/tenants/add")
def add_tenant():
+ if not authorized(request,admin_id):
+ abort(401,"Forbidden")
return cfs_add_tenant.run_www()
@post("/tenants/add_local")
def add_tenant_local ():
+ if not authorized(request,server_id):
+ abort(401,"Forbidden")
tn_name = request.forms.get("tn_name")
tn_pw = request.forms.get("tn_pw")
return cfs_add_tenant.add_local(tn_name,tn_pw)
@route("/tenants/:name/delete")
def delete_tenant (name):
+ if not authorized(request,admin_id):
+ abort(401,"Forbidden")
return cfs_delete_tenant.run_www(name)
@route("/tenants/:tn_name/delete_local")
def delete_tenant_local (tn_name):
+ if not authorized(request,server_id):
+ abort(401,"Forbidden")
return cfs_delete_tenant.delete_local(tn_name)
@route("/tenants/:tn_name/volumes")
@view("tenant_volumes.html")
def show_tenant_volumes (tn_name):
+ if not authorized(request,admin_id):
+ abort(401,"Forbidden")
db_obj = cfs_utils.open_db()
all_vols = [v[3:] for v in db_obj.keys() if v.startswith("vt_")]
print all_vols
@@ -141,20 +202,59 @@ def show_tenant_volumes (tn_name):
@post("/tenants/:tn_name/enable")
def enable_tenant_volumes (tn_name):
+ if not authorized(request,admin_id):
+ abort(401,"Forbidden")
return cfs_enable_tenant.run_www(tn_name)
@post("/tenants/enable_local")
def add_tenant_local ():
+ if not authorized(request,server_id):
+ abort(401,"Forbidden")
tn_name = request.forms.get("tn_name")
vol_list = request.forms.get("vol_list")
return cfs_enable_tenant.enable_local(tn_name,vol_list)
+# We don't really care if anyone sees our styles.
@route("/styles/:sheet")
def get_style (sheet):
return file("styles/%s"%sheet,"r")
+class SecureServer (ss.WSGIServer):
+ def server_activate (self):
+ ss.WSGIServer.server_activate(self)
+ self.socket = ssl.wrap_socket(self.socket,server_side=True,
+ certfile="server.pem", keyfile="server.key",
+ cert_reqs=ssl.CERT_REQUIRED, ca_certs="root.pem")
+
+class SecureHandler (ss.WSGIRequestHandler):
+ def handle (self):
+ for part in self.connection.getpeercert()["subject"]:
+ if part[0][0] == "commonName":
+ #print "### client is %s" % part[0][1]
+ break
+ else:
+ raise ssl.CertificateError, "no matching user"
+ ss.WSGIRequestHandler.handle(self)
+ # For some reason clients hang if we try to handle more than
+ # one request on a connection with WSGIServer (expecting a
+ # switch to non-SSL after a login page?) so we force a close
+ # here.
+ self.connection.shutdown(socket.SHUT_RDWR)
+ self.connection.close()
+
+class SecureAdapter (ServerAdapter):
+ def run (self, handler):
+ srv = ss.make_server(self.host,self.port,handler,
+ server_class=SecureServer,handler_class=SecureHandler,
+ **self.options)
+ srv.serve_forever()
+
if __name__ == "__main__":
debug(True)
#run(host='0.0.0.0',port=cfs_paths.CLOUDFSD_PORT,server=CherryPyServer)
- run(host='0.0.0.0',port=cfs_paths.CLOUDFSD_PORT)
+ if cfs_utils.use_ssl:
+ run(host='0.0.0.0',port=cfs_paths.CLOUDFSD_PORT,
+ server=SecureAdapter)
+ else:
+ run(host='0.0.0.0',port=cfs_paths.CLOUDFSD_PORT)
diff --git a/scripts/ssl_examples/admin.key b/scripts/ssl_examples/admin.key
new file mode 100644
index 0000000..bd1011d
--- /dev/null
+++ b/scripts/ssl_examples/admin.key
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDp/8qCWAKyJLDMPC+V6pDyngE+h+K/ZWA6hjIvjuOktRFEl36Y
+mfM03L0JN4rjNS7JQkH4e2YWP9IrR5fKwlMwz1xqNaPSCFLwbUs52VUAPRT+LtQL
+5bpi8VdwvqAlGJg1SazWjfsXyPSmhYhgCZi9aGApkUStnNAtJ7rgnOFfywIDAQAB
+AoGAWnym0/ayvC7CC4huolt9x8RgGM01WuwZ5SfFumxYDXZTgiHPO7W0vclqdGj9
+FVWYjQ1JM4yMcqglXsUlpqu0vRrdt+WmsKZWhbJdUKIcKO3ztW8f++mWhYJINf/S
+srHQzpDIIOTIUrTOsWqJtLZF2bOdeIShany3QhBlpCSobokCQQD859wvTppW3L9K
+IggG5/G0cDLl5NyeE86pwlVDhoXqH90pMBJKLXvpiUHQ8DJ1LOdTTYbeodnQpdKV
+/bB2da+lAkEA7Ny2eiZS4suULDYvX4ZKKUes5st++/ycp9oJWTaBsLkCdgTi7Gx9
+Lu8K87yzxGCF31d3Fsg2Iia1m07a5CO2rwJBAPyT44crjWCq3jB/hFzBUNfQJkUL
+KHqHdaJ8/wKNWzjT0eDMrbd/bX1zI1Q0T3BPBEhh6Qx3wD1tHr/FuXLVXjECQGVl
+SnxZ4YuS6JhQjdEPtLmdJEgcfvyyNAGq1eup6LTVTldlWhspiiFIVWDnu/Dp/QUR
+9Tn8dSgDeCTHCe811qMCQGscWcY5yA85Ax379RJd7/3WXeUOF8ZMwuZsmbWVMKkO
+QXiiLDMnCzumSaDRiUAZxAz1WQ2WmgyhXF3vbzmowAI=
+-----END RSA PRIVATE KEY-----
diff --git a/scripts/ssl_examples/admin.pem b/scripts/ssl_examples/admin.pem
new file mode 100644
index 0000000..2d76c95
--- /dev/null
+++ b/scripts/ssl_examples/admin.pem
@@ -0,0 +1,16 @@
+-----BEGIN CERTIFICATE-----
+MIICfDCCAeWgAwIBAgIJAKW4Tv+M90fKMA0GCSqGSIb3DQEBBQUAMFcxCzAJBgNV
+BAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQg
+Q29tcGFueSBMdGQxEzARBgNVBAMMClN1cGVyIFVzZXIwHhcNMTEwNTE2MjAyMjEy
+WhcNMTEwNjE1MjAyMjEyWjBXMQswCQYDVQQGEwJYWDEVMBMGA1UEBwwMRGVmYXVs
+dCBDaXR5MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBhbnkgTHRkMRMwEQYDVQQDDApT
+dXBlciBVc2VyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDp/8qCWAKyJLDM
+PC+V6pDyngE+h+K/ZWA6hjIvjuOktRFEl36YmfM03L0JN4rjNS7JQkH4e2YWP9Ir
+R5fKwlMwz1xqNaPSCFLwbUs52VUAPRT+LtQL5bpi8VdwvqAlGJg1SazWjfsXyPSm
+hYhgCZi9aGApkUStnNAtJ7rgnOFfywIDAQABo1AwTjAdBgNVHQ4EFgQUh/pgdfSK
+cnALftl4NALNBHZ84FQwHwYDVR0jBBgwFoAUh/pgdfSKcnALftl4NALNBHZ84FQw
+DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQASXLjNSj91lbrWpn8FpU1F
+2X4EJEXNArKR+d1F/+WJxFSFQbyuhc0cmjuo30Z6Wzova1s2Icnw+EDgYVfXGdIC
+OJuDrtN9uonNPGeVIq+WrbHQF06FNiBgxCdJcpiQG5J6K6jAFC6cJHfWPFvDBSz6
+MkOGZT7Q9qApCepdUct3OA==
+-----END CERTIFICATE-----
diff --git a/scripts/ssl_examples/admin.pfx b/scripts/ssl_examples/admin.pfx
new file mode 100644
index 0000000..ac91a21
Binary files /dev/null and b/scripts/ssl_examples/admin.pfx differ
diff --git a/scripts/ssl_examples/root.pem b/scripts/ssl_examples/root.pem
new file mode 100644
index 0000000..ee2928a
--- /dev/null
+++ b/scripts/ssl_examples/root.pem
@@ -0,0 +1,48 @@
+-----BEGIN CERTIFICATE-----
+MIICfDCCAeWgAwIBAgIJAIBSkSFer4KiMA0GCSqGSIb3DQEBBQUAMFcxCzAJBgNV
+BAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQg
+Q29tcGFueSBMdGQxEzARBgNVBAMMClRoZSBTZXJ2ZXIwHhcNMTEwNTE2MjAyMTU3
+WhcNMTEwNjE1MjAyMTU3WjBXMQswCQYDVQQGEwJYWDEVMBMGA1UEBwwMRGVmYXVs
+dCBDaXR5MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBhbnkgTHRkMRMwEQYDVQQDDApU
+aGUgU2VydmVyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDNGcrorj2eI6D
+FUOu6fgOFAloTSrbfzhCosquyTznCi3MiswOrAWy3Y1aiL2aLSRURnmsaIoVC5FS
+1bZoE63cFkTvv2rKoVmBWmWy497nb/h8DMrsfOT2Xn3y+u79Wt4RCkunRJEfnWsg
+wrW6ULyxVAVeHJEtiudtHU0htAcR4wIDAQABo1AwTjAdBgNVHQ4EFgQUa2eD7AFo
+sPsSX5nMBACmReQBTJIwHwYDVR0jBBgwFoAUa2eD7AFosPsSX5nMBACmReQBTJIw
+DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQBJM6ESfZzgb6qwuHB28niU
+Azvf3zSPO2ji5C/gcPZa/vknxiaydP9c3JmwzLN1Qx7c5dHxLD5n9in1vcnJKR2m
+THv9PkLYH0bIEehL+IBD8F4VfvL8ZAtHos1D+rnMeTtzsR4BqY0qzXjtu1/Q98qF
+nhBvjaJoutWy9+nwxvPHHA==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICfDCCAeWgAwIBAgIJAKW4Tv+M90fKMA0GCSqGSIb3DQEBBQUAMFcxCzAJBgNV
+BAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQg
+Q29tcGFueSBMdGQxEzARBgNVBAMMClN1cGVyIFVzZXIwHhcNMTEwNTE2MjAyMjEy
+WhcNMTEwNjE1MjAyMjEyWjBXMQswCQYDVQQGEwJYWDEVMBMGA1UEBwwMRGVmYXVs
+dCBDaXR5MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBhbnkgTHRkMRMwEQYDVQQDDApT
+dXBlciBVc2VyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDp/8qCWAKyJLDM
+PC+V6pDyngE+h+K/ZWA6hjIvjuOktRFEl36YmfM03L0JN4rjNS7JQkH4e2YWP9Ir
+R5fKwlMwz1xqNaPSCFLwbUs52VUAPRT+LtQL5bpi8VdwvqAlGJg1SazWjfsXyPSm
+hYhgCZi9aGApkUStnNAtJ7rgnOFfywIDAQABo1AwTjAdBgNVHQ4EFgQUh/pgdfSK
+cnALftl4NALNBHZ84FQwHwYDVR0jBBgwFoAUh/pgdfSKcnALftl4NALNBHZ84FQw
+DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQASXLjNSj91lbrWpn8FpU1F
+2X4EJEXNArKR+d1F/+WJxFSFQbyuhc0cmjuo30Z6Wzova1s2Icnw+EDgYVfXGdIC
+OJuDrtN9uonNPGeVIq+WrbHQF06FNiBgxCdJcpiQG5J6K6jAFC6cJHfWPFvDBSz6
+MkOGZT7Q9qApCepdUct3OA==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICeDCCAeGgAwIBAgIJAPVYsIHSJcudMA0GCSqGSIb3DQEBBQUAMFUxCzAJBgNV
+BAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQg
+Q29tcGFueSBMdGQxETAPBgNVBAMMCEEgVGVuYW50MB4XDTExMDUxNjIwMjIyM1oX
+DTExMDYxNTIwMjIyM1owVTELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQg
+Q2l0eTEcMBoGA1UECgwTRGVmYXVsdCBDb21wYW55IEx0ZDERMA8GA1UEAwwIQSBU
+ZW5hbnQwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMNWfsmPx0y2zdrNCl3w
+Puki+AYyYOzGPZRZeRBz5dFXxvJ0mdQMl1Mv4cbgjfaEnT+qT2vV17GFf7XWARd2
+oGLYfHZO2iINRDlyG5g/FDNkeUnVTm0ZBtsBo5hpX49C/MB1cHzmWAu5nopi8fhB
+nej+rAP1GIZkUkWyWIG1jbbRAgMBAAGjUDBOMB0GA1UdDgQWBBTzqu/xb6WUQ2mz
+bDwEGW9XfUEJ1DAfBgNVHSMEGDAWgBTzqu/xb6WUQ2mzbDwEGW9XfUEJ1DAMBgNV
+HRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAHvZ0Az8LLfKc72FXxS6llSrlB40
+Y/D4XTHcBMaiFDZClJvYJ+UmqrxYCJH5py/YRd9GSp5Ry+ghPWg/+eFsSGFtQFTI
+r+r70ux4lRYUk+adSqIiyg0xNGMqgOJKNNN5ixrCvN9k5BeeWyiKLbgcD+oG6sZM
+ImJ71ZY4QkH1MLb+
+-----END CERTIFICATE-----
diff --git a/scripts/ssl_examples/server.key b/scripts/ssl_examples/server.key
new file mode 100644
index 0000000..b188d77
--- /dev/null
+++ b/scripts/ssl_examples/server.key
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXwIBAAKBgQDDNGcrorj2eI6DFUOu6fgOFAloTSrbfzhCosquyTznCi3MiswO
+rAWy3Y1aiL2aLSRURnmsaIoVC5FS1bZoE63cFkTvv2rKoVmBWmWy497nb/h8DMrs
+fOT2Xn3y+u79Wt4RCkunRJEfnWsgwrW6ULyxVAVeHJEtiudtHU0htAcR4wIDAQAB
+AoGBAIxEuqIzcr/BT37IJ/OnspTDNyNY2CQT3eScQBKrDnVi7hgd4JXmbM3jwDA/
+NCd2qrVYUxRdpmOLBWlFoqZVBcdvms4AlaOy9f6tMIwVt1FMgYp5GgPWKv2kZddR
++zwPBuwhkR41aGFAypsS9b5ZAdn5GP6ZfTw3vDtxA7vwoqU5AkEA8NwasAlFJy1E
+ESlqG7GAe1YzjtkO+wu2YCCer5eXxE+G3b8s+QOnc6qaSnG6PABk5JptjUwFKOzo
+m7ZuFk3I5wJBAM95nvVYHZBXutNEOZIINsxvaEMfwbpeQqTwWrGJQY55NpeRIzvg
+lUGDiJNuOdRcWNA6gvSRAjHS1sH9FwSLI6UCQQDcVm4vlftmEHnRPVKtTN8ddUkr
+J5QVwqwvGggw1/vlgV7+IjKRBm+8V1hYO9vDohSqMD+B4AZkXv2X3PaufrAHAkEA
+rW4t+Uq6E9GyAy4xraeeHxA1qH6gU2i97uBX/7YLjcw3XUVenYvjWEtaXFs0jhbP
+yuhOVZ/tpLZo/OnSVuL0XQJBAKQgA9RpoaFqSb4vWStlO54mKmzRA/xXVhDSzYYb
+N0VC77hav1p527nfWAiMuSvuhOStcwGQ32sgWN5Ecgzrg/s=
+-----END RSA PRIVATE KEY-----
diff --git a/scripts/ssl_examples/server.pem b/scripts/ssl_examples/server.pem
new file mode 100644
index 0000000..2c08fe3
--- /dev/null
+++ b/scripts/ssl_examples/server.pem
@@ -0,0 +1,16 @@
+-----BEGIN CERTIFICATE-----
+MIICfDCCAeWgAwIBAgIJAIBSkSFer4KiMA0GCSqGSIb3DQEBBQUAMFcxCzAJBgNV
+BAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQg
+Q29tcGFueSBMdGQxEzARBgNVBAMMClRoZSBTZXJ2ZXIwHhcNMTEwNTE2MjAyMTU3
+WhcNMTEwNjE1MjAyMTU3WjBXMQswCQYDVQQGEwJYWDEVMBMGA1UEBwwMRGVmYXVs
+dCBDaXR5MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBhbnkgTHRkMRMwEQYDVQQDDApU
+aGUgU2VydmVyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDNGcrorj2eI6D
+FUOu6fgOFAloTSrbfzhCosquyTznCi3MiswOrAWy3Y1aiL2aLSRURnmsaIoVC5FS
+1bZoE63cFkTvv2rKoVmBWmWy497nb/h8DMrsfOT2Xn3y+u79Wt4RCkunRJEfnWsg
+wrW6ULyxVAVeHJEtiudtHU0htAcR4wIDAQABo1AwTjAdBgNVHQ4EFgQUa2eD7AFo
+sPsSX5nMBACmReQBTJIwHwYDVR0jBBgwFoAUa2eD7AFosPsSX5nMBACmReQBTJIw
+DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQBJM6ESfZzgb6qwuHB28niU
+Azvf3zSPO2ji5C/gcPZa/vknxiaydP9c3JmwzLN1Qx7c5dHxLD5n9in1vcnJKR2m
+THv9PkLYH0bIEehL+IBD8F4VfvL8ZAtHos1D+rnMeTtzsR4BqY0qzXjtu1/Q98qF
+nhBvjaJoutWy9+nwxvPHHA==
+-----END CERTIFICATE-----
diff --git a/scripts/ssl_examples/tenant.key b/scripts/ssl_examples/tenant.key
new file mode 100644
index 0000000..9124136
--- /dev/null
+++ b/scripts/ssl_examples/tenant.key
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDDVn7Jj8dMts3azQpd8D7pIvgGMmDsxj2UWXkQc+XRV8bydJnU
+DJdTL+HG4I32hJ0/qk9r1dexhX+11gEXdqBi2Hx2TtoiDUQ5chuYPxQzZHlJ1U5t
+GQbbAaOYaV+PQvzAdXB85lgLuZ6KYvH4QZ3o/qwD9RiGZFJFsliBtY220QIDAQAB
+AoGAChPLRLDDMmT358LONxxu0m44Z3Fv6KcthBq9kSi5gXxKyjVTvknMAMGmnzD0
+gfRDfIZXxJNqpkDh3sqkkcZP7dJ0VVGk6swoV24kvt5vtai6dqVD9klmSApDE3Ec
+KsAwJShRp9O2sBrtXMrtq1Q79SfmufJE0vcE73cfqQLijvUCQQDhof+ynTPWHPf6
+LxU6kl/MUaEG1MDQdS2WmUUrwjImWmBJjaPkpnpVX+eFenQt+nd1IpZQwuBWbTeP
+pVZ6z6x/AkEA3aC1Tk6da2NBR8Ef9zZ7DDhdVkQZXVkouGohQNMOyxg9wrqhelkR
+dWWm2ykNqYlxZYEcZc87m5G6xDH8KvM0rwJBAK7WWAuw0r0EH4dmun1zdPYe/rcL
+Xwlo81VyGO5qgW/Esj3smmYQNlU3hnCgzavfHHfQwEd+alWuNdKCXLu3dsMCQDQ7
+DJ+AzX6ibJ8Rd4wWTddqbSzIbcXfHkaf1GhnlSPt+ZgrzaR82y10oGcj/LFIz+2h
+COVBeoXGSWK1eP1SRccCQQC+16Mrw/C9ZWCu+1LUtCWTjoP/S82ooRTF6Ug3XIo6
+sROrv9p85+vf0AUF7s07soh4XrVFa6Cy4u8C/vxsmokT
+-----END RSA PRIVATE KEY-----
diff --git a/scripts/ssl_examples/tenant.pem b/scripts/ssl_examples/tenant.pem
new file mode 100644
index 0000000..593c56f
--- /dev/null
+++ b/scripts/ssl_examples/tenant.pem
@@ -0,0 +1,16 @@
+-----BEGIN CERTIFICATE-----
+MIICeDCCAeGgAwIBAgIJAPVYsIHSJcudMA0GCSqGSIb3DQEBBQUAMFUxCzAJBgNV
+BAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQg
+Q29tcGFueSBMdGQxETAPBgNVBAMMCEEgVGVuYW50MB4XDTExMDUxNjIwMjIyM1oX
+DTExMDYxNTIwMjIyM1owVTELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQg
+Q2l0eTEcMBoGA1UECgwTRGVmYXVsdCBDb21wYW55IEx0ZDERMA8GA1UEAwwIQSBU
+ZW5hbnQwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMNWfsmPx0y2zdrNCl3w
+Puki+AYyYOzGPZRZeRBz5dFXxvJ0mdQMl1Mv4cbgjfaEnT+qT2vV17GFf7XWARd2
+oGLYfHZO2iINRDlyG5g/FDNkeUnVTm0ZBtsBo5hpX49C/MB1cHzmWAu5nopi8fhB
+nej+rAP1GIZkUkWyWIG1jbbRAgMBAAGjUDBOMB0GA1UdDgQWBBTzqu/xb6WUQ2mz
+bDwEGW9XfUEJ1DAfBgNVHSMEGDAWgBTzqu/xb6WUQ2mzbDwEGW9XfUEJ1DAMBgNV
+HRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAHvZ0Az8LLfKc72FXxS6llSrlB40
+Y/D4XTHcBMaiFDZClJvYJ+UmqrxYCJH5py/YRd9GSp5Ry+ghPWg/+eFsSGFtQFTI
+r+r70ux4lRYUk+adSqIiyg0xNGMqgOJKNNN5ixrCvN9k5BeeWyiKLbgcD+oG6sZM
+ImJ71ZY4QkH1MLb+
+-----END CERTIFICATE-----