Securing AWS S3 Glacier vaults with a vault lock policy

One of AWS’s most appealing cloud services is low-cost, archival storage with AWS S3 Glacier. Glacier is purpose-built for long-term archival retention of large amounts of data. Recently, AWS enhanced its PowerShell support in S3 Glacier.  That update coincided with my client’s requirements to secure a Glacier vault in a way that would ensure that only properly authorized users and roles had access. Because this client is in a heavily regulated industry,  security controls must stand up to regulatory scrutiny.

@bellevuesteve‘s post provides an excellent PowerShell-based tutorial on how to archive data. This post is about using PowerShell and IAM to secure the vault containing the archives.

Before I get to the code sample, think about the security features that Glacier comes with out-of-the-box that, by default, make it a secure archive store:

  • There is no console UI in Glacier to add or retrieve archives — so there’s no chance an errant console user will be able to delete data or go on a fishing expedition.
  • Glacier implements AWS IAM’s implicit deny logic. We are all so used to this across AWS services that we sometimes don’t stop to think about its value. Being able to tell a regulator that electronic archives are, by default, accessible by nobody is a very powerful security control.
  • Glacier vaults are additive. That means that you can add an archive or delete an archive but you can never change an existing archive. The archive ID Glacier returns for an archive is unique — if you delete and re-add an archive, you get a different archive ID. This makes any archive itself immutable. Demonstrable immutability is enormously important in proving regulatory compliance for archival storage of electronic records.
  • Glacier offers a “vault lock” policy, which once implemented cannot be changed. 

The Glacier vault lock policy allows users to design a security policy at the vault level that can implement very specific security controls — for example, assigning the ability to add archives to a specific ARN. That user or role could, for example, be a specific application service in the data center or the cloud which is the only authorized user or role that can add archives to the vault or manage the vault itself.

The PowerShell script below demonstrates how to

  • Create a vault using the credentials specified in a stored profile
  • Assign a tag to the newly created Glacier vault
  • Apply a vault lock policy that limits vault deletion to either the user specified in the stored profile or, optionally, to any AWS ARN passed to the script in the UserArn parameter. The vault must also have a specific tag applied
  • Prompt the user to see if the 24 hour vault lock test period should be terminated immediately. IOW, this permanently and immutably applies the vault lock policy. You shouldn’t do this unless you have already tested the policy either via a normal policy applied to the vault or during the 24 hour test period.

I hope this helps you use PowerShell for AWS Glacier for archiving more easily.

The IAM policy is embedded in a here string in the PowerShell script. For clarity, I am attaching the Glacier vault lock policy separately in case you want to look at it outside of the PowerShell script. Also included is an image of what the console output should be after successful completion of the script.

<#
	.SYNOPSIS
		Creates a vault with an aribtrary name and optionally applies a vault lock policy that limits vault deletion
	
	.DESCRIPTION
		The script creates a vault in an account specified by the profile passed to the script and optionally applies a vault lock policy to the vault that limits deletion. If you select to apply the policy via the script, it is IMMEDIATELY applied -- the usual 24 hour waiting period is terminated. Before using this script, test the vault lock policy as a regular vault resource policy.
	
	.PARAMETER VaultName
		Name of Glacier vault to be created
	
	.PARAMETER StoredProfile
		Name of AWS stored profile name (in ./aws/credentials). Must be the name of a stored profile returned by Get-AWSCredential -ListProfile
	
	.PARAMETER UserArn
		Amazon Resource Name of the user that will be given delete permissions on the Glacier vault created in this script, if not the same as the user specified in the AWS stored profile

	.EXAMPLE
		PS C:\> .\CreateGlacierVaultAndAssignVaultDeletePolicy.ps1 -VaultName 'MyVault'  -StoredProfike 'Stored profile name'
	
	.NOTES
		Alex Neihaus 2019-02-18
		(c) 2019 Air11 Technology LLC -- licensed under the Apache OpenSource 2.0 license, https://opensource.org/licenses/Apache-2.0
		Licensed under the Apache License, Version 2.0 (the "License");
		you may not use this file except in compliance with the License.
		You may obtain a copy of the License at
		http://www.apache.org/licenses/LICENSE-2.0
		
		Unless required by applicable law or agreed to in writing, software
		distributed under the License is distributed on an "AS IS" BASIS,
		WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
		See the License for the specific language governing permissions and
		limitations under the License.
		
		Author's blog: https://www.yobyot.com
