def get_template(region, coreos_ami, dns_suffix): cft = CloudFormationTemplate(description='Core OS on EC2 app cluster') data = { 'region': region, 'coreos_ami': coreos_ami, 'dns_suffix': dns_suffix } for Conf in CONFIGs: data.update(Conf(data=data).add(cft)) return cft
def subnet_az(index): azs = availability_zones[options['region']] return azs[index % len(azs)] def resolve_host(hostname): ips = dns.resolver.query(hostname, "A") # sort these so that we deterministically set up the same route # resources (until DNS changes) ips = sorted([i.to_text() for i in ips]) return ips # VPC cft = CloudFormationTemplate(description="Release Engineering network configuration") cft.resources.add(Resource( 'RelengVPC', 'AWS::EC2::VPC', Properties({ 'CidrBlock': subnet_cidr('0.0', 16), 'Tags': [nametag('Releng Network')], }) )) # DHCP options cft.resources.add(Resource( 'DHCPOptions', 'AWS::EC2::DHCPOptions', Properties({ # point to the onsite, IT-managed DNS servers
def make_storage_template(): cft = CloudFormationTemplate(description="Refinery Platform storage") # Parameters cft.parameters.add( Parameter('StaticBucketName', 'String', { 'Description': 'Name of S3 bucket for Django static files', })) cft.parameters.add( Parameter( 'MediaBucketName', 'String', { 'Description': 'Name of S3 bucket for Django media files', # make names DNS-compliant without periods (".") for # compatibility with virtual-hosted-style access and S3 # Transfer Acceleration 'AllowedPattern': '[a-z0-9\-]+', 'ConstraintDescription': 'must only contain lower case letters, numbers, and ' 'hyphens', })) cft.parameters.add( Parameter( 'IdentityPoolName', 'String', { 'Default': 'Refinery Platform', 'Description': 'Name of Cognito identity pool for S3 uploads', })) cft.parameters.add( Parameter( 'DeveloperProviderName', 'String', { 'Default': 'login.refinery', 'Description': '"domain" by which Cognito will refer to users', 'AllowedPattern': '[a-z\-\.]+', 'ConstraintDescription': 'must only contain lower case letters, periods, ' 'underscores, and hyphens' })) # Resources cft.resources.add( Resource( 'StaticStorageBucket', 'AWS::S3::Bucket', Properties({ 'BucketName': ref('StaticBucketName'), 'AccessControl': 'PublicRead', 'CorsConfiguration': { 'CorsRules': [{ 'AllowedOrigins': ['*'], 'AllowedMethods': ['GET'], 'AllowedHeaders': ['Authorization'], 'MaxAge': 3000, }] }, }), DeletionPolicy('Retain'), )) cft.resources.add( Resource( 'MediaStorageBucket', 'AWS::S3::Bucket', Properties({ 'BucketName': ref('MediaBucketName'), 'AccessControl': 'PublicRead', 'CorsConfiguration': { 'CorsRules': [{ 'AllowedOrigins': ['*'], 'AllowedMethods': ['POST', 'PUT', 'DELETE'], 'AllowedHeaders': ['*'], 'ExposedHeaders': ['ETag'], 'MaxAge': 3000, }] } }), DeletionPolicy('Retain'), )) # Cognito Identity Pool for Developer Authenticated Identities Authflow # http://docs.aws.amazon.com/cognito/latest/developerguide/authentication-flow.html cft.resources.add( Resource( 'IdentityPool', 'AWS::Cognito::IdentityPool', Properties({ 'IdentityPoolName': ref('IdentityPoolName'), 'AllowUnauthenticatedIdentities': False, 'DeveloperProviderName': ref('DeveloperProviderName'), }))) cft.resources.add( Resource( 'IdentityPoolAuthenticatedRole', 'AWS::Cognito::IdentityPoolRoleAttachment', Properties({ 'IdentityPoolId': ref('IdentityPool'), 'Roles': { 'authenticated': get_att('CognitoS3UploadRole', 'Arn'), } }))) upload_role_trust_policy = { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Principal": { "Federated": "cognito-identity.amazonaws.com" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "cognito-identity.amazonaws.com:aud": ref('IdentityPool') }, "ForAnyValue:StringLike": { "cognito-identity.amazonaws.com:amr": "authenticated" } } }] } upload_access_policy = { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action": ["cognito-identity:*"], "Resource": "*" }, { "Action": ["s3:PutObject", "s3:AbortMultipartUpload"], "Effect": "Allow", "Resource": { "Fn::Sub": "arn:aws:s3:::${MediaStorageBucket}/uploads/" "${!cognito-identity.amazonaws.com:sub}/*" } }] } cft.resources.add( Resource( 'CognitoS3UploadRole', 'AWS::IAM::Role', Properties({ 'AssumeRolePolicyDocument': upload_role_trust_policy, 'Policies': [{ 'PolicyName': 'AuthenticatedS3UploadPolicy', 'PolicyDocument': upload_access_policy, }] }))) # Outputs cft.outputs.add( Output('IdentityPoolId', ref('IdentityPool'), {'Fn::Sub': '${AWS::StackName}IdentityPoolId'}, 'Cognito identity pool ID')) return cft
# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. from cfn_pyplates.core import CloudFormationTemplate, Resource from cfn_pyplates.core import Properties, options from utils import nametag cft = CloudFormationTemplate(description="Tooltool Infrastructure") rgn = options['region'] # production cft.resources.add(Resource( 'FileBucket', 'AWS::S3::Bucket', Properties({ "AccessControl": "Private", "BucketName": "mozilla-releng-%s-tooltool" % (rgn,), 'Tags': [nametag('Tooltool File Storage - %s' % (rgn,))], }) )) # staging cft.resources.add(Resource( 'StagingFileBucket', 'AWS::S3::Bucket', Properties({ "AccessControl": "Private", "BucketName": "mozilla-releng-staging-%s-tooltool" % (rgn,), 'Tags': [nametag('Tooltool File Storage - Staging - %s' % (rgn,))],
def make_storage_template(): cft = CloudFormationTemplate(description="Refinery Platform storage") # Parameters cft.parameters.add( Parameter('StaticBucketName', 'String', { 'Description': 'Name of S3 bucket for Django static files', })) cft.parameters.add( Parameter( 'MediaBucketName', 'String', { 'Description': 'Name of S3 bucket for Django media files', # make names DNS-compliant without periods (".") for compatibility # with virtual-hosted-style access and S3 Transfer Acceleration 'AllowedPattern': '[a-z0-9\-]+', 'ConstraintDescription': 'must only contain lower case letters, numbers, and hyphens', })) # Resources cft.resources.add( Resource( 'StaticStorageBucket', 'AWS::S3::Bucket', Properties({ 'BucketName': ref('StaticBucketName'), 'AccessControl': 'PublicRead', 'CorsConfiguration': { 'CorsRules': [{ 'AllowedOrigins': ['*'], 'AllowedMethods': ['GET'], 'AllowedHeaders': ['Authorization'], 'MaxAge': 3000, }] }, }), DeletionPolicy('Retain'), )) cft.resources.add( Resource( 'MediaStorageBucket', 'AWS::S3::Bucket', Properties({ 'BucketName': ref('MediaBucketName'), 'AccessControl': 'PublicRead', 'CorsConfiguration': { 'CorsRules': [{ 'AllowedOrigins': ['*'], 'AllowedMethods': ['POST', 'PUT', 'DELETE'], 'AllowedHeaders': ['*'], 'ExposedHeaders': ['ETag'], 'MaxAge': 3000, }] } }), DeletionPolicy('Retain'), )) cft.outputs.add( Output('MediaBucketName', ref('MediaStorageBucket'), {'Fn::Sub': '${AWS::StackName}Media'}, 'Name of S3 bucket for Django media files')) return cft
def build_template(args): """ Build a CloudFormation template allowing for secure CloudTrail log aggregation and fine grained access control to SNS topics for notifications of new CloudTrail logs The reason that we create IAM roles for each client AWS account in order to enable clients to read their own CloudTrail logs, instead of merely delegating access to them in an S3 bucket policy is that "Bucket owner account can delegate permissions to users in its own account, but it cannot delegate permissions to other AWS accounts, because cross-account delegation is not supported." : http://docs.aws.amazon.com/AmazonS3/latest/dev/example-walkthroughs-managing-access-example4.html As a consequence we *can* delegate bucket permissions to client AWS accounts but we *can not* delegate object permissions (the log files themselves) to client AWS accounts. Example config : AccountRootARNs: - arn:aws:iam::012345678901:root # Sales - arn:aws:iam::123456789012:root # HR - arn:aws:iam::234567890123:root # Marketing CloudTrailLogConsumers: - arn:aws:iam::345678901234:user/security_team # Security team user - TrustedARN: arn:aws:iam::456789012343:root # CloudCo Third Party TrustingARNs: - arn:aws:iam::012345678901:root # Sales - arn:aws:iam::234567890123:root # Marketing - TrustedARN: arn:aws:iam::567890123434:root # Other.com Third Party TrustingARNs: - arn:aws:iam::123456789012:root # HR ForeignAccountStatusSubscribers: - arn:aws:iam::345678901234:root # Security Team """ config = args.config account_root_arns = ( config['AccountRootARNs'] if 'AccountRootARNs' in config and isinstance(config['AccountRootARNs'], list) else []) cft = CloudFormationTemplate( description="AWS CloudTrail Storage Account S3 Storage Bucket") # Create the bucket cft.resources.add( Resource("S3Bucket", "AWS::S3::Bucket", {"BucketName": args.bucketname}, DeletionPolicy("Retain"))) # Build the s3 bucket policy statement list bucket_policy_statements = [] # Allow the CloudTrail system to GetBucketAcl on the CloudTrail storage # bucket bucket_policy_statements.append({ "Sid": "AWSCloudTrailAclCheck", "Effect": "Allow", "Principal": { "Service": "cloudtrail.amazonaws.com" }, "Action": ["s3:GetBucketAcl"], "Resource": join("", "arn:aws:s3:::", ref("S3Bucket")) }) # Allow each account to read it's own logs for account_arn in account_root_arns: account_id = get_account_id_from_arn(account_arn) cft.resources.add( Resource( "CloudTrailLogReaderRole%s" % account_id, "AWS::CloudFormation::Stack", { "TemplateURL": "https://s3.amazonaws.com/infosec-cloudformation-templates/manage_iam_role.json", "Parameters": { "RoleName": "CloudTrailLogReader%s" % account_id, "TrustedEntities": get_consumer_arns( account_arn, config) }, "TimeoutInMinutes": "5" })) cft.resources.add( Resource( "CloudTrailLogReaderPolicy%s" % account_id, "AWS::IAM::Policy", { "PolicyName": "CloudTrailLogReaderPolicy%s" % account_id, "PolicyDocument": { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action": "s3:GetObject", "Resource": join("", "arn:aws:s3:::", ref("S3Bucket"), "/AWSLogs/%s/*" % account_id) }] }, "Roles": ["CloudTrailLogReader%s" % account_id] }, DependsOn("CloudTrailLogReaderRole%s" % account_id))) cft.resources.add( Resource( "ReadCloudTrailBucket", "AWS::IAM::ManagedPolicy", { "Description": "ReadCloudTrailBucket", "PolicyDocument": { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action": ["s3:ListAllMyBuckets", "s3:GetBucketLocation"], "Resource": "*" }, { "Effect": "Allow", "Action": [ "s3:GetBucketAcl", "s3:ListBucket", "s3:GetBucketTagging" ], "Resource": join("", "arn:aws:s3:::", ref("S3Bucket")) }] }, "Roles": [ "CloudTrailLogReader%s" % get_account_id_from_arn(account_arn) for account_arn in account_root_arns ] }, DependsOn([ "CloudTrailLogReaderRole%s" % get_account_id_from_arn(account_arn) for account_arn in account_root_arns ]))) bucket_policy_statements.append({ # "Sid":"AWSCloudTrailWrite%s" % get_account_id_from_arn(account_arn), "Effect": "Allow", "Principal": { "Service": "cloudtrail.amazonaws.com" }, "Action": ["s3:PutObject"], "Resource": join("", "arn:aws:s3:::", ref("S3Bucket"), "/AWSLogs/*"), "Condition": { "StringEquals": { "s3:x-amz-acl": "bucket-owner-full-control" } } }) # Apply the bucket policy to the bucket cft.resources.add( Resource( "BucketPolicy", "AWS::S3::BucketPolicy", { "Bucket": ref("S3Bucket"), "PolicyDocument": { "Id": "BucketPolicyDocument", "Version": "2012-10-17", "Statement": bucket_policy_statements } })) # Create a single SNS Topic that each AWS account can publish to to report # on the CloudFormation progress cft.resources.add( Resource( "ForeignAccountStatusTopic", "AWS::SNS::Topic", { "DisplayName": "Topic for foreign accounts to publish status information to", "TopicName": "ForeignAccountStatus" })) cft.resources.add( Resource( "ForeignAccountStatusTopicPolicy", "AWS::SNS::TopicPolicy", { "Topics": [ref("ForeignAccountStatusTopic")], "PolicyDocument": { "Version": "2012-10-17", "Id": "ForeignAccountStatusPolicy", "Statement": [{ "Sid": "ForeignAccountStatusPublisher", "Effect": "Allow", "Principal": { "AWS": account_root_arns }, "Action": "SNS:Publish", "Resource": ref("ForeignAccountStatusTopic"), }, { "Sid": "ForeignAccountStatusSubscriber", "Effect": "Allow", "Principal": { "AWS": config['ForeignAccountStatusSubscribers'] }, "Action": [ "SNS:GetTopicAttributes", "SNS:ListSubscriptionsByTopic", "SNS:Subscribe" ], "Resource": ref("ForeignAccountStatusTopic"), }] } })) # Create SNS Topics for each AWS account and grant those accounts rights # to publish and subscribe to those topics for account_arn in account_root_arns: account_id = get_account_id_from_arn(account_arn) cft.resources.add( Resource( "Topic%s" % account_id, "AWS::SNS::Topic", { "DisplayName": "Mozilla CloudTrail Logs Topic for Account %s" % account_id, "TopicName": "MozillaCloudTrailLogs%s" % account_id })) # http://docs.aws.amazon.com/sns/latest/dg/AccessPolicyLanguage_UseCases_Sns.html#AccessPolicyLanguage_UseCase4_Sns cft.resources.add( Resource( "TopicPolicy%s" % account_id, "AWS::SNS::TopicPolicy", { "Topics": [ref("Topic%s" % account_id)], "PolicyDocument": { "Version": "2012-10-17", "Id": "AWSCloudTrailSNSPolicy%s" % account_id, "Statement": [{ "Sid": "CloudTrailSNSPublish%s" % account_id, "Effect": "Allow", "Principal": { "Service": "cloudtrail.amazonaws.com" }, "Action": "SNS:Publish", "Resource": ref("Topic%s" % account_id) }, { "Sid": "CloudTrailSNSSubscribe%s" % account_id, "Effect": "Allow", "Principal": { "AWS": account_arn }, "Action": [ "SNS:GetTopicAttributes", "SNS:ListSubscriptionsByTopic", "SNS:Subscribe" ], "Resource": join(":", "arn:aws:sns", ref("AWS::Region"), ref("AWS::AccountId"), "MozillaCloudTrailLogs%s" % account_id) }] } })) return cft
# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. from cfn_pyplates.core import CloudFormationTemplate, Resource from cfn_pyplates.core import Properties, options from utils import policy is_prod = options['prod'] cft = CloudFormationTemplate( description="IAM for RelengAPI" if is_prod else "IAM For RelengAPI Staging") # production # RelengAPI is configured with an access/secret keypair for these users, but # that keypair is not created here; it is generated via the console once the # stack is deployed. # Note that you cannot control the physical name of the users; they are ugly # autogenerated names. See # http://stackoverflow.com/questions/16185109/how-to-set-user-name-and-group-name-in-iam-using-cloudformation name = 'RelengAPI' if is_prod else 'RelengAPIStage' def object_arn(rgn): return bucket_arn(rgn) + '/*' def bucket_arn(rgn):
# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. from cfn_pyplates.core import CloudFormationTemplate, Resource from cfn_pyplates.core import Properties, options from utils import nametag from utils import sgcidr cft = CloudFormationTemplate(description="IT Resources") cft.resources.add(Resource( 'NagiosSG', 'AWS::EC2::SecurityGroup', Properties({ 'GroupDescription': 'Nagios Servers', 'Tags': [nametag('nagios')], 'VpcId': options['vpcid'], 'SecurityGroupIngress': [ sgcidr('10.22.8.128/32', -1, -1), sgcidr('10.22.20.0/25', -1, -1), sgcidr('10.22.72.136/32', -1, -1), sgcidr('10.22.72.155/32', -1, -1), sgcidr('10.22.72.158/32', -1, -1), sgcidr('10.22.72.159/32', -1, -1), sgcidr('10.22.75.5/32', -1, -1), sgcidr('10.22.75.6/31', -1, -1), sgcidr('10.22.240.0/20', -1, -1), sgcidr('10.22.74.22/32', -1, -1), sgcidr('10.22.75.30/32', -1, -1), sgcidr('10.22.75.36/32', 'tcp', 22), sgcidr('10.22.75.136/32', 'udp', 161),
# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. from cfn_pyplates.core import CloudFormationTemplate, Resource from cfn_pyplates.core import Properties, options from utils import nametag cft = CloudFormationTemplate(description="Archiver Infrastructure") rgn = options['region'] # production cft.resources.add(Resource( 'FileBucket', 'AWS::S3::Bucket', Properties({ "AccessControl": "Private", "BucketName": "mozilla-releng-%s-archiver" % (rgn,), 'Tags': [nametag('Archiver Archive Storage - %s' % (rgn,))], }) )) # staging cft.resources.add(Resource( 'StagingFileBucket', 'AWS::S3::Bucket', Properties({ "AccessControl": "Private", "BucketName": "mozilla-releng-staging-%s-archiver" % (rgn,), 'Tags': [nametag('Archiver Archive Storage - Staging - %s' % (rgn,))],