feat(Glacier): Service and check (#1480)

This commit is contained in:
Pepe Fagoaga
2022-11-15 17:41:58 +01:00
committed by GitHub
parent 5281d521f4
commit 30738d7810
9 changed files with 565 additions and 56 deletions

View File

@@ -1,56 +0,0 @@
#!/usr/bin/env bash
# Prowler - the handy cloud security tool (copyright 2018) by Toni de la Fuente
#
# 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.
CHECK_ID_extra7147="7.147"
CHECK_TITLE_extra7147="[extra7147] Check if S3 Glacier vaults have policies which allow access to everyone"
CHECK_SCORED_extra7147="NOT_SCORED"
CHECK_CIS_LEVEL_extra7147="EXTRA"
CHECK_SEVERITY_extra7147="Critical"
CHECK_ASFF_RESOURCE_TYPE_extra7147="AwsGlacierVault"
CHECK_ALTERNATE_check7147="extra7147"
CHECK_SERVICENAME_extra7147="glacier"
CHECK_RISK_extra7147='Vaults accessible to everyone could expose sensitive data to bad actors'
CHECK_REMEDIATION_extra7147='Ensure vault policy does not have principle as *'
CHECK_DOC_extra7147='https://docs.aws.amazon.com/amazonglacier/latest/dev/access-control-overview.html'
CHECK_CAF_EPIC_extra7147='Data Protection'
extra7147(){
for regx in $REGIONS; do
LIST_OF_VAULTS=$($AWSCLI glacier list-vaults ${PROFILE_OPT} --region "${regx}" --account-id "${ACCOUNT_NUM}" --query VaultList[*].VaultName --output text 2>&1|xargs -n1)
if grep -q -E 'AccessDenied|UnauthorizedOperation|AuthorizationError' <<< "$LIST_OF_VAULTS"; then
textInfo "$regx: Access Denied trying to list vaults" "${regx}"
continue
fi
# Check for unsupported regions
if grep -q -E 'error' <<< "${LIST_OF_VAULTS}"; then
textInfo "$regx: An error occurred when calling the ListVaults operation - check if this region is supported" "${regx}"
continue
fi
if [[ $LIST_OF_VAULTS ]]; then
for vault in $LIST_OF_VAULTS;do
VAULT_POLICY_STATEMENTS=$($AWSCLI glacier ${PROFILE_OPT} get-vault-access-policy --region "${regx}" --account-id "${ACCOUNT_NUM}" --vault-name "${vault}" --output json --query policy.Policy 2>&1)
if [[ $VAULT_POLICY_STATEMENTS == *GetVaultAccessPolicy* ]]; then
textInfo "${regx}: Vault $vault doesn't have any policy" "${regx}" "$vault"
else
VAULT_POLICY_BAD_STATEMENTS=$(jq '. | fromjson' <<< "${VAULT_POLICY_STATEMENTS}" | jq '.Statement[] | select(.Effect=="Allow") | select(.Principal=="*" or .Principal.AWS=="*" or .Principal.CanonicalUser=="*")')
if [[ $VAULT_POLICY_BAD_STATEMENTS != "" ]]; then
textFail "${regx}: Vault $vault has policy which allows access to everyone" "${regx}" "$vault"
else
textPass "${regx}: Vault $vault has policy which does not allow access to everyone" "${regx}" "$vault"
fi
fi
done
else
textInfo "${regx}: No Glacier vaults found" "${regx}"
fi
done
}

View File

@@ -0,0 +1,4 @@
from providers.aws.lib.audit_info.audit_info import current_audit_info
from providers.aws.services.glacier.glacier_service import Glacier
glacier_client = Glacier(current_audit_info)

View File

@@ -0,0 +1,78 @@
import json
import threading
from pydantic import BaseModel
from lib.logger import logger
from providers.aws.aws_provider import generate_regional_clients
################## Glacier
class Glacier:
def __init__(self, audit_info):
self.service = "glacier"
self.session = audit_info.audit_session
self.audited_account = audit_info.audited_account
self.regional_clients = generate_regional_clients(self.service, audit_info)
self.vaults = {}
self.__threading_call__(self.__list_vaults__)
self.__threading_call__(self.__get_vault_access_policy__)
def __get_session__(self):
return self.session
def __threading_call__(self, call):
threads = []
for regional_client in self.regional_clients.values():
threads.append(threading.Thread(target=call, args=(regional_client,)))
for t in threads:
t.start()
for t in threads:
t.join()
def __list_vaults__(self, regional_client):
logger.info("Glacier - Listing Vaults...")
try:
list_vaults_paginator = regional_client.get_paginator("list_vaults")
for page in list_vaults_paginator.paginate():
for vault in page["VaultList"]:
vault_name = vault["VaultName"]
vault_arn = vault["VaultARN"]
self.vaults[vault_name] = Vault(
name=vault_name,
arn=vault_arn,
region=regional_client.region,
)
except Exception as error:
logger.error(
f"{regional_client.region} --"
f" {error.__class__.__name__}[{error.__traceback__.tb_lineno}]:"
f" {error}"
)
def __get_vault_access_policy__(self, regional_client):
logger.info("Glacier - Getting Vault Access Policy...")
try:
for vault in self.vaults.values():
if vault.region == regional_client.region:
vault_access_policy = regional_client.get_vault_access_policy(
vaultName=vault.name
)
self.vaults[vault.name].access_policy = json.loads(
vault_access_policy["policy"]["Policy"]
)
except Exception as error:
logger.error(
f"{regional_client.region} --"
f" {error.__class__.__name__}[{error.__traceback__.tb_lineno}]:"
f" {error}"
)
class Vault(BaseModel):
name: str
arn: str
region: str
access_policy: dict = {}

View File

@@ -0,0 +1,116 @@
import json
from unittest.mock import patch
import botocore
from moto.core import DEFAULT_ACCOUNT_ID
from providers.aws.lib.audit_info.audit_info import current_audit_info
from providers.aws.services.glacier.glacier_service import Glacier
# Mock Test Region
AWS_REGION = "eu-west-1"
# Mocking Access Analyzer Calls
make_api_call = botocore.client.BaseClient._make_api_call
vault_json_policy = {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "cross-account-upload",
"Principal": {"AWS": [f"arn:aws:iam::{DEFAULT_ACCOUNT_ID}:root"]},
"Effect": "Allow",
"Action": [
"glacier:UploadArchive",
"glacier:InitiateMultipartUpload",
"glacier:AbortMultipartUpload",
"glacier:CompleteMultipartUpload",
],
"Resource": [
f"arn:aws:glacier:{AWS_REGION}:{DEFAULT_ACCOUNT_ID}:vaults/examplevault"
],
}
],
}
def mock_make_api_call(self, operation_name, kwarg):
"""We have to mock every AWS API call using Boto3"""
if operation_name == "ListVaults":
return {
"VaultList": [
{
"VaultARN": f"arn:aws:glacier:{AWS_REGION}:{DEFAULT_ACCOUNT_ID}:vaults/examplevault",
"VaultName": "examplevault",
"CreationDate": "2012-03-16T22:22:47.214Z",
"LastInventoryDate": "2012-03-21T22:06:51.218Z",
"NumberOfArchives": 2,
"SizeInBytes": 12334,
},
],
}
if operation_name == "GetVaultAccessPolicy":
return {"policy": {"Policy": json.dumps(vault_json_policy)}}
return make_api_call(self, operation_name, kwarg)
# Mock generate_regional_clients()
def mock_generate_regional_clients(service, audit_info):
regional_client = audit_info.audit_session.client(service, region_name=AWS_REGION)
regional_client.region = AWS_REGION
return {AWS_REGION: regional_client}
# Patch every AWS call using Boto3 and generate_regional_clients to have 1 client
@patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call)
@patch(
"providers.aws.services.glacier.glacier_service.generate_regional_clients",
new=mock_generate_regional_clients,
)
class Test_Glacier_Service:
# Test Glacier Client
def test__get_client__(self):
glacier = Glacier(current_audit_info)
assert glacier.regional_clients[AWS_REGION].__class__.__name__ == "Glacier"
# Test Glacier Session
def test__get_session__(self):
glacier = Glacier(current_audit_info)
assert glacier.session.__class__.__name__ == "Session"
# Test Glacier Service
def test__get_service__(self):
glacier = Glacier(current_audit_info)
assert glacier.service == "glacier"
def test__list_vaults__(self):
# Set partition for the service
current_audit_info.audited_partition = "aws"
glacier = Glacier(current_audit_info)
vault_name = "examplevault"
assert len(glacier.vaults) == 1
assert glacier.vaults[vault_name]
assert glacier.vaults[vault_name].name == vault_name
assert (
glacier.vaults[vault_name].arn
== f"arn:aws:glacier:{AWS_REGION}:{DEFAULT_ACCOUNT_ID}:vaults/examplevault"
)
assert glacier.vaults[vault_name].region == AWS_REGION
def test__get_vault_access_policy__(self):
# Set partition for the service
current_audit_info.audited_partition = "aws"
glacier = Glacier(current_audit_info)
vault_name = "examplevault"
assert len(glacier.vaults) == 1
assert glacier.vaults[vault_name]
assert glacier.vaults[vault_name].name == vault_name
assert (
glacier.vaults[vault_name].arn
== f"arn:aws:glacier:{AWS_REGION}:{DEFAULT_ACCOUNT_ID}:vaults/examplevault"
)
assert glacier.vaults[vault_name].region == AWS_REGION
assert glacier.vaults[vault_name].access_policy == vault_json_policy

