Revision 62377cf5

b/Makefile.am
404 404
	htools/Ganeti/BasicTypes.hs \
405 405
	htools/Ganeti/Confd.hs \
406 406
	htools/Ganeti/Confd/Server.hs \
407
	htools/Ganeti/Confd/Utils.hs \
407 408
	htools/Ganeti/Config.hs \
408 409
	htools/Ganeti/Daemon.hs \
409 410
	htools/Ganeti/Hash.hs \
b/htools/Ganeti/Confd/Server.hs
32 32
import Control.Concurrent
33 33
import Control.Exception
34 34
import Control.Monad (forever, liftM, when)
35
import qualified Data.ByteString as B
36 35
import Data.IORef
37 36
import Data.List
38 37
import qualified Data.Map as M
......
51 50
import Ganeti.HTools.Utils
52 51
import Ganeti.Objects
53 52
import Ganeti.Confd
53
import Ganeti.Confd.Utils
54 54
import Ganeti.Config
55 55
import Ganeti.Hash
56 56
import Ganeti.Logging
......
131 131

  
132 132
-- * Confd base functionality
133 133

  
134
-- | Returns the HMAC key.
135
getClusterHmac :: IO HashKey
136
getClusterHmac = fmap B.unpack $ B.readFile C.confdHmacKey
137

  
138 134
-- | Computes the node role.
139 135
nodeRole :: ConfigData -> String -> Result ConfdNodeRole
140 136
nodeRole cfg name =
......
242 238
                 (a, b, c, d, e, f) <- minors]
243 239
  return (ReplyStatusOk, J.showJSON encoded)
244 240

  
245
-- | Parses a signed request.
246
parseRequest :: HashKey -> String -> Result (String, String, ConfdRequest)
247
parseRequest key str = do
248
  (SignedMessage hmac msg salt) <- fromJResult "parsing request" $ J.decode str
249
  req <- if verifyMac key (Just salt) msg hmac
250
           then fromJResult "parsing message" $ J.decode msg
251
           else Bad "HMAC verification failed"
252
  return (salt, msg, req)
253

  
254 241
-- | Creates a ConfdReply from a given answer.
255 242
serializeResponse :: Result StatusAnswer -> ConfdReply
256 243
serializeResponse r =
......
262 249
                  , confdReplyAnswer   = result
263 250
                  , confdReplySerial   = 0 }
264 251

  
265
-- | Signs a message with a given key and salt.
266
signMessage :: HashKey -> String -> String -> SignedMessage
267
signMessage key salt msg =
268
  SignedMessage { signedMsgMsg  = msg
269
                , signedMsgSalt = salt
270
                , signedMsgHmac = hmac
271
                }
272
    where hmac = computeMac key (Just salt) msg
273

  
274 252
-- * Configuration handling
275 253

  
276 254
-- ** Helper functions
......
483 461
    Bad err -> logInfo $ "Failed to parse incoming message: " ++ err
484 462
  return ()
485 463

  
486
-- | Mesage parsing. This can either result in a good, valid message,
487
-- or fail in the Result monad.
488
parseMessage :: HashKey -> String -> Integer
489
             -> Result (String, ConfdRequest)
490
parseMessage hmac msg curtime = do
491
  (salt, origmsg, request) <- parseRequest hmac msg
492
  ts <- tryRead "Parsing timestamp" salt::Result Integer
493
  if (abs (ts - curtime) > (fromIntegral C.confdMaxClockSkew))
494
    then fail "Too old/too new timestamp or clock skew"
495
    else return (origmsg, request)
496

  
497 464
-- | Inner helper function for a given client. This generates the
498 465
-- final encoded message (as a string), ready to be sent out to the
499 466
-- client.
b/htools/Ganeti/Confd/Utils.hs
1
{-| Implementation of the Ganeti confd utilities.
2

  
3
This holds a few utility functions that could be useful in both
4
clients and servers.
5

  
6
-}
7

  
8
{-
9

  
10
Copyright (C) 2011, 2012 Google Inc.
11

  
12
This program is free software; you can redistribute it and/or modify
13
it under the terms of the GNU General Public License as published by
14
the Free Software Foundation; either version 2 of the License, or
15
(at your option) any later version.
16

  
17
This program is distributed in the hope that it will be useful, but
18
WITHOUT ANY WARRANTY; without even the implied warranty of
19
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20
General Public License for more details.
21

  
22
You should have received a copy of the GNU General Public License
23
along with this program; if not, write to the Free Software
24
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
25
02110-1301, USA.
26

  
27
-}
28

  
29
module Ganeti.Confd.Utils
30
  ( getClusterHmac
31
  , parseRequest
32
  , parseMessage
33
  , signMessage
34
  ) where
35

  
36
import qualified Data.ByteString as B
37
import qualified Text.JSON as J
38

  
39
import Ganeti.BasicTypes
40
import Ganeti.Confd
41
import Ganeti.Hash
42
import qualified Ganeti.Constants as C
43
import Ganeti.HTools.JSON
44
import Ganeti.HTools.Utils
45

  
46
-- | Returns the HMAC key.
47
getClusterHmac :: IO HashKey
48
getClusterHmac = fmap B.unpack $ B.readFile C.confdHmacKey
49

  
50
-- | Parses a signed request.
51
parseRequest :: HashKey -> String -> Result (String, String, ConfdRequest)
52
parseRequest key str = do
53
  (SignedMessage hmac msg salt) <- fromJResult "parsing request" $ J.decode str
54
  req <- if verifyMac key (Just salt) msg hmac
