diff --git a/changelogs/fragments/feature_icinga_for_windows.yml b/changelogs/fragments/feature_icinga_for_windows.yml new file mode 100644 index 00000000..8346a4c2 --- /dev/null +++ b/changelogs/fragments/feature_icinga_for_windows.yml @@ -0,0 +1,5 @@ +major_changes: + - "Introduction of role :code:`ifw` - Icinga for Windows: This role allows to install the Icinga PowerShell Framework, manage components and repositories, and install and configure Icinga 2 through Icinga for Windows." + - "Module :code:`ifw_backgrounddaemon`: Registers/unregisters an Icinga for Windows background daemon." + - "Module :code:`ifw_component`: Installs/removes/updates Icinga for Windows components (e.g. :code:`agent`, :code:`plugins`)." + - "Module :code:`ifw_restapicommand`: Adds/removes commands to/from the whitelist/blacklist of the Icinga for Windows REST-Api." diff --git a/doc/role-ifw/README.md b/doc/role-ifw/README.md new file mode 120000 index 00000000..ff888298 --- /dev/null +++ b/doc/role-ifw/README.md @@ -0,0 +1 @@ +../../roles/ifw/README.md \ No newline at end of file diff --git a/plugins/modules/ifw_backgrounddaemon.ps1 b/plugins/modules/ifw_backgrounddaemon.ps1 new file mode 100644 index 00000000..b90a60c7 --- /dev/null +++ b/plugins/modules/ifw_backgrounddaemon.ps1 @@ -0,0 +1,125 @@ +#!powershell + +#AnsibleRequires -CSharpUtil Ansible.Basic + +### Input parameters +$spec = @{ + options = @{ + state = @{ type = "str"; choices = "absent", "present"; default = "present" } + command = @{ type = "str"; required = $true } + arguments = @{ type = "dict"; required = $false; default = @{} } + } + supports_check_mode = $true +} + + +### Module initilization +$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec) + + +### Make use of input parameters +$Changed = $false +$State = $module.Params.state +$Command = $module.Params.command +$Arguments = $module.Params.arguments + +# Sanetize $Arguments, prepend "-" to get " - " +$TmpArguments = @{} +foreach ($Argument in $Arguments.Keys) { + if (-Not $Argument.StartsWith("-")) { + $TmpArguments."-$($Argument)" = $Arguments.$Argument + } +} +$Arguments = $TmpArguments + + +### Main code +# Check if IfW is installed +if (-Not (Get-Command | Where-Object -Property Name -EQ "Show-IcingaRegisteredBackgroundDaemons")) { + throw "Necessary command 'Show-IcingaRegisteredBackgroundDaemons' was not found. Is IfW installed?" +} + +# Check that $Command is valid and available +if (-Not (Get-Command | Where-Object -Property Name -EQ $Command)) { + throw "Necessary command '$($Command)' was not found." +} + + +# Check if BackgroundDaemon for given command exists +function BackgroundDaemon-Exists () { + param( + [String]$Command + ); + + $Exists = (Show-IcingaRegisteredBackgroundDaemons) -Contains $Command + return $Exists +} + +# Check if given command arguments are equal to existing command arguments +function ArgumentsAreEqual () { + param( + $Arguments, + $ExistingArguments + ); + + $ArgumentsAreEqual = $true + foreach ($Key in $Arguments.keys) { + if ($Arguments.$Key -NE $ExistingArguments.$Key) { + $ArgumentsAreEqual = $false + break + } + } + return $ArgumentsAreEqual +} + +# Get existing arguments for given command +function Get-ExistingArguments () { + param( + [String]$Command + ); + + $Arguments = (Read-IcingaPowerShellConfig).BackgroundDaemon.EnabledDaemons."$($Command)".Arguments + + return $Arguments +} + + + +$CommandIsRegistered = BackgroundDaemon-Exists -Command $Command +$ExistingArguments = Get-ExistingArguments -Command $Command +$ArgumentsAreEqual = ArgumentsAreEqual -Arguments $Arguments -ExistingArguments $ExistingArguments + + +# Update if needed +if ($State -EQ "absent" -And $CommandIsRegistered) { + if (-Not $module.CheckMode) { + Unregister-IcingaBackgroundDaemon ` + -BackgroundDaemon $Command | Out-Null + } + $Changed = $true + +} elseif ($State -EQ "present" -And (-Not $CommandIsRegistered -Or -Not $ArgumentsAreEqual)) { + if (-Not $module.CheckMode) { + Register-IcingaBackgroundDaemon ` + -Command $Command ` + -Arguments $Arguments | Out-Null + } + $Changed = $true +} + +$Before = @{ + command = (&{ if ($CommandIsRegistered) { $Command } else { $null } } ) + arguments = (&{ if ($CommandIsRegistered) { $ExistingArguments } else { $null } } ) +} +$After = @{ + command = (&{ if ($State -EQ "present") { $Command } else { $null } } ) + arguments = (&{ if ($State -EQ "present") { $Arguments } else { $null } } ) +} + + + +### Module return +$module.Result.before = $Before +$module.Result.after = $After +$module.Result.changed = $Changed +$module.ExitJson() diff --git a/plugins/modules/ifw_backgrounddaemon.py b/plugins/modules/ifw_backgrounddaemon.py new file mode 100644 index 00000000..b81446e6 --- /dev/null +++ b/plugins/modules/ifw_backgrounddaemon.py @@ -0,0 +1,104 @@ +DOCUMENTATION = ''' +--- +name: ifw_backgrounddaemon +short_description: (Un-)Registers an IfW Background Daemon. +description: + - This module allows you to register/unregister an Icinga for Windows Background Daemon. + - They are used to collect metrics over time or used for the IfW API Check Forwarder. +version_added: 0.1.0 +author: + - Matthias Döhler +seealso: + - name: Icinga for Windows Background Daemons + description: Reference for the Background Daemons. + link: https://icinga.com/docs/icinga-for-windows/latest/doc/110-Installation/05-Background-Daemons/ + - name: Icinga for Windows API Check Forwareder + description: Reference for a possible use case regarding the API Check Forwarder. + link: https://icinga.com/docs/icinga-for-windows/latest/doc/110-Installation/30-API-Check-Forwarder/ +options: + state: + description: + - The state of the Background Daemon. + required: false + default: present + choices: [ "present", "absent" ] + type: str + command: + description: + - The name of a valid command available in the used PowerShell. + - This could be something like C(Start-MyCustomDaemon). + - If O(state=absent), only the O(command) is used to determine which Background Daemon should be removed. + required: true + type: str + arguments: + description: + - Arguments to be passed to O(command). + - Must be key value pairs. + - The leading C(-) is prepended in front of the argument name/key (C(key) becomes C(-key)). + required: false + type: dict +''' + +EXAMPLES = r''' +# The PowerShell equivalent is: +# Register-IcingaBackgroundDaemon ` +# -Command 'Start-MyCustomDaemon' ` +# -Arguments @{ +# '-MySwitchParameter' = $True; +# '-MyIntegerParameter' = 42; +# '-MyStringParameter' = 'Example'; +# }; +- name: Register a Background Daemon for a specific command passing argument flags with values to that command + netways.icinga.ifw_backgrounddaemon: + state: present + command: "Start-MyCustomDaemon" + arguments: + MySwitchParameter: true + MyIntegerParameter: 42 + MyStringParameter: "Example" + +- name: Register the Icinga for Windows RESTApi as a Background Daemon to use API Check Forwarder + netways.icinga.ifw_backgrounddaemon: + state: present + command: "Start-IcingaWindowsRESTApi" +''' + +RETURN = r''' +before: + description: + - Shows information about the previously (un-)registered command and its arguments. + - If no change occurs, will be the same as RV(after). + returned: success + type: dict + contains: + command: + description: The name of the previously (un-)registered command. + returned: success + type: str + sample: + arguments: + description: The arguments used previously for the specified command. + returned: success + type: dict + sample: +after: + description: + - Shows information about the newly (un-)registered command and its arguments. + - If no change occurs, will be the same as RV(before). + returned: success + type: dict + contains: + command: + description: The name of the newly (un-)registered command. + returned: success + type: str + sample: Start-MyCustomDaemon + arguments: + description: The arguments now used for the specified command. + returned: success + type: dict + sample: + -MyIntegerParameter: 42 + -MyStringParameter: "Example" + -MySwitchParameter: true +''' diff --git a/plugins/modules/ifw_component.ps1 b/plugins/modules/ifw_component.ps1 new file mode 100644 index 00000000..b3ceada5 --- /dev/null +++ b/plugins/modules/ifw_component.ps1 @@ -0,0 +1,177 @@ +#!powershell + +#AnsibleRequires -CSharpUtil Ansible.Basic + +### Input parameters +$spec = @{ + options = @{ + state = @{ type = "str"; choices = "absent", "present", "latest"; default = "present" } + name = @{ type = "list"; elements = "str"; required = $true; aliases = "component" } + } + supports_check_mode = $true +} + + +### Module initilization +$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec) + + +### Make use of input parameters +$Changed = $false +$State = $module.Params.state +$Components = $module.Params.name + + +### Main code +# Check if IfW is installed +if (-Not (Get-Command | Where-Object -Property Name -EQ "Install-IcingaComponent")) { + throw "Necessary command 'Install-IcingaComponent' was not found. Is IfW installed?" +} + +# Get list of installed components +function Get-InstalledComponentList () { + param( + ); + + $ComponentList = Get-IcingaInstallation + return $ComponentList +} + +# Get list of available components +function Get-AvailableComponentList () { + param( + ); + + $AvailableComponents = (Get-IcingaComponentList).Components + return $AvailableComponents +} + + +function Install-Component () { + param( + [String]$Component, + [String]$Version, + [Switch]$CheckMode + ); + + if ($CheckMode) { + return + } + + Install-IcingaComponent ` + -Name $Component ` + -Version $Version ` + -Confirm ` + -Force ` + | Out-Null +} + + +function Remove-Component () { + param( + [String]$Component, + [Switch]$CheckMode + ); + + if ($Component -EQ "framework") { + throw "Refusing to remove component '$($Component)'!" + } + + if ($CheckMode) { + return + } + + Uninstall-IcingaComponent ` + -Name $Component ` + -Confirm ` + | Out-Null +} + + + +$Added = @( +) +$Removed = @( +) + +$InstalledComponents = Get-InstalledComponentList +$AvailableComponents = Get-AvailableComponentList + +foreach ($Component in $Components) { + $NeedsInstallation = $false + + # Set version to specified version or to latest if state == latest and no version is specified + $Name, $Version = $Component.ToLower() -Split ":" + + $CurrentVersion = $InstalledComponents.$Name.CurrentVersion + $LatestVersion = $AvailableComponents.$Name + + # Allow shorthand ":" to mean ":latest" + if ($Version -EQ "latest" -Or $Version -EQ "") { + $Version = $LatestVersion + } + + switch ($State) { + "present" { + # If component is not installed or installed in wrong version + if ($InstalledComponents.Keys -NotContains $Name) { + if ($Version -EQ $null) { + $Version = $LatestVersion + } + $NeedsInstallation = $true + + } elseif ($Version -NE $null -And $Version -NE $CurrentVersion) { + $NeedsInstallation = $true + } + + } + "latest" { + # Mark any component with lower current version than 'latest' for install, unless component has a version specified + # If a version is specified, check if component is already installed in that version + if ($Version -EQ $null) { + $Version = $LatestVersion + + } + if ($Version -NE $LatestVersion) { + if ($Version -NE $CurrentVersion) { + $NeedsInstallation = $true + } + } elseif ($CurrentVersion -NE $LatestVersion) { + $Version = $LatestVersion + $NeedsInstallation = $true + } + } + "absent" { + if ($InstalledComponents.Keys -Contains $Name) { + Remove-Component ` + -Component $Name ` + -CheckMode:$CheckMode + $Removed += @{ + name = $Name + version = $CurrentVersion + } + $Changed = $true + } + } + } + # For states "present" and "latest" + if ($NeedsInstallation) { + Install-Component ` + -Component $Name ` + -Version $Version ` + -CheckMode:$CheckMode + $Added += @{ + name = $Name + version = $Version + } + $Changed = $true + } +} + + + +### Module return +$module.Result.added = $Added +$module.Result.removed = $Removed +$module.Result.changed = $Changed +$module.ExitJson() diff --git a/plugins/modules/ifw_component.py b/plugins/modules/ifw_component.py new file mode 100644 index 00000000..edf0c9c0 --- /dev/null +++ b/plugins/modules/ifw_component.py @@ -0,0 +1,94 @@ +DOCUMENTATION = ''' +--- +name: ifw_component +short_description: (Un-)Installs an IfW Component. +description: + - This module allows you to install/uninstall an Icinga for Windows Component. +version_added: 0.1.0 +author: + - Matthias Döhler +seealso: + - name: Icinga for Windows Components + description: Reference for the Components. + link: https://icinga.com/docs/icinga-for-windows/latest/doc/120-Repository-Manager/05-Install-Components/ +options: + state: + description: + - The state of the Component. + - Decides whether the Component will be installed (or its version changed) or removed. + required: false + default: present + choices: [ "present", "latest", "absent" ] + type: str + name: + description: + - A list of Component names that should be installed / removed. + - If O(state=present), a Component will be installed if not already present. + - If O(state=absent), a Component will be removed if present. + - If O(state=latest), a Component will be installed if not already present with the latest version. + - Each list entry can use the syntax of C(:) to install Component C() in version C(). C(:latest) will evaluate to the appropriate version for the given Component. C(:) is the same as C(:latest). + - If O(state=present) or O(state=latest), the above syntax takes precedence. This means that specifying a version should always result in that specific version of the component being installed. Has no impact if O(state=absent). + required: true + type: list + elements: str + aliases: + - "component" +''' + +EXAMPLES = r''' +- name: Install the Component 'plugins' in version '1.11.1' + netways.icinga.ifw_component: + state: present + name: "plugins:1.11.1" + +- name: Install multiple Components while keeping installed Components in their current version + netways.icinga.ifw_component: + state: present + name: + - "plugins" + - "agent" + - "service" + - "mssql" + +- name: Install the latest version of Components 'plugins' and 'mssql' while keeping 'agent' in version '2.14.1' + netways.icinga.ifw_component: + state: latest + name: + - "plugins" + - "mssql" + - "agent:2.14.1" + +- name: Remove Components 'mssql' and 'hyperv' + netways.icinga.ifw_component: + state: absent + name: + - "mssql" + - "hyperv" +''' + +RETURN = r''' +added: + description: + - Shows information about newly added or changed Components and the versions they now have. + returned: success + type: list + elements: dict + sample: + - name: "plugins" + version: "1.12.0" + - name: "mssql" + version: "1.5.0" + - name: "agent" + version: "2.14.1" +removed: + description: + - Shows information about removed Components and the versions they were installed with. + returned: success + type: list + elements: dict + sample: + - name: "hyperv" + version: "1.3.0" + - name: "mssql" + version: "1.5.0" +''' diff --git a/plugins/modules/ifw_restapicommand.ps1 b/plugins/modules/ifw_restapicommand.ps1 new file mode 100644 index 00000000..bb7cbc77 --- /dev/null +++ b/plugins/modules/ifw_restapicommand.ps1 @@ -0,0 +1,123 @@ +#!powershell + +#AnsibleRequires -CSharpUtil Ansible.Basic + +### Input parameters +$spec = @{ + options = @{ + state = @{ type = "str"; choices = "absent", "present"; default = "present" } + command = @{ type = "list"; elements = "str"; required = $true } + list = @{ type = "str"; choices = "whitelist", "blacklist"; default = "whitelist" } + endpoint = @{ type = "str"; choices = "apichecks", "checker"; default = "apichecks" } + purge = @{ type = "bool"; default = $false } + } + supports_check_mode = $true +} + + +### Module initilization +$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec) + + +### Make use of input parameters +$Changed = $false +$State = $module.Params.state +$Commands = $module.Params.command +$List = $module.Params.list +$Endpoint = $module.Params.endpoint +$Purge = $module.Params.purge + +$Blacklist = $false +if ($List -EQ "blacklist") { + $Blacklist = $true +} + + +# Check if RESTApiCommand exists in given endpoint and list +function RESTApiCommand-Exists () { + param( + [String]$Command, + [String]$Endpoint, + [String]$List + ); + + $Exists = (Get-IcingaPowerShellConfig -Path "RESTApi.Commands.$($Endpoint).$($List)") -Contains $Command + return $Exists +} + +function Get-ExistingRESTApiCommand () { + param( + [String]$Endpoint, + [String]$List + ); + + $ExistingCommands = Get-IcingaPowerShellConfig -Path "RESTApi.Commands.$($Endpoint).$($List)" + return $ExistingCommands +} + + +### Main code +# Check if IfW is installed +if (-Not (Get-Command | Where-Object -Property Name -EQ "Add-IcingaRESTApiCommand")) { + throw "Necessary command 'Add-IcingaRESTApiCommand' was not found. Is IfW installed?" +} + +$Added = @() +$Removed = @() + +$ExistingCommands = Get-ExistingRESTApiCommand -Endpoint $Endpoint -List $List + +# Purge every command in endpoint and list +# Simply replace with given list of commands +if ($Purge) { + if ($State -EQ "absent") { + $Removed += $ExistingCommands + } else { + $Removed += ($ExistingCommands | Where-Object { $_ -Notin $Commands }) + $Added += ($Commands | Where-Object { $_ -Notin $ExistingCommands }) + } + if (-Not $module.CheckMode) { + if ($State -EQ "absent") { + Set-IcingaPowerShellConfig -Path "RESTApi.Commands.$($Endpoint).$($List)" -Value @() + } else { + Set-IcingaPowerShellConfig -Path "RESTApi.Commands.$($Endpoint).$($List)" -Value $Commands + } + } + if ($Removed -Or $Added) { + $Changed = $true + } +} else { + foreach ($Command in $Commands) { + $CommandExists = ($ExistingCommands -Contains $Command) + + # Update if needed + if ($State -EQ "absent" -And $CommandExists) { + if (-Not $module.CheckMode) { + Remove-IcingaRESTApiCommand ` + -Command $Command ` + -Endpoint $Endpoint ` + -Blacklist:$Blacklist + } + $Removed += $Command + $Changed = $true + } elseif ($State -EQ "present" -And (-Not $CommandExists)) { + if (-Not $module.CheckMode) { + Add-IcingaRESTApiCommand ` + -Command $Command ` + -Endpoint $Endpoint ` + -Blacklist:$Blacklist + } + $Added += $Command + $Changed = $true + } + } +} + + +### Module return +$module.Result.added = $Added +$module.Result.removed = $Removed +$module.Result.list = $List +$module.Result.endpoint = $Endpoint +$module.Result.changed = $Changed +$module.ExitJson() diff --git a/plugins/modules/ifw_restapicommand.py b/plugins/modules/ifw_restapicommand.py new file mode 100644 index 00000000..54fb8761 --- /dev/null +++ b/plugins/modules/ifw_restapicommand.py @@ -0,0 +1,116 @@ +DOCUMENTATION = ''' +--- +name: ifw_restapicommand +short_description: Adds / Removes Icinga REST-Api Commands. +description: + - This module allows you to add / remove Icinga for Windows REST-Api Commands. + - You can also add Commands to the Blacklist. +version_added: 0.1.0 +author: + - Matthias Döhler +seealso: + - name: Icinga REST-Api Whitelists and Blacklists + description: Reference for how to use Whitelists and Blacklists for the Icinga REST-Api. + link: https://icinga.com/docs/icinga-for-windows/latest/doc/110-Installation/30-API-Check-Forwarder/#whitelist-check-commands +options: + state: + description: + - The state of the REST-Api Command. + required: false + default: present + choices: [ "present", "absent" ] + type: str + command: + description: + - The name of a valid command available in the used PowerShell. + - This could be something like C(Invoke-IcingaCheckCPU). + - The use of C(*) as a wildcard is allowed. + - O(command) is added to or removed from the chosen O(list) within the chosen O(endpoint). + required: true + type: list + elements: str + list: + description: + - The list the command should be added to / removed from. + required: false + default: whitelist + choices: [ "whitelist", "blacklist" ] + type: str + endpoint: + description: + - The endpoint the command should be added to / removed from. + required: false + default: apichecks + choices: [ "apichecks", "checker" ] + type: str + purge: + description: + - If O(purge=true), removes any command not specified in O(command) from O(list) within O(endpoint). If also setting O(state=absent), nothing will remain. + - If O(purge=false), commands in O(command) are added or removed if necessary. Existing commands stay untouched. + required: false + default: false + type: bool +''' + +EXAMPLES = r''' +- name: Allow all "Invoke-IcingaCheck" commands via wildcard + netways.icinga.ifw_restapicommand: + state: present + list: whitelist + endpoint: apichecks + command: "Invoke-IcingaCheck*" + +- name: Prohibit the use of "Invoke-IcingaCheckCPU" specifically + netways.icinga.ifw_restapicommand: + state: present + list: blacklist + command: "Invoke-IcingaCheckCPU" + +- name: Remove all entries from the blacklist + netways.icinga.ifw_restapicommand: + state: absent + list: blacklist + purge: true + command: "Invoke-IcingaCheckCPU" # Some valid command needed + +- name: Allow only two specific commands + netways.icinga.ifw_restapicommand: + state: present + purge: true + command: + - "Invoke-IcingaCheckCPU" + - "Invoke-IcingaCheckDiskHealth" +''' + +RETURN = r''' +added: + description: + - Shows information about newly added commands. + - They were added to RV(list) within RV(endpoint). + returned: success + type: list + elements: str + sample: + - Invoke-IcingaCheckCPU +removed: + description: + - Shows information about now removed commands. + - They were removed from RV(list) within RV(endpoint). + returned: success + type: list + elements: str + sample: + - Invoke-IcingaCheckDiskHealth +list: + description: + - The used list. + returned: success + type: str + sample: whitelist +endpoint: + description: + - The used endpoint. + returned: success + type: str + sample: apichecks +''' diff --git a/roles/ifw/README.md b/roles/ifw/README.md new file mode 100644 index 00000000..fffddd70 --- /dev/null +++ b/roles/ifw/README.md @@ -0,0 +1,242 @@ +# Role netways.icinga.ifw + +This role manages the installation/removal of [Icinga for Windows](https://icinga.com/docs/icinga-for-windows/latest/doc/000-Introduction/) components. +It first installs the [Icinga PowerShell Framework](https://github.com/Icinga/icinga-powershell-framework) in case it is not present yet and then goes on to use the framework's commands to manage other components. + +Tasks it can do: + +* Install the Icinga PowerShell Framework +* Manage repositories (no locally synced repositories yet) +* Configure the Icinga 2 Agent +* Create a valid Icinga 2 certificate + +Tasks it will not do: + +* Management of custom Monitoring Plugins +* Management of firewall rules outside of Icinga for Windows (like allowing ICMP echo request) +* Management of Check Commands (available as Icinga Config or Director Basket) + +Table of contents: + +* [Variables](#variables) + * [Getting a Certificate](#getting-a-certificate) +* [Example Playbooks](#example-playbooks) + * [Install Basic Components](#install-basic-components) + * [Install Other Plugins](#install-other-plugins) + * [Add Custom Repositories](#add-custom-repositories) + * [Icinga 2 Setup](#icinga-2-setup) +* [Additional Tasks](#additional-tasks) + +## Variables + +- `ifw_framework_url: string` + The URL to the different verions of the Icinga PowerShell Framework. + Default: `https://packages.icinga.com/IcingaForWindows/stable/framework/` + +- `ifw_framework_version: string` + The version of the Icinga PowerShell Framework to install. You can set a specific version here like `1.11.1`. + This is only used for the initial installation of the framework. Updates can be done using `ifw_components`. + Default: `latest` + +- `ifw_framework_path: string` + The path where the Icinga PowerShell Framework should be installed. If unspecified, the role will try using the best available path. + +- `ifw_repositories: list of dictionaries` + Here you can specify additional repositories from which to pull components from. The default Icinga For Windows repository will always be added. + Default: `none` + Example: + ```yaml + ifw_repositories: + - name: "Custom" + remote_path: "https://example.com/IcingaForWindows/stable/ifw.repo.json" + ``` + +- `ifw_components: list of dictionaries` + Specify which components should be present. Optionally specify which version of the component should be installed. + Components installed but not present within this list will be removed. + Default: `[ { name: "plugins", state: "present" }, { name: "agent", state: "present" } ]` + +- `ifw_icinga2_ca_host: string` + The Ansible inventory hostname of your Icinga 2 CA host (master). + This variable is used to sign the certificate for your Windows host using delegated tasks. + If `ifw_icinga2_ticket` is set, the ticket will take precedence. + Default: `none` + +- `ifw_icinga2_ticket: string` + The ticket obtained from your Icinga 2 CA host (master) using `icinga2 pki ticket --cn ""`. + It is used to receive a valid certificate. + Default: `none` + +- `ifw_connection_direction: string` + This variable decides whether your host should connect to its parent(s), its parent(s) to the host, or if connection should be established from both sides. + This influences the firewall rules that are automatically deployed. + In a well structured Icinga environment the master(s) (or satellite(s)) should not connect to the agent, but the agent should connect "up" to its parent(s). + Default: `fromagent` + +- `ifw_force_newcert: boolean` + If set to true, this forces the generation of a new certificate. + Default: `false` + +- `ifw_icinga2_cn: string` + This variable is used to define what common name your host takes within the Icinga hierarchy. Certificates will be generated using that name. + Default: `"{{ inventory_hostname }}"` + +- `ifw_icinga2_port: int` + This number is used for the local port on which your host should listen. + Default: `5665` + +- `ifw_icinga2_global_zones: list of strings` + Here you can specify all global zones your host should be aware of. + Default: `[]` + +- `ifw_icinga2_parents: list of dictionaries` + Here you can specify the parent endpoint(s) of your host's parent zone (O(ifw_icinga2_parent_zone)). + You can specify each parent's `cn`, its `host` attribute and the `port` on which it listens. The `cn` attribute is **required**. + Default: `none` + Example: + ```yaml + ifw_icinga2_parents: + - cn: parent1 + host: icinga01.example.com + port: 5665 + - cn: parent2 + host: 10.0.0.20 + port: 6556 + ``` + +- `ifw_icinga2_parent_zone: string` + The name of your parent(s) zone. + Default: `none` + +### Getting a Certificate + +If neither `ifw_icinga2_ca_host` nor `ifw_icinga2_ticket` is specified, your target host will connect to the first parent in `ifw_icinga2_parents` and file a CSR. This needs to be signed manually afterwards. + +If `ifw_icinga2_ca_host` is specified, a CSR is created locally and then moved to `ifw_icinga2_ca_host` where it is signed. The resulting certificate is then moved back to your target host. + +If `ifw_icinga2_ticket` is specified, `ifw_icinga2_ca_host` is ignored and the certificate should get signed automatically. `ifw_icinga2_ticket` needs to be acquired beforehand. + +## Example Playbooks + +The examples below showcase different aspects of the Icinga for Windows configuration. In a real scenario the variables from the examples should be used in conjunction. + +### Install Basic Components + +This installs just the Icinga PowerShell Framework, the Agent component and the Icinga PowerShell Plugins. + +```yaml +- name: Run ifw role + hosts: all + + roles: + - netways.icinga.ifw +``` + +### Install Other Plugins + +This installs the Agent component, the basic plugins and in addition to that the plugins for MSSQL and HyperV. +It also pins the Agent component to a specific version. + +```yaml +- name: Run ifw role + hosts: all + + vars: + ifw_components: + - name: "agent" + version: "2.14.0" + - name: "plugins" + - name: "mssql" + - name: "hyperv" + + roles: + - netways.icinga.ifw +``` + +### Add Custom Repositories + +This adds more repositories to Icinga for Windows in addition to the default one. +This is useful if you want to host a local mirror. + +```yaml +- name: Run ifw role + hosts: all + + vars: + ifw_repositories: + - name: "Ansible Managed" + remote_path: "https://example.com/IcingaForWindows/stable/ifw.repo.json" + + roles: + - netways.icinga.ifw +``` + +### Icinga 2 Setup + +This installs the Agent component and sets it up to communicate with both its parents. +It adds the global zone `windows-agents`. + +```yaml +- name: Run ifw role + hosts: all + + vars: + ifw_icinga2_ca_host: icinga01.example.com + + ifw_icinga2_global_zones: + - "windows-agents" + + ifw_icinga2_parent_zone: main + + ifw_icinga2_parents: + - cn: main01 + host: icinga01.example.com + port: 5665 + - cn: main02 + host: 10.0.0.20 + port: 5665 + + roles: + - netways.icinga.ifw +``` + + +## Additional Tasks + +This is meant as a hint for additional tasks you may need but which are not covered by Icinga for Windows and this role. + +This will use [`community.windows.win_firewall_rule`](https://docs.ansible.com/ansible/latest/collections/community/windows/win_firewall_rule_module.html) to allow ICMP (echo request) in all network zones, so default host checks like `hostalive` work. + +```yaml +- name: Allow ICMP (echo request) in firewall + community.windows.win_firewall_rule: + state: present + name: "{{ item.name }}" + enabled: true + profiles: "{{ item.profiles }}" + action: "{{ item.action }}" + direction: "{{ item.direction }}" + protocol: "{{ item.protocol }}" + icmp_type_code: "{{ item.icmp_type }}" + loop: + - name: "Allow inbound ICMPv4 (echo request)" + direction: "in" + protocol: "icmpv4" + icmp_type: + - "8:*" + action: "allow" + profiles: + - "domain" + - "private" + - "public" + - name: "Allow inbound ICMPv6 (echo request)" + direction: "in" + protocol: "icmpv6" + icmp_type: + - "8:*" + action: "allow" + profiles: + - "domain" + - "private" + - "public" +``` diff --git a/roles/ifw/defaults/main.yml b/roles/ifw/defaults/main.yml new file mode 100644 index 00000000..d87141d3 --- /dev/null +++ b/roles/ifw/defaults/main.yml @@ -0,0 +1,20 @@ +--- + +ifw_framework_url: "https://packages.icinga.com/IcingaForWindows/stable/framework/" +ifw_framework_version: "latest" +ifw_framework_path: + +ifw_repositories: [] + +ifw_components: + - name: "plugins" + state: "present" + - name: "agent" + state: "present" + +ifw_icinga2_ca_host: +ifw_connection_direction: "fromagent" +ifw_force_newcert: false +ifw_icinga2_cn: "{{ inventory_hostname }}" +ifw_icinga2_port: 5665 +ifw_icinga2_global_zones: [] diff --git a/roles/ifw/handlers/main.yml b/roles/ifw/handlers/main.yml new file mode 100644 index 00000000..ed97d539 --- /dev/null +++ b/roles/ifw/handlers/main.yml @@ -0,0 +1 @@ +--- diff --git a/roles/ifw/meta/argument_specs.yml b/roles/ifw/meta/argument_specs.yml new file mode 100644 index 00000000..582ce663 --- /dev/null +++ b/roles/ifw/meta/argument_specs.yml @@ -0,0 +1,165 @@ +--- + +argument_specs: + main: + short_description: Role to manage Icinga for Windows + description: + - Role to install, configure and manage L(Icinga for Windows, https://icinga.com/docs/icinga-for-windows/latest/doc/000-Introduction/). + - The role installs the Icinga PowerShell Framework and can manage components. + author: + - Matthias Döhler + options: + ifw_framework_url: + description: + - The URL to the different verions of the Icinga PowerShell Framework. + - This is where the zip files for the different versions are located, e.g. C(icinga-powershell-framework-1.13.3.zip) and C(icinga-powershell-framework-1.13.2.zip). + - HORIZONTALLINE + - The Icinga PowerShell Framework will be downloaded to your Ansible control node and copied over to the target machine. + type: str + required: false + default: "https://packages.icinga.com/IcingaForWindows/stable/framework/" + ifw_framework_version: + description: + - The version of the Icinga PowerShell Framework to install. You can specify a specific version here like C(1.11.1). + - This is only used for the initial installation of the framework. Updates can be done using O(ifw_components). + type: str + required: false + default: "latest" + ifw_framework_path: + description: + - The path where the Icinga PowerShell Framework should be installed. + - If unspecified, the role will try using the best available path. + type: str + required: false + ifw_repositories: + description: + - Here you can specify additional repositories from which to pull components from. + - The default Icinga For Windows repository will always be added. + type: list + elements: dict + required: false + default: [] + options: + name: + description: + - The name of the repository. + type: str + required: true + remote_path: + description: + - The remote path to the repository. This has to end in C(ifw.repo.json). + - "Example: C(remote_path: https://packages.icinga.com/IcingaForWindows/stable/ifw.repo.json)" + type: str + required: true + ifw_components: + description: + - Specify which components should be present. Optionally specify which version of the component should be installed. + - Components installed but not present within this list will be removed. + - &icinga2_requirements + Icinga 2 is only configured if + O(ifw_components) is set to install the component C(agent), + O(ifw_icinga2_parents) is set, and + O(ifw_icinga2_parent_zone) is set. + type: list + elements: dict + required: false + default: + - name: "plugins" + state: "present" + - name: "agent" + state: "present" + options: + state: + description: + - The desired state of the component. + type: str + required: false + default: present + choices: [ present, absent, latest ] + name: + description: + - The name of the component to be installed / removed. + - On the Windows host, a list of available components can be shown using C(Get-IcingaComponentList | Select-Object -Property Components | ConvertTo-Json). + type: str + required: true + version: + description: + - The version of the component to be installed / removed. + type: str + required: false + ifw_icinga2_ca_host: + description: + - The Ansible C(inventory_hostname) of your Icinga 2 CA host (master). + - This variable is used to sign the certificate for your Windows host using delegated tasks. + - If O(ifw_icinga2_ticket) is set, the ticket will take precedence. + type: str + required: false + ifw_icinga2_ticket: + description: + - The ticket obtained from your Icinga 2 CA host (master) using C(icinga2 pki ticket --cn ""). + - It is used to receive a valid certificate. + type: str + required: false + ifw_connection_direction: + description: + - This variable decides whether your host should connect to its parent(s), its parent(s) to the host, or if connection should be established from both sides. + - This influences the firewall rules that are automatically deployed. + - In a well structured Icinga environment the master(s) (or satellite(s)) should not connect to the agent, but the agent should connect "up" to its parent(s). + type: str + required: false + default: fromagent + choices: [ fromagent, fromparent, both ] + ifw_force_newcert: + description: + - If O(ifw_force_newcert=true), this forces the generation of a new certificate. + type: bool + required: false + default: false + ifw_icinga2_cn: + description: + - This variable is used to define what common name your host takes within the Icinga hierarchy. Certificates will be generated using that name. + type: str + required: false + default: "{{ inventory_hostname }}" + ifw_icinga2_port: + description: + - This number is used for the local port on which your host should listen for Icinga 2 cluster communication. + type: int + required: false + default: 5665 + ifw_icinga2_global_zones: + description: + - Here you can specify all global zones your host should be aware of. + type: list + elements: str + required: false + default: [] + ifw_icinga2_parents: + description: + - Here you can specify the parent endpoint(s) of your host's parent zone (O(ifw_icinga2_parent_zone)). + - You can specify each parent's C(cn), its C(host) attribute and the C(port) on which it listens. + - *icinga2_requirements + type: list + elements: dict + required: false + options: + cn: + description: + - The CN of the given host. This is the C(name) of the Icinga 2 Endpoint object. + type: str + required: true + host: + description: + - The hostname or address of the given host. This is the C(host) attribute of the Icinga 2 Endpoint object. + type: str + required: false + port: + description: + - The port of the given host. This is the C(port) attribute of the Icinga 2 Endpoint object. + type: str + required: false + ifw_icinga2_parent_zone: + description: + - The name of the Icinga 2 parent(s) zone. + - *icinga2_requirements + type: list diff --git a/roles/ifw/meta/main.yml b/roles/ifw/meta/main.yml new file mode 100644 index 00000000..34fb8eb7 --- /dev/null +++ b/roles/ifw/meta/main.yml @@ -0,0 +1,17 @@ +galaxy_info: + namespace: netways + role_name: ifw + author: | + - Matthias Döhler + description: Role to install the Icinga PowerShell Framework and manage its components + license: Apache-2.0 + min_ansible_version: "2.9" + platforms: + - name: Windows + galaxy_tags: + - icinga + - monitoring + - icingaforwindows + - ifw + - windows +dependencies: [] diff --git a/roles/ifw/tasks/configure_icinga2.yml b/roles/ifw/tasks/configure_icinga2.yml new file mode 100644 index 00000000..8c98b1ab --- /dev/null +++ b/roles/ifw/tasks/configure_icinga2.yml @@ -0,0 +1,133 @@ +--- + +- name: Get path to Icinga binary + changed_when: false + ansible.windows.win_shell: Get-IcingaAgentBinary + register: _icinga_binary_cmd + +- name: Get Icinga PowerShell Framework path + changed_when: false + ansible.windows.win_shell: Get-IcingaFrameworkRootPath + register: _framework_path + +- name: Get path to Icinga configuration directory + changed_when: false + ansible.windows.win_shell: Get-IcingaAgentConfigDirectory + register: _icinga_config_dir_cmd + +- name: CA certificate + when: not ifw_icinga2_ca_host is none + block: + - name: Get CA certificate + become: true + run_once: true + delegate_to: "{{ ifw_icinga2_ca_host | default('localhost', true) }}" + ansible.builtin.slurp: + src: "/var/lib/icinga2/certs/ca.crt" + register: _icinga2_ca_cert + + - name: Deploy CA certificate + ansible.windows.win_copy: + dest: "{{ _icinga_config_dir_cmd.stdout | trim }}../../var/lib/icinga2/certs/ca.crt" + content: "{{ _icinga2_ca_cert.content | b64decode }}" + +- name: Create private key and signing request + ansible.windows.win_shell: | + & '{{ (_icinga_binary_cmd.stdout | trim) }}' pki new-cert \ + --cn "{{ ifw_icinga2_cn }}" \ + --key "{{ _icinga_config_dir_cmd.stdout | trim }}../../var/lib/icinga2/certs/{{ ifw_icinga2_cn }}.key" \ + --cert "{{ _icinga_config_dir_cmd.stdout | trim }}../../var/lib/icinga2/certs/{{ ifw_icinga2_cn }}.crt" \ + --csr "{{ _icinga_config_dir_cmd.stdout | trim }}../../var/lib/icinga2/certs/{{ ifw_icinga2_cn }}.csr" + args: + creates: "{{ _icinga_config_dir_cmd.stdout | trim }}../../var/lib/icinga2/certs/{{ ifw_icinga2_cn }}.key" + register: _created_private_key + +- name: Handle certificate + when: + - not ifw_icinga2_ca_host is none + - (_created_private_key.skipped is not defined) or (ifw_force_newcert) + block: + - name: Get content of CSR + ansible.builtin.slurp: + src: "{{ _icinga_config_dir_cmd.stdout | trim }}../../var/lib/icinga2/certs/{{ ifw_icinga2_cn }}.csr" + register: _ifw_csr + + - name: Copy CSR to CA + delegate_to: "{{ ifw_icinga2_ca_host | default('localhost', true) }}" + ansible.builtin.copy: + dest: "/tmp/{{ ifw_icinga2_cn }}.csr" + content: "{{ _ifw_csr.content | b64decode }}" + mode: "0664" + + - name: Sign CSR + become: true + delegate_to: "{{ ifw_icinga2_ca_host | default('localhost', true) }}" + changed_when: false + ansible.builtin.shell: | + icinga2 pki sign-csr \ + --csr /tmp/{{ ifw_icinga2_cn }}.csr \ + --cert /tmp/{{ ifw_icinga2_cn }}.crt + + - name: Get host certificate + become: true + delegate_to: "{{ ifw_icinga2_ca_host | default('localhost', true) }}" + ansible.builtin.slurp: + src: "/tmp/{{ ifw_icinga2_cn }}.crt" + register: _icinga2_host_cert + + - name: Deploy host certificate + when: not ifw_icinga2_ca_host is none + ansible.windows.win_copy: + dest: "{{ _icinga_config_dir_cmd.stdout | trim }}../../var/lib/icinga2/certs/{{ ifw_icinga2_cn }}.crt" + content: "{{ _icinga2_host_cert.content | b64decode }}" + +- name: Get current configuration + ansible.builtin.slurp: + src: "{{ _framework_path.stdout | trim }}/config/config.json" + register: _current_icinga_configuration + +- name: Create Icinga install command + vars: + ifw_icinga2_agent_version: "{{ (ifw_components | selectattr('name', 'eq', 'agent')).version | default('latest') }}" + ansible.builtin.set_fact: + _install_command: "{{ lookup('template', role_path + '/templates/windows/icinga_install_command.j2') }}" + +- name: Set facts for comparison + when: + - (_current_icinga_configuration.content | b64decode | from_json).Framework.Config is defined + - (_current_icinga_configuration.content | b64decode | from_json).Framework.Config.Live is defined + vars: + _framework_config_live: "{{ (_current_icinga_configuration.content | b64decode | from_json).Framework.Config.Live }}" + ansible.builtin.set_fact: + _current_ca_server: "{{ _framework_config_live['IfW-CAServer']['Values'][0] | default(none) }}" + _current_global_zones: "{{ _framework_config_live['IfW-CustomZones']['Values'] }}" + _current_port: "{{ _framework_config_live['IfW-Port']['Values'][0] | default(none) }}" + _current_parent_zone: "{{ _framework_config_live['IfW-ParentZone']['Values'][0] | default(none) }}" + _current_parents: "{{ _framework_config_live['IfW-ParentNodes']['Values'] }}" + _current_parents0: "{{ _framework_config_live['IfW-ParentAddress:' + ifw_icinga2_parents[0].cn]['Values'][0] | default(none) }}" + _current_parents1: "{{ (_framework_config_live['IfW-ParentAddress:' + ifw_icinga2_parents[1].cn]['Values'][0] if ifw_icinga2_parents | length > 1 else none) | default(none) }}" # noqa: yaml[line-length] + +- name: Check whether requested and existing configuration is identical + failed_when: false + vars: + _ifw_ca_server: "[{{ ifw_icinga2_parents[0].host | default(ifw_icinga2_parents[0].cn) }}]:{{ ifw_icinga2_parents[0].port | default('5665') }}" + _parent0: "[{{ ifw_icinga2_parents[0].host | default(ifw_icinga2_parents[0].cn) }}]:{{ ifw_icinga2_parents[0].port | default('5665') }}" + _parent1: "[{{ ifw_icinga2_parents[1].host | default(ifw_icinga2_parents[1].cn) | default(none) }}]:{{ ifw_icinga2_parents[1].port | default('5665') | default(none) }}" # noqa: yaml[line-length] + ansible.builtin.assert: + that: + - (_current_icinga_configuration.content | b64decode | from_json).Framework.Config.Live is defined + - (_current_ca_server | default(true, true)) == (_ifw_ca_server) + - _current_global_zones == ifw_icinga2_global_zones + - (_current_port | int) == (ifw_icinga2_port | int) + - _current_parent_zone == ifw_icinga2_parent_zone + - _current_parents == (ifw_icinga2_parents | map(attribute='cn')) + - _current_parents0 == _parent0 + - (_current_parents1 == _parent1 if ifw_icinga2_parents | length > 1 else true) + fail_msg: "Configuration needs an update" + success_msg: "Configuration needs no update" + register: _assertion_result + +- name: Set up Icinga + timeout: 300 + when: _assertion_result.evaluated_to is defined + ansible.windows.win_shell: "Install-Icinga -InstallCommand \"{{ _install_command }}\"" diff --git a/roles/ifw/tasks/install_components.yml b/roles/ifw/tasks/install_components.yml new file mode 100644 index 00000000..69031e04 --- /dev/null +++ b/roles/ifw/tasks/install_components.yml @@ -0,0 +1,40 @@ +--- + +- name: Get list of available components + changed_when: false + ansible.windows.win_shell: ConvertTo-Json -InputObject ((Get-IcingaComponentList).Components) + register: _available_components + +- name: Get list of installed components + changed_when: false + ansible.windows.win_shell: ConvertTo-Json -InputObject (Get-IcingaInstallation) + register: _installed_components + +- name: Convert available/installed components to json + ansible.builtin.set_fact: + _available_components_json: "{{ _available_components.stdout | from_json | dict2items }}" + _installed_components_json: "{{ _installed_components.stdout | from_json | dict2items }}" + +- name: Validate requested components are installable + loop: "{{ ifw_components }}" + ansible.builtin.assert: + that: "{{ item.name in (_available_components_json | map(attribute='key') | sort) }}" + fail_msg: "'{{ item.name }}' is not an installable component! (Keep component names lowercase)" + +- name: Remove non requested components + vars: + _installed_components_list: "{{ _installed_components_json | map(attribute='key') | difference(['framework']) }}" + _components_to_be_removed: "{{ _installed_components_list | difference(ifw_components | map(attribute='name')) }}" + netways.icinga.ifw_component: + state: absent + name: "{{ _components_to_be_removed }}" + +- name: Combine component name with component version + loop: "{{ ifw_components }}" + ansible.builtin.set_fact: + _components_to_be_installed: "{{ (_components_to_be_installed | default([])) + [item.name + (':' + item.version if item.version is defined else '')] }}" + +- name: Install components + netways.icinga.ifw_component: + state: present + name: "{{ _components_to_be_installed }}" diff --git a/roles/ifw/tasks/install_powershell_framework.yml b/roles/ifw/tasks/install_powershell_framework.yml new file mode 100644 index 00000000..8d049e9a --- /dev/null +++ b/roles/ifw/tasks/install_powershell_framework.yml @@ -0,0 +1,73 @@ +--- + +- name: Get latest Framework version + run_once: true + when: ifw_framework_version == "latest" + block: + - name: Get list of available Framework versions + delegate_to: localhost + ansible.builtin.uri: + url: "{{ ifw_framework_url }}" + return_content: true + register: _repo_html + + - name: Set fact for Framework version + ansible.builtin.set_fact: + ifw_framework_version: "{{ _repo_html.content | regex_findall('[\\d\\.]+\\.zip') | community.general.version_sort | last | replace('.zip', '') }}" + +- name: Determine Icinga PowerShell Framework module path + when: not ifw_framework_path + block: + - name: Check existence of possible module paths + ansible.windows.win_stat: + path: "{{ item }}" + register: _module_path_info + loop: "{{ ifw_framework_path_options }}" + + - name: Set Icinga PowerShell Framework module path + ansible.builtin.set_fact: + ifw_framework_path: "{{ (_module_path_info.results | selectattr('stat', 'defined') | selectattr('stat.exists', 'equalto', true) | first).stat.path }}" + when: (_module_path_info.results | selectattr('stat', 'defined') | selectattr('stat.exists', 'equalto', true) | first).stat.exists + +- name: Download Icinga PowerShell Framework + run_once: true + delegate_to: localhost + ansible.builtin.get_url: + url: "{{ ifw_framework_url }}/icinga-powershell-framework-{{ ifw_framework_version }}.zip" + dest: "/tmp/icinga-powershell-framework.zip" + mode: "0665" + +- name: Unzip Icinga PowerShell Framework + run_once: true + delegate_to: localhost + ansible.builtin.unarchive: + src: "/tmp/icinga-powershell-framework.zip" + dest: "/tmp/" + +- name: Remove previous Icinga PowerShell Framework directory + run_once: true + delegate_to: localhost + changed_when: false + ansible.builtin.file: + state: absent + path: "/tmp/icinga-powershell-framework/" + +- name: Rename Icinga PowerShell Framework directory + run_once: true + delegate_to: localhost + changed_when: false + ansible.builtin.command: "cp -r /tmp/icinga-powershell-framework-{{ ifw_framework_version }}/ /tmp/icinga-powershell-framework" + +- name: Check if Icinga PowerShell Framework needs to be copied + ansible.windows.win_stat: + path: "{{ ifw_framework_path }}/icinga-powershell-framework" + register: _ifw_framework_path_stat + +- name: Copy Icinga PowerShell Framework to Windows hosts + when: not _ifw_framework_path_stat.stat.exists + ansible.windows.win_copy: + src: "/tmp/icinga-powershell-framework/" + dest: "{{ ifw_framework_path }}/icinga-powershell-framework/" + +- name: Run Use-Icinga once + ansible.windows.win_shell: Use-Icinga diff --git a/roles/ifw/tasks/main.yml b/roles/ifw/tasks/main.yml new file mode 100644 index 00000000..c5134c03 --- /dev/null +++ b/roles/ifw/tasks/main.yml @@ -0,0 +1,32 @@ +--- + +- name: Check if Icinga PowerShell Framework is installed + ansible.windows.win_shell: Use-Icinga -Minimal + when: ansible_os_family == "Windows" + register: _use_icinga + changed_when: false + failed_when: false + +- name: Install Icinga PowerShell Framework + ansible.builtin.include_tasks: "{{ role_path }}/tasks/install_powershell_framework.yml" + when: + - ansible_os_family == "Windows" + - _use_icinga.stderr != "" + +- name: Manage repositories + ansible.builtin.include_tasks: "{{ role_path }}/tasks/manage_repositories.yml" + when: ansible_os_family == "Windows" + +- name: Install Icinga Powershell Components + ansible.builtin.include_tasks: "{{ role_path }}/tasks/install_components.yml" + when: + - ansible_os_family == "Windows" + - ifw_components | length > 0 + +- name: Configure Icinga 2 + ansible.builtin.include_tasks: "{{ role_path }}/tasks/configure_icinga2.yml" + when: + - ansible_os_family == "Windows" + - ifw_components | selectattr('name', 'eq', 'agent') | length > 0 + - ifw_icinga2_parents is defined + - ifw_icinga2_parent_zone is defined diff --git a/roles/ifw/tasks/manage_repositories.yml b/roles/ifw/tasks/manage_repositories.yml new file mode 100644 index 00000000..a6e52666 --- /dev/null +++ b/roles/ifw/tasks/manage_repositories.yml @@ -0,0 +1,43 @@ +--- + +- name: Get current repositories + changed_when: false + ansible.windows.win_shell: ConvertTo-Json -InputObject @(Get-IcingaRepositories) + register: _current_repositories + +- name: Convert current repositories to json + ansible.builtin.set_fact: + _current_repositories_json: "{{ _current_repositories.stdout | from_json }}" + +- name: Combine requested repositories and fixed repositories + ansible.builtin.set_fact: + ifw_repositories: "{{ ifw_repositories + ifw_fixed_repositories }}" + +- name: Remove non requested repositories + loop: "{{ _current_repositories_json }}" + when: + - not item is none + - item.Name not in (ifw_repositories | map(attribute='name')) + ansible.windows.win_shell: Remove-IcingaRepository -Name "{{ item.Name }}" + +- name: Update existing repositories + loop: "{{ ifw_repositories }}" + vars: + _current_repository: "{{ _current_repositories_json | selectattr('Name', 'defined') | selectattr('Name', 'eq', item.name) }}" + when: + - _current_repository | length > 0 + - _current_repository[0].Value.RemotePath != item.remote_path + ansible.windows.win_shell: Add-IcingaRepository -Name "{{ item.name }}" -RemotePath "{{ item.remote_path }}" -Force + +- name: Add repositories + loop: "{{ ifw_repositories }}" + vars: + _current_repository: "{{ _current_repositories_json | selectattr('Name', 'defined') | selectattr('Name', 'eq', item.name) }}" + when: + - not _current_repository + ansible.windows.win_shell: Add-IcingaRepository -Name "{{ item.name }}" -RemotePath "{{ item.remote_path }}" + +- name: Push fixed repositories to the end of the list (lowest priority) + changed_when: false + loop: "{{ ifw_fixed_repositories | map(attribute='name') }}" + ansible.windows.win_shell: Push-IcingaRepository -Name "{{ item }}" diff --git a/roles/ifw/templates/windows/icinga_install_command.j2 b/roles/ifw/templates/windows/icinga_install_command.j2 new file mode 100644 index 00000000..b56cff55 --- /dev/null +++ b/roles/ifw/templates/windows/icinga_install_command.j2 @@ -0,0 +1,103 @@ +{ + "IfW-CustomZones": { + "Values": {{ ifw_icinga2_global_zones | tojson }} + }, + {% if ifw_icinga2_ticket is defined %} + {# Use ticket if specified, else resort to contacting ca_host (master or satellite) #} + "IfW-Certificate": { + {# 1 -> Use ticket to obtain certificate #} + "Selection": "1" + }, + "IfW-Ticket": { + "Values": [ + "{{ ifw_icinga2_ticket }}" + ] + }, + {% else %} + "IfW-Certificate": { + {# 0 -> Sign certificate manually on the Icinga CA master #} + "Selection": "0" + }, + "IfW-CAServer": { + "Values": [ + "[{{ ifw_icinga2_parents[0].host | default(ifw_icinga2_parents[0].cn) }}]:{{ ifw_icinga2_parents[0].port | default('5665') }}" + ] + }, + {% endif %} + "IfW-GlobalZones": { + {# 3 -> Don't automatically add suggested global zones #} + "Selection": "3" + }, + "IfW-InstallAgent": { + {# 0 -> Set up for installation. Keeps IMC structured correctly. #} + "Selection": "0" + }, + "IfW-AgentVersion": { + "Values": [ + {# 'release' means 'latest' stable #} + "{{ ifw_icinga2_agent_version if ifw_icinga2_agent_version != 'latest' else 'release' }}" + ] + }, + "IfW-Port": { + "Values": [ + "{{ ifw_icinga2_port | default('5665') }}" + ] + }, + "IfW-ParentNodes": { + "Values": {{ ifw_icinga2_parents | map(attribute='cn') | tojson }} + }, + "IfW-ParentZone": { + "Values": [ "{{ ifw_icinga2_parent_zone }}" ] + }, + "IfW-ParentAddress": { + "Values": { + {% for _parent in ifw_icinga2_parents %} + "{{ _parent.cn }}": [ "[{{ _parent.host | default(_parent.cn) }}]:{{ _parent.port | default('5665') }}" ]{{ ',' if (ifw_icinga2_parents | length == 2) and loop.index == 1 }} + {% endfor %} + } + }, + "IfW-InstallPlugins": { + {# 1 -> Don't automatically install plugins #} + "Selection": "1" + }, + "IfW-Connection": { + {# Connection direction -> Influences firewall rules + 0 -> Connection from agent + 1 -> Connection from parent + 2 -> Connection from both sides + #} + {% if (ifw_connection_direction | lower) == 'fromagent' %} + "Selection": "0" + {% elif (ifw_connection_direction | lower) == 'fromparent' %} + "Selection": "1" + {% elif (ifw_connection_direction | lower) == 'both' %} + "Selection": "2" + {% endif %} + }, + "IfW-InstallService": { + {# 1 -> Don't automatically install service. Handled elsewhere #} + "Selection": "1" + }, + "IfW-InstallJEAProfile": { + "Selection": "2" + }, + "IfW-Hostname": { + {# 6 -> Set hostname manually #} + "Selection": "6" + }, + "IfW-CustomHostname": { + "Values": [ + "{{ ifw_icinga2_cn }}" + ] + }, + "IfW-InstallApiChecks": { + {# 0 -> Don't install IfW Api Check Forwarder #} + "Selection": "0" + }, + "IfW-AgentUser": { + {# TODO WIP - Define user to run service as #} + "Values": [ + "NT Authority\\NetworkService" + ] + } +} diff --git a/roles/ifw/vars/main.yml b/roles/ifw/vars/main.yml new file mode 100644 index 00000000..2059025c --- /dev/null +++ b/roles/ifw/vars/main.yml @@ -0,0 +1,14 @@ +--- + +ifw_repository_json: "https://packages.icinga.com/IcingaForWindows/stable/ifw.repo.json" + +ifw_framework_path_options: + - "C:/Program Files/WindowsPowerShell/Modules" + - "C:/Windows/system32/WindowsPowerShell/v1.0/Modules" + - "C:/Users/{{ ansible_user }}/Documents/WindowsPowerShell/Modules" + +ifw_temp_directory: "%localappdata%/Temp/" + +ifw_fixed_repositories: + - name: "Icinga Stable" + remote_path: "https://packages.icinga.com/IcingaForWindows/stable/ifw.repo.json"