lib/core/icingaagent/installer/Install-IcingaAgentCertificates.psm1

function Install-IcingaAgentCertificates()
{
    param(
        [string]$Hostname,
        [string]$Endpoint,
        [int]$Port        = 5665,
        [string]$CACert,
        [string]$Ticket,
        [switch]$Force    = $FALSE
    );

    if ([string]::IsNullOrEmpty($Hostname)) {
        Write-IcingaConsoleError 'Failed to install Icinga Agent certificates. Please provide a hostname';
        return $FALSE;
    }

    # Default for Icinga 2.8.0 and above
    [string]$NewCertificateDirectory = (Join-Path -Path $Env:ProgramData -ChildPath 'icinga2\var\lib\icinga2\certs\');
    [string]$OldCertificateDirectory = (Join-Path -Path $Env:ProgramData -ChildPath 'icinga2\etc\icinga2\pki\');
    [string]$CertificateDirectory    = $NewCertificateDirectory;
    if ((Compare-IcingaVersions -RequiredVersion '2.8.0') -eq $FALSE) {
        # Certificate path for versions older than 2.8.0
        $CertificateDirectory = $OldCertificateDirectory;
        Move-IcingaAgentCertificates -Source $NewCertificateDirectory -Destination $OldCertificateDirectory;
    } else {
        Move-IcingaAgentCertificates -Source $OldCertificateDirectory -Destination $NewCertificateDirectory;
    }

    if (-Not (Test-IcingaAgentCertificates -CertDirectory $CertificateDirectory -Hostname $Hostname -Force $Force)) {
        Write-IcingaConsoleNotice ([string]::Format('Generating host certificates for host "{0}"', $Hostname));

        $arguments = [string]::Format('pki new-cert --cn {0} --key {1}{0}.key --cert {1}{0}.crt',
            $Hostname,
            $CertificateDirectory
        );

        if ((Start-IcingaAgentCertificateProcess -Arguments $arguments) -eq $FALSE) {
            Write-IcingaConsoleError 'Failed to generate host certificate';
            return $FALSE;
        }
    }

    if ([string]::IsNullOrEmpty($Endpoint) -And [string]::IsNullOrEmpty($CACert)) {
        Write-IcingaConsoleWarning 'Your host certificates have been generated successfully. Please either specify an endpoint to connect to or provide the path to a valid ca.crt';
        return $FALSE;
    }

    if (-Not [string]::IsNullOrEmpty($Endpoint)) {
        if (-Not (Test-IcingaAgentCertificates -CertDirectory $CertificateDirectory -Hostname $Hostname -TestTrustedParent -Force $Force)) {

            Write-IcingaConsoleNotice ([string]::Format('Fetching trusted master certificate from "{0}"', $Endpoint));

            # Argument --key for save-cert is deprecated starting with Icinga 2.12.0
            if (Compare-IcingaVersions -RequiredVersion '2.12.0') {
                $arguments = [string]::Format('pki save-cert --trustedcert {0}trusted-parent.crt --host {1} --port {2}',
                    $CertificateDirectory,
                    $Endpoint,
                    $Port
                );
            } else {
                $arguments = [string]::Format('pki save-cert --key {0}{1}.key --trustedcert {0}trusted-parent.crt --host {2} --port {3}',
                    $CertificateDirectory,
                    $Hostname,
                    $Endpoint,
                    $Port
                );
            }

            if ((Start-IcingaAgentCertificateProcess -Arguments $arguments) -eq $FALSE) {
                Write-IcingaConsoleError 'Unable to connect to your provided Icinga CA. Please verify the entered configuration is correct.' `
                    'If you are not able to connect to your Icinga CA from this machine, you will have to provide the path' `
                    'to your Icinga ca.crt and use the CA-Proxy certificate handling.';
                return $FALSE;
            }
        }

        if (-Not (Test-IcingaAgentCertificates -CertDirectory $CertificateDirectory -Hostname $Hostname -TestCACert -Force $Force)) {
            [string]$PKIRequest = 'pki request --host {0} --port {1} --ticket {4} --key {2}{3}.key --cert {2}{3}.crt --trustedcert {2}trusted-parent.crt --ca {2}ca.crt';

            if ([string]::IsNullOrEmpty($Ticket)) {
                $PKIRequest = 'pki request --host {0} --port {1} --key {2}{3}.key --cert {2}{3}.crt --trustedcert {2}trusted-parent.crt --ca {2}ca.crt';
            }

            $arguments = [string]::Format($PKIRequest,
                $Endpoint,
                $Port,
                $CertificateDirectory,
                $Hostname,
                $Ticket
            );

            if ((Start-IcingaAgentCertificateProcess -Arguments $arguments) -eq $FALSE) {
                Write-IcingaConsoleError 'Failed to sign Icinga certificate';
                return $FALSE;
            }

            if ([string]::IsNullOrEmpty($Ticket)) {
                Write-IcingaConsoleNotice 'Your certificates were generated successfully. Please sign the certificate now on your Icinga CA master. You can lookup open requests with "icinga2 ca list"';
            } else {
                Write-IcingaConsoleNotice 'Icinga certificates successfully installed';
            }
        }

        return $TRUE;
    } elseif (-Not [string]::IsNullOrEmpty($CACert)) {
        if (-Not (Copy-IcingaAgentCACertificate -CAPath $CACert -Desination $CertificateDirectory)) {
            return $FALSE;
        }
        Write-IcingaConsoleNotice 'Host-Certificates and ca.crt are present. Please start your Icinga Agent now and manually sign your certificate request on your CA master. You can lookup open requests with "icinga2 ca list"';
    }

    return $TRUE;
}

function Start-IcingaAgentCertificateProcess()
{
    param(
        $Arguments
    );

    $Binary  = Get-IcingaAgentBinary;
    $Process = Start-IcingaProcess -Executable $Binary -Arguments $Arguments;

    if ($Process.ExitCode -ne 0) {
        Write-IcingaConsoleError ([string]::Format('Failed to create certificate.{0}Arguments: {1}{0}Error:{2} {3}', "`r`n", $Arguments, $Process.Message, $Process.Error));
        return $FALSE;
    }

    Write-IcingaConsoleNotice $Process.Message;
    return $TRUE;
}

function Move-IcingaAgentCertificates()
{
    param(
        [string]$Source,
        [string]$Destination
    );

    $SourceDir = Join-Path -Path $Source -ChildPath '\*';
    $TargetDir = Join-Path -Path $Destination -ChildPath '\';

    Move-Item -Path $SourceDir -Destination $TargetDir;
}

function Test-IcingaAgentCertificates()
{
    param(
        [string]$CertDirectory,
        [string]$Hostname,
        [switch]$TestCACert,
        [switch]$TestTrustedParent,
        [bool]$Force
    );

    if ($Force) {
        return $FALSE;
    }

    if ($TestCACert) {
        if (Test-Path (Join-Path -Path $CertDirectory -ChildPath 'ca.crt')) {
            Write-IcingaConsoleNotice 'Your ca.crt is present. No generation or fetching required';
            return $TRUE;
        } else {
            Write-IcingaConsoleWarning 'Your ca.crt is not present. Manuall copy or fetching from your Icinga CA is required.';
            return $FALSE;
        }
    }

    if ($TestTrustedParent) {
        if (Test-Path (Join-Path -Path $CertDirectory -ChildPath 'trusted-parent.crt')) {
            Write-IcingaConsoleNotice 'Your trusted-parent.crt is present. No fetching or generation required';
            return $TRUE;
        } else {
            Write-IcingaConsoleWarning 'Your trusted master certificate is not present. Fetching from your CA server is required';
            return $FALSE;
        }
    }

    if ((-Not (Test-Path ((Join-Path -Path $CertDirectory -ChildPath $Hostname) + '.key'))) `
            -Or -Not (Test-Path ((Join-Path -Path $CertDirectory -ChildPath $Hostname) + '.crt'))) {
        return $FALSE;
    }

    [string]$hostCRT = [string]::Format('{0}.crt', $Hostname);
    [string]$hostKEY = [string]::Format('{0}.key', $Hostname);

    $certificates = Get-ChildItem -Path $CertDirectory;
    # Now loop each file and match their name with our hostname
    foreach ($cert in $certificates) {
        if ($cert.Name.toLower() -eq $hostCRT.toLower() -Or $cert.Name.toLower() -eq $hostKEY.toLower()) {
            $file = $cert.Name.Replace('.key', '').Replace('.crt', '');
            if (-Not ($file -clike $Hostname)) {
                Write-IcingaConsoleWarning ([string]::Format('Certificate file {0} is not matching the hostname {1}. Certificate generation is required.', $cert.Name, $Hostname));
                return $FALSE;
            }
        }
    }

    Write-IcingaConsoleNotice 'Icinga host certificates are present and valid. No generation required';

    return $TRUE;
}

function Copy-IcingaAgentCACertificate()
{
    param(
        [string]$CAPath,
        [string]$Desination
    );

    # Copy ca.crt from local path or network share to certificate path
    if ((Test-Path $CAPath)) {
        Copy-Item -Path $CAPath -Destination (Join-Path -Path $Desination -ChildPath 'ca.crt') | Out-Null;
        Write-IcingaConsoleNotice ([string]::Format('Copied ca.crt from "{0}" to "{1}', $CAPath, $Desination));
    } else {
        Set-IcingaTLSVersion;
        # It could also be a web ressource
        try {
            $response   = Invoke-WebRequest $CAPath -UseBasicParsing;
            [int]$Index = $response.RawContent.IndexOf("`r`n`r`n") + 4;

            [string]$CAContent = $response.RawContent.SubString(
                $Index,
                $response.RawContent.Length - $Index
            );
            Set-Content -Path (Join-Path $Desination -ChildPath 'ca.crt') -Value $CAContent;
            Write-IcingaConsoleNotice ([string]::Format('Downloaded ca.crt from "{0}" to "{1}', $CAPath, $Desination))
        } catch {
            Write-IcingaConsoleError 'Failed to load any provided ca.crt ressource';
            return $FALSE;
        }
    }

    return $TRUE;
}

Export-ModuleMember -Function @('Install-IcingaAgentCertificates');