Skip to content

Commit

Permalink
v0.2.7
Browse files Browse the repository at this point in the history
  • Loading branch information
gabstopper committed Jan 13, 2017
1 parent 1ed28ab commit cc5a612
Show file tree
Hide file tree
Showing 14 changed files with 761 additions and 48 deletions.
12 changes: 6 additions & 6 deletions deploy/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,13 @@ def create_vpc_and_ngfw(awscfg, ngfw):
vpc.security_group = create_security_group(vpc.vpc, 'stonesoft-sg')
authorize_security_group_ingress(vpc.security_group, '0.0.0.0/0', ip_protocol='-1')

# If user wants a client AMI, launch in the background
if awscfg.aws_client_ami:
logger.info('Launching client AMI with id: {}'.format(awscfg.aws_client_ami))
ngfw.aws_ami_ip = spin_up_host(awscfg.aws_keypair, vpc, awscfg.aws_client_ami)

ngfw_init = deploy(vpc, ngfw, awscfg)
return task_runner(ngfw_init)

# If user wants a client AMI, launch in the background
if awscfg.aws_client and awscfg.aws_client_ami:
spin_up_host(awscfg.aws_keypair, vpc, awscfg.aws_instance_type,
awscfg.aws_client_ami)

except (botocore.exceptions.ClientError, CreateEngineFailed,
NodeCommandFailed) as e:
Expand Down Expand Up @@ -186,7 +186,6 @@ def create_inline_ngfw(subnets, public, awscfg, ngfw, queue):
logger.error('Caught exception, rolling back: {}'.format(e))
queue.put(('{}, {}:'.format(subnets[0].availability_zone, subnets), [str(e)]))
rollback_existing_vpc(vpc, subnets)
print("Deleting FW: %s" % ngfw.name)
del_fw_from_smc([ngfw.name])

def create_as_nat_gateway(subnets, public, awscfg, ngfw, queue):
Expand Down Expand Up @@ -308,6 +307,7 @@ def deploy(vpc, ngfw, awscfg):

# Rename NGFW to AMI instance id (availability zone)
ngfw.rename('{} ({})'.format(instance.id, vpc.availability_zone))
ngfw.add_policy()
return ngfw

def main():
Expand Down
19 changes: 10 additions & 9 deletions deploy/aws.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,8 +401,8 @@ def wait_for_resource(resource, iterable):
return
time.sleep(2)

def spin_up_host(key_pair, vpc, instance_type='t2.micro',
aws_client_ami='ami-2d39803a'):
def spin_up_host(key_pair, vpc, aws_client_ami,
instance_type='t2.micro'):
"""
Create an internal amazon host on private subnet for testing
Use ubuntu AMI by default
Expand All @@ -422,6 +422,7 @@ def spin_up_host(key_pair, vpc, instance_type='t2.micro',
ntwk = data.get('PrivateIpAddress')
logger.info('Client instance created: {} with keypair: {} at ipaddress: {}'
.format(instance.id, instance.key_name, ntwk))
return ntwk

def map_az_to_subnet(subnets):
"""
Expand Down Expand Up @@ -516,8 +517,6 @@ def installed_as_list_view(vpc):
instance_id = instance.id
azone = instance.subnet.availability_zone

