Public/cloud-securitygroup.ps1

function Get-CloudSecurityGroup {
    <#
    .SYNOPSIS
        Returns security groups from the Cloud Server.
     
    .DESCRIPTION
        Retrieves a list of security groups. Automatically handles token refresh.
     
    .PARAMETER Name
        Optional. Filter by security group name.
     
    .EXAMPLE
        # List all security groups
        Get-CloudSecurityGroup
         
    .EXAMPLE
        # List security groups matching a name string
        Get-CloudSecurityGroup -Name "WebServers"
 
    .EXAMPLE
        # Get a specific security group by ID
        Get-CloudSecurityGroup -ID 561
    #>

    
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $false)]
        [string]$Name,
        
        [Parameter(Mandatory = $false)]
        [int]$ID
    )
    
    # Build the URI - adjust this to match your actual API endpoint
    $uri = "$($script:CloudConnection.BaseUri)/manifold-api/v2/cloud/security-group"
    
    # Use the helper function which handles token refresh automatically
    $response = Invoke-CloudApiRequest -Uri $uri -Method Get
    
    # Extract security groups from the response
    $securitygroups = $response.security_group
 
    # Filter by name if specified
    if ($Name) {
        $securitygroups = $securitygroups | Where-Object { $_.name -like "*$Name*" }
    }
    
    # Filter by ID if specified - use PSBoundParameters to check if parameter was provided
    elseif ($PSBoundParameters.ContainsKey('ID')) {
            $securitygroups = $securitygroups | Where-Object { ($_.id -eq $ID) -and ($null -ne $_.id) }
    }
    
    return $securitygroups
}

