{-| Implementation of the Ganeti confd utilities. This holds a few utility functions that could be useful in both clients and servers. -} {- Copyright (C) 2011, 2012 Google Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -} module Ganeti.Confd.Utils ( getClusterHmac , parseSignedMessage , parseRequest , parseReply , signMessage , getCurrentTime ) where import qualified Data.ByteString as B import qualified Text.JSON as J import Ganeti.BasicTypes import Ganeti.Confd.Types import Ganeti.Hash import qualified Ganeti.Constants as C import qualified Ganeti.Path as Path import Ganeti.JSON import Ganeti.Utils -- | Type-adjusted max clock skew constant. maxClockSkew :: Integer maxClockSkew = fromIntegral C.confdMaxClockSkew -- | Returns the HMAC key. getClusterHmac :: IO HashKey getClusterHmac = Path.confdHmacKey >>= fmap B.unpack . B.readFile -- | Parses a signed message. parseSignedMessage :: (J.JSON a) => HashKey -> String -> Result (String, String, a) parseSignedMessage key str = do (SignedMessage hmac msg salt) <- fromJResult "parsing signed message" $ J.decode str parsedMsg <- if verifyMac key (Just salt) msg hmac then fromJResult "parsing message" $ J.decode msg else Bad "HMAC verification failed" return (salt, msg, parsedMsg) -- | Message parsing. This can either result in a good, valid request -- message, or fail in the Result monad. parseRequest :: HashKey -> String -> Integer -> Result (String, ConfdRequest) parseRequest hmac msg curtime = do (salt, origmsg, request) <- parseSignedMessage hmac msg ts <- tryRead "Parsing timestamp" salt::Result Integer if abs (ts - curtime) > maxClockSkew then fail "Too old/too new timestamp or clock skew" else return (origmsg, request) -- | Message parsing. This can either result in a good, valid reply -- message, or fail in the Result monad. -- It also checks that the salt in the message corresponds to the one -- that is expected parseReply :: HashKey -> String -> String -> Result (String, ConfdReply) parseReply hmac msg expSalt = do (salt, origmsg, reply) <- parseSignedMessage hmac msg if salt /= expSalt then fail "The received salt differs from the expected salt" else return (origmsg, reply) -- | Signs a message with a given key and salt. signMessage :: HashKey -> String -> String -> SignedMessage signMessage key salt msg = SignedMessage { signedMsgMsg = msg , signedMsgSalt = salt , signedMsgHmac = hmac } where hmac = computeMac key (Just salt) msg