Aberus.AWS.Tools.EKS.KubeConfig.psm1
function Update-EKSKubeConfig { <# .SYNOPSIS Updates the kubeconfig file for an EKS cluster. .DESCRIPTION This function updates the kubeconfig file for an Amazon EKS cluster, allowing users to interact with the cluster using kubectl. .PARAMETER Name The name of the EKS cluster to update the kubeconfig for. .PARAMETER KubeConfigPath The path to the kubeconfig file. If not specified, defaults to `$HOME\.kube\config`. .PARAMETER RoleArn The ARN of the IAM role to assume for accessing the EKS cluster. .PARAMETER Alias An alias for the context in the kubeconfig file. If not specified, defaults to the cluster ARN. .PARAMETER UserAlias An alias for the user in the kubeconfig file. If not specified, defaults to the cluster ARN. .PARAMETER EndpointUrl The endpoint to make the call against. <b>Note:</b> This parameter is primarily for internal AWS use and is not required/should not be specified for normal usage. The cmdlets normally determine which endpoint to call based on the region specified to the -Region parameter or set as default in the shell (via Set-DefaultAWSRegion). Only specify this parameter if you must direct the call to a specific custom endpoint. .PARAMETER Region The system name of an AWS region or an AWSRegion instance. This governs the endpoint that will be used when calling service operations. Note that the AWS resources referenced in a call are usually region-specific. .PARAMETER AccessKey The AWS access key for the user account. This can be a temporary access key if the corresponding session token is supplied to the -SessionToken parameter. .PARAMETER SecretKey The AWS secret key for the user account. This can be a temporary secret key if the corresponding session token is supplied to the -SessionToken parameter. .PARAMETER SessionToken The session token if the access and secret keys are temporary session-based credentials. .PARAMETER ProfileName The user-defined name of an AWS credentials or SAML-based role profile containing credential information. The profile is expected to be found in the secure credential file shared with the AWS SDK for .NET and AWS Toolkit for Visual Studio. You can also specify the name of a profile stored in the .ini-format credential file used with the AWS CLI and other AWS SDKs. .PARAMETER ProfileLocation Used to specify the name and location of the ini-format credential file (shared with the AWS CLI and other AWS SDKs) If this optional parameter is omitted this cmdlet will search the encrypted credential file used by the AWS SDK for .NET and AWS Toolkit for Visual Studio for the 'default' and 'AWS PS Default' profiles. If the profiles are not found then the cmdlet will search in the ini-format credential file at the default location: (user's home directory)\.aws\credentials. If this parameter is specified then this cmdlet will only search the ini-format credential file at the location given. As the current folder can vary in a shell or during script execution it is advised that you use specify a fully qualified path instead of a relative path. .PARAMETER Credential An AWSCredentials object instance containing access and secret key information, and optionally a token for session-based credentials. .PARAMETER NetworkCredential Used with SAML-based authentication when ProfileName references a SAML role profile. Contains the network credentials to be supplied during authentication with the configured identity provider's endpoint. This parameter is not required if the user's default network identity can or should be used during authentication. .EXAMPLE Update-EKSKubeConfig -Name my-eks-cluster -KubeConfigPath "C:\path\to\config" -RoleArn "arn:aws:iam::123456789012:role/EKS-Role" -Alias my-cluster-alias -UserAlias my-user-alias -Region us-west-2 Context Path ------- ---- my-cluster-alias C:\path\to\config .EXAMPLE Update-EKSKubeConfig -Region eu-west-1 -Name my-eks-cluster -ProfileName user1 Context Path ------- ---- arn:aws:eks:us-west-2:012345678910:cluster/example /Users/ericn/.kube/config C:\path\to\config #> [OutputType([PSCustomObject])] param( [Parameter(Position = 0, ValueFromPipelineByPropertyName = $true, ValueFromPipeline = $true, Mandatory = $true)] [string]$Name, [Parameter(ValueFromPipelineByPropertyName = $true)] [string]$KubeConfigPath, [Parameter(ValueFromPipelineByPropertyName = $true)] [string]$RoleArn, [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $false)] [string]$Alias = $null, [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $false)] [string]$UserAlias = $null, [Parameter(ValueFromPipelineByPropertyName = $true)] [Amazon.EKS.AmazonEKSConfig]$ClientConfig, [Parameter(ValueFromPipelineByPropertyName = $true)] [string]$EndpointUrl, [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)] [ArgumentCompleter( { param ($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) $regionHash = @{ } $regions = [Amazon.RegionEndpoint]::EnumerableAllRegions foreach ($r in $regions) { $regionHash.Add($r.SystemName, $r.DisplayName) } $regionHash.Keys | Sort-Object | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object { New-Object System.Management.Automation.CompletionResult $_, $_, 'ParameterValue', $regionHash[$_] } } )] [Alias("RegionToCall")] [object]$Region, [Alias("AK")] [Parameter(ValueFromPipelineByPropertyName = $true)] [string]$AccessKey, [Alias("SK", "SecretAccessKey")] [Parameter(ValueFromPipelineByPropertyName = $true)] [string]$SecretKey, [Alias("ST")] [Parameter(ValueFromPipelineByPropertyName = $true)] [string]$SessionToken, [ArgumentCompleter( { param ($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) # allow for new user with no profiles set up yet $profiles = Get-AWSCredentials -ListProfileDetail | Select-Object -expandproperty ProfileName if ($profiles) { $profiles | Sort-Object | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object { New-Object System.Management.Automation.CompletionResult $_, $_, 'ParameterValue', $_ } } } )] [Parameter(ValueFromPipelineByPropertyName = $true)] [Alias("StoredCredentials", "AWSProfileName")] [string]$ProfileName, [Parameter(ValueFromPipelineByPropertyName = $true)] [Alias("AWSProfilesLocation", "ProfilesLocation")] [string]$ProfileLocation, [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [Amazon.Runtime.AWSCredentials]$Credential, [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [System.Management.Automation.PSCredential]$NetworkCredential ) # Build parameter hashtable for Get-EksCluster $eksClusterParams = @{ Name = $Name } if ($AccessKey) { $eksClusterParams.Add("AccessKey", $AccessKey) } if ($SecretKey) { $eksClusterParams.Add("SecretKey", $SecretKey) } if ($SessionToken) { $eksClusterParams.Add("SessionToken", $SessionToken) } if ($ClientConfig) { $eksClusterParams.Add("ClientConfig", $ClientConfig) } if ($Credential) { $eksClusterParams.Add("Credential", $Credential) } if ($EndpointUrl) { $eksClusterParams.Add("EndpointUrl", $EndpointUrl) } if ($NetworkCredential) { $eksClusterParams.Add("NetworkCredential", $NetworkCredential) } if ($ProfileLocation) { $eksClusterParams.Add("ProfileLocation", $ProfileLocation) } if ($ProfileName) { $eksClusterParams.Add("ProfileName", $ProfileName) } if ($Region) { $eksClusterParams.Add("Region", $Region) } # Get EKS Cluster information try { $eksCluster = Get-EksCluster @eksClusterParams } catch { Write-Error -Exception $_.Exception -Category $_.CategoryInfo.Category -TargetObject $_.TargetObject return } if ($null -eq $eksCluster) { Write-Error "EKS Cluster with name '$Name' not found." return } if (-not $KubeConfigPath) { $KubeConfigPath = Join-Path -Path "$HOME" -ChildPath "\.kube\config" } if (-not (Test-Path -PathType Leaf $KubeConfigPath)) { New-Item -ItemType File -Path $KubeConfigPath } # Load existing kubeconfig; create new if not exists if (ValidateKubeConfigFile $kubeConfigPath) { $kubeConfig = ConvertFrom-Yaml (Get-Content $kubeConfigPath -Raw) -Ordered if (-not $kubeConfig.clusters) { Write-Verbose "The 'clusters' field is missing in the KubeConfig file." $kubeConfig.clusters = New-Object 'Collections.Generic.List[System.Object]' } if (-not $kubeConfig.contexts) { Write-Verbose "The 'contexts' field is missing in the KubeConfig file." $kubeConfig.contexts = New-Object 'Collections.Generic.List[System.Object]' } if (-not $kubeConfig.users) { Write-Verbose "The 'users' field is missing in the KubeConfig file." $kubeConfig.users = New-Object 'Collections.Generic.List[System.Object]' } if (-not $kubeConfig.'current-context') { Write-Verbose "The 'current-context' field is missing in the KubeConfig file." } } else { $kubeConfig = [ordered]@{ apiVersion = 'v1' clusters = New-Object 'Collections.Generic.List[System.Object]' contexts = New-Object 'Collections.Generic.List[System.Object]' 'current-context' = '' kind = 'Config' preferences = @{} users = New-Object 'Collections.Generic.List[System.Object]' } } if ($Alias) { $contextName = $Alias } else { $contextName = $EksCluster.Arn } if ($UserAlias) { $userName = $UserAlias } else { $userName = $EksCluster.Arn } # Update or add cluster UpdateCluster -EksCluster $EksCluster -KubeConfig ([ref]$kubeConfig) # Update or add user UpdateUser -EksCluster $EksCluster -UserName $userName -KubeConfig ([ref]$kubeConfig) -ProfileName $ProfileName # Update or add context UpdateContext -ClusterName $EksCluster.Arn -ContexName $contextName -UserName $userName -KubeConfig ([ref]$kubeConfig) # Update the current context (optional) $kubeConfig.'current-context' = $contextName # Save updated kubeconfig $kubeConfig | ConvertTo-Yaml | Set-Content $kubeConfigPath [PSCustomObject]@{ Context = $contextName Path = $kubeConfigPath } } function ValidateKubeConfigFile([string]$kubeConfigPath) { # Check if the kubeconfig file exists if (-not (Test-Path $kubeConfigPath)) { Write-Verbose "KubeConfig file does not exist at path: $kubeConfigPath" return $false } # Check if the kubeconfig file is not empty if ((Get-Item $kubeConfigPath).Length -eq 0) { Write-Verbose "KubeConfig file is empty: $kubeConfigPath" return $false } try { # Attempt to parse the kubeconfig file as YAML Get-Content $kubeConfigPath -Raw | ConvertFrom-Yaml | Out-Null } catch { Write-Verbose "Failed to parse KubeConfig file as YAML: $_" return $false } # If all checks pass return $true } function UpdateCluster([PSCustomObject]$EksCluster, [ref]$KubeConfig) { # Extract cluster details $clusterName = $EksCluster.Arn $endpoint = $EksCluster.Endpoint $certificateAuthorityData = $EksCluster.CertificateAuthority.Data # Check if the cluster already exists in kubeconfig $clusterIndex = $KubeConfig.Value.clusters.FindIndex({ $args.name -eq $clusterName }) $clusterEntry = [ordered]@{ cluster = [ordered]@{ 'certificate-authority-data' = $certificateAuthorityData server = $endpoint } name = $clusterName } if ($clusterIndex -ge 0) { # Update existing cluster entry $KubeConfig.Value.clusters[$clusterIndex] = $clusterEntry } else { # Add new cluster entry $KubeConfig.Value.clusters += $clusterEntry } } function UpdateUser([PSCustomObject]$EksCluster, [ref]$KubeConfig, [string]$ProfileName, [string]$UserName) { $outpostConfig = $EksCluster.OutpostConfig $region = $EksCluster.Arn.Split(":")[3] if ($outpostConfig) { #$clusterIdentificationParameter = "--cluster-id" $clusterIdentificationValue = $EksCluster.Id } else { #$clusterIdentificationParameter = "--cluster-name" $clusterIdentificationValue = $EksCluster.Name } # Check if user entry exists and update if necessary $userIndex = $KubeConfig.Value.users.FindIndex({ $args.name -eq $UserName }) # Add new user entry $userEntry = [ordered]@{ name = $userName user = [ordered]@{ exec = [ordered]@{ apiVersion = 'client.authentication.k8s.io/v1beta1' args = @( '-command', "&{ &'Get-EKSToken' -ClusterNameOrId $($clusterIdentificationValue) -ProfileName $($ProfileName) -Region $($region)}" ) command = 'pwsh' interactiveMode = 'IfAvailable' provideClusterInfo = $False } } } if ($ProfileName) { $userEntry.user.exec.env = @( [ordered]@{ name = 'AWS_PROFILE' value = $ProfileName } ) } if ($userIndex -ge 0) { $KubeConfig.Value.users[$userIndex] = $userEntry } else { # Add new user entry $KubeConfig.Value.users += $userEntry } } function UpdateContext([string]$ClusterName, [string]$ContexName, [ref]$KubeConfig, [string]$UserName) { # Check if context entry exists and update if necessary $contextIndex = $KubeConfig.Value.contexts.FindIndex({ $args.name -eq $ContexName }) $contextEntry = [ordered]@{ context = [ordered]@{ cluster = $ClusterName user = $UserName } name = $ContexName } if ($contextIndex -ge 0) { # Update context configuration if needed $KubeConfig.Value.contexts[$contextIndex] = $contextEntry } else { # Add new context entry $KubeConfig.Value.contexts += $contextEntry } } function Get-EKSToken { param( [Parameter(Mandatory = $true)] [string]$ClusterNameOrId, [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)] [ArgumentCompleter( { param ($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) $regionHash = @{ } $regions = [Amazon.RegionEndpoint]::EnumerableAllRegions foreach ($r in $regions) { $regionHash.Add($r.SystemName, $r.DisplayName) } $regionHash.Keys | Sort-Object | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object { New-Object System.Management.Automation.CompletionResult $_, $_, 'ParameterValue', $regionHash[$_] } } )] [Alias("RegionToCall")] [object]$Region, [ArgumentCompleter( { param ($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) # allow for new user with no profiles set up yet $profiles = Get-AWSCredentials -ListProfileDetail | Select-Object -expandproperty ProfileName if ($profiles) { $profiles | Sort-Object | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object { New-Object System.Management.Automation.CompletionResult $_, $_, 'ParameterValue', $_ } } } )] [Parameter(ValueFromPipelineByPropertyName = $true)] [Alias("StoredCredentials", "AWSProfileName")] [string]$ProfileName, [Parameter(ValueFromPipelineByPropertyName = $true)] [Alias("AWSProfilesLocation", "ProfilesLocation")] [string]$ProfileLocation ) if (-not $Region) { $Region = Get-DefaultAWSRegion if (-not $Region) { Write-Error -Message "No region specified or obtained from persisted/shell defaults." -Category InvalidOperation return } } $region = [Amazon.RegionEndpoint]::GetBySystemName($Region) $credentials = Get-AwsCredential -ProfileName $ProfileName # Create the STS client configuration $config = New-Object Amazon.SecurityToken.AmazonSecurityTokenServiceConfig $config.RegionEndpoint = $region # Create the GetCallerIdentity request $getCallerIdentityRequest = [Amazon.SecurityToken.Model.GetCallerIdentityRequest]::new() $marshaller = [Amazon.SecurityToken.Model.Internal.MarshallTransformations.GetCallerIdentityRequestMarshaller]::new() $request = $marshaller.Marshall($getCallerIdentityRequest) # $request = [Amazon.Runtime.Internal.DefaultRequest]::new($getCallerIdentityRequest, $config.AuthenticationServiceName) # $request.Parameters.Add("Action", "GetCallerIdentity") # $request.Parameters.Add("Version", "2011-06-15") $request.UseQueryString = $true $request.HttpMethod = "GET" $request.Endpoint = [UriBuilder]::new([Uri]::UriSchemeHttps, $config.RegionEndpoint.GetEndpointForService($config.AuthenticationServiceName).Hostname).Uri $expirationTime = New-TimeSpan -Seconds 60 $request.Parameters["X-Amz-Expires"] = [int]$expirationTime.TotalSeconds.ToString([System.Globalization.CultureInfo]::InvariantCulture) # Get credentials (assuming credentials is defined) $immutableCredentials = $credentials.GetCredentials() if ($immutableCredentials.UseToken) { $request.Parameters["X-Amz-Security-Token"] = $immutableCredentials.Token } $request.Headers["x-k8s-aws-id"] = $ClusterNameOrId # Sign the request $signingResult = [Amazon.Runtime.Internal.Auth.AWS4PreSignedUrlSigner]::SignRequest( $request, $config, [Amazon.Runtime.Internal.Util.RequestMetrics]::new(), $immutableCredentials.AccessKey, $immutableCredentials.SecretKey, $config.AuthenticationServiceName, $Region.SystemName ) # Calculate token expiration $tokenExpiration = $signingResult.DateTime.AddMinutes(14) # Compose the URL $authorization = "&" + $signingResult.ForQueryParameters $url = [Amazon.Runtime.AmazonServiceClient]::ComposeUrl($request).AbsoluteUri + $authorization # Output the URL # Write-Output ([System.Web.HttpUtility]::UrlDecode($url)) $Bytes = [System.Text.Encoding]::UTF8.GetBytes($url) $EncodedText = [Convert]::ToBase64String($Bytes) $expirationTimestamp = [DateTime]::new($tokenExpiration.Year, $tokenExpiration.Month, $tokenExpiration.Day, $tokenExpiration.Hour, $tokenExpiration.Minute, $tokenExpiration.Second, $tokenExpiration.Kind) $json = [PSCustomObject]@{ kind = 'ExecCredential' apiVersion = 'client.authentication.k8s.io/v1beta1' spec = @{} status = @{ expirationTimestamp = $expirationTimestamp #"2024-08-22T12:09:49Z" token = 'k8s-aws-v1.' + $EncodedText.Replace("=", "") } } ConvertTo-Json $json } |