function New-CloudSecurityGroup {
    <#
    .SYNOPSIS
        Creates a new security group on the Cloud Server.
     
    .DESCRIPTION
        Creates a new security group with specified name, description, and rules.
        Automatically handles token refresh.
     
    .PARAMETER Name
        Required. Name of the new security group.
     
    .PARAMETER Description
        Optional. Description for the security group.
     
    .PARAMETER Rules
        Optional. Array of rule hashtables. Each rule should contain:
        - Protocol: ALL, TCP, UDP, ICMP, ICMPV6, IPSEC
        - RuleType: INBOUND, OUTBOUND
        - IP: Optional IP address (e.g., "192.168.1.0")
        - Size: Optional network size (e.g., "24")
        - Range: Optional port range (e.g., "80:80", "1000:2000")
        - IcmpType: Optional ICMP type for ICMP protocol
     
    .EXAMPLE
        # Create basic security group
        New-CloudSecurityGroup -Name "WebServer-SG" -Description "Security group for web servers"
         
    .EXAMPLE
        # Create security group with HTTP and HTTPS rules
        $rules = @(
            @{ Protocol = "ALL"; RuleType = "OUTBOUND" }
            @{ Protocol = "TCP"; RuleType = "INBOUND"; Range = "80:80" }
            @{ Protocol = "TCP"; RuleType = "INBOUND"; Range = "443:443" }
        )
        New-CloudSecurityGroup -Name "WebServer-SG" -Description "Web server access" -Rules $rules
         
    .EXAMPLE
        # Create security group allowing SSH from specific subnet
        $rules = @(
            @{ Protocol = "ALL"; RuleType = "OUTBOUND" }
            @{ Protocol = "TCP"; RuleType = "INBOUND"; Range = "22:22"; IP = "10.0.0.0"; Size = "24" }
        )
        New-CloudSecurityGroup -Name "SSH-Admin-SG" -Description "SSH access from admin subnet" -Rules $rules
         
    .EXAMPLE
        # Create security group with ICMP ping allowed
        $rules = @(
            @{ Protocol = "ALL"; RuleType = "OUTBOUND" }
            @{ Protocol = "ICMP"; RuleType = "INBOUND"; IcmpType = "8" }
        )
        New-CloudSecurityGroup -Name "Monitoring-SG" -Description "Allow ping for monitoring" -Rules $rules
         
    .EXAMPLE
        # Create security group with multiple port ranges
        $rules = @(
            @{ Protocol = "ALL"; RuleType = "OUTBOUND" }
            @{ Protocol = "TCP"; RuleType = "INBOUND"; Range = "80:80" }
            @{ Protocol = "TCP"; RuleType = "INBOUND"; Range = "443:443" }
            @{ Protocol = "TCP"; RuleType = "INBOUND"; Range = "8080:8090" }
        )
        New-CloudSecurityGroup -Name "AppServer-SG" -Rules $rules
    #>

    
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Name,
        
        [Parameter(Mandatory = $false)]
        [string]$Description,
        
        [Parameter(Mandatory = $false)]
        [array]$Rules
    )
    
    # Build the template string
    $templateParts = @()
    
    # Name is required (no quotes)
    $templateParts += "NAME = $Name"
    
    # Add description if provided (with quotes)
    if ($PSBoundParameters.ContainsKey('Description')) {
        $templateParts += "DESCRIPTION = `"$Description`""
    }
    
    # Add rules if specified
    if ($PSBoundParameters.ContainsKey('Rules') -and $Rules.Count -gt 0) {
        foreach ($rule in $Rules) {
            $ruleParts = @()
            
            # Protocol is required
            if ($rule.Protocol) {
                $ruleParts += "PROTOCOL = $($rule.Protocol)"
            } else {
                Write-Warning "Rule missing Protocol, skipping rule"
                continue
            }
            
            # Rule type is required
            if ($rule.RuleType) {
                $ruleParts += "RULE_TYPE = $($rule.RuleType)"
            } else {
                Write-Warning "Rule missing RuleType, skipping rule"
                continue
            }
            
            # Optional: IP address
            if ($rule.IP) {
                $ruleParts += "IP = `"$($rule.IP)`""
            }
            
            # Optional: Network size
            if ($rule.Size) {
                $ruleParts += "SIZE = `"$($rule.Size)`""
            }
            
            # Optional: Port range
            if ($rule.Range) {
                $ruleParts += "RANGE = `"$($rule.Range)`""
            }
            
            # Optional: ICMP type
            if ($rule.IcmpType) {
                $ruleParts += "ICMP_TYPE = `"$($rule.IcmpType)`""
            }
            
            $templateParts += "RULE = [ $($ruleParts -join ', ') ]"
        }
    }
    
    # Join all parts with newlines
    $templateString = $templateParts -join "`n"
    
    # Build the configuration - template goes in "data" field per API spec
    $config = [PSCustomObject]@{
        template = $templateString
    }
    
    # Build the URI
    $uri = "$($script:CloudConnection.BaseUri)/manifold-api/v2/cloud/security-group"
    
    Write-Verbose "Creating security group '$Name'"
    Write-Verbose "Request body: $($config | ConvertTo-Json -Compress)"
    Write-Verbose "Template:`n$templateString"
    
    try {
        # Use the helper function which handles token refresh automatically
        $response = Invoke-CloudApiRequest -Uri $uri -Method Post -Body $config
        
        Write-Host "Security Group '$Name' created successfully (ID: $($response.securitygroup))." -ForegroundColor Green
        return $response
    }
    catch {
        Write-Error "Failed to create security group: $_"
        throw
    }
}