for intf in instance.network_interfaces:
print(intf.id, intf.private_ip_address, intf.subnet_id)
dt = '{:{dfmt} {tfmt}}'.format(instance.launch_time, dfmt='%Y-%m-%d', tfmt='%H:%M %Z')
instances.append((instance_id, azone, instance.instance_type,
instance.state.get('Name'), dt))
Expand Down Expand Up @@ -735,8 +734,8 @@ def rollback(vpc):
logger.info("Terminating instance: {}".format(instance.instance_id))
instance_ids.append(instance.instance_id)
instance.terminate()
waiter = ec2.meta.client.get_waiter('instance_terminated')
waiter.wait(InstanceIds=[instance.id])
waiter = ec2.meta.client.get_waiter('instance_terminated')
waiter.wait(InstanceIds=instance_ids)
# Network interfaces
for intf in vpc.network_interfaces.all():
intf.delete()
Expand Down Expand Up @@ -818,25 +817,27 @@ def validate_aws(awscfg, vpc_create=False):
missing.append('vpc_private')
if not awscfg.vpc_public:
missing.append('vpc_public')
if awscfg.aws_client_ami:
ec2.meta.client.describe_images(ImageIds=[awscfg.aws_client_ami])
if missing:
raise MissingRequiredInput('Missing required settings in configuration: {}'
.format(missing))

aws = namedtuple('aws', 'aws_keypair ngfw_ami aws_access_key_id aws_secret_access_key\
aws_client vpc_public vpc_private vpc_subnet aws_instance_type\
aws_client_ami vpc_public vpc_private vpc_subnet aws_instance_type\
aws_region')

def AWSConfig(aws_keypair, ngfw_ami,
aws_access_key_id=None,
aws_secret_access_key=None,
aws_client=False,
aws_client_ami=None,
vpc_public=None,
vpc_private=None,
vpc_subnet=None,
aws_instance_type='t2.micro',
aws_region=None, **kwargs):
return aws(aws_keypair, ngfw_ami, aws_access_key_id, aws_secret_access_key,
aws_client, vpc_public, vpc_private, vpc_subnet, aws_instance_type, aws_region)
aws_client_ami, vpc_public, vpc_private, vpc_subnet, aws_instance_type, aws_region)

