Stricter check for OS modifications passed to OpClusterSetParams
[ganeti-local] / lib / confd / server.py
1 #
2 #
3
4 # Copyright (C) 2009 Google Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
20
21
22 """Ganeti configuration daemon server library.
23
24 Ganeti-confd is a daemon to query master candidates for configuration values.
25 It uses UDP+HMAC for authentication with a global cluster key.
26
27 """
28
29 import logging
30 import time
31
32 from ganeti import constants
33 from ganeti import objects
34 from ganeti import errors
35 from ganeti import utils
36 from ganeti import serializer
37 from ganeti import ssconf
38
39 from ganeti.confd import querylib
40
41
42 class ConfdProcessor(object):
43   """A processor for confd requests.
44
45   @ivar reader: confd SimpleConfigReader
46   @ivar disabled: whether confd serving is disabled
47
48   """
49   DISPATCH_TABLE = {
50     constants.CONFD_REQ_PING: querylib.PingQuery,
51     constants.CONFD_REQ_NODE_ROLE_BYNAME: querylib.NodeRoleQuery,
52     constants.CONFD_REQ_NODE_PIP_BY_INSTANCE_IP:
53       querylib.InstanceIpToNodePrimaryIpQuery,
54     constants.CONFD_REQ_CLUSTER_MASTER: querylib.ClusterMasterQuery,
55     constants.CONFD_REQ_NODE_PIP_LIST: querylib.NodesPipsQuery,
56     constants.CONFD_REQ_MC_PIP_LIST: querylib.MasterCandidatesPipsQuery,
57     constants.CONFD_REQ_INSTANCES_IPS_LIST: querylib.InstancesIpsQuery,
58     }
59
60   def __init__(self):
61     """Constructor for ConfdProcessor
62
63     """
64     self.disabled = True
65     self.hmac_key = utils.ReadFile(constants.CONFD_HMAC_KEY)
66     self.reader = None
67     assert \
68       not constants.CONFD_REQS.symmetric_difference(self.DISPATCH_TABLE), \
69       "DISPATCH_TABLE is unaligned with CONFD_REQS"
70
71   def Enable(self):
72     try:
73       self.reader = ssconf.SimpleConfigReader()
74       self.disabled = False
75     except errors.ConfigurationError:
76       self.disabled = True
77       raise
78
79   def Disable(self):
80     self.disabled = True
81     self.reader = None
82
83   def ExecQuery(self, payload_in, ip, port):
84     """Process a single UDP request from a client.
85
86     @type payload_in: string
87     @param payload_in: request raw data
88     @type ip: string
89     @param ip: source ip address
90     @param port: integer
91     @type port: source port
92
93     """
94     if self.disabled:
95       logging.debug('Confd is disabled. Ignoring query.')
96       return
97     try:
98       request = self.ExtractRequest(payload_in)
99       reply, rsalt = self.ProcessRequest(request)
100       payload_out = self.PackReply(reply, rsalt)
101       return payload_out
102     except errors.ConfdRequestError, err:
103       logging.info('Ignoring broken query from %s:%d: %s', ip, port, err)
104       return None
105
106   def ExtractRequest(self, payload):
107     """Extracts a ConfdRequest object from a serialized hmac signed string.
108
109     This functions also performs signature/timestamp validation.
110
111     """
112     current_time = time.time()
113     logging.debug("Extracting request with size: %d", len(payload))
114     try:
115       (message, salt) = serializer.LoadSigned(payload, self.hmac_key)
116     except errors.SignatureError, err:
117       msg = "invalid signature: %s" % err
118       raise errors.ConfdRequestError(msg)
119     try:
120       message_timestamp = int(salt)
121     except (ValueError, TypeError):
122       msg = "non-integer timestamp: %s" % salt
123       raise errors.ConfdRequestError(msg)
124
125     skew = abs(current_time - message_timestamp)
126     if skew > constants.CONFD_MAX_CLOCK_SKEW:
127       msg = "outside time range (skew: %d)" % skew
128       raise errors.ConfdRequestError(msg)
129
130     try:
131       request = objects.ConfdRequest.FromDict(message)
132     except AttributeError, err:
133       raise errors.ConfdRequestError('%s' % err)
134
135     return request
136
137   def ProcessRequest(self, request):
138     """Process one ConfdRequest request, and produce an answer
139
140     @type request: L{objects.ConfdRequest}
141     @rtype: (L{objects.ConfdReply}, string)
142     @return: tuple of reply and salt to add to the signature
143
144     """
145     logging.debug("Processing request: %s", request)
146     if request.protocol != constants.CONFD_PROTOCOL_VERSION:
147       msg = "wrong protocol version %d" % request.protocol
148       raise errors.ConfdRequestError(msg)
149
150     if request.type not in constants.CONFD_REQS:
151       msg = "wrong request type %d" % request.type
152       raise errors.ConfdRequestError(msg)
153
154     rsalt = request.rsalt
155     if not rsalt:
156       msg = "missing requested salt"
157       raise errors.ConfdRequestError(msg)
158
159     query_object = self.DISPATCH_TABLE[request.type](self.reader)
160     status, answer = query_object.Exec(request.query)
161     reply = objects.ConfdReply(
162               protocol=constants.CONFD_PROTOCOL_VERSION,
163               status=status,
164               answer=answer,
165               serial=self.reader.GetConfigSerialNo(),
166               )
167
168     logging.debug("Sending reply: %s", reply)
169
170     return (reply, rsalt)
171
172   def PackReply(self, reply, rsalt):
173     """Serialize and sign the given reply, with salt rsalt
174
175     @type reply: L{objects.ConfdReply}
176     @type rsalt: string
177
178     """
179     return serializer.DumpSigned(reply.ToDict(), self.hmac_key, rsalt)