55
           then fromJResult "parsing message" $ J.decode msg
56
           else Bad "HMAC verification failed"
57
  return (salt, msg, req)
58

  
59
-- | Mesage parsing. This can either result in a good, valid message,
60
-- or fail in the Result monad.
61
parseMessage :: HashKey -> String -> Integer
62
             -> Result (String, ConfdRequest)
63
parseMessage hmac msg curtime = do
64
  (salt, origmsg, request) <- parseRequest hmac msg
65
  ts <- tryRead "Parsing timestamp" salt::Result Integer
66
  if (abs (ts - curtime) > (fromIntegral C.confdMaxClockSkew))
67
    then fail "Too old/too new timestamp or clock skew"
68
    else return (origmsg, request)
69

  
70
-- | Signs a message with a given key and salt.
71
signMessage :: HashKey -> String -> String -> SignedMessage
72
signMessage key salt msg =
73
  SignedMessage { signedMsgMsg  = msg
74
                , signedMsgSalt = salt
75
                , signedMsgHmac = hmac
76
                }
77
    where hmac = computeMac key (Just salt) msg
b/htools/Ganeti/HTools/Luxi.hs
33 33
import qualified Text.JSON
34 34

  
35 35
import qualified Ganeti.Luxi as L
36
import qualified Ganeti.Qlang as Qlang
36 37
import Ganeti.HTools.Loader
37 38
import Ganeti.HTools.Types
38 39
import qualified Ganeti.HTools.Group as Group
39 40
import qualified Ganeti.HTools.Node as Node
40 41
import qualified Ganeti.HTools.Instance as Instance
41 42
import Ganeti.HTools.JSON
42
import Ganeti.Qlang as Qlang
43 43

  
44 44
{-# ANN module "HLint: ignore Eta reduce" #-}
45 45

  
......
78 78
fromJValWithStatus :: (Text.JSON.JSON a, Monad m) => (JSValue, JSValue) -> m a
79 79
fromJValWithStatus (st, v) = do
80 80
  st' <- fromJVal st
81
  L.checkRS st' v >>= fromJVal
81
  Qlang.checkRS st' v >>= fromJVal
82 82

  
83 83
-- | Annotate errors when converting values with owner/attribute for
84 84
-- better debugging.
b/htools/Ganeti/HTools/QC.hs
75 75
import System.Process (readProcessWithExitCode)
76 76

  
77 77
import qualified Ganeti.Confd as Confd
78
import qualified Ganeti.Confd.Server as Confd.Server
79
import qualified Ganeti.Confd.Utils as Confd.Utils
78 80
import qualified Ganeti.Config as Config
79 81
import qualified Ganeti.Daemon as Daemon
80 82
import qualified Ganeti.Hash as Hash
b/htools/Ganeti/Luxi.hs
27 27

  
28 28
module Ganeti.Luxi
29 29
  ( LuxiOp(..)
30
  , ResultStatus(..)
31 30
  , LuxiReq(..)
32 31
  , Client
33 32
  , JobId
34 33
  , RecvResult(..)
35 34
  , TagObject(..)
36 35
  , strOfOp
37
  , checkRS
38 36
  , getClient
39 37
  , getServer
40 38
  , acceptClient
......
183 181
-- | The serialisation of LuxiOps into strings in messages.
184 182
$(genStrOfOp ''LuxiOp "strOfOp")
185 183

  
186
$(declareIADT "ResultStatus"
187
  [ ("RSNormal", 'rsNormal)
188
  , ("RSUnknown", 'rsUnknown)
189
  , ("RSNoData", 'rsNodata)
190
  , ("RSUnavailable", 'rsUnavail)
191
  , ("RSOffline", 'rsOffline)
192
  ])
193

  
194
$(makeJSONInstance ''ResultStatus)
195

  
196 184
-- | Type holding the initial (unparsed) Luxi call.
197 185
data LuxiCall = LuxiCall LuxiReq JSValue
198 186

  
199
-- | Check that ResultStatus is success or fail with descriptive message.
200
checkRS :: (Monad m) => ResultStatus -> a -> m a
201
checkRS RSNormal val    = return val
202
checkRS RSUnknown _     = fail "Unknown field"
203
checkRS RSNoData _      = fail "No data for a field"
204
checkRS RSUnavailable _ = fail "Ganeti reports unavailable data"
205
checkRS RSOffline _     = fail "Ganeti reports resource as offline"
206

  
207 187
-- | The end-of-message separator.
208 188
eOM :: Word8
209 189
eOM = 3
b/htools/Ganeti/Qlang.hs
35 35
    , FieldDefinition(..)
36 36
    , ResultEntry(..)
37 37
    , ItemType(..)
38
    , checkRS
38 39
    ) where
39 40

  
40 41
import Control.Applicative
......
58 59
  ])
59 60
$(makeJSONInstance ''ResultStatus)
60 61

  
62
-- | Check that ResultStatus is success or fail with descriptive
63
-- message.
64
checkRS :: (Monad m) => ResultStatus -> a -> m a
65
checkRS RSNormal val = return val
66
checkRS RSUnknown  _ = fail "Unknown field"
67
checkRS RSNoData   _ = fail "No data for a field"
68
checkRS RSUnavail  _ = fail "Ganeti reports unavailable data"
69
checkRS RSOffline  _ = fail "Ganeti reports resource as offline"
70

  
61 71
-- | Type of a query field.
62 72
$(declareSADT "FieldType"
63 73
  [ ("QFTUnknown",   'C.qftUnknown )

Also available in: Unified diff