-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Ryan Gerstenkorn
committed
Aug 19, 2021
1 parent
d57398a
commit 37fc1ee
Showing
8 changed files
with
317 additions
and
2 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import random | ||
import os | ||
|
||
|
||
def lambda_handler(event, context): | ||
request = event['Records'][0]['cf']['request'] | ||
headers = request['headers'] | ||
|
||
host = os.getenv('HOST') | ||
if not host: | ||
raise UserWarning('The environment variable HOST is not set.') | ||
|
||
forwarded_for = os.getenv('X_FORWARDED_FOR') | ||
if not forwarded_for: | ||
# Set randomly | ||
pass | ||
|
||
headers['host'] = [{ | ||
"key": "Host", | ||
"value": host, | ||
}] | ||
|
||
headers['x-forwarded-for'] = [{ | ||
"key": "X-Forwarded-For", | ||
"value": forwarded_for, | ||
}] | ||
|
||
return request |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,198 @@ | ||
import io | ||
import zipfile | ||
from datetime import time | ||
from pathlib import Path | ||
|
||
import botocore.exceptions | ||
import boto3.exceptions | ||
|
||
|
||
def create_function(sess, function_name, role_arn, target): | ||
source_code = (Path(__file__).parent / 'lambdas/request/main.py').read_text() | ||
zip = io.BytesIO() | ||
with zipfile.ZipFile(zip, 'w', zipfile.ZIP_DEFLATED) as zip_file: | ||
zip_file.writestr('main.py', source_code) | ||
zip.seek(0) | ||
|
||
lamb = sess.client('lambda', region_name='us-east-1') | ||
lambda_exists = False | ||
try: | ||
lamb.get_function(FunctionName=function_name) | ||
lambda_exists = True | ||
except Exception as e: | ||
if e.response['Error']['Code'] != 'ResourceNotFoundException': | ||
raise e | ||
|
||
if not lambda_exists: | ||
lamb.create_function( | ||
FunctionName=function_name, | ||
Runtime='python3.9', | ||
Role=role_arn, | ||
Handler='main.lambda_handler', | ||
Code={ | ||
'ZipFile': zip.read(), | ||
}, | ||
Description='Request handler for the cdn-bypass Burp plugin.', | ||
Timeout=31, | ||
MemorySize=128, | ||
Publish=True, | ||
PackageType='Zip', | ||
Environment={ | ||
'Variables': { | ||
'HOST': target, | ||
} | ||
}, | ||
) | ||
|
||
|
||
def create_lambda_role(sess, function_name, role_name): | ||
iam = sess.client('iam', region_name='us-east-1') | ||
role_exists = False | ||
try: | ||
iam.get_role(RoleName=role_name) | ||
role_exists = True | ||
except botocore.exceptions.ClientError as e: | ||
if e.response['Error']['Code'] != 'NoSuchEntity': | ||
raise e | ||
|
||
if role_exists: | ||
return | ||
|
||
resp = iam.create_role( | ||
RoleName=role_name, | ||
AssumeRolePolicyDocument=''' | ||
{ | ||
"Version": "2012-10-17", | ||
"Statement": [ | ||
{ | ||
"Effect": "Allow", | ||
"Principal": { | ||
"Service": [ | ||
"edgelambda.amazonaws.com", | ||
"lambda.amazonaws.com" | ||
] | ||
}, | ||
"Action": "sts:AssumeRole" | ||
} | ||
] | ||
} | ||
''', | ||
Description='Execution roles for lambdas created by the cdn-bypass burp plugin.', | ||
) | ||
iam.put_role_policy( | ||
RoleName=role_name, | ||
PolicyName='basic-execution', | ||
PolicyDocument=f''' | ||
{{ | ||
"Version": "2012-10-17", | ||
"Statement": [ | ||
{{ | ||
"Effect": "Allow", | ||
"Action": "logs:CreateLogGroup", | ||
"Resource": "arn:aws:logs:*:*:*" | ||
}}, | ||
{{ | ||
"Effect": "Allow", | ||
"Action": [ | ||
"logs:CreateLogStream", | ||
"logs:PutLogEvents" | ||
], | ||
"Resource": [ | ||
"arn:aws:logs:*:*:log-group:/aws/lambda/{function_name}:*" | ||
] | ||
}} | ||
] | ||
}} | ||
''' | ||
) | ||
return resp['Role']['Arn'] | ||
|
||
|
||
def create_cloudfront_distribution(sess, target): | ||
client = sess.client('cloudfront', region_name='us-east-1') | ||
resp = client.create_distribution( | ||
DistributionConfig={ | ||
'CallerReference': str(time()), | ||
'Origins': { | ||
'Quantity': 1, | ||
'Items': [ | ||
{ | ||
'Id': 'default', | ||
'DomainName': target, | ||
'CustomOriginConfig': { | ||
'HTTPPort': 80, | ||
'HTTPSPort': 443, | ||
'OriginProtocolPolicy': 'http-only', | ||
# TODO: Add option for this | 'match-viewer' | 'https-only', | ||
'OriginSslProtocols': { | ||
'Quantity': 4, | ||
'Items': [ | ||
'SSLv3', | ||
'TLSv1', | ||
'TLSv1.1', | ||
'TLSv1.2', | ||
] | ||
}, | ||
}, | ||
'OriginShield': { | ||
'Enabled': False, | ||
} | ||
}, | ||
] | ||
}, | ||
'DefaultCacheBehavior': { | ||
'TargetOriginId': 'default', | ||
'ViewerProtocolPolicy': 'allow-all', | ||
'AllowedMethods': { | ||
'Quantity': 7, | ||
'Items': [ | ||
'GET', | ||
'HEAD', | ||
'POST', | ||
'PUT', | ||
'PATCH', | ||
'OPTIONS', | ||
'DELETE', | ||
], | ||
'CachedMethods': { | ||
'Quantity': 2, | ||
'Items': [ | ||
'GET', | ||
'HEAD', | ||
] | ||
} | ||
}, | ||
'Compress': False, | ||
'LambdaFunctionAssociations': { | ||
'Quantity': 1, | ||
'Items': [ | ||
{ | ||
'LambdaFunctionARN': ..., | ||
'EventType': 'origin-request', | ||
'IncludeBody': False, | ||
}, | ||
# Rewrite all links? | ||
# { | ||
# 'LambdaFunctionARN': ..., | ||
# 'EventType': 'origin-response', | ||
# 'IncludeBody': True | False | ||
# }, | ||
] | ||
}, | ||
'CachePolicyId': '4135ea2d-6df8-44a3-9df3-4b5a84be39ad', | ||
}, | ||
'CacheBehaviors': { | ||
'Quantity': 0, | ||
}, | ||
'Comment': 'cdn-bypass todo update description', | ||
'PriceClass': 'PriceClass_100', | ||
'Enabled': True, | ||
'ViewerCertificate': { | ||
'CloudFrontDefaultCertificate': True, | ||
'MinimumProtocolVersion': 'TLSv1.2_2018', | ||
}, | ||
'HttpVersion': 'http1.1', | ||
'IsIPV6Enabled': False, | ||
} | ||
) | ||
return resp['Distribution']['Id'] |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
from __future__ import print_function | ||
|
||
from time import time | ||
|
||
from cdn_bypass.lib import create_function, create_lambda_role, create_cloudfront_distribution | ||
|
||
|
||
class CdnBypass: | ||
def __init__(self, target): | ||
self.target = target | ||
|
||
def deploy(self): | ||
pass | ||
|
||
def destroy(self): | ||
pass | ||
|
||
|
||
LAMBDA_ROLE_NAME = 'cdn-bypass-lambda-execution' | ||
LAMBDA_FUNCTION_NAME = 'cdn-bypass-lambda-request' | ||
|
||
|
||
class CloudFrontBypass(CdnBypass): | ||
def __init__(self, sess, *args, **kwargs): | ||
self.sess = sess | ||
self.distribution_id = None | ||
super(CloudFrontBypass, self).__init__(*args, **kwargs) | ||
|
||
def deploy(self): | ||
create_lambda_role(LAMBDA_FUNCTION_NAME, LAMBDA_ROLE_NAME) | ||
create_function(LAMBDA_FUNCTION_NAME, LAMBDA_ROLE_NAME, self.target) | ||
self.distribution_id = create_cloudfront_distribution(self.target) | ||
print('CloudFront deployed') | ||
return self.distribution_id | ||
|
||
def destroy(self): | ||
client = self.sess.client('cloudfront', region_name='us-east-1') | ||
resp = client.delete_distribution(Id=self.distribution_id) | ||
print('CloudFront destroyed') |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
pytest=4.6 # Last version that supported python2 | ||
moto[lambda,iam] | ||
docker |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
botocore==1.20.112 # Last version that supported python2 | ||
boto3==1.17.112 # Last version that supported python2 | ||
boto3==1.17.112 # Last version that supported python2 |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import os | ||
|
||
import pytest | ||
import boto3 | ||
from moto import mock_iam, mock_lambda | ||
|
||
from cdn_bypass.lib import create_function, create_lambda_role | ||
|
||
|
||
@pytest.fixture | ||
def aws_credentials(): | ||
"""Mocked AWS Credentials for moto.""" | ||
os.environ['AWS_ACCESS_KEY_ID'] = 'testing' | ||
os.environ['AWS_SECRET_ACCESS_KEY'] = 'testing' | ||
os.environ['AWS_SECURITY_TOKEN'] = 'testing' | ||
os.environ['AWS_SESSION_TOKEN'] = 'testing' | ||
|
||
|
||
@pytest.fixture | ||
def sess(aws_credentials): | ||
with (mock_iam(), mock_lambda()): | ||
yield boto3.session.Session(region_name='us-east-1') | ||
|
||
|
||
# moto complains if you try to create a function using a role that doesn't exist, so these need to be tested together. | ||
def test_lib(sess): | ||
role_arn = create_lambda_role(sess, 'test-function', 'lambda-cfn') | ||
create_function(sess, 'test-function', role_arn, 'target') |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import pytest | ||
import boto3 | ||
|
||
from cdn_bypass.lib import create_cloudfront_distribution | ||
from cdn_bypass.main import CloudFrontBypass | ||
|
||
|
||
@pytest.fixture | ||
def sess(): | ||
return boto3.session.Session(region_name='us-east-1') | ||
|
||
|
||
@pytest.fixture | ||
def cloudfront(sess): | ||
return CloudFrontBypass(sess, 'example.com') | ||
|
||
|
||
def test_create_cloudfront_distribution(cloudfront): | ||
assert create_cloudfront_distribution(sess, 'target') |