Skip to content

Instantly share code, notes, and snippets.

@panasenco
Last active May 28, 2025 00:47
Show Gist options
  • Save panasenco/cf30cb47adb6347bfc74c08746c7f352 to your computer and use it in GitHub Desktop.
Save panasenco/cf30cb47adb6347bfc74c08746c7f352 to your computer and use it in GitHub Desktop.
PowerShell script to add an 'allow' policy to AWS S3 buckets
<#
.Synopsis
Backs up current policies for S3 buckets.
.Description
Saves the current policies for S3 buckets in a file in JSON format.
.Parameter FilePath
Path to file (ideally with .json extension) to save the policies in.
.Parameter AwsProfile
Name of the AWS profile to use.
.Example
Backup-AwsS3Policies -FilePath .\aws-s3-policies-myprofile-2025-05-27.bak.json -AwsProfile test -Verbose
#>
function Backup-AwsS3Policies {
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)][string] $FilePath,
[Parameter()][string] $AwsProfile = 'default'
)
$BucketPolicies = @{}
[System.Array](aws s3api list-buckets --query "Buckets[].Name" --profile $AwsProfile --output json | ConvertFrom-Json) | foreach {
$RawPolicy = $(aws s3api get-bucket-policy --bucket $_ --profile $AwsProfile --output text 2> $null)
Write-Verbose "Raw policy for bucket ${_}: $RawPolicy"
if ($RawPolicy) {
$BucketPolicies.$_ = $RawPolicy | ConvertFrom-Json
}
}
$BucketPolicies | ConvertTo-Json -Depth 100 | Out-File $FilePath -Encoding ASCII
}
<#
.Synopsis
Adds an 'Allow' policy to AWS S3 buckets if its SID is not already present.
.Description
Pipe a list of bucket IDs to this function.
Note that policies won't be set for buckets that don't currently have any policies at all.
If a bucket already has a policy with the same SID, it will be REPLACED.
.Parameter BucketId
ID of the bucket to add the policy to.
This is a string that will be passed through the pipeline.
.Parameter Sid
SID of the allow policy. Used to determine whether the policy is already present in the statement list.
.Parameter PrincipalArns
List of AWS ARNs to pass to Condition/StringLike/aws:PrincipalArn. Can contain wildcards.
Example: @('awn:aws:iam:1234567890/role/myroleprefix-*')
.Parameter Action
String action or list of actions to allow to the principal.
.Parameter RemoveDenyExcludeArns
List of ARNs to remove from the exclusions to global deny policies if found.
.Parameter AwsProfile
Name of the AWS profile to use.
.Parameter DryRun
Set this switch to do a dry run of what the changes would look like without actually making them.
.Example
aws s3api list-buckets --query "Buckets[].Name" --profile dev --output json | ConvertFrom-Json | Add-AwsBucketAllowPolicy -Sid 'AllowMyPrefix' -PrincipalArns @('arn:aws:iam::1234567890:role/myprefix-*') -Action 's3:*' -AwsProfile dev
#>
function Add-AwsBucketAllowPolicy {
[CmdletBinding()]
param (
[Parameter(Mandatory=$true, ValueFromPipeline=$true)][string] $BucketId,
[Parameter(Mandatory=$true)][string] $Sid,
[Parameter(Mandatory=$true)][string[]] $PrincipalArns,
[Parameter(Mandatory=$true)] $Action,
[Parameter()][string[]] $RemoveDenyExcludeArns = @(),
[Parameter()][string] $AwsProfile = 'default',
[Parameter()][switch] $DryRun
)
process {
$RawPolicy = $(aws s3api get-bucket-policy --bucket $BucketId --profile $AwsProfile --output text 2> $null)
if (-not $RawPolicy) {
Write-Host "No policies found for bucket '$BucketId', skipping..." -ForegroundColor Yellow
return
}
$Policy = $RawPolicy | ConvertFrom-Json
# Remove existing policy with the same SID if it exists
if ($Policy.Statement.Sid -eq $Sid) {
Write-Warning "Policy SID '$Sid' already found in bucket '$BucketId', replacing!"
$Policy.Statement = [System.Array]($Policy.Statement | where { $_.Sid -ne $Sid })
}
# Ensure Statement is always an array (the filtering above could turn it into PSObject)
if ($Policy.Statement -isnot [System.Array]) {
$Policy.Statement = @($Policy.Statement)
}
# Amend deny sections if they exist
$Policy.Statement | foreach {
if ($_.Effect -eq 'Deny' -and $_.Principal -eq '*' -and $_.Condition.ArnNotLike.'aws:PrincipalArn') {
Write-Verbose "Removing ARNs '$RemoveDenyExcludeArns' from deny exclude policy for bucket '$BucketId'..."
$_.Condition.ArnNotLike.'aws:PrincipalArn' = [System.Array](
$_.Condition.ArnNotLike.'aws:PrincipalArn' |
where { $_ -notin $RemoveDenyExcludeArns -and $_ -notin $PrincipalArns }
)
Write-Verbose "Adding ARNs '$PrincipalArns' to deny exclude policy for bucket '$BucketId'..."
$_.Condition.ArnNotLike.'aws:PrincipalArn' += $PrincipalArns
}
}
# Add new allow policy
$Policy.Statement += [PSCustomObject]@{
Sid = $Sid
Effect = 'Allow'
Principal = @{
AWS = '*'
}
Action = $Action
Resource = @(
"arn:aws:s3:::$BucketId",
"arn:aws:s3:::$BucketId/*"
)
Condition = @{
StringLike = @{
'aws:PrincipalArn' = $PrincipalArns
}
}
}
$Policy | ConvertTo-Json -Depth 100 -Compress | Out-File $Env:TMP\policy.json -Encoding ASCII
Get-Content -Path $Env:TMP\policy.json | Write-Verbose
if ($DryRun) {
Write-Host "Would be adding policy '$Sid' to bucket '$BucketId' (dry run)..."
}
if (-not $DryRun) {
Write-Host "Adding policy '$Sid' to bucket '$BucketId'..."
aws s3api put-bucket-policy --bucket $BucketId --policy file://$Env:TEMP\policy.json --profile $AwsProfile
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment