Split kind tests into component-owned probes
This commit is contained in:
parent
b3c0f6d58a
commit
691ad92e30
1 changed files with 4269 additions and 3108 deletions
108
test/RuntimeAuditProbe.hs
Normal file
108
test/RuntimeAuditProbe.hs
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
module Main (main) where
|
||||
|
||||
import Data.Aeson (Value (..), eitherDecodeStrict')
|
||||
import qualified Data.Aeson.Key as Key
|
||||
import qualified Data.Aeson.KeyMap as KeyMap
|
||||
import qualified Data.ByteString as BS
|
||||
import qualified Data.ByteString.Char8 as B8
|
||||
import Data.List (nub)
|
||||
import Data.Maybe (mapMaybe)
|
||||
import qualified Data.Text as Text
|
||||
import qualified Data.Text.Encoding as Text
|
||||
import Nuthatch.Probe.Assert
|
||||
import System.Environment (lookupEnv)
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
logBytes <- BS.readFile "/run/nuthatch-runtime/app.log"
|
||||
let auditLines =
|
||||
filter (BS.isPrefixOf "{") (B8.lines logBytes)
|
||||
records <- traverse decodeRecord auditLines
|
||||
assert "runtime audit log is empty" (not (null records))
|
||||
mapM_ verifyRequiredFields records
|
||||
mapM_ (requireAction records)
|
||||
["authentication", "login", "logout", "authorization", "put", "delete"]
|
||||
verifyMutationFields records
|
||||
verifyNoProhibitedData logBytes
|
||||
putStrLn "Runtime audit structure and redaction passed"
|
||||
|
||||
decodeRecord :: BS.ByteString -> IO (KeyMap.KeyMap Value)
|
||||
decodeRecord bytes =
|
||||
case eitherDecodeStrict' bytes of
|
||||
Right (Object object) -> pure object
|
||||
Right _ -> fail "runtime audit record is not an object"
|
||||
Left reason -> fail ("invalid runtime audit JSON: " <> reason)
|
||||
|
||||
verifyRequiredFields :: KeyMap.KeyMap Value -> IO ()
|
||||
verifyRequiredFields record =
|
||||
mapM_
|
||||
(\name ->
|
||||
assert ("runtime audit record omitted " <> name) $
|
||||
KeyMap.member (Key.fromString name) record)
|
||||
["ts", "action", "result"]
|
||||
|
||||
requireAction :: [KeyMap.KeyMap Value] -> String -> IO ()
|
||||
requireAction records expected =
|
||||
assert ("runtime audit omitted action " <> expected) $
|
||||
any ((== Just expected) . textField "action") records
|
||||
|
||||
verifyMutationFields :: [KeyMap.KeyMap Value] -> IO ()
|
||||
verifyMutationFields records = do
|
||||
let puts = filter ((== Just "put") . textField "action") records
|
||||
mutations =
|
||||
filter
|
||||
(\record ->
|
||||
textField "action" record `elem` [Just "put", Just "delete"])
|
||||
records
|
||||
failed =
|
||||
filter ((/= Just "ok") . textField "result") mutations
|
||||
assert "runtime audit omitted put records" (not (null puts))
|
||||
mapM_
|
||||
(\record ->
|
||||
mapM_
|
||||
(\name ->
|
||||
assert ("put audit omitted " <> name) $
|
||||
KeyMap.member (Key.fromString name) record)
|
||||
["user_id", "root", "bytes"])
|
||||
puts
|
||||
assert "runtime audit omitted failed mutations" (not (null failed))
|
||||
mapM_
|
||||
(\record ->
|
||||
assert "failed mutation audit omitted reason" $
|
||||
KeyMap.member "reason" record)
|
||||
failed
|
||||
|
||||
verifyNoProhibitedData :: BS.ByteString -> IO ()
|
||||
verifyNoProhibitedData logBytes = do
|
||||
let folded = Text.toCaseFold (Text.decodeUtf8 logBytes)
|
||||
mapM_
|
||||
(\value ->
|
||||
assert ("runtime log exposed prohibited value " <> value) $
|
||||
Text.toCaseFold (Text.pack value) `Text.isInfixOf` folded == False)
|
||||
[ "Bearer "
|
||||
, "\"authorization\":"
|
||||
, "client_secret"
|
||||
, "NUTHATCH_"
|
||||
, "private-a"
|
||||
, "runtime-coherence"
|
||||
, "first-personal"
|
||||
, "second-personal"
|
||||
]
|
||||
configured <- mapMaybe id <$> traverse lookupEnv
|
||||
[ "NUTHATCH_JWT_SECRET"
|
||||
, "NUTHATCH_PG_CONN"
|
||||
, "NUTHATCH_S3_ACCESS_KEY"
|
||||
, "NUTHATCH_S3_SECRET_KEY"
|
||||
, "NUTHATCH_OIDC_CLIENT_SECRET"
|
||||
]
|
||||
mapM_
|
||||
(\value ->
|
||||
assert "runtime log exposed a configured secret" $
|
||||
not (B8.pack value `BS.isInfixOf` logBytes))
|
||||
(filter (not . null) (nub configured))
|
||||
|
||||
textField :: String -> KeyMap.KeyMap Value -> Maybe String
|
||||
textField name record =
|
||||
case KeyMap.lookup (Key.fromString name) record of
|
||||
Just (String value) -> Just (Text.unpack value)
|
||||
_ -> Nothing
|
||||
Loading…
Reference in a new issue