From 72de4dd5717e954c2460338d1c6e89860e70c6e6 Mon Sep 17 00:00:00 2001 From: Nicolas Schodet Date: Mon, 29 Jun 2020 00:24:52 +0200 Subject: Add Icinga 2 support --- src/Propellor/Property/Icinga2.hs | 191 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 src/Propellor/Property/Icinga2.hs (limited to 'src/Propellor/Property/Icinga2.hs') diff --git a/src/Propellor/Property/Icinga2.hs b/src/Propellor/Property/Icinga2.hs new file mode 100644 index 00000000..8f6f5eeb --- /dev/null +++ b/src/Propellor/Property/Icinga2.hs @@ -0,0 +1,191 @@ +-- | Maintainer: Nicolas Schodet +-- +-- Support for Icinga2 monitoring system. + +module Propellor.Property.Icinga2 ( + ApiPermissions(..), + allApiPermissions, + webApiPermissions, + installed, + restarted, + reloaded, + idoMysqlConfigured, + apiConfigured, + apiUserGranted, + webInstalled, + confAdjustSection, + confContainsSetting, + confFileContains, + confHosts, + confServices, + confQuote, +) where + +import Propellor.Base +import Data.List +import qualified Propellor.Property.Apt as Apt +import qualified Propellor.Property.ConfFile as ConfFile +import qualified Propellor.Property.File as File +import qualified Propellor.Property.Service as Service + +-- | API User permissions. +newtype ApiPermissions = ApiPermissions [String] + +-- | Allow everything. +allApiPermissions :: ApiPermissions +allApiPermissions = ApiPermissions ["*"] + +-- | Needed permissions for Icinga Web 2. +webApiPermissions :: ApiPermissions +webApiPermissions = ApiPermissions + [ "status/query" + , "actions/*" + , "objects/modify/*" + , "objects/query/*" ] + +-- | A section in configuration, need to be the full section description. +type ConfSection = String + +-- | A key in configuration. +type ConfKey = String + +-- | A configuration statement, can be multiline. +type ConfStatement = [String] + +-- | Make sure Icinga 2 is installed. +installed :: Property DebianLike +installed = Apt.installed ["icinga2"] + +-- | Restart Icinga 2. +restarted :: Property DebianLike +restarted = Service.restarted "icinga2" + +-- | Reload Icinga 2 configuration. +reloaded :: Property DebianLike +reloaded = + Service.reloaded "icinga2" `before` delay + where + -- Reloading too fast causes problems, systemd waits forever. + delay = cmdProperty "sleep" ["1"] `assume` MadeChange + +-- | Configure IDO MySQL. +idoMysqlConfigured :: Property DebianLike +idoMysqlConfigured = + enabled + `describe` "icinga2 IDO MySQL feature enabled" + `requires` installed + `requires` Apt.installed ["icinga2-ido-mysql"] + `onChange` restarted + where + enabled = pathEnabled `File.isSymlinkedTo` pathAvailable + pathEnabled = "/etc/icinga2/features-enabled/ido-mysql.conf" + pathAvailable = File.LinkTarget "../features-available/ido-mysql.conf" + +-- | Configure and enable the API. +apiConfigured :: Property DebianLike +apiConfigured = + check (not <$> isConfigured) go + `requires` installed + `onChange` restarted + where + isConfigured :: IO Bool + isConfigured = doesFileExist "/etc/icinga2/features-enabled/api.conf" + go = cmdProperty "icinga2" ["api", "setup"] + +-- | Create an API user and grant permissions. +apiUserGranted + :: IsContext c + => User + -> ApiPermissions + -> c + -> Property (HasInfo + DebianLike) +apiUserGranted (User username) (ApiPermissions perms) context = + go + `requires` apiConfigured + where + go :: Property (HasInfo + DebianLike) + go = withPrivData (Password username) context $ \getpassword -> + property' desc $ \w -> getpassword $ \priv -> ensureProperty w $ + goprop $ privDataVal priv + goprop :: String -> Property DebianLike + goprop password = confAdjustSection desc section (adj password) (ins password) usersFile + desc = ("user " ++ username ++ " granted API usage") + section = "object ApiUser " ++ (confQuote username) + usersFile = "conf.d/api-users.conf" + adj password = const + [ section ++ " {" + , " password = " ++ (confQuote password) + , " permissions = [ " ++ qperms ++ " ]" ] + ins password ls = ls ++ [""] ++ (adj password []) ++ ["}"] + qperms = intercalate ", " $ map confQuote $ perms + +-- | Install Icinga Web 2. +webInstalled :: Property DebianLike +webInstalled = Apt.installed ["icingaweb2"] + +-- | Adjust a section of Icinga configuration file. +confAdjustSection + :: Desc + -> ConfSection + -> ConfFile.AdjustSection + -> ConfFile.InsertSection + -> FilePath + -> Property DebianLike +confAdjustSection desc section adj ins f = + ConfFile.adjustSection desc start past adj ins conff + `onChange` reloaded + where + start = (== (section ++ " {")) + past = (== "}") + conff = "/etc/icinga2" f + +-- | Ensures that a configuration file contains a section with a key=value +-- setting. Works for simple item using a simple format (single line). +confContainsSetting + :: FilePath + -> (ConfSection, ConfKey, String) + -> Property DebianLike +confContainsSetting f (section, key, value) = confAdjustSection + (f ++ " section '" ++ section ++ "' contains " ++ key ++ "=" ++ value) + section + go + (++ confsection) + f + where + confkey = " " ++ key ++ " = " + confline = confkey ++ value + go [] = [confline] + go (l:ls) = if isKeyVal l then confline : ls else l : go ls + isKeyVal = (confkey `isPrefixOf`) + confsection = ["", section ++ " {", confline, "}"] + +-- | Replace a full configuration file. +confFileContains :: FilePath -> [ConfStatement] -> Property DebianLike +confFileContains f stmts = + conff `File.hasContent` (concat stmts) + `onChange` reloaded + where + conff = "/etc/icinga2" f + +-- | Replace the full host definition. +confHosts :: [ConfStatement] -> Property DebianLike +confHosts hosts = confFileContains "conf.d/hosts.conf" hosts + +-- | Replace the full services definition. +confServices :: [ConfStatement] -> Property DebianLike +confServices services = confFileContains "conf.d/services.conf" services + +-- | Quote a string to be included in configuration. +confQuote :: String -> String +confQuote s = + ['"'] ++ (concatMap escape s) ++ ['"'] + where + escape c + | c == '"' = ['\\', '"'] + | c == '\\' = ['\\', '\\'] + | c == '\t' = ['\\', 't'] + | c == '\r' = ['\\', 'r'] + | c == '\n' = ['\\', 'n'] + | c == '\b' = ['\\', 'b'] + | c == '\f' = ['\\', 'f'] + | otherwise = [c] -- cgit v1.2.3