Public/ssh-keypair.ps1

function Get-CSSSHKeyPair {
    <#
    .SYNOPSIS
        Lists registered SSH key pairs.

    .DESCRIPTION
        Wraps listSSHKeyPairs. CloudStack stores only the public key and its
        fingerprint; the private key is shown once, by New-CSSSHKeyPair.

    .PARAMETER Name
        Filter by key pair name

    .PARAMETER Id
        Filter by key pair ID

    .PARAMETER Fingerprint
        Filter by public key fingerprint

    .PARAMETER Keyword
        Filter by keyword

    .PARAMETER Account
        Filter by account name. Must be used with -DomainId.

    .PARAMETER DomainId
        Filter by domain ID

    .PARAMETER ProjectId
        Filter by project ID (-1 for all projects)

    .PARAMETER IsRecursive
        With -DomainId, also include key pairs in subdomains

    .PARAMETER ListAll
        List every key pair the caller is allowed to see

    .PARAMETER Page
        Page number of results to return

    .PARAMETER PageSize
        Number of results per page

    .EXAMPLE
        Get-CSSSHKeyPair
        Lists your key pairs.

    .EXAMPLE
        Get-CSSSHKeyPair -Name 'ops-laptop'
        Gets one key pair by name.

    .EXAMPLE
        Get-CSSSHKeyPair -ListAll | Group-Object fingerprint | Where-Object Count -gt 1
        Finds the same public key registered more than once.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Position = 0)]
        [string]$Name,

        [string]$Id,

        [string]$Fingerprint,

        [string]$Keyword,

        [string]$Account,

        [string]$DomainId,

        [string]$ProjectId,

        [switch]$IsRecursive,

        [switch]$ListAll,

        [int]$Page,

        [int]$PageSize
    )

    if ($PSBoundParameters.ContainsKey('Account') -and -not $PSBoundParameters.ContainsKey('DomainId')) {
        throw 'DomainId is required when Account is specified.'
    }
    $apiParams = @{}
    Add-CSOptionalParameter -ApiParameters $apiParams -BoundParameters $PSBoundParameters -Map ([ordered]@{
        Name = 'name'; Id = 'id'; Fingerprint = 'fingerprint'; Keyword = 'keyword'; Account = 'account'
        DomainId = 'domainid'; ProjectId = 'projectid'; IsRecursive = 'isrecursive'; ListAll = 'listall'
        Page = 'page'; PageSize = 'pagesize'
    })
    ConvertFrom-CSResponse -Response (Invoke-CSApiRequest -Command 'listSSHKeyPairs' -Parameters $apiParams) -Command 'listSSHKeyPairs'
}

function New-CSSSHKeyPair {
    <#
    .SYNOPSIS
        Has CloudStack generate a new SSH key pair.

    .DESCRIPTION
        Wraps createSSHKeyPair. CloudStack generates the key pair, keeps the public
        key, and returns the private key in the 'privatekey' property. That is the
        only time the private key is available, so save it straight away. To use a
        key you already have, use Register-CSSSHKeyPair instead.

    .PARAMETER Name
        The name of the key pair

    .PARAMETER Account
        Account that will own the key pair. Must be used with -DomainId.

    .PARAMETER DomainId
        Domain of the owning account

    .PARAMETER ProjectId
        Project that will own the key pair

    .EXAMPLE
        (New-CSSSHKeyPair -Name 'build-agents').privatekey | Set-Content -Path ./build-agents.pem -NoNewline
        Creates a key pair and saves the private key. On Linux/macOS, run
        'chmod 600 ./build-agents.pem' before using it with ssh.

    .EXAMPLE
        New-CSSSHKeyPair -Name 'build-agents' -Account 'engineering' -DomainId domain-uuid
        Creates a key pair owned by another account.
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Low')]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$Name,

        [string]$Account,

        [string]$DomainId,

        [string]$ProjectId
    )

    if ($PSBoundParameters.ContainsKey('Account') -and -not $PSBoundParameters.ContainsKey('DomainId')) {
        throw 'DomainId is required when Account is specified.'
    }
    $apiParams = @{ name = $Name }
    Add-CSOptionalParameter -ApiParameters $apiParams -BoundParameters $PSBoundParameters -Map ([ordered]@{
        Account = 'account'; DomainId = 'domainid'; ProjectId = 'projectid'
    })
    if ($PSCmdlet.ShouldProcess("SSH key pair $Name", 'Create')) {
        ConvertFrom-CSResponse -Response (Invoke-CSApiRequest -Command 'createSSHKeyPair' -Parameters $apiParams) -Command 'createSSHKeyPair'
    }
}