def get_ec2_client(awscfg, prompt_for_region=False):
"""
Expand Down
2 changes: 1 addition & 1 deletion deploy/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,6 @@ def smc_creds():
for opt in SMC_CREDS:
yield opt

AWS_CLIENT = [fields('Enter AWS client AMI', 'aws_client_ami')]
AWS_CLIENT = [fields('Enter AWS client AMI (required)', 'aws_client_ami')]

FILE_PATH = [fields('Location for yaml file', 'path')]
50 changes: 47 additions & 3 deletions deploy/ngfw.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@
from smc.administration.tasks import Task
from smc.actions.search import element_by_href_as_json
from smc.elements.collection import describe_vpn, describe_fw_policy,\
describe_single_fw, describe_mgt_server, describe_log_server
describe_single_fw, describe_mgt_server, describe_log_server,\
describe_tcp_service, describe_alias, describe_host
from smc.elements.other import prepare_contact_address
from smc.elements.service import TCPService
from smc.policy.layer3 import FirewallPolicy

logger = logging.getLogger(__name__)

Expand All @@ -25,7 +28,7 @@ def __init__(self, dns=None, default_nat=True,
firewall_policy=None, vpn=None,
reverse_connection=False, nat_address=None,
**kwargs):
self.engine = None
self.engine = None #smc.core.engine.Engine
self.dns = dns if dns else []
self.default_nat = default_nat
self.antivirus = antivirus
Expand All @@ -35,6 +38,7 @@ def __init__(self, dns=None, default_nat=True,
self.vpn = vpn
self.firewall_policy = firewall_policy
self.reverse_connection = reverse_connection
self.aws_ami_ip = None #IP used for client AMI rules
# Unique temporary name
uid = uuid.uuid4()
self.name = uid.hex
Expand Down Expand Up @@ -115,6 +119,41 @@ def upload_policy(self):
except TaskRunFailed as e:
logger.error(e)

def add_policy(self):
"""
If a client AMI was specified when building a new VPC, this will add
rules to allow inbound access to the AMI. This could be extended to
more generically support VPN rules.
"""
if self.aws_ami_ip:
services = describe_tcp_service(name='2222')
if not services:
TCPService.create('ssh_2222', 2222)
service = (TCPService('ssh_2222'))

alias = describe_alias(name='$$ Interface ID 0.ip')

# Create
f = FirewallPolicy(self.firewall_policy)
f.fw_ipv4_access_rules.create(name=self.engine.name,
sources='any',
destinations=[alias[0].href],
services=[service.href],
action='allow')

f.fw_ipv4_nat_rules.create(name=self.engine.name,
sources='any',
destinations=[alias[0].href],
services=[service.href],
static_dst_nat={'original_value': {
'max_port':2222,
'min_port':2222},
'translated_value': {
'ip_descriptor': self.aws_ami_ip,
'max_port':22,
'min_port':22}},
)

def add_location(self, location_name):
"""
Create a unique Location for the AWS Firewall if the NAT address is set.
Expand All @@ -123,7 +162,6 @@ def add_location(self, location_name):
:return: str of location or None
"""
#TODO: If vpn policy isnt required, use a common NAT element?
if self.nat_address: #SMC behind NAT
# Add to management server
mgt = describe_mgt_server()
Expand Down Expand Up @@ -234,6 +272,12 @@ def del_fw_from_smc(instance_ids):
for server in log:
server.remove_contact_address(location_ref)
del_from_smc_vpn_policy(fw.name)
# Quick search to delete rules
policy = fw.nodes[0].status().installed_policy
f = FirewallPolicy(policy)
search = f.search_rule(fw.name)
for rules in search:
rules.delete()
response = fw.delete()
if response.msg:
logger.error('Could not delete fw: {}, {}'
Expand Down
64 changes: 36 additions & 28 deletions deploy/validators.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import sys
import ipaddress
import yaml
from deploy.ngfw import get_smc_session
Expand Down Expand Up @@ -35,8 +36,14 @@ def __init__(self, field_name):
self.field_name = field_name

def validate(self, data):
try:
ipaddress.IPv4Network(data)
try:
if len(data.split('/')) != 2:
raise ValueError('Invalid CIDR syntax')
# need to be unicode for py27
if sys.version_info > (3,):
ipaddress.IPv4Network(data)
else:
ipaddress.IPv4Network(u'{}'.format(data))
except (ValueError, ipaddress.AddressValueError) as e:
raise ValidationError(e)
return {self.field_name: data}
Expand All @@ -47,17 +54,14 @@ def __init__(self, field_name, default_val):
self.default_val = default_val

def validate(self, data):
if not isinstance(data, str) or len(data) == 0:
if len(data) == 0:
data = self.default_val
elif isinstance(self.default_val, bool):
if data.lower() == 'true':
data = True
elif data.lower() == 'false':
data = False
else:
data = self.default_val
if data.lower().startswith('yes'):
data = True
elif data.lower().startswith('no'):
data = False
return {self.field_name: data}

class ChoiceValidator(Validator):
def __init__(self, field_name):
self.field_name = field_name
Expand Down Expand Up @@ -101,10 +105,10 @@ def prompt(opt):
return validate.validate(value)

except (ValidationError) as e:
print("Invalid choice: %s" % e)
print('Invalid choice. {}'.format(e.message))

def write_cfg_to_yml(data, path=None):
""" Writing collected data to yml """
# Write out yml
with open(path, 'w') as yaml_file:
yaml.safe_dump(data, yaml_file, default_flow_style=False)
print('Wrote ngfw-deploy.yml to dir %s' % path)
Expand Down Expand Up @@ -144,20 +148,20 @@ def prompt_user(path=None):
suite.add_validator(DefaultValidator('smc_address', ''))
suite.add_validator(RequiredValidator('smc_apikey'))
suite.add_validator(DefaultValidator('smc_port', '8082'))
suite.add_validator(DefaultValidator('smc_ssl', False))
suite.add_validator(DefaultValidator('verify_ssl', False))
suite.add_validator(DefaultValidator('smc_ssl', 'Yes'))
suite.add_validator(DefaultValidator('verify_ssl', 'Yes'))
suite.add_validator(RequiredValidator('ssl_cert_file'))
suite.add_validator(DefaultValidator('dns', '8.8.8.8'))
suite.add_validator(DefaultValidator('default_nat', True))
suite.add_validator(DefaultValidator('antivirus', False))
suite.add_validator(DefaultValidator('gti', False))
suite.add_validator(DefaultValidator('vpn', False))
suite.add_validator(DefaultValidator('default_nat', 'Yes'))
suite.add_validator(DefaultValidator('antivirus', 'No'))
suite.add_validator(DefaultValidator('gti', 'No'))
suite.add_validator(DefaultValidator('vpn', 'No'))
suite.add_validator(ChoiceValidator('firewall_policy'))
suite.add_validator(ChoiceValidator('vpn_policy'))
suite.add_validator(DefaultValidator('vpn_role', 'central'))
suite.add_validator(DefaultValidator('vpn_networks', ''))
suite.add_validator(DefaultValidator('nat_address', ''))
suite.add_validator(DefaultValidator('vpc', False))
suite.add_validator(DefaultValidator('vpc', 'No'))
suite.add_validator(IPSubnetValidator('vpc_subnet'))
suite.add_validator(IPSubnetValidator('vpc_private'))
suite.add_validator(IPSubnetValidator('vpc_public'))
Expand All @@ -167,7 +171,7 @@ def prompt_user(path=None):
suite.add_validator(RequiredValidator('aws_keypair'))
suite.add_validator(RequiredValidator('ngfw_ami'))
suite.add_validator(DefaultValidator('aws_instance_type', 't2.micro'))
suite.add_validator(DefaultValidator('aws_client', False))
suite.add_validator(DefaultValidator('aws_client', 'No'))
suite.add_validator(RequiredValidator('aws_client_ami'))
suite.add_validator(DefaultValidator('path', '{}/ngfw-deploy.yml'.format(expanduser("~"))))

Expand Down Expand Up @@ -196,7 +200,7 @@ def prompt_user(path=None):
break
except SMCConnectionError as e:
print('Failed connecting to SMC: {}'.format(e))

fw={}
for opt in FW:
fw.update(prompt(opt))
Expand All @@ -206,8 +210,10 @@ def prompt_user(path=None):
vpn_sub.update(prompt(opt))
if vpn_sub.get('vpn_networks'):
vpn_sub.update(vpn_networks=vpn_sub.get('vpn_networks').split(','))
fw.update(vpn=vpn_sub)
else:
fw.pop('vpn', None)
fw.update(dns=fw.get('dns').split(','))
fw.update(vpn=vpn_sub)
data.update({'NGFW': fw})

aws = {}
Expand All @@ -219,24 +225,26 @@ def prompt_user(path=None):
if aws.get('aws_access_key_id'):
continue
break

if not aws.get('aws_access_key_id'):
aws.pop('aws_access_key_id', None)

print(AWS_REQ_BANNER)
for opt in AWS_REQ:
aws.update(prompt(opt))

print(AWS_OPT_BANNER)
for opt in AWS_OPT_ASK:
print(opt)
if prompt(opt).get('vpc'):
for opt in AWS_OPT:
aws.update(prompt(opt))
if aws.get('aws_client'):
for opt in AWS_CLIENT:
aws.update(prompt(opt))

if not aws.get('aws_access_key_id'):
aws.pop('aws_access_key_id', None)
data.update({'AWS': aws})
aws.pop('aws_client')

data.update({'AWS': aws})

path = prompt(FILE_PATH[0]).get('path')
write_cfg_to_yml(data, path)
return path
Expand Down
20 changes: 20 additions & 0 deletions docs/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#

# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SPHINXPROJ = StonesoftNGFWDeployforAWS
SOURCEDIR = .
BUILDDIR = _build

# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

.PHONY: help Makefile

# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
Loading

0 comments on commit cc5a612

Please sign in to comment.