Do not accept certificates that do not match our key.
[certmaster.git] / certmaster / utils.py
1 """
2 Copyright 2007-2008, Red Hat, Inc
3 see AUTHORS
4
5 This software may be freely redistributed under the terms of the GNU
6 general public license.
7
8 You should have received a copy of the GNU General Public License
9 along with this program; if not, write to the Free Software
10 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
11 """
12
13 import os
14 import string
15 import sys
16 import traceback
17 import xmlrpclib
18 import socket
19 import time
20 import glob
21
22 import codes
23 import certs
24 from config import read_config
25 from commonconfig import MinionConfig
26 import logger
27 import sub_process
28
29 # FIXME: module needs better pydoc
30
31
32 # FIXME: can remove this constant?
33 REMOTE_ERROR = "REMOTE_ERROR"
34
35 # The standard I/O file descriptors are redirected to /dev/null by default.
36 if (hasattr(os, "devnull")):
37 REDIRECT_TO = os.devnull
38 else:
39 REDIRECT_TO = "/dev/null"
40
41
42
43
44 def trace_me():
45 x = traceback.extract_stack()
46 bar = string.join(traceback.format_list(x))
47 return bar
48
49 def daemonize(pidfile=None):
50 """
51 Daemonize this process with the UNIX double-fork trick.
52 Writes the new PID to the provided file name if not None.
53 """
54
55 pid = os.fork()
56 if pid > 0:
57 sys.exit(0)
58 os.chdir("/")
59 os.setsid()
60 os.umask(0)
61 pid = os.fork()
62
63 os.close(0)
64 os.close(1)
65 os.close(2)
66
67 # based on http://code.activestate.com/recipes/278731/
68 os.open(REDIRECT_TO, os.O_RDWR) # standard input (0)
69
70 os.dup2(0, 1) # standard output (1)
71 os.dup2(0, 2) # standard error (2)
72
73
74
75 if pid > 0:
76 if pidfile is not None:
77 open(pidfile, "w").write(str(pid))
78 sys.exit(0)
79
80
81 def nice_exception(etype, evalue, etb):
82 etype = str(etype)
83 try:
84 lefti = etype.index("'") + 1
85 righti = etype.rindex("'")
86 nicetype = etype[lefti:righti]
87 except:
88 nicetype = etype
89 nicestack = string.join(traceback.format_list(traceback.extract_tb(etb)))
90 return [ REMOTE_ERROR, nicetype, str(evalue), nicestack ]
91
92 def is_error(result):
93 # FIXME: I believe we can remove this function
94 if type(result) != list:
95 return False
96 if len(result) == 0:
97 return False
98 if result[0] == REMOTE_ERROR:
99 return True
100 return False
101
102 def get_hostname(talk_to_certmaster=True):
103 """
104 "localhost" is a lame hostname to use for a key, so try to get
105 a more meaningful hostname. We do this by connecting to the certmaster
106 and seeing what interface/ip it uses to make that connection, and looking
107 up the hostname for that.
108 """
109 # FIXME: this code ignores http proxies (which granted, we don't
110 # support elsewhere either.
111 hostname = None
112 hostname = socket.gethostname()
113 # print "DEBUG: HOSTNAME TRY1: %s" % hostname
114 try:
115 ip = socket.gethostbyname(hostname)
116 except:
117 return hostname
118 if ip != "127.0.0.1":
119 return hostname
120
121
122 # FIXME: move to requestor module and also create a verbose mode
123 # prints to the screen for usage by /usr/bin/certmaster-request
124
125 def create_minion_keys():
126 # FIXME: paths should not be hard coded here, move to settings universally
127 config_file = '/etc/certmaster/minion.conf'
128 config = read_config(config_file, MinionConfig)
129 cert_dir = config.cert_dir
130 master_uri = 'http://%s:%s/' % (config.certmaster, config.certmaster_port)
131 # print "DEBUG: acquiring hostname"
132 hn = get_hostname()
133 # print "DEBUG: hostname = %s\n" % hn
134
135 if hn is None:
136 raise codes.CMException("Could not determine a hostname other than localhost")
137
138 key_file = '%s/%s.pem' % (cert_dir, hn)
139 csr_file = '%s/%s.csr' % (cert_dir, hn)
140 cert_file = '%s/%s.cert' % (cert_dir, hn)
141 ca_cert_file = '%s/ca.cert' % cert_dir
142
143
144 if os.path.exists(cert_file) and os.path.exists(ca_cert_file):
145 # print "DEBUG: err, no cert_file"
146 return
147
148 keypair = None
149 try:
150 if not os.path.exists(cert_dir):
151 os.makedirs(cert_dir)
152 if not os.path.exists(key_file):
153 keypair = certs.make_keypair(dest=key_file)
154 if not os.path.exists(csr_file):
155 if not keypair:
156 keypair = certs.retrieve_key_from_file(key_file)
157 csr = certs.make_csr(keypair, dest=csr_file)
158 except Exception, e:
159 traceback.print_exc()
160 raise codes.CMException, "Could not create local keypair or csr for session"
161
162 result = False
163 log = logger.Logger().logger
164 while not result:
165 try:
166 # print "DEBUG: submitting CSR to certmaster: %s" % master_uri
167 log.debug("submitting CSR to certmaster %s" % master_uri)
168 result, cert_string, ca_cert_string = submit_csr_to_master(csr_file, master_uri)
169 except socket.gaierror, e:
170 raise codes.CMException, "Could not locate certmaster at %s" % master_uri
171
172 # logging here would be nice
173 if not result:
174 # print "DEBUG: no response from certmaster, sleeping 10 seconds"
175 log.warning("no response from certmaster %s, sleeping 10 seconds" % master_uri)
176 time.sleep(10)
177
178
179 if result:
180 # print "DEBUG: recieved certificate from certmaster"
181 log.debug("received certificate from certmaster %s, storing to %s" % (master_uri, cert_file))
182 if not keypair:
183 keypair = certs.retrieve_key_from_file(key_file)
184 valid = certs.check_cert_key_match(cert_string, keypair)
185 if not valid:
186 log.info("certificate does not match key (run certmaster-ca --clean first?)")
187 sys.stderr.write("certificate does not match key (run certmaster-ca --clean first?)\n")
188 return
189 cert_fd = os.open(cert_file, os.O_RDWR|os.O_CREAT, 0644)
190 os.write(cert_fd, cert_string)
191 os.close(cert_fd)
192
193 ca_cert_fd = os.open(ca_cert_file, os.O_RDWR|os.O_CREAT, 0644)
194 os.write(ca_cert_fd, ca_cert_string)
195 os.close(ca_cert_fd)
196
197 def run_triggers(ref, globber):
198 """
199 Runs all the trigger scripts in a given directory.
200 ref can be a certmaster object, if not None, the name will be passed
201 to the script. If ref is None, the script will be called with
202 no argumenets. Globber is a wildcard expression indicating which
203 triggers to run. Example: "/var/lib/certmaster/triggers/blah/*"
204 """
205
206 log = logger.Logger().logger
207 triggers = glob.glob(globber)
208 triggers.sort()
209 for file in triggers:
210 log.debug("Executing trigger: %s" % file)
211 try:
212 if file.find(".rpm") != -1:
213 # skip .rpmnew files that may have been installed
214 # in the triggers directory
215 continue
216 if ref:
217 rc = sub_process.call([file, ref], shell=False)
218 else:
219 rc = sub_process.call([file], shell=False)
220 except:
221 log.warning("Warning: failed to execute trigger: %s" % file)
222 continue
223
224 if rc != 0:
225 raise codes.CMException, "certmaster trigger failed: %(file)s returns %(code)d" % { "file" : file, "code" : rc }
226
227
228 def submit_csr_to_master(csr_file, master_uri):
229 """"
230 gets us our cert back from the certmaster.wait_for_cert() method
231 takes csr_file as path location and master_uri
232 returns Bool, str(cert), str(ca_cert)
233 """
234
235 fo = open(csr_file)
236 csr = fo.read()
237 s = xmlrpclib.ServerProxy(master_uri)
238
239 # print "DEBUG: waiting for cert"
240 return s.wait_for_cert(csr)
241