function Register-CSSSHKeyPair {
    <#
    .SYNOPSIS
        Registers an existing SSH public key with CloudStack.

    .DESCRIPTION
        Wraps registerSSHKeyPair. Pass the key text with -PublicKey, or a .pub file
        with -PublicKeyPath. The key pair can then be used with New-CSVM -KeyPair
        or Reset-CSVMSSHKey.

    .PARAMETER Name
        The name to register the key under

    .PARAMETER PublicKey
        The public key text, e.g. 'ssh-ed25519 AAAA... user@host'

    .PARAMETER PublicKeyPath
        Path to a public key file, e.g. ~/.ssh/id_ed25519.pub

    .PARAMETER Account
        Account that will own the key pair. Must be used with -DomainId.

    .PARAMETER DomainId
        Domain of the owning account

    .PARAMETER ProjectId
        Project that will own the key pair

    .EXAMPLE
        Register-CSSSHKeyPair -Name 'ops-laptop' -PublicKeyPath ~/.ssh/id_ed25519.pub
        Registers your own public key.

    .EXAMPLE
        Register-CSSSHKeyPair -Name 'ci' -PublicKey $env:CI_SSH_PUBLIC_KEY -ProjectId project-uuid
        Registers a public key held in an environment variable for a project.
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Low', DefaultParameterSetName = 'Text')]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$Name,

        [Parameter(Mandatory = $true, ParameterSetName = 'Text')]
        [string]$PublicKey,

        [Parameter(Mandatory = $true, ParameterSetName = 'Path')]
        [string]$PublicKeyPath,

        [string]$Account,

        [string]$DomainId,

        [string]$ProjectId
    )

    if ($PSBoundParameters.ContainsKey('Account') -and -not $PSBoundParameters.ContainsKey('DomainId')) {
        throw 'DomainId is required when Account is specified.'
    }
    if ($PSCmdlet.ParameterSetName -eq 'Path') {
        $resolvedPath = (Resolve-Path -Path $PublicKeyPath -ErrorAction Stop).ProviderPath
        $PublicKey = ([System.IO.File]::ReadAllText($resolvedPath)).Trim()
        # Guard against the classic mistake of pointing at the private key file.
        if ($PublicKey -match 'PRIVATE KEY') {
            throw "'$PublicKeyPath' holds a private key. Pass the matching .pub file instead."
        }
    }
    if ([string]::IsNullOrWhiteSpace($PublicKey)) {
        throw 'The public key is empty.'
    }

    $apiParams = @{ name = $Name; publickey = $PublicKey }
    Add-CSOptionalParameter -ApiParameters $apiParams -BoundParameters $PSBoundParameters -Map ([ordered]@{
        Account = 'account'; DomainId = 'domainid'; ProjectId = 'projectid'
    })
    if ($PSCmdlet.ShouldProcess("SSH key pair $Name", 'Register')) {
        ConvertFrom-CSResponse -Response (Invoke-CSApiRequest -Command 'registerSSHKeyPair' -Parameters $apiParams) -Command 'registerSSHKeyPair'
    }
}

function Remove-CSSSHKeyPair {
    <#
    .SYNOPSIS
        Deletes an SSH key pair.

    .DESCRIPTION
        Wraps deleteSSHKeyPair. Key pairs are deleted by name, and names are only
        unique within an account, so a piped key pair also passes its account,
        domainid and projectid. That way an admin deleting another account's key
        cannot hit a same-named key of their own. VMs that already use the key
        keep it in their authorized_keys.

    .PARAMETER Name
        The name of the key pair (binds from a piped key pair's name)

    .PARAMETER Account
        The account that owns the key pair. Must be used with -DomainId. Binds from
        a piped key pair.

    .PARAMETER DomainId
        The domain of the owning account. Binds from a piped key pair.

    .PARAMETER ProjectId
        The project that owns the key pair. Binds from a piped key pair.

    .EXAMPLE
        Remove-CSSSHKeyPair -Name 'old-laptop'
        Deletes one of your key pairs after prompting for confirmation.

    .EXAMPLE
        Get-CSSSHKeyPair -ListAll -Keyword 'temp-' | Remove-CSSSHKeyPair -Confirm:$false
        Deletes every temporary key pair, whichever account owns it.
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipelineByPropertyName = $true)]
        [string]$Name,

        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [string]$Account,

        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [string]$DomainId,

        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [string]$ProjectId
    )

    process {
        $apiParams = @{ name = $Name }
        # A project's key pair is listed with the project's internal account too,
        # and CloudStack rejects account and projectid together, so the project wins.
        if (-not [string]::IsNullOrWhiteSpace($ProjectId)) {
            $apiParams['projectid'] = $ProjectId
            $owner = "project $ProjectId"
        }
        elseif (-not [string]::IsNullOrWhiteSpace($Account)) {
            if ([string]::IsNullOrWhiteSpace($DomainId)) { throw 'DomainId is required when Account is specified.' }
            $apiParams['account'] = $Account
            $apiParams['domainid'] = $DomainId
            $owner = "account $Account"
        }
        else {
            if (-not [string]::IsNullOrWhiteSpace($DomainId)) { $apiParams['domainid'] = $DomainId }
            $owner = 'your account'
        }
        if ($PSCmdlet.ShouldProcess("SSH key pair $Name ($owner)", 'Delete')) {
            ConvertFrom-CSResponse -Response (Invoke-CSApiRequest -Command 'deleteSSHKeyPair' -Parameters $apiParams) -Command 'deleteSSHKeyPair'
        }
    }
}