c46dfe66f7cedddc0044aab5bda6a01768e45373
[sfa.git] / sfa / server / sfa-server.py
1 #!/usr/bin/python
2 #
3 # SFA PLC Wrapper
4 #
5 # This wrapper implements the SFA Registry and Slice Interfaces on PLC.
6 # Depending on command line options, it starts some combination of a
7 # Registry, an Aggregate Manager, and a Slice Manager.
8 #
9 # There are several items that need to be done before starting the wrapper
10 # server.
11 #
12 # NOTE:  Many configuration settings, including the PLC maintenance account
13 # credentials, URI of the PLCAPI, and PLC DB URI and admin credentials are initialized
14 # from your MyPLC configuration (/etc/planetlab/plc_config*).  Please make sure this information
15 # is up to date and accurate.
16 #
17 # 1) Import the existing planetlab database, creating the
18 #    appropriate SFA records. This is done by running the "sfa-import-plc.py" tool.
19 #
20 # 2) Create a "trusted_roots" directory and place the certificate of the root
21 #    authority in that directory. Given the defaults in sfa-import-plc.py, this
22 #    certificate would be named "planetlab.gid". For example,
23 #
24 #    mkdir trusted_roots; cp authorities/planetlab.gid trusted_roots/
25 #
26 # TODO: Can all three servers use the same "registry" certificate?
27 ##
28
29 # TCP ports for the three servers
30 #registry_port=12345
31 #aggregate_port=12346
32 #slicemgr_port=12347
33 ### xxx todo not in the config yet
34 component_port=12346
35 import os, os.path
36 import traceback
37 import sys
38 import sfa.util.xmlrpcprotocol as xmlrpcprotocol
39 from optparse import OptionParser
40
41 from sfa.util.sfalogging import logger
42 from sfa.trust.certificate import Keypair, Certificate
43 from sfa.trust.hierarchy import Hierarchy
44 from sfa.trust.gid import GID
45 from sfa.util.config import Config
46 from sfa.plc.api import SfaAPI
47 from sfa.server.registry import Registries
48 from sfa.server.aggregate import Aggregates
49 from sfa.util.xrn import get_authority, hrn_to_urn
50 from sfa.util.sfalogging import logger
51
52 from sfa.managers.import_manager import import_manager
53
54 # after http://www.erlenstar.demon.co.uk/unix/faq_2.html
55 def daemon():
56     """Daemonize the current process."""
57     if os.fork() != 0: os._exit(0)
58     os.setsid()
59     if os.fork() != 0: os._exit(0)
60     os.umask(0)
61     devnull = os.open(os.devnull, os.O_RDWR)
62     os.dup2(devnull, 0)
63     # xxx fixme - this is just to make sure that nothing gets stupidly lost - should use devnull
64     crashlog = os.open('/var/log/httpd/sfa_access_log', os.O_RDWR | os.O_APPEND | os.O_CREAT, 0644)
65     os.dup2(crashlog, 1)
66     os.dup2(crashlog, 2)
67
68 def init_server_key(server_key_file, server_cert_file, config, hierarchy):
69
70     hrn = config.SFA_INTERFACE_HRN.lower()
71     # check if the server's private key exists. If it doesnt,
72     # get the right one from the authorities directory. If it cant be
73     # found in the authorities directory, generate a random one
74     if not os.path.exists(server_key_file):
75         hrn = config.SFA_INTERFACE_HRN.lower()
76         hrn_parts = hrn.split(".")
77         rel_key_path = hrn
78         pkey_filename = hrn+".pkey"
79
80         # sub authority's have "." in their hrn. This must
81         # be converted to os.path separator
82         if len(hrn_parts) > 0:
83             rel_key_path = hrn.replace(".", os.sep)
84             pkey_filename= hrn_parts[-1]+".pkey"
85
86         key_file = os.sep.join([hierarchy.basedir, rel_key_path, pkey_filename])
87         if not os.path.exists(key_file):
88             # if it doesnt exist then this is probably a fresh interface
89             # with no records. Generate a random keypair for now
90             logger.debug("server's public key not found in %s" % key_file)
91
92             logger.debug("generating a random server key pair")
93             key = Keypair(create=True)
94             key.save_to_file(server_key_file)
95             init_server_cert(hrn, key, server_cert_file, self_signed=True)    
96
97         else:
98             # the pkey was found in the authorites directory. lets 
99             # copy it to where the server key should be and generate
100             # the cert
101             key = Keypair(filename=key_file)
102             key.save_to_file(server_key_file)
103             init_server_cert(hrn, key, server_cert_file)    
104
105     # If private key exists and cert doesnt, recreate cert
106     if (os.path.exists(server_key_file)) and (not os.path.exists(server_cert_file)):
107         key = Keypair(filename=server_key_file)
108         init_server_cert(hrn, key, server_cert_file)    
109
110
111 def init_server_cert(hrn, key, server_cert_file, self_signed=False):
112     """
113     Setup the certificate for this server. Attempt to use gid before 
114     creating a self signed cert 
115     """
116     if self_signed:
117         init_self_signed_cert(hrn, key, server_cert_file)
118     else:
119         try:
120             # look for gid file
121             logger.debug("generating server cert from gid: %s"% hrn)
122             hierarchy = Hierarchy()
123             auth_info = hierarchy.get_auth_info(hrn)
124             gid = GID(filename=auth_info.gid_filename)
125             gid.save_to_file(filename=server_cert_file)
126         except:
127             # fall back to self signed cert
128             logger.debug("gid for %s not found" % hrn)
129             init_self_signed_cert(hrn, key, server_cert_file)        
130         
131 def init_self_signed_cert(hrn, key, server_cert_file):
132     logger.debug("generating self signed cert")
133     # generate self signed certificate
134     cert = Certificate(subject=hrn)
135     cert.set_issuer(key=key, subject=hrn)
136     cert.set_pubkey(key)
137     cert.sign()
138     cert.save_to_file(server_cert_file)
139
140 def init_server(options, config):
141     """
142     Locate the manager based on config.*TYPE
143     Execute the init_server method (well in fact function, sigh) if defined in that module
144     In order to migrate to a more generic approach:
145     * search for <>_manager_<type>.py
146     * if not found, try <>_manager.py (and issue a warning if <type>!='pl')
147     """
148     if options.registry:
149         manager=import_manager ("registry",       config.SFA_REGISTRY_TYPE)
150         if manager and hasattr(manager, 'init_server'): manager.init_server()
151     if options.am:      
152         manager=import_manager ("aggregate",      config.SFA_AGGREGATE_TYPE)
153         if manager and hasattr(manager, 'init_server'): manager.init_server()
154     if options.sm:
155         manager=import_manager ("slice",          config.SFA_SM_TYPE)
156         if manager and hasattr(manager, 'init_server'): manager.init_server()
157     if options.cm:
158         manager=import_manager ("component",      config.SFA_CM_TYPE)
159         if manager and hasattr(manager, 'init_server'): manager.init_server()
160
161
162 def install_peer_certs(server_key_file, server_cert_file):
163     """
164     Attempt to install missing trusted gids and db records for 
165     our federated interfaces
166     """
167     # Attempt to get any missing peer gids
168     # There should be a gid file in /etc/sfa/trusted_roots for every
169     # peer registry found in in the registries.xml config file. If there
170     # are any missing gids, request a new one from the peer registry.
171     api = SfaAPI(key_file = server_key_file, cert_file = server_cert_file)
172     registries = Registries()
173     aggregates = Aggregates()
174     interfaces = dict(registries.items() + aggregates.items())
175     gids_current = api.auth.trusted_cert_list
176     hrns_current = [gid.get_hrn() for gid in gids_current]
177     hrns_expected = set([hrn for hrn in interfaces])
178     new_hrns = set(hrns_expected).difference(hrns_current)
179     #gids = self.get_peer_gids(new_hrns) + gids_current
180     peer_gids = []
181     if not new_hrns:
182         return 
183
184     trusted_certs_dir = api.config.get_trustedroots_dir()
185     for new_hrn in new_hrns:
186         if not new_hrn: continue
187         # the gid for this interface should already be installed
188         if new_hrn == api.config.SFA_INTERFACE_HRN: continue
189         try:
190             # get gid from the registry
191             url = interfaces[new_hrn].get_url()
192             interface = interfaces[new_hrn].get_server(server_key_file, server_cert_file, timeout=30)
193             # skip non sfa aggregates
194             server_version = api.get_cached_server_version(interface)
195             if 'sfa' not in server_version:
196                 logger.info("get_trusted_certs: skipping non sfa aggregate: %s" % new_hrn)
197                 continue
198       
199             trusted_gids = interface.get_trusted_certs()
200             if trusted_gids:
201                 # the gid we want should be the first one in the list,
202                 # but lets make sure
203                 for trusted_gid in trusted_gids:
204                     # default message
205                     message = "interface: %s\t" % (api.interface)
206                     message += "unable to install trusted gid for %s" % \
207                                (new_hrn)
208                     gid = GID(string=trusted_gids[0])
209                     peer_gids.append(gid)
210                     if gid.get_hrn() == new_hrn:
211                         gid_filename = os.path.join(trusted_certs_dir, '%s.gid' % new_hrn)
212                         gid.save_to_file(gid_filename, save_parents=True)
213                         message = "installed trusted cert for %s" % new_hrn
214                     # log the message
215                     api.logger.info(message)
216         except:
217             message = "interface: %s\tunable to install trusted gid for %s" % \
218                         (api.interface, new_hrn)
219             api.logger.log_exc(message)
220     # doesnt matter witch one
221     update_cert_records(peer_gids)
222
223 def update_cert_records(gids):
224     """
225     Make sure there is a record in the registry for the specified gids. 
226     Removes old records from the db.
227     """
228     # import SfaTable here so this module can be loaded by ComponentAPI
229     from sfa.util.table import SfaTable
230     from sfa.util.record import SfaRecord
231     if not gids:
232         return
233     table = SfaTable()
234     # get records that actually exist in the db
235     gid_urns = [gid.get_urn() for gid in gids]
236     hrns_expected = [gid.get_hrn() for gid in gids]
237     records_found = table.find({'hrn': hrns_expected, 'pointer': -1}) 
238
239     # remove old records
240     for record in records_found:
241         if record['hrn'] not in hrns_expected and \
242             record['hrn'] != self.api.config.SFA_INTERFACE_HRN:
243             table.remove(record)
244
245     # TODO: store urn in the db so we do this in 1 query 
246     for gid in gids:
247         hrn, type = gid.get_hrn(), gid.get_type()
248         record = table.find({'hrn': hrn, 'type': type, 'pointer': -1})
249         if not record:
250             record = {
251                 'hrn': hrn, 'type': type, 'pointer': -1,
252                 'authority': get_authority(hrn),
253                 'gid': gid.save_to_string(save_parents=True),
254             }
255             record = SfaRecord(dict=record)
256             table.insert(record)
257         
258 def main():
259     # Generate command line parser
260     parser = OptionParser(usage="sfa-server [options]")
261     parser.add_option("-r", "--registry", dest="registry", action="store_true",
262          help="run registry server", default=False)
263     parser.add_option("-s", "--slicemgr", dest="sm", action="store_true",
264          help="run slice manager", default=False)
265     parser.add_option("-a", "--aggregate", dest="am", action="store_true",
266          help="run aggregate manager", default=False)
267     parser.add_option("-c", "--component", dest="cm", action="store_true",
268          help="run component server", default=False)
269     parser.add_option("-t", "--trusted-certs", dest="trusted_certs", action="store_true",
270          help="refresh trusted certs", default=False)
271     parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
272          help="verbose mode - cumulative")
273     parser.add_option("-d", "--daemon", dest="daemon", action="store_true",
274          help="Run as daemon.", default=False)
275     (options, args) = parser.parse_args()
276     
277     config = Config()
278     if config.SFA_API_DEBUG: pass
279     hierarchy = Hierarchy()
280     server_key_file = os.path.join(hierarchy.basedir, "server.key")
281     server_cert_file = os.path.join(hierarchy.basedir, "server.cert")
282
283     init_server_key(server_key_file, server_cert_file, config, hierarchy)
284     init_server(options, config)
285  
286     if (options.daemon):  daemon()
287     
288     if options.trusted_certs:
289         install_peer_certs(server_key_file, server_cert_file)   
290     
291     # start registry server
292     if (options.registry):
293         from sfa.server.registry import Registry
294         r = Registry("", config.SFA_REGISTRY_PORT, server_key_file, server_cert_file)
295         r.start()
296
297     if (options.am):
298         from sfa.server.aggregate import Aggregate
299         a = Aggregate("", config.SFA_AGGREGATE_PORT, server_key_file, server_cert_file)
300         a.start()
301
302     # start slice manager
303     if (options.sm):
304         from sfa.server.slicemgr import SliceMgr
305         s = SliceMgr("", config.SFA_SM_PORT, server_key_file, server_cert_file)
306         s.start()
307
308     if (options.cm):
309         from sfa.server.component import Component
310         c = Component("", config.component_port, server_key_file, server_cert_file)
311 #        c = Component("", config.SFA_COMPONENT_PORT, server_key_file, server_cert_file)
312         c.start()
313
314 if __name__ == "__main__":
315     try:
316         main()
317     except:
318         logger.log_exc_critical("SFA server is exiting")