diff --git a/htools/Ganeti/HTools/QC.hs b/htools/Ganeti/HTools/QC.hs index 15e591c8e8e1803db0a39729e0964a441bbc30ba..37736806eb8d22c2bed5d6352b3f5d9b5d95c9ca 100644 --- a/htools/Ganeti/HTools/QC.hs +++ b/htools/Ganeti/HTools/QC.hs @@ -50,6 +50,7 @@ module Ganeti.HTools.QC , testSsconf , testRpc , testQlang + , testConfd ) where import qualified Test.HUnit as HUnit @@ -2089,3 +2090,74 @@ prop_Qlang_Serialisation = testSuite "Qlang" [ 'prop_Qlang_Serialisation ] + + +-- * Confd tests (generic library) + +instance Arbitrary Confd.ConfdRequestType where + arbitrary = elements [minBound..maxBound] + +instance Arbitrary Confd.ConfdReqField where + arbitrary = elements [minBound..maxBound] + +instance Arbitrary Confd.ConfdReqQ where + arbitrary = Confd.ConfdReqQ <$> arbitrary <*> arbitrary <*> + arbitrary <*> arbitrary + +instance Arbitrary Confd.ConfdQuery where + arbitrary = oneof [ pure Confd.EmptyQuery + , Confd.PlainQuery <$> getName + , Confd.DictQuery <$> arbitrary + ] + +instance Arbitrary Confd.ConfdRequest where + arbitrary = Confd.ConfdRequest <$> arbitrary <*> arbitrary <*> arbitrary + <*> arbitrary + +-- | Test that signing messages and checking signatures is correct. It +-- also tests, indirectly the serialisation of messages so we don't +-- need a separate test for that. +prop_Confd_req_sign :: Hash.HashKey -- ^ The hash key + -> NonNegative Integer -- ^ The base timestamp + -> Positive Integer -- ^ Delta for out of window + -> Bool -- ^ Whether delta should be + or - + -> Confd.ConfdRequest + -> Property +prop_Confd_req_sign key (NonNegative timestamp) (Positive bad_delta) pm crq = + forAll (choose (0, fromIntegral C.confdMaxClockSkew)) $ \ good_delta -> + let encoded = J.encode crq + salt = show timestamp + signed = J.encode $ Confd.Utils.signMessage key salt encoded + good_timestamp = timestamp + if pm then good_delta else (-good_delta) + bad_delta' = fromIntegral C.confdMaxClockSkew + bad_delta + bad_timestamp = timestamp + if pm then bad_delta' else (-bad_delta') + ts_ok = Confd.Utils.parseMessage key signed good_timestamp + ts_bad = Confd.Utils.parseMessage key signed bad_timestamp + in printTestCase "Failed to parse good message" + (ts_ok ==? Types.Ok (encoded, crq)) .&&. + printTestCase ("Managed to deserialise message with bad\ + \ timestamp, got " ++ show ts_bad) + (ts_bad ==? Types.Bad "Too old/too new timestamp or clock skew") + +-- | Tests that signing with a different key fails detects failure +-- correctly. +prop_Confd_bad_key :: String -- ^ Salt + -> Confd.ConfdRequest -- ^ Request + -> Property +prop_Confd_bad_key salt crq = + -- fixme: we hardcode here the expected length of a sha1 key, as + -- otherwise we could have two short keys that differ only in the + -- final zero elements count, and those will be expanded to be the + -- same + forAll (vector 20) $ \key_sign -> + forAll (vector 20 `suchThat` (/= key_sign)) $ \key_verify -> + let signed = Confd.Utils.signMessage key_sign salt (J.encode crq) + encoded = J.encode signed + in printTestCase ("Accepted message signed with different key" ++ encoded) $ + Types.Bad "HMAC verification failed" ==? + Confd.Utils.parseRequest key_verify encoded + +testSuite "Confd" + [ 'prop_Confd_req_sign + , 'prop_Confd_bad_key + ] diff --git a/htools/test.hs b/htools/test.hs index ca2fc7e4b39e85110fbed9ed411aa2de233015e5..7f3da8b9f2b6f2653174e64c3b46c066690a1efc 100644 --- a/htools/test.hs +++ b/htools/test.hs @@ -70,6 +70,7 @@ allTests = , (True, testSsconf) , (True, testQlang) , (True, testRpc) + , (True, testConfd) , (False, testCluster) ]