#>
[CmdletBinding()]
param
(
	[Parameter(Mandatory = $true,
			   ValueFromPipeline = $true,
			   ValueFromPipelineByPropertyName = $true,
			   HelpMessage = 'Name of the vault')]
	[System.String]$VaultName,
	[Parameter(Mandatory = $true,
			   ValueFromPipeline = $true,
			   ValueFromPipelineByPropertyName = $true,
			   HelpMessage = 'Name of stored credentials')]
	[System.String]$StoredProfile,
	[Parameter(Mandatory = $false,
			   ValueFromPipeline = $true,
			   ValueFromPipelineByPropertyName = $true,
			   HelpMessage = 'ARN of user to be assigned delete permissions')]
	[System.String]$UserArn
)
# Function returns a hashtable with $AWSAccount (account), $AWSARN (ARN) and $AWSUserid (userid). 
# See https://www.yobyot.com/aws/a-function-to-set-up-aws-powershell-credentials/2019/01/23/.
[hashtable]$AWSConnection = Connect-AWS -AWSProfile $StoredProfile -AWSRegion us-east-1 # Change the region to contain the vault here, if desired
$vLockPolicy = '{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "allow-delete-for-a-specific-user-and-tag",
            "Principal": { 
                "AWS": [
                    "arn:aws:iam::111222333444:user/dummy"
                    ] 
            },
            "Effect": "Allow",
            "Action": "glacier:DeleteVault",
            "Resource": [
                "arn:aws:glacier:us-east-1:111222333444:vaults/DummyVault"
            ],
            "Condition": {
                "StringLike": {
                  "glacier:ResourceTag/Immutable": [
                    "true",
                    ""
                  ]
                }
            }
        }
    ]
}' # JSON policy that denies vault deletion until 3 days after creation
New-GLCVault -VaultName $vaultName # Create the Glacier vault
$tags = @{"Immutable" = "true"}
Add-GLCTagsToVault -VaultName $vaultName -Tags $tags
# Fix up dummy policy above to reflect either the default user in $AWSConnection or the user specified in the parameter that invoked the script
if ($UserArn) # A specific ARN was specified
{
    $vp = $vLockPolicy.Replace("arn:aws:iam::111222333444:user/dummy", $UserArn) # Put the ARN into the JSON policy
}
else # Use the default arn specified associated with the user logged in via the stored profile
{
    $vp = $vLockPolicy.Replace("arn:aws:iam::111222333444:user/dummy", $AWSConnection.AWSArn) # Put the ARN into the JSON policy
}
$VaultArn = (Get-GLCVault -VaultName $vaultName).VaultARN # Get the ARN of the new vault
$vp = $vp.Replace("arn:aws:glacier:us-east-1:111222333444:vaults/DummyVault", $VaultArn) # Put the ARN into the JSON policy that has been modified to contain the connect ARN
$objVaultPolicy = New-Object Amazon.Glacier.Model.VaultLockPolicy # Create an object that Start-GLCVaultLock requires
$objVaultPolicy.policy = $vp # Set the policy property to the value of the JSON string with the correct ARN
$lockID = Start-GLCVaultLock -Policy $objVaultPolicy -VaultName $vaultName # Start policy implementation; returns lock id which must be used to confirm the lock within 24 hours
Write-Host "Would you like to prevent $vaultName from being deleted?`nTHIS PERMANENELT APPLIES THE VAULT LOCK POLICY!`n(Default is no)" -ForegroundColor Yellow 
$ReadHost = Read-Host " ( y / N) " 
Switch ($ReadHost) 
{ 
    Y
    {
        Complete-GLCVaultLock -LockId $lockID -VaultName $vaultName
        Write-Host "Locked" -ForegroundColor Yellow
    } 
    N
    {
        Write-Host "Skipped"
    } 
    Default
    {
        Write-Host "Skipped"
    }

} 
"Complete"
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "allow-delete-for-a-specific-user-and-tag",
            "Principal": { 
                "AWS": [
                    "arn:aws:iam::111222333444:user/dummy"
                    ] 
            },
            "Effect": "Allow",
            "Action": "glacier:DeleteVault",
            "Resource": [
                "arn:aws:glacier:us-east-1:111222333444:vaults/DummyVault"
            ],
            "Condition": {
                "StringLike": {
                  "glacier:ResourceTag/Immutable": [
                    "true",
                    ""
                  ]
                }
            }
        }
    ]
}
AWS Glacier Vault Lock Policy
AWS Glacier Vault Lock Policy (click to enlarge)

Posted

in

, ,

by

Tags:

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *