Last active
May 28, 2025 00:47
-
-
Save panasenco/cf30cb47adb6347bfc74c08746c7f352 to your computer and use it in GitHub Desktop.
PowerShell script to add an 'allow' policy to AWS S3 buckets
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
.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