function Update-CloudSecurityGroupOwner {
    <#
    .SYNOPSIS
        Updates the ownership of a security group in the Cloud Server.
     
    .DESCRIPTION
        Changes the user and/or group ownership of a security group. Prompts for confirmation.
     
    .PARAMETER ID
        Required. ID of the security group.
     
    .PARAMETER UserID
        Required. ID of the user to own the security group.
         
    .PARAMETER GroupID
        Optional. ID of the group to own the security group.
         
    .EXAMPLE
        # Change the ownership of a security group to a specific user
        Update-CloudSecurityGroupOwner -ID 5 -UserID 2
         
    .EXAMPLE
        # Change the ownership of a security group to a specific user and group
        Update-CloudSecurityGroupOwner -ID 5 -UserID 2 -GroupID 100
         
    .EXAMPLE
        # Change the ownership of a security group to a specific user, bypassing confirmation prompt
        Update-CloudSecurityGroupOwner -ID 5 -UserID 2 -Confirm:$false
    #>

    
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact='High')]
    param(
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [int]$ID,
        
        [Parameter(Mandatory = $true)]
        [Alias('User')]
        [int]$UserID,
                
        [Parameter(Mandatory = $false)]
        [Alias('Group')]
        [int]$GroupID
    )
    
    process {
        # Build the update body
        $body = [PSCustomObject]@{
            user = $UserID
        }
        
        if ($PSBoundParameters.ContainsKey('GroupID')) {
            $body | Add-Member -NotePropertyName 'group' -NotePropertyValue $GroupID
        }
        
        $uri = "$($script:CloudConnection.BaseUri)/manifold-api/v2/cloud/security-group/${ID}/chown"
        
        # Build action description for confirmation
        $actionParts = @("Change owner to User ID $UserID")
        if ($PSBoundParameters.ContainsKey('GroupID')) {
            $actionParts += "Group ID $GroupID"
        }
        $actionDescription = $actionParts -join " and "
        write-verbose "Body data: $body"
        # Use the helper function
        Invoke-CloudResourceUpdate `
            -CallerPSCmdlet $PSCmdlet `
            -ResourceType "Security Group" `
            -ID $ID `
            -Uri $uri `
            -Body $body `
            -Action $actionDescription `
            -GetResourceScript { Get-CloudSecurityGroup -ID $ID } `
            -SuccessMessage "Security Group $ID ownership updated successfully."
    }
}

