sshd-lint (empty) → 0.1.0.1
raw patch · 7 files changed
+387/−0 lines, 7 filesdep +basedep +containersdep +hspecsetup-changed
Dependencies added: base, containers, hspec, keyword-args, nagios-check, parsec
Files
- LICENSE +20/−0
- Setup.hs +2/−0
- spec/Spec.hs +1/−0
- spec/fixtures/sshd_config +88/−0
- src/Main.hs +135/−0
- src/System/SshdLint/Check.hs +89/−0
- sshd-lint.cabal +52/−0
+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2015 Justin Leitgeb++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be included+in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ spec/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
+ spec/fixtures/sshd_config view
@@ -0,0 +1,88 @@+# Package generated configuration file+# See the sshd_config(5) manpage for details++# What ports, IPs and protocols we listen for+Port 22+# Use these options to restrict which interfaces/protocols sshd will bind to+#ListenAddress ::+#ListenAddress 0.0.0.0+Protocol 2+# HostKeys for protocol version 2+HostKey /etc/ssh/ssh_host_rsa_key+HostKey /etc/ssh/ssh_host_dsa_key+HostKey /etc/ssh/ssh_host_ecdsa_key+#Privilege Separation is turned on for security+UsePrivilegeSeparation yes++# Lifetime and size of ephemeral version 1 server key+KeyRegenerationInterval 3600+ServerKeyBits 768++# Logging+SyslogFacility AUTH+LogLevel INFO++# Authentication:+LoginGraceTime 120+PermitRootLogin yes+PermitRootLogin no+StrictModes yes++RSAAuthentication yes+PubkeyAuthentication yes+#AuthorizedKeysFile %h/.ssh/authorized_keys++# Don't read the user's ~/.rhosts and ~/.shosts files+IgnoreRhosts yes+# For this to work you will also need host keys in /etc/ssh_known_hosts+RhostsRSAAuthentication no+# similar for protocol version 2+HostbasedAuthentication no+# Uncomment if you don't trust ~/.ssh/known_hosts for RhostsRSAAuthentication+#IgnoreUserKnownHosts yes++# To enable empty passwords, change to yes (NOT RECOMMENDED)+PermitEmptyPasswords no++# Change to yes to enable challenge-response passwords (beware issues with+# some PAM modules and threads)+ChallengeResponseAuthentication no++# Change to no to disable tunnelled clear text passwords+PasswordAuthentication no++# Kerberos options+#KerberosAuthentication no+#KerberosGetAFSToken no+#KerberosOrLocalPasswd yes+#KerberosTicketCleanup yes++# GSSAPI options+#GSSAPIAuthentication no+#GSSAPICleanupCredentials yes++X11Forwarding yes+X11DisplayOffset 10+PrintMotd no+PrintLastLog yes+TCPKeepAlive yes+#UseLogin no++#MaxStartups 10:30:60+#Banner /etc/issue.net++# Allow client to pass locale environment variables+AcceptEnv LANG LC_*++Subsystem sftp /usr/lib/openssh/sftp-server++# Set this to 'yes' to enable PAM authentication, account processing,+# and session processing. If this is enabled, PAM authentication will+# be allowed through the ChallengeResponseAuthentication and+# PasswordAuthentication. Depending on your PAM configuration,+# PAM authentication via ChallengeResponseAuthentication may bypass+# the setting of "PermitRootLogin without-password".+# If you just want the PAM account and session checks to run without+# PAM authentication, then enable this but set PasswordAuthentication+# and ChallengeResponseAuthentication to 'no'.+UsePAM yes
+ src/Main.hs view
@@ -0,0 +1,135 @@+{-# LANGUAGE OverloadedStrings #-}++module Main where++import System.Console.GetOpt++import System.Environment (getArgs, getProgName)+import System.Exit (ExitCode(..), exitWith, exitSuccess)++import Paths_sshd_lint (version)++import Data.Version (showVersion)++import Data.KeywordArgs.Parse+import System.SshdLint.Check++import qualified Data.Map as Map+import qualified Data.Set as Set++import Control.Monad (unless, when)+import System.Nagios.Plugin++import Text.Parsec (ParseError, parse)++data Options = Options+ { optShowVersion :: Bool+ , optShowHelp :: Bool+ , optNagios :: Bool+ , optFile :: FilePath+ } deriving Show++defaultOptions :: Options+defaultOptions = Options+ { optShowVersion = False+ , optShowHelp = False+ , optNagios = False+ , optFile = "/etc/ssh/sshd_config"+ }++options :: [OptDescr (Options -> Options)]+options =+ [ Option "V" ["version"] (NoArg (\opts -> opts { optShowVersion = True }))+ "show version number"++ , Option "f" ["file"]+ (ReqArg (\f opts -> opts { optFile = f })+ "FILE") "input FILE"++ , Option "h" ["help"] (NoArg (\opts -> opts { optShowHelp = True }))+ "show help"++ , Option "n" ["nagios"] (NoArg (\opts -> opts { optNagios = True }))+ "display output in Nagios-consumable format"+ ]++showHelp :: IO ()+showHelp = do+ prg <- getProgName+ putStrLn (usageInfo prg options)+ exitSuccess++headerMessage :: String -> String+headerMessage progName = "Usage: " ++ progName ++ " [OPTION...]"++lintOpts :: [String] -> IO (Options, [String])+lintOpts argv = do+ name <- getProgName++ case getOpt Permute options argv of+ (o, n, [] ) -> return (foldl (flip id) defaultOptions o, n)+ (_, _, errs) ->+ ioError (userError (concat errs ++ usageInfo (headerMessage name) options))++parseConfig :: String -> Either ParseError [(String, [String])]+parseConfig = parse configParser "(unknown)"++printResult :: String -> [String] -> IO ()+printResult msg errs =+ unless (null errs) $+ do+ name <- getProgName+ putStrLn $ name ++ " encountered the following " ++ msg ++ ":\n"+ mapM_ (\err -> putStrLn $ "\t* " ++ err) errs+ putStr "\n"++runCheck :: Options -> IO ()+runCheck opts = do++ f <- readFile $ optFile opts++ unless (optNagios opts) $ putStrLn ("Checking " ++ optFile opts ++ ":\n")++ case parseConfig f of+ Left e -> putStrLn $ "Parse error: " ++ show e+ Right config -> do+ let dupVals = Set.toList $ duplicatedValues $ map fst config+ recs = recommendations (Map.fromList defaultAcceptedValues) $+ activeSettings config++ unless (optNagios opts) $ do+ printResult "insecure settings" recs+ printResult "duplicate values" dupVals++ if (not . null) dupVals || (not . null) recs+ then if optNagios opts+ then+ runNagiosPlugin (sshdSafeCheck $ length $ recs ++ dupVals)++ else do+ putStrLn $ show (length (recs ++ dupVals)) ++ " suggestions"+ exitWith (ExitFailure 1)++ else+ putStrLn "No suggestions"++sshdSafeCheck :: Int -> NagiosPlugin ()+sshdSafeCheck errorCount =+ when (errorCount > 0) $+ addResult Critical "Security warnings found in sshd_config"++main :: IO ()+main = do+ opts <- getArgs >>= lintOpts++ name <- getProgName++ if optShowHelp $ fst opts+ then+ putStrLn $ usageInfo (headerMessage name) options+ else+ if optShowVersion $ fst opts then+ putStrLn $ name ++ " " ++ showVersion version +++ " (C) 2015 Stack Builders Inc."+ else+ runCheck $ fst opts
+ src/System/SshdLint/Check.hs view
@@ -0,0 +1,89 @@+module System.SshdLint.Check+ ( duplicatedValues+ , activeSettings+ , recommendations+ , defaultAcceptedValues+ , checkSafeSetting ) where++import Data.Char (toLower)++import qualified Data.Map as Map+import qualified Data.Set as Set++type RecommendedSettings = Map.Map String [String]+type ActiveSettings = Map.Map String [String]+++defaultAcceptedValues :: [(String, [String])]+defaultAcceptedValues =+ [ ("PermitEmptyPasswords", ["no"])+ , ("PasswordAuthentication", ["no"])+ , ("HostbasedAuthentication", ["no"])+ , ("PermitRootLogin", ["no"])+ , ("IgnoreRhosts", ["yes"])+ , ("Protocol", ["2"])+ , ("StrictModes", ["yes"])+ , ("UsePrivilegeSeparation", ["yes"]) ]++duplicatedValues :: [String] -> Set.Set String+duplicatedValues values =+ snd $ foldr checkDuplicate (Set.empty, Set.empty) values++activeSettings :: [(String, [String])] -> ActiveSettings+activeSettings =+ foldr registerSetting Map.empty++ where registerSetting (configOption, value) =+ Map.insert (map toLower configOption) value++-- | Given a collection of recommendation strings and a current+-- setting, returns a new list of recommendations with new+-- recommendations added depending on the setting of the given+-- configOption and value.+checkSafeSetting :: Map.Map String [String]+ -> String+ -> [String]+ -> [String]+ -> [String]+checkSafeSetting recs configOption values previousRecommendations =+ let recommendation = Map.lookup configOption recs in++ case recommendation of+ Nothing -> previousRecommendations+ Just r -> if Set.fromList r == Set.fromList values then+ previousRecommendations+ else+ previousRecommendations ++ [ configOption ++ " should be "+ ++ show r ++ ", found " +++ show values ]+++recommendations :: RecommendedSettings -> ActiveSettings -> [String]+recommendations recommendedSettings settings =+ Map.foldWithKey (checkSafeSetting normalizedRecommendedSettings) []+ normalizedSettings++ where normalizedRecommendedSettings = lowerCaseMapKeys recommendedSettings+ normalizedSettings = lowerCaseMapKeys settings+++-- | Some values legitimately appear multiple times in the configuration.+allowedDuplicates :: Set.Set String+allowedDuplicates = Set.fromList ["hostkey"]++checkDuplicate :: String+ -> (Set.Set String, Set.Set String)+ -> (Set.Set String, Set.Set String)++checkDuplicate aValue (allValues, dupes) =+ if Set.notMember loweredValue allowedDuplicates &&+ Set.member loweredValue allValues then++ (allValues, Set.insert loweredValue dupes)+ else+ (Set.insert loweredValue allValues, dupes)++ where loweredValue = map toLower aValue++lowerCaseMapKeys :: Map.Map String [String] -> Map.Map String [String]+lowerCaseMapKeys = Map.mapKeys (map toLower)
+ sshd-lint.cabal view
@@ -0,0 +1,52 @@+name: sshd-lint+version: 0.1.0.1+synopsis: Check sshd configuration for adherence to best practices++description: If not configured correctly, it may be easy for attackers+ to gain access to a system. sshd-lint checks the sshd_config file+ for adherence to best practices.++license: MIT+license-file: LICENSE+author: Justin Leitgeb+maintainer: justin@stackbuilders.com+copyright: 2015 Stack Builders Inc.+category: System+build-type: Simple+extra-source-files: spec/fixtures/sshd_config+cabal-version: >=1.10++executable sshd-lint+ main-is: Main.hs+ other-modules: System.SshdLint.Check++ build-depends: base >=4.5 && <4.8+ , parsec >= 3.1.0 && <= 3.2+ , keyword-args >= 0.2.0.1 && < 0.3.0.0+ , containers+ , nagios-check++ hs-source-dirs: src+ default-language: Haskell2010+ ghc-options: -Wall++++test-suite sshd-lint-test+ type: exitcode-stdio-1.0+ hs-source-dirs: spec, src+ main-is: Spec.hs+ build-depends: base >=4.5 && <4.8+ , parsec >= 3.1.0 && <= 3.2+ , keyword-args >= 0.2.0.0 && < 0.3.0.0+ , containers+ , nagios-check++ , hspec++ default-language: Haskell2010+ ghc-options: -Wall++source-repository head+ type: git+ location: https://github.com/stackbuilders/sshd-lint