View File

@@ -0,0 +1,35 @@
{
"Provider": "aws",
"CheckID": "glacier_vaults_policy_public_access",
"CheckTitle": "Check if S3 Glacier vaults have policies which allow access to everyone.",
"CheckType": [],
"ServiceName": "glacier",
"SubServiceName": "",
"ResourceIdTemplate": "arn:aws:glacier:region:account-id:vaults/vault-name",
"Severity": "critical",
"ResourceType": "AwsGlacierVault",
"Description": "Ensure CodeArtifact internal packages do not allow external public source publishing.",
"Risk": "Vaults accessible to everyone could expose sensitive data to bad actors.",
"RelatedUrl": "https://docs.aws.amazon.com/amazonglacier/latest/dev/access-control-overview.html",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": "https://docs.bridgecrew.io/docs/ensure-glacier-vault-access-policy-is-not-public-by-only-allowing-specific-services-or-principals-to-access-it#terraform"
},
"Recommendation": {
"Text": "Ensure vault policy does not have principle as *.",
"Url": "https://docs.aws.amazon.com/amazonglacier/latest/dev/access-control-overview.html"
}
},
"Categories": [],
"Tags": {
"Tag1Key": "value",
"Tag2Key": "value"
},
"DependsOn": [],
"RelatedTo": [],
"Notes": "",
"Compliance": []
}

View File

@@ -0,0 +1,50 @@
from lib.check.models import Check, Check_Report
from providers.aws.services.glacier.glacier_client import (
glacier_client,
)
class glacier_vaults_policy_public_access(Check):
def execute(self):
findings = []
for vault in glacier_client.vaults.values():
report = Check_Report(self.metadata)
report.region = vault.region
report.resource_id = vault.name
report.resource_arn = vault.arn
report.status = "PASS"
report.status_extended = (
f"Vault {vault.name} has policy which does not allow access to everyone"
)
public_access = False
if vault.access_policy:
for statement in vault.access_policy["Statement"]:
# Only check allow statements
if statement["Effect"] == "Allow":
if (
"*" in statement["Principal"]
or (
"AWS" in statement["Principal"]
and "*" in statement["Principal"]["AWS"]
)
or (
"CanonicalUser" in statement["Principal"]
and "*" in statement["Principal"]["CanonicalUser"]
)
):
public_access = True
break
if public_access:
report.status = "FAIL"
report.status_extended = (
f"Vault {vault.name} has policy which allows access to everyone"
)
findings.append(report)
return findings

View File

@@ -0,0 +1,282 @@
from unittest import mock
from moto.core import DEFAULT_ACCOUNT_ID
from providers.aws.services.glacier.glacier_service import Vault
AWS_REGION = "eu-west-1"
class Test_glacier_vaults_policy_public_access:
def test_no_vaults(self):
glacier_client = mock.MagicMock
glacier_client.vaults = {}
with mock.patch(
"providers.aws.services.glacier.glacier_service.Glacier",
new=glacier_client,
):
# Test Check
from providers.aws.services.glacier.glacier_vaults_policy_public_access.glacier_vaults_policy_public_access import (
glacier_vaults_policy_public_access,
)
check = glacier_vaults_policy_public_access()
result = check.execute()
assert len(result) == 0
def test_vault_no_policy(self):
glacier_client = mock.MagicMock
vault_name = "test-vault"
vault_arn = (
f"arn:aws:glacier:{AWS_REGION}:{DEFAULT_ACCOUNT_ID}:vaults/examplevault"
)
glacier_client.vaults = {
vault_name: Vault(
name=vault_name,
arn=vault_arn,
access_policy={},
region=AWS_REGION,
)
}
with mock.patch(
"providers.aws.services.glacier.glacier_service.Glacier",
new=glacier_client,
):
# Test Check
from providers.aws.services.glacier.glacier_vaults_policy_public_access.glacier_vaults_policy_public_access import (
glacier_vaults_policy_public_access,
)
check = glacier_vaults_policy_public_access()
result = check.execute()
assert len(result) == 1
assert result[0].region == AWS_REGION
assert result[0].resource_id == vault_name
assert result[0].resource_arn == vault_arn
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Vault {vault_name} has policy which does not allow access to everyone"
)
def test_vault_policy_pricipal_aws_list_asterisk(self):
glacier_client = mock.MagicMock
vault_name = "test-vault"
vault_arn = (
f"arn:aws:glacier:{AWS_REGION}:{DEFAULT_ACCOUNT_ID}:vaults/examplevault"
)
glacier_client.vaults = {
vault_name: Vault(
name=vault_name,
arn=vault_arn,
access_policy={
"Version": "2012-10-17",
"Statement": [
{
"Sid": "cross-account-upload",
"Principal": {"AWS": ["*", DEFAULT_ACCOUNT_ID]},
"Effect": "Allow",
"Action": [
"glacier:UploadArchive",
"glacier:InitiateMultipartUpload",
"glacier:AbortMultipartUpload",
"glacier:CompleteMultipartUpload",
],
"Resource": [
f"arn:aws:glacier:{AWS_REGION}:{DEFAULT_ACCOUNT_ID}:vaults/examplevault"
],
}
],
},
region=AWS_REGION,
)
}
with mock.patch(
"providers.aws.services.glacier.glacier_service.Glacier",
new=glacier_client,
):
# Test Check
from providers.aws.services.glacier.glacier_vaults_policy_public_access.glacier_vaults_policy_public_access import (
glacier_vaults_policy_public_access,
)
check = glacier_vaults_policy_public_access()
result = check.execute()
assert len(result) == 1
assert result[0].region == AWS_REGION
assert result[0].resource_id == vault_name
assert result[0].resource_arn == vault_arn
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Vault {vault_name} has policy which allows access to everyone"
)
def test_vault_policy_pricipal_asterisk(self):
glacier_client = mock.MagicMock
vault_name = "test-vault"
vault_arn = (
f"arn:aws:glacier:{AWS_REGION}:{DEFAULT_ACCOUNT_ID}:vaults/examplevault"
)
glacier_client.vaults = {
vault_name: Vault(
name=vault_name,
arn=vault_arn,
access_policy={
"Version": "2012-10-17",
"Statement": [
{
"Sid": "cross-account-upload",
"Principal": {"AWS": ["*"]},
"Effect": "Allow",
"Action": [
"glacier:UploadArchive",
"glacier:InitiateMultipartUpload",
"glacier:AbortMultipartUpload",
"glacier:CompleteMultipartUpload",
],
"Resource": [
f"arn:aws:glacier:{AWS_REGION}:{DEFAULT_ACCOUNT_ID}:vaults/examplevault"
],
}
],
},
region=AWS_REGION,
)
}
with mock.patch(
"providers.aws.services.glacier.glacier_service.Glacier",
new=glacier_client,
):
# Test Check
from providers.aws.services.glacier.glacier_vaults_policy_public_access.glacier_vaults_policy_public_access import (
glacier_vaults_policy_public_access,
)
check = glacier_vaults_policy_public_access()
result = check.execute()
assert len(result) == 1
assert result[0].region == AWS_REGION
assert result[0].resource_id == vault_name
assert result[0].resource_arn == vault_arn
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Vault {vault_name} has policy which allows access to everyone"
)
def test_vault_policy_pricipal_canonical_user_asterisk(self):
glacier_client = mock.MagicMock
vault_name = "test-vault"
vault_arn = (
f"arn:aws:glacier:{AWS_REGION}:{DEFAULT_ACCOUNT_ID}:vaults/examplevault"
)
glacier_client.vaults = {
vault_name: Vault(
name=vault_name,
arn=vault_arn,
access_policy={
"Version": "2012-10-17",
"Statement": [
{
"Sid": "cross-account-upload",
"Principal": {"CanonicalUser": ["*"]},
"Effect": "Allow",
"Action": [
"glacier:UploadArchive",
"glacier:InitiateMultipartUpload",
"glacier:AbortMultipartUpload",
"glacier:CompleteMultipartUpload",
],
"Resource": [
f"arn:aws:glacier:{AWS_REGION}:{DEFAULT_ACCOUNT_ID}:vaults/examplevault"
],
}
],
},
region=AWS_REGION,
)
}
with mock.patch(
"providers.aws.services.glacier.glacier_service.Glacier",
new=glacier_client,
):
# Test Check
from providers.aws.services.glacier.glacier_vaults_policy_public_access.glacier_vaults_policy_public_access import (
glacier_vaults_policy_public_access,
)
check = glacier_vaults_policy_public_access()
result = check.execute()
assert len(result) == 1
assert result[0].region == AWS_REGION
assert result[0].resource_id == vault_name
assert result[0].resource_arn == vault_arn
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Vault {vault_name} has policy which allows access to everyone"
)
def test_vault_policy_private(self):
glacier_client = mock.MagicMock
vault_name = "test-vault"
vault_arn = (
f"arn:aws:glacier:{AWS_REGION}:{DEFAULT_ACCOUNT_ID}:vaults/examplevault"
)
glacier_client.vaults = {
vault_name: Vault(
name=vault_name,
arn=vault_arn,
access_policy={
"Version": "2012-10-17",
"Statement": [
{
"Sid": "cross-account-upload",
"Principal": {
"CanonicalUser": [
f"arn:aws:iam::{DEFAULT_ACCOUNT_ID}:root",
]
},
"Effect": "Allow",
"Action": [
"glacier:UploadArchive",
"glacier:InitiateMultipartUpload",
"glacier:AbortMultipartUpload",
"glacier:CompleteMultipartUpload",
],
"Resource": [
f"arn:aws:glacier:{AWS_REGION}:{DEFAULT_ACCOUNT_ID}:vaults/examplevault"
],
}
],
},
region=AWS_REGION,
)
}
with mock.patch(
"providers.aws.services.glacier.glacier_service.Glacier",
new=glacier_client,
):
# Test Check
from providers.aws.services.glacier.glacier_vaults_policy_public_access.glacier_vaults_policy_public_access import (
glacier_vaults_policy_public_access,
)
check = glacier_vaults_policy_public_access()
result = check.execute()
assert len(result) == 1
assert result[0].region == AWS_REGION
assert result[0].resource_id == vault_name
assert result[0].resource_arn == vault_arn
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Vault {vault_name} has policy which does not allow access to everyone"
)