function Update-CloudSecurityGroupPermissions {
    <#
    .SYNOPSIS
        Updates the permissions of a security group in the Cloud Server.
     
    .DESCRIPTION
        Changes the permission of a security group. Prompts for confirmation.
     
    .PARAMETER ID
        Required. ID of the security group.
     
    .PARAMETER OwnerUse
        Optional. True or false, enable OwnerUse
 
    .PARAMETER OwnerManage
        Optional. True or false, enable OwnerManage
 
    .PARAMETER OwnerAdmin
        Optional. True or false, enable OwnerAdmin
 
    .PARAMETER GroupUse
        Optional. True or false, enable GroupUse
         
    .PARAMETER GroupManage
        Optional. True or false, enable GroupManage
         
    .PARAMETER GroupAdmin
        Optional. True or false, enable GroupAdmin
         
    .PARAMETER OtherUse
        Optional. True or false, enable OtherUse
         
    .PARAMETER OtherManage
        Optional. True or false, enable OtherManage
         
    .PARAMETER OtherAdmin
        Optional. True or false, enable OtherManage
         
    .EXAMPLE
        # Give group users permission to use the security group
        Update-CloudSecurityGroupPermissions -ID 35 -GroupUse $true
         
    .EXAMPLE
        # Deny group admin permission to the security group
        Update-CloudSecurityGroupPermissions -ID 35 -GroupAdmin $false
         
    .EXAMPLE
        # Set multiple permissions at once
        Update-CloudSecurityGroupPermissions -ID 35 -GroupUse $true -GroupManage $true -OtherUse $true
    #>


    [CmdletBinding(SupportsShouldProcess, ConfirmImpact='High')]
    param(
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [int]$ID,
        
        [Parameter(Mandatory = $false)]
        [bool]$OwnerUse,
        
        [Parameter(Mandatory = $false)]
        [bool]$OwnerManage,
        
        [Parameter(Mandatory = $false)]
        [bool]$OwnerAdmin,
        
        [Parameter(Mandatory = $false)]
        [bool]$GroupUse,
        
        [Parameter(Mandatory = $false)]
        [bool]$GroupManage,
        
        [Parameter(Mandatory = $false)]
        [bool]$GroupAdmin,
        
        [Parameter(Mandatory = $false)]
        [bool]$OtherUse,
        
        [Parameter(Mandatory = $false)]
        [bool]$OtherManage,
        
        [Parameter(Mandatory = $false)]
        [bool]$OtherAdmin
    )
    
    process {
        # Build the permissions object structure as shown in the API spec
        $permissions = @{}
        
        if ($PSBoundParameters.ContainsKey('OwnerUse')) { $permissions['owner_use'] = $OwnerUse }
        if ($PSBoundParameters.ContainsKey('OwnerManage')) { $permissions['owner_manage'] = $OwnerManage }
        if ($PSBoundParameters.ContainsKey('OwnerAdmin')) { $permissions['owner_admin'] = $OwnerAdmin }
        
        if ($PSBoundParameters.ContainsKey('GroupUse')) { $permissions['group_use'] = $GroupUse }
        if ($PSBoundParameters.ContainsKey('GroupManage')) { $permissions['group_manage'] = $GroupManage }
        if ($PSBoundParameters.ContainsKey('GroupAdmin')) { $permissions['group_admin'] = $GroupAdmin }
        
        if ($PSBoundParameters.ContainsKey('OtherUse')) { $permissions['other_use'] = $OtherUse }
        if ($PSBoundParameters.ContainsKey('OtherManage')) { $permissions['other_manage'] = $OtherManage }
        if ($PSBoundParameters.ContainsKey('OtherAdmin')) { $permissions['other_admin'] = $OtherAdmin }
        
        # Create the body with the permissions nested structure
        $body = @{
            perms = $permissions
        }
        
        $uri = "$($script:CloudConnection.BaseUri)/manifold-api/v2/cloud/security-group/${ID}/chmod"
        
        Invoke-CloudResourceUpdate `
            -CallerPSCmdlet $PSCmdlet `
            -ResourceType "Security Group" `
            -ID $ID `
            -Uri $uri `
            -Body $body `
            -Action "Update permissions" `
            -GetResourceScript { Get-CloudSecurityGroup -ID $ID }
    }
}

function Rename-CloudSecurityGroup {
    <#
    .SYNOPSIS
        Renames a security group on Cloud Server.
     
    .DESCRIPTION
        Renames a security group. Automatically handles token refresh.
     
    .PARAMETER Name
        Required. New name for the security group.
         
    .PARAMETER ID
        Required. ID of the security group security group name.
     
    .EXAMPLE
        # Rename a security group
        Rename-CloudSecurityGroup -ID 561 -Name "Updated Security Group"
    #>

    
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Name,
        
        [Parameter(Mandatory = $true)]
        [int]$ID
    )

$body = @{
name = $Name
}


    # Build the URI - adjust this to match your actual API endpoint
    $uri = "$($script:CloudConnection.BaseUri)/manifold-api/v2/cloud/security-group/${ID}/rename"
    
    # Use the helper function which handles token refresh automatically
    $response = Invoke-CloudApiRequest -Uri $uri -Method PATCH -body $body
    
    # Extract security groups from the response
    $securitygroup = $response.security_group
    
    write-host "Group $securitygroup renamed to $Name"
}

function Copy-CloudSecurityGroup {
    <#
    .SYNOPSIS
        Clone a security group in the Cloud Server.
     
    .DESCRIPTION
        Clones a security group. Automatically handles token refresh.
     
    .PARAMETER Name
        Required. Name of the new security group
         
    .PARAMETER ID
        Required. ID of the the security group you wish to clone
         
    .EXAMPLE
        # Clone the security group with ID 5
        Copy-CloudSecurityGroup -Name "RemoteUsers" -ID 5
    #>

    
    [CmdletBinding(DefaultParameterSetName='Individual')]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Name,
        
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName=$true)]
        [int]$ID
    )

    $sgData = [PSCustomObject]@{
        name=$Name
    }
 
    $uri = "$($script:CloudConnection.BaseUri)/manifold-api/v2/cloud/security-group/${ID}/clone"
    
    Write-Verbose "Request URI: $uri"
    Write-Verbose "SecurityGroup:`n$sgData"
    
    $response = Invoke-CloudApiRequest -Uri $uri -Method POST -Body $sgData
    
    return $response
}

function Update-CloudSecurityGroup {
    <#
    .SYNOPSIS
        Updates a security group on the Cloud Server.
     
    .DESCRIPTION
        Updates a security group's configuration including name, description, and rules.
        You can choose to merge with the existing configuration or replace it entirely.
        Merge is enabled by default. Automatically handles token refresh.
     
    .PARAMETER ID
        Required. ID of the security group to update.
     
    .PARAMETER Name
        Optional. New name for the security group.
     
    .PARAMETER Description
        Optional. Description for the security group.
     
    .PARAMETER Rules
        Optional. Array of rule hashtables. Each rule should contain:
        - Protocol: ALL, TCP, UDP, ICMP, ICMPV6, IPSEC
        - RuleType: INBOUND, OUTBOUND
        - IP: Optional IP address (e.g., "192.168.1.0")
        - Size: Optional network size (e.g., "24")
        - Range: Optional port range (e.g., "80:80", "1000:2000")
        - IcmpType: Optional ICMP type for ICMP protocol
     
    .PARAMETER Merge
        Optional. If true (default), merges with existing configuration. If false, replaces entirely.
        Use -Merge:$false to completely replace the security group configuration.
     
    .EXAMPLE
        # Update security group description
        Update-CloudSecurityGroup -ID 0 -Description "Updated default security group"
         
    .EXAMPLE
        # Add HTTP and HTTPS inbound rules
        $rules = @(
            @{ Protocol = "TCP"; RuleType = "INBOUND"; Range = "80:80" }
            @{ Protocol = "TCP"; RuleType = "INBOUND"; Range = "443:443" }
        )
        Update-CloudSecurityGroup -ID 100 -Rules $rules
         
    .EXAMPLE
        # Allow all traffic from specific subnet
        $rules = @(
            @{ Protocol = "ALL"; RuleType = "INBOUND"; IP = "10.0.0.0"; Size = "24" }
        )
        Update-CloudSecurityGroup -ID 100 -Rules $rules
         
    .EXAMPLE
        # Replace entire security group configuration
        $rules = @(
            @{ Protocol = "ALL"; RuleType = "OUTBOUND" }
            @{ Protocol = "TCP"; RuleType = "INBOUND"; Range = "22:22" }
        )
        Update-CloudSecurityGroup -ID 100 -Name "SSH-Only" -Description "SSH access only" -Rules $rules -Merge:$false
         
    .EXAMPLE
        # Allow ICMP ping
        $rules = @(
            @{ Protocol = "ICMP"; RuleType = "INBOUND"; IcmpType = "8" }
        )
        Update-CloudSecurityGroup -ID 100 -Rules $rules
         
    .EXAMPLE
        # Update without confirmation
        Update-CloudSecurityGroup -ID 100 -Description "New description" -Confirm:$false
    #>

    
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact='High')]
    param(
        [Parameter(Mandatory = $true)]
        [int]$ID,
        
        [Parameter(Mandatory = $false)]
        [string]$Name,
        
        [Parameter(Mandatory = $false)]
        [string]$Description,
        
        [Parameter(Mandatory = $false)]
        [array]$Rules,
        
        [Parameter()]
        [bool]$Merge = $true
    )
    
    # Build the template string - only include specified parameters
    $templateParts = @()
    
    # Only add parameters that were actually specified
    if ($PSBoundParameters.ContainsKey('Name')) {
        $templateParts += "NAME = `"$Name`""
    }
    
    if ($PSBoundParameters.ContainsKey('Description')) {
        $templateParts += "DESCRIPTION = `"$Description`""
    }
    
    # Add rules if specified
        foreach ($rule in $Rules) {
            $ruleParts = @()
            
            # Protocol is required
            if ($rule.Protocol) {
                $ruleParts += "PROTOCOL = $($rule.Protocol)"
            } else {
                Write-Warning "Rule missing Protocol, skipping rule"
                continue
            }
            
            # Rule type is required
            if ($rule.RuleType) {
                $ruleParts += "RULE_TYPE = $($rule.RuleType)"
            } else {
                Write-Warning "Rule missing RuleType, skipping rule"
                continue
            }
            
            # Optional: IP address
            if ($rule.IP) {
                $ruleParts += "IP = `"$($rule.IP)`""
            }
            
            # Optional: Network size
            if ($rule.Size) {
                $ruleParts += "SIZE = `"$($rule.Size)`""
            }
            
            # Optional: Port range
            if ($rule.Range) {
                $ruleParts += "RANGE = `"$($rule.Range)`""
            }
            
            # Optional: ICMP type
            if ($rule.IcmpType) {
                $ruleParts += "ICMP_TYPE = `"$($rule.IcmpType)`""
            }
            
            $templateParts += "RULE = [ $($ruleParts -join ', ') ]"
        }
    
    # Only proceed if there are changes to make
    if ($templateParts.Count -eq 0) {
        Write-Warning "No parameters specified for update. Please specify at least one parameter to update."
        return
    }
    
    # Join all parts with newlines
    $templateString = $templateParts -join "`n"
    
    # Build the request body
    $body = @{
        merge = $Merge
        template = $templateString
    }
    
    # Build the URI
    $uri = "$($script:CloudConnection.BaseUri)/manifold-api/v2/cloud/security-group/${ID}"
    
    Write-Verbose "Request URI: $uri"
    Write-Verbose "Merge: $Merge"
    Write-Verbose "Template:`n$templateString"
    
    try {
        Invoke-CloudResourceUpdate `
            -CallerPSCmdlet $PSCmdlet `
            -ResourceType "Security Group" `
            -ID $ID `
            -Uri $uri `
            -Body $body `
            -Action "Update" `
            -GetResourceScript { Get-CloudSecurityGroup -ID $ID }
        Write-Verbose "Successfully updated security group ID: $ID"
    }
    catch {
        Write-Error "Failed to update security group '$ID': $_"
        throw
    }
}

function Invoke-CloudSecurityGroup {
    <#
    .SYNOPSIS
        Commit security group changes to associated VMs on the Cloud server.
     
    .DESCRIPTION
        Commit security group changes to associated VMs. Automatically handles token refresh.
           
    .PARAMETER ID
        Required. ID of the the security group you wish to commit
         
    .EXAMPLE
        # Clone the security group with ID 5 and prompt for confirmation
        Invoke-CloudSecurityGroup -ID 5
         
    .EXAMPLE
        # Clone the security group with ID 5 and bypass confirmation
        Invoke-CloudSecurityGroup -ID 5 -confirm:$false
    #>

    
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact='High')]
    param(      
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName=$true)]
        [int]$ID
    )

    $body = [PSCustomObject]@{
        all=$True
    }
    
    $uri = "$($script:CloudConnection.BaseUri)/manifold-api/v2/cloud/security-group/${ID}/commit"
    try {
        Invoke-CloudResourceUpdate `
            -CallerPSCmdlet $PSCmdlet `
            -ResourceType "Security Group" `
            -ID $ID `
            -Uri $uri `
            -Body $body `
            -Action "Commit" `
            -GetResourceScript { Get-CloudSecurityGroup -ID $ID }
        Write-Verbose "Successfully updated security group ID: $ID"
    }
    catch {
        Write-Error "Failed to commit security group '$ID': $_"
        throw
    }
}

function Remove-CloudSecurityGroup {
    <#
    .SYNOPSIS
        Removes a security group from the Cloud Server.
     
    .DESCRIPTION
        Removes a security group. Automatically handles token refresh.
     
    .PARAMETER ID
        Required. Security group ID.
     
    .EXAMPLE
        # Remove security group with confirmation prompt (default behavior):
        Remove-CloudSecurityGroup -ID 5
        Confirm
        Are you sure you want to perform this action?
        Performing the operation "Remove Security Group" on target "Security group 5".
        [Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"): Y
 
    .EXAMPLE
        # Remove security group without confirmation prompt:
        Remove-CloudSecurityGroup -ID 5 -Confirm:$false
    #>

    
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact='High')]
    param(
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [int]$ID
    )
    
    process {
        $uri = "$($script:CloudConnection.BaseUri)/manifold-api/v2/cloud/security-group/${ID}"
        
        Invoke-CloudResourceRemoval `
            -CallerPSCmdlet $PSCmdlet `
            -ResourceType "Security group" `
            -ID $ID `
            -Uri $uri `
            -GetResourceScript { Get-CloudSecurityGroup -ID $ID }
    }
}