diff --git a/Convert-PSObjectToHashTable.ps1 b/Convert-PSObjectToHashTable.ps1 new file mode 100644 index 0000000..4d5c214 --- /dev/null +++ b/Convert-PSObjectToHashTable.ps1 @@ -0,0 +1,48 @@ +function Convert-PSObjectToHashTable { + <# + .SYNOPSIS + Converts a PSObject to a hash table. + .DESCRIPTION + Converts a System.Management.Automation.PSObject to a System.Collections.Hashtable. + .PARAMETER InputObject + Specifies the PSObject to send down the pipeline. + .EXAMPLE + Get-Content -Path 'C:\groups.json' -Raw | ConvertFrom-Json | Convert-PSObjectToHashTable + + Gets the content from a JSON file, converts it to a PSObject, and finally to a hash table. + .EXAMPLE + $psObject = Get-ADUser -Identity $env:USERNAME -Properties * | Select-Object -Property Name, Description, UserPrincipalName + Convert-PSObjectToHashTable -InputObject $psObject + + Converts the resulting PSObject from the Select-Object cmdlet into a hash table. + .INPUTS + System.Management.Automation.PSObject + + A PSObject is received by the InputObject parameter. + .OUTPUTS + System.Collections.Hashtable + .LINK + Get-Content + ConvertFrom-Json + #> + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + Param ( + [Parameter( + Position = 0, + Mandatory = $true, + ValueFromPipeline = $true + )][ValidateNotNullOrEmpty()] + [System.Management.Automation.PSObject]$InputObject + ) + + PROCESS { + $hashTable = @{} + + $InputObject.PSObject.Properties | ForEach-Object { + $hashTable.Add($_.Name, $_.Value) + } + + Write-Output -InputObject $hashTable + } +} \ No newline at end of file diff --git a/Get-KubernetesNamespaceMetadata.ps1 b/Get-KubernetesNamespaceMetadata.ps1 new file mode 100644 index 0000000..4386c0b --- /dev/null +++ b/Get-KubernetesNamespaceMetadata.ps1 @@ -0,0 +1,56 @@ +function Get-KubernetesNamespaceMetadata { + <# + .SYNOPSIS + Gets a Kubernetes namespace metadata. + .DESCRIPTION + Obtains a subset of Kubernetes namespace metadata including creation and labels. + .PARAMETER Namespace + The target namespace to obtain metadata from. + .EXAMPLE + Get-KubernetesNamespaceMetadata + + Gets metadata for all Kubernetes namespaces that the authenticated principal has access to. + .EXAMPLE + Get-KubernetesNamespaceMetadata -Namespace "apps" + + Gets metadata for the "apps" Kubernetes namespace. + .EXAMPLE + gknm + + Gets metadata for all Kubernetes namespaces that the authenticated principal has access to. + .EXAMPLE + gknm -n "apps" + + Gets metadata for the "apps" Kubernetes namespace. + #> + [CmdletBinding()] + [Alias('gknm')] + [OutputType([PSCustomObject])] + Param + ( + [Parameter(Mandatory = $false)][Alias('ns', 'n')][String]$Namespace = 'default' + ) + PROCESS { + $NamespaceName = @{Name = "Namespace"; Expression = { $_.name } } + $CreatedOn = @{Name = "CreatedOn"; Expression = { $_.creationTimestamp } } + $Labels = @{Name = "Labels"; Expression = { $_.Labels } } + + try { + if ($PSBoundParameters.ContainsKey("Namespace")) { + $(kubectl get namespaces $Namespace --output=json 2>&1 | ConvertFrom-Json -ErrorAction Stop).items.metadata | Select-Object -Property $NamespaceName, $CreatedOn, $Labels + } + else { + $(kubectl get namespaces --output=json 2>&1 | ConvertFrom-Json -ErrorAction Stop).items.metadata | Select-Object -Property $NamespaceName, $CreatedOn, $Labels + } + } + catch { + [string]$argExceptionMessage = "Unable to obtain namespace metadata." + if ($PSBoundParameters.ContainsKey("Namespace")) { + $argExceptionMessage = "Unable to namespace metadata for the following namespace: $Namespace." + } + + $ArgumentException = [ArgumentException]::new($argExceptionMessage) + Write-Error -Exception $ArgumentException -ErrorAction Stop + } + } +} \ No newline at end of file diff --git a/Set-KubernetesSecretAnnotation.ps1 b/Set-KubernetesSecretAnnotation.ps1 new file mode 100644 index 0000000..b6f3d13 --- /dev/null +++ b/Set-KubernetesSecretAnnotation.ps1 @@ -0,0 +1,70 @@ +function Set-KubernetesSecretAnnotation { + <##> + [CmdletBinding()] + [Alias('sksa')] + [OutputType([void])] + Param + ( + [Parameter(Mandatory = $false)][Alias('ns', 'n')][String]$Namespace = 'default', + + [Parameter(Mandatory = $true)][Alias('s', 'sn')][String]$SecretName, + + [Parameter(Mandatory = $true)][ValidateNotNull()][Alias('an', 'Annotations')][System.Collections.Hashtable]$Annotation + ) + BEGIN { + if (-not(Test-KubernetesNamespaceAccess -Namespace $Namespace)) { + $ArgumentException = [Security.SecurityException]::new("The following namespace was either not found or inaccessible: $Namespace") + Write-Error -Exception $ArgumentException -ErrorAction Stop + } + + if ($(kubectl auth can-i update secret -n $Namespace).ToLower() -ne "yes") { + $SecurityException = [Security.SecurityException]::new("Current context cannot set secret annotations within the $Namespace namespace.") + Write-Error -Exception $SecurityException -ErrorAction Stop + } + } + PROCESS { + if (-not(Test-KubernetesSecretExistence -Namespace $Namespace -SecretName $SecretName)) { + $argExceptionMessage = "The following secret was not found {0}:{1}" -f $Namespace, $SecretName + $ArgumentException = [ArgumentException]::new($argExceptionMessage) + Write-Error -Exception $ArgumentException -ErrorAction Stop + } + + $annotationResults = @() + $Annotation.GetEnumerator() | ForEach-Object { + $annotationKey = $_.Key + $annotationValue = $_.Value + $annotationResult = kubectl annotate secrets $SecretName -n $Namespace "$annotationKey=$annotationValue" --overwrite --output=json | ConvertFrom-Json -Depth 25 -ErrorAction Stop + + $annotationResults += $annotationResult.metadata.annotations + } + + $resultingAnnotationHashtable = $annotationResults | Get-Unique | Convert-PSObjectToHashTable + + [bool]$resultingAnnotationsMatch = $false + $Annotation.GetEnumerator() | ForEach-Object { + if ($resultingAnnotationHashtable.ContainsKey($_.Key)) { + if ($resultingAnnotationHashtable[$_.Key] -eq $Annotation[$_.Key]) { + $resultingAnnotationsMatch = $true + } + else { + $resultingAnnotationsMatch = $false + break + } + } + else { + $resultingAnnotationsMatch = $false + break + } + } + + if ($resultingAnnotationsMatch) { + $verboseMessage = "Annotations successfully set on the secret {0}:{1}" -f $Namespace, $SecretName + Write-Verbose -Message $verboseMessage + } + else { + $argExceptionMessage = "Unable to set annotations on the secret {0}:{1}" -f $Namespace, $SecretName + $ArgumentException = [System.ArgumentException]::new($argExceptionMessage) + Write-Error -Exception $ArgumentException -ErrorAction Stop + } + } +} \ No newline at end of file diff --git a/Test-KubernetesNamespaceAccess.ps1 b/Test-KubernetesNamespaceAccess.ps1 new file mode 100644 index 0000000..03a64bf --- /dev/null +++ b/Test-KubernetesNamespaceAccess.ps1 @@ -0,0 +1,24 @@ +function Test-KubernetesNamespaceAccess { + [CmdletBinding()] + [OutputType([bool])] + Param + ( + [Parameter(Mandatory = $true)][String]$Namespace + ) + PROCESS { + [bool]$namespaceIsAccessible = $false + + try { + $targetNamespace = (kubectl get namespaces $Namespace --output=json 2>&1 | ConvertFrom-Json -ErrorAction Stop).metadata.labels.'kubernetes.io/metadata.name' + + if ($targetNamespace -eq $Namespace) { + $namespaceIsAccessible = $true + } + } + catch { + $namespaceIsAccessible = $false + } + + return $namespaceIsAccessible + } +} \ No newline at end of file diff --git a/Test-KubernetesSecretExistence.ps1 b/Test-KubernetesSecretExistence.ps1 new file mode 100644 index 0000000..bf2e594 --- /dev/null +++ b/Test-KubernetesSecretExistence.ps1 @@ -0,0 +1,29 @@ +function Test-KubernetesSecretExistence { + [CmdletBinding()] + [OutputType([bool])] + Param + ( + [Parameter(Mandatory = $true)][String]$Namespace, + [Parameter(Mandatory = $true)][String]$SecretName + ) + PROCESS { + [bool]$secretIsAccessible = $false + + if (($(kubectl auth can-i get secrets -n $Namespace).ToLower() -eq "yes") -and ($(kubectl auth can-i list secrets -n $Namespace).ToLower() -eq "yes")) { + if (Test-KubernetesNamespaceAccess -Namespace $Namespace) { + try { + $allSecrets = $(kubectl get secrets -n $Namespace --output=json | ConvertFrom-Json).items.metadata.name + + if ($SecretName -in $allSecrets) { + $secretIsAccessible = $true + } + } + catch { + $secretIsAccessible = $false + } + } + } + + return $secretIsAccessible + } +} \ No newline at end of file