diff --git a/.gitignore b/.gitignore index 8900dc55..f7bc66ae 100644 --- a/.gitignore +++ b/.gitignore @@ -74,8 +74,9 @@ gradle-app.setting # Python cache **/__pycache__/ - -# Ignore generated CF templates -src/main/resources/cloudformation/** -!src/main/resources/cloudformation/.gitkeep -/smaas-cf/.idea/ +/certs/ +build/certs/ +*.pem +*.key +*.crt +*.csr diff --git a/.travis.yml b/.travis.yml index 02de1a38..04704516 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,10 +3,6 @@ language: java dist: trusty jdk: - oraclejdk8 -before_install: - - sudo apt-get -y install python3-pip python-dev - - python3 -V - - pip3 -V deploy: skip_cleanup: true provider: releases diff --git a/build.gradle b/build.gradle index 1ed6a58a..c0636fe7 100644 --- a/build.gradle +++ b/build.gradle @@ -15,7 +15,7 @@ apply from: file('gradle/integration.gradle') mainClassName = 'com.nike.cerberus.cli.CerberusRunner' -task wrapper (type: Wrapper) { +task wrapper(type: Wrapper) { gradleVersion = '3.4' } @@ -35,32 +35,6 @@ task wrapper (type: Wrapper) { * limitations under the License. */ -/** - * Executes the python code that uses Troposphere to generate the cloud formation needed to - */ -task createCloudFormation() { - try { - def requirementsPath = "${getProject().getRootDir()}${File.separator}smaas-cf${File.separator}requirements.txt" - executeOnShell "pip3 install -r ${requirementsPath}" - - [ - 'consul-cluster.py', - 'vault-cluster.py', - 'vpc-and-base.py', - 'gateway-cluster.py', - 'cms-cluster.py', - 'cloudfront-elb-security-group-updater-lambda.py' - ].each { String script -> - executeOnShell " PYTHONPATH=${getProject().getRootDir()}${File.separator}smaas-cf python3 ${script}", new File("${getProject().getRootDir()}${File.separator}smaas-cf${File.separator}smaas${File.separator}"), true - } - } catch (Throwable t) { - logger.lifecycle t.getMessage() - } -} - -tasks.shadowJar.dependsOn createCloudFormation -tasks.assemble.dependsOn createCloudFormation - shadowJar { def releaseVersion = version doFirst { @@ -72,32 +46,3 @@ shadowJar { } tasks.assemble.finalizedBy shadowJar - -/** - * Runs a command on the command line, streaming the output to STDOUT - */ -def executeOnShell(String command, File workingDir = new File("./"), boolean print = false) { - logger.debug "Executing command: ${command}" - def commandArray = new String[3] - commandArray[0] = "sh" - commandArray[1] = "-c" - commandArray[2] = command - def process = new ProcessBuilder(commandArray) - .directory(workingDir) - .redirectErrorStream(true) - .start() - def stdout = [] - process.inputStream.eachLine { line -> - stdout.add line - if (print) { - logger.lifecycle line - } - } - process.waitFor(); - if (process.exitValue()) { - def msg = "Failed to execute command exiting" - logger.lifecycle msg - throw new GradleException(msg) - } - return stdout -} diff --git a/gradle.properties b/gradle.properties index a317a055..00730170 100644 --- a/gradle.properties +++ b/gradle.properties @@ -16,4 +16,4 @@ group=com.nike artifactId=cerberus-lifecycle-cli -version=3.3.1 +version=4.0.0 diff --git a/gradle/check.gradle b/gradle/check.gradle index e3445ba8..280a90f2 100644 --- a/gradle/check.gradle +++ b/gradle/check.gradle @@ -22,11 +22,11 @@ apply plugin: 'com.github.kt3k.coveralls' findbugs { ignoreFailures = false excludeFilter = rootProject.file('codequality/findbugs-suppressions.xml') - sourceSets = [ project.sourceSets.main ] + sourceSets = [project.sourceSets.main] } pmd { - sourceSets = [ project.sourceSets.main ] + sourceSets = [project.sourceSets.main] } tasks.withType(FindBugs) { diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 85795e49..91492b8e 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -19,7 +19,7 @@ allprojects { jcenter() } - def awsSDKVersion = '1.11.155' + def awsSDKVersion = '1.11.229' //noinspection GroovyAssignabilityCheck dependencies { @@ -33,21 +33,33 @@ allprojects { compile group: 'com.amazonaws', name: 'aws-java-sdk-sts', version: awsSDKVersion compile group: 'com.amazonaws', name: 'aws-java-sdk-sns', version: awsSDKVersion compile group: 'com.amazonaws', name: 'aws-java-sdk-lambda', version: awsSDKVersion + compile group: 'com.amazonaws', name: 'aws-java-sdk-route53', version: awsSDKVersion + compile group: 'com.amazonaws', name: 'aws-java-sdk-elasticloadbalancingv2', version: awsSDKVersion + compile group: 'com.amazonaws', name: 'aws-java-sdk-rds', version: awsSDKVersion + + // https://mvnrepository.com/artifact/com.amazonaws/aws-encryption-sdk-java + compile group: 'com.amazonaws', name: 'aws-encryption-sdk-java', version: '1.3.1' compile 'com.nike:vault-client:1.4.1' compile 'com.squareup.okhttp3:okhttp:3.3.1' - compile 'com.beust:jcommander:1.55' + compile 'com.beust:jcommander:1.55' // newer version up to at least 1.71 have broken variableArity, which breaks create cms cmk command compile 'com.fasterxml.jackson.core:jackson-core:2.7.+' compile 'com.fasterxml.jackson.core:jackson-databind:2.7.+' compile 'com.fasterxml.jackson.core:jackson-annotations:2.7.+' compile 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.7+' compile 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.8.5' + compile group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-joda', version: '2.4.3' + compile group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jdk8', version: '2.9.2' + compile 'org.slf4j:slf4j-api:1.7.+' compile 'ch.qos.logback:logback-classic:1.1.+' compile 'commons-cli:commons-cli:1.3.1' - compile 'com.google.inject:guice:4.0' + compile 'com.google.inject:guice:4.1.0' + compile group: 'com.google.inject.extensions', name: 'guice-multibindings', version: '4.1.0' + compile group: 'com.google.guava', name: 'guava', version: '23.5-jre' + compile 'com.google.code.findbugs:jsr305:3.0.+' compile 'com.github.tomas-langer:chalk:1.0.2' compile 'commons-net:commons-net:3.4' @@ -57,9 +69,29 @@ allprojects { compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.6' compile group: 'com.google.code.findbugs', name: 'annotations', version: '3.0.0' + // Cert Generation + compile group: 'dnsjava', name: 'dnsjava', version: '2.1.8' + compile group: 'org.shredzone.acme4j', name: 'acme4j-client', version: '0.13' + compile group: 'org.shredzone.acme4j', name: 'acme4j-utils', version: '0.13' + testCompile "junit:junit:4.12" - testCompile ("org.mockito:mockito-core:1.10.19") { + testCompile("org.mockito:mockito-core:1.10.19") { exclude group: 'org.hamcrest' } + testCompile 'com.fieldju:commons:1.2.0' + testCompile group: 'io.netty', name: 'netty-handler', version: '4.1.16.Final' + + } + + configurations.all { + // check for updates every build + resolutionStrategy { + cacheChangingModulesFor 0, 'seconds' + + // aws-encryption-sdk-java brings in a slightly older version with some constants missing + def bouncyVersion = 1.58 + force "org.bouncycastle:bcpkix-jdk15on:$bouncyVersion", "org.bouncycastle:bcprov-ext-jdk15on:$bouncyVersion" + } } + } diff --git a/gradle/integration.gradle b/gradle/integration.gradle index 4195bccb..f2780c7e 100644 --- a/gradle/integration.gradle +++ b/gradle/integration.gradle @@ -33,4 +33,10 @@ configurations { task integrationTest(type: Test) { testClassesDir = sourceSets.integrationTest.output.classesDir classpath = sourceSets.integrationTest.runtimeClasspath +} + +integrationTest { + testLogging { + showStandardStreams = true + } } \ No newline at end of file diff --git a/smaas-cf/requirements.txt b/smaas-cf/requirements.txt deleted file mode 100644 index 1011592b..00000000 --- a/smaas-cf/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -boto3==1.3.1 -troposphere==1.8.1 diff --git a/smaas-cf/smaas/__init__.py b/smaas-cf/smaas/__init__.py deleted file mode 100644 index 480e9246..00000000 --- a/smaas-cf/smaas/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Placeholder to force package structure diff --git a/smaas-cf/smaas/cloudfront-elb-security-group-updater-lambda.py b/smaas-cf/smaas/cloudfront-elb-security-group-updater-lambda.py deleted file mode 100644 index 05169795..00000000 --- a/smaas-cf/smaas/cloudfront-elb-security-group-updater-lambda.py +++ /dev/null @@ -1,148 +0,0 @@ -### -# Copyright (c) 2016 Nike Inc. -# -# 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. -### - -from io import open -from troposphere import Ref, Template, GetAtt, AWS_ACCOUNT_ID, Parameter, Output -from troposphere.iam import Role, Policy -from troposphere.awslambda import Function, Code, Permission - -CF_FILE = '../../src/main/resources/cloudformation/cloudfront-elb-security-group-updater-lambda.json' -CLOUD_FRONT_PREFIX = 'cf-logs/' - -# Create Template -template = Template() -template.description = "Launches the gateway cluster in the Cerberus VPC" -template.version = '2010-09-09' - -### -# -# Inputs -# -### - -lambda_bucket = template.add_parameter(Parameter( - "lambdaBucket", - Type="String", - Description="S3 Bucket for lambda function artifact" -)) - -lambda_key = template.add_parameter(Parameter( - "lambdaKey", - Type="String", - Description="Key for lambda function artifact" -)) - - -### -# -# IAM roles -# -### - -cloud_front_origin_elb_sg_ip_sync_lambda_iam_role = template.add_resource(Role( - "CloudFrontOriginElbSgIpSyncLambdaIamRole", - AssumeRolePolicyDocument={ - "Version": "2012-10-17", - "Statement": [{ - "Effect": "Allow", - "Principal": { - "Service": ["lambda.amazonaws.com"] - }, - "Action": ["sts:AssumeRole"] - }] - }, - Policies=[ - Policy( - PolicyName="cloud_front_origin_elb_sg_ip_sync_lambda_iam_role_policy", - PolicyDocument={ - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents" - ], - "Resource": "arn:aws:logs:*:*:*" - }, - { - "Effect": "Allow", - "Action": [ - "ec2:DescribeSecurityGroups", - "ec2:AuthorizeSecurityGroupIngress", - "ec2:RevokeSecurityGroupIngress" - ], - "Resource": "*" - } - ] - } - ) - ], - Path="/" -)) - -### -# -# Lambda Function -# -### - -cloud_front_origin_elb_sg_ip_sync_function = template.add_resource(Function( - "CloudFrontOriginElbSgIpSyncFunction", - Description="Lambda for syncing AWSs IPs for Cloud Front to origin ELB SGs", - Handler="update_security_groups.lambda_handler", - Role=GetAtt(cloud_front_origin_elb_sg_ip_sync_lambda_iam_role, "Arn"), - Code=Code( - S3Bucket=Ref(lambda_bucket), - S3Key=Ref(lambda_key) - ), - Runtime="python2.7", - MemorySize="128", - Timeout="5" -)) - -lambda_invoke_permission = template.add_resource(Permission( - "LambdaInvokePermission", - DependsOn="CloudFrontOriginElbSgIpSyncFunction", - FunctionName=GetAtt(cloud_front_origin_elb_sg_ip_sync_function, "Arn"), - Action="lambda:*", - Principal="sns.amazonaws.com", - SourceAccount=Ref(AWS_ACCOUNT_ID) -)) - -### -# -# Outputs -# -### - -template.add_output(Output( - "cloudFrontOriginElbSgIpSyncFunctionArn", - Value=GetAtt(cloud_front_origin_elb_sg_ip_sync_function, "Arn"), -)) - -### -# -# Print! -# -### - -cf_file = open(CF_FILE, 'w') -cf_file.truncate() -cf_file.write(template.to_json(indent=0)) -cf_file.close() - -print("CloudFormation template written to: {0}".format(CF_FILE)) diff --git a/smaas-cf/smaas/cms-cluster.py b/smaas-cf/smaas/cms-cluster.py deleted file mode 100644 index a75aa8f1..00000000 --- a/smaas-cf/smaas/cms-cluster.py +++ /dev/null @@ -1,333 +0,0 @@ -### -# Copyright (c) 2017 Nike Inc. -# -# 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. -### - -from io import open -from troposphere import Ref, Template, Parameter, Output, GetAtt -from troposphere.autoscaling import AutoScalingGroup, LaunchConfiguration -from troposphere.elasticloadbalancing import LoadBalancer, ConnectionDrainingPolicy, ConnectionSettings, HealthCheck, \ - Listener, Policy -from troposphere.policies import AutoScalingRollingUpdate, UpdatePolicy -from troposphere.route53 import RecordSetType - -from smaas.support.tags import CerberusTags - -CF_FILE = '../../src/main/resources/cloudformation/cms-cluster.json' - -# Create Template -template = Template() -template.description = "Launches the CMS cluster in the Cerberus VPC" -template.version = '2010-09-09' - -### -# -# Inputs -# -### - -cerberus_tags = CerberusTags() -cerberus_tags.add_tag_parameters(template) - -cert_public_key_param = template.add_parameter(Parameter( - "certPublicKey", - Description="TLS certificate public key to be used for backend authentication", - Type="String" -)) - -ssl_certificate_arn_param = template.add_parameter(Parameter( - "sslCertificateArn", - Description="TLS certificate ARN for the CMS ELB", - Type="String" -)) - -vpc_id_param = template.add_parameter(Parameter( - "vpcId", - Description="The ID of the VPC", - Type="AWS::EC2::VPC::Id" -)) - -instance_profile_name_param = template.add_parameter(Parameter( - "instanceProfileName", - Description="The name for the CMS instance profile", - Type="String" -)) - -ami_id_param = template.add_parameter(Parameter( - "amiId", - Description="The AMI ID for the CMS instances", - Type="String" -)) - -instance_size_param = template.add_parameter(Parameter( - "instanceSize", - Description="The instance size for the CMS instances", - Type="String" -)) - -cms_elb_sg_id_param = template.add_parameter(Parameter( - "cmsElbSgId", - Description="CMS ELB Security Group ID", - Type="AWS::EC2::SecurityGroup::Id" -)) - -cms_sg_id_param = template.add_parameter(Parameter( - "cmsSgId", - Description="CMS Security Group ID", - Type="AWS::EC2::SecurityGroup::Id" -)) - -tools_ingress_sg_id_param = template.add_parameter(Parameter( - "toolsIngressSgId", - Description="Tools Ingress Security Group ID", - Type="AWS::EC2::SecurityGroup::Id" -)) - -subnet_id_refs = [] -for zone_identifier in range(1, 4): - vpc_subnet_id = template.add_parameter(Parameter( - "vpcSubnetIdForAz{0}".format(zone_identifier), - Description="VPC Subnet ID for Zone {0}".format(zone_identifier), - Type="String" - )) - - subnet_id_refs.append(Ref(vpc_subnet_id)) - -key_pair_name_param = template.add_parameter(Parameter( - "keyPairName", - Description="The key pair to be associated with the EC2 instances", - Type="String", - Default="cpe-cerberus" -)) - -user_data_param = template.add_parameter(Parameter( - "userData", - Description="CMS user data", - Type="String" -)) - -hosted_zone_id_param = template.add_parameter(Parameter( - "hostedZoneId", - Description="The hosted zone id to associate the CNAME record with", - Type="String" -)) - -cname_param = template.add_parameter(Parameter( - "cname", - Description="The CNAME to be created for the CMS ELB", - Type="String" -)) - -desired_instances_param = template.add_parameter(Parameter( - "desiredInstances", - Description="Desired Number of Auto Scaling Instances", - Type="Number", - Default="3" -)) - -maximum_instances_param = template.add_parameter(Parameter( - "maximumInstances", - Description="Maximum Number of Auto Scaling Instances (must be larger than min)", - Type="Number", - Default="4" -)) - -minimum_instances_param = template.add_parameter(Parameter( - "minimumInstances", - Description="Minimum Number of Auto Scaling Instances", - Type="Number", - Default="3" -)) - -pause_time_param = template.add_parameter(Parameter( - "pauseTime", - Description="Pause time for AutoScalingRollingUpdate e.g PT15M", - Type="String", - Default="PT15M" -)) - -wait_on_resource_signals_param = template.add_parameter(Parameter( - "waitOnResourceSignals", - Description="Enabling WaitOnResourceSignals allows CloudFormation to wait until you have received a success signal before performing the next scaling action.", - Type="String", - Default="True" -)) - -### -# -# Elastic Load Balancers -# -### - -load_balancer = template.add_resource(LoadBalancer( - "CmsElasticLoadBalancer", - ConnectionDrainingPolicy=ConnectionDrainingPolicy( - Enabled=True, - Timeout=10 - ), - ConnectionSettings=ConnectionSettings( - IdleTimeout=10 - ), - CrossZone=True, - HealthCheck=HealthCheck( - HealthyThreshold=2, - Interval=5, - Target="HTTP:8080/healthcheck", - Timeout=2, - UnhealthyThreshold=2 - ), - Listeners=[ - Listener( - InstancePort=8080, - InstanceProtocol="HTTP", - LoadBalancerPort=443, - PolicyNames=[ - "CmsTLSNegotiationPolicy" - ], - Protocol="HTTPS", - SSLCertificateId=Ref(ssl_certificate_arn_param) - ) - ], - Policies=[ - Policy( - PolicyName="CmsTLSNegotiationPolicy", - PolicyType="SSLNegotiationPolicyType", - Attributes=[ - { - "Name": "Reference-Security-Policy", - "Value": "ELBSecurityPolicy-TLS-1-2-2017-01" - } - ] - ) - ], - Scheme="internal", - SecurityGroups=[ - Ref(cms_elb_sg_id_param) - ], - Subnets=subnet_id_refs, - Tags=cerberus_tags.get_tags_as_list() -)) - -### -# -# Launch Configuration and Auto Scaling Group for CMS -# -### - -launch_config = template.add_resource(LaunchConfiguration( - "CmsLaunchConfiguration", - AssociatePublicIpAddress=True, - IamInstanceProfile=Ref(instance_profile_name_param), - ImageId=Ref(ami_id_param), - InstanceMonitoring=True, - InstanceType=Ref(instance_size_param), - KeyName=Ref(key_pair_name_param), - SecurityGroups=[ - Ref(tools_ingress_sg_id_param), - Ref(cms_sg_id_param) - ], - UserData=Ref(user_data_param) -)) - -autoscaling_group = template.add_resource(AutoScalingGroup( - "CmsAutoScalingGroup", - DesiredCapacity=Ref(desired_instances_param), - HealthCheckGracePeriod=900, - HealthCheckType="ELB", - LaunchConfigurationName=Ref(launch_config), - LoadBalancerNames=[ - Ref(load_balancer) - ], - MaxSize=Ref(maximum_instances_param), - MinSize=Ref(minimum_instances_param), - UpdatePolicy=UpdatePolicy( - AutoScalingRollingUpdate=AutoScalingRollingUpdate( - MaxBatchSize=1, - MinInstancesInService=Ref(minimum_instances_param), - PauseTime=Ref(pause_time_param), - WaitOnResourceSignals=Ref(wait_on_resource_signals_param) - ) - ), - VPCZoneIdentifier=subnet_id_refs, - Tags=cerberus_tags.get_autoscaling_tags_as_list() -)) - -### -# -# Record Set for CMS CNAME -# -### - -record_set = template.add_resource(RecordSetType( - "CmsRecordSet", - HostedZoneId=Ref(hosted_zone_id_param), - Name=Ref(cname_param), - TTL=30, - Type="CNAME", - ResourceRecords=[GetAtt(load_balancer, "DNSName")] -)) - -### -# -# Outputs -# -### - -template.add_output(Output( - "autoscalingGroupLogicalId", - Value=Ref(autoscaling_group) -)) - -template.add_output(Output( - "launchConfigurationLogicalId", - Value=Ref(launch_config) -)) - -template.add_output(Output( - "elbLogicalId", - Value=Ref(load_balancer) -)) - -template.add_output(Output( - "elbCanonicalHostedZoneNameId", - Value=GetAtt(load_balancer, "CanonicalHostedZoneNameID") -)) - -template.add_output(Output( - "elbDnsName", - Value=GetAtt(load_balancer, "DNSName") -)) - -template.add_output(Output( - "elbSourceSecurityGroupName", - Value=GetAtt(load_balancer, "SourceSecurityGroup.GroupName") -)) - -template.add_output(Output( - "elbSourceSecurityGroupOwnerAlias", - Value=GetAtt(load_balancer, "SourceSecurityGroup.OwnerAlias") -)) - -### -# -# Print! -# -### - -cf_file = open(CF_FILE, 'w') -cf_file.truncate() -cf_file.write(template.to_json(indent=0)) -cf_file.close() - -print("CloudFormation template written to: {0}".format(CF_FILE)) diff --git a/smaas-cf/smaas/consul-cluster.py b/smaas-cf/smaas/consul-cluster.py deleted file mode 100644 index 0c71314d..00000000 --- a/smaas-cf/smaas/consul-cluster.py +++ /dev/null @@ -1,210 +0,0 @@ -### -# Copyright (c) 2017 Nike Inc. -# -# 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. -### - -from io import open -from troposphere.autoscaling import AutoScalingGroup, LaunchConfiguration -from troposphere import Ref, Template, Parameter, Output, GetAtt -from troposphere.policies import AutoScalingRollingUpdate, UpdatePolicy - -from smaas.support.tags import CerberusTags - -CF_FILE = '../../src/main/resources/cloudformation/consul-cluster.json' - -# Create Template -template = Template() -template.description = "Launches the Consul cluster in the Cerberus VPC" -template.version = '2010-09-09' - -### -# -# Inputs -# -### - -cerberus_tags = CerberusTags() -cerberus_tags.add_tag_parameters(template) - -vpc_id_param = template.add_parameter(Parameter( - "vpcId", - Description="The ID of the VPC", - Type="AWS::EC2::VPC::Id" -)) - -instance_profile_param = template.add_parameter(Parameter( - "instanceProfileName", - Description="The name for the Consul instance profile", - Type="String" -)) - -ami_id_param = template.add_parameter(Parameter( - "amiId", - Description="The AMI ID for the Consul server instances", - Type="String" -)) - -instance_size_param = template.add_parameter(Parameter( - "instanceSize", - Description="The instance size for the Consul server instances", - Type="String" -)) - -tools_ingress_sg_id_param = template.add_parameter(Parameter( - "toolsIngressSgId", - Description="Tools Ingress Security Group ID", - Type="AWS::EC2::SecurityGroup::Id" -)) - -consul_client_sg_id_param = template.add_parameter(Parameter( - "consulClientSgId", - Description="Consul Client Security Group ID", - Type="AWS::EC2::SecurityGroup::Id" -)) - -consul_server_sg_id_param = template.add_parameter(Parameter( - "consulServerSgId", - Description="Consul Server Security Group ID", - Type="AWS::EC2::SecurityGroup::Id" -)) - -key_pair_name_param = template.add_parameter(Parameter( - "keyPairName", - Description="The key pair to be associated with the EC2 instances", - Type="String", - Default="cpe-cerberus" -)) - -user_data_param = template.add_parameter(Parameter( - "userData", - Description="Consul User Data", - Type="String" -)) - -desired_instances_param = template.add_parameter(Parameter( - "desiredInstances", - Description="Desired Number of Auto Scaling Instances", - Type="Number", - Default="3" -)) - -maximum_instances_param = template.add_parameter(Parameter( - "maximumInstances", - Description="Maximum Number of Auto Scaling Instances (must be larger than min)", - Type="Number", - Default="4" -)) - -minimum_instances_param = template.add_parameter(Parameter( - "minimumInstances", - Description="Minimum Number of Auto Scaling Instances", - Type="Number", - Default="3" -)) - -pause_time_param = template.add_parameter(Parameter( - "pauseTime", - Description="Pause time for AutoScalingRollingUpdate e.g PT15M", - Type="String", - Default="PT15M" -)) - -wait_on_resource_signals_param = template.add_parameter(Parameter( - "waitOnResourceSignals", - Description="Enabling WaitOnResourceSignals allows CloudFormation to wait until you have received a success signal before performing the next scaling action.", - Type="String", - Default="True" -)) - -subnet_id_refs = [] -for zone_identifier in range(1, 4): - vpc_subnet_id = template.add_parameter(Parameter( - "vpcSubnetIdForAz{0}".format(zone_identifier), - Description="VPC Subnet ID for Zone {0}".format(zone_identifier), - Type="String" - )) - - subnet_id_refs.append(Ref(vpc_subnet_id)) - - -### -# -# EC2 Consul Server Instances -# -### - -consul_launch_config = template.add_resource(LaunchConfiguration( - "ConsulLaunchConfiguration", - AssociatePublicIpAddress=True, - IamInstanceProfile=Ref(instance_profile_param), - ImageId=Ref(ami_id_param), - InstanceMonitoring=True, - InstanceType=Ref(instance_size_param), - KeyName=Ref(key_pair_name_param), - SecurityGroups=[ - Ref(tools_ingress_sg_id_param), - Ref(consul_client_sg_id_param), - Ref(consul_server_sg_id_param) - ], - UserData=Ref(user_data_param) -)) - -consul_autoscaling_group = template.add_resource(AutoScalingGroup( - "ConsulAutoScalingGroup", - DesiredCapacity=Ref(desired_instances_param), - HealthCheckGracePeriod=60, - HealthCheckType="EC2", - LaunchConfigurationName=Ref(consul_launch_config), - MaxSize=Ref(maximum_instances_param), - MinSize=Ref(minimum_instances_param), - UpdatePolicy=UpdatePolicy( - AutoScalingRollingUpdate=AutoScalingRollingUpdate( - MaxBatchSize=1, - MinInstancesInService=Ref(minimum_instances_param), - PauseTime=Ref(pause_time_param), - WaitOnResourceSignals=Ref(wait_on_resource_signals_param) - ) - ), - VPCZoneIdentifier=subnet_id_refs, - Tags=cerberus_tags.get_autoscaling_tags_as_list() -)) - -### -# -# Outputs -# -### - -template.add_output(Output( - "autoscalingGroupLogicalId", - Value=Ref(consul_autoscaling_group) -)) - -template.add_output(Output( - "launchConfigurationLogicalId", - Value=Ref(consul_launch_config) -)) - -### -# -# Print! -# -### - -cf_file = open(CF_FILE, 'w') -cf_file.truncate() -cf_file.write(template.to_json(indent=0)) -cf_file.close() - -print("CloudFormation template written to: {0}".format(CF_FILE)) diff --git a/smaas-cf/smaas/gateway-cluster.py b/smaas-cf/smaas/gateway-cluster.py deleted file mode 100644 index 7080a02b..00000000 --- a/smaas-cf/smaas/gateway-cluster.py +++ /dev/null @@ -1,817 +0,0 @@ -### -# Copyright (c) 2017 Nike Inc. -# -# 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. -### - -from io import open -from troposphere import Ref, Template, Parameter, Output, GetAtt, Join, AWS_ACCOUNT_ID -from troposphere.autoscaling import AutoScalingGroup, LaunchConfiguration -from troposphere.cloudfront import Distribution, DefaultCacheBehavior, ForwardedValues, DistributionConfig, Origin, \ - CustomOrigin, ViewerCertificate, Logging -from troposphere.elasticloadbalancing import LoadBalancer, ConnectionDrainingPolicy, ConnectionSettings, HealthCheck, \ - Listener, Policy -from troposphere.policies import UpdatePolicy, AutoScalingRollingUpdate -from troposphere.route53 import RecordSetType -from troposphere.waf import WebACL, Action, Rule, SizeConstraintSet, SizeConstraint, FieldToMatch, Predicates, Rules, \ - IPSet, SqlInjectionMatchSet, SqlInjectionMatchTuples, XssMatchSet, XssMatchTuple -from troposphere.s3 import Bucket, BucketOwnerFullControl, NotificationConfiguration, LambdaConfigurations, Filter, \ - S3Key, Rules as S3Rules, BucketPolicy -from troposphere.awslambda import Function, Code, Permission - -from smaas.support.tags import CerberusTags - -CF_FILE = '../../src/main/resources/cloudformation/gateway-cluster.json' -CLOUD_FRONT_PREFIX = 'cf-logs/' - -# Create Template -template = Template() -template.description = "Launches the gateway cluster in the Cerberus VPC" -template.version = '2010-09-09' - -### -# -# Inputs -# -### - -cerberus_tags = CerberusTags() -cerberus_tags.add_tag_parameters(template) - -cert_public_key_param = template.add_parameter(Parameter( - "certPublicKey", - Description="TLS certificate public key to be used for backend authentication", - Type="String" -)) - -ssl_certificate_arn_param = template.add_parameter(Parameter( - "sslCertificateArn", - Description="TLS certificate ARN for the ELB", - Type="String" -)) - -ssl_certificate_id_param = template.add_parameter(Parameter( - "sslCertificateId", - Description="TLS certificate ID for the CloudFront distribution and ELB", - Type="String" -)) - -vpc_id_param = template.add_parameter(Parameter( - "vpcId", - Description="The ID of the VPC", - Type="AWS::EC2::VPC::Id" -)) - -instance_profile_name_param = template.add_parameter(Parameter( - "instanceProfileName", - Description="The name for the instance profile", - Type="String" -)) - -ami_id_param = template.add_parameter(Parameter( - "amiId", - Description="The AMI ID for the instances", - Type="String" -)) - -instance_size_param = template.add_parameter(Parameter( - "instanceSize", - Description="The instance size", - Type="String" -)) - -gateway_elb_sg_id_param = template.add_parameter(Parameter( - "gatewayElbSgId", - Description="Gateway Server ELB Security Group ID", - Type="AWS::EC2::SecurityGroup::Id" -)) - -gateway_server_sg_id_param = template.add_parameter(Parameter( - "gatewayServerSgId", - Description="Gateway Server Security Group ID", - Type="AWS::EC2::SecurityGroup::Id" -)) - -tools_ingress_sg_id_param = template.add_parameter(Parameter( - "toolsIngressSgId", - Description="Tools Ingress Security Group ID", - Type="AWS::EC2::SecurityGroup::Id" -)) - -cloud_front_log_processor_lambda_iam_role_param = template.add_parameter(Parameter( - "cloudFrontLogProcessorLambdaIamRoleArn", - Description="The IAM Role Arn for the lambda", - Type="String" -)) - -subnet_id_refs = [] -for zone_identifier in range(1, 4): - vpc_subnet_id = template.add_parameter(Parameter( - "vpcSubnetIdForAz{0}".format(zone_identifier), - Description="VPC Subnet ID for Zone {0}".format(zone_identifier), - Type="String" - )) - - subnet_id_refs.append(Ref(vpc_subnet_id)) - -key_pair_name_param = template.add_parameter(Parameter( - "keyPairName", - Description="The key pair to be associated with the EC2 instances", - Type="String", - Default="cpe-cerberus" -)) - -gateway_user_data_param = template.add_parameter(Parameter( - "userData", - Description="Gateway User Data", - Type="String" -)) - -hosted_zone_id_param = template.add_parameter(Parameter( - "hostedZoneId", - Description="The hosted zone id to associate the CNAME record with", - Type="String" -)) - -hostname_param = template.add_parameter(Parameter( - "hostname", - Description="The base hostname for the public facing gateway / router", - Type="String" -)) - -waf_lambda_bucket = template.add_parameter(Parameter( - "wafLambdaBucket", - Type="String", - Description="S3 Bucket for waf lambda function artifact" -)) - -waf_lambda_key = template.add_parameter(Parameter( - "wafLambdaKey", - Type="String", - Description="Key for waf lambda function artifact" -)) - -desired_instances_param = template.add_parameter(Parameter( - "desiredInstances", - Description="Desired Number of Auto Scaling Instances", - Type="Number", - Default="3" -)) - -maximum_instances_param = template.add_parameter(Parameter( - "maximumInstances", - Description="Maximum Number of Auto Scaling Instances (must be larger than min)", - Type="Number", - Default="4" -)) - -minimum_instances_param = template.add_parameter(Parameter( - "minimumInstances", - Description="Minimum Number of Auto Scaling Instances", - Type="Number", - Default="3" -)) - -pause_time_param = template.add_parameter(Parameter( - "pauseTime", - Description="Pause time for AutoScalingRollingUpdate e.g PT15M", - Type="String", - Default="PT15M" -)) - -wait_on_resource_signals_param = template.add_parameter(Parameter( - "waitOnResourceSignals", - Description="Enabling WaitOnResourceSignals allows CloudFormation to wait until you have received a success signal before performing the next scaling action.", - Type="String", - Default="True" -)) - -### -# -# Elastic Load Balancer -# -### - -gateway_load_balancer = template.add_resource(LoadBalancer( - "GatewayElasticLoadBalancer", - ConnectionDrainingPolicy=ConnectionDrainingPolicy( - Enabled=True, - Timeout=10 - ), - ConnectionSettings=ConnectionSettings( - IdleTimeout=10 - ), - CrossZone=True, - HealthCheck=HealthCheck( - HealthyThreshold=2, - Interval=5, - Target="HTTPS:443/sys/health", - Timeout=2, - UnhealthyThreshold=2 - ), - Listeners=[ - Listener( - InstancePort=443, - InstanceProtocol="HTTPS", - LoadBalancerPort=443, - PolicyNames=[ - "GatewayTLSNegotiationPolicy" - ], - Protocol="HTTPS", - SSLCertificateId=Ref(ssl_certificate_arn_param) - ) - ], - Policies=[ - Policy( - PolicyName="GatewayTLSNegotiationPolicy", - PolicyType="SSLNegotiationPolicyType", - Attributes=[ - { - "Name": "Reference-Security-Policy", - "Value": "ELBSecurityPolicy-TLS-1-2-2017-01" - } - ] - ), - Policy( - PolicyName="GatewayPublicKeyPolicy", - PolicyType="PublicKeyPolicyType", - Attributes=[ - { - "Name": "PublicKey", - "Value": Ref(cert_public_key_param) - } - ] - ), - Policy( - PolicyName="GatewayBackendServerAuthenticationPolicy", - PolicyType="BackendServerAuthenticationPolicyType", - Attributes=[ - { - "Name": "PublicKeyPolicyName", - "Value": "GatewayPublicKeyPolicy" - } - ], - InstancePorts=[ - "443" - ] - ) - ], - Scheme="internet-facing", - SecurityGroups=[ - Ref(gateway_elb_sg_id_param) - ], - Subnets=subnet_id_refs, - Tags=cerberus_tags.get_tags_as_list() -)) - -### -# -# Launch Configuration and Auto Scaling Group for Gateway -# -### - -gateway_launch_config = template.add_resource(LaunchConfiguration( - "GatewayLaunchConfiguration", - AssociatePublicIpAddress=True, - IamInstanceProfile=Ref(instance_profile_name_param), - ImageId=Ref(ami_id_param), - InstanceMonitoring=True, - InstanceType=Ref(instance_size_param), - KeyName=Ref(key_pair_name_param), - SecurityGroups=[ - Ref(tools_ingress_sg_id_param), - Ref(gateway_server_sg_id_param), - ], - UserData=Ref(gateway_user_data_param) -)) - -gateway_autoscaling_group = template.add_resource(AutoScalingGroup( - "GatewayAutoScalingGroup", - DesiredCapacity=Ref(desired_instances_param), - HealthCheckGracePeriod=300, - HealthCheckType="ELB", - LaunchConfigurationName=Ref(gateway_launch_config), - LoadBalancerNames=[ - Ref(gateway_load_balancer) - ], - MaxSize=Ref(maximum_instances_param), - MinSize=Ref(minimum_instances_param), - UpdatePolicy=UpdatePolicy( - AutoScalingRollingUpdate=AutoScalingRollingUpdate( - MaxBatchSize=1, - MinInstancesInService=Ref(minimum_instances_param), - PauseTime=Ref(pause_time_param), - WaitOnResourceSignals=Ref(wait_on_resource_signals_param) - ) - ), - VPCZoneIdentifier=subnet_id_refs, - Tags=cerberus_tags.get_autoscaling_tags_as_list() -)) - -### -# -# Record Set for public Cerberus CNAME -# -### - -gateway_record_set = template.add_resource(RecordSetType( - "OriginCerberusPublicRecordSet", - HostedZoneId=Ref(hosted_zone_id_param), - Name=Join(".", ["origin", Ref(hostname_param), ""]), - TTL=30, - Type="CNAME", - ResourceRecords=[GetAtt(gateway_load_balancer, "CanonicalHostedZoneName")] -)) - -### -# -# WAF Web ACL -# -### - -xss_match_set = template.add_resource(XssMatchSet( - "CerberusWafXssMatchSet", - Name="CerberusWafXssMatchSet", - XssMatchTuples=[ - XssMatchTuple( - "CerberusWafXssUriMatch", - FieldToMatch=FieldToMatch( - Type="URI" - ), - TextTransformation="NONE" - ), - XssMatchTuple( - "CerberusWafXssQueryStringMatch", - FieldToMatch=FieldToMatch( - Type="QUERY_STRING" - ), - TextTransformation="NONE" - ), - XssMatchTuple( - "CerberusWafXssBodyMatch", - FieldToMatch=FieldToMatch( - Type="BODY" - ), - TextTransformation="NONE" - ) - ] -)) - -xss_rule = template.add_resource(Rule( - "CerberusWafXssRule", - Name="CerberusWafXssRule", - MetricName="CerberusWafXss", - Predicates=[ - Predicates( - DataId=Ref(xss_match_set), - Negated=False, - Type="XssMatch" - ) - ] -)) - -sql_injection_match_set = template.add_resource(SqlInjectionMatchSet( - "CerberusWafSqlInjectionMatchSet", - Name="CerberusWafSqlInjectionMatchSet", - SqlInjectionMatchTuples=[ - SqlInjectionMatchTuples( - "CerberusWafSqlInjectionUriMatch", - FieldToMatch=FieldToMatch( - Type="URI" - ), - TextTransformation="NONE" - ), - SqlInjectionMatchTuples( - "CerberusWafSqlInjectionQueryStringMatch", - FieldToMatch=FieldToMatch( - Type="QUERY_STRING" - ), - TextTransformation="NONE" - ), - SqlInjectionMatchTuples( - "CerberusWafSqlInjectionBodyMatch", - FieldToMatch=FieldToMatch( - Type="BODY" - ), - TextTransformation="NONE" - ) - ] -)) - -sql_injection_rule = template.add_resource(Rule( - "CerberusWafSqlInjectionRule", - Name="CerberusWafSqlInjectionRule", - MetricName="CerberusWafSqlInjection", - Predicates=[ - Predicates( - DataId=Ref(sql_injection_match_set), - Negated=False, - Type="SqlInjectionMatch" - ) - ] -)) - -size_constraint_set = template.add_resource(SizeConstraintSet( - "CerberusWafSizeConstraintSet", - Name="CerberusWafSizeConstraintSet", - SizeConstraints=[ - SizeConstraint( - "CerberusWafSizeConstraint", - ComparisonOperator="GE", - FieldToMatch=FieldToMatch( - Type="BODY" - ), - Size=256000, - TextTransformation="NONE" - ) - ] -)) - -size_constraint_rule = template.add_resource(Rule( - "CerberusWafSizeConstraintRule", - Name="CerberusWafSizeConstraintRule", - MetricName="CerberusWafSizeConstraint", - Predicates=[ - Predicates( - DataId=Ref(size_constraint_set), - Negated=False, - Type="SizeConstraint" - ) - ] -)) - -waf_white_list_set = template.add_resource(IPSet( - "WAFWhiteListSet", - Name="White List Set" -)) - -waf_manual_block_set = template.add_resource(IPSet( - "WAFManualBlockSet", - Name="Manual Block Set" -)) - -waf_auto_block_set = template.add_resource(IPSet( - "WAFAutoBlockSet", - Name="Auto Block Set" -)) - -waf_white_list_rule = template.add_resource(Rule( - "WAFWhiteListRule", - DependsOn="WAFWhiteListSet", - Name="White List Rule", - MetricName="WhiteListRule", - Predicates=[ - Predicates( - DataId=Ref(waf_white_list_set), - Negated=False, - Type="IPMatch" - ) - ] -)) - -waf_manual_block_rule = template.add_resource(Rule( - "WAFManualBlockRule", - DependsOn="WAFManualBlockSet", - Name="Manual Block Rule", - MetricName="ManualBlockRule", - Predicates=[ - Predicates( - DataId=Ref(waf_manual_block_set), - Negated=False, - Type="IPMatch" - ) - ] -)) - -waf_auto_block_rule = template.add_resource(Rule( - "WAFAutoBlockRule", - DependsOn="WAFAutoBlockSet", - Name="Auto Block Rule", - MetricName="AutoBlockRule", - Predicates=[ - Predicates( - DataId=Ref(waf_auto_block_set), - Negated=False, - Type="IPMatch" - ) - ] -)) - -web_acl = template.add_resource(WebACL( - "CerberusWAFWebAcl", - DependsOn=["WAFManualBlockRule", "WAFAutoBlockRule"], - MetricName="CerberusWAF", - DefaultAction=Action( - Type="ALLOW" - ), - Name=Join(".", ["waf", Ref(hostname_param)]), - Rules=[ - Rules( - Action=Action( - Type="BLOCK" - ), - Priority=1, - RuleId=Ref(size_constraint_rule) - ), - Rules( - Action=Action( - Type="BLOCK" - ), - Priority=2, - RuleId=Ref(sql_injection_rule) - ), - Rules( - Action=Action( - Type="BLOCK" - ), - Priority=3, - RuleId=Ref(xss_rule) - ), - Rules( - Action=Action( - Type="ALLOW" - ), - Priority=4, - RuleId=Ref(waf_white_list_rule) - ), - Rules( - Action=Action( - Type="BLOCK" - ), - Priority=5, - RuleId=Ref(waf_manual_block_rule) - ), - Rules( - Action=Action( - Type="BLOCK" - ), - Priority=6, - RuleId=Ref(waf_auto_block_rule) - ) - ] -)) - -### -# -# WAF Lambda Function -# -### - -lambda_waf_blacklisting_function = template.add_resource(Function( - "LambdaWAFBlacklistingFunction", - Description="Function for auto black listing ips that are misbehaving", - Handler="com.nike.cerberus.lambda.waf.handler.CloudFrontLogEventHandler::handleNewS3Event", - Role=Ref(cloud_front_log_processor_lambda_iam_role_param), - Code=Code( - S3Bucket=Ref(waf_lambda_bucket), - S3Key=Ref(waf_lambda_key) - ), - Runtime="java8", - MemorySize="512", - Timeout="60" -)) - -lambda_invoke_permission = template.add_resource(Permission( - "LambdaInvokePermission", - DependsOn="LambdaWAFBlacklistingFunction", - FunctionName=GetAtt(lambda_waf_blacklisting_function, "Arn"), - Action="lambda:*", - Principal="s3.amazonaws.com", - SourceAccount=Ref(AWS_ACCOUNT_ID) -)) - -### -# -# Bucket for Cloud Front Logs -# -### - -cloud_front_logs_bucket = template.add_resource(Bucket( - "CloudFrontBucket", - AccessControl=BucketOwnerFullControl, - Tags=cerberus_tags.get_tags(), - NotificationConfiguration=NotificationConfiguration( - LambdaConfigurations=[LambdaConfigurations( - Event="s3:ObjectCreated:*", - Filter=Filter( - S3Key=S3Key( - Rules=[S3Rules( - Name="suffix", - Value="gz" - )] - ) - ), - Function=GetAtt(lambda_waf_blacklisting_function, "Arn") - )] - ) -)) - -cloud_front_logs_bucket_policy = template.add_resource(BucketPolicy( - "CloudFrontLogsBucketPolicy", - Bucket=Ref(cloud_front_logs_bucket), - PolicyDocument={ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "Allow-CloudFront-Log-Access", - "Effect": "Allow", - "Principal": { - "AWS": [ - Ref(cloud_front_log_processor_lambda_iam_role_param) - ] - }, - "Action": [ - "s3:*" - ], - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:aws:s3:::", - Ref(cloud_front_logs_bucket), - "/*" - ] - ] - } - ] - } - ] - } - -)) - -### -# -# Cloud Front for Web Application Firewall -# -### - -gateway_distribution = template.add_resource(Distribution( - "CerberusDistribution", - DistributionConfig=DistributionConfig( - Aliases=[ - Ref(hostname_param) - ], - DefaultCacheBehavior=DefaultCacheBehavior( - AllowedMethods=[ - "GET", - "PUT", - "POST", - "DELETE", - "PATCH", - "HEAD", - "OPTIONS" - ], - CachedMethods=[ - "GET", - "HEAD", - "OPTIONS" - ], - ForwardedValues=ForwardedValues( - Headers=[ - "*" - ], - QueryString=True - ), - MaxTTL=0, - MinTTL=0, - DefaultTTL=0, - TargetOriginId="CerberusGatewayOrigin", - ViewerProtocolPolicy="https-only" - ), - Enabled=True, - Origins=[ - Origin( - Id="CerberusGatewayOrigin", - DomainName=Join(".", ["origin", Ref(hostname_param)]), - CustomOriginConfig=CustomOrigin( - HTTPSPort="443", - OriginProtocolPolicy="https-only", - OriginSSLProtocols=[ - "TLSv1.2" - ] - ) - ) - ], - PriceClass="PriceClass_100", - ViewerCertificate=ViewerCertificate( - IamCertificateId=Ref(ssl_certificate_id_param), - MinimumProtocolVersion="TLSv1", - SslSupportMethod="sni-only" - ), - WebACLId=Ref(web_acl), - Logging=Logging( - Bucket=GetAtt(cloud_front_logs_bucket, "DomainName") - ) - ) -)) - -### -# -# Record Set for public Cerberus Cloud Front CNAME -# -### - -cf_gateway_record_set = template.add_resource(RecordSetType( - "CerberusPublicRecordSet", - HostedZoneId=Ref(hosted_zone_id_param), - Name=Join(".", [Ref(hostname_param), ""]), - TTL=30, - Type="CNAME", - ResourceRecords=[GetAtt(gateway_distribution, "DomainName")] -)) - -### -# -# Outputs -# -### - -template.add_output(Output( - "autoscalingGroupLogicalId", - Value=Ref(gateway_autoscaling_group) -)) - -template.add_output(Output( - "launchConfigurationLogicalId", - Value=Ref(gateway_launch_config) -)) - -template.add_output(Output( - "elbLogicalId", - Value=Ref(gateway_load_balancer) -)) - -template.add_output(Output( - "elbCanonicalHostedZoneNameId", - Value=GetAtt(gateway_load_balancer, "CanonicalHostedZoneNameID") -)) - -template.add_output(Output( - "elbDnsName", - Value=GetAtt(gateway_load_balancer, "DNSName") -)) - -template.add_output(Output( - "elbSourceSecurityGroupName", - Value=GetAtt(gateway_load_balancer, "SourceSecurityGroup.GroupName") -)) - -template.add_output(Output( - "elbSourceSecurityGroupOwnerAlias", - Value=GetAtt(gateway_load_balancer, "SourceSecurityGroup.OwnerAlias") -)) - -template.add_output(Output( - "cloudFrontDistributionId", - Value=Ref(gateway_distribution), -)) - -template.add_output(Output( - "cloudFrontDistributionDomainName", - Value=GetAtt(gateway_distribution, "DomainName") -)) - -template.add_output(Output( - "cloudFrontAccessLogBucket", - Value=Ref(cloud_front_logs_bucket) -)) - -template.add_output(Output( - "whiteListIPSetID", - Value=Ref(waf_white_list_set) -)) - -template.add_output(Output( - "manualBlockIPSetID", - Value=Ref(waf_manual_block_set) -)) - -template.add_output(Output( - "autoBlockIPSetID", - Value=Ref(waf_auto_block_set) -)) - -### -# -# Print! -# -### - -cf_file = open(CF_FILE, 'w') -cf_file.truncate() -cf_file.write(template.to_json(indent=0)) -cf_file.close() - -print("CloudFormation template written to: {0}".format(CF_FILE)) diff --git a/smaas-cf/smaas/support/constants.py b/smaas-cf/smaas/support/constants.py deleted file mode 100644 index ec8f6361..00000000 --- a/smaas-cf/smaas/support/constants.py +++ /dev/null @@ -1,3 +0,0 @@ -from troposphere import Ref - -aws_region_ref = Ref("AWS::Region") \ No newline at end of file diff --git a/smaas-cf/smaas/support/network.py b/smaas-cf/smaas/support/network.py deleted file mode 100644 index b860f251..00000000 --- a/smaas-cf/smaas/support/network.py +++ /dev/null @@ -1,98 +0,0 @@ -from troposphere import Parameter, Template, Output - -VPC_SUBNET_CIDRS = { - 1: "172.20.0.0/24", - 2: "172.20.4.0/24", - 3: "172.20.8.0/24" -} - - -class CerberusNetwork: - gateway_cidr_block_param = None - vpc_cidr_block_param = None - subnet_cidr_block_param_by_az_map = {} - subnet_cidr_block_for_az1_output = None - subnet_cidr_block_for_az2_output = None - subnet_cidr_block_for_az3_output = None - - def __init__(self): - self.gateway_cidr_block_param = Parameter( - "gatewayCidrBlock", - Description="The internet gateway CIDR block for where traffic is allowed from", - Type="String", - MinLength="9", - MaxLength="20", - AllowedPattern="[0-9./]*", - Default="0.0.0.0/0" - ) - - self.vpc_cidr_block_param = Parameter( - "vpcCidrBlock", - Description="The VPC's CIDR block for internal IPv4 addressing", - Type="String", - MinLength="9", - MaxLength="20", - AllowedPattern="[0-9./]*", - Default="172.20.0.0/20" - ) - - self.subnet_cidr_block_param_by_az_map[1] = Parameter( - "subnetCidrBlockForAz1", - Description="Cidr block for subnet in AZ '1'", - Type="String", - MinLength="9", - MaxLength="20", - AllowedPattern="[0-9./]*", - Default=VPC_SUBNET_CIDRS[1] - ) - - self.subnet_cidr_block_param_by_az_map[2] = Parameter( - "subnetCidrBlockForAz2", - Description="Cidr block for subnet in AZ '2'", - Type="String", - MinLength="9", - MaxLength="20", - AllowedPattern="[0-9./]*", - Default=VPC_SUBNET_CIDRS[2] - ) - - self.subnet_cidr_block_param_by_az_map[3] = Parameter( - "subnetCidrBlockForAz3", - Description="Cidr block for subnet in AZ '3'", - Type="String", - MinLength="9", - MaxLength="20", - AllowedPattern="[0-9./]*", - Default=VPC_SUBNET_CIDRS[3] - ) - - self.subnet_cidr_block_for_az1_output = Output( - "subnetCidrBlockForAz1", - Description="Cidr block for subnet in AZ '1'", - Value=VPC_SUBNET_CIDRS[1] - ) - - self.subnet_cidr_block_for_az2_output = Output( - "subnetCidrBlockForAz2", - Description="Cidr block for subnet in AZ '2'", - Value=VPC_SUBNET_CIDRS[2] - ) - - self.subnet_cidr_block_for_az3_output = Output( - "subnetCidrBlockForAz3", - Description="Cidr block for subnet in AZ '3'", - Value=VPC_SUBNET_CIDRS[3] - ) - - def add_parameters(self, template: Template): - template.add_parameter(self.gateway_cidr_block_param) - template.add_parameter(self.vpc_cidr_block_param) - - for subnet_cidr_block in self.subnet_cidr_block_param_by_az_map.values(): - template.add_parameter(subnet_cidr_block) - - - def add_outputs(self, template: Template): - template.add_output(self.subnet_cidr_block_for_az1_output) - template.add_output(self.subnet_cidr_block_for_az2_output) - template.add_output(self.subnet_cidr_block_for_az3_output) diff --git a/smaas-cf/smaas/support/tags.py b/smaas-cf/smaas/support/tags.py deleted file mode 100644 index d491dfad..00000000 --- a/smaas-cf/smaas/support/tags.py +++ /dev/null @@ -1,70 +0,0 @@ -from troposphere import Parameter, Ref, Tags, Template -from troposphere.ec2 import Tag -from troposphere.autoscaling import Tag as AutoScalingTag - - -class CerberusTags: - tag_map = {} - - def __init__(self): - self.tag_map["tagName"] = Parameter( - "tagName", - Description="Name assigned to the stack. Format: {appGroup}-{appName}", - Type="String" - ) - - self.tag_map["tagEmail"] = Parameter( - "tagEmail", - Description="E-mail address for group or person responsible for the stack.", - Type="String" - ) - - self.tag_map["tagClassification"] = Parameter( - "tagClassification", - Description="Denotes which category of Data Classification the instance is grouped under.", - Type="String", - Default="Gold" - ) - - self.tag_map["tagCostcenter"] = Parameter( - "tagCostcenter", - Description="Represents the Cost Center associated with the team/project.", - Type="String" - ) - - def add_tag_parameters(self, template: Template): - for param in self.tag_map.values(): - template.add_parameter(param) - - def get_tags_as_list(self): - return [ - Tag("Name", Ref(self.tag_map["tagName"])), - Tag("email", Ref(self.tag_map["tagEmail"])), - Tag("classification", Ref(self.tag_map["tagClassification"])), - Tag("costcenter", Ref(self.tag_map["tagCostcenter"])) - ] - - def get_gateway_tags_as_list(self): - return [ - Tag("Name", "cloudfront"), - Tag("AutoUpdate", "true"), - Tag("email", Ref(self.tag_map["tagEmail"])), - Tag("classification", Ref(self.tag_map["tagClassification"])), - Tag("costcenter", Ref(self.tag_map["tagCostcenter"])) - ] - - def get_autoscaling_tags_as_list(self): - return [ - AutoScalingTag("Name", Ref(self.tag_map["tagName"]), True), - AutoScalingTag("email", Ref(self.tag_map["tagEmail"]), True), - AutoScalingTag("classification", Ref(self.tag_map["tagClassification"]), True), - AutoScalingTag("costcenter", Ref(self.tag_map["tagCostcenter"]), True) - ] - - def get_tags(self): - return Tags( - Name=Ref(self.tag_map["tagName"]), - email=Ref(self.tag_map["tagEmail"]), - classification=Ref(self.tag_map["tagClassification"]), - costcenter=Ref(self.tag_map["tagCostcenter"]) - ) diff --git a/smaas-cf/smaas/vault-cluster.py b/smaas-cf/smaas/vault-cluster.py deleted file mode 100644 index 659aa2a6..00000000 --- a/smaas-cf/smaas/vault-cluster.py +++ /dev/null @@ -1,379 +0,0 @@ -### -# Copyright (c) 2017 Nike Inc. -# -# 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. -### - -from io import open -from troposphere import Ref, Template, Parameter, Output, GetAtt -from troposphere.autoscaling import AutoScalingGroup, LaunchConfiguration -from troposphere.elasticloadbalancing import LoadBalancer, ConnectionDrainingPolicy, ConnectionSettings, HealthCheck, \ - Listener, Policy -from troposphere.route53 import RecordSetType -from troposphere.policies import AutoScalingRollingUpdate, UpdatePolicy - -from smaas.support.tags import CerberusTags - -CF_FILE = '../../src/main/resources/cloudformation/vault-cluster.json' - -# Create Template -template = Template() -template.description = "Launches the Vault stack in the Cerberus VPC" -template.version = '2010-09-09' - -### -# -# Inputs -# -### - -cerberus_tags = CerberusTags() -cerberus_tags.add_tag_parameters(template) - -cert_public_key_param = template.add_parameter(Parameter( - "certPublicKey", - Description="TLS certificate public key to be used for backend authentication", - Type="String" -)) - -ssl_certificate_arn_param = template.add_parameter(Parameter( - "sslCertificateArn", - Description="TLS certificate ARN for the ELB", - Type="String" -)) - -vpc_id_param = template.add_parameter(Parameter( - "vpcId", - Description="The ID of the VPC", - Type="AWS::EC2::VPC::Id" -)) - -instance_profile_name_param = template.add_parameter(Parameter( - "instanceProfileName", - Description="The name for the Vault Instance Profile", - Type="String" -)) - -ami_id_param = template.add_parameter(Parameter( - "amiId", - Description="The AMI ID for the Vault Instances", - Type="String" -)) - -instance_size_param = template.add_parameter(Parameter( - "instanceSize", - Description="The instance size for the Vault instances", - Type="String" -)) - -vault_server_elb_sg_id_param = template.add_parameter(Parameter( - "vaultServerElbSgId", - Description="Vault Server ELB Security Group ID", - Type="AWS::EC2::SecurityGroup::Id" -)) - -tools_ingress_sg_id_param = template.add_parameter(Parameter( - "toolsIngressSgId", - Description="Tools Ingress Security Group ID", - Type="AWS::EC2::SecurityGroup::Id" -)) - -vault_client_sg_id_param = template.add_parameter(Parameter( - "vaultClientSgId", - Description="Vault Security Group ID for clients", - Type="AWS::EC2::SecurityGroup::Id" -)) - -vault_server_sg_id_param = template.add_parameter(Parameter( - "vaultServerSgId", - Description="Vault Security Group ID for servers", - Type="AWS::EC2::SecurityGroup::Id" -)) - -consul_client_sg_id_param = template.add_parameter(Parameter( - "consulClientSgId", - Description="Consul Client Security Group ID", - Type="AWS::EC2::SecurityGroup::Id" -)) - -consul_server_sg_id_param = template.add_parameter(Parameter( - "consulServerSgId", - Description="Consul Server Security Group ID", - Type="AWS::EC2::SecurityGroup::Id" -)) - -subnet_id_refs = [] -for zone_identifier in range(1, 4): - vpc_subnet_id = template.add_parameter(Parameter( - "vpcSubnetIdForAz{0}".format(zone_identifier), - Description="VPC Subnet ID for Zone {0}".format(zone_identifier), - Type="String" - )) - - subnet_id_refs.append(Ref(vpc_subnet_id)) - -key_pair_name_param = template.add_parameter(Parameter( - "keyPairName", - Description="The key pair to be associated with the EC2 instances", - Type="String", - Default="cpe-cerberus" -)) - -vault_user_data_param = template.add_parameter(Parameter( - "userData", - Description="Vault User Data", - Type="String" -)) - -hosted_zone_id_param = template.add_parameter(Parameter( - "hostedZoneId", - Description="The hosted zone id to associate the CNAME record with", - Type="String" -)) - -cname_param = template.add_parameter(Parameter( - "cname", - Description="The CNAME to be created", - Type="String" -)) - -desired_instances_param = template.add_parameter(Parameter( - "desiredInstances", - Description="Desired Number of Auto Scaling Instances", - Type="Number", - Default="3" -)) - -maximum_instances_param = template.add_parameter(Parameter( - "maximumInstances", - Description="Maximum Number of Auto Scaling Instances (must be larger than min)", - Type="Number", - Default="4" -)) - -minimum_instances_param = template.add_parameter(Parameter( - "minimumInstances", - Description="Minimum Number of Auto Scaling Instances", - Type="Number", - Default="3" -)) - - -pause_time_param = template.add_parameter(Parameter( - "pauseTime", - Description="Pause time for AutoScalingRollingUpdate e.g PT15M", - Type="String", - Default="PT15M" -)) - -wait_on_resource_signals_param = template.add_parameter(Parameter( - "waitOnResourceSignals", - Description="Enabling WaitOnResourceSignals allows CloudFormation to wait until you have received a success signal before performing the next scaling action.", - Type="String", - Default="True" -)) - - -### -# -# Elastic Load Balancer -# -### - -vault_load_balancer = template.add_resource(LoadBalancer( - "VaultElasticLoadBalancer", - ConnectionDrainingPolicy=ConnectionDrainingPolicy( - Enabled=True, - Timeout=10 - ), - ConnectionSettings=ConnectionSettings( - IdleTimeout=10 - ), - CrossZone=True, - HealthCheck=HealthCheck( - HealthyThreshold=2, - Interval=5, - Target="HTTPS:8200/v1/sys/health?standbyok", - Timeout=2, - UnhealthyThreshold=2 - ), - Listeners=[ - Listener( - InstancePort=8200, - InstanceProtocol="HTTPS", - LoadBalancerPort=443, - PolicyNames=[ - "VaultTLSNegotiationPolicy" - ], - Protocol="HTTPS", - SSLCertificateId=Ref(ssl_certificate_arn_param) - ) - ], - Policies=[ - Policy( - PolicyName="VaultTLSNegotiationPolicy", - PolicyType="SSLNegotiationPolicyType", - Attributes=[ - { - "Name": "Reference-Security-Policy", - "Value": "ELBSecurityPolicy-TLS-1-2-2017-01" - } - ] - ), - Policy( - PolicyName="VaultPublicKeyPolicy", - PolicyType="PublicKeyPolicyType", - Attributes=[ - { - "Name": "PublicKey", - "Value": Ref(cert_public_key_param) - } - ] - ), - Policy( - PolicyName="VaultBackendServerAuthenticationPolicy", - PolicyType="BackendServerAuthenticationPolicyType", - Attributes=[ - { - "Name": "PublicKeyPolicyName", - "Value": "VaultPublicKeyPolicy" - } - ], - InstancePorts=[ - "8200" - ] - ) - ], - Scheme="internal", - SecurityGroups=[ - Ref(vault_server_elb_sg_id_param) - ], - Subnets=subnet_id_refs, - Tags=cerberus_tags.get_tags_as_list() -)) - -### -# -# Launch Configuration and Auto Scaling Group for Vault -# -### - -vault_launch_config = template.add_resource(LaunchConfiguration( - "VaultLaunchConfiguration", - AssociatePublicIpAddress=True, - IamInstanceProfile=Ref(instance_profile_name_param), - ImageId=Ref(ami_id_param), - InstanceMonitoring=True, - InstanceType=Ref(instance_size_param), - KeyName=Ref(key_pair_name_param), - SecurityGroups=[ - Ref(tools_ingress_sg_id_param), - Ref(vault_client_sg_id_param), - Ref(vault_server_sg_id_param), - Ref(consul_client_sg_id_param), - Ref(consul_server_sg_id_param) - ], - UserData=Ref(vault_user_data_param) -)) - -vault_autoscaling_group = template.add_resource(AutoScalingGroup( - "VaultAutoScalingGroup", - DesiredCapacity=Ref(desired_instances_param), - HealthCheckGracePeriod=60, - HealthCheckType="EC2", - LaunchConfigurationName=Ref(vault_launch_config), - LoadBalancerNames=[ - Ref(vault_load_balancer) - ], - MaxSize=Ref(maximum_instances_param), - MinSize=Ref(minimum_instances_param), - UpdatePolicy=UpdatePolicy( - AutoScalingRollingUpdate=AutoScalingRollingUpdate( - MaxBatchSize=1, - MinInstancesInService=Ref(minimum_instances_param), - PauseTime=Ref(pause_time_param), - WaitOnResourceSignals=Ref(wait_on_resource_signals_param) - ) - ), - VPCZoneIdentifier=subnet_id_refs, - Tags=cerberus_tags.get_autoscaling_tags_as_list() -)) - -### -# -# Record Set for Vault CNAME -# -### - -vault_record_set = template.add_resource(RecordSetType( - "VaultRecordSet", - HostedZoneId=Ref(hosted_zone_id_param), - Name=Ref(cname_param), - TTL=30, - Type="CNAME", - ResourceRecords=[GetAtt(vault_load_balancer, "DNSName")] -)) - -### -# -# Outputs -# -### - -template.add_output(Output( - "autoscalingGroupLogicalId", - Value=Ref(vault_autoscaling_group) -)) - -template.add_output(Output( - "launchConfigurationLogicalId", - Value=Ref(vault_launch_config) -)) - -template.add_output(Output( - "elbLogicalId", - Value=Ref(vault_load_balancer) -)) - -template.add_output(Output( - "elbCanonicalHostedZoneNameId", - Value=GetAtt(vault_load_balancer, "CanonicalHostedZoneNameID") -)) - -template.add_output(Output( - "elbDnsName", - Value=GetAtt(vault_load_balancer, "DNSName") -)) - -template.add_output(Output( - "elbSourceSecurityGroupName", - Value=GetAtt(vault_load_balancer, "SourceSecurityGroup.GroupName") -)) - -template.add_output(Output( - "elbSourceSecurityGroupOwnerAlias", - Value=GetAtt(vault_load_balancer, "SourceSecurityGroup.OwnerAlias") -)) - -### -# -# Print! -# -### - -cf_file = open(CF_FILE, 'w') -cf_file.truncate() -cf_file.write(template.to_json(indent=0)) -cf_file.close() - -print("CloudFormation template written to: {0}".format(CF_FILE)) diff --git a/smaas-cf/smaas/vpc-and-base.py b/smaas-cf/smaas/vpc-and-base.py deleted file mode 100644 index a4b27ac4..00000000 --- a/smaas-cf/smaas/vpc-and-base.py +++ /dev/null @@ -1,1418 +0,0 @@ -### -# Copyright (c) 2016 Nike Inc. -# -# 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. -### - -from io import open -from troposphere import Ref, Template, Parameter, Output, GetAtt, Join, AWS_ACCOUNT_ID, Equals, If -from troposphere.ec2 import VPC -from troposphere.ec2 import DHCPOptions -from troposphere.ec2 import VPCDHCPOptionsAssociation -from troposphere.ec2 import RouteTable -from troposphere.ec2 import InternetGateway -from troposphere.ec2 import Route -from troposphere.ec2 import VPCGatewayAttachment -from troposphere.ec2 import Subnet -from troposphere.ec2 import SubnetRouteTableAssociation -from troposphere.ec2 import SecurityGroup -from troposphere.ec2 import SecurityGroupIngress -from troposphere.iam import Role, InstanceProfile, PolicyType, Policy -from troposphere.kms import Key -from troposphere.rds import DBInstance, DBSubnetGroup, DBParameterGroup -from troposphere.route53 import HostedZone, HostedZoneVPCs, HostedZoneConfiguration -from troposphere.s3 import Bucket, Private, BucketPolicy, PublicRead, WebsiteConfiguration, VersioningConfiguration - -from smaas.support.constants import aws_region_ref -from smaas.support.tags import CerberusTags -from smaas.support.network import CerberusNetwork - -CF_FILE = '../../src/main/resources/cloudformation/vpc-and-base.json' - -# Create Template -template = Template() -template.description = "Creates the Cerberus VPC and foundational resources" -template.version = '2010-09-09' - -cerberus_tags = CerberusTags() -cerberus_tags.add_tag_parameters(template) - -cerberus_network = CerberusNetwork() -cerberus_network.add_parameters(template) - -### -# -# Parameters -# -### - -# Required account admin ARN for KMS key administration -account_admin_arn_param = template.add_parameter(Parameter( - "accountAdminArn", - Description="The ARN for a IAM user, group or role that can create this stack.", - Type="String" -)) - -# Availability Zones: We use a more generic 1, 2, 3 to represent the actual zones for abstraction -az1_param = template.add_parameter(Parameter( - "az1", - Description="An availability zone identifier for zone '1'", - Type="String", - AllowedPattern="[a-z]{2}-[a-z]+-\d\w" -)) - -az2_param = template.add_parameter(Parameter( - "az2", - Description="An availability zone identifier for zone '2'", - Type="String", - AllowedPattern="[a-z]{2}-[a-z]+-\d\w" -)) - -az3_param = template.add_parameter(Parameter( - "az3", - Description="An availability zone identifier for zone '3'", - Type="String", - AllowedPattern="[a-z]{2}-[a-z]+-\d\w" -)) - -# RDS instance parameters for the management DB -cms_db_allocated_storage_param = template.add_parameter(Parameter( - "cmsDbAllocatedStorage", - Description="The allocated storage for the RDS instance.", - Type="String", - Default="100" -)) - -cms_db_instance_size_param = template.add_parameter(Parameter( - "cmsDbInstanceSize", - Description="MySQL DB instance class", - Type="String", - Default="db.r3.large" -)) - -cms_db_name_param = template.add_parameter(Parameter( - "cmsDbName", - Description="The name of the database initially create on the RDS instance", - Type="String" -)) - -cms_db_master_username_param = template.add_parameter(Parameter( - "cmsDbMasterUsername", - Description="Master username for the cms RDS instance", - Type="String" -)) - -cms_db_master_password_param = template.add_parameter(Parameter( - "cmsDbMasterPassword", - Description="Master password for the cms RDS instance", - Type="String" -)) - -cms_db_port_param = template.add_parameter(Parameter( - "cmsDbPort", - Description="Port for the cms DB instance", - Type="Number", - Default="3306" -)) - -# Route 53 hosted zone and record sets for VPC -vpc_hosted_zone_name_param = template.add_parameter(Parameter( - "vpcHostedZoneName", - Description="The hosted zone name used in the Cerberus VPC", - Type="String" -)) - -az_by_identifier_map = { - 1: az1_param, - 2: az2_param, - 3: az3_param -} - -### -# -# VPC and Supporting Networking -# -### - -# Create the VPC -cerberus_vpc = template.add_resource(VPC( - "CerberusVpc", - CidrBlock=Ref(cerberus_network.vpc_cidr_block_param), - EnableDnsSupport=True, - EnableDnsHostnames=True, - Tags=cerberus_tags.get_tags_as_list() -)) - -# DHCP Options -# http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_DHCP_Options.html -# If you're using AmazonProvidedDNS in us-east-1, specify ec2.internal. -# If you're using AmazonProvidedDNS in another region, specify region.compute.internal -# (for example, ap-northeast-1.compute.internal). Otherwise, specify a domain name -# (for example, MyCompany.com). This value is used to complete unqualified DNS hostnames. -template.add_condition("RegionEqualsEastOne", Equals(aws_region_ref, "us-east-1")) -cerberus_dhcp_options = template.add_resource(DHCPOptions( - "CerberusDhcpOptions", - DomainName=If("RegionEqualsEastOne", "ec2.internal", Join(".", [aws_region_ref, "compute.internal"])), - DomainNameServers=["AmazonProvidedDNS"], - Tags=cerberus_tags.get_tags_as_list() -)) - -cerberus_dhcp_options_association = template.add_resource(VPCDHCPOptionsAssociation( - "CerberusVpcDhcpOptionsAssociation", - DhcpOptionsId=Ref(cerberus_dhcp_options), - VpcId=Ref(cerberus_vpc) -)) - -# Routing, Subnets and Gateways -cerberus_route_table = template.add_resource(RouteTable( - "CerberusRouteTable", - VpcId=Ref(cerberus_vpc), - Tags=cerberus_tags.get_tags_as_list() -)) - -cerberus_internet_gateway = template.add_resource(InternetGateway( - "CerberusInternetGateway", - Tags=cerberus_tags.get_tags_as_list() -)) - -cerberus_route_internet_gateway = template.add_resource(Route( - "CerberusRouteInternetGateway", - DestinationCidrBlock=Ref(cerberus_network.gateway_cidr_block_param), - GatewayId=Ref(cerberus_internet_gateway), - RouteTableId=Ref(cerberus_route_table) -)) - -cerberus_vpc_gateway_attachment = template.add_resource(VPCGatewayAttachment( - "CerberusVpcGatewayAttachment", - InternetGatewayId=Ref(cerberus_internet_gateway), - VpcId=Ref(cerberus_vpc) -)) - -# Associate the three subnets to the route table -vpc_subnet_resources_by_zone_identifier = {} -for zone_identifier, subnet_cidr_block in cerberus_network.subnet_cidr_block_param_by_az_map.items(): - subnet = template.add_resource(Subnet( - "CerberusSubnetForAz{0}".format(zone_identifier), - AvailabilityZone=Ref(az_by_identifier_map[zone_identifier]), - CidrBlock=Ref(subnet_cidr_block), - MapPublicIpOnLaunch=True, - VpcId=Ref(cerberus_vpc), - Tags=cerberus_tags.get_tags_as_list() - )) - - vpc_subnet_resources_by_zone_identifier[zone_identifier] = subnet - - template.add_resource(SubnetRouteTableAssociation( - "CerberusSubnetRouteTableAssociationForAz{0}".format(zone_identifier), - RouteTableId=Ref(cerberus_route_table), - SubnetId=Ref(subnet) - )) - -### -# -# Security Groups -# -### - -tools_ingress_sg = template.add_resource(SecurityGroup( - "ToolsIngressSg", - GroupDescription="Administration ingress from tools NAT boxes", - VpcId=Ref(cerberus_vpc), - Tags=cerberus_tags.get_tags_as_list() -)) - -gateway_elb_sg = template.add_resource(SecurityGroup( - "GatewayElbSg", - GroupDescription="Gateway ELB Security Group", - VpcId=Ref(cerberus_vpc), - Tags=cerberus_tags.get_gateway_tags_as_list() -)) - -gateway_server_sg = template.add_resource(SecurityGroup( - "GatewayServerSg", - GroupDescription="Gateway Server Instance Security Group", - VpcId=Ref(cerberus_vpc), - Tags=cerberus_tags.get_tags_as_list() -)) - -cms_elb_sg = template.add_resource(SecurityGroup( - "CmsElbSg", - GroupDescription="Management Server ELB Security Group", - VpcId=Ref(cerberus_vpc), - Tags=cerberus_tags.get_tags_as_list() -)) - -cms_sg = template.add_resource(SecurityGroup( - "CmsSg", - GroupDescription="Management Server Instance Security Group", - VpcId=Ref(cerberus_vpc), - Tags=cerberus_tags.get_tags_as_list() -)) - -cms_db_sg = template.add_resource(SecurityGroup( - "CmsDbSg", - GroupDescription="Management Server Database Security Group", - VpcId=Ref(cerberus_vpc), - Tags=cerberus_tags.get_tags_as_list() -)) - -vault_server_elb_sg = template.add_resource(SecurityGroup( - "VaultServerElbSg", - GroupDescription="Vault ServerELB Security Group", - VpcId=Ref(cerberus_vpc), - Tags=cerberus_tags.get_tags_as_list() -)) - -vault_client_sg = template.add_resource(SecurityGroup( - "VaultClientSg", - GroupDescription="Vault Client Security Group", - VpcId=Ref(cerberus_vpc), - Tags=cerberus_tags.get_tags_as_list() -)) - -vault_server_sg = template.add_resource(SecurityGroup( - "VaultServerSg", - GroupDescription="Vault Server Security Group", - VpcId=Ref(cerberus_vpc), - Tags=cerberus_tags.get_tags_as_list() -)) - -consul_client_sg = template.add_resource(SecurityGroup( - "ConsulClientSg", - GroupDescription="Consul Client Security Group", - VpcId=Ref(cerberus_vpc), - Tags=cerberus_tags.get_tags_as_list() -)) - -consul_server_sg = template.add_resource(SecurityGroup( - "ConsulServerSg", - GroupDescription="Consul Server Security Group", - VpcId=Ref(cerberus_vpc), - Tags=cerberus_tags.get_tags_as_list() -)) - -### -# -# Security Group Ingress Rules -# -### - -# Allow HTTPS through from the internet to the ELB -template.add_resource(SecurityGroupIngress( - "GatewayElbIngressFromInternetSg443", - GroupId=Ref(gateway_elb_sg), - CidrIp=Ref(cerberus_network.gateway_cidr_block_param), - FromPort=443, - IpProtocol="tcp", - ToPort=443 -)) - -# Allow the internet facing gateway ELB to talk to the gateway instances over HTTPS (443) -template.add_resource(SecurityGroupIngress( - "GatewayServerIngressFromGatewayElb443", - GroupId=Ref(gateway_server_sg), - SourceSecurityGroupId=Ref(gateway_elb_sg), - FromPort=443, - IpProtocol="tcp", - ToPort=443 -)) - -# Allow gateway server instances to talk to the management server ELB over HTTPS (443) -template.add_resource(SecurityGroupIngress( - "CmsElbFromGatewayServer443", - GroupId=Ref(cms_elb_sg), - SourceSecurityGroupId=Ref(gateway_server_sg), - FromPort=443, - IpProtocol="tcp", - ToPort=443 -)) - -# Allow gateway server instances to talk to the vault ELB over HTTPS (443) -template.add_resource(SecurityGroupIngress( - "VaultServerElbFromGatewayServer443", - GroupId=Ref(vault_server_elb_sg), - SourceSecurityGroupId=Ref(gateway_server_sg), - FromPort=443, - IpProtocol="tcp", - ToPort=443 -)) - -# Allow the management server ELB to talk to the management server instances over HTTPS (8443) -template.add_resource(SecurityGroupIngress( - "CmsIngressFromCmsElb8443", - GroupId=Ref(cms_sg), - SourceSecurityGroupId=Ref(cms_elb_sg), - FromPort=8443, - IpProtocol="tcp", - ToPort=8443 -)) - -# Allow the management server ELB to talk to the management server instances over HTTP (8080) -template.add_resource(SecurityGroupIngress( - "CmsIngressFromCmsElb8080", - GroupId=Ref(cms_sg), - SourceSecurityGroupId=Ref(cms_elb_sg), - FromPort=8080, - IpProtocol="tcp", - ToPort=8080 -)) - -# Allow the management server ELB to talk to the management server instances over HTTP (8077) -template.add_resource(SecurityGroupIngress( - "CmsIngressFromCmsElb8077", - GroupId=Ref(cms_sg), - SourceSecurityGroupId=Ref(cms_elb_sg), - FromPort=8077, - IpProtocol="tcp", - ToPort=8077 -)) - -# Allow the management server instances to talk to the Vault ELB over HTTPS (443) -template.add_resource(SecurityGroupIngress( - "VaultElbIngressFromCms443", - GroupId=Ref(vault_server_elb_sg), - SourceSecurityGroupId=Ref(cms_sg), - FromPort=443, - IpProtocol="tcp", - ToPort=443 -)) - -# Allow the management server instances to access the management db instance -template.add_resource(SecurityGroupIngress( - "CmsDbFromCms", - GroupId=Ref(cms_db_sg), - SourceSecurityGroupId=Ref(cms_sg), - FromPort=Ref(cms_db_port_param), - IpProtocol="tcp", - ToPort=Ref(cms_db_port_param) -)) - -# Allow the Vault server ELB to talk to Vault server instances over HTTPS (8200) -template.add_resource(SecurityGroupIngress( - "VaultServerIngressFromVaultElb8200", - GroupId=Ref(vault_server_sg), - SourceSecurityGroupId=Ref(vault_server_elb_sg), - FromPort=8200, - IpProtocol="tcp", - ToPort=8200 -)) - -# Allow Vault server instances to talk to other Vault server instances on 8201 -# Vault does internal communication by default on the port above the normal listening port, in this case 8201 -template.add_resource(SecurityGroupIngress( - "VaultServerIngress8201", - GroupId=Ref(vault_server_sg), - SourceSecurityGroupId=Ref(vault_client_sg), - FromPort=8201, - IpProtocol="tcp", - ToPort=8201 -)) - -# Allow Vault server instances to talk to other Vault server instances on 8200 -template.add_resource(SecurityGroupIngress( - "VaultServerIngress8200", - GroupId=Ref(vault_server_sg), - SourceSecurityGroupId=Ref(vault_client_sg), - FromPort=8200, - IpProtocol="tcp", - ToPort=8200 -)) - -# Allow Consul agents to talk on 8300, 8301 and 8302 -template.add_resource(SecurityGroupIngress( - "ConsulServerIngress8300", - GroupId=Ref(consul_server_sg), - SourceSecurityGroupId=Ref(consul_client_sg), - FromPort=8300, - IpProtocol="tcp", - ToPort=8300 -)) - -for port in [8301, 8302]: - template.add_resource(SecurityGroupIngress( - "ConsulServerIngressTcp{0}".format(port), - GroupId=Ref(consul_server_sg), - SourceSecurityGroupId=Ref(consul_client_sg), - FromPort=port, - IpProtocol="tcp", - ToPort=port - )) - - template.add_resource(SecurityGroupIngress( - "ConsulServerIngressUdp{0}".format(port), - GroupId=Ref(consul_server_sg), - SourceSecurityGroupId=Ref(consul_client_sg), - FromPort=port, - IpProtocol="udp", - ToPort=port - )) - -### -# -# IAM roles -# -### - -cloud_front_log_processor_lambda_iam_role = template.add_resource(Role( - "WAFLambdaRole", - AssumeRolePolicyDocument={ - "Version": "2012-10-17", - "Statement": [{ - "Effect": "Allow", - "Principal": { - "Service": ["lambda.amazonaws.com"] - }, - "Action": ["sts:AssumeRole"] - }] - }, - Policies=[ - Policy( - PolicyName="WAFAccess", - PolicyDocument={ - "Statement": [{ - "Effect": "Allow", - "Action": [ - "waf:*", - ], - "Resource": "*" - }], - } - ), - Policy( - PolicyName="S3BucketList", - PolicyDocument={ - "Statement": [{ - "Effect": "Allow", - "Action": [ - "s3:ListAllMyBuckets", - ], - "Resource": "*" - }], - } - ), - Policy( - PolicyName="LogsAccess", - PolicyDocument={ - "Statement": [{ - "Effect": "Allow", - "Action": [ - "logs:*", - ], - "Resource": "*" - }], - } - ), - Policy( - PolicyName="LambdaAccess", - PolicyDocument={ - "Statement": [{ - "Effect": "Allow", - "Action": [ - "lambda:*", - ], - "Resource": "*" - }], - } - ), - Policy( - PolicyName="CloudFormationAccess", - PolicyDocument={ - "Statement": [{ - "Effect": "Allow", - "Action": [ - "cloudformation:DescribeStacks", - "cloudformation:ListStacks", - ], - "Resource": "*" - }], - } - ), - Policy( - PolicyName="CloudWatchAccess", - PolicyDocument={ - "Statement": [{ - "Effect": "Allow", - "Action": [ - "cloudwatch:PutMetricData", - ], - "Resource": "*" - }], - } - ) - ], - Path="/" -)) - -gateway_iam_role = template.add_resource(Role( - "GatewayIamRole", - AssumeRolePolicyDocument={ - "Version": "2012-10-17", - "Statement": [{ - "Effect": "Allow", - "Principal": { - "Service": ["ec2.amazonaws.com"] - }, - "Action": ["sts:AssumeRole"] - }] - }, - Policies=[ - Policy( - PolicyName="gatewayPolicy", - PolicyDocument={ - "Statement": [{ - "Effect": "Allow", - "Action": [ - "EC2:Describe*", - "cloudformation:SignalResource" - ], - "Resource": "*" - }], - } - ) - ], - Path="/" -)) - -cms_iam_role = template.add_resource(Role( - "CmsIamRole", - AssumeRolePolicyDocument={ - "Version": "2012-10-17", - "Statement": [{ - "Effect": "Allow", - "Principal": { - "Service": ["ec2.amazonaws.com"] - }, - "Action": ["sts:AssumeRole"] - }] - }, - Policies=[ - Policy( - PolicyName="cmsPolicy", - PolicyDocument={ - "Statement": [{ - "Effect": "Allow", - "Action": [ - "EC2:Describe*", - "cloudformation:SignalResource" - ], - "Resource": "*" - }], - } - ) - ], - Path="/" -)) - -vault_iam_role = template.add_resource(Role( - "VaultIamRole", - AssumeRolePolicyDocument={ - "Version": "2012-10-17", - "Statement": [{ - "Effect": "Allow", - "Principal": { - "Service": ["ec2.amazonaws.com"] - }, - "Action": ["sts:AssumeRole"] - }] - }, - Policies=[ - Policy( - PolicyName="consulPolicy", - PolicyDocument={ - "Statement": [{ - "Effect": "Allow", - "Action": [ - "EC2:Describe*", - "cloudformation:SignalResource" - ], - "Resource": "*" - }], - } - ) - ], - Path="/" -)) - -consul_iam_role = template.add_resource(Role( - "ConsulIamRole", - AssumeRolePolicyDocument={ - "Version": "2012-10-17", - "Statement": [{ - "Effect": "Allow", - "Principal": { - "Service": ["ec2.amazonaws.com"] - }, - "Action": ["sts:AssumeRole"] - }] - }, - Policies=[ - Policy( - PolicyName="consulPolicy", - PolicyDocument={ - "Statement": [{ - "Effect": "Allow", - "Action": [ - "EC2:Describe*", - "cloudformation:DescribeStacks", - "cloudformation:SignalResource" - ], - "Resource": "*" - }], - } - ) - ], - Path="/" -)) - -gateway_instance_profile = template.add_resource(InstanceProfile( - "GatewayInstanceProfile", - Path="/", - Roles=[ - Ref(gateway_iam_role) - ] -)) - -cms_instance_profile = template.add_resource(InstanceProfile( - "CmsInstanceProfile", - Path="/", - Roles=[ - Ref(cms_iam_role) - ] -)) - -vault_instance_profile = template.add_resource(InstanceProfile( - "VaultInstanceProfile", - Path="/", - Roles=[ - Ref(vault_iam_role) - ] -)) - -consul_instance_profile = template.add_resource(InstanceProfile( - "ConsulInstanceProfile", - Path="/", - Roles=[ - Ref(consul_iam_role) - ] -)) - -### -# -# S3 Bucket for the dashboard client-side application -# -### - -dashboard_bucket = template.add_resource(Bucket( - "DashboardBucket", - AccessControl=PublicRead, - WebsiteConfiguration=WebsiteConfiguration( - IndexDocument="index.html", - ErrorDocument="error.html" - ), - Tags=cerberus_tags.get_tags() -)) - -dashboard_bucket_access_policy = template.add_resource(BucketPolicy( - "DashboardBucketAccessPolicy", - Bucket=Ref(dashboard_bucket), - PolicyDocument={ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "Allow-Bucket-Access", - "Effect": "Allow", - "Principal": "*", - "Action": [ - "s3:GetObject" - ], - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:aws:s3:::", - Ref(dashboard_bucket), - "/*" - ] - ] - } - ] - } - ] - } -)) - -### -# -# S3 Bucket for runtime configuration files for Vault and Consul -# -### - -config_bucket = template.add_resource(Bucket( - "CerberusConfigBucket", - AccessControl=Private, - VersioningConfiguration=VersioningConfiguration( - Status="Enabled" - ), - Tags=cerberus_tags.get_tags() -)) - -config_bucket_access_policy = template.add_resource(BucketPolicy( - "CerberusConfigBucketAccessPolicy", - Bucket=Ref(config_bucket), - PolicyDocument={ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "Allow-ListBucket-Access", - "Effect": "Allow", - "Principal": { - "AWS": [ - GetAtt(gateway_iam_role, "Arn"), - GetAtt(cms_iam_role, "Arn"), - GetAtt(vault_iam_role, "Arn"), - GetAtt(consul_iam_role, "Arn"), - GetAtt(cloud_front_log_processor_lambda_iam_role, "Arn") - ] - }, - "Action": [ - "s3:ListBucket" - ], - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:aws:s3:::", - Ref(config_bucket) - ] - ] - }, - { - "Fn::Join": [ - "", - [ - "arn:aws:s3:::", - Ref(config_bucket), - "/*" - ] - ] - } - ] - }, - { - "Sid": "Allow-Bucket-Access-For-Gateway", - "Effect": "Allow", - "Principal": { - "AWS": [ - GetAtt(gateway_iam_role, "Arn") - ] - }, - "Action": [ - "s3:*" - ], - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:aws:s3:::", - Ref(config_bucket), - "/data/gateway/*" - ] - ] - }, - { - "Fn::Join": [ - "", - [ - "arn:aws:s3:::", - Ref(config_bucket), - "/data/gateway" - ] - ] - } - ] - }, - { - "Sid": "Allow-Bucket-Access-For-CMS", - "Effect": "Allow", - "Principal": { - "AWS": [ - GetAtt(cms_iam_role, "Arn") - ] - }, - "Action": [ - "s3:*" - ], - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:aws:s3:::", - Ref(config_bucket), - "/data/cms/*" - ] - ] - }, - { - "Fn::Join": [ - "", - [ - "arn:aws:s3:::", - Ref(config_bucket), - "/data/cms" - ] - ] - } - ] - }, - { - "Sid": "Allow-Bucket-Access-For-Log-Processing-Lambda", - "Effect": "Allow", - "Principal": { - "AWS": [ - GetAtt(cloud_front_log_processor_lambda_iam_role, "Arn") - ] - }, - "Action": [ - "s3:*" - ], - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:aws:s3:::", - Ref(config_bucket), - "/data/cloud-front-log-processor/*" - ] - ] - }, - { - "Fn::Join": [ - "", - [ - "arn:aws:s3:::", - Ref(config_bucket), - "/data/cloud-front-log-processor" - ] - ] - } - ] - }, - { - "Sid": "Allow-Bucket-Access-For-Vault", - "Effect": "Allow", - "Principal": { - "AWS": [ - GetAtt(vault_iam_role, "Arn") - ] - }, - "Action": [ - "s3:*" - ], - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:aws:s3:::", - Ref(config_bucket), - "/data/vault/*" - ] - ] - }, - { - "Fn::Join": [ - "", - [ - "arn:aws:s3:::", - Ref(config_bucket), - "/data/vault" - ] - ] - }, - { - "Fn::Join": [ - "", - [ - "arn:aws:s3:::", - Ref(config_bucket), - "/data/consul/*" - ] - ] - }, - { - "Fn::Join": [ - "", - [ - "arn:aws:s3:::", - Ref(config_bucket), - "/data/consul" - ] - ] - }, - { - "Fn::Join": [ - "", - [ - "arn:aws:s3:::", - Ref(config_bucket), - "/config/secrets.json" - ] - ] - } - ] - }, - { - "Sid": "Allow-Bucket-Access-For-Consul", - "Effect": "Allow", - "Principal": { - "AWS": [ - GetAtt(consul_iam_role, "Arn") - ] - }, - "Action": [ - "s3:*" - ], - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:aws:s3:::", - Ref(config_bucket), - "/data/consul/*" - ] - ] - }, - { - "Fn::Join": [ - "", - [ - "arn:aws:s3:::", - Ref(config_bucket), - "/data/consul" - ] - ] - }, - { - "Fn::Join": [ - "", - [ - "arn:aws:s3:::", - Ref(config_bucket), - "/consul/*" - ] - ] - }, - { - "Fn::Join": [ - "", - [ - "arn:aws:s3:::", - Ref(config_bucket), - "/consul" - ] - ] - } - ] - } - ] - } -)) - -### -# -# KMS for config file decryption by vault and consul instances -# -### - -config_file_key = template.add_resource(Key( - "ConfigFileKey", - Description="Cerberus encryption key for storing config files in an encrypted state.", - Enabled=True, - KeyPolicy={ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "Allow-Root-User", - "Effect": "Allow", - "Principal": { - "AWS": [ - { - "Fn::Join": [ - "", - [ - "arn:aws:iam::", - Ref(AWS_ACCOUNT_ID), - ":root" - ] - ] - } - ] - }, - "Action": [ - "kms:*" - ], - "Resource": "*" - }, - { - "Sid": "Allow-Decrypt-From-Instances", - "Effect": "Allow", - "Principal": { - "AWS": [ - GetAtt(gateway_iam_role, "Arn"), - GetAtt(cms_iam_role, "Arn"), - GetAtt(vault_iam_role, "Arn"), - GetAtt(consul_iam_role, "Arn") - ] - }, - "Action": [ - "kms:Decrypt" - ], - "Resource": "*" - }, - { - "Sid": "Allow-Account-Admin", - "Effect": "Allow", - "Principal": { - "AWS": [ - Ref(account_admin_arn_param) - ] - }, - "Action": [ - "kms:*" - ], - "Resource": "*" - } - ] - } -)) - -### -# -# Policy for the CMS role being able to manage KMS keys -# -### - -cms_kms_policy = template.add_resource(PolicyType( - "CmsKmsPolicy", - PolicyName="CmsKmsPolicy", - Roles=[ - Ref(cms_iam_role) - ], - PolicyDocument={ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "kms:CreateAlias", - "kms:CreateKey", - "kms:DeleteAlias", - "kms:DescribeKey", - "kms:DisableKey", - "kms:DisableKeyRotation", - "kms:EnableKey", - "kms:EnableKeyRotation", - "kms:GetKeyPolicy", - "kms:GetKeyRotationStatus", - "kms:ListAliases", - "kms:ListKeyPolicies", - "kms:ListKeys", - "kms:PutKeyPolicy", - "kms:UpdateAlias", - "kms:UpdateKeyDescription" - ], - "Resource": [ - "*" - ] - } - ] - } -)) - -### -# -# RDS for the management service -# -### - -cms_db_subnet_group = template.add_resource(DBSubnetGroup( - "CmsDatabaseSubnetGroup", - DBSubnetGroupDescription="DB Subnet Group for management DB", - SubnetIds=[ - Ref(vpc_subnet_resources_by_zone_identifier[1]), - Ref(vpc_subnet_resources_by_zone_identifier[2]), - Ref(vpc_subnet_resources_by_zone_identifier[3]) - ] -)) - -cms_db_param_group = template.add_resource(DBParameterGroup( - "CmsDatabaseParamGroup", - Description="Default parameters for the cms DB", - Family="mysql5.6", - Parameters={ - "slow_query_log": 1, - "log_output": "TABLE" - } -)) - -cms_db_instance = template.add_resource(DBInstance( - "CmsDatabase", - AllocatedStorage=Ref(cms_db_allocated_storage_param), - AllowMajorVersionUpgrade=True, - AutoMinorVersionUpgrade=True, - BackupRetentionPeriod=14, - DBInstanceClass=Ref(cms_db_instance_size_param), - DBName=Ref(cms_db_name_param), - DBParameterGroupName=Ref(cms_db_param_group), - DBSubnetGroupName=Ref(cms_db_subnet_group), - Engine="MySQL", - MasterUsername=Ref(cms_db_master_username_param), - MasterUserPassword=Ref(cms_db_master_password_param), - VPCSecurityGroups=[ - Ref(tools_ingress_sg), - Ref(cms_db_sg) - ], - MultiAZ=True, - PreferredBackupWindow="13:14-13:44", - PreferredMaintenanceWindow="tue:06:48-tue:07:18", - PubliclyAccessible=False, - StorageEncrypted=True, - StorageType="gp2", - Port=Ref(cms_db_port_param), - Tags=cerberus_tags.get_tags_as_list() -)) - -### -# -# VPC Hosted Zone and Record Sets -# -### - -vpc_hosted_zone = template.add_resource(HostedZone( - "CerberusInternalHostedZone", - Name=Join(".", [aws_region_ref, Ref(vpc_hosted_zone_name_param)]), - VPCs=[ - HostedZoneVPCs( - VPCId=Ref(cerberus_vpc), - VPCRegion=aws_region_ref - ) - ], - HostedZoneTags=cerberus_tags.get_tags(), - HostedZoneConfig=HostedZoneConfiguration( - Comment="The hosted zone for the Cerberus VPC" - ) -)) - -### -# -# Outputs -# -### - -template.add_output(Output( - "vpcId", - Value=Ref(cerberus_vpc) -)) - -template.add_output(Output( - "gatewayIamRoleArn", - Value=GetAtt(gateway_iam_role, "Arn") -)) - -template.add_output(Output( - "cmsIamRoleArn", - Value=GetAtt(cms_iam_role, "Arn") -)) - -template.add_output(Output( - "consulIamRoleArn", - Value=GetAtt(consul_iam_role, "Arn") -)) - -template.add_output(Output( - "vaultIamRoleArn", - Value=GetAtt(vault_iam_role, "Arn") -)) - -template.add_output(Output( - "cloudFrontLogProcessorLambdaIamRoleArn", - Value=GetAtt(cloud_front_log_processor_lambda_iam_role, "Arn") -)) - -template.add_output(Output( - "gatewayInstanceProfileName", - Value=Ref(gateway_instance_profile) -)) - -template.add_output(Output( - "cmsInstanceProfileName", - Value=Ref(cms_instance_profile) -)) - -template.add_output(Output( - "consulInstanceProfileName", - Value=Ref(consul_instance_profile) -)) - -template.add_output(Output( - "vaultInstanceProfileName", - Value=Ref(vault_instance_profile) -)) - -template.add_output(Output( - "toolsIngressSgId", - Value=GetAtt(tools_ingress_sg, "GroupId") -)) - -template.add_output(Output( - "gatewayElbSgId", - Value=GetAtt(gateway_elb_sg, "GroupId") -)) - -template.add_output(Output( - "gatewayServerSgId", - Value=GetAtt(gateway_server_sg, "GroupId") -)) - -template.add_output(Output( - "cmsElbSgId", - Value=GetAtt(cms_elb_sg, "GroupId") -)) - -template.add_output(Output( - "cmsSgId", - Value=GetAtt(cms_sg, "GroupId") -)) - -template.add_output(Output( - "cmsDbSgId", - Value=GetAtt(cms_db_sg, "GroupId") -)) - -template.add_output(Output( - "vaultServerElbSgId", - Value=GetAtt(vault_server_elb_sg, "GroupId") -)) - -template.add_output(Output( - "vaultClientSgId", - Value=GetAtt(vault_client_sg, "GroupId") -)) - -template.add_output(Output( - "vaultServerSgId", - Value=GetAtt(vault_server_sg, "GroupId") -)) - -template.add_output(Output( - "consulClientSgId", - Value=GetAtt(consul_client_sg, "GroupId") -)) - -template.add_output(Output( - "consulServerSgId", - Value=GetAtt(consul_server_sg, "GroupId") -)) - -template.add_output(Output( - "configFileKeyId", - Value=Ref(config_file_key) -)) - -template.add_output(Output( - "dashboardBucketName", - Value=Ref(dashboard_bucket) -)) - -template.add_output(Output( - "dashboardBucketWebsiteUrl", - Value=GetAtt(dashboard_bucket, "WebsiteURL") -)) - -template.add_output(Output( - "configBucketName", - Value=Ref(config_bucket) -)) - -template.add_output(Output( - "configBucketDomainName", - Value=GetAtt(config_bucket, "DomainName") -)) - -template.add_output(Output( - "cmsDbId", - Value=Ref(cms_db_instance) -)) - -template.add_output(Output( - "cmsDbAddress", - Value=GetAtt(cms_db_instance, "Endpoint.Address") -)) - -template.add_output(Output( - "cmsDbPort", - Value=GetAtt(cms_db_instance, "Endpoint.Port") -)) - -template.add_output(Output( - "cmsDbJdbcConnectionString", - Description="JDBC connection string for cms database", - Value=Join("", [ - "jdbc:mysql://", - GetAtt(cms_db_instance, "Endpoint.Address"), - ":", - GetAtt(cms_db_instance, "Endpoint.Port"), - "/", - Ref(cms_db_name_param), - "?useUnicode=true&characterEncoding=utf8&useLegacyDatetimeCode=false&serverTimezone=UTC" - ]) -)) - -template.add_output(Output( - "vpcHostedZoneId", - Description="The VPC hosted zone ID", - Value=Ref(vpc_hosted_zone) -)) - -for zone_identifier, subnet in vpc_subnet_resources_by_zone_identifier.items(): - template.add_output(Output( - "vpcSubnetIdForAz{0}".format(zone_identifier), - Value=Ref(subnet) - )) - -template.add_output(Output( - "cmsKmsPolicyId", - Description="The policy id for CMS managing KMS keys", - Value=Ref(cms_kms_policy) -)) - -cerberus_network.add_outputs(template) - -### -# -# Print! -# -### - -cf_file = open(CF_FILE, 'w') -cf_file.truncate() -cf_file.write(template.to_json(indent=0)) -cf_file.close() - -print("CloudFormation template written to: {0}".format(CF_FILE)) diff --git a/src/integration-test/java/com/nike/cerberus/operation/RestoreCompleteCerberusDataFromS3BackupOperationUserAcceptanceTest.java b/src/integration-test/java/com/nike/cerberus/operation/RestoreCompleteCerberusDataFromS3BackupOperationUserAcceptanceTest.java index adb5d06f..e90f6719 100644 --- a/src/integration-test/java/com/nike/cerberus/operation/RestoreCompleteCerberusDataFromS3BackupOperationUserAcceptanceTest.java +++ b/src/integration-test/java/com/nike/cerberus/operation/RestoreCompleteCerberusDataFromS3BackupOperationUserAcceptanceTest.java @@ -23,7 +23,6 @@ import com.nike.cerberus.operation.core.RestoreCerberusBackupOperation; import com.nike.cerberus.service.ConsoleService; import com.nike.cerberus.utils.TestUtils; -import com.nike.cerberus.vault.VaultAdminClientFactory; import com.nike.vault.client.StaticVaultUrlResolver; import okhttp3.OkHttpClient; import org.apache.http.conn.ssl.NoopHostnameVerifier; @@ -34,8 +33,8 @@ import java.io.IOException; -import static com.nike.cerberus.client.CerberusAdminClientFactory.DEFAULT_TIMEOUT; -import static com.nike.cerberus.client.CerberusAdminClientFactory.DEFAULT_TIMEOUT_UNIT; +import static com.nike.cerberus.client.HttpClientFactory.DEFAULT_TIMEOUT; +import static com.nike.cerberus.client.HttpClientFactory.DEFAULT_TIMEOUT_UNIT; import static org.mockito.Mockito.*; import static org.mockito.MockitoAnnotations.initMocks; @@ -46,7 +45,7 @@ public class RestoreCompleteCerberusDataFromS3BackupOperationUserAcceptanceTest { @Spy - private CerberusAdminClientFactory vaultAdminClientFactory; + private CerberusAdminClientFactory cerberusAdminClientFactory; @Mock private ConsoleService consoleService; @@ -65,7 +64,7 @@ public void before() { operation = new RestoreCerberusBackupOperation( CerberusModule.configObjectMapper(), consoleService, - vaultAdminClientFactory); + cerberusAdminClientFactory); when(command.getCerberusUrl()).thenReturn(TestUtils.getRequiredEnvVar("CERBERUS_URL", "The Cerberus API to restore against")); @@ -85,7 +84,7 @@ public void before() { CerberusAdminClient adminClient = new CerberusAdminClient( new StaticVaultUrlResolver("http://127.0.0.1:8200"), - new VaultAdminClientFactory.RootCredentialsProvider(rootToken), + null, new OkHttpClient.Builder() .hostnameVerifier(new NoopHostnameVerifier()) .connectTimeout(DEFAULT_TIMEOUT, DEFAULT_TIMEOUT_UNIT) @@ -95,7 +94,7 @@ public void before() { CerberusModule.configObjectMapper() ); - when(vaultAdminClientFactory.createCerberusAdminClient(anyString())).thenReturn(adminClient); + when(cerberusAdminClientFactory.createCerberusAdminClient(anyString())).thenReturn(adminClient); operation.run(command); } diff --git a/src/integration-test/java/com/nike/cerberus/operation/core/GenerateCertsOperationIntegrationTest.java b/src/integration-test/java/com/nike/cerberus/operation/core/GenerateCertsOperationIntegrationTest.java new file mode 100644 index 00000000..81d34900 --- /dev/null +++ b/src/integration-test/java/com/nike/cerberus/operation/core/GenerateCertsOperationIntegrationTest.java @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.operation.core; + +import com.amazonaws.regions.Regions; +import com.amazonaws.services.identitymanagement.AmazonIdentityManagement; +import com.amazonaws.services.identitymanagement.AmazonIdentityManagementClient; +import com.amazonaws.services.identitymanagement.model.DeleteServerCertificateRequest; +import com.amazonaws.services.identitymanagement.model.UploadServerCertificateRequest; +import com.amazonaws.services.identitymanagement.model.UploadServerCertificateResult; +import com.amazonaws.services.route53.AmazonRoute53Client; +import com.google.common.collect.ImmutableSet; +import com.nike.cerberus.command.core.GenerateCertificateFilesCommand; +import com.nike.cerberus.domain.EnvironmentMetadata; +import com.nike.cerberus.service.CertificateService; +import com.nike.cerberus.service.ConsoleService; +import com.nike.cerberus.service.IdentityManagementService; +import com.nike.cerberus.store.ConfigStore; +import com.nike.cerberus.util.UuidSupplier; +import io.netty.handler.ssl.SslContextBuilder; +import org.apache.commons.lang3.StringUtils; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.UUID; + +import static com.fieldju.commons.PropUtils.getPropWithDefaultValue; +import static com.fieldju.commons.PropUtils.getRequiredProperty; +import static com.nike.cerberus.service.CertificateService.DOMAIN_CERT_CHAIN_FILE; +import static com.nike.cerberus.service.CertificateService.DOMAIN_CERT_FILE; +import static com.nike.cerberus.service.CertificateService.DOMAIN_CSR_FILE; +import static com.nike.cerberus.service.CertificateService.DOMAIN_FULL_CERT_WITH_CHAIN_CRT_FILE; +import static com.nike.cerberus.service.CertificateService.DOMAIN_PKCS1_KEY_FILE; +import static com.nike.cerberus.service.CertificateService.DOMAIN_PKCS8_KEY_FILE; +import static com.nike.cerberus.service.CertificateService.DOMAIN_PUBLIC_KEY_FILE; +import static com.nike.cerberus.service.CertificateService.USER_KEY_FILE; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.contains; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.initMocks; + +/** + * This integration test will generate certs using an provided ACME api and test that the certs are usable my an AWS ALB and Netty's SSL Context Builder. + * + * Running this Integration Test will automatically accept the TOS of the ACME provider if present. + * By Running this test you are agreeing with the Terms of Service of the ACME Provider API you provide. + * + * Required Props (can be set via env or system props) + * + * CERT_GEN_TEST_DOMAIN: A domain that will be used as the common name for the generated certs, + * you must also provide a hosted zone id that can create records for this domain. + * + * CERT_GEN_HOSTED_ZONE_ID: The hosted zone id that can create records for CERT_GEN_TEST_DOMAIN + * + * CERT_GEN_CONTACT_EMAIL: The email to use as the contact when generating the cert + * + * Optional Props + * + * CERT_GEN_ACME_API: the ACME api to test against, defaults to 'acme://letsencrypt.org/staging' + * + * EX: CERT_GEN_TEST_DOMAIN=cerberus-oss.io CERT_GEN_HOSTED_ZONE_ID=123123 CERT_GEN_CONTACT_EMAIL=justin.field@nike.com ./gradlew clean integrationTest -DintegrationTest.debug -DintegrationTest.single=GenerateCertsOperationIntegrationTest + * + */ +public class GenerateCertsOperationIntegrationTest { + + public static String CERT_DIR = "build/certs/"; + + @Mock + private GenerateCertificateFilesCommand command; + + @Mock + private ConsoleService consoleService; + + @Mock + private EnvironmentMetadata environmentMetadata; + + @Mock + private ConfigStore configStore; + + @Mock + IdentityManagementService identityManagementService; + + private GenerateCertificateFilesOperation operation; + + private File certDir; + + @Before + public void before() { + initMocks(this); + + CertificateService certificateService = new CertificateService(consoleService, + AmazonRoute53Client.builder().withRegion("us-west-2").build(), new UuidSupplier(), configStore, + identityManagementService, environmentMetadata); + + operation = new GenerateCertificateFilesOperation(certificateService, environmentMetadata, consoleService); + + certDir = new File(CERT_DIR); + } + + @Test + public void test_that_command_generates_valid_certs() throws IOException { + when(environmentMetadata.getName()).thenReturn("integration-test"); + when(environmentMetadata.getRegionName()).thenReturn("us-west-2"); + when(command.getBaseDomainName()).thenReturn(getRequiredProperty("CERT_GEN_TEST_DOMAIN", + "CERT_GEN_TEST_DOMAIN is a required env or system prop and must be a domain that you have a" + + " hosted zone for that can create records")); + when(command.getHostedZoneId()).thenReturn(getRequiredProperty("CERT_GEN_HOSTED_ZONE_ID", + "The hosted zone id that can create records for CERT_GEN_TEST_DOMAIN")); + when(consoleService.readLine(contains("Would you like to proceed?"))).thenReturn("y"); + when(command.enableLetsEncryptCertfix()).thenReturn(true); + when(command.getCertDir()).thenReturn(certDir.getAbsolutePath()); + when(consoleService.readLine(contains("accept"))) + .thenReturn("i accept"); + when(command.getAcmeApiUrl()).thenReturn(getPropWithDefaultValue("CERT_GEN_ACME_API", + "acme://letsencrypt.org/staging")); + when(command.getContactEmail()).thenReturn(getRequiredProperty("CERT_GEN_CONTACT_EMAIL", + "The email contact to use when generating the cert")); + + operation.run(command); + + ImmutableSet.of( + USER_KEY_FILE, + DOMAIN_PKCS1_KEY_FILE, + DOMAIN_PKCS8_KEY_FILE, + DOMAIN_CSR_FILE, + DOMAIN_FULL_CERT_WITH_CHAIN_CRT_FILE, + DOMAIN_CERT_FILE, + DOMAIN_CERT_CHAIN_FILE, + DOMAIN_PUBLIC_KEY_FILE + ).forEach(expectedFileName -> { + File expectedFile = new File(certDir.getAbsolutePath() + File.separator + expectedFileName); + assertTrue("The expected file should exist after running the generate certs command", expectedFile.exists()); + }); + + // test that the certs are usable by and ALB + assertThatCertsAreUsableByAws(); + + // test that the PKCS8 Private key and ACME generated cert are loadable by CMS + assertThatCertsAreUsableByCms(); + + System.out.println("The Keys and Certificates where properly generated and passed validation"); + } + + private void assertThatCertsAreUsableByAws() throws IOException { + String uuid = UUID.randomUUID().toString(); + AmazonIdentityManagement amazonIdentityManagement = AmazonIdentityManagementClient.builder().withRegion(Regions.DEFAULT_REGION).build(); + final UploadServerCertificateRequest request = new UploadServerCertificateRequest() + .withServerCertificateName("cert-gen-integration-test-" + uuid) + .withPath("/cert-gen-integration-test/" + uuid + "/") + .withCertificateBody(new String(Files.readAllBytes((new File(certDir.getAbsolutePath() + File.separator + DOMAIN_CERT_FILE).toPath())))) + .withCertificateChain(new String(Files.readAllBytes((new File(certDir.getAbsolutePath() + File.separator + DOMAIN_CERT_CHAIN_FILE).toPath())))) + .withPrivateKey(new String(Files.readAllBytes(new File(certDir.getAbsolutePath() + File.separator + DOMAIN_PKCS1_KEY_FILE).toPath()))); + + final UploadServerCertificateResult result = amazonIdentityManagement.uploadServerCertificate(request); + + assertTrue(StringUtils.isNotBlank(result.getServerCertificateMetadata().getArn())); + + amazonIdentityManagement.deleteServerCertificate( + new DeleteServerCertificateRequest() + .withServerCertificateName(result.getServerCertificateMetadata().getServerCertificateName()) + ); + } + + private void assertThatCertsAreUsableByCms() { + String[] cmsSupportedProtocols = new String[] { + "TLSv1.2" + }; + + try { + SslContextBuilder.forServer( + new File(certDir.getAbsolutePath() + File.separator + DOMAIN_CERT_FILE), + new File(certDir.getAbsolutePath() + File.separator + DOMAIN_PKCS8_KEY_FILE) + ).protocols(cmsSupportedProtocols).build(); + } catch (Exception e) { + fail("Failed to generate the SSL Context that CMS uses to validate Cert and PKCS8 private key. Reason: " + e.getMessage()); + } + } + +} diff --git a/src/main/java/com/nike/cerberus/ConfigConstants.java b/src/main/java/com/nike/cerberus/ConfigConstants.java index 1c26b761..7bf8741c 100644 --- a/src/main/java/com/nike/cerberus/ConfigConstants.java +++ b/src/main/java/com/nike/cerberus/ConfigConstants.java @@ -30,62 +30,26 @@ public class ConfigConstants { public static final String DEFAULT_CMS_DB_NAME = "cms"; - public static final String ENV_DATA_FILE = "config/environment.json"; - - public static final String CF_LOG_PROCESSOR_LAMBDA_CONFIG_FILE = "data/cloud-front-log-processor/lambda-config.json"; - - public static final String SECRETS_DATA_FILE = "config/secrets.json"; + public static final String ENVIRONMENT_DATA_FILE = "environment.json"; public static final String CERT_PART_CA = "ca.pem"; public static final String CERT_PART_CERT = "cert.pem"; + public static final String CERT_PART_PKCS8_KEY = "pkcs8-key.pem"; + public static final String CERT_PART_KEY = "key.pem"; public static final String CERT_PART_PUBKEY = "pubkey.pem"; - public static final String BASE_STACK_NAME = "base"; - - public static final String BASE_STACK_TEMPLATE_PATH = "/cloudformation/vpc-and-base.json"; - - public static final String CONSUL_DATACENTER = "cerberus"; - - public static final String CONSUL_SERVER_CONFIG_FILE = "data/consul/consul-server-config.json"; - - public static final String CONSUL_CLIENT_CONFIG_FILE = "data/consul/consul-client-config.json"; - - public static final String CONSUL_STACK_TEMPLATE_PATH = "/cloudformation/consul-cluster.json"; - - public static final String VAULT_ACL_ENTRY_FILE = "data/consul/vault-acl.json"; - - public static final String VAULT_CONFIG_FILE = "data/vault/vault-config.json"; - - public static final int VAULT_SECRET_SHARES = 5; - - public static final int VAULT_SECRET_THRESHOLD = 3; - - public static final String VAULT_STACK_TEMPLATE_PATH = "/cloudformation/vault-cluster.json"; - - public static final String CMS_STACK_TEMPLATE_PATH = "/cloudformation/cms-cluster.json"; + public static final String CERT_ACME_ACCOUNT_PRIVATE_KEY = "certificates/acme/account-private-key-pkcs1.pem"; - public static final String GATEWAY_STACK_TEMPLATE_PATH = "/cloudformation/gateway-cluster.json"; - - public static final String GATEWAY_SITE_CONFIG_FILE = "data/gateway/gateway.conf"; - - public static final String GATEWAY_GLOBAL_CONFIG_FILE = "data/gateway/nginx.conf"; - - public static final String CMS_ENV_CONFIG_PATH = "data/cms/environment.properties"; - - public static final String CF_ELB_IP_SYNC_STACK_TEMPLATE_PATH = "/cloudformation/cloudfront-elb-security-group-updater-lambda.json"; + public static final String CMS_ENV_CONFIG_PATH = "cms/environment.properties"; public static final String VERSION_PROPERTY = "cli.version"; public static final String CMS_ADMIN_GROUP_KEY = "cms.admin.group"; - public static final String VAULT_ADDR_KEY = "vault.addr"; - - public static final String VAULT_TOKEN_KEY = "vault.token"; - public static final String ROOT_USER_ARN_KEY = "root.user.arn"; public static final String ADMIN_ROLE_ARN_KEY = "admin.role.arn"; @@ -96,36 +60,40 @@ public class ConfigConstants { public static final String JDBC_USERNAME_KEY = "JDBC.username"; - public static final String JDBC_PASSWORD_KEY ="JDBC.password"; + public static final String JDBC_PASSWORD_KEY = "JDBC.password"; + + public static final String CMK_ARNS_KEY = "cms.encryption.cmk.arns"; + + public static final String HASH_SALT = "cms.auth.token.hash.salt"; + + public static final String CMS_ENV_NAME = "cms.env.name"; + + public static final String CMS_CERTIFICATE_TO_USE = "cms.ssl.certificateName"; public static final ImmutableSet SYSTEM_CONFIGURED_CMS_PROPERTIES = ImmutableSet.of( - VAULT_ADDR_KEY, - VAULT_TOKEN_KEY, ROOT_USER_ARN_KEY, ADMIN_ROLE_ARN_KEY, CMS_ROLE_ARN_KEY, JDBC_URL_KEY, JDBC_USERNAME_KEY, - JDBC_PASSWORD_KEY); + JDBC_PASSWORD_KEY, + CMK_ARNS_KEY, + HASH_SALT, + CMS_ENV_NAME, + CMS_CERTIFICATE_TO_USE); public static final String CERBERUS_AMI_TAG_NAME = "tag:cerberus_component"; public static final String CMS_AMI_TAG_VALUE = "cms"; - public static final String GATEWAY_AMI_TAG_VALUE = "gateway"; - - public static final String CONSUL_AMI_TAG_VALUE = "consul"; - - public static final String VAULT_AMI_TAG_VALUE = "vault"; - public static final String SKIP_AMI_TAG_CHECK_ARG = "--skip-ami-tag-check"; public static final String SKIP_AMI_TAG_CHECK_DESCRIPTION = "Skip validation of 'cerberus_component' tag on AMI"; public static final String AMI_TAG_CHECK_ERROR_MESSAGE - = "FAIL: AMI tag check failed!\n" - + "Given AMI ID either does not exist " - + "or does not contain cerberus tag 'cerberus_component' with stack name " - + "or cerberus tag does not match the stack that is being deployed.\n" - + "Please refer documentation on AMI Tagging or use '"+ SKIP_AMI_TAG_CHECK_ARG +"' option to skip this check."; + = "FAIL: AMI tag check failed!\n" + + "Given AMI ID either does not exist " + + "or does not contain cerberus tag 'cerberus_component' with stack name " + + "or cerberus tag does not match the stack that is being deployed.\n" + + "Please refer documentation on AMI Tagging or use '" + SKIP_AMI_TAG_CHECK_ARG + "' option to skip this check."; } \ No newline at end of file diff --git a/src/main/java/com/nike/cerberus/cli/ArgsBuilder.java b/src/main/java/com/nike/cerberus/cli/ArgsBuilder.java new file mode 100644 index 00000000..20cd350b --- /dev/null +++ b/src/main/java/com/nike/cerberus/cli/ArgsBuilder.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.cli; + +import java.util.LinkedList; +import java.util.List; + +public class ArgsBuilder { + + private List args; + + private ArgsBuilder() { + args = new LinkedList<>(); + } + + public static ArgsBuilder create() { + return new ArgsBuilder(); + } + + public ArgsBuilder addOption(String optionKey, String optionValue) { + args.add(optionKey); + args.add(optionValue); + return this; + } + + public ArgsBuilder addDynamicProperty(String dynamicOptionFlag, String key, String value) { + args.add(String.format("%s%s=%s", dynamicOptionFlag, key, value)); + return this; + } + + public ArgsBuilder addFlag(String flag) { + args.add(flag); + return this; + } + + public ArgsBuilder addAll(List argsToAdd) { + this.args.addAll(argsToAdd); + return this; + } + + /** + * Adds an argument option to the args list using the supplied value unless passed args contains the option and a value. + * @param optionKey The option to add to the args + * @param optionValue The option value to use if passed args doesn't already contain the option key and value + * @param passedArgs The passed args from the user + * + * @return The builder + */ + public ArgsBuilder addOptionUsingPassedArgIfPresent(String optionKey, String optionValue, String [] passedArgs) { + if (passedArgs == null || passedArgs.length == 0) { + return addOption(optionKey, optionValue); + } + int index = -1; + for (int i = 0; i < passedArgs.length; i++) { + if (optionKey.equals(passedArgs[i])) { + index = i; + break; + } + } + + args.add(optionKey); + if (index > -1 && index < passedArgs.length - 2) { + args.add(passedArgs[index + 1]); + } else { + args.add(optionValue); + } + + return this; + } + + public List build() { + return args; + } +} \ No newline at end of file diff --git a/src/main/java/com/nike/cerberus/cli/CerberusHelp.java b/src/main/java/com/nike/cerberus/cli/CerberusHelp.java index 2327f1fa..89413fb8 100644 --- a/src/main/java/com/nike/cerberus/cli/CerberusHelp.java +++ b/src/main/java/com/nike/cerberus/cli/CerberusHelp.java @@ -44,7 +44,7 @@ private void printCustomUsage() { int longestName = 0; List sorted = Lists.newArrayList(); for (ParameterDescription pd : commander.getParameters()) { - if (! pd.getParameter().hidden()) { + if (!pd.getParameter().hidden()) { sorted.add(pd); // + to have an extra space between the name and the description int length = pd.getNames().length() + 2; @@ -72,8 +72,8 @@ private void printCustomUsage() { : def.toString(); sb.append("\n" + s(indentCount)).append("Default: " + Chalk.on(displayedDef).yellow().bold().toString()); } - Class type = pd.getParameterized().getType(); - if(type.isEnum()){ + Class type = pd.getParameterized().getType(); + if (type.isEnum()) { String values = EnumSet.allOf((Class) type).toString(); sb.append("\n" + s(indentCount)).append("Possible Values: " + Chalk.on(values).yellow().bold().toString()); } diff --git a/src/main/java/com/nike/cerberus/cli/CerberusRunner.java b/src/main/java/com/nike/cerberus/cli/CerberusRunner.java index ca6513e7..9d182bf4 100644 --- a/src/main/java/com/nike/cerberus/cli/CerberusRunner.java +++ b/src/main/java/com/nike/cerberus/cli/CerberusRunner.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Nike, Inc. + * Copyright (c) 2017 Nike, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,44 +27,43 @@ import com.nike.cerberus.ConfigConstants; import com.nike.cerberus.command.CerberusCommand; import com.nike.cerberus.command.Command; +import com.nike.cerberus.command.certificates.RotateAcmeAccountPrivateKeyCommand; import com.nike.cerberus.command.cms.CreateCmsClusterCommand; import com.nike.cerberus.command.cms.CreateCmsConfigCommand; import com.nike.cerberus.command.cms.UpdateCmsConfigCommand; -import com.nike.cerberus.command.core.CreateCerberusBackupCommand; -import com.nike.cerberus.command.core.RollingRebootWithHealthCheckCommand; -import com.nike.cerberus.command.core.ViewConfigCommand; -import com.nike.cerberus.command.consul.CreateConsulClusterCommand; -import com.nike.cerberus.command.consul.CreateConsulConfigCommand; -import com.nike.cerberus.command.consul.CreateVaultAclCommand; -import com.nike.cerberus.command.consul.UpdateConsulConfigCommand; -import com.nike.cerberus.command.core.CreateBaseCommand; +import com.nike.cerberus.command.composite.CreateEnvironmentCommand; +import com.nike.cerberus.command.composite.DeleteEnvironmentCommand; +import com.nike.cerberus.command.certificates.GenerateAndRotateCertificatesCommand; +import com.nike.cerberus.command.composite.PrintAllStackInformationCommand; +import com.nike.cerberus.command.certificates.RotateCertificatesCommand; +import com.nike.cerberus.command.core.InitializeEnvironmentCommand; +import com.nike.cerberus.command.rds.CleanUpRdsSnapshotsCommand; +import com.nike.cerberus.command.rds.CopyRdsSnapshotsCommand; +import com.nike.cerberus.command.rds.CreateDatabaseCommand; +import com.nike.cerberus.command.core.CreateEdgeDomainRecordCommand; +import com.nike.cerberus.command.core.CreateLoadBalancerCommand; +import com.nike.cerberus.command.core.CreateRoute53Command; +import com.nike.cerberus.command.core.CreateSecurityGroupsCommand; +import com.nike.cerberus.command.core.CreateVpcCommand; +import com.nike.cerberus.command.core.CreateWafCommand; +import com.nike.cerberus.command.certificates.DeleteOldestCertificatesCommand; +import com.nike.cerberus.command.core.DeleteStackCommand; +import com.nike.cerberus.command.core.GenerateCertificateFilesCommand; import com.nike.cerberus.command.core.PrintStackInfoCommand; import com.nike.cerberus.command.core.RestoreCerberusBackupCommand; +import com.nike.cerberus.command.core.RebootCmsCommand; import com.nike.cerberus.command.core.UpdateStackCommand; -import com.nike.cerberus.command.core.UploadCertFilesCommand; -import com.nike.cerberus.command.core.SetBackupAdminPrincipalsCommand; +import com.nike.cerberus.command.certificates.UploadCertificateFilesCommand; +import com.nike.cerberus.command.core.ViewConfigCommand; import com.nike.cerberus.command.core.WhitelistCidrForVpcAccessCommand; -import com.nike.cerberus.command.dashboard.PublishDashboardCommand; -import com.nike.cerberus.command.gateway.CreateCloudFrontLogProcessingLambdaConfigCommand; -import com.nike.cerberus.command.gateway.CreateCloudFrontSecurityGroupUpdaterLambdaCommand; -import com.nike.cerberus.command.gateway.CreateGatewayClusterCommand; -import com.nike.cerberus.command.gateway.CreateGatewayConfigCommand; -import com.nike.cerberus.command.gateway.PublishLambdaCommand; -import com.nike.cerberus.command.vault.CreateCmsVaultTokenCommand; -import com.nike.cerberus.command.vault.CreateVaultClusterCommand; -import com.nike.cerberus.command.vault.CreateVaultConfigCommand; -import com.nike.cerberus.command.vault.DisableAuditBackendCommand; -import com.nike.cerberus.command.vault.EnableAuditBackendCommand; -import com.nike.cerberus.command.vault.InitVaultClusterCommand; -import com.nike.cerberus.command.vault.LoadDefaultVaultPoliciesCommand; -import com.nike.cerberus.command.vault.UnsealVaultClusterCommand; -import com.nike.cerberus.command.vault.VaultHealthCheckCommand; import com.nike.cerberus.domain.input.EnvironmentConfig; import com.nike.cerberus.logging.LoggingConfigurer; import com.nike.cerberus.module.CerberusModule; import com.nike.cerberus.module.PropsModule; import com.nike.cerberus.operation.Operation; import com.nike.cerberus.util.LocalEnvironmentValidator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.Map; @@ -102,16 +101,18 @@ public void run(String[] args) { commander.parse(args); configureLogging(cerberusCommand.isDebug()); + + final Logger log = LoggerFactory.getLogger(getClass()); + String commandName = commander.getParsedCommand(); Command command = commandMap.get(commandName); - if(cerberusCommand.isVersion()) { + if (cerberusCommand.isVersion()) { printCliVersion(); } else if (cerberusCommand.isHelp() || commandName == null) { cerberusHelp.print(); } else { - Injector injector = Guice.createInjector(new CerberusModule(cerberusCommand.getProxyDelegate(), - cerberusCommand.getEnvironment(), cerberusCommand.getRegion()), new PropsModule()); + Injector injector = Guice.createInjector(new CerberusModule(cerberusCommand), new PropsModule()); // fail early if there is any problem in local environment LocalEnvironmentValidator validator = injector.getInstance(LocalEnvironmentValidator.class); @@ -120,7 +121,9 @@ public void run(String[] args) { Operation operation = injector.getInstance(command.getOperationClass()); if (operation.isRunnable(command)) { + log.info("Running command: {}", commandName); operation.run(command); + log.info("Finished command: {}", commandName); } else { throw new RuntimeException("Command not runnable"); } @@ -128,19 +131,21 @@ public void run(String[] args) { } catch (Throwable e) { if (cerberusCommand.isHelp()) { cerberusHelp.print(); - } - else { + } else { System.err.println(Chalk.on("ERROR: " + e.getMessage()).red().bold().toString()); e.printStackTrace(); cerberusHelp.print(); System.exit(1); } } + + // Shutdown the thread pool executors + System.exit(0); } /** * If --file, -f was passed in we will map the dsl params to args. - * + *

* Due to the way jCommander works and validates args, we will create a new local command and parse the args * without validation and get the env config and return the new args back to the main commander to parse. * @@ -174,38 +179,35 @@ private void printCliVersion() { * Convenience method for registering all top level commands. */ private void registerAllCommands() { - registerCommand(new CreateBaseCommand()); - registerCommand(new UploadCertFilesCommand()); - registerCommand(new CreateConsulConfigCommand()); - registerCommand(new UpdateConsulConfigCommand()); - registerCommand(new CreateVaultAclCommand()); - registerCommand(new CreateConsulClusterCommand()); - registerCommand(new CreateVaultConfigCommand()); - registerCommand(new CreateVaultClusterCommand()); - registerCommand(new PublishDashboardCommand()); - registerCommand(new InitVaultClusterCommand()); - registerCommand(new UnsealVaultClusterCommand()); - registerCommand(new EnableAuditBackendCommand()); - registerCommand(new DisableAuditBackendCommand()); - registerCommand(new LoadDefaultVaultPoliciesCommand()); - registerCommand(new CreateCmsVaultTokenCommand()); + registerCommand(new InitializeEnvironmentCommand()); + registerCommand(new UploadCertificateFilesCommand()); registerCommand(new CreateCmsConfigCommand()); registerCommand(new CreateCmsClusterCommand()); - registerCommand(new CreateGatewayConfigCommand()); - registerCommand(new CreateGatewayClusterCommand()); registerCommand(new UpdateStackCommand()); registerCommand(new PrintStackInfoCommand()); - registerCommand(new VaultHealthCheckCommand()); - registerCommand(new PublishLambdaCommand()); - registerCommand(new CreateCloudFrontLogProcessingLambdaConfigCommand()); - registerCommand(new CreateCloudFrontSecurityGroupUpdaterLambdaCommand()); + registerCommand(new PrintAllStackInformationCommand()); registerCommand(new WhitelistCidrForVpcAccessCommand()); registerCommand(new RestoreCerberusBackupCommand()); registerCommand(new ViewConfigCommand()); registerCommand(new UpdateCmsConfigCommand()); - registerCommand(new RollingRebootWithHealthCheckCommand()); - registerCommand(new CreateCerberusBackupCommand()); - registerCommand(new SetBackupAdminPrincipalsCommand()); + registerCommand(new RebootCmsCommand()); + registerCommand(new GenerateCertificateFilesCommand()); + registerCommand(new CreateVpcCommand()); + registerCommand(new CreateWafCommand()); + registerCommand(new CreateDatabaseCommand()); + registerCommand(new CreateRoute53Command()); + registerCommand(new CreateSecurityGroupsCommand()); + registerCommand(new CreateLoadBalancerCommand()); + registerCommand(new CreateEdgeDomainRecordCommand()); + registerCommand(new CreateEnvironmentCommand()); + registerCommand(new DeleteStackCommand()); + registerCommand(new DeleteEnvironmentCommand()); + registerCommand(new RotateCertificatesCommand()); + registerCommand(new DeleteOldestCertificatesCommand()); + registerCommand(new CopyRdsSnapshotsCommand()); + registerCommand(new GenerateAndRotateCertificatesCommand()); + registerCommand(new RotateAcmeAccountPrivateKeyCommand()); + registerCommand(new CleanUpRdsSnapshotsCommand()); } /** @@ -234,6 +236,7 @@ private void configureLogging(boolean isDebug) { /** * Program entry point. Instantiates and calls run. + * * @param args Command line arguments */ public static void main(String[] args) { diff --git a/src/main/java/com/nike/cerberus/cli/EnvironmentConfigToArgsMapper.java b/src/main/java/com/nike/cerberus/cli/EnvironmentConfigToArgsMapper.java index 125a3d7a..bc88c8bf 100644 --- a/src/main/java/com/nike/cerberus/cli/EnvironmentConfigToArgsMapper.java +++ b/src/main/java/com/nike/cerberus/cli/EnvironmentConfigToArgsMapper.java @@ -16,33 +16,35 @@ package com.nike.cerberus.cli; +import com.google.common.collect.Lists; import com.nike.cerberus.command.StackDelegate; import com.nike.cerberus.command.cms.CreateCmsClusterCommand; import com.nike.cerberus.command.cms.CreateCmsConfigCommand; import com.nike.cerberus.command.cms.UpdateCmsConfigCommand; -import com.nike.cerberus.command.consul.CreateConsulClusterCommand; -import com.nike.cerberus.command.core.CreateBaseCommand; -import com.nike.cerberus.command.core.UpdateStackCommand; -import com.nike.cerberus.command.core.UploadCertFilesCommand; +import com.nike.cerberus.command.certificates.GenerateAndRotateCertificatesCommand; +import com.nike.cerberus.command.certificates.RotateCertificatesCommand; +import com.nike.cerberus.command.core.InitializeEnvironmentCommand; +import com.nike.cerberus.command.rds.CreateDatabaseCommand; +import com.nike.cerberus.command.core.CreateEdgeDomainRecordCommand; +import com.nike.cerberus.command.core.CreateLoadBalancerCommand; +import com.nike.cerberus.command.core.CreateRoute53Command; +import com.nike.cerberus.command.core.CreateSecurityGroupsCommand; +import com.nike.cerberus.command.core.CreateVpcCommand; +import com.nike.cerberus.command.core.CreateWafCommand; +import com.nike.cerberus.command.core.GenerateCertificateFilesCommand; +import com.nike.cerberus.command.core.GenerateCertificateFilesCommandParametersDelegate; +import com.nike.cerberus.command.certificates.UploadCertificateFilesCommand; +import com.nike.cerberus.command.certificates.UploadCertificateFilesCommandParametersDelegate; import com.nike.cerberus.command.core.WhitelistCidrForVpcAccessCommand; -import com.nike.cerberus.command.dashboard.PublishDashboardCommand; -import com.nike.cerberus.command.gateway.CreateCloudFrontLogProcessingLambdaConfigCommand; -import com.nike.cerberus.command.gateway.CreateGatewayClusterCommand; -import com.nike.cerberus.command.gateway.CreateGatewayConfigCommand; -import com.nike.cerberus.command.gateway.PublishLambdaCommand; -import com.nike.cerberus.command.vault.CreateVaultClusterCommand; -import com.nike.cerberus.domain.input.CerberusStack; -import com.nike.cerberus.domain.input.Consul; -import com.nike.cerberus.domain.input.Dashboard; -import com.nike.cerberus.domain.input.EdgeSecurity; +import com.nike.cerberus.domain.cloudformation.TagParametersDelegate; import com.nike.cerberus.domain.input.EnvironmentConfig; -import com.nike.cerberus.domain.input.Gateway; -import com.nike.cerberus.domain.input.ManagementService; -import com.nike.cerberus.domain.input.Vault; -import com.nike.cerberus.domain.input.VpcAccessWhitelist; +import com.nike.cerberus.domain.input.ManagementServiceInput; +import com.nike.cerberus.domain.input.ManagementServiceRegionSpecificInput; +import com.nike.cerberus.domain.input.RegionSpecificConfigurationInput; +import com.nike.cerberus.domain.input.VpcAccessWhitelistInput; import org.apache.commons.lang3.StringUtils; +import org.slf4j.LoggerFactory; -import java.util.Arrays; import java.util.LinkedList; import java.util.List; @@ -63,8 +65,8 @@ public static String[] getArgs(EnvironmentConfig environmentConfig, String[] pas for (int i = 0; i < passedArgs.length; i++) { if (StringUtils.startsWith(passedArgs[i], "-")) { args.add(passedArgs[i]); - if (i < passedArgs.length && ! StringUtils.startsWith(passedArgs[i+1], "-")) { - args.add(passedArgs[i+1]); + if (i < passedArgs.length && !StringUtils.startsWith(passedArgs[i + 1], "-")) { + args.add(passedArgs[i + 1]); i++; } } else { @@ -76,7 +78,7 @@ public static String[] getArgs(EnvironmentConfig environmentConfig, String[] pas // now if the command supplied is a command that that is reused for multiple steps, like update-stack, or upload-cert // we need to source the args - if (! StringUtils.isBlank(commandName)) { + if (!StringUtils.isBlank(commandName)) { args.addAll(getArgsForCommand(environmentConfig, commandName, passedArgs)); } @@ -84,332 +86,248 @@ public static String[] getArgs(EnvironmentConfig environmentConfig, String[] pas return args.toArray(new String[args.size()]); } - private static List getArgsForCommand(EnvironmentConfig environmentConfig, String commandName, String[] passedArgs) { + public static List getArgsForCommand(EnvironmentConfig environmentConfig, String commandName, String[] passedArgs) { + if (environmentConfig == null) { + return Lists.newArrayList(passedArgs); + } + + List args = new LinkedList<>(); switch (commandName) { - case CreateBaseCommand.COMMAND_NAME: - return getCreateBaseCommandArgs(environmentConfig); - case UploadCertFilesCommand.COMMAND_NAME: - return getUploadCertFilesCommandArgs(environmentConfig, passedArgs); - case CreateConsulClusterCommand.COMMAND_NAME: - return getCreateConsulClusterCommandArgs(environmentConfig); - case CreateVaultClusterCommand.COMMAND_NAME: - return getCreateVaultClusterCommandArgs(environmentConfig); + case InitializeEnvironmentCommand.COMMAND_NAME: + args = getInitializeEnvironmentCommandArgs(environmentConfig); + break; + case UploadCertificateFilesCommand.COMMAND_NAME: + args = getUploadCertFilesCommandArgs(environmentConfig, passedArgs); + break; case CreateCmsClusterCommand.COMMAND_NAME: - return getCreateCmsClusterCommandArgs(environmentConfig); - case CreateGatewayClusterCommand.COMMAND_NAME: - return getCreateGatewayClusterCommandArgs(environmentConfig); - case PublishDashboardCommand.COMMAND_NAME: - return getPublishDashboardCommandArgs(environmentConfig); + args = getCreateCmsClusterCommandArgs(environmentConfig); + break; case WhitelistCidrForVpcAccessCommand.COMMAND_NAME: - return getWhitelistCidrForVpcAccessCommandArgs(environmentConfig); + args = getWhitelistCidrForVpcAccessCommandArgs(environmentConfig); + break; case CreateCmsConfigCommand.COMMAND_NAME: - return getCreateCmsConfigCommandArgs(environmentConfig); - case CreateGatewayConfigCommand.COMMAND_NAME: - return getCreateGatewayClusterConfigCommandArgs(environmentConfig); - case UpdateStackCommand.COMMAND_NAME: - return getUpdateStackCommandArgs(environmentConfig, passedArgs); - case PublishLambdaCommand.COMMAND_NAME: - return getPublishLambdaCommandArgs(environmentConfig, passedArgs); - case CreateCloudFrontLogProcessingLambdaConfigCommand.COMMAND_NAME: - return getCreateCloudFrontLogProcessingLambdaConfigCommandArgs(environmentConfig); + args = getCreateCmsConfigCommandArgs(environmentConfig); + break; case UpdateCmsConfigCommand.COMMAND_NAME: - return getCreateCmsConfigCommandArgs(environmentConfig); + args = getCreateCmsConfigCommandArgs(environmentConfig); + break; + case CreateVpcCommand.COMMAND_NAME: + args = getCreateVpcCommandArgs(environmentConfig); + break; + case CreateSecurityGroupsCommand.COMMAND_NAME: + args = getCreateSecurityGroupsCommandArgs(environmentConfig); + break; + case CreateDatabaseCommand.COMMAND_NAME: + args = getCreateDatabaseCommandArgs(environmentConfig); + break; + case CreateLoadBalancerCommand.COMMAND_NAME: + args = getCreateLoadBalancerCommandArgs(environmentConfig); + break; + case CreateRoute53Command.COMMAND_NAME: + args = getCreateRoute53CommandArgs(environmentConfig); + break; + case CreateWafCommand.COMMAND_NAME: + args = getCreateWafCommandArgs(environmentConfig); + break; + case GenerateCertificateFilesCommand.COMMAND_NAME: + args = getGenerateCertificatesCommandArgs(environmentConfig); + break; + case CreateEdgeDomainRecordCommand.COMMAND_NAME: + args = getCreateEdgeDomainRecordCommandArgs(environmentConfig); + break; + case GenerateAndRotateCertificatesCommand.COMMAND_NAME: + args = getGenerateCertificatesCommandArgs(environmentConfig); + break; + case RotateCertificatesCommand.COMMAND_NAME: + args = getUploadCertFilesCommandArgs(environmentConfig, passedArgs); + break; default: - return new LinkedList<>(); - } - } - - private static List getCreateCloudFrontLogProcessingLambdaConfigCommandArgs(EnvironmentConfig environmentConfig) { - List args = new LinkedList<>(); - - EdgeSecurity edgeSecurity = environmentConfig.getEdgeSecurity(); - - args.add(CreateCloudFrontLogProcessingLambdaConfigCommand.RATE_LIMIT_PER_MINUTE_LONG_ARG); - args.add(edgeSecurity.getRateLimitPerMinute()); - args.add(CreateCloudFrontLogProcessingLambdaConfigCommand.RATE_LIMIT_VIOLATION_BLOCK_PERIOD_IN_MINUTES_LONG_ARG); - args.add(edgeSecurity.getRateLimitViolationBlockPeriodInMinutes()); - - if (StringUtils.isNotBlank(edgeSecurity.getGoogleAnalyticsTrackingId())) { - args.add(CreateCloudFrontLogProcessingLambdaConfigCommand.GOOGLE_ANALYTICS_TRACKING_ID_LONG_ARG); - args.add(edgeSecurity.getGoogleAnalyticsTrackingId()); - } - - if (StringUtils.isNotBlank(edgeSecurity.getSlackWebHookUrl())) { - args.add(CreateCloudFrontLogProcessingLambdaConfigCommand.SLACK_WEB_HOOK_URL_LONG_ARG); - args.add(edgeSecurity.getSlackWebHookUrl()); + break; } - if (StringUtils.isNotBlank(edgeSecurity.getSlackIcon())) { - args.add(CreateCloudFrontLogProcessingLambdaConfigCommand.SLACK_ICON_LONG_ARG); - args.add(edgeSecurity.getSlackIcon()); - } + LoggerFactory.getLogger("com.nike.cerberus.cli.EnvironmentConfigToArgsMapper") + .debug("Mapped the following args from the provided YAML\n" + String.join("\n", args)); return args; } - private static List getPublishLambdaCommandArgs(EnvironmentConfig environmentConfig, String[] passedArgs) { - List args = new LinkedList<>(); - String lambdaName = null; - for (int i = 0; i < passedArgs.length; i++) { - if (StringUtils.equals(passedArgs[i], PublishLambdaCommand.LAMBDA_NAME_LONG_ARG) && i < passedArgs.length - 1) { - lambdaName = passedArgs[i+1]; - } - } - - if (StringUtils.isBlank(lambdaName)) { - return args; - } - - args.add(PublishLambdaCommand.LAMBDA_NAME_LONG_ARG); - args.add(lambdaName); - args.add(PublishLambdaCommand.ARTIFACT_URL_LONG_ARG); + private static List getCreateEdgeDomainRecordCommandArgs(EnvironmentConfig environmentConfig) { + ArgsBuilder args = ArgsBuilder.create() + .addOption(CreateEdgeDomainRecordCommand.BASE_DOMAIN_NAME_LONG_ARG, environmentConfig.getBaseDomainName()) + .addOption(CreateEdgeDomainRecordCommand.HOSTED_ZONE_ID_LONG_ARG, environmentConfig.getHostedZoneId()); - switch (lambdaName.toUpperCase()) { - case "CLOUD_FRONT_SG_GROUP_IP_SYNC": - args.add(environmentConfig.getEdgeSecurity().getCloudfrontSecurityGroupIpSyncLambdaArtifactUrl()); - break; - case "WAF": - args.add(environmentConfig.getEdgeSecurity().getCloudfrontLambdaArtifactUrl()); - break; - default: - args.add(""); + if (StringUtils.isNotBlank(environmentConfig.getEdgeDomainNameOverride())) { + args.addOption(CreateEdgeDomainRecordCommand.EDGE_DOMAIN_NAME_OVERRIDE, environmentConfig.getEdgeDomainNameOverride()); } - return args; + return args.build(); } private static List getCreateCmsConfigCommandArgs(EnvironmentConfig environmentConfig) { - List args = new LinkedList<>(); - - ManagementService managementService = environmentConfig.getManagementService(); - - args.add(CreateCmsConfigCommand.ADMIN_GROUP_LONG_ARG); - args.add(managementService.getAdminGroup()); - + ArgsBuilder args = ArgsBuilder.create(); + ManagementServiceInput managementService = environmentConfig.getManagementService(); + args.addOption(CreateCmsConfigCommand.ADMIN_GROUP_LONG_ARG, managementService.getAdminGroup()); managementService.getProperties().forEach(property -> { - args.add(CreateCmsConfigCommand.PROPERTY_SHORT_ARG); - args.add(property); + args.addOption(CreateCmsConfigCommand.PROPERTY_SHORT_ARG, property); }); - - return args; - } - - private static List getCreateGatewayClusterConfigCommandArgs(EnvironmentConfig environmentConfig) { - List args = new LinkedList<>(); - - args.add(CreateGatewayClusterCommand.HOSTNAME_LONG_ARG); - args.add(environmentConfig.getHostname()); - - return args; + return args.build(); } private static List getWhitelistCidrForVpcAccessCommandArgs(EnvironmentConfig environmentConfig) { - List args = new LinkedList<>(); + ArgsBuilder args = ArgsBuilder.create(); - VpcAccessWhitelist vpcAccessWhitelist = environmentConfig.getVpcAccessWhitelist(); + VpcAccessWhitelistInput vpcAccessWhitelist = environmentConfig.getVpcAccessWhitelist(); vpcAccessWhitelist.getCidrs().forEach(cidr -> { - args.add(WhitelistCidrForVpcAccessCommand.CIDR_LONG_ARG); - args.add(cidr); + args.addOption(WhitelistCidrForVpcAccessCommand.CIDR_LONG_ARG, cidr); }); vpcAccessWhitelist.getPorts().forEach(port -> { - args.add(WhitelistCidrForVpcAccessCommand.PORT_LONG_ARG); - args.add(port); + args.addOption(WhitelistCidrForVpcAccessCommand.PORT_LONG_ARG, port); }); - return args; + return args.build(); } - private static List getPublishDashboardCommandArgs(EnvironmentConfig environmentConfig) { - List args = new LinkedList<>(); - - Dashboard dashboard = environmentConfig.getDashboard(); - - args.add(PublishDashboardCommand.ARTIFACT_URL_LONG_ARG); - args.add(dashboard.getArtifactUrl()); - - if ( StringUtils.isNotBlank(dashboard.getOverrideArtifactUrl()) ) { - args.add(PublishDashboardCommand.OVERRIDE_ARTIFACT_URL_LONG_ARG); - args.add(dashboard.getOverrideArtifactUrl()); - } - - return args; + private static List getCreateCmsClusterCommandArgs(EnvironmentConfig config) { + RegionSpecificConfigurationInput primaryRegion = config.getPrimaryRegionConfig(); + ManagementServiceRegionSpecificInput cmsConfig = primaryRegion.getManagementService().orElseThrow(() -> + new RuntimeException("management service config not defined in primary region config")); + + return ArgsBuilder.create() + .addOption(StackDelegate.AMI_ID_LONG_ARG, cmsConfig.getAmiId()) + .addOption(StackDelegate.INSTANCE_SIZE_LONG_ARG, cmsConfig.getInstanceSize()) + .addOption(StackDelegate.KEY_PAIR_NAME_LONG_ARG, cmsConfig.getKeyPairName()) + .addAll(getGlobalTags(config)) + .build(); } - private static List getCreateConsulClusterCommandArgs(EnvironmentConfig environmentConfig) { - List args = new LinkedList<>(); - - Consul component = environmentConfig.getConsul(); - addCommonStackArgs(environmentConfig, args, component); + private static List getGlobalTags(EnvironmentConfig environmentConfig) { + ArgsBuilder args = ArgsBuilder.create(); + environmentConfig.getGlobalTags().forEach((key, value) -> + args.addDynamicProperty(TagParametersDelegate.TAG_SHORT_ARG, key, value) + ); + return args.build(); + } - return args; + private static List getUploadCertFilesCommandArgs(EnvironmentConfig environmentConfig, String[] passedArgs) { + return ArgsBuilder.create() + .addOptionUsingPassedArgIfPresent( + UploadCertificateFilesCommandParametersDelegate.CERT_PATH_LONG_ARG, + environmentConfig.getCertificateDirectory(), + passedArgs + ) + .build(); } - private static List getCreateVaultClusterCommandArgs(EnvironmentConfig environmentConfig) { - List args = new LinkedList<>(); + private static List getInitializeEnvironmentCommandArgs(EnvironmentConfig config) { + ArgsBuilder args = ArgsBuilder.create() + .addAll(getGlobalTags(config)) + .addOption(InitializeEnvironmentCommand.ADMIN_ROLE_ARN_LONG_ARG, config.getAdminRoleArn()) + .addOption(InitializeEnvironmentCommand.PRIMARY_REGION, config.getPrimaryRegion()); - Vault component = environmentConfig.getVault(); - addCommonStackArgs(environmentConfig, args, component); + args.addFlag(InitializeEnvironmentCommand.REGION_LONG_ARG); + config.getRegionSpecificConfiguration().forEach((region, data) -> { + args.addFlag(region); + }); - return args; + return args.build(); } - private static List getCreateCmsClusterCommandArgs(EnvironmentConfig environmentConfig) { - List args = new LinkedList<>(); - - ManagementService component = environmentConfig.getManagementService(); - addCommonStackArgs(environmentConfig, args, component); - - return args; + private static List getCreateVpcCommandArgs(EnvironmentConfig config) { + return getGlobalTags(config); } - private static List getCreateGatewayClusterCommandArgs(EnvironmentConfig environmentConfig) { - List args = new LinkedList<>(); + private static List getCreateSecurityGroupsCommandArgs(EnvironmentConfig config) { + return getGlobalTags(config); + } - Gateway component = environmentConfig.getGateway(); - addCommonStackArgs(environmentConfig, args, component); - args.add(CreateGatewayClusterCommand.HOSTNAME_LONG_ARG); - args.add(environmentConfig.getHostname()); - args.add(CreateGatewayClusterCommand.HOSTED_ZONE_ID_LONG_ARG); - args.add(environmentConfig.getHostedZoneId()); + private static List getCreateDatabaseCommandArgs(EnvironmentConfig config) { + ArgsBuilder args = ArgsBuilder.create(); + if (config.getPrimaryRegionConfig().getRds().isPresent()) { + args.addOption(CreateDatabaseCommand.INSTANCE_CLASS_LONG_ARG, + config.getPrimaryRegionConfig().getRds().get().getSize()); - return args; + args.addOption(CreateDatabaseCommand.RESTORE_FROM_SNAPSHOT, + config.getPrimaryRegionConfig().getRds().get().getDbClusterIdentifier()); + } + args.addAll(getGlobalTags(config)); + return args.build(); } - private static void addCommonStackArgs(EnvironmentConfig environmentConfig, List args, CerberusStack stack) { - args.add(StackDelegate.AMI_ID_LONG_ARG); - args.add(stack.getAmiId()); - args.add(StackDelegate.INSTANCE_SIZE_LONG_ARG); - args.add(stack.getInstanceSize()); - args.add(StackDelegate.KEY_PAIR_NAME_LONG_ARG); - args.add(stack.getKeyPairName()); - - if (stack.getDesiredInstances() != null) { - args.add(StackDelegate.DESIRED_INSTANCES_LONG_ARG); - args.add(stack.getDesiredInstances()); - } - if (stack.getMinInstances() != null) { - args.add(StackDelegate.MIN_INSTANCES_LONG_ARG); - args.add(stack.getMinInstances()); - } - if (stack.getMaxInstances() != null) { - args.add(StackDelegate.MAX_INSTANCES_LONG_ARG); - args.add(stack.getMaxInstances()); + private static List getCreateLoadBalancerCommandArgs(EnvironmentConfig config) { + ArgsBuilder args = ArgsBuilder.create(); + if (StringUtils.isNotBlank(config.getLoadBalancerSslPolicyOverride())) { + args.addOption(CreateLoadBalancerCommand.LOAD_BALANCER_SSL_POLICY_OVERRIDE_LONG_ARG, + config.getLoadBalancerSslPolicyOverride()); } - args.add(StackDelegate.COST_CENTER_LONG_ARG); - args.add(environmentConfig.getCostCenter()); - args.add(StackDelegate.OWNER_EMAIL_LONG_ARG); - args.add(environmentConfig.getOwnerEmail()); - args.add(StackDelegate.OWNER_GROUP_LONG_ARG); - args.add(environmentConfig.getOwnerGroup()); + args.addAll(getGlobalTags(config)); + return args.build(); } - private static List getUploadCertFilesCommandArgs(EnvironmentConfig environmentConfig, String[] passedArgs) { - String stackName = getStackName(passedArgs); - List args = new LinkedList<>(); - - if (stackName == null) { - return args; - } + private static List getCreateRoute53CommandArgs(EnvironmentConfig config) { + ArgsBuilder args = ArgsBuilder.create() + .addOption(CreateRoute53Command.BASE_DOMAIN_NAME_LONG_ARG, config.getBaseDomainName()) + .addOption(CreateRoute53Command.HOSTED_ZONE_ID_LONG_ARG, config.getHostedZoneId()); - args.add(UploadCertFilesCommand.STACK_NAME_LONG_ARG); - args.add(stackName); - args.add(UploadCertFilesCommand.CERT_PATH_LONG_ARG); - switch (stackName) { - case "consul": - args.add(environmentConfig.getConsul().getCertPath()); - break; - case "vault": - args.add(environmentConfig.getVault().getCertPath()); - break; - case "cms": - args.add(environmentConfig.getManagementService().getCertPath()); - break; - case "gateway": - args.add(environmentConfig.getGateway().getCertPath()); - break; - default: - args.add(""); + if (StringUtils.isNotBlank(config.getOriginDomainNameOverride())) { + args.addOption(CreateRoute53Command.ORIGIN_DOMAIN_NAME_OVERRIDE, config.getOriginDomainNameOverride()); } - Arrays.stream(passedArgs).forEach(arg -> { - if (arg.equals("--overwrite")) { - args.add(UploadCertFilesCommand.OVERWRITE_LONG_ARG); - } - }); + if (config.getPrimaryRegionConfig().getLoadBalancerDomainNameOverride().isPresent()) { + args.addOption(CreateRoute53Command.LOAD_BALANCER_DOMAIN_NAME_OVERRIDE, + config.getPrimaryRegionConfig().getLoadBalancerDomainNameOverride().orElse(null)); + } - return args; + return args.build(); } - private static List getCreateBaseCommandArgs(EnvironmentConfig config) { - List args = new LinkedList<>(); - - args.add(CreateBaseCommand.OWNER_EMAIL_LONG_ARG); - args.add(config.getOwnerEmail()); - args.add(CreateBaseCommand.COST_CENTER_LONG_ARG); - args.add(config.getCostCenter()); - args.add(CreateBaseCommand.ADMIN_ROLE_ARN_LONG_ARG); - args.add(config.getAdminRoleArn()); - args.add(CreateBaseCommand.VPC_HOSTED_ZONE_NAME_LONG_ARG); - args.add(config.getVpcHostedZoneName()); - - return args; + private static List getCreateWafCommandArgs(EnvironmentConfig config) { + return ArgsBuilder.create() + .addAll(getGlobalTags(config)) + .build(); } - private static List getUpdateStackCommandArgs(EnvironmentConfig environmentConfig, String[] passedArgs) { - String stackName = getStackName(passedArgs); - List args = new LinkedList<>(); + private static List getGenerateCertificatesCommandArgs(EnvironmentConfig config) { + ArgsBuilder args = ArgsBuilder.create() + .addOption(GenerateCertificateFilesCommandParametersDelegate.BASE_DOMAIN_LONG_ARG, config.getBaseDomainName()) + .addOption(GenerateCertificateFilesCommandParametersDelegate.HOSTED_ZONE_ID_LONG_ARG, config.getHostedZoneId()) + .addOption(GenerateCertificateFilesCommandParametersDelegate.ACME_API_LONG_ARG, config.getAcmeApiUrl()) + .addOption(GenerateCertificateFilesCommandParametersDelegate.CONTACT_EMAIL_LONG_ARG, config.getAcmeContactEmail()) + .addOption(GenerateCertificateFilesCommandParametersDelegate.CERT_FOLDER_LONG_ARG, config.getCertificateDirectory()); - if (StringUtils.isBlank(stackName)) { - return args; + if (StringUtils.isNotBlank(config.getEdgeDomainNameOverride())) { + args.addOption(GenerateCertificateFilesCommandParametersDelegate.EDGE_DOMAIN_NAME_OVERRIDE_LONG_ARG, config.getEdgeDomainNameOverride()); } - args.add(STACK_NAME_KEY); - args.add(stackName); + if (StringUtils.isNotBlank(config.getOriginDomainNameOverride())) { + args.addOption(CreateRoute53Command.ORIGIN_DOMAIN_NAME_OVERRIDE, config.getOriginDomainNameOverride()); + } - CerberusStack cerberusStack; - switch (stackName) { - case "consul": - cerberusStack = environmentConfig.getConsul(); - break; - case "vault": - cerberusStack = environmentConfig.getVault(); - break; - case "cms": - cerberusStack = environmentConfig.getManagementService(); - break; - case "gateway": - cerberusStack = environmentConfig.getGateway(); - break; - default: - cerberusStack = null; + if (StringUtils.isNotBlank(config.getPrimaryRegionConfig().getLoadBalancerDomainNameOverride().orElse(null))) { + args.addOption(GenerateCertificateFilesCommandParametersDelegate.LOAD_BALANCER_DOMAIN_NAME_OVERRIDE_LONG_ARG, + config.getPrimaryRegionConfig().getLoadBalancerDomainNameOverride().orElse(null)); } - if (cerberusStack != null) { - addCommonStackArgs(environmentConfig, args, cerberusStack); + if (config.isEnableLeCertFix()) { + args.addFlag(GenerateCertificateFilesCommandParametersDelegate.ENABLE_LE_CERTFIX_LONG_ARG); } - for (int i = 0; i < passedArgs.length; i++) { - String arg = passedArgs[i]; - if (arg.equals(UpdateStackCommand.OVERWRITE_TEMPLATE_LONG_ARG)) { - args.add(UpdateStackCommand.OVERWRITE_TEMPLATE_LONG_ARG); - } - if (arg.equals(UpdateStackCommand.PARAMETER_SHORT_ARG) && i < passedArgs.length -1) { - args.add(UpdateStackCommand.PARAMETER_SHORT_ARG); - args.add(passedArgs[i+1]); - } + if (config.getAdditionalSubjectNames() != null) { + config.getAdditionalSubjectNames().forEach(sn -> { + args.addOption(GenerateCertificateFilesCommandParametersDelegate.SUBJECT_ALT_NAME_LONG_ARG, sn); + }); } - return args; + return args.build(); } private static String getStackName(String[] passedArgs) { for (int i = 0; i < passedArgs.length; i++) { if (StringUtils.equals(passedArgs[i], STACK_NAME_KEY)) { - return passedArgs[i+1]; + return passedArgs[i + 1]; } } return null; diff --git a/src/main/java/com/nike/cerberus/client/CerberusAdminClient.java b/src/main/java/com/nike/cerberus/client/CerberusAdminClient.java index f7e5e3d7..0ca2df95 100644 --- a/src/main/java/com/nike/cerberus/client/CerberusAdminClient.java +++ b/src/main/java/com/nike/cerberus/client/CerberusAdminClient.java @@ -17,77 +17,53 @@ package com.nike.cerberus.client; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.gson.FieldNamingPolicy; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonSyntaxException; -import com.nike.cerberus.domain.cms.SafeDepositBox; -import com.nike.cerberus.domain.cms.SdbMetadataResult; -import com.nike.vault.client.UrlResolver; -import com.nike.vault.client.VaultAdminClient; import com.nike.vault.client.VaultClientException; -import com.nike.vault.client.VaultServerException; import com.nike.vault.client.auth.VaultCredentialsProvider; import com.nike.vault.client.http.HttpHeader; import com.nike.vault.client.http.HttpMethod; -import com.nike.vault.client.http.HttpStatus; import okhttp3.HttpUrl; +import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; -import okhttp3.ResponseBody; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.net.ssl.SSLException; import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; /** * A Cerberus admin client with the ability to restore metadata */ -public class CerberusAdminClient extends VaultAdminClient { +public class CerberusAdminClient { private final Logger log = LoggerFactory.getLogger(getClass()); + public static final MediaType DEFAULT_MEDIA_TYPE = MediaType.parse("application/json; charset=utf-8"); + protected OkHttpClient httpClient; - protected VaultCredentialsProvider credentialsProvider; - protected UrlResolver vaultUrlResolver; - protected ObjectMapper objectMapper; - protected final Gson gson = new GsonBuilder() - .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) - .disableHtmlEscaping() - .create(); + protected VaultCredentialsProvider credentialsProvider; /** * Explicit constructor that allows for full control over construction of the Vault client. * - * @param vaultUrlResolver URL resolver for Vault * @param credentialsProvider Credential provider for acquiring a token for interacting with Vault * @param httpClient HTTP client for calling Vault */ - public CerberusAdminClient(UrlResolver vaultUrlResolver, - VaultCredentialsProvider credentialsProvider, + public CerberusAdminClient(VaultCredentialsProvider credentialsProvider, OkHttpClient httpClient, ObjectMapper objectMapper) { - - super(vaultUrlResolver, credentialsProvider, httpClient); this.httpClient = httpClient; this.credentialsProvider = credentialsProvider; - this.vaultUrlResolver = vaultUrlResolver; - this.objectMapper = objectMapper; } - public void restoreMetadata(String jsonPayload) { - HttpUrl url = buildUrl("v1/", "metadata"); + public void restoreMetadata(String baseUrl, String jsonPayload) { + HttpUrl url = buildUrl(baseUrl, "v1/", "restore-sdb"); Response response = execute(url, HttpMethod.PUT, jsonPayload); - if (! response.isSuccessful()) { + if (!response.isSuccessful()) { String body; try { body = response.body().string(); @@ -98,55 +74,6 @@ public void restoreMetadata(String jsonPayload) { } } - public SdbMetadataResult getSDBMetaData(int offset, int limit) { - URL baseUrl = null; - try { - baseUrl = new URL(vaultUrlResolver.resolve()); - } catch (MalformedURLException e) { - throw new RuntimeException("Failed to process cerberus base url", e); - } - - HttpUrl url = new HttpUrl.Builder() - .scheme(baseUrl.getProtocol()) - .host(baseUrl.getHost()) - .addPathSegments("v1/metadata") - .addQueryParameter("limit", String.valueOf(limit)) - .addQueryParameter("offset", String.valueOf(offset)) - .build(); - - Response response = execute(url, HttpMethod.GET, null); - if (! response.isSuccessful()) { - throw new RuntimeException(String.format("Failed to get metadata from Cerberus. Code: %s, Msg: %s", - response.code(), response.message())); - } - - return parseCmsResponseBody(response, SdbMetadataResult.class); - } - - public List getAllSdbMetadata() { - List sdbMetadataList = new LinkedList<>(); - SdbMetadataResult currentResult = null; - int offset = 0; - int limit = 100; - do { - currentResult = getSDBMetaData(offset, limit); - sdbMetadataList.addAll(currentResult.getSafeDepositBoxMetadata()); - offset = currentResult.getNextOffset(); - log.info("Retrieved metadata for {} SDBs", currentResult.getSdbCountInResult()); - } while (currentResult.hasNext()); - - return sdbMetadataList; - } - - public void writeJson(final String path, final Map data) { - final HttpUrl url = buildUrl(SECRET_PATH_PREFIX, path); - final Response response = execute(url, HttpMethod.POST, data); - - if (response.code() != HttpStatus.NO_CONTENT) { - parseAndThrowErrorResponse(response); - } - } - protected Response execute(final HttpUrl url, final String method, final String json) { try { Request.Builder requestBuilder = new Request.Builder() @@ -155,15 +82,13 @@ protected Response execute(final HttpUrl url, final String method, final String .addHeader(HttpHeader.ACCEPT, DEFAULT_MEDIA_TYPE.toString()); requestBuilder.addHeader(HttpHeader.CONTENT_TYPE, DEFAULT_MEDIA_TYPE.toString()) - .method(method, json != null ? RequestBody.create(DEFAULT_MEDIA_TYPE, json) : null ); + .method(method, json != null ? RequestBody.create(DEFAULT_MEDIA_TYPE, json) : null); return httpClient.newCall(requestBuilder.build()).execute(); } catch (IOException e) { if (e instanceof SSLException && e.getMessage() != null && e.getMessage().contains("Unrecognized SSL message, plaintext connection?")) { - // AnyConnect web security proxy can be disabled with: - // `sudo /opt/cisco/anyconnect/bin/acwebsecagent -disablesvc -websecurity` throw new VaultClientException("I/O error while communicating with vault. Unrecognized SSL message may be due to a web proxy e.g. AnyConnect", e); } else { throw new VaultClientException("I/O error while communicating with vault.", e); @@ -172,64 +97,18 @@ protected Response execute(final HttpUrl url, final String method, final String } /** - * Convenience method for parsing the HTTP response and mapping it to a class. + * Builds the full URL for preforming an operation against Vault. * - * @param response The HTTP response object - * @param responseClass The class to map the response body to - * @param Represents the type to map to - * @return Deserialized object from the response body + * @param baseUrl Base URL for Cerberus + * @param prefix Prefix between the environment URL and specified path + * @param path Path for the requested operation + * @return Full URL to execute a request against */ - protected M parseCmsResponseBody(final Response response, final Class responseClass) { - try(ResponseBody body = response.body()) { - return objectMapper.readValue(body.bytes(), responseClass); - } catch (IOException e) { - throw new VaultClientException("Error parsing the response body from CMS", e); - } - } - - /** - * Read operation for a specified path. Will return a {@link Map} of the data stored at the specified path. - * If Vault returns an unexpected response code, a {@link VaultServerException} will be thrown with the code - * and error details. If an unexpected I/O error is encountered, a {@link VaultClientException} will be thrown - * wrapping the underlying exception. - * - * @param path Path to the data - * @return Map of the data - */ - public GenericVaultResponse readDataGenerically(final String path) { - final HttpUrl url = buildUrl(SECRET_PATH_PREFIX, path); - log.debug("read: requestUrl={}", url); - - final Response response = execute(url, HttpMethod.GET, null); - - if (response.code() != HttpStatus.OK) { - parseAndThrowErrorResponse(response); + protected HttpUrl buildUrl(String baseUrl, String prefix, String path) { + if (!StringUtils.endsWith(baseUrl, "/")) { + baseUrl += "/"; } - return parseResponseBody(response, GenericVaultResponse.class); - } - - public class GenericVaultResponse { - private Map data; - - public Map getData() { - return data; - } - - public GenericVaultResponse setData(Map data) { - this.data = data; - return this; - } - } - - protected M parseResponseBody(final Response response, final Class responseClass) { - final String responseBodyStr = responseBodyAsString(response); - try { - return gson.fromJson(responseBodyStr, responseClass); - } catch (JsonSyntaxException e) { - log.error("parseResponseBody: responseCode={}, requestUrl={}", - response.code(), response.request().url()); - throw new VaultClientException("Error parsing the response body from vault, response code: " + response.code(), e); - } + return HttpUrl.parse(baseUrl + prefix + path); } } diff --git a/src/main/java/com/nike/cerberus/client/CerberusAdminClientFactory.java b/src/main/java/com/nike/cerberus/client/CerberusAdminClientFactory.java index 710e6b99..687474ab 100644 --- a/src/main/java/com/nike/cerberus/client/CerberusAdminClientFactory.java +++ b/src/main/java/com/nike/cerberus/client/CerberusAdminClientFactory.java @@ -18,83 +18,32 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.google.inject.Inject; -import com.nike.cerberus.module.CerberusModule; -import com.nike.cerberus.vault.VaultAdminClientFactory; import com.nike.vault.client.StaticVaultUrlResolver; -import com.nike.vault.client.VaultAdminClient; -import com.nike.vault.client.VaultClientException; -import com.nike.vault.client.model.VaultAuthResponse; -import com.nike.vault.client.model.VaultTokenAuthRequest; -import okhttp3.OkHttpClient; -import org.apache.http.conn.ssl.NoopHostnameVerifier; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Named; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.TimeUnit; +import com.nike.vault.client.auth.DefaultVaultCredentialsProviderChain; public class CerberusAdminClientFactory { - private final Logger logger = LoggerFactory.getLogger(getClass()); - - public static final int DEFAULT_TIMEOUT = 60; - public static final TimeUnit DEFAULT_TIMEOUT_UNIT = TimeUnit.SECONDS; - - private final VaultAdminClientFactory vaultAdminClientFactory; private final ObjectMapper objectMapper; + private final HttpClientFactory httpClientFactory; @Inject - public CerberusAdminClientFactory(VaultAdminClientFactory vaultAdminClientFactory, - @Named(CerberusModule.CONFIG_OBJECT_MAPPER) ObjectMapper objectMapper) { + public CerberusAdminClientFactory(ObjectMapper objectMapper, + HttpClientFactory httpClientFactory) { - this.vaultAdminClientFactory = vaultAdminClientFactory; this.objectMapper = objectMapper; + this.httpClientFactory = httpClientFactory; } - public CerberusAdminClient createCerberusAdminClient(String url) { + + /** + * Admin client for doing admin cms tasks + */ + public CerberusAdminClient createCerberusAdminClient() { return new CerberusAdminClient( - new StaticVaultUrlResolver(url), - new VaultAdminClientFactory.RootCredentialsProvider(generateAdminToken()), - new OkHttpClient.Builder() - .hostnameVerifier(new NoopHostnameVerifier()) - .connectTimeout(DEFAULT_TIMEOUT, DEFAULT_TIMEOUT_UNIT) - .writeTimeout(DEFAULT_TIMEOUT, DEFAULT_TIMEOUT_UNIT) - .readTimeout(DEFAULT_TIMEOUT, DEFAULT_TIMEOUT_UNIT) - .build(), + new DefaultVaultCredentialsProviderChain(), + httpClientFactory.getGenericClient(), objectMapper ); } - /** - * Generates and admin token that CMS will recognize as an Admin so we can us the restore endpoint - */ - private String generateAdminToken() { - try { - logger.info("Attempting to generate an admin token with the root token"); - VaultAdminClient adminClient = vaultAdminClientFactory.getClientForLeader().get(); - - Map metadata = new HashMap<>(); - metadata.put("is_admin", "true"); - metadata.put("username", "admin-cli"); - - Set policies = new HashSet<>(); - policies.add("root"); - - VaultAuthResponse vaultAuthResponse = adminClient.createOrphanToken(new VaultTokenAuthRequest() - .setDisplayName("admin-cli") - .setPolicies(policies) - .setMeta(metadata) - .setTtl("30m") - .setNoDefaultPolicy(true)); - - return vaultAuthResponse.getClientToken(); - } catch (VaultClientException e) { - throw new RuntimeException("There was an error while trying to create an admin token, this command " + - "requires proxy access or direct a connect to the vault leader, is your ip white listed?", e); - } - } } diff --git a/src/main/java/com/nike/cerberus/client/HttpClientFactory.java b/src/main/java/com/nike/cerberus/client/HttpClientFactory.java new file mode 100644 index 00000000..8e0d2559 --- /dev/null +++ b/src/main/java/com/nike/cerberus/client/HttpClientFactory.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.client; + +import com.google.inject.Inject; +import com.nike.cerberus.ConfigConstants; +import com.nike.cerberus.domain.environment.CertificateInformation; +import com.nike.cerberus.store.ConfigStore; +import okhttp3.OkHttpClient; +import okio.Buffer; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.net.ssl.*; +import java.io.InputStream; +import java.net.Proxy; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +public class HttpClientFactory { + + private final Logger log = LoggerFactory.getLogger(getClass()); + + public static final int DEFAULT_TIMEOUT = 15; + public static final TimeUnit DEFAULT_TIMEOUT_UNIT = TimeUnit.SECONDS; + + private final ConfigStore configStore; + private final Proxy proxy; + + @Inject + public HttpClientFactory(ConfigStore configStore, + Proxy proxy) { + + this.configStore = configStore; + this.proxy = proxy; + } + + + /** + * @return Generic default client with timeouts for making manual http calls + */ + public OkHttpClient getGenericClient() { + return new OkHttpClient.Builder() + .hostnameVerifier(new NoopHostnameVerifier()) + .proxy(proxy) + .connectTimeout(DEFAULT_TIMEOUT, DEFAULT_TIMEOUT_UNIT) + .writeTimeout(DEFAULT_TIMEOUT, DEFAULT_TIMEOUT_UNIT) + .readTimeout(DEFAULT_TIMEOUT, DEFAULT_TIMEOUT_UNIT) + .build(); + } + + /** + * Downloads the CA Chains from S3 for the Cerberus certs and creates a client that can talk to the + * individual CMS instances without going through the alb, or manually having to add the chain to the trust store. + */ + public OkHttpClient getGenericClientWithCustomTruststore() { + try { + List caChains = new LinkedList<>(); + for (CertificateInformation certInfo : configStore.getCertificationInformationList()) { + String certificateName = certInfo.getCertificateName(); + caChains.add(configStore.getCertPart(certificateName, ConfigConstants.CERT_PART_CA) + .orElseThrow(() -> new RuntimeException("Failed to download ca chain"))); + } + + Buffer buffer = new Buffer(); + caChains.forEach(buffer::writeUtf8); + + X509TrustManager trustManager = trustManagerForCertificates(buffer.inputStream()); + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, new TrustManager[] { trustManager }, null); + SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); + + return new OkHttpClient.Builder() + .hostnameVerifier(new NoopHostnameVerifier()) + .proxy(proxy) + .sslSocketFactory(sslSocketFactory, trustManager) + .connectTimeout(DEFAULT_TIMEOUT, DEFAULT_TIMEOUT_UNIT) + .writeTimeout(DEFAULT_TIMEOUT, DEFAULT_TIMEOUT_UNIT) + .readTimeout(DEFAULT_TIMEOUT, DEFAULT_TIMEOUT_UNIT) + .build(); + } catch (GeneralSecurityException e) { + throw new RuntimeException("Failed to create ok http client with custom trust manager from ca certs downloaded from S3"); + } + } + + /** + * https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/CustomTrust.java + * + * Returns a trust manager that trusts {@code certificates} and none other. HTTPS services whose + * certificates have not been signed by these certificates will fail with a {@code + * SSLHandshakeException}. + */ + private X509TrustManager trustManagerForCertificates(InputStream in) + throws GeneralSecurityException { + CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); + Collection certificates = certificateFactory.generateCertificates(in); + if (certificates.isEmpty()) { + throw new IllegalArgumentException("expected non-empty set of trusted certificates"); + } + + // Put the certificates a key store. + char[] password = "password".toCharArray(); // Any password will work. + KeyStore keyStore = newEmptyKeyStore(password); + int index = 0; + for (Certificate certificate : certificates) { + String certificateAlias = Integer.toString(index++); + keyStore.setCertificateEntry(certificateAlias, certificate); + } + + // Use it to build an X509 trust manager. + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance( + KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(keyStore, password); + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( + TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(keyStore); + TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); + if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) { + throw new IllegalStateException("Unexpected default trust managers:" + + Arrays.toString(trustManagers)); + } + return (X509TrustManager) trustManagers[0]; + } + + /** + * https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/CustomTrust.java + */ + private KeyStore newEmptyKeyStore(char[] password) throws GeneralSecurityException { + try { + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + InputStream in = null; // By convention, 'null' creates an empty key store. + keyStore.load(in, password); + return keyStore; + } catch (Exception e) { + throw new AssertionError(e); + } + } + +} diff --git a/src/main/java/com/nike/cerberus/command/CerberusCommand.java b/src/main/java/com/nike/cerberus/command/CerberusCommand.java index 51429c15..779c8fb5 100644 --- a/src/main/java/com/nike/cerberus/command/CerberusCommand.java +++ b/src/main/java/com/nike/cerberus/command/CerberusCommand.java @@ -16,6 +16,7 @@ package com.nike.cerberus.command; +import com.amazonaws.regions.Regions; import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; import com.beust.jcommander.ParametersDelegate; @@ -24,8 +25,9 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.nike.cerberus.command.validator.EnvironmentNameValidator; import com.nike.cerberus.domain.input.EnvironmentConfig; -import com.nike.cerberus.domain.input.ProxyConfig; import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; @@ -42,20 +44,35 @@ public class CerberusCommand { public static final String COMMAND_NAME = "cerberus"; + private final Logger log = LoggerFactory.getLogger(getClass()); + private EnvironmentConfig environmentConfig; @Parameter private List parameters = new ArrayList<>(); - @Parameter(names = {"--file", "-f"}, description = "The environment yaml file, allows users to define their environments in a yaml file rather than passing args for each command. This file is required when using composite commands. If file is supplied all args passed for individual commands are ignored and sourced from the yaml, except on commands that require special identifiers like update-stack and --stack-name, which would be used to point to what stack you want to update in the yaml") + @Parameter( + names = {"--file", "-f"}, + description = "The environment yaml file, allows users to define their environments in a yaml file " + + "rather than passing args for each command. This file is required when using composite commands. " + + "If file is supplied all args passed for individual commands are ignored and sourced from the yaml," + + " except on commands that require special identifiers like update-stack and --stack-name, which " + + "would be used to point to what stack you want to update in the yaml") private String file; - @Parameter(names = {"--environment", "--env", "-e"}, - description = "Cerberus environment name to execute against. This is required to be set via 'CERBERUS_CLI_ENV' env var or supplied via command arg", - validateWith = EnvironmentNameValidator.class) + @Parameter( + names = {"--environment", "--env", "-e"}, + description = "Cerberus environment name to execute against. This is required to be set via " + + "'CERBERUS_CLI_ENV' env var or supplied via command arg", + validateWith = EnvironmentNameValidator.class + ) private String environment; - @Parameter(names = {"--region", "-r"}, description = "The AWS region to execute against. This is required to be set via 'CERBERUS_CLI_REGION' env var or supplied via command arg") + @Parameter( + names = {"--region", "-r"}, + description = "The AWS Region to use for looking up the environment config in S3 to load environment state. " + + "This should be one of the enabled regions, if not supplied the CLI will try to default to us-west-2" + ) private String region; @Parameter(names = {"--debug"}, description = "Enables debug output.") @@ -67,6 +84,9 @@ public class CerberusCommand { @Parameter(names = {"--version", "-v"}, description = "Prints the version of the CLI.") private boolean version; + @Parameter(names = {"--no-tty"}, description = "Flag to set when no tty is availible, ex: running on a Continuous Integration (CI) server") + boolean noTty = false; + @ParametersDelegate private ProxyDelegate proxyDelegate = new ProxyDelegate(); @@ -84,7 +104,7 @@ public EnvironmentConfig getEnvironmentConfig() { } File environmentConfigFile = new File(file); - if (! environmentConfigFile.exists() || environmentConfigFile.isDirectory()) { + if (!environmentConfigFile.exists() || environmentConfigFile.isDirectory()) { throw new IllegalArgumentException(String.format("The file: %s does not exist or is a directory", file)); } @@ -105,7 +125,7 @@ public EnvironmentConfig getEnvironmentConfig() { * 2. If an env yaml was supplied use the env name defined there * 3. If 1 and 2 fail look for value in CERBERUS_CLI_ENV env var */ - public String getEnvironment() { + public String getEnvironmentName() { String commandLinePassedEnvironment = environment; String environmentConfigFileEnvironment = getEnvironmentConfig() == null ? null : getEnvironmentConfig().getEnvironmentName(); String EnvironmentalVarEnvironment = System.getenv("CERBERUS_CLI_ENV"); @@ -115,7 +135,8 @@ public String getEnvironment() { EnvironmentalVarEnvironment; if (StringUtils.isBlank(calculatedEnv)) { - throw new IllegalArgumentException("Failed to determine environment, checked 'CERBERUS_CLI_ENV' env var and -e, --env, --environment command options, options must go before the command"); + throw new RuntimeException("Failed to determine environment, checked 'CERBERUS_CLI_ENV' env var and -e, --env, --environment command options, options must go before the command"); + } return calculatedEnv; @@ -127,9 +148,9 @@ public String getEnvironment() { * 2. If an env yaml was supplied use the region defined there * 3. If 1 and 2 fail look for value in CERBERUS_CLI_REGION env var */ - public String getRegion() { + public String getConfigRegion() { String commandLinePassedRegion = region; - String environmentConfigFileRegion = getEnvironmentConfig() == null ? null : getEnvironmentConfig().getRegion(); + String environmentConfigFileRegion = getEnvironmentConfig() == null ? null : getEnvironmentConfig().getPrimaryRegion(); String EnvironmentalVarRegion = System.getenv("CERBERUS_CLI_REGION"); String calculatedRegion = StringUtils.isNotBlank(commandLinePassedRegion) ? commandLinePassedRegion : @@ -137,7 +158,8 @@ public String getRegion() { EnvironmentalVarRegion; if (StringUtils.isBlank(calculatedRegion)) { - throw new IllegalArgumentException("Failed to determine environment, checked 'CERBERUS_CLI_REGION' env var and -r, --region command options, options must go before the command"); + log.warn("Failed to determine region, checked 'CERBERUS_CLI_REGION' env var and -r, --region command options as well as primary region config. Will attempt to use the AWS Default region (us-west-2)"); + calculatedRegion = Regions.DEFAULT_REGION.getName(); } return calculatedRegion; @@ -155,24 +177,11 @@ public boolean isVersion() { return version; } - /** - * Load poxy config from args first then look to replace them with yaml. - */ - public ProxyDelegate getProxyDelegate() { - - if (getEnvironmentConfig() != null && getEnvironmentConfig().getProxyConfig() != null) { - ProxyConfig environmentYamlProxyConfig = getEnvironmentConfig().getProxyConfig(); - if (StringUtils.isBlank(proxyDelegate.getProxyHost()) && StringUtils.isNotBlank(environmentYamlProxyConfig.getHost())) { - proxyDelegate.setProxyHost(environmentYamlProxyConfig.getHost()); - } - if (proxyDelegate.getProxyPort() == null && environmentYamlProxyConfig.getPort() != null) { - proxyDelegate.setProxyPort(environmentYamlProxyConfig.getPort()); - } - if (proxyDelegate.getProxyType() == null && environmentYamlProxyConfig.getType() != null) { - proxyDelegate.setProxyType(environmentYamlProxyConfig.getType()); - } - } + public boolean isTty() { + return ! noTty; + } + public ProxyDelegate getProxyDelegate() { return proxyDelegate; } } diff --git a/src/main/java/com/nike/cerberus/command/StackDelegate.java b/src/main/java/com/nike/cerberus/command/StackDelegate.java index af98eab6..b3d5537e 100644 --- a/src/main/java/com/nike/cerberus/command/StackDelegate.java +++ b/src/main/java/com/nike/cerberus/command/StackDelegate.java @@ -16,7 +16,13 @@ package com.nike.cerberus.command; +import com.beust.jcommander.DynamicParameter; import com.beust.jcommander.Parameter; +import com.beust.jcommander.ParametersDelegate; +import com.nike.cerberus.domain.cloudformation.TagParametersDelegate; + +import java.util.HashMap; +import java.util.Map; /** * Represents CloudFormation stack parameters that are common to all Cerberus cluster components. @@ -26,12 +32,7 @@ public class StackDelegate { public static final String AMI_ID_LONG_ARG = "--ami-id"; public static final String INSTANCE_SIZE_LONG_ARG = "--instance-size"; public static final String KEY_PAIR_NAME_LONG_ARG = "--key-pair-name"; - public static final String OWNER_GROUP_LONG_ARG = "--owner-group"; - public static final String OWNER_EMAIL_LONG_ARG = "--owner-email"; - public static final String COST_CENTER_LONG_ARG = "--costcenter"; - public static final String DESIRED_INSTANCES_LONG_ARG = "--desired-instances"; - public static final String MAX_INSTANCES_LONG_ARG = "--max-instances"; - public static final String MIN_INSTANCES_LONG_ARG = "--min-instances"; + public static final String PARAMETER_SHORT_ARG = "-P"; @Parameter(names = AMI_ID_LONG_ARG, description = "The AMI ID for the specified stack.", required = true) private String amiId; @@ -42,29 +43,11 @@ public class StackDelegate { @Parameter(names = KEY_PAIR_NAME_LONG_ARG, required = true, description = "SSH key pair name.") private String keyPairName; - @Parameter(names = OWNER_GROUP_LONG_ARG, - description = "The owning group for the provision resources. Will be tagged on all resources.", - required = true) - private String ownerGroup; - - @Parameter(names = OWNER_EMAIL_LONG_ARG, - description = "The e-mail for who owns the provisioned resources. Will be tagged on all resources.", - required = true) - private String ownerEmail; - - @Parameter(names = COST_CENTER_LONG_ARG, - description = "Costcenter for where to bill provisioned resources. Will be tagged on all resources.", - required = true) - private String costcenter; - - @Parameter(names = DESIRED_INSTANCES_LONG_ARG, description = "Desired number of auto scaling instances.") - private int desiredInstances = 3; + @ParametersDelegate + private TagParametersDelegate tagParameters = new TagParametersDelegate(); - @Parameter(names = MAX_INSTANCES_LONG_ARG, description = "Maximum number of auto scaling instances (must be larger than min).") - private int maximumInstances = 4; - - @Parameter(names = MIN_INSTANCES_LONG_ARG, description = "Minimum number of auto scaling instances") - private int minimumInstances = 3; + @DynamicParameter(names = PARAMETER_SHORT_ARG, description = "Dynamic parameters for overriding the values for specific parameters in the CloudFormation.") + private Map dynamicParameters = new HashMap<>(); public String getAmiId() { return amiId; @@ -78,27 +61,11 @@ public String getKeyPairName() { return keyPairName; } - public String getOwnerGroup() { - return ownerGroup; - } - - public String getOwnerEmail() { - return ownerEmail; - } - - public String getCostcenter() { - return costcenter; - } - - public int getDesiredInstances() { - return desiredInstances; - } - - public int getMaximumInstances() { - return maximumInstances; + public TagParametersDelegate getTagParameters() { + return tagParameters; } - public int getMinimumInstances() { - return minimumInstances; + public Map getDynamicParameters() { + return dynamicParameters; } } diff --git a/src/main/java/com/nike/cerberus/command/certificates/DeleteOldestCertificatesCommand.java b/src/main/java/com/nike/cerberus/command/certificates/DeleteOldestCertificatesCommand.java new file mode 100644 index 00000000..5c6cd505 --- /dev/null +++ b/src/main/java/com/nike/cerberus/command/certificates/DeleteOldestCertificatesCommand.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.command.certificates; + +import com.beust.jcommander.Parameters; +import com.beust.jcommander.ParametersDelegate; +import com.nike.cerberus.command.Command; +import com.nike.cerberus.operation.Operation; +import com.nike.cerberus.operation.certificates.DeleteOldestCertificatesOperation; + +import static com.nike.cerberus.command.certificates.DeleteOldestCertificatesCommand.COMMAND_NAME; + +@Parameters( + commandNames = { + COMMAND_NAME + }, + commandDescription = "Deletes the oldest certificates from S3 and the identity management service" +) +public class DeleteOldestCertificatesCommand implements Command { + + public static final String COMMAND_NAME = "delete-oldest-certificates"; + + @ParametersDelegate + DeleteOldestCertificatesCommandParametersDelegate deleteParametersDelegate = + new DeleteOldestCertificatesCommandParametersDelegate(); + + public DeleteOldestCertificatesCommandParametersDelegate getDeleteParametersDelegate() { + return deleteParametersDelegate; + } + + @Override + public String getCommandName() { + return COMMAND_NAME; + } + + @Override + public Class> getOperationClass() { + return DeleteOldestCertificatesOperation.class; + } +} diff --git a/src/main/java/com/nike/cerberus/command/certificates/DeleteOldestCertificatesCommandParametersDelegate.java b/src/main/java/com/nike/cerberus/command/certificates/DeleteOldestCertificatesCommandParametersDelegate.java new file mode 100644 index 00000000..d9a0643b --- /dev/null +++ b/src/main/java/com/nike/cerberus/command/certificates/DeleteOldestCertificatesCommandParametersDelegate.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.command.certificates; + +import com.beust.jcommander.Parameter; +import com.nike.cerberus.command.validator.UploadCertFilesPathValidator; + +import java.nio.file.Path; + +public class DeleteOldestCertificatesCommandParametersDelegate { + + public static final String REVOKE_LONG_ARG = "--revoke-certificates"; + + public static final String ACME_API_LONG_ARG = "--acme-api-url"; + + @Parameter( + names = REVOKE_LONG_ARG, + description = "Provide this flag and an acme-url to revoke the certificates when deleting the old certs" + ) + private boolean revokeCertificates; + + public boolean isRevokeCertificates() { + return revokeCertificates; + } + + @Parameter( + names = ACME_API_LONG_ARG, + description = "The ACME API URL to use when attempting to revoke the certificates" + ) + private String acmeUrl; + + public String getAcmeUrl() { + return acmeUrl; + } +} \ No newline at end of file diff --git a/src/main/java/com/nike/cerberus/command/certificates/GenerateAndRotateCertificatesCommand.java b/src/main/java/com/nike/cerberus/command/certificates/GenerateAndRotateCertificatesCommand.java new file mode 100644 index 00000000..a7f0463f --- /dev/null +++ b/src/main/java/com/nike/cerberus/command/certificates/GenerateAndRotateCertificatesCommand.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.command.certificates; + +import com.beust.jcommander.Parameters; +import com.beust.jcommander.ParametersDelegate; +import com.nike.cerberus.command.Command; +import com.nike.cerberus.command.core.GenerateCertificateFilesCommandParametersDelegate; +import com.nike.cerberus.operation.Operation; +import com.nike.cerberus.operation.certificates.GenerateAndRotateCertificatesOperation; + +import static com.nike.cerberus.command.certificates.GenerateAndRotateCertificatesCommand.COMMAND_NAME; + +@Parameters( + commandNames = { + COMMAND_NAME + }, + commandDescription = "Generates certificates with an ACME certificate provider and performs the necessary " + + "steps to rotate the certificates used by the ALB and CMS" +) +public class GenerateAndRotateCertificatesCommand implements Command { + + public static final String COMMAND_NAME = "generate-and-rotate-certificates"; + + @ParametersDelegate + private GenerateCertificateFilesCommandParametersDelegate generateCertificateFilesCommandParametersDelegate = + new GenerateCertificateFilesCommandParametersDelegate(); + + public GenerateCertificateFilesCommandParametersDelegate getGenerateCertificateFilesCommandParametersDelegate() { + return generateCertificateFilesCommandParametersDelegate; + } + + @Override + public String getCommandName() { + return COMMAND_NAME; + } + + @Override + public Class> getOperationClass() { + return GenerateAndRotateCertificatesOperation.class; + } +} diff --git a/src/main/java/com/nike/cerberus/command/certificates/RotateAcmeAccountPrivateKeyCommand.java b/src/main/java/com/nike/cerberus/command/certificates/RotateAcmeAccountPrivateKeyCommand.java new file mode 100644 index 00000000..03978e3d --- /dev/null +++ b/src/main/java/com/nike/cerberus/command/certificates/RotateAcmeAccountPrivateKeyCommand.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.command.certificates; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; +import com.nike.cerberus.command.Command; +import com.nike.cerberus.operation.Operation; +import com.nike.cerberus.operation.certificates.RotateAcmeAccountPrivateKeyOperation; + +import static com.nike.cerberus.command.certificates.RotateAcmeAccountPrivateKeyCommand.COMMAND_NAME; + +@Parameters( + commandNames = { + COMMAND_NAME + }, + commandDescription = "Rotates the ACME Account private key." +) +public class RotateAcmeAccountPrivateKeyCommand implements Command { + + public static final String COMMAND_NAME = "rotate-acme-account-key"; + + public static final String ACME_URL_LONG_ARG = "--acme-api-url"; + public static final String ENABLE_LE_CERTFIX_LONG_ARG = "--enable-letsecrypt-certfix"; + + @Parameter( + names = ACME_URL_LONG_ARG, + description = "The ACME API URL to use.", + required = true + ) + private String acmeUrl; + + public String getAcmeUrl() { + return acmeUrl; + } + + @Parameter( + names = { + ENABLE_LE_CERTFIX_LONG_ARG + }, + description = "This command uses the acme4j client to communicate with the ACME server, " + + "it supports uses a hardcoded Let'sEncrypt cert to get around SSL errors, you can use this " + + "flag if your truststore is not configured to trust LE certificates, which can be found " + + " here: https://letsencrypt.org/certificates/" + ) + private boolean enableLetsEncryptCertfix = false; + + public boolean isEnableLetsEncryptCertfix() { + return enableLetsEncryptCertfix; + } + + @Override + public String getCommandName() { + return COMMAND_NAME; + } + + @Override + public Class> getOperationClass() { + return RotateAcmeAccountPrivateKeyOperation.class; + } +} diff --git a/src/main/java/com/nike/cerberus/command/certificates/RotateCertificatesCommand.java b/src/main/java/com/nike/cerberus/command/certificates/RotateCertificatesCommand.java new file mode 100644 index 00000000..69ceaf78 --- /dev/null +++ b/src/main/java/com/nike/cerberus/command/certificates/RotateCertificatesCommand.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.command.certificates; + +import com.beust.jcommander.Parameters; +import com.beust.jcommander.ParametersDelegate; +import com.nike.cerberus.command.Command; +import com.nike.cerberus.operation.Operation; +import com.nike.cerberus.operation.certificates.RotateCertificatesOperation; + +import static com.nike.cerberus.command.certificates.RotateCertificatesCommand.COMMAND_DESCRIPTION; +import static com.nike.cerberus.command.certificates.RotateCertificatesCommand.COMMAND_NAME; + +@Parameters( + commandNames = COMMAND_NAME, + commandDescription = COMMAND_DESCRIPTION +) +public class RotateCertificatesCommand implements Command { + + public static final String COMMAND_NAME = "rotate-certificates"; + public static final String COMMAND_DESCRIPTION = "Rotates the certificates used by the ALB and CMS."; + + @ParametersDelegate + private UploadCertificateFilesCommandParametersDelegate uploadParametersDelegate + = new UploadCertificateFilesCommandParametersDelegate(); + + public UploadCertificateFilesCommandParametersDelegate getUploadParametersDelegate() { + return uploadParametersDelegate; + } + + @ParametersDelegate + DeleteOldestCertificatesCommandParametersDelegate deleteParametersDelegate = + new DeleteOldestCertificatesCommandParametersDelegate(); + + public DeleteOldestCertificatesCommandParametersDelegate getDeleteParametersDelegate() { + return deleteParametersDelegate; + } + + @Override + public String getCommandName() { + return COMMAND_NAME; + } + + @Override + public Class> getOperationClass() { + return RotateCertificatesOperation.class; + } +} diff --git a/src/main/java/com/nike/cerberus/command/certificates/UploadCertificateFilesCommand.java b/src/main/java/com/nike/cerberus/command/certificates/UploadCertificateFilesCommand.java new file mode 100644 index 00000000..32eab223 --- /dev/null +++ b/src/main/java/com/nike/cerberus/command/certificates/UploadCertificateFilesCommand.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.command.certificates; + +import com.beust.jcommander.Parameters; +import com.beust.jcommander.ParametersDelegate; +import com.nike.cerberus.command.Command; +import com.nike.cerberus.operation.Operation; +import com.nike.cerberus.operation.certificates.UploadCertificatesFilesOperation; + +import static com.nike.cerberus.command.certificates.UploadCertificateFilesCommand.COMMAND_NAME; + +/** + * Command for uploading a certificate for a specific component. + */ +@Parameters(commandNames = COMMAND_NAME, commandDescription = "Upload certificate files for a specific component.") +public class UploadCertificateFilesCommand implements Command { + + public static final String COMMAND_NAME = "upload-certificate-files"; + + @ParametersDelegate + private UploadCertificateFilesCommandParametersDelegate uploadCertificatesPathParametersDelegate = + new UploadCertificateFilesCommandParametersDelegate(); + + public UploadCertificateFilesCommandParametersDelegate getUploadCertificatesPathParametersDelegate() { + return uploadCertificatesPathParametersDelegate; + } + + @Override + public String getCommandName() { + return COMMAND_NAME; + } + + @Override + public Class> getOperationClass() { + return UploadCertificatesFilesOperation.class; + } +} diff --git a/src/main/java/com/nike/cerberus/command/certificates/UploadCertificateFilesCommandParametersDelegate.java b/src/main/java/com/nike/cerberus/command/certificates/UploadCertificateFilesCommandParametersDelegate.java new file mode 100644 index 00000000..a7ae4c94 --- /dev/null +++ b/src/main/java/com/nike/cerberus/command/certificates/UploadCertificateFilesCommandParametersDelegate.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.command.certificates; + +import com.beust.jcommander.Parameter; +import com.nike.cerberus.command.validator.UploadCertFilesPathValidator; + +import java.nio.file.Path; + +public class UploadCertificateFilesCommandParametersDelegate { + + public static final String CERT_PATH_LONG_ARG = "--cert-dir-path"; + + @Parameter( + names = {CERT_PATH_LONG_ARG}, + required = true, + description = "Path to the directory that contains the certificate files.", + validateValueWith = UploadCertFilesPathValidator.class) + private Path certPath; + + public Path getCertPath() { + return certPath; + } +} \ No newline at end of file diff --git a/src/main/java/com/nike/cerberus/command/cms/CreateCmsClusterCommand.java b/src/main/java/com/nike/cerberus/command/cms/CreateCmsClusterCommand.java index df95951c..124253a0 100644 --- a/src/main/java/com/nike/cerberus/command/cms/CreateCmsClusterCommand.java +++ b/src/main/java/com/nike/cerberus/command/cms/CreateCmsClusterCommand.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Nike, Inc. + * Copyright (c) 2017 Nike, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,9 +24,9 @@ import com.nike.cerberus.operation.Operation; import com.nike.cerberus.operation.cms.CreateCmsClusterOperation; -import static com.nike.cerberus.command.cms.CreateCmsClusterCommand.COMMAND_NAME; import static com.nike.cerberus.ConfigConstants.SKIP_AMI_TAG_CHECK_ARG; import static com.nike.cerberus.ConfigConstants.SKIP_AMI_TAG_CHECK_DESCRIPTION; +import static com.nike.cerberus.command.cms.CreateCmsClusterCommand.COMMAND_NAME; /** * Command to create the CMS cluster. diff --git a/src/main/java/com/nike/cerberus/command/cms/CreateCmsConfigCommand.java b/src/main/java/com/nike/cerberus/command/cms/CreateCmsConfigCommand.java index 6788eb42..c00da752 100644 --- a/src/main/java/com/nike/cerberus/command/cms/CreateCmsConfigCommand.java +++ b/src/main/java/com/nike/cerberus/command/cms/CreateCmsConfigCommand.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Nike, Inc. + * Copyright (c) 2017 Nike, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ import java.util.HashMap; import java.util.Map; -import static com.nike.cerberus.command.cms.CreateCmsClusterCommand.COMMAND_NAME; +import static com.nike.cerberus.command.cms.CreateCmsConfigCommand.COMMAND_NAME; /** * Command to create the CMS cluster. diff --git a/src/main/java/com/nike/cerberus/command/cms/UpdateCmsConfigCommand.java b/src/main/java/com/nike/cerberus/command/cms/UpdateCmsConfigCommand.java index e4b3d46c..6b36e55b 100644 --- a/src/main/java/com/nike/cerberus/command/cms/UpdateCmsConfigCommand.java +++ b/src/main/java/com/nike/cerberus/command/cms/UpdateCmsConfigCommand.java @@ -36,6 +36,7 @@ public class UpdateCmsConfigCommand implements Command { public static final String COMMAND_NAME = "update-cms-config"; public static final String OVERWRITE_LONG_ARG = "--overwrite"; + public static final String FORCE_ARG = "--force"; @Parameter(names = CreateCmsConfigCommand.ADMIN_GROUP_LONG_ARG, description = "Group that has admin privileges in CMS.") private String adminGroup; @@ -46,6 +47,9 @@ public class UpdateCmsConfigCommand implements Command { @DynamicParameter(names = CreateCmsConfigCommand.PROPERTY_SHORT_ARG, description = "Dynamic parameters for setting additional properties in the CMS environment configuration.") private Map additionalProperties = new HashMap<>(); + @Parameter(names = FORCE_ARG, description = "Force allow overwriting of system generated property. This may break your configuration.") + private boolean force = false; + public String getAdminGroup() { return adminGroup; } @@ -58,6 +62,10 @@ public Map getAdditionalProperties() { return additionalProperties; } + public boolean isForce() { + return force; + } + @Override public String getCommandName() { return COMMAND_NAME; diff --git a/src/main/java/com/nike/cerberus/command/vault/InitVaultClusterCommand.java b/src/main/java/com/nike/cerberus/command/composite/CreateEnvironmentCommand.java similarity index 58% rename from src/main/java/com/nike/cerberus/command/vault/InitVaultClusterCommand.java rename to src/main/java/com/nike/cerberus/command/composite/CreateEnvironmentCommand.java index d1ca10eb..b5db3799 100644 --- a/src/main/java/com/nike/cerberus/command/vault/InitVaultClusterCommand.java +++ b/src/main/java/com/nike/cerberus/command/composite/CreateEnvironmentCommand.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Nike, Inc. + * Copyright (c) 2017 Nike, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,22 +14,24 @@ * limitations under the License. */ -package com.nike.cerberus.command.vault; +package com.nike.cerberus.command.composite; import com.beust.jcommander.Parameters; import com.nike.cerberus.command.Command; import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.operation.vault.InitVaultClusterOperation; +import com.nike.cerberus.operation.composite.CreateEnvironmentOperation; -import static com.nike.cerberus.command.vault.InitVaultClusterCommand.COMMAND_NAME; +import static com.nike.cerberus.command.composite.CreateEnvironmentCommand.COMMAND_NAME; -/** - * Command to initialize the Vault cluster. - */ -@Parameters(commandNames = COMMAND_NAME, commandDescription = "Initializes the Vault cluster.") -public class InitVaultClusterCommand implements Command { +@Parameters( + commandNames = { + COMMAND_NAME + }, + commandDescription = "Creates a complete Cerberus environment from scratch using an environment yaml" +) +public class CreateEnvironmentCommand implements Command { - public static final String COMMAND_NAME = "init-vault-cluster"; + public static final String COMMAND_NAME = "create-environment"; @Override public String getCommandName() { @@ -38,6 +40,6 @@ public String getCommandName() { @Override public Class> getOperationClass() { - return InitVaultClusterOperation.class; + return CreateEnvironmentOperation.class; } } diff --git a/src/main/java/com/nike/cerberus/command/composite/DeleteEnvironmentCommand.java b/src/main/java/com/nike/cerberus/command/composite/DeleteEnvironmentCommand.java new file mode 100644 index 00000000..2a8109ea --- /dev/null +++ b/src/main/java/com/nike/cerberus/command/composite/DeleteEnvironmentCommand.java @@ -0,0 +1,29 @@ +package com.nike.cerberus.command.composite; + +import com.beust.jcommander.Parameters; +import com.nike.cerberus.command.Command; +import com.nike.cerberus.operation.Operation; +import com.nike.cerberus.operation.composite.DeleteEnvironmentOperation; + +import static com.nike.cerberus.command.composite.DeleteEnvironmentCommand.COMMAND_NAME; + +@Parameters( + commandNames = { + COMMAND_NAME + }, + commandDescription = "Deletes all the cf stacks, and data associated with a Cerberus environment that is managed by the CLI" +) +public class DeleteEnvironmentCommand implements Command { + + public static final String COMMAND_NAME = "delete-environment"; + + @Override + public String getCommandName() { + return COMMAND_NAME; + } + + @Override + public Class> getOperationClass() { + return DeleteEnvironmentOperation.class; + } +} diff --git a/src/main/java/com/nike/cerberus/command/vault/LoadDefaultVaultPoliciesCommand.java b/src/main/java/com/nike/cerberus/command/composite/PrintAllStackInformationCommand.java similarity index 56% rename from src/main/java/com/nike/cerberus/command/vault/LoadDefaultVaultPoliciesCommand.java rename to src/main/java/com/nike/cerberus/command/composite/PrintAllStackInformationCommand.java index 906735b3..482472c2 100644 --- a/src/main/java/com/nike/cerberus/command/vault/LoadDefaultVaultPoliciesCommand.java +++ b/src/main/java/com/nike/cerberus/command/composite/PrintAllStackInformationCommand.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Nike, Inc. + * Copyright (c) 2017 Nike, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,22 +14,25 @@ * limitations under the License. */ -package com.nike.cerberus.command.vault; +package com.nike.cerberus.command.composite; import com.beust.jcommander.Parameters; import com.nike.cerberus.command.Command; import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.operation.vault.LoadDefaultVaultPoliciesOperation; +import com.nike.cerberus.operation.composite.PrintAllStackInformationOperation; -import static com.nike.cerberus.command.vault.LoadDefaultVaultPoliciesCommand.COMMAND_NAME; +import static com.nike.cerberus.command.composite.PrintAllStackInformationCommand.COMMAND_NAME; /** - * Command to initialize the Vault cluster. + * Prints information for all the stacks */ -@Parameters(commandNames = COMMAND_NAME, commandDescription = "Loads the default policies into Vault.") -public class LoadDefaultVaultPoliciesCommand implements Command { +@Parameters( + commandNames = COMMAND_NAME, + commandDescription = "Iterates through all known stacks for a cerberus environment and prints the stack info" +) +public class PrintAllStackInformationCommand implements Command { - public static final String COMMAND_NAME = "load-default-policies"; + public static final String COMMAND_NAME = "print-stack-info-for-all-stacks"; @Override public String getCommandName() { @@ -38,6 +41,6 @@ public String getCommandName() { @Override public Class> getOperationClass() { - return LoadDefaultVaultPoliciesOperation.class; + return PrintAllStackInformationOperation.class; } } diff --git a/src/main/java/com/nike/cerberus/command/consul/CreateConsulClusterCommand.java b/src/main/java/com/nike/cerberus/command/consul/CreateConsulClusterCommand.java deleted file mode 100644 index 5fd06339..00000000 --- a/src/main/java/com/nike/cerberus/command/consul/CreateConsulClusterCommand.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.command.consul; - -import com.beust.jcommander.Parameter; -import com.beust.jcommander.Parameters; -import com.beust.jcommander.ParametersDelegate; -import com.nike.cerberus.command.Command; -import com.nike.cerberus.command.StackDelegate; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.operation.consul.CreateConsulClusterOperation; - -import static com.nike.cerberus.command.consul.CreateConsulClusterCommand.COMMAND_NAME; -import static com.nike.cerberus.ConfigConstants.SKIP_AMI_TAG_CHECK_ARG; -import static com.nike.cerberus.ConfigConstants.SKIP_AMI_TAG_CHECK_DESCRIPTION; - -/** - * Command to create the consul cluster. - */ -@Parameters(commandNames = COMMAND_NAME, commandDescription = "Creates the consul cluster.") -public class CreateConsulClusterCommand implements Command { - - public static final String COMMAND_NAME = "create-consul-cluster"; - - @ParametersDelegate - private StackDelegate stackDelegate = new StackDelegate(); - - public StackDelegate getStackDelegate() { - return stackDelegate; - } - - @Override - public String getCommandName() { - return COMMAND_NAME; - } - - @Parameter(names = SKIP_AMI_TAG_CHECK_ARG, - description = SKIP_AMI_TAG_CHECK_DESCRIPTION) - private boolean skipAmiTagCheck; - - public boolean isSkipAmiTagCheck() { - return skipAmiTagCheck; - } - - @Override - public Class> getOperationClass() { - return CreateConsulClusterOperation.class; - } -} diff --git a/src/main/java/com/nike/cerberus/command/consul/CreateConsulConfigCommand.java b/src/main/java/com/nike/cerberus/command/consul/CreateConsulConfigCommand.java deleted file mode 100644 index 5f34540f..00000000 --- a/src/main/java/com/nike/cerberus/command/consul/CreateConsulConfigCommand.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.command.consul; - -import com.beust.jcommander.Parameters; -import com.nike.cerberus.command.Command; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.operation.consul.CreateConsulConfigOperation; - -import static com.nike.cerberus.command.consul.CreateConsulConfigCommand.COMMAND_NAME; - -/** - * Command to create the consul configuration. - */ -@Parameters(commandNames = COMMAND_NAME, commandDescription = "Creates the consul configuration for the cluster.") -public class CreateConsulConfigCommand implements Command { - - public static final String COMMAND_NAME = "create-consul-config"; - - @Override - public String getCommandName() { - return COMMAND_NAME; - } - - @Override - public Class> getOperationClass() { - return CreateConsulConfigOperation.class; - } -} - diff --git a/src/main/java/com/nike/cerberus/command/consul/CreateVaultAclCommand.java b/src/main/java/com/nike/cerberus/command/consul/CreateVaultAclCommand.java deleted file mode 100644 index c9272254..00000000 --- a/src/main/java/com/nike/cerberus/command/consul/CreateVaultAclCommand.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.command.consul; - -import com.beust.jcommander.Parameters; -import com.nike.cerberus.command.Command; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.operation.consul.CreateVaultAclOperation; - -import static com.nike.cerberus.command.consul.CreateVaultAclCommand.COMMAND_NAME; - -/** - * Command to create the Vault ACL with Consul. - */ -@Parameters(commandNames = COMMAND_NAME, commandDescription = "Creates the Vault ACL with Consul.") -public class CreateVaultAclCommand implements Command { - - public static final String COMMAND_NAME = "create-vault-acl"; - - @Override - public String getCommandName() { - return COMMAND_NAME; - } - - @Override - public Class> getOperationClass() { - return CreateVaultAclOperation.class; - } -} diff --git a/src/main/java/com/nike/cerberus/command/consul/UpdateConsulConfigCommand.java b/src/main/java/com/nike/cerberus/command/consul/UpdateConsulConfigCommand.java deleted file mode 100644 index f24324f5..00000000 --- a/src/main/java/com/nike/cerberus/command/consul/UpdateConsulConfigCommand.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.nike.cerberus.command.consul; - -import com.beust.jcommander.Parameter; -import com.beust.jcommander.Parameters; -import com.nike.cerberus.command.Command; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.operation.consul.UpdateConsulConfigOperation; - -import static com.nike.cerberus.command.consul.CreateConsulConfigCommand.COMMAND_NAME; - -@Parameters(commandNames = COMMAND_NAME, commandDescription = "Updates the consul configuration for the cluster while maintaining the secrets in the current config.") -public class UpdateConsulConfigCommand implements Command { - - public static final String COMMAND_NAME = "update-consul-config"; - - @Parameter(names = {"--acl-master-token"}, description = "Overwrites the existing ACL Master Token with the value supplied." - + " If not supplied, the existing token in secrets.json will be maintained (safer and generally preferred).") - private String aclMasterToken = null; - - @Parameter(names = {"--gossip-encryption-token"}, description = "Overwrites the existing Gossip Encryption Token with the value supplied." - + " If not supplied, the existing token in secrets.json will be maintained (safer and generally preferred).") - private String gossipEncryptionToken = null; - - public String getAclMasterToken() { - return aclMasterToken; - } - - public String getGossipEncryptionToken() { - return gossipEncryptionToken; - } - - @Override - public String getCommandName() { - return COMMAND_NAME; - } - - @Override - public Class> getOperationClass() { - return UpdateConsulConfigOperation.class; - } -} \ No newline at end of file diff --git a/src/main/java/com/nike/cerberus/command/core/CreateBaseCommand.java b/src/main/java/com/nike/cerberus/command/core/CreateBaseCommand.java deleted file mode 100644 index 8b69e2cb..00000000 --- a/src/main/java/com/nike/cerberus/command/core/CreateBaseCommand.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.command.core; - -import com.beust.jcommander.Parameter; -import com.beust.jcommander.Parameters; -import com.nike.cerberus.command.Command; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.operation.core.CreateBaseOperation; - -import static com.nike.cerberus.command.core.CreateBaseCommand.COMMAND_NAME; - -/** - * Command for creating the base components for Cerberus. - */ -@Parameters(commandNames = COMMAND_NAME, commandDescription = "Creates the base components to support Cerberus.") -public class CreateBaseCommand implements Command { - - public static final String COMMAND_NAME = "create-base"; - public static final String ADMIN_ROLE_ARN_LONG_ARG = "--admin-role-arn"; - public static final String VPC_HOSTED_ZONE_NAME_LONG_ARG = "--vpc-hosted-zone-name"; - public static final String OWNER_EMAIL_LONG_ARG = "--owner-email"; - public static final String COST_CENTER_LONG_ARG = "--costcenter"; - - @Parameter(names = ADMIN_ROLE_ARN_LONG_ARG, - description = "A IAM role ARN that will be given elevated privileges for the KMS CMK created.", - required = true) - private String adminRoleArn; - - @Parameter(names = VPC_HOSTED_ZONE_NAME_LONG_ARG, - description = "The Route 53 hosted zone name that will be created for CNAME records used by internal ELBs.", - required = true) - private String vpcHostedZoneName; - - @Parameter(names = OWNER_EMAIL_LONG_ARG, - description = "The e-mail for who owns the provisioned resources. Will be tagged on all resources.", - required = true) - private String ownerEmail; - - @Parameter(names = COST_CENTER_LONG_ARG, - description = "Costcenter for where to bill provisioned resources. Will be tagged on all resources.", - required = true) - private String costcenter; - - public String getAdminRoleArn() { - return adminRoleArn; - } - - public String getVpcHostedZoneName() { - return vpcHostedZoneName; - } - - public String getOwnerEmail() { - return ownerEmail; - } - - public String getCostcenter() { - return costcenter; - } - - @Override - public String getCommandName() { - return COMMAND_NAME; - } - - @Override - public Class> getOperationClass() { - return CreateBaseOperation.class; - } -} diff --git a/src/main/java/com/nike/cerberus/command/core/CreateCerberusBackupCommand.java b/src/main/java/com/nike/cerberus/command/core/CreateCerberusBackupCommand.java deleted file mode 100644 index a2b968fb..00000000 --- a/src/main/java/com/nike/cerberus/command/core/CreateCerberusBackupCommand.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2017 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.command.core; - -import com.beust.jcommander.Parameter; -import com.beust.jcommander.Parameters; -import com.nike.cerberus.command.Command; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.operation.core.CreateCerberusBackupOperation; - -import java.util.ArrayList; -import java.util.List; - -import static com.nike.cerberus.command.core.CreateCerberusBackupCommand.COMMAND_NAME; - - -/** - * Command for creating Safe Deposit Box Metadata and Vault secret data backups in S3 encrypted with KMS - */ -@Parameters( - commandNames = COMMAND_NAME, - commandDescription = "Allows Cerberus operators to create a complete backup in S3 encrypted with KMS. that can be restored with the restore command" -) -public class CreateCerberusBackupCommand implements Command { - - public static final String COMMAND_NAME = "create-backup"; - - public static final String BACKUP_REGIONS_LONG_ARG = "--backup-region"; - public static final String BACKUP_REGIONS_SHORT_ARG = "-br"; - - @Parameter( - names = { - BACKUP_REGIONS_LONG_ARG, - BACKUP_REGIONS_SHORT_ARG - }, - description = "One or more regions to store backup data in.", - required = true - ) - private List backupRegions = new ArrayList<>(); - - public List getBackupRegions() { - return backupRegions; - } - - @Override - public String getCommandName() { - return COMMAND_NAME; - } - - @Override - public Class> getOperationClass() { - return CreateCerberusBackupOperation.class; - } -} diff --git a/src/main/java/com/nike/cerberus/command/core/CreateEdgeDomainRecordCommand.java b/src/main/java/com/nike/cerberus/command/core/CreateEdgeDomainRecordCommand.java new file mode 100644 index 00000000..c36fc4b0 --- /dev/null +++ b/src/main/java/com/nike/cerberus/command/core/CreateEdgeDomainRecordCommand.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.command.core; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; +import com.nike.cerberus.command.Command; +import com.nike.cerberus.operation.Operation; +import com.nike.cerberus.operation.core.CreateEdgeDomainRecordOperation; + +import static com.nike.cerberus.command.core.CreateEdgeDomainRecordCommand.COMMAND_NAME; + +/** + * Command to create the edge domain Route53 record for Cerberus. + */ +@Parameters(commandNames = COMMAND_NAME, + commandDescription = "Create the Route53 record for use by Cerberus") +public class CreateEdgeDomainRecordCommand implements Command { + + public static final String COMMAND_NAME = "create-edge-domain-record"; + + public static final String BASE_DOMAIN_NAME_LONG_ARG = "--base-domain-name"; + + public static final String HOSTED_ZONE_ID_LONG_ARG = "--hosted-zone-id"; + + public static final String EDGE_DOMAIN_NAME_OVERRIDE = "--edge-domain-name-override"; + + @Parameter(names = BASE_DOMAIN_NAME_LONG_ARG, + description = "The base domain name for Cerberus (e.g. url: https://env.cerberus.example.com => base hostname: cerberus.example.com)", + required = true) + private String baseDomainName; + + @Parameter(names = EDGE_DOMAIN_NAME_OVERRIDE, + description = "Override the edge domain name for Cerberus. Default: env.example.domain.com") + private String edgeDomainNameOverride; + + @Parameter(names = HOSTED_ZONE_ID_LONG_ARG, + description = "The Route53 Hosted Zone in which to create the new Cerberus record", + required = true) + private String hostedZoneId; + + public String getBaseDomainName() { + return baseDomainName; + } + + public String getEdgeDomainNameOverride() { + return edgeDomainNameOverride; + } + + public String getHostedZoneId() { + return hostedZoneId; + } + + @Override + public String getCommandName() { + return COMMAND_NAME; + } + + @Override + public Class> getOperationClass() { + return CreateEdgeDomainRecordOperation.class; + } + +} diff --git a/src/main/java/com/nike/cerberus/command/core/CreateLoadBalancerCommand.java b/src/main/java/com/nike/cerberus/command/core/CreateLoadBalancerCommand.java new file mode 100644 index 00000000..30a5a8cd --- /dev/null +++ b/src/main/java/com/nike/cerberus/command/core/CreateLoadBalancerCommand.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.command.core; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; +import com.beust.jcommander.ParametersDelegate; +import com.nike.cerberus.command.Command; +import com.nike.cerberus.domain.cloudformation.TagParametersDelegate; +import com.nike.cerberus.operation.Operation; +import com.nike.cerberus.operation.core.CreateLoadBalancerOperation; + +import static com.nike.cerberus.command.core.CreateLoadBalancerCommand.COMMAND_NAME; + +/** + * Command to create the load balancer for Cerberus. + */ +@Parameters(commandNames = COMMAND_NAME, + commandDescription = "Create the load balancer used for Cerberus.") +public class CreateLoadBalancerCommand implements Command { + + public static final String COMMAND_NAME = "create-load-balancer"; + + public static final String LOAD_BALANCER_SSL_POLICY_OVERRIDE_LONG_ARG = "--ssl-policy-override"; + + @ParametersDelegate + private TagParametersDelegate tagsDelegate = new TagParametersDelegate(); + + public TagParametersDelegate getTagsDelegate() { + return tagsDelegate; + } + + @Parameter( + names = { + LOAD_BALANCER_SSL_POLICY_OVERRIDE_LONG_ARG + }, + description = "The SSL Policy that will get applied to the application load balancer, " + + "see http://docs.aws.amazon.com/elasticloadbalancing/latest/classic/elb-security-policy-table.html" + + " for more information." + ) + private String loadBalancerSslPolicyOverride; + + public String getLoadBalancerSslPolicyOverride() { + return loadBalancerSslPolicyOverride; + } + + @Override + public String getCommandName() { + return COMMAND_NAME; + } + + @Override + public Class> getOperationClass() { + return CreateLoadBalancerOperation.class; + } + +} diff --git a/src/main/java/com/nike/cerberus/command/core/CreateRoute53Command.java b/src/main/java/com/nike/cerberus/command/core/CreateRoute53Command.java new file mode 100644 index 00000000..6845e905 --- /dev/null +++ b/src/main/java/com/nike/cerberus/command/core/CreateRoute53Command.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.command.core; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; +import com.beust.jcommander.ParametersDelegate; +import com.nike.cerberus.command.Command; +import com.nike.cerberus.domain.cloudformation.TagParametersDelegate; +import com.nike.cerberus.operation.Operation; +import com.nike.cerberus.operation.core.CreateRoute53Operation; + +import static com.nike.cerberus.command.core.CreateRoute53Command.COMMAND_NAME; + +/** + * Creates the origin and load balancer Route53 records for Cerberus + */ +@Parameters(commandNames = COMMAND_NAME, + commandDescription = "Create the Route53 record for use by Cerberus") +public class CreateRoute53Command implements Command { + + public static final String COMMAND_NAME = "create-route53-stack"; + + public static final String BASE_DOMAIN_NAME_LONG_ARG = "--base-domain-name"; + + public static final String HOSTED_ZONE_ID_LONG_ARG = "--hosted-zone-id"; + + public static final String ORIGIN_DOMAIN_NAME_OVERRIDE = "--origin-domain-name-override"; + + public static final String LOAD_BALANCER_DOMAIN_NAME_OVERRIDE = "--load-balancer-domain-name-override"; + + @ParametersDelegate + private TagParametersDelegate tagsDelegate = new TagParametersDelegate(); + + public TagParametersDelegate getTagsDelegate() { + return tagsDelegate; + } + + @Parameter(names = BASE_DOMAIN_NAME_LONG_ARG, + description = "The base hostname for Cerberus (e.g. url: https://env.cerberus.example.com => base hostname: cerberus.example.com)", + required = true) + private String baseDomainName; + + @Parameter(names = HOSTED_ZONE_ID_LONG_ARG, + description = "The Route53 Hosted Zone in which to create the new Cerberus record", + required = true) + private String hostedZoneId; + + @Parameter(names = LOAD_BALANCER_DOMAIN_NAME_OVERRIDE, + description = "Override the load balancer domain name for Cerberus. Default: env.region.example.domain.com") + private String loadBalancerDomainNameOverride; + + @Parameter(names = ORIGIN_DOMAIN_NAME_OVERRIDE, + description = "Override the origin domain name for Cerberus. Default: origin.env.example.domain.com") + private String originDomainNameOverride; + + public String getBaseDomainName() { + return baseDomainName; + } + + public String getOriginDomainNameOverride() { + return originDomainNameOverride; + } + + public String getHostedZoneId() { + return hostedZoneId; + } + + public String getLoadBalancerDomainNameOverride() { + return loadBalancerDomainNameOverride; + } + + @Override + public String getCommandName() { + return COMMAND_NAME; + } + + @Override + public Class> getOperationClass() { + return CreateRoute53Operation.class; + } + +} diff --git a/src/main/java/com/nike/cerberus/command/core/CreateSecurityGroupsCommand.java b/src/main/java/com/nike/cerberus/command/core/CreateSecurityGroupsCommand.java new file mode 100644 index 00000000..10633136 --- /dev/null +++ b/src/main/java/com/nike/cerberus/command/core/CreateSecurityGroupsCommand.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.command.core; + +import com.beust.jcommander.Parameters; +import com.beust.jcommander.ParametersDelegate; +import com.nike.cerberus.command.Command; +import com.nike.cerberus.domain.cloudformation.TagParametersDelegate; +import com.nike.cerberus.operation.Operation; +import com.nike.cerberus.operation.core.CreateSecurityGroupsOperation; + +import static com.nike.cerberus.command.core.CreateSecurityGroupsCommand.COMMAND_NAME; + +/** + * Command to create the security groups for Cerberus. + */ +@Parameters(commandNames = COMMAND_NAME, + commandDescription = "Create the IAM roles, KMS keys, and S3 buckets for Cerberus") +public class CreateSecurityGroupsCommand implements Command { + + public static final String COMMAND_NAME = "create-security-groups"; + + @ParametersDelegate + private TagParametersDelegate tagParameters = new TagParametersDelegate(); + + public TagParametersDelegate getTagParameters() { + return tagParameters; + } + + @Override + public String getCommandName() { + return COMMAND_NAME; + } + + @Override + public Class> getOperationClass() { + return CreateSecurityGroupsOperation.class; + } + +} diff --git a/src/main/java/com/nike/cerberus/command/vault/DisableAuditBackendCommand.java b/src/main/java/com/nike/cerberus/command/core/CreateVpcCommand.java similarity index 50% rename from src/main/java/com/nike/cerberus/command/vault/DisableAuditBackendCommand.java rename to src/main/java/com/nike/cerberus/command/core/CreateVpcCommand.java index 70ebe27e..aac7e8a4 100644 --- a/src/main/java/com/nike/cerberus/command/vault/DisableAuditBackendCommand.java +++ b/src/main/java/com/nike/cerberus/command/core/CreateVpcCommand.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Nike, Inc. + * Copyright (c) 2017 Nike, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,30 +14,31 @@ * limitations under the License. */ -package com.nike.cerberus.command.vault; +package com.nike.cerberus.command.core; -import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; +import com.beust.jcommander.ParametersDelegate; import com.nike.cerberus.command.Command; -import com.nike.cerberus.domain.vault.AuditBackend; +import com.nike.cerberus.domain.cloudformation.TagParametersDelegate; import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.operation.vault.DisableAuditBackendOperation; +import com.nike.cerberus.operation.core.CreateVpcOperation; -import static com.nike.cerberus.command.vault.DisableAuditBackendCommand.COMMAND_NAME; +import static com.nike.cerberus.command.core.CreateVpcCommand.COMMAND_NAME; /** - * Command for disabling a specific audit backend. + * Command to create the VPC in which Cerberus components will live. */ -@Parameters(commandNames = COMMAND_NAME, commandDescription = "Disables the specified audit backend.") -public class DisableAuditBackendCommand implements Command { +@Parameters(commandNames = COMMAND_NAME, + commandDescription = "Create the VPC in which Cerberus components live") +public class CreateVpcCommand implements Command { - public static final String COMMAND_NAME = "disable-audit-backend"; + public static final String COMMAND_NAME = "create-vpc"; - @Parameter(names = {"--backend"}, required = true, description = "Audit backend.") - private AuditBackend auditBackend; + @ParametersDelegate + private TagParametersDelegate tagsDelegate = new TagParametersDelegate(); - public AuditBackend getAuditBackend() { - return auditBackend; + public TagParametersDelegate getTagsDelegate() { + return tagsDelegate; } @Override @@ -47,6 +48,6 @@ public String getCommandName() { @Override public Class> getOperationClass() { - return DisableAuditBackendOperation.class; + return CreateVpcOperation.class; } } diff --git a/src/main/java/com/nike/cerberus/command/vault/VaultHealthCheckCommand.java b/src/main/java/com/nike/cerberus/command/core/CreateWafCommand.java similarity index 51% rename from src/main/java/com/nike/cerberus/command/vault/VaultHealthCheckCommand.java rename to src/main/java/com/nike/cerberus/command/core/CreateWafCommand.java index a4f205e5..1525a811 100644 --- a/src/main/java/com/nike/cerberus/command/vault/VaultHealthCheckCommand.java +++ b/src/main/java/com/nike/cerberus/command/core/CreateWafCommand.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Nike, Inc. + * Copyright (c) 2017 Nike, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,30 +14,31 @@ * limitations under the License. */ -package com.nike.cerberus.command.vault; +package com.nike.cerberus.command.core; -import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; +import com.beust.jcommander.ParametersDelegate; import com.nike.cerberus.command.Command; +import com.nike.cerberus.domain.cloudformation.TagParametersDelegate; import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.operation.vault.VaultHealthCheckOperation; +import com.nike.cerberus.operation.core.CreateWafOperation; -import static com.nike.cerberus.command.vault.UnsealVaultClusterCommand.COMMAND_NAME; +import static com.nike.cerberus.command.core.CreateWafCommand.COMMAND_NAME; /** - * Polls health statuses for vault cluster + * Command to create the WAF for Cerberus. */ -@Parameters(commandNames = COMMAND_NAME, commandDescription = "Reports the health of each Vault instance.") -public class VaultHealthCheckCommand implements Command { +@Parameters(commandNames = COMMAND_NAME, + commandDescription = "Create the Web Application Firewall (WAF) for the load balancer.") +public class CreateWafCommand implements Command { - public static final String COMMAND_NAME = "vault-health"; + public static final String COMMAND_NAME = "create-waf"; - @Parameter(names = "--poll", - description = "Flag for polling health check vs running once") - private boolean poll; + @ParametersDelegate + private TagParametersDelegate tagsDelegate = new TagParametersDelegate(); - public boolean isPoll() { - return poll; + public TagParametersDelegate getTagsDelegate() { + return tagsDelegate; } @Override @@ -47,6 +48,7 @@ public String getCommandName() { @Override public Class> getOperationClass() { - return VaultHealthCheckOperation.class; + return CreateWafOperation.class; } + } diff --git a/src/main/java/com/nike/cerberus/command/core/DeleteStackCommand.java b/src/main/java/com/nike/cerberus/command/core/DeleteStackCommand.java new file mode 100644 index 00000000..b9ef64a6 --- /dev/null +++ b/src/main/java/com/nike/cerberus/command/core/DeleteStackCommand.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.command.core; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; +import com.nike.cerberus.command.Command; +import com.nike.cerberus.domain.environment.Stack; +import com.nike.cerberus.operation.Operation; +import com.nike.cerberus.operation.core.DeleteStackOperation; +import com.nike.cerberus.util.StackConverter; + +import static com.nike.cerberus.command.core.DeleteStackCommand.COMMAND_NAME; + +@Parameters( + commandNames = { + COMMAND_NAME + }, + commandDescription = "Uses CloudFormation to delete a Stack" +) +public class DeleteStackCommand implements Command { + + public static final String COMMAND_NAME = "delete-stack"; + + public static final String STACK_NAME_LONG_ARG = "--stack-name"; + + public static final String REGION_LONG_ARG = "--region"; + + @Parameter( + names = {STACK_NAME_LONG_ARG}, + required = true, + description = "Stack name to delete", + converter = StackConverter.class) + private Stack stack; + + @Parameter( + names = {REGION_LONG_ARG}, + description = "Region to delete stack in, defaults to primary region" + ) + private String region; + + public Stack getStack() { + return stack; + } + + public String getRegion() { + return region; + } + + @Override + public String getCommandName() { + return COMMAND_NAME; + } + + @Override + public Class> getOperationClass() { + return DeleteStackOperation.class; + } +} diff --git a/src/main/java/com/nike/cerberus/command/core/GenerateCertificateFilesCommand.java b/src/main/java/com/nike/cerberus/command/core/GenerateCertificateFilesCommand.java new file mode 100644 index 00000000..bf7418f4 --- /dev/null +++ b/src/main/java/com/nike/cerberus/command/core/GenerateCertificateFilesCommand.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.command.core; + +import com.beust.jcommander.Parameters; +import com.beust.jcommander.ParametersDelegate; +import com.nike.cerberus.command.Command; +import com.nike.cerberus.operation.Operation; +import com.nike.cerberus.operation.core.GenerateCertificateFilesOperation; + +import static com.nike.cerberus.command.core.GenerateCertificateFilesCommand.COMMAND_DESCRIPTION; +import static com.nike.cerberus.command.core.GenerateCertificateFilesCommand.COMMAND_NAME; + +@Parameters( + commandNames = COMMAND_NAME, + commandDescription = COMMAND_DESCRIPTION +) +public class GenerateCertificateFilesCommand implements Command { + + public static final String COMMAND_NAME = "generate-certificate-files"; + public static final String COMMAND_DESCRIPTION = "Generates the TLS certificates needed to enable https " + + "through out the system, using an ACME provider such as LetsEncrypt"; + + @ParametersDelegate + private GenerateCertificateFilesCommandParametersDelegate generateCertificateFilesCommandParametersDelegate = + new GenerateCertificateFilesCommandParametersDelegate(); + + public GenerateCertificateFilesCommandParametersDelegate getGenerateCertificateFilesCommandParametersDelegate() { + return generateCertificateFilesCommandParametersDelegate; + } + + @Override + public String getCommandName() { + return COMMAND_NAME; + } + + @Override + public Class> getOperationClass() { + return GenerateCertificateFilesOperation.class; + } +} diff --git a/src/main/java/com/nike/cerberus/command/core/GenerateCertificateFilesCommandParametersDelegate.java b/src/main/java/com/nike/cerberus/command/core/GenerateCertificateFilesCommandParametersDelegate.java new file mode 100644 index 00000000..3de3a958 --- /dev/null +++ b/src/main/java/com/nike/cerberus/command/core/GenerateCertificateFilesCommandParametersDelegate.java @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.command.core; + +import com.beust.jcommander.Parameter; + +import java.util.ArrayList; +import java.util.List; + +public class GenerateCertificateFilesCommandParametersDelegate { + + public static final String BASE_DOMAIN_LONG_ARG = "--base-domain"; + public static final String EDGE_DOMAIN_NAME_OVERRIDE_LONG_ARG = "--edge-domain-name-override"; + public static final String ORIGIN_DOMAIN_NAME_OVERRIDE_LONG_ARG = "--origin-domain-name-override"; + public static final String LOAD_BALANCER_DOMAIN_NAME_OVERRIDE_LONG_ARG = "--load-balancer-domain-name-override"; + public static final String HOSTED_ZONE_ID_LONG_ARG = "--hosted-zone-id"; + public static final String SUBJECT_ALT_NAME_LONG_ARG = "--subject-alternative-name"; + public static final String ENABLE_LE_CERTFIX_LONG_ARG = "--enable-letsecrypt-certfix"; + public static final String CERT_FOLDER_LONG_ARG = "--local-certificate-directory"; + public static final String ACME_API_LONG_ARG = "--acme-api-url"; + public static final String CONTACT_EMAIL_LONG_ARG = "--contact-email"; + public static final String ACCEPT_ACME_TOS = "--no-tty-force-acme-tos-accept"; + + public static final String LETS_ENCRYPT_ACME_API_URI = "acme://letsencrypt.org"; + + @Parameter( + names = { + BASE_DOMAIN_LONG_ARG + }, + description = "The base domain for the environment that this command will use to generate the following " + + "subject name {env}.{base-domain} and with the following subject alternative name {env}.{region}.{base-domain}\n" + + "ex: cerberus -e demo -r us-west-2 generate-certificates --base-domain cerberus.example would make a cert for demo.cerberus.example " + + "with a sans of demo.us-west-2.cerberus.example so that we can create a CNAME record for " + + "demo.cerberus.example that will point to an ALB in us-west-2 with a CNAME record of " + + "demo.us-west-2.cerberus.example and the cert will be valid for both" + ) + private String baseDomainName; + + @Parameter( + names = { + EDGE_DOMAIN_NAME_OVERRIDE_LONG_ARG + }, + description = "This command uses {environment}.{base-domain} as the common name, override it with this option" + ) + private String edgeDomainNameOverride; + + @Parameter( + names = { + ORIGIN_DOMAIN_NAME_OVERRIDE_LONG_ARG + }, + description = "Origin domain name defaults to origin.{environment-name}.{base-domain}, " + + "this command automatically creates a subject alternate name for this, override it with this option" + ) + private String originDomainNameOverride; + + @Parameter( + names = { + LOAD_BALANCER_DOMAIN_NAME_OVERRIDE_LONG_ARG + }, + description = "The load balancer domain name defaults to {environment-name}.{primary-primaryRegion}.{base-domain}, " + + "this command automatically creates a subject alternate name for this, override it with this option" + ) + private String loadBalancerDomainNameOverride; + + @Parameter( + names = { + HOSTED_ZONE_ID_LONG_ARG + }, + description = "The AWS Route 53 hosted zone id that is configured to create records for the base domain", + required = true + ) + private String hostedZoneId; + + @Parameter( + names = { + SUBJECT_ALT_NAME_LONG_ARG + }, + description = "Alternative subject names, this should be any additional cnames that need to be secured. such as " + ) + private List subjectAlternativeNames = new ArrayList(); + + @Parameter( + names = { + ENABLE_LE_CERTFIX_LONG_ARG + }, + description = "This command uses the acme4j client to communicate with the ACME server, " + + "it supports uses a hardcoded Let'sEncrypt cert to get around SSL errors, you can use this " + + "flag if your truststore is not configured to trust LE certificates, which can be found " + + " here: https://letsencrypt.org/certificates/" + ) + private boolean enableLetsEncryptCertfix = false; + + @Parameter( + names = { + CERT_FOLDER_LONG_ARG + }, + description = "A local writable folder to store the generated key and cert files", + required = true + ) + private String certDir; + + @Parameter( + names = { + ACME_API_LONG_ARG + }, + description = "The ACME provider API URL to use, e.g. Let's Encrypt: " + LETS_ENCRYPT_ACME_API_URI, + required = true + ) + private String acmeApiUrl; + + @Parameter( + names = { + CONTACT_EMAIL_LONG_ARG + }, + description = "The email contact to use when creating the certificates", + required = true + ) + private String contactEmail; + + + @Parameter( + names = { + ACCEPT_ACME_TOS + }, + description = "If supplying --no-tty and creating a new ACME account you must go read the TOS and supply " + + "this flag to indicate that you accept the TOS" + ) + private boolean autoAcceptAcmeTos = false; + + public String getBaseDomainName() { + return baseDomainName; + } + + public String getEdgeDomainNameOverride() { + return edgeDomainNameOverride; + } + + public String getOriginDomainNameOverride() { + return originDomainNameOverride; + } + + public String getLoadBalancerDomainNameOverride() { + return loadBalancerDomainNameOverride; + } + + public String getHostedZoneId() { + return hostedZoneId; + } + + public List getSubjectAlternativeNames() { + return subjectAlternativeNames; + } + + public boolean enableLetsEncryptCertfix() { + return enableLetsEncryptCertfix; + } + + public String getCertDir() { + return certDir; + } + + public String getAcmeApiUrl() { + return acmeApiUrl; + } + + public String getContactEmail() { + return contactEmail; + } + + public boolean isAutoAcceptAcmeTos() { + return autoAcceptAcmeTos; + } +} \ No newline at end of file diff --git a/src/main/java/com/nike/cerberus/command/core/InitializeEnvironmentCommand.java b/src/main/java/com/nike/cerberus/command/core/InitializeEnvironmentCommand.java new file mode 100644 index 00000000..2fe117b6 --- /dev/null +++ b/src/main/java/com/nike/cerberus/command/core/InitializeEnvironmentCommand.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.command.core; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; +import com.beust.jcommander.ParametersDelegate; +import com.nike.cerberus.command.Command; +import com.nike.cerberus.domain.cloudformation.TagParametersDelegate; +import com.nike.cerberus.operation.Operation; +import com.nike.cerberus.operation.core.InitializeEnvironmentOperation; + +import java.util.List; + +import static com.nike.cerberus.command.core.InitializeEnvironmentCommand.COMMAND_NAME; + +/** + * Command for creating the base components for Cerberus. + */ +@Parameters(commandNames = COMMAND_NAME, + commandDescription = "Create the IAM roles, KMS keys, and S3 buckets and base config files for needed to " + + "bootstrap a Cerberus environment and store env infrastructure state") +public class InitializeEnvironmentCommand implements Command { + + public static final String COMMAND_NAME = "init-env"; + public static final String ADMIN_ROLE_ARN_LONG_ARG = "--admin-role-arn"; + public static final String REGION_LONG_ARG = "--regions"; + public static final String PRIMARY_REGION = "--primary-region"; + + @Parameter( + names = ADMIN_ROLE_ARN_LONG_ARG, + description = "An IAM role ARN that will be given elevated privileges for the KMS CMKs created.", + required = true + ) + private String adminRoleArn; + + @Parameter( + names = REGION_LONG_ARG, + description = "The regions to use with the Cerberus environment, you must declare at least 2 regions, " + + "The CLI and the Management service use the AWS Encryption SDK that supports encrypting payloads " + + "with data keys from multiple regions so that the data can be decrypted in other regions in case of " + + "region outages. We require at least 2 regions to ensure high availability of config and secure data", + variableArity = true, + required = true + ) + private List regions; + + @Parameter( + names = PRIMARY_REGION, + description = "The primary region is the region that will serve traffic by default, this region will " + + "have the necessary infrastructure stood up to serve traffic and store data. " + + "The primary region must be one of the --region args passed", + required = true + ) + private String primaryRegion; + + @ParametersDelegate + private TagParametersDelegate tagParameters = new TagParametersDelegate(); + + public String getAdminRoleArn() { + return adminRoleArn; + } + + public List getRegions() { + return regions; + } + + public String getPrimaryRegion() { + return primaryRegion; + } + + public TagParametersDelegate getTagsDelegate() { + return tagParameters; + } + + @Override + public String getCommandName() { + return COMMAND_NAME; + } + + @Override + public Class> getOperationClass() { + return InitializeEnvironmentOperation.class; + } + +} diff --git a/src/main/java/com/nike/cerberus/command/core/PrintStackInfoCommand.java b/src/main/java/com/nike/cerberus/command/core/PrintStackInfoCommand.java index 534e298f..e436aad9 100644 --- a/src/main/java/com/nike/cerberus/command/core/PrintStackInfoCommand.java +++ b/src/main/java/com/nike/cerberus/command/core/PrintStackInfoCommand.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Nike, Inc. + * Copyright (c) 2017 Nike, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,9 +19,10 @@ import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; import com.nike.cerberus.command.Command; -import com.nike.cerberus.domain.environment.StackName; +import com.nike.cerberus.domain.environment.Stack; import com.nike.cerberus.operation.Operation; import com.nike.cerberus.operation.core.PrintStackInfoOperation; +import com.nike.cerberus.util.StackConverter; import static com.nike.cerberus.command.core.PrintStackInfoCommand.COMMAND_NAME; @@ -33,11 +34,13 @@ public class PrintStackInfoCommand implements Command { public static final String COMMAND_NAME = "print-stack-info"; - @Parameter(names = {"--stack-name"}, required = true, description = "The stack name to print information about.") - private StackName stackName; + public static final String STACK_NAME_LONG_ARG = "--stack-name"; - public StackName getStackName() { - return stackName; + @Parameter(names = {STACK_NAME_LONG_ARG}, required = true, description = "The stack name to print information about.", converter = StackConverter.class) + private Stack stack; + + public Stack getStack() { + return stack; } @Override diff --git a/src/main/java/com/nike/cerberus/command/vault/CreateVaultConfigCommand.java b/src/main/java/com/nike/cerberus/command/core/RebootCmsCommand.java similarity index 57% rename from src/main/java/com/nike/cerberus/command/vault/CreateVaultConfigCommand.java rename to src/main/java/com/nike/cerberus/command/core/RebootCmsCommand.java index bc889df7..adb4f26d 100644 --- a/src/main/java/com/nike/cerberus/command/vault/CreateVaultConfigCommand.java +++ b/src/main/java/com/nike/cerberus/command/core/RebootCmsCommand.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Nike, Inc. + * Copyright (c) 2017 Nike, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,22 +14,26 @@ * limitations under the License. */ -package com.nike.cerberus.command.vault; +package com.nike.cerberus.command.core; import com.beust.jcommander.Parameters; import com.nike.cerberus.command.Command; import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.operation.vault.CreateVaultConfigOperation; +import com.nike.cerberus.operation.core.RebootCmsOperation; -import static com.nike.cerberus.command.vault.CreateVaultConfigCommand.COMMAND_NAME; +import static com.nike.cerberus.command.core.RebootCmsCommand.COMMAND_NAME; /** - * Command to create the vault configuration. + * Command to reboot the CMS cluster. */ -@Parameters(commandNames = COMMAND_NAME, commandDescription = "Creates the vault configuration for the cluster.") -public class CreateVaultConfigCommand implements Command { +@Parameters( + commandNames = COMMAND_NAME, + commandDescription = "Performs a safe rolling reboot on instances in the cms ASG, checking that " + + "the previous instance is healthy before rebooting the next one." +) +public class RebootCmsCommand implements Command { - public static final String COMMAND_NAME = "create-vault-config"; + public static final String COMMAND_NAME = "reboot-cms"; @Override public String getCommandName() { @@ -38,6 +42,6 @@ public String getCommandName() { @Override public Class> getOperationClass() { - return CreateVaultConfigOperation.class; + return RebootCmsOperation.class; } } diff --git a/src/main/java/com/nike/cerberus/command/core/RestoreCerberusBackupCommand.java b/src/main/java/com/nike/cerberus/command/core/RestoreCerberusBackupCommand.java index 2d65fce9..7ce8ba8c 100644 --- a/src/main/java/com/nike/cerberus/command/core/RestoreCerberusBackupCommand.java +++ b/src/main/java/com/nike/cerberus/command/core/RestoreCerberusBackupCommand.java @@ -22,7 +22,7 @@ import com.nike.cerberus.operation.Operation; import com.nike.cerberus.operation.core.RestoreCerberusBackupOperation; -import static com.nike.cerberus.command.core.WhitelistCidrForVpcAccessCommand.COMMAND_NAME; +import static com.nike.cerberus.command.core.RestoreCerberusBackupCommand.COMMAND_NAME; /** * Command for restoring Safe Deposit Box Metadata and Vault secret data for SDBs from backups that are in S3 from diff --git a/src/main/java/com/nike/cerberus/command/core/RollingRebootWithHealthCheckCommand.java b/src/main/java/com/nike/cerberus/command/core/RollingRebootWithHealthCheckCommand.java deleted file mode 100644 index a0b2bbab..00000000 --- a/src/main/java/com/nike/cerberus/command/core/RollingRebootWithHealthCheckCommand.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.nike.cerberus.command.core; - -import com.beust.jcommander.Parameter; -import com.beust.jcommander.Parameters; -import com.nike.cerberus.command.Command; -import com.nike.cerberus.domain.environment.StackName; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.operation.core.RollingRebootWithHealthCheckOperation; - -import static com.nike.cerberus.command.core.RollingRebootWithHealthCheckCommand.COMMAND_NAME; - -/** - * Command to reboot the CMS cluster. - */ -@Parameters( - commandNames = COMMAND_NAME, - commandDescription = "Performs a safe rolling reboot on instances in the given cluster, checking that " + - "the previous instance is healthy before rebooting the next one." -) -public class RollingRebootWithHealthCheckCommand implements Command { - - public static final String COMMAND_NAME = "rolling-reboot"; - - @Parameter(names = {"--stack-name"}, required = true, description = "The stack name to reboot.") - private StackName stackName = StackName.CMS; - - public StackName getStackName() { - return stackName; - } - - @Override - public String getCommandName() { - return COMMAND_NAME; - } - - @Override - public Class> getOperationClass() { - return RollingRebootWithHealthCheckOperation.class; - } -} diff --git a/src/main/java/com/nike/cerberus/command/core/SetBackupAdminPrincipalsCommand.java b/src/main/java/com/nike/cerberus/command/core/SetBackupAdminPrincipalsCommand.java deleted file mode 100644 index e32c36a8..00000000 --- a/src/main/java/com/nike/cerberus/command/core/SetBackupAdminPrincipalsCommand.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2017 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.command.core; - -import com.beust.jcommander.Parameter; -import com.beust.jcommander.Parameters; -import com.nike.cerberus.command.Command; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.operation.core.SetBackupAdminPrincipalsOperation; - -import java.util.ArrayList; -import java.util.List; - -import static com.nike.cerberus.command.core.SetBackupAdminPrincipalsCommand.COMMAND_NAME; - -/** - * Command to update which principals besides for the root account will have permissions to use the backup cmk, - * AKA create and restore backups. - */ -@Parameters( - commandNames = COMMAND_NAME, - commandDescription = "Update the IAM Principals that are allowed to create and restore backups. " + - "This command automatically adds by default the root user and configured admin user arn, " + - "but you can use this command to add iam principals such as CI systems and additional user principals " + - "that will have access to encrypt and decrypt backup data" -) -public class SetBackupAdminPrincipalsCommand implements Command { - - public static final String COMMAND_NAME = "set-backup-principals"; - - public static final String PRINCIPAL_LONG_ARG = "--principal"; - public static final String PRINCIPAL_SHORT_ARG = "-p"; - - @Parameter( - names = { - PRINCIPAL_LONG_ARG, - PRINCIPAL_SHORT_ARG - }, - description = "One or more additional principals to grant access to." - ) - private List additionalPrincipals = new ArrayList<>(); - - public List getAdditionalPrincipals() { - return additionalPrincipals; - } - - @Override - public String getCommandName() { - return COMMAND_NAME; - } - - @Override - public Class> getOperationClass() { - return SetBackupAdminPrincipalsOperation.class; - } -} diff --git a/src/main/java/com/nike/cerberus/command/core/UpdateStackCommand.java b/src/main/java/com/nike/cerberus/command/core/UpdateStackCommand.java index 7d818b9d..e6be80e0 100644 --- a/src/main/java/com/nike/cerberus/command/core/UpdateStackCommand.java +++ b/src/main/java/com/nike/cerberus/command/core/UpdateStackCommand.java @@ -19,18 +19,18 @@ import com.beust.jcommander.DynamicParameter; import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; +import com.beust.jcommander.ParametersDelegate; import com.nike.cerberus.command.Command; -import com.nike.cerberus.command.StackDelegate; -import com.nike.cerberus.domain.environment.StackName; +import com.nike.cerberus.domain.cloudformation.TagParametersDelegate; +import com.nike.cerberus.domain.environment.Stack; import com.nike.cerberus.operation.Operation; import com.nike.cerberus.operation.core.UpdateStackOperation; +import com.nike.cerberus.util.StackConverter; import java.util.HashMap; import java.util.Map; import static com.nike.cerberus.command.core.UpdateStackCommand.COMMAND_NAME; -import static com.nike.cerberus.ConfigConstants.SKIP_AMI_TAG_CHECK_ARG; -import static com.nike.cerberus.ConfigConstants.SKIP_AMI_TAG_CHECK_DESCRIPTION; /** @@ -40,80 +40,29 @@ public class UpdateStackCommand implements Command { public static final String COMMAND_NAME = "update-stack"; + public static final String STACK_NAME_LONG_ARG = "--stack-name"; public static final String OVERWRITE_TEMPLATE_LONG_ARG = "--overwrite-template"; public static final String PARAMETER_SHORT_ARG = "-P"; - @Parameter(names = {"--stack-name"}, required = true, description = "The stack name to update.") - private StackName stackName; + @Parameter(names = {STACK_NAME_LONG_ARG}, required = true, description = "The stack name to update.", converter = StackConverter.class) + private Stack stack; - @Parameter(names = StackDelegate.OWNER_GROUP_LONG_ARG, - description = "The owning group for the resources to be updated. Will be tagged on all resources.", - required = true) - private String ownerGroup; + @ParametersDelegate + private TagParametersDelegate tagsDelegate = new TagParametersDelegate(); - @Parameter(names = StackDelegate.AMI_ID_LONG_ARG, description = "The AMI ID for the specified stack.") - private String amiId; - - @Parameter(names = StackDelegate.INSTANCE_SIZE_LONG_ARG, description = "Specify a custom instance size.") - private String instanceSize; - - @Parameter(names = StackDelegate.KEY_PAIR_NAME_LONG_ARG, description = "SSH key pair name.") - private String keyPairName; - - @Parameter(names = StackDelegate.OWNER_EMAIL_LONG_ARG, - description = "The e-mail for who owns the provisioned resources. Will be tagged on all resources.") - private String ownerEmail; - - @Parameter(names = StackDelegate.COST_CENTER_LONG_ARG, - description = "Costcenter for where to bill provisioned resources. Will be tagged on all resources.") - private String costcenter; + public TagParametersDelegate getTagsDelegate() { + return tagsDelegate; + } @Parameter(names = OVERWRITE_TEMPLATE_LONG_ARG, description = "Flag for overwriting existing CloudFormation template") private boolean overwriteTemplate; - @Parameter(names = StackDelegate.DESIRED_INSTANCES_LONG_ARG, description = "Desired number of auto scaling instances.") - private Integer desiredInstances; - - @Parameter(names = StackDelegate.MAX_INSTANCES_LONG_ARG, description = "Maximum number of auto scaling instances.") - private Integer maximumInstances; - - @Parameter(names = StackDelegate.MIN_INSTANCES_LONG_ARG, description = "Minimum number of autos scaling instances") - private Integer minimumInstances; - - @Parameter(names = SKIP_AMI_TAG_CHECK_ARG, - description = SKIP_AMI_TAG_CHECK_DESCRIPTION) - private boolean skipAmiTagCheck; - @DynamicParameter(names = PARAMETER_SHORT_ARG, description = "Dynamic parameters for overriding the values for specific parameters in the CloudFormation.") private Map dynamicParameters = new HashMap<>(); - public StackName getStackName() { - return stackName; - } - - public String getOwnerGroup() { - return ownerGroup; - } - - public String getAmiId() { - return amiId; - } - - public String getInstanceSize() { - return instanceSize; - } - - public String getKeyPairName() { - return keyPairName; - } - - public String getOwnerEmail() { - return ownerEmail; - } - - public String getCostcenter() { - return costcenter; + public Stack getStack() { + return stack; } public boolean isOverwriteTemplate() { @@ -124,22 +73,6 @@ public Map getDynamicParameters() { return dynamicParameters; } - public Integer getDesiredInstances() { - return desiredInstances; - } - - public Integer getMaximumInstances() { - return maximumInstances; - } - - public Integer getMinimumInstances() { - return minimumInstances; - } - - public boolean isSkipAmiTagCheck() { - return skipAmiTagCheck; - } - @Override public String getCommandName() { return COMMAND_NAME; diff --git a/src/main/java/com/nike/cerberus/command/core/UploadCertFilesCommand.java b/src/main/java/com/nike/cerberus/command/core/UploadCertFilesCommand.java deleted file mode 100644 index e3052d19..00000000 --- a/src/main/java/com/nike/cerberus/command/core/UploadCertFilesCommand.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.command.core; - -import com.beust.jcommander.Parameter; -import com.beust.jcommander.Parameters; -import com.nike.cerberus.command.Command; -import com.nike.cerberus.command.validator.UploadCertFilesPathValidator; -import com.nike.cerberus.command.validator.UploadCertFilesStackNameValidator; -import com.nike.cerberus.domain.environment.StackName; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.operation.core.UploadCertFilesOperation; - -import java.nio.file.Path; - -import static com.nike.cerberus.command.core.UploadCertFilesCommand.COMMAND_NAME; - -/** - * Command for uploading a certificate for a specific component. - */ -@Parameters(commandNames = COMMAND_NAME, commandDescription = "Upload certificate files for a specific component.") -public class UploadCertFilesCommand implements Command { - - public static final String COMMAND_NAME = "upload-cert"; - public static final String STACK_NAME_LONG_ARG = "--stack-name"; - public static final String CERT_PATH_LONG_ARG = "--cert-path"; - public static final String OVERWRITE_LONG_ARG = "--overwrite"; - - @Parameter( - names = {STACK_NAME_LONG_ARG}, - required = true, - description = "Stack name the certificate is for.", - validateValueWith = UploadCertFilesStackNameValidator.class) - private StackName stackName; - - @Parameter( - names = {CERT_PATH_LONG_ARG}, - required = true, - description = "Path to the directory that contains the certificate files.", - validateValueWith = UploadCertFilesPathValidator.class) - private Path certPath; - - @Parameter( - names = {OVERWRITE_LONG_ARG}, - description = "If certificate is already present for the environment and component, overwrite it." - ) - private boolean overwrite; - - public StackName getStackName() { - return stackName; - } - - public Path getCertPath() { - return certPath; - } - - public boolean isOverwrite() { - return overwrite; - } - - @Override - public String getCommandName() { - return COMMAND_NAME; - } - - @Override - public Class> getOperationClass() { - return UploadCertFilesOperation.class; - } -} diff --git a/src/main/java/com/nike/cerberus/command/core/ViewConfigCommand.java b/src/main/java/com/nike/cerberus/command/core/ViewConfigCommand.java index 671b3c20..eb24742f 100644 --- a/src/main/java/com/nike/cerberus/command/core/ViewConfigCommand.java +++ b/src/main/java/com/nike/cerberus/command/core/ViewConfigCommand.java @@ -22,7 +22,7 @@ import com.nike.cerberus.operation.Operation; import com.nike.cerberus.operation.core.ViewConfigOperation; -import static com.nike.cerberus.command.cms.CreateCmsClusterCommand.COMMAND_NAME; +import static com.nike.cerberus.command.core.ViewConfigCommand.COMMAND_NAME; /** * Command to view configuration files in S3. diff --git a/src/main/java/com/nike/cerberus/command/core/WhitelistCidrForVpcAccessCommand.java b/src/main/java/com/nike/cerberus/command/core/WhitelistCidrForVpcAccessCommand.java index 8144659e..9646d411 100644 --- a/src/main/java/com/nike/cerberus/command/core/WhitelistCidrForVpcAccessCommand.java +++ b/src/main/java/com/nike/cerberus/command/core/WhitelistCidrForVpcAccessCommand.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Nike, Inc. + * Copyright (c) 2017 Nike, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ import com.beust.jcommander.Parameters; import com.nike.cerberus.command.Command; import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.operation.core.WhitelistCidrForVpcAccessOpertaion; +import com.nike.cerberus.operation.core.WhitelistCidrForVpcAccessOperation; import java.util.ArrayList; import java.util.List; @@ -58,6 +58,6 @@ public String getCommandName() { @Override public Class> getOperationClass() { - return WhitelistCidrForVpcAccessOpertaion.class; + return WhitelistCidrForVpcAccessOperation.class; } } diff --git a/src/main/java/com/nike/cerberus/command/dashboard/PublishDashboardCommand.java b/src/main/java/com/nike/cerberus/command/dashboard/PublishDashboardCommand.java deleted file mode 100644 index 21b69091..00000000 --- a/src/main/java/com/nike/cerberus/command/dashboard/PublishDashboardCommand.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.command.dashboard; - -import com.beust.jcommander.Parameter; -import com.beust.jcommander.Parameters; -import com.nike.cerberus.command.Command; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.operation.dashboard.PublishDashboardOperation; - -import java.net.URL; - -import static com.nike.cerberus.command.dashboard.PublishDashboardCommand.COMMAND_NAME; - -/** - * Command for uploading a new dashboard. - */ -@Parameters(commandNames = COMMAND_NAME, commandDescription = "Publishes the dashboard artifact to Cerberus.") -public class PublishDashboardCommand implements Command { - - public static final String COMMAND_NAME = "publish-dashboard"; - public static final String ARTIFACT_URL_LONG_ARG = "--artifact-url"; - public static final String OVERRIDE_ARTIFACT_URL_LONG_ARG = "--override-artifact-url"; - - @Parameter(names = ARTIFACT_URL_LONG_ARG, description = "URL to the dashboard artifact.", required = true) - private URL artifactUrl; - - @Parameter(names = OVERRIDE_ARTIFACT_URL_LONG_ARG, description = "URL to an artifact that will be extracted and merged into the main artifact before upload to s3.") - private URL overrideArtifactUrl; - - public URL getArtifactUrl() { - return artifactUrl; - } - - public URL getOverrideArtifactUrl() { - return overrideArtifactUrl; - } - - @Override - public String getCommandName() { - return COMMAND_NAME; - } - - @Override - public Class> getOperationClass() { - return PublishDashboardOperation.class; - } -} diff --git a/src/main/java/com/nike/cerberus/command/gateway/CreateCloudFrontLogProcessingLambdaConfigCommand.java b/src/main/java/com/nike/cerberus/command/gateway/CreateCloudFrontLogProcessingLambdaConfigCommand.java deleted file mode 100644 index 87b38838..00000000 --- a/src/main/java/com/nike/cerberus/command/gateway/CreateCloudFrontLogProcessingLambdaConfigCommand.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.command.gateway; - -import com.beust.jcommander.Parameter; -import com.beust.jcommander.Parameters; -import com.nike.cerberus.command.Command; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.operation.gateway.CreateCloudFrontLogProcessingLambdaConfigOperation; - -import static com.nike.cerberus.command.gateway.CreateCloudFrontLogProcessingLambdaConfigCommand.COMMAND_NAME; - -/** - * Command for creating config file for Cloud Front Log Processing Lambda - */ -@Parameters(commandNames = COMMAND_NAME, commandDescription = "Creates Config file needed to drive CloudFront log processing lambda.") -public class CreateCloudFrontLogProcessingLambdaConfigCommand implements Command { - - public static final String COMMAND_NAME = "create-cloud-front-log-processor-lambda-config"; - public static final String RATE_LIMIT_PER_MINUTE_LONG_ARG = "--rate-limit-per-minute"; - public static final String RATE_LIMIT_VIOLATION_BLOCK_PERIOD_IN_MINUTES_LONG_ARG = "--rate-limit-violation-block-period-in-minutes"; - public static final String SLACK_WEB_HOOK_URL_LONG_ARG = "--slack-web-hook-url"; - public static final String SLACK_ICON_LONG_ARG = "--slack-icon"; - public static final String GOOGLE_ANALYTICS_TRACKING_ID_LONG_ARG = "--google-analytics-tracking-id"; - - @Parameter(names = RATE_LIMIT_PER_MINUTE_LONG_ARG, description = "The maximum number of requests from an IP per minute that can flow through the gateway before being auto blocked.", required = true) - private Integer requestPerMinuteLimit; - - @Parameter(names = RATE_LIMIT_VIOLATION_BLOCK_PERIOD_IN_MINUTES_LONG_ARG, description = "Time in minutes to block an ip that violates the rate limit.", required = true) - private Integer rateLimitViolationBlacklistPeriodInMinutes; - - @Parameter(names = SLACK_WEB_HOOK_URL_LONG_ARG, description = "If you provide a web hook url for slack the lambda will send messages on errors and summary info.") - private String slackWebHookUrl; - - @Parameter(names = SLACK_ICON_LONG_ARG, description = "If you provide an emoji or an icon url the lambda will use it when sending messages.") - private String slackIcon; - - @Parameter(names = GOOGLE_ANALYTICS_TRACKING_ID_LONG_ARG, description = "If you provide a Google Analytics tracking id, the KPI processer will send events to that GA Account.") - private String googleAnalyticsId; - - public Integer getRequestPerMinuteLimit() { - return requestPerMinuteLimit; - } - - public Integer getRateLimitViolationBlacklistPeriodInMinutes() { - return rateLimitViolationBlacklistPeriodInMinutes; - } - - public String getSlackWebHookUrl() { - return slackWebHookUrl; - } - - public String getSlackIcon() { - return slackIcon; - } - - public String getGoogleAnalyticsId() { - return googleAnalyticsId; - } - - @Override - public String getCommandName() { - return COMMAND_NAME; - } - - @Override - public Class> getOperationClass() { - return CreateCloudFrontLogProcessingLambdaConfigOperation.class; - } -} diff --git a/src/main/java/com/nike/cerberus/command/gateway/CreateCloudFrontSecurityGroupUpdaterLambdaCommand.java b/src/main/java/com/nike/cerberus/command/gateway/CreateCloudFrontSecurityGroupUpdaterLambdaCommand.java deleted file mode 100644 index 4758c1ff..00000000 --- a/src/main/java/com/nike/cerberus/command/gateway/CreateCloudFrontSecurityGroupUpdaterLambdaCommand.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.command.gateway; - -import com.beust.jcommander.Parameters; -import com.nike.cerberus.command.Command; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.operation.gateway.CreateCloudFrontSecurityGroupUpdaterLambdaOperation; - -import static com.nike.cerberus.command.gateway.CreateCloudFrontLogProcessingLambdaConfigCommand.COMMAND_NAME; - -/** - * This command creates the Lambda needed to update the Security Groups that limit traffic ingress to only IPs coming - * from AWS Cloud Front. - */ -@Parameters(commandNames = COMMAND_NAME, commandDescription = "creates the Lambda needed to update the Security Groups that limit traffic ingress to only IPs coming from AWS Cloud Front.") -public class CreateCloudFrontSecurityGroupUpdaterLambdaCommand implements Command { - - public static final String COMMAND_NAME = "create-cloud-front-security-group-updater-lambda"; - - @Override - public String getCommandName() { - return COMMAND_NAME; - } - - @Override - public Class> getOperationClass() { - return CreateCloudFrontSecurityGroupUpdaterLambdaOperation.class; - } -} diff --git a/src/main/java/com/nike/cerberus/command/gateway/CreateGatewayClusterCommand.java b/src/main/java/com/nike/cerberus/command/gateway/CreateGatewayClusterCommand.java deleted file mode 100644 index b4a755e9..00000000 --- a/src/main/java/com/nike/cerberus/command/gateway/CreateGatewayClusterCommand.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.command.gateway; - -import com.beust.jcommander.Parameter; -import com.beust.jcommander.Parameters; -import com.beust.jcommander.ParametersDelegate; -import com.nike.cerberus.command.Command; -import com.nike.cerberus.command.StackDelegate; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.operation.gateway.CreateGatewayClusterOperation; - -import static com.nike.cerberus.command.gateway.CreateGatewayClusterCommand.COMMAND_NAME; -import static com.nike.cerberus.ConfigConstants.SKIP_AMI_TAG_CHECK_ARG; -import static com.nike.cerberus.ConfigConstants.SKIP_AMI_TAG_CHECK_DESCRIPTION; - -/** - * Command to create the Gateway cluster. - */ -@Parameters(commandNames = COMMAND_NAME, commandDescription = "Creates the CMS cluster.") -public class CreateGatewayClusterCommand implements Command { - - public static final String COMMAND_NAME = "create-gateway-cluster"; - public static final String HOSTED_ZONE_ID_LONG_ARG = "--hosted-zone-id"; - public static final String HOSTNAME_LONG_ARG = "--hostname"; - - @Parameter(names = HOSTED_ZONE_ID_LONG_ARG, - description = "The Route 53 hosted zone ID that will be used to create the CNAME record for Cerberus.", - required = true) - private String hostedZoneId; - - @Parameter(names = HOSTNAME_LONG_ARG, - description = "The hostname that will be used to expose this Cerberus environment.", - required = true) - private String hostname; - - @Parameter(names = SKIP_AMI_TAG_CHECK_ARG, - description = SKIP_AMI_TAG_CHECK_DESCRIPTION) - private boolean skipAmiTagCheck; - - @ParametersDelegate - private StackDelegate stackDelegate = new StackDelegate(); - - public String getHostedZoneId() { - return hostedZoneId; - } - - public String getHostname() { - return hostname; - } - - public boolean isSkipAmiTagCheck() { - return skipAmiTagCheck; - } - - public StackDelegate getStackDelegate() { - return stackDelegate; - } - - @Override - public String getCommandName() { - return COMMAND_NAME; - } - - @Override - public Class> getOperationClass() { - return CreateGatewayClusterOperation.class; - } -} diff --git a/src/main/java/com/nike/cerberus/command/gateway/PublishLambdaCommand.java b/src/main/java/com/nike/cerberus/command/gateway/PublishLambdaCommand.java deleted file mode 100644 index 5bcb0975..00000000 --- a/src/main/java/com/nike/cerberus/command/gateway/PublishLambdaCommand.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.command.gateway; - -import com.beust.jcommander.Parameter; -import com.beust.jcommander.Parameters; -import com.nike.cerberus.command.Command; -import com.nike.cerberus.domain.environment.LambdaName; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.operation.gateway.PublishLambdaOperation; - -import java.net.URL; - -import static com.nike.cerberus.command.gateway.PublishLambdaCommand.COMMAND_NAME; - -/** - * Command for uploading a new lambda. - */ -@Parameters(commandNames = COMMAND_NAME, commandDescription = "Publishes the lambda artifact to Cerberus.") -public class PublishLambdaCommand implements Command { - - public static final String COMMAND_NAME = "publish-lambda"; - public static final String LAMBDA_NAME_LONG_ARG = "--lambda-name"; - public static final String ARTIFACT_URL_LONG_ARG = "--artifact-url"; - - @Parameter(names = LAMBDA_NAME_LONG_ARG, description = "Which lambda is being uploaded.", required = true) - private LambdaName lambdaName; - - @Parameter(names = ARTIFACT_URL_LONG_ARG, description = "URL to the lambda artifact.", required = true) - private URL artifactUrl; - - public LambdaName getLambdaName() { - return lambdaName; - } - - public URL getArtifactUrl() { - return artifactUrl; - } - - @Override - public String getCommandName() { - return COMMAND_NAME; - } - - @Override - public Class> getOperationClass() { - return PublishLambdaOperation.class; - } -} diff --git a/src/main/java/com/nike/cerberus/command/rds/CleanUpRdsSnapshotsCommand.java b/src/main/java/com/nike/cerberus/command/rds/CleanUpRdsSnapshotsCommand.java new file mode 100644 index 00000000..8d2055f7 --- /dev/null +++ b/src/main/java/com/nike/cerberus/command/rds/CleanUpRdsSnapshotsCommand.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2018 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.command.rds; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; +import com.nike.cerberus.command.Command; +import com.nike.cerberus.operation.Operation; +import com.nike.cerberus.operation.rds.CleanUpRdsSnapshotsOperation; + +import static com.nike.cerberus.command.rds.CleanUpRdsSnapshotsCommand.COMMAND_NAME; + +@Parameters( + commandNames = COMMAND_NAME, + commandDescription = "Copies RDS cluster snapshots in the primary for this environment to the secondary regions" +) +public class CleanUpRdsSnapshotsCommand implements Command { + + public static final String COMMAND_NAME = "cleanup-rds-snapshots"; + + public static final String DAYS_LONG_ARG = "--days"; + + public static final String DRY_LONG_ARG = "--dry"; + + @Parameter( + names = DAYS_LONG_ARG, + description = "How old cross region snapshot copies can be before they are deleted" + ) + private int days = 14; + + public int getDays() { + return days; + } + + @Parameter( + names = DRY_LONG_ARG, + description = "Add this flag to list the snapshots that would be deleted" + ) + boolean dryRun = false; + + public boolean isDryRun() { + return dryRun; + } + + @Override + public String getCommandName() { + return COMMAND_NAME; + } + + @Override + public Class> getOperationClass() { + return CleanUpRdsSnapshotsOperation.class; + } +} diff --git a/src/main/java/com/nike/cerberus/command/gateway/CreateGatewayConfigCommand.java b/src/main/java/com/nike/cerberus/command/rds/CopyRdsSnapshotsCommand.java similarity index 50% rename from src/main/java/com/nike/cerberus/command/gateway/CreateGatewayConfigCommand.java rename to src/main/java/com/nike/cerberus/command/rds/CopyRdsSnapshotsCommand.java index 6c3e1093..0b20aded 100644 --- a/src/main/java/com/nike/cerberus/command/gateway/CreateGatewayConfigCommand.java +++ b/src/main/java/com/nike/cerberus/command/rds/CopyRdsSnapshotsCommand.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Nike, Inc. + * Copyright (c) 2017 Nike, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,33 +14,37 @@ * limitations under the License. */ -package com.nike.cerberus.command.gateway; +package com.nike.cerberus.command.rds; import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; import com.nike.cerberus.command.Command; import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.operation.gateway.CreateGatewayConfigOperation; +import com.nike.cerberus.operation.rds.CopyRdsSnapshotsOperation; -import static com.nike.cerberus.command.gateway.CreateGatewayConfigCommand.COMMAND_NAME; +import static com.nike.cerberus.command.rds.CopyRdsSnapshotsCommand.COMMAND_NAME; -/** - * Command to create the gateway configuration. - */ -@Parameters(commandNames = COMMAND_NAME, commandDescription = "Creates the gateway configuration for the cluster.") -public class CreateGatewayConfigCommand implements Command { +@Parameters( + commandNames = COMMAND_NAME, + commandDescription = "Copies RDS cluster snapshots in the primary for this environment to the secondary regions" +) +public class CopyRdsSnapshotsCommand implements Command { + + public static final String COMMAND_NAME = "copy-rds-snapshots"; - public static final String COMMAND_NAME = "create-gateway-config"; + public static final String DAYS_LONG_ARG = "--days"; - @Parameter(names = CreateGatewayClusterCommand.HOSTNAME_LONG_ARG, - description = "The hostname that will be used to expose this Cerberus environment.", - required = true) - private String hostname; + @Parameter( + names = DAYS_LONG_ARG, + description = "How old a RDS cluster snapshot can be and still be considered for copying, defaults to 1 day" + ) + private long days = 1; - public String getHostname() { - return hostname; + public long getDays() { + return days; } + @Override public String getCommandName() { return COMMAND_NAME; @@ -48,6 +52,6 @@ public String getCommandName() { @Override public Class> getOperationClass() { - return CreateGatewayConfigOperation.class; + return CopyRdsSnapshotsOperation.class; } } diff --git a/src/main/java/com/nike/cerberus/command/rds/CreateDatabaseCommand.java b/src/main/java/com/nike/cerberus/command/rds/CreateDatabaseCommand.java new file mode 100644 index 00000000..89dc1b15 --- /dev/null +++ b/src/main/java/com/nike/cerberus/command/rds/CreateDatabaseCommand.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.command.rds; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; +import com.beust.jcommander.ParametersDelegate; +import com.nike.cerberus.command.Command; +import com.nike.cerberus.domain.cloudformation.TagParametersDelegate; +import com.nike.cerberus.operation.Operation; +import com.nike.cerberus.operation.rds.CreateDatabaseOperation; + +import static com.nike.cerberus.command.rds.CreateDatabaseCommand.COMMAND_NAME; + +/** + * Command to create the database for Cerberus. + */ +@Parameters(commandNames = COMMAND_NAME, + commandDescription = "Create the database to be used by the Cerberus Management Service (CMS)") +public class CreateDatabaseCommand implements Command { + + public static final String COMMAND_NAME = "create-database"; + + public static final String INSTANCE_CLASS_LONG_ARG = "--instance-class"; + + public static final String RESTORE_FROM_SNAPSHOT = "--restore-from-snapshot-using-identifier"; + + @ParametersDelegate + private TagParametersDelegate tagsDelegate = new TagParametersDelegate(); + + public TagParametersDelegate getTagsDelegate() { + return tagsDelegate; + } + + @Parameter(names = INSTANCE_CLASS_LONG_ARG, + description = "The Instance Class to use, defaults to db.r3.large" + ) + private String instanceClass; + + public String getInstanceClass() { + return instanceClass; + } + + @Parameter( + names = RESTORE_FROM_SNAPSHOT, + description = "option for setting a snapshot identifier on the RDS stack to restore from the snapshot " + + "while standing up the new RDS cluster via cloudformation" + ) + String snapshotIdentifier; + + public String getSnapshotIdentifier() { + return snapshotIdentifier; + } + + @Override + public String getCommandName() { + return COMMAND_NAME; + } + + @Override + public Class> getOperationClass() { + return CreateDatabaseOperation.class; + } + +} diff --git a/src/main/java/com/nike/cerberus/command/validator/EnvironmentNameValidator.java b/src/main/java/com/nike/cerberus/command/validator/EnvironmentNameValidator.java index 7fdf1cc1..99ccbf54 100644 --- a/src/main/java/com/nike/cerberus/command/validator/EnvironmentNameValidator.java +++ b/src/main/java/com/nike/cerberus/command/validator/EnvironmentNameValidator.java @@ -26,12 +26,13 @@ */ public class EnvironmentNameValidator implements IParameterValidator { - private final Pattern pattern = Pattern.compile("\\w+"); + public static final Pattern VALID_NAME_PATTERN = Pattern.compile("[A-Za-z\\d-]+"); + public static final String ALLOWED_DESCRIPTION = "Environment name may only contain alpha-numeric characters and hyphens."; @Override public void validate(final String name, final String value) throws ParameterException { - if (!pattern.matcher(value).matches()) { - throw new ParameterException("Environment name may only contain alpha-numeric characters and underscores."); + if (!VALID_NAME_PATTERN.matcher(value).matches()) { + throw new ParameterException(ALLOWED_DESCRIPTION); } } } diff --git a/src/main/java/com/nike/cerberus/command/validator/UploadCertFilesPathValidator.java b/src/main/java/com/nike/cerberus/command/validator/UploadCertFilesPathValidator.java index c566e5a7..d6bdd655 100644 --- a/src/main/java/com/nike/cerberus/command/validator/UploadCertFilesPathValidator.java +++ b/src/main/java/com/nike/cerberus/command/validator/UploadCertFilesPathValidator.java @@ -18,17 +18,9 @@ import com.beust.jcommander.IValueValidator; import com.beust.jcommander.ParameterException; -import com.beust.jcommander.internal.Sets; -import org.apache.commons.io.filefilter.RegexFileFilter; import java.io.File; -import java.io.FilenameFilter; import java.nio.file.Path; -import java.util.Arrays; -import java.util.Set; -import java.util.StringJoiner; - -import static com.nike.cerberus.operation.core.UploadCertFilesOperation.EXPECTED_FILE_NAMES; /** * Validates that the specified directory contains all the correct files for cert upload. @@ -43,25 +35,13 @@ public void validate(final String name, final Path value) throws ParameterExcept } final File certDirectory = value.toFile(); - final Set filenames = Sets.newHashSet(); if (!certDirectory.canRead()) { - throw new ParameterException("Specified path is not readable."); + throw new ParameterException(String.format("Specified path: %s is not readable.", certDirectory.getAbsolutePath())); } if (!certDirectory.isDirectory()) { - throw new ParameterException("Specified path is not a directory."); - } - - - final FilenameFilter filter = new RegexFileFilter("^.*\\.pem$"); - final File[] files = certDirectory.listFiles(filter); - Arrays.stream(files).forEach(file -> filenames.add(file.getName())); - - if (!filenames.containsAll(EXPECTED_FILE_NAMES)) { - final StringJoiner sj = new StringJoiner(", ", "[", "]"); - EXPECTED_FILE_NAMES.stream().forEach(sj::add); - throw new ParameterException("Not all expected files are present! Expected: " + sj.toString()); + throw new ParameterException(String.format("Specified path: %s is not a directory.", certDirectory.getAbsolutePath())); } } } diff --git a/src/main/java/com/nike/cerberus/command/validator/UploadCertFilesStackNameValidator.java b/src/main/java/com/nike/cerberus/command/validator/UploadCertFilesStackNameValidator.java index 68d20ae1..a8100908 100644 --- a/src/main/java/com/nike/cerberus/command/validator/UploadCertFilesStackNameValidator.java +++ b/src/main/java/com/nike/cerberus/command/validator/UploadCertFilesStackNameValidator.java @@ -19,21 +19,20 @@ import com.beust.jcommander.IValueValidator; import com.beust.jcommander.ParameterException; import com.google.common.collect.Sets; -import com.nike.cerberus.domain.environment.StackName; +import com.nike.cerberus.domain.environment.Stack; import java.util.Set; /** * Validates that the stack name specified actually requires a certificate to be uploaded. */ -public class UploadCertFilesStackNameValidator implements IValueValidator { +public class UploadCertFilesStackNameValidator implements IValueValidator { - private final Set stackNamesWithCerts = Sets.newHashSet( - StackName.VAULT, StackName.CONSUL, StackName.CMS, StackName.GATEWAY); + private final Set stackNamesWithCerts = Sets.newHashSet(Stack.CMS); @Override - public void validate(final String name, final StackName stackName) throws ParameterException { - if (!stackNamesWithCerts.contains(stackName)) { + public void validate(final String name, final Stack stack) throws ParameterException { + if (!stackNamesWithCerts.contains(stack)) { throw new ParameterException("Stack specified doesn't require a certificate to be uploaded."); } } diff --git a/src/main/java/com/nike/cerberus/command/vault/CreateCmsVaultTokenCommand.java b/src/main/java/com/nike/cerberus/command/vault/CreateCmsVaultTokenCommand.java deleted file mode 100644 index 1495bd24..00000000 --- a/src/main/java/com/nike/cerberus/command/vault/CreateCmsVaultTokenCommand.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.command.vault; - -import com.beust.jcommander.Parameter; -import com.beust.jcommander.Parameters; -import com.nike.cerberus.command.Command; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.operation.vault.CreateCmsVaultTokenOperation; - -import static com.nike.cerberus.command.vault.CreateCmsVaultTokenCommand.COMMAND_NAME; - -/** - * Command to create the Vault token for CMS. - */ -@Parameters(commandNames = COMMAND_NAME, commandDescription = "Creates the Vault token for CMS.") -public class CreateCmsVaultTokenCommand implements Command { - - public static final String COMMAND_NAME = "create-cms-vault-token"; - - public static final String FORCE_OVERWRITE_LONG_ARG = "--force-overwrite"; - - // This option is mainly useful if: - // 1) you want to go through some manual steps to rotate the CMS vault token - // 2) you've partially hosed a development system - @Parameter(names = FORCE_OVERWRITE_LONG_ARG, - description = "Force overwriting existing CMS vault token in secrets.json. It is important to manually revoke the old CMS token when using this option.") - private boolean forceOverwrite = false; - - public boolean isForceOverwrite() { - return forceOverwrite; - } - - @Override - public String getCommandName() { - return COMMAND_NAME; - } - - @Override - public Class> getOperationClass() { - return CreateCmsVaultTokenOperation.class; - } -} diff --git a/src/main/java/com/nike/cerberus/command/vault/CreateVaultClusterCommand.java b/src/main/java/com/nike/cerberus/command/vault/CreateVaultClusterCommand.java deleted file mode 100644 index 6791beef..00000000 --- a/src/main/java/com/nike/cerberus/command/vault/CreateVaultClusterCommand.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.command.vault; - -import com.beust.jcommander.Parameter; -import com.beust.jcommander.Parameters; -import com.beust.jcommander.ParametersDelegate; -import com.nike.cerberus.command.Command; -import com.nike.cerberus.command.StackDelegate; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.operation.vault.CreateVaultClusterOperation; - -import static com.nike.cerberus.command.vault.CreateVaultClusterCommand.COMMAND_NAME; -import static com.nike.cerberus.ConfigConstants.SKIP_AMI_TAG_CHECK_ARG; -import static com.nike.cerberus.ConfigConstants.SKIP_AMI_TAG_CHECK_DESCRIPTION; - -/** - * Command to create the Vault cluster. - */ -@Parameters(commandNames = COMMAND_NAME, commandDescription = "Creates the Vault cluster.") -public class CreateVaultClusterCommand implements Command { - - public static final String COMMAND_NAME = "create-vault-cluster"; - - @ParametersDelegate - private StackDelegate stackDelegate = new StackDelegate(); - - public StackDelegate getStackDelegate() { - return stackDelegate; - } - - @Override - public String getCommandName() { - return COMMAND_NAME; - } - - @Parameter(names = SKIP_AMI_TAG_CHECK_ARG, - description = SKIP_AMI_TAG_CHECK_DESCRIPTION) - private boolean skipAmiTagCheck; - - public boolean isSkipAmiTagCheck() { - return skipAmiTagCheck; - } - - @Override - public Class> getOperationClass() { - return CreateVaultClusterOperation.class; - } -} diff --git a/src/main/java/com/nike/cerberus/command/vault/EnableAuditBackendCommand.java b/src/main/java/com/nike/cerberus/command/vault/EnableAuditBackendCommand.java deleted file mode 100644 index c9476fed..00000000 --- a/src/main/java/com/nike/cerberus/command/vault/EnableAuditBackendCommand.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.command.vault; - -import com.beust.jcommander.Parameter; -import com.beust.jcommander.Parameters; -import com.nike.cerberus.command.Command; -import com.nike.cerberus.domain.vault.AuditBackend; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.operation.vault.EnableAuditBackendOperation; - -import java.nio.file.Path; - -import static com.nike.cerberus.command.vault.EnableAuditBackendCommand.COMMAND_NAME; - -/** - * Command to enable the audit backend for Vault. - */ -@Parameters(commandNames = COMMAND_NAME, commandDescription = "Enables the audit backend for Vault.") -public class EnableAuditBackendCommand implements Command { - - public static final String COMMAND_NAME = "enable-audit-backend"; - - @Parameter(names = {"--backend"}, required = true, description = "Audit backend.") - private AuditBackend auditBackend; - - @Parameter(names = {"--file-path"}, - description = "File path for where the audit log will be written on the Vault instance. " + - "Only required for the file backend type.") - private Path filePath; - - public AuditBackend getAuditBackend() { - return auditBackend; - } - - public Path getFilePath() { - return filePath; - } - - @Override - public String getCommandName() { - return COMMAND_NAME; - } - - @Override - public Class> getOperationClass() { - return EnableAuditBackendOperation.class; - } -} diff --git a/src/main/java/com/nike/cerberus/command/vault/UnsealVaultClusterCommand.java b/src/main/java/com/nike/cerberus/command/vault/UnsealVaultClusterCommand.java deleted file mode 100644 index 4882e477..00000000 --- a/src/main/java/com/nike/cerberus/command/vault/UnsealVaultClusterCommand.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.command.vault; - -import com.beust.jcommander.Parameters; -import com.nike.cerberus.command.Command; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.operation.vault.UnsealVaultClusterOperation; - -import static com.nike.cerberus.command.vault.UnsealVaultClusterCommand.COMMAND_NAME; - -/** - * Command to unseal the Vault cluster. - */ -@Parameters(commandNames = COMMAND_NAME, commandDescription = "Unseals the Vault cluster.") -public class UnsealVaultClusterCommand implements Command { - - public static final String COMMAND_NAME = "unseal-vault-cluster"; - - @Override - public String getCommandName() { - return COMMAND_NAME; - } - - @Override - public Class> getOperationClass() { - return UnsealVaultClusterOperation.class; - } -} diff --git a/src/main/java/com/nike/cerberus/domain/EnvironmentMetadata.java b/src/main/java/com/nike/cerberus/domain/EnvironmentMetadata.java deleted file mode 100644 index 486a95fc..00000000 --- a/src/main/java/com/nike/cerberus/domain/EnvironmentMetadata.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.domain; - -import com.amazonaws.regions.Regions; - -/** - * Environment metadata. Everything but the bucket name is immutable. Bucket name isn't known immediately - * if running operation is provisioning it! - */ -public class EnvironmentMetadata { - - private final String name; - - private final String regionName; - - private String bucketName; - - public EnvironmentMetadata(final String name, final String regionName) { - this.name = name; - this.regionName = regionName; - } - - public String getName() { - return name; - } - - public String getRegionName() { - return regionName; - } - - public Regions getRegions() { - return Regions.fromName(regionName); - } - - public String getBucketName() { - return bucketName; - } - - public void setBucketName(String bucketName) { - this.bucketName = bucketName; - } -} diff --git a/src/main/java/com/nike/cerberus/domain/cloudformation/BaseOutputs.java b/src/main/java/com/nike/cerberus/domain/cloudformation/BaseOutputs.java deleted file mode 100644 index 49aa498f..00000000 --- a/src/main/java/com/nike/cerberus/domain/cloudformation/BaseOutputs.java +++ /dev/null @@ -1,440 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.domain.cloudformation; - -/** - * Represents the base stack outputs. - */ -public class BaseOutputs { - - private String vpcId; - - private String gatewayIamRoleArn; - - private String cmsIamRoleArn; - - private String consulIamRoleArn; - - private String vaultIamRoleArn; - - private String cloudFrontLogProcessorLambdaIamRoleArn; - - private String gatewayInstanceProfileName; - - private String cmsInstanceProfileName; - - private String consulInstanceProfileName; - - private String vaultInstanceProfileName; - - private String toolsIngressSgId; - - private String gatewayElbSgId; - - private String gatewayServerSgId; - - private String cmsElbSgId; - - private String cmsSgId; - - private String cmsDbSgId; - - private String vaultServerElbSgId; - - private String vaultClientSgId; - - private String vaultServerSgId; - - private String consulClientSgId; - - private String consulServerSgId; - - private String configFileKeyId; - - private String dashboardBucketName; - - private String dashboardBucketWebsiteUrl; - - private String configBucketName; - - private String configBucketDomainName; - - private String cmsDbId; - - private String cmsDbAddress; - - private String cmsDbPort; - - private String cmsDbJdbcConnectionString; - - private String vpcHostedZoneId; - - private String vpcSubnetIdForAz1; - - private String vpcSubnetIdForAz2; - - private String vpcSubnetIdForAz3; - - private String cmsKmsPolicyId; - - private String subnetCidrBlockForAz1; - - private String subnetCidrBlockForAz2; - - private String subnetCidrBlockForAz3; - - public String getVpcId() { - return vpcId; - } - - public BaseOutputs setVpcId(String vpcId) { - this.vpcId = vpcId; - return this; - } - - public String getGatewayIamRoleArn() { - return gatewayIamRoleArn; - } - - public BaseOutputs setGatewayIamRoleArn(String gatewayIamRoleArn) { - this.gatewayIamRoleArn = gatewayIamRoleArn; - return this; - } - - public String getCmsIamRoleArn() { - return cmsIamRoleArn; - } - - public BaseOutputs setCmsIamRoleArn(String cmsIamRoleArn) { - this.cmsIamRoleArn = cmsIamRoleArn; - return this; - } - - public String getConsulIamRoleArn() { - return consulIamRoleArn; - } - - public BaseOutputs setConsulIamRoleArn(String consulIamRoleArn) { - this.consulIamRoleArn = consulIamRoleArn; - return this; - } - - public String getVaultIamRoleArn() { - return vaultIamRoleArn; - } - - public BaseOutputs setVaultIamRoleArn(String vaultIamRoleArn) { - this.vaultIamRoleArn = vaultIamRoleArn; - return this; - } - - public String getCloudFrontLogProcessorLambdaIamRoleArn() { - return cloudFrontLogProcessorLambdaIamRoleArn; - } - - public void setCloudFrontLogProcessorLambdaIamRoleArn(String cloudFrontLogProcessorLambdaIamRoleArn) { - this.cloudFrontLogProcessorLambdaIamRoleArn = cloudFrontLogProcessorLambdaIamRoleArn; - } - - public String getGatewayInstanceProfileName() { - return gatewayInstanceProfileName; - } - - public BaseOutputs setGatewayInstanceProfileName(String gatewayInstanceProfileName) { - this.gatewayInstanceProfileName = gatewayInstanceProfileName; - return this; - } - - public String getCmsInstanceProfileName() { - return cmsInstanceProfileName; - } - - public BaseOutputs setCmsInstanceProfileName(String cmsInstanceProfileName) { - this.cmsInstanceProfileName = cmsInstanceProfileName; - return this; - } - - public String getConsulInstanceProfileName() { - return consulInstanceProfileName; - } - - public BaseOutputs setConsulInstanceProfileName(String consulInstanceProfileName) { - this.consulInstanceProfileName = consulInstanceProfileName; - return this; - } - - public String getVaultInstanceProfileName() { - return vaultInstanceProfileName; - } - - public BaseOutputs setVaultInstanceProfileName(String vaultInstanceProfileName) { - this.vaultInstanceProfileName = vaultInstanceProfileName; - return this; - } - - public String getToolsIngressSgId() { - return toolsIngressSgId; - } - - public BaseOutputs setToolsIngressSgId(String toolsIngressSgId) { - this.toolsIngressSgId = toolsIngressSgId; - return this; - } - - public String getGatewayElbSgId() { - return gatewayElbSgId; - } - - public BaseOutputs setGatewayElbSgId(String gatewayElbSgId) { - this.gatewayElbSgId = gatewayElbSgId; - return this; - } - - public String getGatewayServerSgId() { - return gatewayServerSgId; - } - - public BaseOutputs setGatewayServerSgId(String gatewayServerSgId) { - this.gatewayServerSgId = gatewayServerSgId; - return this; - } - - public String getCmsElbSgId() { - return cmsElbSgId; - } - - public BaseOutputs setCmsElbSgId(String cmsElbSgId) { - this.cmsElbSgId = cmsElbSgId; - return this; - } - - public String getCmsSgId() { - return cmsSgId; - } - - public BaseOutputs setCmsSgId(String cmsSgId) { - this.cmsSgId = cmsSgId; - return this; - } - - public String getCmsDbSgId() { - return cmsDbSgId; - } - - public BaseOutputs setCmsDbSgId(String cmsDbSgId) { - this.cmsDbSgId = cmsDbSgId; - return this; - } - - public String getVaultServerElbSgId() { - return vaultServerElbSgId; - } - - public BaseOutputs setVaultServerElbSgId(String vaultServerElbSgId) { - this.vaultServerElbSgId = vaultServerElbSgId; - return this; - } - - public String getVaultClientSgId() { - return vaultClientSgId; - } - - public BaseOutputs setVaultClientSgId(String vaultClientSgId) { - this.vaultClientSgId = vaultClientSgId; - return this; - } - - public String getVaultServerSgId() { - return vaultServerSgId; - } - - public BaseOutputs setVaultServerSgId(String vaultServerSgId) { - this.vaultServerSgId = vaultServerSgId; - return this; - } - - public String getConsulClientSgId() { - return consulClientSgId; - } - - public BaseOutputs setConsulClientSgId(String consulClientSgId) { - this.consulClientSgId = consulClientSgId; - return this; - } - - public String getConsulServerSgId() { - return consulServerSgId; - } - - public BaseOutputs setConsulServerSgId(String consulServerSgId) { - this.consulServerSgId = consulServerSgId; - return this; - } - - public String getConfigFileKeyId() { - return configFileKeyId; - } - - public BaseOutputs setConfigFileKeyId(String configFileKeyId) { - this.configFileKeyId = configFileKeyId; - return this; - } - - public String getDashboardBucketName() { - return dashboardBucketName; - } - - public BaseOutputs setDashboardBucketName(String dashboardBucketName) { - this.dashboardBucketName = dashboardBucketName; - return this; - } - - public String getDashboardBucketWebsiteUrl() { - return dashboardBucketWebsiteUrl; - } - - public BaseOutputs setDashboardBucketWebsiteUrl(String dashboardBucketWebsiteUrl) { - this.dashboardBucketWebsiteUrl = dashboardBucketWebsiteUrl; - return this; - } - - public String getConfigBucketName() { - return configBucketName; - } - - public BaseOutputs setConfigBucketName(String configBucketName) { - this.configBucketName = configBucketName; - return this; - } - - public String getConfigBucketDomainName() { - return configBucketDomainName; - } - - public BaseOutputs setConfigBucketDomainName(String configBucketDomainName) { - this.configBucketDomainName = configBucketDomainName; - return this; - } - - public String getCmsDbId() { - return cmsDbId; - } - - public BaseOutputs setCmsDbId(String cmsDbId) { - this.cmsDbId = cmsDbId; - return this; - } - - public String getCmsDbAddress() { - return cmsDbAddress; - } - - public BaseOutputs setCmsDbAddress(String cmsDbAddress) { - this.cmsDbAddress = cmsDbAddress; - return this; - } - - public String getCmsDbPort() { - return cmsDbPort; - } - - public BaseOutputs setCmsDbPort(String cmsDbPort) { - this.cmsDbPort = cmsDbPort; - return this; - } - - public String getCmsDbJdbcConnectionString() { - return cmsDbJdbcConnectionString; - } - - public BaseOutputs setCmsDbJdbcConnectionString(String cmsDbJdbcConnectionString) { - this.cmsDbJdbcConnectionString = cmsDbJdbcConnectionString; - return this; - } - - public String getVpcHostedZoneId() { - return vpcHostedZoneId; - } - - public BaseOutputs setVpcHostedZoneId(String vpcHostedZoneId) { - this.vpcHostedZoneId = vpcHostedZoneId; - return this; - } - - public String getVpcSubnetIdForAz1() { - return vpcSubnetIdForAz1; - } - - public BaseOutputs setVpcSubnetIdForAz1(String vpcSubnetIdForAz1) { - this.vpcSubnetIdForAz1 = vpcSubnetIdForAz1; - return this; - } - - public String getVpcSubnetIdForAz2() { - return vpcSubnetIdForAz2; - } - - public BaseOutputs setVpcSubnetIdForAz2(String vpcSubnetIdForAz2) { - this.vpcSubnetIdForAz2 = vpcSubnetIdForAz2; - return this; - } - - public String getVpcSubnetIdForAz3() { - return vpcSubnetIdForAz3; - } - - public BaseOutputs setVpcSubnetIdForAz3(String vpcSubnetIdForAz3) { - this.vpcSubnetIdForAz3 = vpcSubnetIdForAz3; - return this; - } - - public String getCmsKmsPolicyId() { - return cmsKmsPolicyId; - } - - public BaseOutputs setCmsKmsPolicyId(String cmsKmsPolicyId) { - this.cmsKmsPolicyId = cmsKmsPolicyId; - return this; - } - - public String getSubnetCidrBlockForAz1() { - return subnetCidrBlockForAz1; - } - - public BaseOutputs setSubnetCidrBlockForAz1(String subnetCidrBlockForAz1) { - this.subnetCidrBlockForAz1 = subnetCidrBlockForAz1; - return this; - } - - public String getSubnetCidrBlockForAz2() { - return subnetCidrBlockForAz2; - } - - public BaseOutputs setSubnetCidrBlockForAz2(String subnetCidrBlockForAz2) { - this.subnetCidrBlockForAz2 = subnetCidrBlockForAz2; - return this; - } - - public String getSubnetCidrBlockForAz3() { - return subnetCidrBlockForAz3; - } - - public BaseOutputs setSubnetCidrBlockForAz3(String subnetCidrBlockForAz3) { - this.subnetCidrBlockForAz3 = subnetCidrBlockForAz3; - return this; - } -} diff --git a/src/main/java/com/nike/cerberus/domain/cloudformation/BaseParameters.java b/src/main/java/com/nike/cerberus/domain/cloudformation/BaseParameters.java deleted file mode 100644 index 93d57227..00000000 --- a/src/main/java/com/nike/cerberus/domain/cloudformation/BaseParameters.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.domain.cloudformation; - -import com.fasterxml.jackson.annotation.JsonUnwrapped; - -/** - * Represents the base stack inputs. - */ -public class BaseParameters implements TagParameters { - - private String accountAdminArn; - - private String az1; - - private String az2; - - private String az3; - - private String cmsDbAllocatedStorage; - - private String cmsDbInstanceSize; - - private String cmsDbName; - - private String cmsDbMasterUsername; - - private String cmsDbMasterPassword; - - private Integer cmsDbPort; - - private String vpcHostedZoneName; - - @JsonUnwrapped - private TagParametersDelegate tagParameters = new TagParametersDelegate(); - - public String getAccountAdminArn() { - return accountAdminArn; - } - - public BaseParameters setAccountAdminArn(String accountAdminArn) { - this.accountAdminArn = accountAdminArn; - return this; - } - - public String getAz1() { - return az1; - } - - public BaseParameters setAz1(String az1) { - this.az1 = az1; - return this; - } - - public String getAz2() { - return az2; - } - - public BaseParameters setAz2(String az2) { - this.az2 = az2; - return this; - } - - public String getAz3() { - return az3; - } - - public BaseParameters setAz3(String az3) { - this.az3 = az3; - return this; - } - - public String getCmsDbAllocatedStorage() { - return cmsDbAllocatedStorage; - } - - public BaseParameters setCmsDbAllocatedStorage(String cmsDbAllocatedStorage) { - this.cmsDbAllocatedStorage = cmsDbAllocatedStorage; - return this; - } - - public String getCmsDbInstanceSize() { - return cmsDbInstanceSize; - } - - public BaseParameters setCmsDbInstanceSize(String cmsDbInstanceSize) { - this.cmsDbInstanceSize = cmsDbInstanceSize; - return this; - } - - public String getCmsDbName() { - return cmsDbName; - } - - public BaseParameters setCmsDbName(String cmsDbName) { - this.cmsDbName = cmsDbName; - return this; - } - - public String getCmsDbMasterUsername() { - return cmsDbMasterUsername; - } - - public BaseParameters setCmsDbMasterUsername(String cmsDbMasterUsername) { - this.cmsDbMasterUsername = cmsDbMasterUsername; - return this; - } - - public String getCmsDbMasterPassword() { - return cmsDbMasterPassword; - } - - public BaseParameters setCmsDbMasterPassword(String cmsDbMasterPassword) { - this.cmsDbMasterPassword = cmsDbMasterPassword; - return this; - } - - public Integer getCmsDbPort() { - return cmsDbPort; - } - - public BaseParameters setCmsDbPort(Integer cmsDbPort) { - this.cmsDbPort = cmsDbPort; - return this; - } - - public String getVpcHostedZoneName() { - return vpcHostedZoneName; - } - - public BaseParameters setVpcHostedZoneName(String vpcHostedZoneName) { - this.vpcHostedZoneName = vpcHostedZoneName; - return this; - } - - @Override - public TagParametersDelegate getTagParameters() { - return tagParameters; - } - - public BaseParameters setTagParameters(TagParametersDelegate tagParameters) { - this.tagParameters = tagParameters; - return this; - } -} diff --git a/src/main/java/com/nike/cerberus/domain/cloudformation/CmsOutputs.java b/src/main/java/com/nike/cerberus/domain/cloudformation/CmsOutputs.java index 6a628ec6..054c2a5b 100644 --- a/src/main/java/com/nike/cerberus/domain/cloudformation/CmsOutputs.java +++ b/src/main/java/com/nike/cerberus/domain/cloudformation/CmsOutputs.java @@ -25,15 +25,7 @@ public class CmsOutputs { private String launchConfigurationLogicalId; - private String elbLogicalId; - - private String elbCanonicalHostedZoneNameId; - - private String elbDnsName; - - private String elbSourceSecurityGroupName; - - private String elbSourceSecurityGroupOwnerAlias; + private String cmsInstanceProfileName; public String getAutoscalingGroupLogicalId() { return autoscalingGroupLogicalId; @@ -53,48 +45,12 @@ public CmsOutputs setLaunchConfigurationLogicalId(String launchConfigurationLogi return this; } - public String getElbLogicalId() { - return elbLogicalId; - } - - public CmsOutputs setElbLogicalId(String elbLogicalId) { - this.elbLogicalId = elbLogicalId; - return this; - } - - public String getElbCanonicalHostedZoneNameId() { - return elbCanonicalHostedZoneNameId; - } - - public CmsOutputs setElbCanonicalHostedZoneNameId(String elbCanonicalHostedZoneNameId) { - this.elbCanonicalHostedZoneNameId = elbCanonicalHostedZoneNameId; - return this; - } - - public String getElbDnsName() { - return elbDnsName; - } - - public CmsOutputs setElbDnsName(String elbDnsName) { - this.elbDnsName = elbDnsName; - return this; - } - - public String getElbSourceSecurityGroupName() { - return elbSourceSecurityGroupName; - } - - public CmsOutputs setElbSourceSecurityGroupName(String elbSourceSecurityGroupName) { - this.elbSourceSecurityGroupName = elbSourceSecurityGroupName; - return this; - } - - public String getElbSourceSecurityGroupOwnerAlias() { - return elbSourceSecurityGroupOwnerAlias; + public String getCmsInstanceProfileName() { + return cmsInstanceProfileName; } - public CmsOutputs setElbSourceSecurityGroupOwnerAlias(String elbSourceSecurityGroupOwnerAlias) { - this.elbSourceSecurityGroupOwnerAlias = elbSourceSecurityGroupOwnerAlias; + public CmsOutputs setCmsInstanceProfileName(String cmsInstanceProfileName) { + this.cmsInstanceProfileName = cmsInstanceProfileName; return this; } } diff --git a/src/main/java/com/nike/cerberus/domain/cloudformation/CmsParameters.java b/src/main/java/com/nike/cerberus/domain/cloudformation/CmsParameters.java index dec33c65..af527bc0 100644 --- a/src/main/java/com/nike/cerberus/domain/cloudformation/CmsParameters.java +++ b/src/main/java/com/nike/cerberus/domain/cloudformation/CmsParameters.java @@ -23,15 +23,11 @@ */ public class CmsParameters implements LaunchConfigParameters { - private String vpcId; + private String baseStackName; - private String instanceProfileName; + private String loadBalancerStackName; - private String cmsElbSgId; - - private String cmsSgId; - - private String toolsIngressSgId; + private String sgStackName; private String vpcSubnetIdForAz1; @@ -39,61 +35,33 @@ public class CmsParameters implements LaunchConfigParameters { private String vpcSubnetIdForAz3; - private String hostedZoneId; - - private String cname; - - @JsonUnwrapped - private SslConfigParametersDelegate sslConfigParameters = new SslConfigParametersDelegate(); - @JsonUnwrapped private LaunchConfigParametersDelegate launchConfigParameters = new LaunchConfigParametersDelegate(); - @JsonUnwrapped - private TagParametersDelegate tagParameters = new TagParametersDelegate(); - - public String getVpcId() { - return vpcId; + public String getBaseStackName() { + return baseStackName; } - public CmsParameters setVpcId(String vpcId) { - this.vpcId = vpcId; + public CmsParameters setBaseStackName(String baseStackName) { + this.baseStackName = baseStackName; return this; } - public String getInstanceProfileName() { - return instanceProfileName; + public String getLoadBalancerStackName() { + return loadBalancerStackName; } - public CmsParameters setInstanceProfileName(String instanceProfileName) { - this.instanceProfileName = instanceProfileName; + public CmsParameters setLoadBalancerStackName(String loadBalancerStackName) { + this.loadBalancerStackName = loadBalancerStackName; return this; } - public String getCmsElbSgId() { - return cmsElbSgId; + public String getSgStackName() { + return sgStackName; } - public CmsParameters setCmsElbSgId(String cmsElbSgId) { - this.cmsElbSgId = cmsElbSgId; - return this; - } - - public String getCmsSgId() { - return cmsSgId; - } - - public CmsParameters setCmsSgId(String cmsSgId) { - this.cmsSgId = cmsSgId; - return this; - } - - public String getToolsIngressSgId() { - return toolsIngressSgId; - } - - public CmsParameters setToolsIngressSgId(String toolsIngressSgId) { - this.toolsIngressSgId = toolsIngressSgId; + public CmsParameters setSgStackName(String sgStackName) { + this.sgStackName = sgStackName; return this; } @@ -124,34 +92,6 @@ public CmsParameters setVpcSubnetIdForAz3(String vpcSubnetIdForAz3) { return this; } - public String getHostedZoneId() { - return hostedZoneId; - } - - public CmsParameters setHostedZoneId(String hostedZoneId) { - this.hostedZoneId = hostedZoneId; - return this; - } - - public String getCname() { - return cname; - } - - public CmsParameters setCname(String cname) { - this.cname = cname; - return this; - } - - @Override - public SslConfigParametersDelegate getSslConfigParameters() { - return sslConfigParameters; - } - - public CmsParameters setSslConfigParameters(SslConfigParametersDelegate sslConfigParameters) { - this.sslConfigParameters = sslConfigParameters; - return this; - } - public LaunchConfigParametersDelegate getLaunchConfigParameters() { return launchConfigParameters; } @@ -160,14 +100,4 @@ public CmsParameters setLaunchConfigParameters(LaunchConfigParametersDelegate la this.launchConfigParameters = launchConfigParameters; return this; } - - @Override - public TagParametersDelegate getTagParameters() { - return tagParameters; - } - - public CmsParameters setTagParameters(TagParametersDelegate tagParameters) { - this.tagParameters = tagParameters; - return this; - } } diff --git a/src/main/java/com/nike/cerberus/domain/cloudformation/ConfigOutputs.java b/src/main/java/com/nike/cerberus/domain/cloudformation/ConfigOutputs.java new file mode 100644 index 00000000..5424822a --- /dev/null +++ b/src/main/java/com/nike/cerberus/domain/cloudformation/ConfigOutputs.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.domain.cloudformation; + +public class ConfigOutputs { + + private String configBucketDomainName; + + private String configBucketName; + + private String configCmkArn; + + private String managementServiceCmkArn; + + public String getConfigBucketDomainName() { + return configBucketDomainName; + } + + public ConfigOutputs setConfigBucketDomainName(String configBucketDomainName) { + this.configBucketDomainName = configBucketDomainName; + return this; + } + + public String getConfigBucketName() { + return configBucketName; + } + + public ConfigOutputs setConfigBucketName(String configBucketName) { + this.configBucketName = configBucketName; + return this; + } + + public String getConfigCmkArn() { + return configCmkArn; + } + + public void setConfigCmkArn(String configCmkArn) { + this.configCmkArn = configCmkArn; + } + + public String getManagementServiceCmkArn() { + return managementServiceCmkArn; + } + + public void setManagementServiceCmkArn(String managementServiceCmkArn) { + this.managementServiceCmkArn = managementServiceCmkArn; + } +} diff --git a/src/main/java/com/nike/cerberus/domain/cloudformation/ConfigParameters.java b/src/main/java/com/nike/cerberus/domain/cloudformation/ConfigParameters.java new file mode 100644 index 00000000..97764400 --- /dev/null +++ b/src/main/java/com/nike/cerberus/domain/cloudformation/ConfigParameters.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.domain.cloudformation; + +/** + * Represents the base stack inputs. + */ +public class ConfigParameters { + + private String accountAdminArn; + + private String cmsIamRoleArn; + + private String environmentName; + + public String getAccountAdminArn() { + return accountAdminArn; + } + + public ConfigParameters setAccountAdminArn(String accountAdminArn) { + this.accountAdminArn = accountAdminArn; + return this; + } + + public String getCmsIamRoleArn() { + return cmsIamRoleArn; + } + + public ConfigParameters setCmsIamRoleArn(String cmsIamRoleArn) { + this.cmsIamRoleArn = cmsIamRoleArn; + return this; + } + + public String getEnvironmentName() { + return environmentName; + } + + public ConfigParameters setEnvironmentName(String environmentName) { + this.environmentName = environmentName; + return this; + } +} diff --git a/src/main/java/com/nike/cerberus/domain/cloudformation/ConsulOutputs.java b/src/main/java/com/nike/cerberus/domain/cloudformation/ConsulOutputs.java deleted file mode 100644 index ff70c334..00000000 --- a/src/main/java/com/nike/cerberus/domain/cloudformation/ConsulOutputs.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.domain.cloudformation; - -/** - * Consul CloudFormation stack outputs - */ -public class ConsulOutputs { - - private String autoscalingGroupLogicalId; - - private String launchConfigurationLogicalId; - - public String getAutoscalingGroupLogicalId() { - return autoscalingGroupLogicalId; - } - - public ConsulOutputs setAutoscalingGroupLogicalId(String autoscalingGroupLogicalId) { - this.autoscalingGroupLogicalId = autoscalingGroupLogicalId; - return this; - } - - public String getLaunchConfigurationLogicalId() { - return launchConfigurationLogicalId; - } - - public ConsulOutputs setLaunchConfigurationLogicalId(String launchConfigurationLogicalId) { - this.launchConfigurationLogicalId = launchConfigurationLogicalId; - return this; - } -} diff --git a/src/main/java/com/nike/cerberus/domain/cloudformation/ConsulParameters.java b/src/main/java/com/nike/cerberus/domain/cloudformation/ConsulParameters.java deleted file mode 100644 index 515e38aa..00000000 --- a/src/main/java/com/nike/cerberus/domain/cloudformation/ConsulParameters.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.domain.cloudformation; - -import com.fasterxml.jackson.annotation.JsonUnwrapped; - -/** - * Consul CloudFormation stack inputs - */ -public class ConsulParameters implements LaunchConfigParameters { - - private String instanceProfileName; - - private String consulClientSgId; - - private String consulServerSgId; - - private String toolsIngressSgId; - - private String vpcId; - - private String vpcSubnetIdForAz1; - - private String vpcSubnetIdForAz2; - - private String vpcSubnetIdForAz3; - - @JsonUnwrapped - private SslConfigParametersDelegate sslConfigParameters = new SslConfigParametersDelegate(); - - @JsonUnwrapped - private LaunchConfigParametersDelegate launchConfigParameters = new LaunchConfigParametersDelegate(); - - @JsonUnwrapped - private TagParametersDelegate tagParameters = new TagParametersDelegate(); - - public String getInstanceProfileName() { - return instanceProfileName; - } - - public ConsulParameters setInstanceProfileName(String instanceProfileName) { - this.instanceProfileName = instanceProfileName; - return this; - } - - public String getConsulClientSgId() { - return consulClientSgId; - } - - public ConsulParameters setConsulClientSgId(String consulClientSgId) { - this.consulClientSgId = consulClientSgId; - return this; - } - - public String getConsulServerSgId() { - return consulServerSgId; - } - - public ConsulParameters setConsulServerSgId(String consulServerSgId) { - this.consulServerSgId = consulServerSgId; - return this; - } - - public String getToolsIngressSgId() { - return toolsIngressSgId; - } - - public ConsulParameters setToolsIngressSgId(String toolsIngressSgId) { - this.toolsIngressSgId = toolsIngressSgId; - return this; - } - - public String getVpcId() { - return vpcId; - } - - public ConsulParameters setVpcId(String vpcId) { - this.vpcId = vpcId; - return this; - } - - public String getVpcSubnetIdForAz1() { - return vpcSubnetIdForAz1; - } - - public ConsulParameters setVpcSubnetIdForAz1(String vpcSubnetIdForAz1) { - this.vpcSubnetIdForAz1 = vpcSubnetIdForAz1; - return this; - } - - public String getVpcSubnetIdForAz2() { - return vpcSubnetIdForAz2; - } - - public ConsulParameters setVpcSubnetIdForAz2(String vpcSubnetIdForAz2) { - this.vpcSubnetIdForAz2 = vpcSubnetIdForAz2; - return this; - } - - public String getVpcSubnetIdForAz3() { - return vpcSubnetIdForAz3; - } - - public ConsulParameters setVpcSubnetIdForAz3(String vpcSubnetIdForAz3) { - this.vpcSubnetIdForAz3 = vpcSubnetIdForAz3; - return this; - } - - @Override - public SslConfigParametersDelegate getSslConfigParameters() { - return sslConfigParameters; - } - - @Override - public LaunchConfigParametersDelegate getLaunchConfigParameters() { - return launchConfigParameters; - } - - public ConsulParameters setLaunchConfigParameters(LaunchConfigParametersDelegate launchConfigParameters) { - this.launchConfigParameters = launchConfigParameters; - return this; - } - - @Override - public TagParametersDelegate getTagParameters() { - return tagParameters; - } - - public ConsulParameters setTagParameters(TagParametersDelegate tagParameters) { - this.tagParameters = tagParameters; - return this; - } -} diff --git a/src/main/java/com/nike/cerberus/domain/cloudformation/DatabaseOutputs.java b/src/main/java/com/nike/cerberus/domain/cloudformation/DatabaseOutputs.java new file mode 100644 index 00000000..144e9076 --- /dev/null +++ b/src/main/java/com/nike/cerberus/domain/cloudformation/DatabaseOutputs.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.domain.cloudformation; + +/** + * Represents the database stack outputs. + */ +public class DatabaseOutputs { + + private String cmsDbAddress; + + private String cmsDbInstanceId1; + + private String cmsDbInstanceId2; + + private String cmsDbJdbcConnectionString; + + public String getCmsDbAddress() { + return cmsDbAddress; + } + + public DatabaseOutputs setCmsDbAddress(String cmsDbAddress) { + this.cmsDbAddress = cmsDbAddress; + return this; + } + + public String getCmsDbInstanceId1() { + return cmsDbInstanceId1; + } + + public DatabaseOutputs setCmsDbInstanceId1(String cmsDbInstanceId1) { + this.cmsDbInstanceId1 = cmsDbInstanceId1; + return this; + } + + public String getCmsDbInstanceId2() { + return cmsDbInstanceId2; + } + + public DatabaseOutputs setCmsDbInstanceId2(String cmsDbInstanceId2) { + this.cmsDbInstanceId2 = cmsDbInstanceId2; + return this; + } + + public String getCmsDbJdbcConnectionString() { + return cmsDbJdbcConnectionString; + } + + public DatabaseOutputs setCmsDbJdbcConnectionString(String cmsDbJdbcConnectionString) { + this.cmsDbJdbcConnectionString = cmsDbJdbcConnectionString; + return this; + } +} diff --git a/src/main/java/com/nike/cerberus/domain/cloudformation/DatabaseParameters.java b/src/main/java/com/nike/cerberus/domain/cloudformation/DatabaseParameters.java new file mode 100644 index 00000000..aa22373b --- /dev/null +++ b/src/main/java/com/nike/cerberus/domain/cloudformation/DatabaseParameters.java @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.domain.cloudformation; + +/** + * Represents the database stack inputs. + */ +public class DatabaseParameters { + + private String cmsDbInstanceAz1; + + private String cmsDbInstanceAz2; + + private String cmsDbInstanceAz3; + + private String cmsDbInstanceClass; + + private String cmsDbMasterPassword; + + private String cmsDbMasterUsername; + + private String cmsDbName; + + private String sgStackName; + + private String vpcSubnetIdForAz1; + + private String vpcSubnetIdForAz2; + + private String vpcSubnetIdForAz3; + + private String snapshotIdentifier; + + private String vpcInternalBaseDomainName; + + private String vpcInternalHostedZoneId; + + public String getCmsDbInstanceAz1() { + return cmsDbInstanceAz1; + } + + public DatabaseParameters setCmsDbInstanceAz1(String cmsDbInstanceAz1) { + this.cmsDbInstanceAz1 = cmsDbInstanceAz1; + return this; + } + + public String getCmsDbInstanceAz2() { + return cmsDbInstanceAz2; + } + + public DatabaseParameters setCmsDbInstanceAz2(String cmsDbInstanceAz2) { + this.cmsDbInstanceAz2 = cmsDbInstanceAz2; + return this; + } + + public String getCmsDbInstanceAz3() { + return cmsDbInstanceAz3; + } + + public DatabaseParameters setCmsDbInstanceAz3(String cmsDbInstanceAz3) { + this.cmsDbInstanceAz3 = cmsDbInstanceAz3; + return this; + } + + public String getCmsDbInstanceClass() { + return cmsDbInstanceClass; + } + + public DatabaseParameters setCmsDbInstanceClass(String cmsDbInstanceClass) { + this.cmsDbInstanceClass = cmsDbInstanceClass; + return this; + } + + public String getCmsDbMasterPassword() { + return cmsDbMasterPassword; + } + + public DatabaseParameters setCmsDbMasterPassword(String cmsDbMasterPassword) { + this.cmsDbMasterPassword = cmsDbMasterPassword; + return this; + } + + public String getCmsDbMasterUsername() { + return cmsDbMasterUsername; + } + + public DatabaseParameters setCmsDbMasterUsername(String cmsDbMasterUsername) { + this.cmsDbMasterUsername = cmsDbMasterUsername; + return this; + } + + public String getCmsDbName() { + return cmsDbName; + } + + public DatabaseParameters setCmsDbName(String cmsDbName) { + this.cmsDbName = cmsDbName; + return this; + } + + public String getSgStackName() { + return sgStackName; + } + + public DatabaseParameters setSgStackName(String sgStackName) { + this.sgStackName = sgStackName; + return this; + } + + public String getVpcSubnetIdForAz1() { + return vpcSubnetIdForAz1; + } + + public DatabaseParameters setVpcSubnetIdForAz1(String vpcSubnetIdForAz1) { + this.vpcSubnetIdForAz1 = vpcSubnetIdForAz1; + return this; + } + + public String getVpcSubnetIdForAz2() { + return vpcSubnetIdForAz2; + } + + public DatabaseParameters setVpcSubnetIdForAz2(String vpcSubnetIdForAz2) { + this.vpcSubnetIdForAz2 = vpcSubnetIdForAz2; + return this; + } + + public String getVpcSubnetIdForAz3() { + return vpcSubnetIdForAz3; + } + + public DatabaseParameters setVpcSubnetIdForAz3(String vpcSubnetIdForAz3) { + this.vpcSubnetIdForAz3 = vpcSubnetIdForAz3; + return this; + } + + public String getSnapshotIdentifier() { + return snapshotIdentifier; + } + + public DatabaseParameters setSnapshotIdentifier(String snapshotIdentifier) { + this.snapshotIdentifier = snapshotIdentifier; + return this; + } + + public String getVpcInternalBaseDomainName() { + return vpcInternalBaseDomainName; + } + + public DatabaseParameters setVpcInternalBaseDomainName(String vpcInternalBaseDomainName) { + this.vpcInternalBaseDomainName = vpcInternalBaseDomainName; + return this; + } + + public String getVpcInternalHostedZoneId() { + return vpcInternalHostedZoneId; + } + + public DatabaseParameters setVpcInternalHostedZoneId(String vpcInternalHostedZoneId) { + this.vpcInternalHostedZoneId = vpcInternalHostedZoneId; + return this; + } +} diff --git a/src/main/java/com/nike/cerberus/domain/cloudformation/GatewayOutputs.java b/src/main/java/com/nike/cerberus/domain/cloudformation/GatewayOutputs.java deleted file mode 100644 index 6fe4be4e..00000000 --- a/src/main/java/com/nike/cerberus/domain/cloudformation/GatewayOutputs.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.domain.cloudformation; - -/** - * Gateway CloudFormation stack outputs - */ -public class GatewayOutputs { - - private String autoscalingGroupLogicalId; - - private String launchConfigurationLogicalId; - - private String elbLogicalId; - - private String elbCanonicalHostedZoneName; - - private String elbCanonicalHostedZoneNameId; - - private String elbDnsName; - - private String elbSourceSecurityGroupName; - - private String elbSourceSecurityGroupOwnerAlias; - - private String cloudFrontDistributionId; - - private String cloudFrontDistributionDomainName; - - private String cloudFrontAccessLogBucket; - - private String whiteListIPSetID; - - private String manualBlockIPSetID; - - private String autoBlockIPSetID; - - public String getAutoscalingGroupLogicalId() { - return autoscalingGroupLogicalId; - } - - public GatewayOutputs setAutoscalingGroupLogicalId(String autoscalingGroupLogicalId) { - this.autoscalingGroupLogicalId = autoscalingGroupLogicalId; - return this; - } - - public String getLaunchConfigurationLogicalId() { - return launchConfigurationLogicalId; - } - - public GatewayOutputs setLaunchConfigurationLogicalId(String launchConfigurationLogicalId) { - this.launchConfigurationLogicalId = launchConfigurationLogicalId; - return this; - } - - public String getElbLogicalId() { - return elbLogicalId; - } - - public GatewayOutputs setElbLogicalId(String elbLogicalId) { - this.elbLogicalId = elbLogicalId; - return this; - } - - public String getElbCanonicalHostedZoneName() { - return elbCanonicalHostedZoneName; - } - - public GatewayOutputs setElbCanonicalHostedZoneName(String elbCanonicalHostedZoneName) { - this.elbCanonicalHostedZoneName = elbCanonicalHostedZoneName; - return this; - } - - public String getElbCanonicalHostedZoneNameId() { - return elbCanonicalHostedZoneNameId; - } - - public GatewayOutputs setElbCanonicalHostedZoneNameId(String elbCanonicalHostedZoneNameId) { - this.elbCanonicalHostedZoneNameId = elbCanonicalHostedZoneNameId; - return this; - } - - public String getElbDnsName() { - return elbDnsName; - } - - public GatewayOutputs setElbDnsName(String elbDnsName) { - this.elbDnsName = elbDnsName; - return this; - } - - public String getElbSourceSecurityGroupName() { - return elbSourceSecurityGroupName; - } - - public GatewayOutputs setElbSourceSecurityGroupName(String elbSourceSecurityGroupName) { - this.elbSourceSecurityGroupName = elbSourceSecurityGroupName; - return this; - } - - public String getElbSourceSecurityGroupOwnerAlias() { - return elbSourceSecurityGroupOwnerAlias; - } - - public GatewayOutputs setElbSourceSecurityGroupOwnerAlias(String elbSourceSecurityGroupOwnerAlias) { - this.elbSourceSecurityGroupOwnerAlias = elbSourceSecurityGroupOwnerAlias; - return this; - } - - public String getCloudFrontDistributionId() { - return cloudFrontDistributionId; - } - - public GatewayOutputs setCloudFrontDistributionId(String cloudFrontDistributionId) { - this.cloudFrontDistributionId = cloudFrontDistributionId; - return this; - } - - public String getCloudFrontDistributionDomainName() { - return cloudFrontDistributionDomainName; - } - - public GatewayOutputs setCloudFrontDistributionDomainName(String cloudFrontDistributionDomainName) { - this.cloudFrontDistributionDomainName = cloudFrontDistributionDomainName; - return this; - } - - public String getCloudFrontAccessLogBucket() { - return cloudFrontAccessLogBucket; - } - - public GatewayOutputs setCloudFrontAccessLogBucket(String cloudFrontAccessLogBucket) { - this.cloudFrontAccessLogBucket = cloudFrontAccessLogBucket; - return this; - } - - public String getWhiteListIPSetID() { - return whiteListIPSetID; - } - - public GatewayOutputs setWhiteListIPSetID(String whiteListIPSetID) { - this.whiteListIPSetID = whiteListIPSetID; - return this; - } - - public String getManualBlockIPSetID() { - return manualBlockIPSetID; - } - - public GatewayOutputs setManualBlockIPSetID(String manualBlockIPSetID) { - this.manualBlockIPSetID = manualBlockIPSetID; - return this; - } - - public String getAutoBlockIPSetID() { - return autoBlockIPSetID; - } - - public GatewayOutputs setAutoBlockIPSetID(String autoBlockIPSetID) { - this.autoBlockIPSetID = autoBlockIPSetID; - return this; - } -} diff --git a/src/main/java/com/nike/cerberus/domain/cloudformation/GatewayParameters.java b/src/main/java/com/nike/cerberus/domain/cloudformation/GatewayParameters.java index 7c11efcc..45ecc87e 100644 --- a/src/main/java/com/nike/cerberus/domain/cloudformation/GatewayParameters.java +++ b/src/main/java/com/nike/cerberus/domain/cloudformation/GatewayParameters.java @@ -148,7 +148,6 @@ public GatewayParameters setHostname(String hostname) { return this; } - @Override public SslConfigParametersDelegate getSslConfigParameters() { return sslConfigParameters; } @@ -168,7 +167,6 @@ public GatewayParameters setLaunchConfigParameters(LaunchConfigParametersDelegat return this; } - @Override public TagParametersDelegate getTagParameters() { return tagParameters; } diff --git a/src/main/java/com/nike/cerberus/domain/configuration/GatewayConfiguration.java b/src/main/java/com/nike/cerberus/domain/cloudformation/IamRolesOutputs.java similarity index 52% rename from src/main/java/com/nike/cerberus/domain/configuration/GatewayConfiguration.java rename to src/main/java/com/nike/cerberus/domain/cloudformation/IamRolesOutputs.java index c20f4e3e..7a2eab1b 100644 --- a/src/main/java/com/nike/cerberus/domain/configuration/GatewayConfiguration.java +++ b/src/main/java/com/nike/cerberus/domain/cloudformation/IamRolesOutputs.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Nike, Inc. + * Copyright (c) 2017 Nike, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,32 +14,32 @@ * limitations under the License. */ -package com.nike.cerberus.domain.configuration; +package com.nike.cerberus.domain.cloudformation; /** - * Gateway configuration POJO. + * Represents the base stack outputs. */ -public class GatewayConfiguration { +public class IamRolesOutputs { - private String siteConfig; + private String cmsIamRoleArn; - private String globalConfig; + private String cmsIamRoleName; - public String getSiteConfig() { - return siteConfig; + public String getCmsIamRoleArn() { + return cmsIamRoleArn; } - public GatewayConfiguration setSiteConfig(String siteConfig) { - this.siteConfig = siteConfig; + public IamRolesOutputs setCmsIamRoleArn(String cmsIamRoleArn) { + this.cmsIamRoleArn = cmsIamRoleArn; return this; } - public String getGlobalConfig() { - return globalConfig; + public String getCmsIamRoleName() { + return cmsIamRoleName; } - public GatewayConfiguration setGlobalConfig(String globalConfig) { - this.globalConfig = globalConfig; + public IamRolesOutputs setCmsIamRoleName(String cmsIamRoleName) { + this.cmsIamRoleName = cmsIamRoleName; return this; } } diff --git a/src/main/java/com/nike/cerberus/domain/cloudformation/LambdaInputParameters.java b/src/main/java/com/nike/cerberus/domain/cloudformation/LambdaInputParameters.java deleted file mode 100644 index 3ce83da4..00000000 --- a/src/main/java/com/nike/cerberus/domain/cloudformation/LambdaInputParameters.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.domain.cloudformation; - -public class LambdaInputParameters { - - private String lambdaJarS3Bucket; - private String lambdaJarS3Key; - private String instanceProfileArn; - private String vaultIngressSecurityGroup; - private String vpcSubnetIdForAz1; - private String vpcSubnetIdForAz2; - private String vpcSubnetIdForAz3; - private String tagName; - private String tagEmail; - private String tagCostcenter; - - public String getLambdaJarS3Bucket() { - return lambdaJarS3Bucket; - } - - public LambdaInputParameters setLambdaJarS3Bucket(String lambdaJarS3Bucket) { - this.lambdaJarS3Bucket = lambdaJarS3Bucket; - return this; - } - - public String getLambdaJarS3Key() { - return lambdaJarS3Key; - } - - public LambdaInputParameters setLambdaJarS3Key(String lambdaJarS3Key) { - this.lambdaJarS3Key = lambdaJarS3Key; - return this; - } - - public String getInstanceProfileArn() { - return instanceProfileArn; - } - - public LambdaInputParameters setInstanceProfileArn(String instanceProfileArn) { - this.instanceProfileArn = instanceProfileArn; - return this; - } - - public String getVaultIngressSecurityGroup() { - return vaultIngressSecurityGroup; - } - - public LambdaInputParameters setVaultIngressSecurityGroup(String vaultIngressSecurityGroup) { - this.vaultIngressSecurityGroup = vaultIngressSecurityGroup; - return this; - } - - public String getVpcSubnetIdForAz1() { - return vpcSubnetIdForAz1; - } - - public LambdaInputParameters setVpcSubnetIdForAz1(String vpcSubnetIdForAz1) { - this.vpcSubnetIdForAz1 = vpcSubnetIdForAz1; - return this; - } - - public String getVpcSubnetIdForAz2() { - return vpcSubnetIdForAz2; - } - - public LambdaInputParameters setVpcSubnetIdForAz2(String vpcSubnetIdForAz2) { - this.vpcSubnetIdForAz2 = vpcSubnetIdForAz2; - return this; - } - - public String getVpcSubnetIdForAz3() { - return vpcSubnetIdForAz3; - } - - public LambdaInputParameters setVpcSubnetIdForAz3(String vpcSubnetIdForAz3) { - this.vpcSubnetIdForAz3 = vpcSubnetIdForAz3; - return this; - } - - public String getTagName() { - return tagName; - } - - public LambdaInputParameters setTagName(String tagName) { - this.tagName = tagName; - return this; - } - - public String getTagEmail() { - return tagEmail; - } - - public LambdaInputParameters setTagEmail(String tagEmail) { - this.tagEmail = tagEmail; - return this; - } - - public String getTagCostcenter() { - return tagCostcenter; - } - - public LambdaInputParameters setTagCostcenter(String tagCostcenter) { - this.tagCostcenter = tagCostcenter; - return this; - } -} diff --git a/src/main/java/com/nike/cerberus/domain/cloudformation/LaunchConfigParameters.java b/src/main/java/com/nike/cerberus/domain/cloudformation/LaunchConfigParameters.java index b595d994..a8b0b25f 100644 --- a/src/main/java/com/nike/cerberus/domain/cloudformation/LaunchConfigParameters.java +++ b/src/main/java/com/nike/cerberus/domain/cloudformation/LaunchConfigParameters.java @@ -19,7 +19,7 @@ /** * Interface implemented by stack parameter POJOs that have launch configuration parameters. */ -public interface LaunchConfigParameters extends TagParameters, SslConfigParameters { +public interface LaunchConfigParameters { LaunchConfigParametersDelegate getLaunchConfigParameters(); } diff --git a/src/main/java/com/nike/cerberus/domain/cloudformation/LaunchConfigParametersDelegate.java b/src/main/java/com/nike/cerberus/domain/cloudformation/LaunchConfigParametersDelegate.java index 11d51c6c..addb08e6 100644 --- a/src/main/java/com/nike/cerberus/domain/cloudformation/LaunchConfigParametersDelegate.java +++ b/src/main/java/com/nike/cerberus/domain/cloudformation/LaunchConfigParametersDelegate.java @@ -29,12 +29,6 @@ public class LaunchConfigParametersDelegate { private String userData; - private int desiredInstances; - - private int maximumInstances; - - private int minimumInstances; - public String getAmiId() { return amiId; } @@ -70,31 +64,4 @@ public LaunchConfigParametersDelegate setUserData(String userData) { this.userData = userData; return this; } - - public int getDesiredInstances() { - return desiredInstances; - } - - public LaunchConfigParametersDelegate setDesiredInstances(int desiredInstances) { - this.desiredInstances = desiredInstances; - return this; - } - - public int getMaximumInstances() { - return maximumInstances; - } - - public LaunchConfigParametersDelegate setMaximumInstances(int maximumInstances) { - this.maximumInstances = maximumInstances; - return this; - } - - public int getMinimumInstances() { - return minimumInstances; - } - - public LaunchConfigParametersDelegate setMinimumInstances(int minimumInstances) { - this.minimumInstances = minimumInstances; - return this; - } } diff --git a/src/main/java/com/nike/cerberus/domain/cloudformation/LoadBalancerOutputs.java b/src/main/java/com/nike/cerberus/domain/cloudformation/LoadBalancerOutputs.java new file mode 100644 index 00000000..80903f34 --- /dev/null +++ b/src/main/java/com/nike/cerberus/domain/cloudformation/LoadBalancerOutputs.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.domain.cloudformation; + +/** + * Represents the load balancer stack outputs. + */ +public class LoadBalancerOutputs { + + private String cmsTargetGroup; + + private String loadBalancerAccessLogBucket; + + private String loadBalancerDnsName; + + private String loadBalancerPhysicalId; + + public String getCmsTargetGroup() { + return cmsTargetGroup; + } + + public LoadBalancerOutputs setCmsTargetGroup(String cmsTargetGroup) { + this.cmsTargetGroup = cmsTargetGroup; + return this; + } + + public String getLoadBalancerAccessLogBucket() { + return loadBalancerAccessLogBucket; + } + + public LoadBalancerOutputs setLoadBalancerAccessLogBucket(String loadBalancerAccessLogBucket) { + this.loadBalancerAccessLogBucket = loadBalancerAccessLogBucket; + return this; + } + + public String getLoadBalancerDnsName() { + return loadBalancerDnsName; + } + + public LoadBalancerOutputs setLoadBalancerDnsName(String loadBalancerDnsName) { + this.loadBalancerDnsName = loadBalancerDnsName; + return this; + } + + public String getLoadBalancerPhysicalId() { + return loadBalancerPhysicalId; + } + + public void setLoadBalancerPhysicalId(String loadBalancerPhysicalId) { + this.loadBalancerPhysicalId = loadBalancerPhysicalId; + } +} diff --git a/src/main/java/com/nike/cerberus/domain/cloudformation/LoadBalancerParameters.java b/src/main/java/com/nike/cerberus/domain/cloudformation/LoadBalancerParameters.java new file mode 100644 index 00000000..7b9270ba --- /dev/null +++ b/src/main/java/com/nike/cerberus/domain/cloudformation/LoadBalancerParameters.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.domain.cloudformation; + + +/** + * Represents the load balancer stack inputs. + */ +public class LoadBalancerParameters { + + private String sgStackName; + + private String sslCertificateArn; + + private String vpcId; + + private String vpcSubnetIdForAz1; + + private String vpcSubnetIdForAz2; + + private String vpcSubnetIdForAz3; + + private String sslPolicy; + + private String elasticLoadBalancingAccountId; + + public String getSgStackName() { + return sgStackName; + } + + public LoadBalancerParameters setSgStackName(String sgStackName) { + this.sgStackName = sgStackName; + return this; + } + + public String getSslCertificateArn() { + return sslCertificateArn; + } + + public LoadBalancerParameters setSslCertificateArn(String sslCertificateArn) { + this.sslCertificateArn = sslCertificateArn; + return this; + } + + public String getVpcId() { + return vpcId; + } + + public LoadBalancerParameters setVpcId(String vpcId) { + this.vpcId = vpcId; + return this; + } + + public String getVpcSubnetIdForAz1() { + return vpcSubnetIdForAz1; + } + + public LoadBalancerParameters setVpcSubnetIdForAz1(String vpcSubnetIdForAz1) { + this.vpcSubnetIdForAz1 = vpcSubnetIdForAz1; + return this; + } + + public String getVpcSubnetIdForAz2() { + return vpcSubnetIdForAz2; + } + + public LoadBalancerParameters setVpcSubnetIdForAz2(String vpcSubnetIdForAz2) { + this.vpcSubnetIdForAz2 = vpcSubnetIdForAz2; + return this; + } + + public String getVpcSubnetIdForAz3() { + return vpcSubnetIdForAz3; + } + + public LoadBalancerParameters setVpcSubnetIdForAz3(String vpcSubnetIdForAz3) { + this.vpcSubnetIdForAz3 = vpcSubnetIdForAz3; + return this; + } + + public String getSslPolicy() { + return sslPolicy; + } + + public LoadBalancerParameters setSslPolicy(String sslPolicy) { + this.sslPolicy = sslPolicy; + return this; + } + + public String getElasticLoadBalancingAccountId() { + return elasticLoadBalancingAccountId; + } + + public LoadBalancerParameters setElasticLoadBalancingAccountId(String elasticLoadBalancingAccountId) { + this.elasticLoadBalancingAccountId = elasticLoadBalancingAccountId; + return this; + } +} diff --git a/src/main/java/com/nike/cerberus/domain/cloudformation/Route53Outputs.java b/src/main/java/com/nike/cerberus/domain/cloudformation/Route53Outputs.java new file mode 100644 index 00000000..d845adf6 --- /dev/null +++ b/src/main/java/com/nike/cerberus/domain/cloudformation/Route53Outputs.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.domain.cloudformation; + +/** + * Represents the route53 stack outputs. + */ +public class Route53Outputs { + + private String loadBalancerDomainName; + + private String originDomainName; + + public String getLoadBalancerDomainName() { + return loadBalancerDomainName; + } + + public void setLoadBalancerDomainName(String loadBalancerDomainName) { + this.loadBalancerDomainName = loadBalancerDomainName; + } + + public String getOriginDomainName() { + return originDomainName; + } + + public void setOriginDomainName(String originDomainName) { + this.originDomainName = originDomainName; + } +} diff --git a/src/main/java/com/nike/cerberus/domain/cloudformation/Route53Parameters.java b/src/main/java/com/nike/cerberus/domain/cloudformation/Route53Parameters.java new file mode 100644 index 00000000..4fc63940 --- /dev/null +++ b/src/main/java/com/nike/cerberus/domain/cloudformation/Route53Parameters.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.domain.cloudformation; + +/** + * Represents the route53 stack inputs. + */ +public class Route53Parameters { + private String hostedZoneId; + + private String loadBalancerDomainName; + + private String loadBalancerStackName; + + private String originDomainName; + + public String getHostedZoneId() { + return hostedZoneId; + } + + public Route53Parameters setHostedZoneId(String hostedZoneId) { + this.hostedZoneId = hostedZoneId; + return this; + } + + public String getLoadBalancerDomainName() { + return loadBalancerDomainName; + } + + public Route53Parameters setLoadBalancerDomainName(String loadBalancerDomainName) { + this.loadBalancerDomainName = loadBalancerDomainName; + return this; + } + + public String getLoadBalancerStackName() { + return loadBalancerStackName; + } + + public Route53Parameters setLoadBalancerStackName(String loadBalancerStackName) { + this.loadBalancerStackName = loadBalancerStackName; + return this; + } + + public String getOriginDomainName() { + return originDomainName; + } + + public Route53Parameters setOriginDomainName(String originDomainName) { + this.originDomainName = originDomainName; + return this; + } +} diff --git a/src/main/java/com/nike/cerberus/domain/cloudformation/SecurityGroupOutputs.java b/src/main/java/com/nike/cerberus/domain/cloudformation/SecurityGroupOutputs.java new file mode 100644 index 00000000..d924434e --- /dev/null +++ b/src/main/java/com/nike/cerberus/domain/cloudformation/SecurityGroupOutputs.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.domain.cloudformation; + +/** + * Represents the security group stack outputs. + */ +public class SecurityGroupOutputs { + + private Integer cmsDbPort; + + private String cmsSgId; + + private String cmsDbSgId; + + private String loadBalancerSgId; + + private String whitelistIngressSgId; + + public Integer getCmsDbPort() { + return cmsDbPort; + } + + public SecurityGroupOutputs setCmsDbPort(Integer cmsDbPort) { + this.cmsDbPort = cmsDbPort; + return this; + } + + public String getCmsSgId() { + return cmsSgId; + } + + public SecurityGroupOutputs setCmsSgId(String cmsSgId) { + this.cmsSgId = cmsSgId; + return this; + } + + public String getCmsDbSgId() { + return cmsDbSgId; + } + + public SecurityGroupOutputs setCmsDbSgId(String cmsDbSgId) { + this.cmsDbSgId = cmsDbSgId; + return this; + } + + public String getLoadBalancerSgId() { + return loadBalancerSgId; + } + + public SecurityGroupOutputs setLoadBalancerSgId(String loadBalancerSgId) { + this.loadBalancerSgId = loadBalancerSgId; + return this; + } + + public String getWhitelistIngressSgId() { + return whitelistIngressSgId; + } + + public SecurityGroupOutputs setWhitelistIngressSgId(String whitelistIngressSgId) { + this.whitelistIngressSgId = whitelistIngressSgId; + return this; + } +} diff --git a/src/main/java/com/nike/cerberus/domain/cloudformation/CloudFrontIpSynchronizerParameters.java b/src/main/java/com/nike/cerberus/domain/cloudformation/SecurityGroupParameters.java similarity index 57% rename from src/main/java/com/nike/cerberus/domain/cloudformation/CloudFrontIpSynchronizerParameters.java rename to src/main/java/com/nike/cerberus/domain/cloudformation/SecurityGroupParameters.java index 2be59ec9..943f2712 100644 --- a/src/main/java/com/nike/cerberus/domain/cloudformation/CloudFrontIpSynchronizerParameters.java +++ b/src/main/java/com/nike/cerberus/domain/cloudformation/SecurityGroupParameters.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Nike, Inc. + * Copyright (c) 2017 Nike, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,27 +16,31 @@ package com.nike.cerberus.domain.cloudformation; -public class CloudFrontIpSynchronizerParameters { +/** + * Represents the security group stack inputs. + */ +public class SecurityGroupParameters { - private String lambdaBucket; + private Integer cmsDbPort; - private String lambdaKey; + private String vpcId; - public String getLambdaBucket() { - return lambdaBucket; + public Integer getCmsDbPort() { + return cmsDbPort; } - public CloudFrontIpSynchronizerParameters setLambdaBucket(String lambdaBucket) { - this.lambdaBucket = lambdaBucket; + public SecurityGroupParameters setCmsDbPort(Integer cmsDbPort) { + this.cmsDbPort = cmsDbPort; return this; } - public String getLambdaKey() { - return lambdaKey; + public String getVpcId() { + return vpcId; } - public CloudFrontIpSynchronizerParameters setLambdaKey(String lambdaKey) { - this.lambdaKey = lambdaKey; + public SecurityGroupParameters setVpcId(String vpcId) { + this.vpcId = vpcId; return this; } + } diff --git a/src/main/java/com/nike/cerberus/domain/cloudformation/TagParameters.java b/src/main/java/com/nike/cerberus/domain/cloudformation/TagParameters.java deleted file mode 100644 index ac28a3f2..00000000 --- a/src/main/java/com/nike/cerberus/domain/cloudformation/TagParameters.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.domain.cloudformation; - -/** - * Interface implemented by stack parameter POJOs that have tag parameters. - */ -public interface TagParameters { - - TagParametersDelegate getTagParameters(); -} diff --git a/src/main/java/com/nike/cerberus/domain/cloudformation/TagParametersDelegate.java b/src/main/java/com/nike/cerberus/domain/cloudformation/TagParametersDelegate.java index 2a1c6798..545cdf70 100644 --- a/src/main/java/com/nike/cerberus/domain/cloudformation/TagParametersDelegate.java +++ b/src/main/java/com/nike/cerberus/domain/cloudformation/TagParametersDelegate.java @@ -16,41 +16,29 @@ package com.nike.cerberus.domain.cloudformation; +import com.beust.jcommander.DynamicParameter; + +import java.util.HashMap; +import java.util.Map; + /** * CloudFormation input parameters common to all Cerberus CloudFormation stacks. */ public class TagParametersDelegate { - private String tagName; - - private String tagEmail; - - private String tagCostcenter; + public static final String TAG_LONG_ARG = "--TAG"; + public static final String TAG_SHORT_ARG = "-T"; - public String getTagName() { - return tagName; - } - - public TagParametersDelegate setTagName(String tagName) { - this.tagName = tagName; - return this; - } - - public String getTagEmail() { - return tagEmail; - } - - public TagParametersDelegate setTagEmail(String tagEmail) { - this.tagEmail = tagEmail; - return this; - } - - public String getTagCostcenter() { - return tagCostcenter; - } + @DynamicParameter( + names = { + TAG_LONG_ARG, + TAG_SHORT_ARG + }, + description = "Dynamic parameters for setting tags to be used on generated aws resources." + ) + private Map tags = new HashMap<>(); - public TagParametersDelegate setTagCostcenter(String tagCostcenter) { - this.tagCostcenter = tagCostcenter; - return this; + public Map getTags() { + return tags; } } diff --git a/src/main/java/com/nike/cerberus/domain/cloudformation/VaultOutputs.java b/src/main/java/com/nike/cerberus/domain/cloudformation/VaultOutputs.java deleted file mode 100644 index 8fa403fa..00000000 --- a/src/main/java/com/nike/cerberus/domain/cloudformation/VaultOutputs.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.domain.cloudformation; - -/** - * Represents output parameters for the Vault CloudFormation script. - */ -public class VaultOutputs { - - private String autoscalingGroupLogicalId; - - private String launchConfigurationLogicalId; - - private String elbLogicalId; - - private String elbCanonicalHostedZoneNameId; - - private String elbDnsName; - - private String elbSourceSecurityGroupName; - - private String elbSourceSecurityGroupOwnerAlias; - - public String getAutoscalingGroupLogicalId() { - return autoscalingGroupLogicalId; - } - - public VaultOutputs setAutoscalingGroupLogicalId(String autoscalingGroupLogicalId) { - this.autoscalingGroupLogicalId = autoscalingGroupLogicalId; - return this; - } - - public String getLaunchConfigurationLogicalId() { - return launchConfigurationLogicalId; - } - - public VaultOutputs setLaunchConfigurationLogicalId(String launchConfigurationLogicalId) { - this.launchConfigurationLogicalId = launchConfigurationLogicalId; - return this; - } - - public String getElbLogicalId() { - return elbLogicalId; - } - - public VaultOutputs setElbLogicalId(String elbLogicalId) { - this.elbLogicalId = elbLogicalId; - return this; - } - - public String getElbCanonicalHostedZoneNameId() { - return elbCanonicalHostedZoneNameId; - } - - public VaultOutputs setElbCanonicalHostedZoneNameId(String elbCanonicalHostedZoneNameId) { - this.elbCanonicalHostedZoneNameId = elbCanonicalHostedZoneNameId; - return this; - } - - public String getElbDnsName() { - return elbDnsName; - } - - public VaultOutputs setElbDnsName(String elbDnsName) { - this.elbDnsName = elbDnsName; - return this; - } - - public String getElbSourceSecurityGroupName() { - return elbSourceSecurityGroupName; - } - - public VaultOutputs setElbSourceSecurityGroupName(String elbSourceSecurityGroupName) { - this.elbSourceSecurityGroupName = elbSourceSecurityGroupName; - return this; - } - - public String getElbSourceSecurityGroupOwnerAlias() { - return elbSourceSecurityGroupOwnerAlias; - } - - public VaultOutputs setElbSourceSecurityGroupOwnerAlias(String elbSourceSecurityGroupOwnerAlias) { - this.elbSourceSecurityGroupOwnerAlias = elbSourceSecurityGroupOwnerAlias; - return this; - } -} diff --git a/src/main/java/com/nike/cerberus/domain/cloudformation/VaultParameters.java b/src/main/java/com/nike/cerberus/domain/cloudformation/VaultParameters.java deleted file mode 100644 index 4c8e6d64..00000000 --- a/src/main/java/com/nike/cerberus/domain/cloudformation/VaultParameters.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.domain.cloudformation; - -import com.fasterxml.jackson.annotation.JsonUnwrapped; - -/** - * Represents the input parameters for the Vault CloudFormation script. - */ -public class VaultParameters implements LaunchConfigParameters { - - private String vpcId; - - private String instanceProfileName; - - private String toolsIngressSgId; - - private String vaultServerElbSgId; - - private String vaultClientSgId; - - private String vaultServerSgId; - - private String consulClientSgId; - - private String consulServerSgId; - - private String vpcSubnetIdForAz1; - - private String vpcSubnetIdForAz2; - - private String vpcSubnetIdForAz3; - - private String hostedZoneId; - - private String cname; - - @JsonUnwrapped - private SslConfigParametersDelegate sslConfigParameters = new SslConfigParametersDelegate(); - - @JsonUnwrapped - private LaunchConfigParametersDelegate launchConfigParameters = new LaunchConfigParametersDelegate(); - - @JsonUnwrapped - private TagParametersDelegate tagParameters = new TagParametersDelegate(); - - public String getVpcId() { - return vpcId; - } - - public VaultParameters setVpcId(String vpcId) { - this.vpcId = vpcId; - return this; - } - - public String getInstanceProfileName() { - return instanceProfileName; - } - - public VaultParameters setInstanceProfileName(String instanceProfileName) { - this.instanceProfileName = instanceProfileName; - return this; - } - - public String getToolsIngressSgId() { - return toolsIngressSgId; - } - - public VaultParameters setToolsIngressSgId(String toolsIngressSgId) { - this.toolsIngressSgId = toolsIngressSgId; - return this; - } - - public String getVaultServerElbSgId() { - return vaultServerElbSgId; - } - - public VaultParameters setVaultServerElbSgId(String vaultServerElbSgId) { - this.vaultServerElbSgId = vaultServerElbSgId; - return this; - } - - public String getVaultClientSgId() { - return vaultClientSgId; - } - - public VaultParameters setVaultClientSgId(String vaultClientSgId) { - this.vaultClientSgId = vaultClientSgId; - return this; - } - - public String getVaultServerSgId() { - return vaultServerSgId; - } - - public VaultParameters setVaultServerSgId(String vaultServerSgId) { - this.vaultServerSgId = vaultServerSgId; - return this; - } - - public String getConsulClientSgId() { - return consulClientSgId; - } - - public VaultParameters setConsulClientSgId(String consulClientSgId) { - this.consulClientSgId = consulClientSgId; - return this; - } - - public String getConsulServerSgId() { - return consulServerSgId; - } - - public VaultParameters setConsulServerSgId(String consulServerSgId) { - this.consulServerSgId = consulServerSgId; - return this; - } - - public String getVpcSubnetIdForAz1() { - return vpcSubnetIdForAz1; - } - - public VaultParameters setVpcSubnetIdForAz1(String vpcSubnetIdForAz1) { - this.vpcSubnetIdForAz1 = vpcSubnetIdForAz1; - return this; - } - - public String getVpcSubnetIdForAz2() { - return vpcSubnetIdForAz2; - } - - public VaultParameters setVpcSubnetIdForAz2(String vpcSubnetIdForAz2) { - this.vpcSubnetIdForAz2 = vpcSubnetIdForAz2; - return this; - } - - public String getVpcSubnetIdForAz3() { - return vpcSubnetIdForAz3; - } - - public VaultParameters setVpcSubnetIdForAz3(String vpcSubnetIdForAz3) { - this.vpcSubnetIdForAz3 = vpcSubnetIdForAz3; - return this; - } - - public String getHostedZoneId() { - return hostedZoneId; - } - - public VaultParameters setHostedZoneId(String hostedZoneId) { - this.hostedZoneId = hostedZoneId; - return this; - } - - public String getCname() { - return cname; - } - - public VaultParameters setCname(String cname) { - this.cname = cname; - return this; - } - - @Override - public SslConfigParametersDelegate getSslConfigParameters() { - return sslConfigParameters; - } - - public VaultParameters setSslConfigParameters(SslConfigParametersDelegate sslConfigParameters) { - this.sslConfigParameters = sslConfigParameters; - return this; - } - - @Override - public LaunchConfigParametersDelegate getLaunchConfigParameters() { - return launchConfigParameters; - } - - public VaultParameters setLaunchConfigParameters(LaunchConfigParametersDelegate launchConfigParameters) { - this.launchConfigParameters = launchConfigParameters; - return this; - } - - @Override - public TagParametersDelegate getTagParameters() { - return tagParameters; - } - - public VaultParameters setTagParameters(TagParametersDelegate tagParameters) { - this.tagParameters = tagParameters; - return this; - } -} diff --git a/src/main/java/com/nike/cerberus/domain/cloudformation/VpcOutputs.java b/src/main/java/com/nike/cerberus/domain/cloudformation/VpcOutputs.java new file mode 100644 index 00000000..524e67ba --- /dev/null +++ b/src/main/java/com/nike/cerberus/domain/cloudformation/VpcOutputs.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.domain.cloudformation; + +/** + * Represents the vpc stack outputs. + */ +public class VpcOutputs { + + private String subnetCidrBlockForAz1; + + private String subnetCidrBlockForAz2; + + private String subnetCidrBlockForAz3; + + private String vpcSubnetIdForAz1; + + private String vpcSubnetIdForAz2; + + private String vpcSubnetIdForAz3; + + private String vpcId; + + private String vpcInternalBaseDomainName; + + private String vpcInternalHostedZoneId; + + public String getSubnetCidrBlockForAz1() { + return subnetCidrBlockForAz1; + } + + public VpcOutputs setSubnetCidrBlockForAz1(String subnetCidrBlockForAz1) { + this.subnetCidrBlockForAz1 = subnetCidrBlockForAz1; + return this; + } + + public String getSubnetCidrBlockForAz2() { + return subnetCidrBlockForAz2; + } + + public VpcOutputs setSubnetCidrBlockForAz2(String subnetCidrBlockForAz2) { + this.subnetCidrBlockForAz2 = subnetCidrBlockForAz2; + return this; + } + + public String getSubnetCidrBlockForAz3() { + return subnetCidrBlockForAz3; + } + + public VpcOutputs setSubnetCidrBlockForAz3(String subnetCidrBlockForAz3) { + this.subnetCidrBlockForAz3 = subnetCidrBlockForAz3; + return this; + } + + public String getVpcSubnetIdForAz1() { + return vpcSubnetIdForAz1; + } + + public VpcOutputs setVpcSubnetForAz1(String vpcSubnetForAz1) { + this.vpcSubnetIdForAz1 = vpcSubnetForAz1; + return this; + } + + public String getVpcSubnetIdForAz2() { + return vpcSubnetIdForAz2; + } + + public VpcOutputs setVpcSubnetForAz2(String vpcSubnetForAz2) { + this.vpcSubnetIdForAz2 = vpcSubnetForAz2; + return this; + } + + public String getVpcSubnetIdForAz3() { + return vpcSubnetIdForAz3; + } + + public VpcOutputs setVpcSubnetForAz3(String vpcSubnetForAz3) { + this.vpcSubnetIdForAz3 = vpcSubnetForAz3; + return this; + } + + public String getVpcId() { + return vpcId; + } + + public VpcOutputs setVpcId(String vpcId) { + this.vpcId = vpcId; + return this; + } + + public String getVpcInternalBaseDomainName() { + return vpcInternalBaseDomainName; + } + + public VpcOutputs setVpcInternalBaseDomainName(String vpcInternalBaseDomainName) { + this.vpcInternalBaseDomainName = vpcInternalBaseDomainName; + return this; + } + + public String getVpcInternalHostedZoneId() { + return vpcInternalHostedZoneId; + } + + public VpcOutputs setVpcInternalHostedZoneId(String vpcInternalHostedZoneId) { + this.vpcInternalHostedZoneId = vpcInternalHostedZoneId; + return this; + } +} diff --git a/src/main/java/com/nike/cerberus/domain/cloudformation/VpcParameters.java b/src/main/java/com/nike/cerberus/domain/cloudformation/VpcParameters.java new file mode 100644 index 00000000..01b1d7b5 --- /dev/null +++ b/src/main/java/com/nike/cerberus/domain/cloudformation/VpcParameters.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.domain.cloudformation; + +/** + * Represents the vpc stack inputs. + */ +public class VpcParameters { + + private String az1; + + private String az2; + + private String az3; + + private String internetGatewayCidrBlock; + + private String subnetCidrBlockForAz1; + + private String subnetCidrBlockForAz2; + + private String subnetCidrBlockForAz3; + + private String vpcCidrBlock; + + private String environmentName; + + public String getAz1() { + return az1; + } + + public VpcParameters setAz1(String az1) { + this.az1 = az1; + return this; + } + + public String getAz2() { + return az2; + } + + public VpcParameters setAz2(String az2) { + this.az2 = az2; + return this; + } + + public String getAz3() { + return az3; + } + + public VpcParameters setAz3(String az3) { + this.az3 = az3; + return this; + } + + public String getInternetGatewayCidrBlock() { + return internetGatewayCidrBlock; + } + + public VpcParameters setInternetGatewayCidrBlock(String internetGatewayCidrBlock) { + this.internetGatewayCidrBlock = internetGatewayCidrBlock; + return this; + } + + public String getSubnetCidrBlockForAz1() { + return subnetCidrBlockForAz1; + } + + public VpcParameters setSubnetCidrBlockForAz1(String subnetCidrBlockForAz1) { + this.subnetCidrBlockForAz1 = subnetCidrBlockForAz1; + return this; + } + + public String getSubnetCidrBlockForAz2() { + return subnetCidrBlockForAz2; + } + + public VpcParameters setSubnetCidrBlockForAz2(String subnetCidrBlockForAz2) { + this.subnetCidrBlockForAz2 = subnetCidrBlockForAz2; + return this; + } + + public String getSubnetCidrBlockForAz3() { + return subnetCidrBlockForAz3; + } + + public VpcParameters setSubnetCidrBlockForAz3(String subnetCidrBlockForAz3) { + this.subnetCidrBlockForAz3 = subnetCidrBlockForAz3; + return this; + } + + public String getVpcCidrBlock() { + return vpcCidrBlock; + } + + public VpcParameters setVpcCidrBlock(String vpcCidrBlock) { + this.vpcCidrBlock = vpcCidrBlock; + return this; + } + + public String getEnvironmentName() { + return environmentName; + } + + public VpcParameters setEnvironmentName(String environmentName) { + this.environmentName = environmentName; + return this; + } +} diff --git a/src/main/java/com/nike/cerberus/domain/cloudformation/WafOutputs.java b/src/main/java/com/nike/cerberus/domain/cloudformation/WafOutputs.java new file mode 100644 index 00000000..cc5c2730 --- /dev/null +++ b/src/main/java/com/nike/cerberus/domain/cloudformation/WafOutputs.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.domain.cloudformation; + +/** + * Represents the waf stack outputs. + */ +public class WafOutputs { + + private Integer autoBlockIPSetID; + + private String manualBlockIPSetID; + + private String whitelistIPSetID; + + public Integer getAutoBlockIPSetID() { + return autoBlockIPSetID; + } + + public WafOutputs setAutoBlockIPSetID(Integer autoBlockIPSetID) { + this.autoBlockIPSetID = autoBlockIPSetID; + return this; + } + + public String getManualBlockIPSetID() { + return manualBlockIPSetID; + } + + public WafOutputs setManualBlockIPSetID(String manualBlockIPSetID) { + this.manualBlockIPSetID = manualBlockIPSetID; + return this; + } + + public String getWhitelistIPSetID() { + return whitelistIPSetID; + } + + public WafOutputs setWhitelistIPSetID(String whitelistIPSetID) { + this.whitelistIPSetID = whitelistIPSetID; + return this; + } +} diff --git a/src/main/java/com/nike/cerberus/domain/cloudformation/CloudFrontIpSynchronizerOutputs.java b/src/main/java/com/nike/cerberus/domain/cloudformation/WafParameters.java similarity index 52% rename from src/main/java/com/nike/cerberus/domain/cloudformation/CloudFrontIpSynchronizerOutputs.java rename to src/main/java/com/nike/cerberus/domain/cloudformation/WafParameters.java index 15eb5a65..5e0dff50 100644 --- a/src/main/java/com/nike/cerberus/domain/cloudformation/CloudFrontIpSynchronizerOutputs.java +++ b/src/main/java/com/nike/cerberus/domain/cloudformation/WafParameters.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Nike, Inc. + * Copyright (c) 2017 Nike, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,16 +16,30 @@ package com.nike.cerberus.domain.cloudformation; -public class CloudFrontIpSynchronizerOutputs { +/** + * Represents the waf stack inputs. + */ +public class WafParameters { + + private String loadBalancerStackName; - private String cloudFrontOriginElbSgIpSyncFunctionArn; + private String wafName; + + public String getLoadBalancerStackName() { + return loadBalancerStackName; + } + + public WafParameters setLoadBalancerStackName(String loadBalancerStackName) { + this.loadBalancerStackName = loadBalancerStackName; + return this; + } - public String getCloudFrontOriginElbSgIpSyncFunctionArn() { - return cloudFrontOriginElbSgIpSyncFunctionArn; + public String getWafName() { + return wafName; } - public CloudFrontIpSynchronizerOutputs setCloudFrontOriginElbSgIpSyncFunctionArn(String cloudFrontOriginElbSgIpSyncFunctionArn) { - this.cloudFrontOriginElbSgIpSyncFunctionArn = cloudFrontOriginElbSgIpSyncFunctionArn; + public WafParameters setWafName(String wafName) { + this.wafName = wafName; return this; } } diff --git a/src/main/java/com/nike/cerberus/domain/configuration/ConsulConfiguration.java b/src/main/java/com/nike/cerberus/domain/configuration/ConsulConfiguration.java deleted file mode 100644 index 8ca7a77d..00000000 --- a/src/main/java/com/nike/cerberus/domain/configuration/ConsulConfiguration.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.domain.configuration; - -import com.nike.cerberus.domain.template.ConsulConfigurationInput; - -/** - * Consul configuration POJO. - */ -public class ConsulConfiguration { - - private ConsulConfigurationInput input; - - private String clientConfiguration; - - private String serverConfiguration; - - public ConsulConfigurationInput getInput() { - return input; - } - - public ConsulConfiguration setInput(ConsulConfigurationInput input) { - this.input = input; - return this; - } - - public String getClientConfiguration() { - return clientConfiguration; - } - - public ConsulConfiguration setClientConfiguration(String clientConfiguration) { - this.clientConfiguration = clientConfiguration; - return this; - } - - public String getServerConfiguration() { - return serverConfiguration; - } - - public ConsulConfiguration setServerConfiguration(String serverConfiguration) { - this.serverConfiguration = serverConfiguration; - return this; - } -} diff --git a/src/main/java/com/nike/cerberus/domain/configuration/VaultAclEntry.java b/src/main/java/com/nike/cerberus/domain/configuration/VaultAclEntry.java deleted file mode 100644 index 3e784824..00000000 --- a/src/main/java/com/nike/cerberus/domain/configuration/VaultAclEntry.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.domain.configuration; - -/** - * POJO creating the ACL token and entry JSON. - */ -public class VaultAclEntry { - - private String aclToken; - - private String entry; - - public String getAclToken() { - return aclToken; - } - - public VaultAclEntry setAclToken(String aclToken) { - this.aclToken = aclToken; - return this; - } - - public String getEntry() { - return entry; - } - - public VaultAclEntry setEntry(String entry) { - this.entry = entry; - return this; - } -} diff --git a/src/main/java/com/nike/cerberus/domain/configuration/VaultConfiguration.java b/src/main/java/com/nike/cerberus/domain/configuration/VaultConfiguration.java deleted file mode 100644 index a2387918..00000000 --- a/src/main/java/com/nike/cerberus/domain/configuration/VaultConfiguration.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.domain.configuration; - -/** - * Represents the generated Vault configuration. - */ -public class VaultConfiguration { - private String config; - - public VaultConfiguration setConfig(String config) { - this.config = config; - return this; - } - - public String getConfig() { - return config; - } -} diff --git a/src/main/java/com/nike/cerberus/domain/environment/CertificateInformation.java b/src/main/java/com/nike/cerberus/domain/environment/CertificateInformation.java new file mode 100644 index 00000000..30a8bf7f --- /dev/null +++ b/src/main/java/com/nike/cerberus/domain/environment/CertificateInformation.java @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.domain.environment; + +import org.joda.time.DateTime; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; + +import java.util.List; + +public class CertificateInformation { + + private String certificateName; + private String certificateId; + private String identityManagementCertificateArn; + private DateTime notBefore; + private DateTime notAfter; + private DateTime uploaded; + private String commonName; + private List subjectAlternateNames; + + public String getCertificateName() { + return certificateName; + } + + public void setCertificateName(String certificateName) { + this.certificateName = certificateName; + } + + public String getCertificateId() { + return certificateId; + } + + public void setCertificateId(String certificateId) { + this.certificateId = certificateId; + } + + public DateTime getNotBefore() { + return notBefore; + } + + public void setNotBefore(DateTime notBefore) { + this.notBefore = notBefore; + } + + public DateTime getNotAfter() { + return notAfter; + } + + public void setNotAfter(DateTime notAfter) { + this.notAfter = notAfter; + } + + public DateTime getUploaded() { + return uploaded; + } + + public void setUploaded(DateTime uploaded) { + this.uploaded = uploaded; + } + + public String getCommonName() { + return commonName; + } + + public void setCommonName(String commonName) { + this.commonName = commonName; + } + + public List getSubjectAlternateNames() { + return subjectAlternateNames; + } + + public void setSubjectAlternateNames(List subjectAlternateNames) { + this.subjectAlternateNames = subjectAlternateNames; + } + + public String getIdentityManagementCertificateArn() { + return identityManagementCertificateArn; + } + + public void setIdentityManagementCertificateArn(String identityManagementCertificateArn) { + this.identityManagementCertificateArn = identityManagementCertificateArn; + } + + @Override + public String toString() { + DateTimeFormatter fmt = DateTimeFormat.fullDateTime(); + return "CertificateInformation{" + + "certificateName='" + certificateName + '\'' + + ", certificateId='" + certificateId + '\'' + + ", identityManagementCertificateArn='" + identityManagementCertificateArn + '\'' + + ", notBefore=" + fmt.print(notBefore) + + ", notAfter=" + fmt.print(notAfter) + + ", uploaded=" + fmt.print(uploaded) + + ", commonName='" + commonName + '\'' + + ", subjectAlternateNames=" + subjectAlternateNames + + '}'; + } + + public static final class Builder { + private String certificateName; + private String certificateId; + private String identityManagementCertificateArn; + private DateTime notBefore; + private DateTime notAfter; + private DateTime uploaded; + private String commonName; + private List subjectAlternateNames; + + private Builder() { + } + + public static Builder create() { + return new Builder(); + } + + public Builder withCertificateName(String certificateName) { + this.certificateName = certificateName; + return this; + } + + public Builder withCertificateId(String certificateId) { + this.certificateId = certificateId; + return this; + } + + public Builder withIdentityManagementCertificateArn(String identityManagementCertificateArn) { + this.identityManagementCertificateArn = identityManagementCertificateArn; + return this; + } + + public Builder withNotBefore(DateTime notBefore) { + this.notBefore = notBefore; + return this; + } + + public Builder withNotAfter(DateTime notAfter) { + this.notAfter = notAfter; + return this; + } + + public Builder withUploaded(DateTime uploaded) { + this.uploaded = uploaded; + return this; + } + + public Builder withCommonName(String commonName) { + this.commonName = commonName; + return this; + } + + public Builder withSubjectAlternateNames(List subjectAlternateNames) { + this.subjectAlternateNames = subjectAlternateNames; + return this; + } + + public CertificateInformation build() { + CertificateInformation certificateInformation = new CertificateInformation(); + certificateInformation.setCertificateName(certificateName); + certificateInformation.setCertificateId(certificateId); + certificateInformation.setIdentityManagementCertificateArn(identityManagementCertificateArn); + certificateInformation.setNotBefore(notBefore); + certificateInformation.setNotAfter(notAfter); + certificateInformation.setUploaded(uploaded); + certificateInformation.setCommonName(commonName); + certificateInformation.setSubjectAlternateNames(subjectAlternateNames); + return certificateInformation; + } + } +} diff --git a/src/main/java/com/nike/cerberus/domain/environment/CloudFrontLogProcessingLambdaConfig.java b/src/main/java/com/nike/cerberus/domain/environment/CloudFrontLogProcessingLambdaConfig.java deleted file mode 100644 index 9b85127c..00000000 --- a/src/main/java/com/nike/cerberus/domain/environment/CloudFrontLogProcessingLambdaConfig.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.domain.environment; - -public class CloudFrontLogProcessingLambdaConfig { - - private String manualWhiteListIpSetId; - - private String manualBlackListIpSetId; - - private String rateLimitAutoBlackListIpSetId; - - private Integer rateLimitViolationBlacklistPeriodInMinutes; - - private Integer requestPerMinuteLimit; - - private String slackWebHookUrl; - - private String slackIcon; - - private String googleAnalyticsId; - - public String getManualWhiteListIpSetId() { - return manualWhiteListIpSetId; - } - - public void setManualWhiteListIpSetId(String manualWhiteListIpSetId) { - this.manualWhiteListIpSetId = manualWhiteListIpSetId; - } - - public String getManualBlackListIpSetId() { - return manualBlackListIpSetId; - } - - public void setManualBlackListIpSetId(String manualBlackListIpSetId) { - this.manualBlackListIpSetId = manualBlackListIpSetId; - } - - public String getRateLimitAutoBlackListIpSetId() { - return rateLimitAutoBlackListIpSetId; - } - - public void setRateLimitAutoBlackListIpSetId(String rateLimitAutoBlackListIpSetId) { - this.rateLimitAutoBlackListIpSetId = rateLimitAutoBlackListIpSetId; - } - - public Integer getRateLimitViolationBlacklistPeriodInMinutes() { - return rateLimitViolationBlacklistPeriodInMinutes; - } - - public void setRateLimitViolationBlacklistPeriodInMinutes(Integer rateLimitViolationBlacklistPeriodInMinutes) { - this.rateLimitViolationBlacklistPeriodInMinutes = rateLimitViolationBlacklistPeriodInMinutes; - } - - public Integer getRequestPerMinuteLimit() { - return requestPerMinuteLimit; - } - - public void setRequestPerMinuteLimit(Integer requestPerMinuteLimit) { - this.requestPerMinuteLimit = requestPerMinuteLimit; - } - - public String getSlackWebHookUrl() { - return slackWebHookUrl; - } - - public void setSlackWebHookUrl(String slackWebHookUrl) { - this.slackWebHookUrl = slackWebHookUrl; - } - - public String getSlackIcon() { - return slackIcon; - } - - public void setSlackIcon(String slackIcon) { - this.slackIcon = slackIcon; - } - - public String getGoogleAnalyticsId() { - return googleAnalyticsId; - } - - public void setGoogleAnalyticsId(String googleAnalyticsId) { - this.googleAnalyticsId = googleAnalyticsId; - } -} diff --git a/src/main/java/com/nike/cerberus/domain/environment/CmsSecrets.java b/src/main/java/com/nike/cerberus/domain/environment/CmsSecrets.java deleted file mode 100644 index 04752ad0..00000000 --- a/src/main/java/com/nike/cerberus/domain/environment/CmsSecrets.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.domain.environment; - -/** - * Represents sensitive information used by CMS. - */ -public class CmsSecrets { - - private String adminGroup; - - private String databasePassword; - - private String vaultToken; - - public String getAdminGroup() { - return adminGroup; - } - - public CmsSecrets setAdminGroup(String adminGroup) { - this.adminGroup = adminGroup; - return this; - } - - public String getDatabasePassword() { - return databasePassword; - } - - public CmsSecrets setDatabasePassword(String databasePassword) { - this.databasePassword = databasePassword; - return this; - } - - public String getVaultToken() { - return vaultToken; - } - - public CmsSecrets setVaultToken(String vaultToken) { - this.vaultToken = vaultToken; - return this; - } -} diff --git a/src/main/java/com/nike/cerberus/domain/environment/ConsulSecrets.java b/src/main/java/com/nike/cerberus/domain/environment/ConsulSecrets.java deleted file mode 100644 index d7fff020..00000000 --- a/src/main/java/com/nike/cerberus/domain/environment/ConsulSecrets.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.domain.environment; - -/** - * Represents sensitive information used by Consul. - */ -public class ConsulSecrets { - - private String gossipEncryptionToken; - - private String aclMasterToken; - - private String vaultAclToken; - - public String getGossipEncryptionToken() { - return gossipEncryptionToken; - } - - public ConsulSecrets setGossipEncryptionToken(String gossipEncryptionToken) { - this.gossipEncryptionToken = gossipEncryptionToken; - return this; - } - - public String getAclMasterToken() { - return aclMasterToken; - } - - public ConsulSecrets setAclMasterToken(String aclMasterToken) { - this.aclMasterToken = aclMasterToken; - return this; - } - - public String getVaultAclToken() { - return vaultAclToken; - } - - public ConsulSecrets setVaultAclToken(String vaultAclToken) { - this.vaultAclToken = vaultAclToken; - return this; - } -} diff --git a/src/main/java/com/nike/cerberus/domain/environment/Environment.java b/src/main/java/com/nike/cerberus/domain/environment/Environment.java deleted file mode 100644 index 4257f4d3..00000000 --- a/src/main/java/com/nike/cerberus/domain/environment/Environment.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.domain.environment; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -/** - * General purpose environment data that isn't sensitive. - */ -public class Environment { - - private String az1; - - private String az2; - - private String az3; - - private Map stackMap; - - private Map serverCertificateIdMap; - - private String configKeyId; - - private Map regionBackupBucketMap; - - private Set backupAdminIamPrincipals; - - private String metricsTopicArn; - - /** - * Is the environment configured for continuous delivery - */ - private boolean isCd; - - public Environment() { - stackMap = new HashMap<>(); - for (StackName stackName : StackName.values()) { - stackMap.put(stackName, ""); - } - - serverCertificateIdMap = new HashMap<>(); - serverCertificateIdMap.put(StackName.GATEWAY, ""); - serverCertificateIdMap.put(StackName.CMS, ""); - serverCertificateIdMap.put(StackName.VAULT, ""); - serverCertificateIdMap.put(StackName.CONSUL, ""); - } - - public String getAz1() { - return az1; - } - - public Environment setAz1(String az1) { - this.az1 = az1; - return this; - } - - public String getAz2() { - return az2; - } - - public Environment setAz2(String az2) { - this.az2 = az2; - return this; - } - - public String getAz3() { - return az3; - } - - public Environment setAz3(String az3) { - this.az3 = az3; - return this; - } - - public Map getStackMap() { - return stackMap; - } - - public Environment setStackMap(Map stackMap) { - this.stackMap = stackMap; - return this; - } - - public Map getServerCertificateIdMap() { - return serverCertificateIdMap; - } - - public Environment setServerCertificateIdMap(Map serverCertificateIdMap) { - this.serverCertificateIdMap = serverCertificateIdMap; - return this; - } - - public String getConfigKeyId() { - return configKeyId; - } - - public Environment setConfigKeyId(String configKeyId) { - this.configKeyId = configKeyId; - return this; - } - - public boolean isCd() { - return isCd; - } - - public Environment setCd(boolean cd) { - isCd = cd; - return this; - } - - public Map getRegionBackupBucketMap() { - return regionBackupBucketMap == null ? new HashMap<>() : regionBackupBucketMap; - } - - public void setRegionBackupBucketMap(Map regionBackupBucketMap) { - this.regionBackupBucketMap = regionBackupBucketMap; - } - - public Set getBackupAdminIamPrincipals() { - return backupAdminIamPrincipals == null ? new HashSet<>() : backupAdminIamPrincipals; - } - - public void setBackupAdminIamPrincipals(Set backupAdminIamPrincipals) { - this.backupAdminIamPrincipals = backupAdminIamPrincipals; - } - - public String getMetricsTopicArn() { - return metricsTopicArn; - } - - public void setMetricsTopicArn(String metricsTopicArn) { - this.metricsTopicArn = metricsTopicArn; - } -} diff --git a/src/main/java/com/nike/cerberus/domain/environment/EnvironmentData.java b/src/main/java/com/nike/cerberus/domain/environment/EnvironmentData.java new file mode 100644 index 00000000..11c876d4 --- /dev/null +++ b/src/main/java/com/nike/cerberus/domain/environment/EnvironmentData.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2016 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.domain.environment; + +import com.amazonaws.regions.Regions; +import com.fasterxml.jackson.annotation.JsonIgnore; + +import javax.inject.Singleton; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Container for data needed by Cerberus. + */ +@Singleton +public class EnvironmentData { + + private String environmentName; + + private String adminIamRoleArn; + + private String cmsIamRoleArn; + + private String rootIamRoleArn; + + private String databasePassword; + + private LinkedList certificateInfoList = new LinkedList<>(); + + private Map regionData = new HashMap<>();; + + public String getEnvironmentName() { + return environmentName; + } + + public void setEnvironmentName(String environmentName) { + this.environmentName = environmentName; + } + + public String getAdminIamRoleArn() { + return adminIamRoleArn; + } + + public void setAdminIamRoleArn(String adminIamRoleArn) { + this.adminIamRoleArn = adminIamRoleArn; + } + + public String getRootIamRoleArn() { + return rootIamRoleArn; + } + + public void setRootIamRoleArn(String rootIamRoleArn) { + this.rootIamRoleArn = rootIamRoleArn; + } + + public String getCmsIamRoleArn() { + return cmsIamRoleArn; + } + + public void setCmsIamRoleArn(String cmsIamRoleArn) { + this.cmsIamRoleArn = cmsIamRoleArn; + } + + public String getDatabasePassword() { + return databasePassword; + } + + public void setDatabasePassword(String databasePassword) { + this.databasePassword = databasePassword; + } + + public LinkedList getCertificateData() { + return certificateInfoList; + } + + public void addNewCertificateData(CertificateInformation certificateData) { + getCertificateData().add(certificateData); + } + + public void removeCertificateInformationByName(String identityManagementCertificateName) { + List certsInfoToBeDeleted = certificateInfoList.stream() + .filter(certificateInformation -> + certificateInformation.getCertificateName().equals(identityManagementCertificateName)) + .collect(Collectors.toList()); + + certificateInfoList.removeAll(certsInfoToBeDeleted); + } + + public Map getRegionData() { + return regionData; + } + + public void addRegionData(Regions region, RegionData rData) { + regionData.put(region, rData); + } + + /** + * @return List of all the regions that are configured for storing config. + */ + @JsonIgnore + public List getConfigRegions() { + List configRegions = new LinkedList<>(); + regionData.forEach((region, rData) -> { + if (rData.getConfigCmkArn().isPresent() + && rData.getConfigBucket().isPresent()) { + configRegions.add(region); + } + }); + return configRegions; + } + + @JsonIgnore + public Regions getPrimaryRegion() { + return regionData.entrySet().stream() + .filter(entry -> entry.getValue().isPrimary()).findFirst() + .orElseThrow(() -> new RuntimeException("Failed to find primary region in region specific state")) + .getKey(); + } + + public List getManagementServiceCmkArns() { + return regionData.entrySet().stream() + .filter(entry -> entry.getValue().getManagementServiceCmkArn().isPresent()) + .map(entry -> entry.getValue().getManagementServiceCmkArn().get()) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/com/nike/cerberus/domain/environment/LambdaName.java b/src/main/java/com/nike/cerberus/domain/environment/LambdaName.java deleted file mode 100644 index f5e25b12..00000000 --- a/src/main/java/com/nike/cerberus/domain/environment/LambdaName.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.domain.environment; - -/** - * Describes the lambdas that are part of Cerberus. - */ -public enum LambdaName { - WAF("waf", "lambda/waf.jar"), - CLOUD_FRONT_SG_GROUP_IP_SYNC("cf-sg-ip-sync", "lambda/cf-sg-ip-sync.zip"); - - private final String name; - - private final String bucketKey; - - LambdaName(final String name, final String bucketKey) { - this.name = name; - this.bucketKey = bucketKey; - } - - public String getName() { - return name; - } - - public String getBucketKey() { - return bucketKey; - } - - public static LambdaName fromName(final String name) { - for (LambdaName lambdaName : LambdaName.values()) { - if (lambdaName.getName().equals(name)) { - return lambdaName; - } - } - - throw new IllegalArgumentException("Unknown lambda name: " + name); - } -} diff --git a/src/main/java/com/nike/cerberus/domain/environment/RegionData.java b/src/main/java/com/nike/cerberus/domain/environment/RegionData.java new file mode 100644 index 00000000..fec4f4fd --- /dev/null +++ b/src/main/java/com/nike/cerberus/domain/environment/RegionData.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.domain.environment; + +import java.util.Optional; + +public class RegionData { + + private boolean primary; + private String configBucket; + private String configCmkArn; + private String managementServiceCmkArn; + + public boolean isPrimary() { + return primary; + } + + public void setPrimary(boolean primary) { + this.primary = primary; + } + + public Optional getConfigBucket() { + return Optional.ofNullable(configBucket); + } + + public void setConfigBucket(String configBucket) { + this.configBucket = configBucket; + } + + public Optional getConfigCmkArn() { + return Optional.ofNullable(configCmkArn); + } + + public void setConfigCmkArn(String configCmkArn) { + this.configCmkArn = configCmkArn; + } + + public Optional getManagementServiceCmkArn() { + return Optional.ofNullable(managementServiceCmkArn); + } + + public void setManagementServiceCmkArn(String managementServiceCmkArn) { + this.managementServiceCmkArn = managementServiceCmkArn; + } + +} diff --git a/src/main/java/com/nike/cerberus/domain/environment/RegionDeserializer.java b/src/main/java/com/nike/cerberus/domain/environment/RegionDeserializer.java new file mode 100644 index 00000000..2a138013 --- /dev/null +++ b/src/main/java/com/nike/cerberus/domain/environment/RegionDeserializer.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.domain.environment; + +import com.amazonaws.regions.Regions; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; + +import java.io.IOException; + +public class RegionDeserializer extends StdDeserializer { + + public RegionDeserializer() { + this(null); + } + + public RegionDeserializer(Class r) { + super(r); + } + + @Override + public Regions deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { + String name = p.getCodec().readValue(p, String.class); + return Regions.fromName(name); + } +} diff --git a/src/main/java/com/nike/cerberus/domain/environment/RegionKeyDeserializer.java b/src/main/java/com/nike/cerberus/domain/environment/RegionKeyDeserializer.java new file mode 100644 index 00000000..1f280966 --- /dev/null +++ b/src/main/java/com/nike/cerberus/domain/environment/RegionKeyDeserializer.java @@ -0,0 +1,17 @@ +package com.nike.cerberus.domain.environment; + +import com.amazonaws.regions.Regions; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.KeyDeserializer; + +import java.io.IOException; + +public class RegionKeyDeserializer extends KeyDeserializer { + + @Override + public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException, JsonProcessingException { + return Regions.fromName(key); + } + +} diff --git a/src/main/java/com/nike/cerberus/domain/environment/RegionKeySerializer.java b/src/main/java/com/nike/cerberus/domain/environment/RegionKeySerializer.java new file mode 100644 index 00000000..3bed18f1 --- /dev/null +++ b/src/main/java/com/nike/cerberus/domain/environment/RegionKeySerializer.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.domain.environment; + +import com.amazonaws.regions.Regions; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; + +public class RegionKeySerializer extends JsonSerializer { + + @Override + public void serialize(Regions value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException { + gen.writeFieldName(value.getName()); + } +} diff --git a/src/main/java/com/nike/cerberus/domain/environment/VaultSecrets.java b/src/main/java/com/nike/cerberus/domain/environment/RegionSerializer.java similarity index 50% rename from src/main/java/com/nike/cerberus/domain/environment/VaultSecrets.java rename to src/main/java/com/nike/cerberus/domain/environment/RegionSerializer.java index c0c9e5a4..00a8ca1d 100644 --- a/src/main/java/com/nike/cerberus/domain/environment/VaultSecrets.java +++ b/src/main/java/com/nike/cerberus/domain/environment/RegionSerializer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Nike, Inc. + * Copyright (c) 2017 Nike, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,33 +16,25 @@ package com.nike.cerberus.domain.environment; -import java.util.LinkedList; -import java.util.List; +import com.amazonaws.regions.Regions; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; -/** - * Represents sensitive information used by Vault. - */ -public class VaultSecrets { - - private List keys = new LinkedList<>(); +import java.io.IOException; - private String rootToken; - - public List getKeys() { - return keys; - } +public class RegionSerializer extends StdSerializer { - public VaultSecrets setKeys(List keys) { - this.keys = keys; - return this; + public RegionSerializer() { + this(null); } - public String getRootToken() { - return rootToken; + public RegionSerializer(Class t) { + super(t); } - public VaultSecrets setRootToken(String rootToken) { - this.rootToken = rootToken; - return this; + @Override + public void serialize(Regions value, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeString(value.getName()); } } diff --git a/src/main/java/com/nike/cerberus/domain/environment/Secrets.java b/src/main/java/com/nike/cerberus/domain/environment/Secrets.java deleted file mode 100644 index 80994aa7..00000000 --- a/src/main/java/com/nike/cerberus/domain/environment/Secrets.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.domain.environment; - -/** - * Container for sensitive data needed by Cerberus. - */ -public class Secrets { - - private ConsulSecrets consul = new ConsulSecrets(); - - private CmsSecrets cms = new CmsSecrets(); - - private VaultSecrets vault = new VaultSecrets(); - - public ConsulSecrets getConsul() { - return consul; - } - - public Secrets setConsul(ConsulSecrets consul) { - this.consul = consul; - return this; - } - - public CmsSecrets getCms() { - return cms; - } - - public Secrets setCms(CmsSecrets cms) { - this.cms = cms; - return this; - } - - public VaultSecrets getVault() { - return vault; - } - - public Secrets setVault(VaultSecrets vault) { - this.vault = vault; - return this; - } -} diff --git a/src/main/java/com/nike/cerberus/domain/environment/Stack.java b/src/main/java/com/nike/cerberus/domain/environment/Stack.java new file mode 100644 index 00000000..d86b5674 --- /dev/null +++ b/src/main/java/com/nike/cerberus/domain/environment/Stack.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.domain.environment; + +import com.google.common.collect.ImmutableList; +import com.nike.cerberus.ConfigConstants; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.util.stream.Collectors; + +/** + * Describes the stacks that make up Cerberus. + */ +public class Stack implements Comparable { + + public static final Stack IAM_ROLES = new Stack("iam-roles", "iam-roles.yaml", false); + public static final Stack CONFIG = new Stack("config", "config.yaml", false); + public static final Stack VPC = new Stack("vpc", "vpc.yaml", false); + public static final Stack SECURITY_GROUPS = new Stack("security-groups", "security-groups.yaml", false); + public static final Stack DATABASE = new Stack("database", "database.yaml", false); + public static final Stack LOAD_BALANCER = new Stack("load-balancer", "load-balancer.yaml", false); + public static final Stack CMS = new Stack("cms", "cms-cluster.yaml", true); + public static final Stack WAF = new Stack("web-app-firewall", "web-app-firewall.yaml", false); + public static final Stack ROUTE53 = new Stack("route53", "route53.yaml", false); + + public static final ImmutableList ALL_STACKS = ImmutableList.of( + IAM_ROLES, + CONFIG, + VPC, + SECURITY_GROUPS, + DATABASE, + LOAD_BALANCER, + CMS, + WAF, + ROUTE53 + ); + + public static final ImmutableList ALL_STACK_NAMES = ImmutableList.copyOf(ALL_STACKS.stream().map(Stack::getName).collect(Collectors.toList())); + + private static final String TEMPLATE_PATH_ROOT = "/cloudformation/"; + + private final Logger logger = LoggerFactory.getLogger(Stack.class); + + + private final String name; + private final String templatePath; + private final boolean needsUserData; + + + private Stack(final String name, + final String cloudFormationFileName, + final boolean needsUserData) { + this.name = name; + this.templatePath = TEMPLATE_PATH_ROOT + cloudFormationFileName; + this.needsUserData = needsUserData; + } + + public String getName() { + return name; + } + + + public String getTemplatePath() { + return templatePath; + } + + public boolean needsUserData() { + return needsUserData; + } + + /** + * Gets the template contents from the file on the classpath. + * + * @return Template contents + */ + public String getTemplateText() { + final InputStream templateStream = getClass().getResourceAsStream(templatePath); + + if (templateStream == null) { + throw new IllegalStateException( + String.format("The CloudFormation JSON template doesn't exist on the classpath for stack %s, path: %s", name, templatePath)); + } + + try { + return IOUtils.toString(templateStream, ConfigConstants.DEFAULT_ENCODING); + } catch (final IOException e) { + final String errorMessage = String.format("Unable to read input stream from %s", templatePath); + logger.error(errorMessage); + throw new RuntimeException(errorMessage, e); + } + } + + /** + * Generate the CloudFormation stack name for each Cerberus component + * + * @param environmentName The name of the environment in which the component lives (e.g. demo, preprod, devel, etc.) + * @return The generated CloudFormation stack name + */ + public String getFullName(String environmentName) { + String tokenizedEnvName = StringUtils.replaceAll(environmentName, "_", "-"); + return String.format("%s-cerberus-%s", tokenizedEnvName, name); + } + + public static Stack fromName(final String name) { + for (Stack stack : ALL_STACKS) { + // ignore case and all enum style stack names + if (stack.getName().equalsIgnoreCase(StringUtils.replaceAll(name, "_", "-"))) { + return stack; + } + } + throw new IllegalArgumentException("Unknown stack name '" + name + "', please choose from " + ALL_STACK_NAMES); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Stack stack = (Stack) o; + + if (!name.equals(stack.name)) return false; + return templatePath.equals(stack.templatePath); + + } + + @Override + public String toString() { + return getName(); + } + + @Override + public int hashCode() { + int result = name.hashCode(); + result = 31 * result + templatePath.hashCode(); + return result; + } + + @Override + public int compareTo(Stack o) { + return this.name.compareTo(o.name); + } +} diff --git a/src/main/java/com/nike/cerberus/domain/environment/StackKeyDeserializer.java b/src/main/java/com/nike/cerberus/domain/environment/StackKeyDeserializer.java new file mode 100644 index 00000000..f027c044 --- /dev/null +++ b/src/main/java/com/nike/cerberus/domain/environment/StackKeyDeserializer.java @@ -0,0 +1,15 @@ +package com.nike.cerberus.domain.environment; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.KeyDeserializer; + +import java.io.IOException; + +public class StackKeyDeserializer extends KeyDeserializer { + + @Override + public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException, JsonProcessingException { + return Stack.fromName(key); + } +} diff --git a/src/main/java/com/nike/cerberus/domain/environment/StackName.java b/src/main/java/com/nike/cerberus/domain/environment/StackName.java deleted file mode 100644 index 1182227e..00000000 --- a/src/main/java/com/nike/cerberus/domain/environment/StackName.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.domain.environment; - -/** - * Describes the stacks that make up Cerberus. - */ -public enum StackName { - BASE("base"), - CONSUL("consul"), - VAULT("vault"), - CMS("cms"), - GATEWAY("gateway"), - @Deprecated - LAMBDA("lambda"), - @Deprecated - RDSBACKUP("rdsbackup"), // TODO: need to remove but casually deleting will cause JSON parse error - CLOUD_FRONT_IP_SYNCHRONIZER("cloud-front-ip-synchronizer"); - - private final String name; - - StackName(final String name) { - this.name = name; - } - - public String getName() { - return name; - } - - public static StackName fromName(final String name) { - for (StackName stackName : StackName.values()) { - if (stackName.getName().equals(name)) { - return stackName; - } - } - - throw new IllegalArgumentException("Unknown stack name: " + name); - } -} diff --git a/src/main/java/com/nike/cerberus/domain/input/Consul.java b/src/main/java/com/nike/cerberus/domain/input/Consul.java deleted file mode 100644 index d648d28f..00000000 --- a/src/main/java/com/nike/cerberus/domain/input/Consul.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.domain.input; - -/** - * Stores Consul-specific parameters parsed from YAML - */ -public class Consul extends CerberusStack { -} diff --git a/src/main/java/com/nike/cerberus/domain/input/EdgeSecurity.java b/src/main/java/com/nike/cerberus/domain/input/EdgeSecurity.java deleted file mode 100644 index 17a876d4..00000000 --- a/src/main/java/com/nike/cerberus/domain/input/EdgeSecurity.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.domain.input; - - -/** - * Stores CloudFront-specific parameters parsed from YAML - */ -public class EdgeSecurity { - - private String cloudfrontLambdaArtifactUrl; - private String cloudfrontSecurityGroupIpSyncLambdaArtifactUrl; - private String rateLimitPerMinute; - private String rateLimitViolationBlockPeriodInMinutes; - private String googleAnalyticsTrackingId; - private String slackWebHookUrl; - private String slackIcon; - - public String getCloudfrontLambdaArtifactUrl() { - return cloudfrontLambdaArtifactUrl; - } - - public void setCloudfrontLambdaArtifactUrl(String cloudfrontLambdaArtifactUrl) { - this.cloudfrontLambdaArtifactUrl = cloudfrontLambdaArtifactUrl; - } - - public String getCloudfrontSecurityGroupIpSyncLambdaArtifactUrl() { - return cloudfrontSecurityGroupIpSyncLambdaArtifactUrl; - } - - public void setCloudfrontSecurityGroupIpSyncLambdaArtifactUrl(String cloudfrontSecurityGroupIpSyncLambdaArtifactUrl) { - this.cloudfrontSecurityGroupIpSyncLambdaArtifactUrl = cloudfrontSecurityGroupIpSyncLambdaArtifactUrl; - } - - public String getRateLimitPerMinute() { - return rateLimitPerMinute; - } - - public void setRateLimitPerMinute(String rateLimitPerMinute) { - this.rateLimitPerMinute = rateLimitPerMinute; - } - - public String getRateLimitViolationBlockPeriodInMinutes() { - return rateLimitViolationBlockPeriodInMinutes; - } - - public void setRateLimitViolationBlockPeriodInMinutes(String rateLimitViolationBlockPeriodInMinutes) { - this.rateLimitViolationBlockPeriodInMinutes = rateLimitViolationBlockPeriodInMinutes; - } - - public String getGoogleAnalyticsTrackingId() { - return googleAnalyticsTrackingId; - } - - public void setGoogleAnalyticsTrackingId(String googleAnalyticsTrackingId) { - this.googleAnalyticsTrackingId = googleAnalyticsTrackingId; - } - - public String getSlackWebHookUrl() { - return slackWebHookUrl; - } - - public void setSlackWebHookUrl(String slackWebHookUrl) { - this.slackWebHookUrl = slackWebHookUrl; - } - - public String getSlackIcon() { - return slackIcon; - } - - public void setSlackIcon(String slackIcon) { - this.slackIcon = slackIcon; - } -} diff --git a/src/main/java/com/nike/cerberus/domain/input/EnvironmentConfig.java b/src/main/java/com/nike/cerberus/domain/input/EnvironmentConfig.java index b4eb2911..a731be6a 100644 --- a/src/main/java/com/nike/cerberus/domain/input/EnvironmentConfig.java +++ b/src/main/java/com/nike/cerberus/domain/input/EnvironmentConfig.java @@ -16,46 +16,32 @@ package com.nike.cerberus.domain.input; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * Stores all YAML data for a given Cerberus environment */ public class EnvironmentConfig { - private String version; - private ProxyConfig proxyConfig; private String environmentName; - private String region; - private String costCenter; - private String ownerEmail; - private String ownerGroup; + private Map globalTags; private String adminRoleArn; - private String vpcHostedZoneName; - private String hostname; + private String baseDomainName; + private String edgeDomainNameOverride; + private String originDomainNameOverride; + private List additionalSubjectNames; + private String loadBalancerSslPolicyOverride; private String hostedZoneId; - private VpcAccessWhitelist vpcAccessWhitelist; - private Consul consul; - private Vault vault; - private ManagementService managementService; - private Gateway gateway; - private Dashboard dashboard; - private EdgeSecurity edgeSecurity; - - public String getVersion() { - return version; - } - - public void setVersion(String version) { - this.version = version; - } - - public ProxyConfig getProxyConfig() { - return proxyConfig; - } - - public void setProxyConfig(ProxyConfig proxyConfig) { - this.proxyConfig = proxyConfig; - } + private boolean generateKeysAndCerts; + private String acmeApiUrl; + private boolean enableLeCertFix; + private String acmeContactEmail; + private String certificateDirectory; + private VpcAccessWhitelistInput vpcAccessWhitelist; + private ManagementServiceInput managementService; + private Map regionSpecificConfiguration = new HashMap<>(); public String getEnvironmentName() { return environmentName; @@ -65,60 +51,60 @@ public void setEnvironmentName(String environmentName) { this.environmentName = environmentName; } - public String getRegion() { - return region; + public Map getGlobalTags() { + return globalTags; } - public void setRegion(String region) { - this.region = region; + public void setGlobalTags(Map globalTags) { + this.globalTags = globalTags; } - public String getCostCenter() { - return costCenter; + public String getAdminRoleArn() { + return adminRoleArn; } - public void setCostCenter(String costCenter) { - this.costCenter = costCenter; + public void setAdminRoleArn(String adminRoleArn) { + this.adminRoleArn = adminRoleArn; } - public String getOwnerEmail() { - return ownerEmail; + public String getBaseDomainName() { + return baseDomainName; } - public void setOwnerEmail(String ownerEmail) { - this.ownerEmail = ownerEmail; + public void setBaseDomainName(String baseDomainName) { + this.baseDomainName = baseDomainName; } - public String getOwnerGroup() { - return ownerGroup; + public String getEdgeDomainNameOverride() { + return edgeDomainNameOverride; } - public void setOwnerGroup(String ownerGroup) { - this.ownerGroup = ownerGroup; + public void setEdgeDomainNameOverride(String edgeDomainNameOverride) { + this.edgeDomainNameOverride = edgeDomainNameOverride; } - public String getAdminRoleArn() { - return adminRoleArn; + public String getOriginDomainNameOverride() { + return originDomainNameOverride; } - public void setAdminRoleArn(String adminRoleArn) { - this.adminRoleArn = adminRoleArn; + public void setOriginDomainNameOverride(String originDomainNameOverride) { + this.originDomainNameOverride = originDomainNameOverride; } - public String getVpcHostedZoneName() { - return vpcHostedZoneName; + public List getAdditionalSubjectNames() { + return additionalSubjectNames; } - public void setVpcHostedZoneName(String vpcHostedZoneName) { - this.vpcHostedZoneName = vpcHostedZoneName; + public void setAdditionalSubjectNames(List additionalSubjectNames) { + this.additionalSubjectNames = additionalSubjectNames; } - public String getHostname() { - return hostname; + public String getLoadBalancerSslPolicyOverride() { + return loadBalancerSslPolicyOverride; } - public void setHostname(String hostname) { - this.hostname = hostname; + public void setLoadBalancerSslPolicyOverride(String loadBalancerSslPolicyOverride) { + this.loadBalancerSslPolicyOverride = loadBalancerSslPolicyOverride; } public String getHostedZoneId() { @@ -129,59 +115,81 @@ public void setHostedZoneId(String hostedZoneId) { this.hostedZoneId = hostedZoneId; } - public VpcAccessWhitelist getVpcAccessWhitelist() { - return vpcAccessWhitelist; + public boolean isGenerateKeysAndCerts() { + return generateKeysAndCerts; } - public void setVpcAccessWhitelist(VpcAccessWhitelist vpcAccessWhitelist) { - this.vpcAccessWhitelist = vpcAccessWhitelist; + public void setGenerateKeysAndCerts(boolean generateKeysAndCerts) { + this.generateKeysAndCerts = generateKeysAndCerts; } - public Consul getConsul() { - return consul; + public String getAcmeApiUrl() { + return acmeApiUrl; } - public void setConsul(Consul consul) { - this.consul = consul; + public void setAcmeApiUrl(String acmeApiUrl) { + this.acmeApiUrl = acmeApiUrl; } - public Vault getVault() { - return vault; + public boolean isEnableLeCertFix() { + return enableLeCertFix; } - public void setVault(Vault vault) { - this.vault = vault; + public void setEnableLeCertFix(boolean enableLeCertFix) { + this.enableLeCertFix = enableLeCertFix; } - public ManagementService getManagementService() { - return managementService; + public String getAcmeContactEmail() { + return acmeContactEmail; } - public void setManagementService(ManagementService managementService) { - this.managementService = managementService; + public void setAcmeContactEmail(String acmeContactEmail) { + this.acmeContactEmail = acmeContactEmail; + } + + public String getCertificateDirectory() { + return certificateDirectory; } - public Gateway getGateway() { - return gateway; + public void setCertificateDirectory(String certificateDirectory) { + this.certificateDirectory = certificateDirectory; + } + + public VpcAccessWhitelistInput getVpcAccessWhitelist() { + return vpcAccessWhitelist; + } + + public void setVpcAccessWhitelist(VpcAccessWhitelistInput vpcAccessWhitelist) { + this.vpcAccessWhitelist = vpcAccessWhitelist; + } + + public ManagementServiceInput getManagementService() { + return managementService; + } + + public void setManagementService(ManagementServiceInput managementService) { + this.managementService = managementService; } - public void setGateway(Gateway gateway) { - this.gateway = gateway; + public Map getRegionSpecificConfiguration() { + return regionSpecificConfiguration; } - public Dashboard getDashboard() { - return dashboard; + public void setRegionSpecificConfiguration(Map regionSpecificConfiguration) { + this.regionSpecificConfiguration = regionSpecificConfiguration; } - public void setDashboard(Dashboard dashboard) { - this.dashboard = dashboard; + public RegionSpecificConfigurationInput getPrimaryRegionConfig() { + return getPrimaryEntry().getValue(); } - public EdgeSecurity getEdgeSecurity() { - return edgeSecurity; + public String getPrimaryRegion() { + return getPrimaryEntry().getKey(); } - public void setEdgeSecurity(EdgeSecurity edgeSecurity) { - this.edgeSecurity = edgeSecurity; + private Map.Entry getPrimaryEntry() { + return regionSpecificConfiguration.entrySet().stream() + .filter(entry -> entry.getValue() != null && entry.getValue().isPrimary()).findFirst() + .orElseThrow(() -> new RuntimeException("Failed to find primary region in region specific config")); } } diff --git a/src/main/java/com/nike/cerberus/domain/input/Gateway.java b/src/main/java/com/nike/cerberus/domain/input/Gateway.java deleted file mode 100644 index 6672af5a..00000000 --- a/src/main/java/com/nike/cerberus/domain/input/Gateway.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.domain.input; - -public class Gateway extends CerberusStack { -} diff --git a/src/main/java/com/nike/cerberus/domain/input/ManagementService.java b/src/main/java/com/nike/cerberus/domain/input/ManagementServiceInput.java similarity index 92% rename from src/main/java/com/nike/cerberus/domain/input/ManagementService.java rename to src/main/java/com/nike/cerberus/domain/input/ManagementServiceInput.java index fc736102..e6eda0b0 100644 --- a/src/main/java/com/nike/cerberus/domain/input/ManagementService.java +++ b/src/main/java/com/nike/cerberus/domain/input/ManagementServiceInput.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Nike, Inc. + * Copyright (c) 2017 Nike, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ /** * Stores Management Service-specific parameters parsed from YAML */ -public class ManagementService extends CerberusStack { +public class ManagementServiceInput { private String adminGroup; private List properties; @@ -42,4 +42,5 @@ public List getProperties() { public void setProperties(List properties) { this.properties = properties; } + } diff --git a/src/main/java/com/nike/cerberus/domain/input/CerberusStack.java b/src/main/java/com/nike/cerberus/domain/input/ManagementServiceRegionSpecificInput.java similarity index 87% rename from src/main/java/com/nike/cerberus/domain/input/CerberusStack.java rename to src/main/java/com/nike/cerberus/domain/input/ManagementServiceRegionSpecificInput.java index ccebad1b..17f09bd8 100644 --- a/src/main/java/com/nike/cerberus/domain/input/CerberusStack.java +++ b/src/main/java/com/nike/cerberus/domain/input/ManagementServiceRegionSpecificInput.java @@ -17,11 +17,10 @@ package com.nike.cerberus.domain.input; /** - * Stores CloudFormation stack parameters parsed from YAML + * Stores region specific Management Service parameters parsed from YAML */ -public class CerberusStack { +public class ManagementServiceRegionSpecificInput { - private String certPath; private String amiId; private String instanceSize; private String keyPairName; @@ -29,14 +28,6 @@ public class CerberusStack { private String maxInstances; private String minInstances; - public String getCertPath() { - return certPath; - } - - public void setCertPath(String certPath) { - this.certPath = certPath; - } - public String getAmiId() { return amiId; } @@ -84,4 +75,5 @@ public String getMinInstances() { public void setMinInstances(String minInstances) { this.minInstances = minInstances; } + } diff --git a/src/main/java/com/nike/cerberus/domain/input/ProxyConfig.java b/src/main/java/com/nike/cerberus/domain/input/ProxyConfig.java deleted file mode 100644 index c343c25c..00000000 --- a/src/main/java/com/nike/cerberus/domain/input/ProxyConfig.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.domain.input; - -import java.net.Proxy; - -/** - * Stores parameters for a Squid Proxy or other proxy parsed from YAML - */ -public class ProxyConfig { - private boolean enabled = false; - private String host; - private Integer port; - private Proxy.Type type; - - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public String getHost() { - return host; - } - - public void setHost(String host) { - this.host = host; - } - - public Integer getPort() { - return port; - } - - public void setPort(Integer port) { - this.port = port; - } - - public Proxy.Type getType() { - return type; - } - - public void setType(Proxy.Type type) { - this.type = type; - } -} diff --git a/src/main/java/com/nike/cerberus/domain/input/Dashboard.java b/src/main/java/com/nike/cerberus/domain/input/RdsRegionSpecificInput.java similarity index 53% rename from src/main/java/com/nike/cerberus/domain/input/Dashboard.java rename to src/main/java/com/nike/cerberus/domain/input/RdsRegionSpecificInput.java index 980082a2..9af28701 100644 --- a/src/main/java/com/nike/cerberus/domain/input/Dashboard.java +++ b/src/main/java/com/nike/cerberus/domain/input/RdsRegionSpecificInput.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Nike, Inc. + * Copyright (c) 2017 Nike, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,26 +17,25 @@ package com.nike.cerberus.domain.input; /** - * Stores Dashboard-specific parameters parsed from YAML + * Stores the region specific RDS configuration */ -public class Dashboard { +public class RdsRegionSpecificInput { + private String size; + private String dbClusterIdentifier; - private String artifactUrl; - private String overrideArtifactUrl; - - public String getArtifactUrl() { - return artifactUrl; + public String getSize() { + return size; } - public void setArtifactUrl(String artifactUrl) { - this.artifactUrl = artifactUrl; + public void setSize(String size) { + this.size = size; } - public String getOverrideArtifactUrl() { - return overrideArtifactUrl; + public String getDbClusterIdentifier() { + return dbClusterIdentifier; } - public void setOverrideArtifactUrl(String overrideArtifactUrl) { - this.overrideArtifactUrl = overrideArtifactUrl; + public void setDbClusterIdentifier(String dbClusterIdentifier) { + this.dbClusterIdentifier = dbClusterIdentifier; } } diff --git a/src/main/java/com/nike/cerberus/domain/input/RegionSpecificConfigurationInput.java b/src/main/java/com/nike/cerberus/domain/input/RegionSpecificConfigurationInput.java new file mode 100644 index 00000000..567bd17a --- /dev/null +++ b/src/main/java/com/nike/cerberus/domain/input/RegionSpecificConfigurationInput.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.domain.input; + +import java.util.Optional; + +/** + * Stores all the region specific configuration for a given region + */ +public class RegionSpecificConfigurationInput { + + private boolean primary = false; + private RdsRegionSpecificInput rds; + private ManagementServiceRegionSpecificInput managementService; + private String loadBalancerDomainNameOverride; + + public boolean isPrimary() { + return primary; + } + + public void setPrimary(boolean primary) { + this.primary = primary; + } + + public Optional getRds() { + return Optional.ofNullable(rds); + } + + public void setRds(RdsRegionSpecificInput rds) { + this.rds = rds; + } + + public Optional getManagementService() { + return Optional.ofNullable(managementService); + } + + public void setManagementService(ManagementServiceRegionSpecificInput managementService) { + this.managementService = managementService; + } + + public Optional getLoadBalancerDomainNameOverride() { + return Optional.ofNullable(loadBalancerDomainNameOverride); + } + + public void setLoadBalancerDomainNameOverride(String loadBalancerDomainNameOverride) { + this.loadBalancerDomainNameOverride = loadBalancerDomainNameOverride; + } +} diff --git a/src/main/java/com/nike/cerberus/domain/input/Vault.java b/src/main/java/com/nike/cerberus/domain/input/Vault.java deleted file mode 100644 index 6345b910..00000000 --- a/src/main/java/com/nike/cerberus/domain/input/Vault.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.domain.input; - - -/** - * Stores Vault-specific parameters parsed from YAML - */ -public class Vault extends CerberusStack { -} diff --git a/src/main/java/com/nike/cerberus/domain/input/VpcAccessWhitelist.java b/src/main/java/com/nike/cerberus/domain/input/VpcAccessWhitelistInput.java similarity index 96% rename from src/main/java/com/nike/cerberus/domain/input/VpcAccessWhitelist.java rename to src/main/java/com/nike/cerberus/domain/input/VpcAccessWhitelistInput.java index b4333d67..20497373 100644 --- a/src/main/java/com/nike/cerberus/domain/input/VpcAccessWhitelist.java +++ b/src/main/java/com/nike/cerberus/domain/input/VpcAccessWhitelistInput.java @@ -21,7 +21,7 @@ /** * Stores CIDRs to whitelist for VPC access */ -public class VpcAccessWhitelist { +public class VpcAccessWhitelistInput { private List cidrs; private List ports; diff --git a/src/main/java/com/nike/cerberus/domain/template/ConsulConfigurationInput.java b/src/main/java/com/nike/cerberus/domain/template/ConsulConfigurationInput.java deleted file mode 100644 index 85f24aa1..00000000 --- a/src/main/java/com/nike/cerberus/domain/template/ConsulConfigurationInput.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.domain.template; - -/** - * Consul configuration input. - */ -public class ConsulConfigurationInput { - - private String aclMasterToken; - - private String gossipEncryptionToken; - - private String datacenter; - - public String getAclMasterToken() { - return aclMasterToken; - } - - public ConsulConfigurationInput setAclMasterToken(String aclMasterToken) { - this.aclMasterToken = aclMasterToken; - return this; - } - - public String getGossipEncryptionToken() { - return gossipEncryptionToken; - } - - public ConsulConfigurationInput setGossipEncryptionToken(String gossipEncryptionToken) { - this.gossipEncryptionToken = gossipEncryptionToken; - return this; - } - - public String getDatacenter() { - return datacenter; - } - - public ConsulConfigurationInput setDatacenter(String datacenter) { - this.datacenter = datacenter; - return this; - } -} diff --git a/src/main/java/com/nike/cerberus/domain/template/GatewayConfigurationInput.java b/src/main/java/com/nike/cerberus/domain/template/GatewayConfigurationInput.java deleted file mode 100644 index fafe3b66..00000000 --- a/src/main/java/com/nike/cerberus/domain/template/GatewayConfigurationInput.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.domain.template; - -/** - * POJO for gateway configuration inputs. - */ -public class GatewayConfigurationInput { - - private String gatewayHost; - - private String dashboardHost; - - private String vaultHost; - - private String cmsHost; - - public String getGatewayHost() { - return gatewayHost; - } - - public GatewayConfigurationInput setGatewayHost(String gatewayHost) { - this.gatewayHost = gatewayHost; - return this; - } - - public String getDashboardHost() { - return dashboardHost; - } - - public GatewayConfigurationInput setDashboardHost(String dashboardHost) { - this.dashboardHost = dashboardHost; - return this; - } - - public String getVaultHost() { - return vaultHost; - } - - public GatewayConfigurationInput setVaultHost(String vaultHost) { - this.vaultHost = vaultHost; - return this; - } - - public String getCmsHost() { - return cmsHost; - } - - public GatewayConfigurationInput setCmsHost(String cmsHost) { - this.cmsHost = cmsHost; - return this; - } -} diff --git a/src/main/java/com/nike/cerberus/domain/template/VaultAclInput.java b/src/main/java/com/nike/cerberus/domain/template/VaultAclInput.java deleted file mode 100644 index e32f1929..00000000 --- a/src/main/java/com/nike/cerberus/domain/template/VaultAclInput.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.domain.template; - -/** - * Input for the template generation of the Vault ACL entry. - */ -public class VaultAclInput { - - private String aclToken; - - public String getAclToken() { - return aclToken; - } - - public VaultAclInput setAclToken(String aclToken) { - this.aclToken = aclToken; - return this; - } -} diff --git a/src/main/java/com/nike/cerberus/domain/template/VaultConfigurationInput.java b/src/main/java/com/nike/cerberus/domain/template/VaultConfigurationInput.java deleted file mode 100644 index 41d098f4..00000000 --- a/src/main/java/com/nike/cerberus/domain/template/VaultConfigurationInput.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.domain.template; - -/** - * Represents the inputs for the Vault configuration template. - */ -public class VaultConfigurationInput { - - private String datacenter; - - private String aclToken; - - public String getDatacenter() { - return datacenter; - } - - public VaultConfigurationInput setDatacenter(String datacenter) { - this.datacenter = datacenter; - return this; - } - - public String getAclToken() { - return aclToken; - } - - public VaultConfigurationInput setAclToken(String aclToken) { - this.aclToken = aclToken; - return this; - } -} diff --git a/src/main/java/com/nike/cerberus/domain/vault/AuditBackend.java b/src/main/java/com/nike/cerberus/domain/vault/AuditBackend.java deleted file mode 100644 index d108e263..00000000 --- a/src/main/java/com/nike/cerberus/domain/vault/AuditBackend.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.domain.vault; - -/** - * Supported backends for audit in Vault. - */ -public enum AuditBackend { - FILE("file"), - SYSLOG("syslog"); - - private final String type; - - AuditBackend(final String type) { - this.type = type; - } - - public String getType() { - return type; - } -} diff --git a/src/main/java/com/nike/cerberus/generator/ConfigGenerationException.java b/src/main/java/com/nike/cerberus/generator/ConfigGenerationException.java deleted file mode 100644 index b6216362..00000000 --- a/src/main/java/com/nike/cerberus/generator/ConfigGenerationException.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.generator; - -/** - * Error thrown when config generation fails. - */ -public class ConfigGenerationException extends RuntimeException { - public ConfigGenerationException(String message, Throwable throwable) { - super(message, throwable); - } -} diff --git a/src/main/java/com/nike/cerberus/generator/ConsulConfigGenerator.java b/src/main/java/com/nike/cerberus/generator/ConsulConfigGenerator.java deleted file mode 100644 index 191ae156..00000000 --- a/src/main/java/com/nike/cerberus/generator/ConsulConfigGenerator.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.generator; - -import com.github.mustachejava.Mustache; -import com.github.mustachejava.MustacheFactory; -import com.google.inject.Inject; -import com.nike.cerberus.domain.configuration.ConsulConfiguration; -import com.nike.cerberus.domain.template.ConsulConfigurationInput; -import com.nike.cerberus.util.TokenSupplier; -import com.nike.cerberus.util.UuidSupplier; - -import java.io.IOException; -import java.io.StringWriter; - -/** - * Handles generating the Consul configuration files. - */ -public class ConsulConfigGenerator { - - private static final String serverTemplatePath = "templates/consul-server.json.mustache"; - - private static final String clientTemplatePath = "templates/consul-client.json.mustache"; - - private final UuidSupplier uuidSupplier; - - private final TokenSupplier tokenSupplier; - - private final MustacheFactory mustacheFactory; - - @Inject - public ConsulConfigGenerator(final UuidSupplier uuidSupplier, - final TokenSupplier tokenSupplier, - final MustacheFactory mustacheFactory) { - this.uuidSupplier = uuidSupplier; - this.tokenSupplier = tokenSupplier; - this.mustacheFactory = mustacheFactory; - } - - /** - * Generates a configuration object that contains the newly generated gossip encryption token, - * acl master token, and string representations of the full JSON configuration for Consul. - */ - public ConsulConfiguration generate(final String datacenter) { - final String aclMasterToken = uuidSupplier.get(); - final String gossipEncryptionToken = tokenSupplier.get(); - return generate(datacenter, aclMasterToken, gossipEncryptionToken); - } - - /** - * Generates a configuration object that contains the supplied gossip encryption token, - * acl master token, and string representations of the full JSON configuration for Consul. - */ - public ConsulConfiguration generate(final String datacenter, - final String aclMasterToken, - final String gossipEncryptionToken) { - final ConsulConfigurationInput input = new ConsulConfigurationInput() - .setAclMasterToken(aclMasterToken) - .setGossipEncryptionToken(gossipEncryptionToken) - .setDatacenter(datacenter); - final Mustache serverTemplateCompiler = mustacheFactory.compile(serverTemplatePath); - final Mustache clientTemplateCompiler = mustacheFactory.compile(clientTemplatePath); - final StringWriter serverConfigWriter = new StringWriter(); - final StringWriter clientConfigWriter = new StringWriter(); - - try { - serverTemplateCompiler.execute(serverConfigWriter, input).flush(); - clientTemplateCompiler.execute(clientConfigWriter, input).flush(); - } catch (IOException ioe) { - throw new ConfigGenerationException("Failed to generate Consul configuration!", ioe); - } - - return new ConsulConfiguration() - .setInput(input) - .setServerConfiguration(serverConfigWriter.toString()) - .setClientConfiguration(clientConfigWriter.toString()); - } -} diff --git a/src/main/java/com/nike/cerberus/generator/GatewayConfigGenerator.java b/src/main/java/com/nike/cerberus/generator/GatewayConfigGenerator.java deleted file mode 100644 index b90d7897..00000000 --- a/src/main/java/com/nike/cerberus/generator/GatewayConfigGenerator.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.generator; - -import com.github.mustachejava.Mustache; -import com.github.mustachejava.MustacheFactory; -import com.google.inject.Inject; -import com.nike.cerberus.domain.configuration.GatewayConfiguration; -import com.nike.cerberus.domain.template.GatewayConfigurationInput; - -import java.io.IOException; -import java.io.StringWriter; - -/** - * Handles generating the Gateway configuration files. - */ -public class GatewayConfigGenerator { - - private static final String siteConfigTemplatePath = "templates/site-gateway.conf.mustache"; - - private static final String globalConfigTemplatePath = "templates/nginx.conf.mustache"; - - private final MustacheFactory mustacheFactory; - - @Inject - public GatewayConfigGenerator(final MustacheFactory mustacheFactory) { - this.mustacheFactory = mustacheFactory; - } - - /** - * Generates a configuration object that contains the newly generated gossip encryption token, - * acl master token and a string representation of the full JSON configuration for Consul. - */ - public GatewayConfiguration generate(final GatewayConfigurationInput input) { - final Mustache siteConfigTemplateCompiler = mustacheFactory.compile(siteConfigTemplatePath); - final Mustache globalConfigTemplateCompiler = mustacheFactory.compile(globalConfigTemplatePath); - final StringWriter siteConfigWriter = new StringWriter(); - final StringWriter globalConfigWriter = new StringWriter(); - - try { - siteConfigTemplateCompiler.execute(siteConfigWriter, input).flush(); - globalConfigTemplateCompiler.execute(globalConfigWriter, input).flush(); - } catch (IOException ioe) { - throw new ConfigGenerationException("Failed to generate Consul configuration!", ioe); - } - - return new GatewayConfiguration() - .setSiteConfig(siteConfigWriter.toString()) - .setGlobalConfig(globalConfigWriter.toString()); - } -} diff --git a/src/main/java/com/nike/cerberus/generator/VaultAclGenerator.java b/src/main/java/com/nike/cerberus/generator/VaultAclGenerator.java deleted file mode 100644 index a1542110..00000000 --- a/src/main/java/com/nike/cerberus/generator/VaultAclGenerator.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.generator; - -import com.github.mustachejava.Mustache; -import com.github.mustachejava.MustacheFactory; -import com.google.inject.Inject; -import com.nike.cerberus.domain.configuration.VaultAclEntry; -import com.nike.cerberus.domain.template.VaultAclInput; -import com.nike.cerberus.util.UuidSupplier; - -import java.io.IOException; -import java.io.StringWriter; - -/** - * Generates the Vault ACL entry and token. - */ -public class VaultAclGenerator { - - private static final String vaultAclTemplatePath = "templates/vault-acl.json.mustache"; - - private final UuidSupplier uuidSupplier; - - private final MustacheFactory mustacheFactory; - - @Inject - public VaultAclGenerator(final UuidSupplier uuidSupplier, - final MustacheFactory mustacheFactory) { - this.uuidSupplier = uuidSupplier; - this.mustacheFactory = mustacheFactory; - } - - /** - * Generates the Vault ACL token and entry JSON. - * - * @return POJO with the token and JSON - */ - public VaultAclEntry generate() { - final String aclToken = uuidSupplier.get(); - final VaultAclInput input = new VaultAclInput().setAclToken(aclToken); - final Mustache vaultAclEntryCompiler = mustacheFactory.compile(vaultAclTemplatePath); - final StringWriter vaulAclEntryStringWriter = new StringWriter(); - - try { - vaultAclEntryCompiler.execute(vaulAclEntryStringWriter, input).flush(); - } catch (final IOException ioe) { - throw new ConfigGenerationException("Failed to generate Vault ACL entry!", ioe); - } - - return new VaultAclEntry().setAclToken(aclToken).setEntry(vaulAclEntryStringWriter.toString()); - } -} diff --git a/src/main/java/com/nike/cerberus/generator/VaultConfigGenerator.java b/src/main/java/com/nike/cerberus/generator/VaultConfigGenerator.java deleted file mode 100644 index df7cccad..00000000 --- a/src/main/java/com/nike/cerberus/generator/VaultConfigGenerator.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.generator; - -import com.github.mustachejava.Mustache; -import com.github.mustachejava.MustacheFactory; -import com.google.inject.Inject; -import com.nike.cerberus.domain.configuration.VaultConfiguration; -import com.nike.cerberus.domain.template.VaultConfigurationInput; - -import java.io.IOException; -import java.io.StringWriter; - -/** - * Generates the Vault configuration. - */ -public class VaultConfigGenerator { - - private static final String vaultTemplatePath = "templates/vault.json.mustache"; - - private final MustacheFactory mustacheFactory; - - @Inject - public VaultConfigGenerator(final MustacheFactory mustacheFactory) { - this.mustacheFactory = mustacheFactory; - } - - public VaultConfiguration generate(final String consulDatacenter, final String vaultAclToken) { - final VaultConfigurationInput input = new VaultConfigurationInput() - .setDatacenter(consulDatacenter) - .setAclToken(vaultAclToken); - final Mustache templateCompiler = mustacheFactory.compile(vaultTemplatePath); - final StringWriter configWriter = new StringWriter(); - - try { - templateCompiler.execute(configWriter, input).flush(); - } catch (IOException ioe) { - throw new ConfigGenerationException("Failed to generate Vault configuration!", ioe); - } - - return new VaultConfiguration().setConfig(configWriter.toString()); - } -} diff --git a/src/main/java/com/nike/cerberus/logging/LoggingConfigurer.java b/src/main/java/com/nike/cerberus/logging/LoggingConfigurer.java index 40f1be4b..69b910b9 100644 --- a/src/main/java/com/nike/cerberus/logging/LoggingConfigurer.java +++ b/src/main/java/com/nike/cerberus/logging/LoggingConfigurer.java @@ -31,7 +31,8 @@ */ public final class LoggingConfigurer { - private LoggingConfigurer() {} + private LoggingConfigurer() { + } /** * Initializes the logger and the requested log level. diff --git a/src/main/java/com/nike/cerberus/module/CerberusModule.java b/src/main/java/com/nike/cerberus/module/CerberusModule.java index 50582595..8ee35715 100644 --- a/src/main/java/com/nike/cerberus/module/CerberusModule.java +++ b/src/main/java/com/nike/cerberus/module/CerberusModule.java @@ -16,48 +16,42 @@ package com.nike.cerberus.module; -import com.amazonaws.AmazonWebServiceClient; -import com.amazonaws.ClientConfiguration; -import com.amazonaws.auth.AWSCredentialsProviderChain; -import com.amazonaws.auth.EnvironmentVariableCredentialsProvider; -import com.amazonaws.auth.InstanceProfileCredentialsProvider; -import com.amazonaws.auth.STSAssumeRoleSessionCredentialsProvider; -import com.amazonaws.auth.SystemPropertiesCredentialsProvider; -import com.amazonaws.auth.profile.ProfileCredentialsProvider; -import com.amazonaws.regions.Region; import com.amazonaws.regions.Regions; -import com.amazonaws.services.autoscaling.AmazonAutoScaling; import com.amazonaws.services.autoscaling.AmazonAutoScalingClient; -import com.amazonaws.services.cloudformation.AmazonCloudFormation; import com.amazonaws.services.cloudformation.AmazonCloudFormationClient; -import com.amazonaws.services.ec2.AmazonEC2; import com.amazonaws.services.ec2.AmazonEC2Client; -import com.amazonaws.services.identitymanagement.AmazonIdentityManagement; +import com.amazonaws.services.elasticloadbalancingv2.AmazonElasticLoadBalancingClient; import com.amazonaws.services.identitymanagement.AmazonIdentityManagementClient; -import com.amazonaws.services.kms.AWSKMS; import com.amazonaws.services.kms.AWSKMSClient; -import com.amazonaws.services.lambda.AWSLambda; import com.amazonaws.services.lambda.AWSLambdaClient; -import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.rds.AmazonRDSClient; +import com.amazonaws.services.route53.AmazonRoute53Client; import com.amazonaws.services.s3.AmazonS3Client; -import com.amazonaws.services.s3.model.Bucket; -import com.amazonaws.services.securitytoken.AWSSecurityTokenService; import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClient; -import com.amazonaws.services.sns.AmazonSNS; import com.amazonaws.services.sns.AmazonSNSClient; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.joda.JodaModule; import com.github.mustachejava.DefaultMustacheFactory; import com.github.mustachejava.MustacheFactory; import com.google.inject.AbstractModule; import com.google.inject.Provides; -import com.nike.cerberus.ConfigConstants; +import com.google.inject.TypeLiteral; +import com.google.inject.multibindings.OptionalBinder; +import com.google.inject.name.Names; +import com.google.inject.util.Providers; +import com.nike.cerberus.command.CerberusCommand; import com.nike.cerberus.command.ProxyDelegate; -import com.nike.cerberus.domain.EnvironmentMetadata; -import com.nike.cerberus.util.TokenSupplier; +import com.nike.cerberus.domain.environment.RegionDeserializer; +import com.nike.cerberus.domain.environment.RegionKeyDeserializer; +import com.nike.cerberus.domain.environment.RegionKeySerializer; +import com.nike.cerberus.domain.environment.RegionSerializer; +import com.nike.cerberus.domain.input.EnvironmentConfig; +import com.nike.cerberus.service.AwsClientFactory; import com.nike.cerberus.util.UuidSupplier; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -67,22 +61,16 @@ import javax.inject.Singleton; import java.net.InetSocketAddress; import java.net.Proxy; -import java.util.List; -import java.util.Optional; -import java.util.UUID; /** * Guice module for enabling DI. */ public class CerberusModule extends AbstractModule { - public static final String CF_OBJECT_MAPPER = "cloudformationObjectMapper"; - public static final String CONFIG_OBJECT_MAPPER = "configObjectMapper"; - - public static final String CERBERUS_ASSUME_ROLE_ARN = "CERBERUS_ASSUME_ROLE_ARN"; - - public static final String CERBERUS_ASSUME_ROLE_EXTERNAL_ID = "CERBERUS_ASSUME_ROLE_EXTERNAL_ID"; + public static final String ENV_NAME = "environmentName"; + public static final String CONFIG_REGION = "configRegionName"; + public static final String IS_TTY = "isTty"; private final Logger logger = LoggerFactory.getLogger(getClass()); @@ -90,12 +78,18 @@ public class CerberusModule extends AbstractModule { private final String environmentName; - private final String regionName; + private final String configRegionName; + + private final EnvironmentConfig environmentConfig; + + private final boolean isTty; - public CerberusModule(ProxyDelegate proxyDelegate, String environmentName, String regionName) { - this.proxyDelegate = proxyDelegate; - this.environmentName = environmentName; - this.regionName = regionName; + public CerberusModule(CerberusCommand cerberusCommand) { + proxyDelegate = cerberusCommand.getProxyDelegate(); + environmentName = cerberusCommand.getEnvironmentName(); + configRegionName = cerberusCommand.getConfigRegion(); + environmentConfig = cerberusCommand.getEnvironmentConfig(); + isTty = cerberusCommand.isTty(); } /** @@ -103,31 +97,31 @@ public CerberusModule(ProxyDelegate proxyDelegate, String environmentName, Strin */ @Override protected void configure() { - final Region region = Region.getRegion(Regions.fromName(regionName)); - bind(AmazonEC2.class).toInstance(createAmazonClientInstance(AmazonEC2Client.class, region)); - bind(AmazonCloudFormation.class).toInstance(createAmazonClientInstance(AmazonCloudFormationClient.class, region)); - bind(AmazonIdentityManagement.class).toInstance(createAmazonClientInstance(AmazonIdentityManagementClient.class, region)); - bind(AWSKMS.class).toInstance(createAmazonClientInstance(AWSKMSClient.class, region)); - bind(AmazonS3.class).toInstance(createAmazonClientInstance(AmazonS3Client.class, region)); - bind(AmazonAutoScaling.class).toInstance(createAmazonClientInstance(AmazonAutoScalingClient.class, region)); - bind(AWSSecurityTokenService.class).toInstance(createAmazonClientInstance(AWSSecurityTokenServiceClient.class, region)); - bind(AWSLambda.class).toInstance(createAmazonClientInstance(AWSLambdaClient.class, region)); - bind(AmazonSNS.class).toInstance(createAmazonClientInstance(AmazonSNSClient.class, region)); + // If a environment yaml was provided make it injectable as an Optional + OptionalBinder.newOptionalBinder(binder(), EnvironmentConfig.class); + bind(EnvironmentConfig.class).toProvider(Providers.of(environmentConfig)); + + bindConstant().annotatedWith(Names.named(IS_TTY)).to(isTty); + bindConstant().annotatedWith(Names.named(ENV_NAME)).to(environmentName); + bindConstant().annotatedWith(Names.named(CONFIG_REGION)).to(configRegionName); + + // bind the aws client factories + bindAwsClientFactories(); } - /** - * Object mapper for handling CloudFormation parameters and outputs. - * - * @return Object mapper - */ - @Provides - @Singleton - @Named(CF_OBJECT_MAPPER) - public ObjectMapper cloudFormationObjectMapper() { - final ObjectMapper om = new ObjectMapper(); - om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - om.setSerializationInclusion(JsonInclude.Include.NON_ABSENT); - return om; + private void bindAwsClientFactories() { + bind(new TypeLiteral>() {}).toInstance(new AwsClientFactory() {}); + bind(new TypeLiteral>() {}).toInstance(new AwsClientFactory() {}); + bind(new TypeLiteral>() {}).toInstance(new AwsClientFactory() {}); + bind(new TypeLiteral>() {}).toInstance(new AwsClientFactory() {}); + bind(new TypeLiteral>() {}).toInstance(new AwsClientFactory() {}); + bind(new TypeLiteral>() {}).toInstance(new AwsClientFactory() {}); + bind(new TypeLiteral>() {}).toInstance(new AwsClientFactory() {}); + bind(new TypeLiteral>() {}).toInstance(new AwsClientFactory() {}); + bind(new TypeLiteral>() {}).toInstance(new AwsClientFactory() {}); + bind(new TypeLiteral>() {}).toInstance(new AwsClientFactory() {}); + bind(new TypeLiteral>() {}).toInstance(new AwsClientFactory() {}); + bind(new TypeLiteral>() {}).toInstance(new AwsClientFactory() {}); } /** @@ -141,6 +135,16 @@ public ObjectMapper cloudFormationObjectMapper() { public static ObjectMapper configObjectMapper() { final ObjectMapper om = new ObjectMapper(); om.findAndRegisterModules(); + om.registerModule(new JodaModule()); + om.registerModule(new Jdk8Module()); + + SimpleModule cerberusCustom = new SimpleModule(); + cerberusCustom.addSerializer(Regions.class, new RegionSerializer()); + cerberusCustom.addDeserializer(Regions.class, new RegionDeserializer()); + cerberusCustom.addKeyDeserializer(Regions.class, new RegionKeyDeserializer()); + cerberusCustom.addKeySerializer(Regions.class, new RegionKeySerializer()); + + om.registerModule(cerberusCustom); om.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); om.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); om.enable(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS); @@ -150,38 +154,12 @@ public static ObjectMapper configObjectMapper() { return om; } - /** - * Environment metadata object for describing the environment being executed against. - * - * @return Environment metadata - */ - @Provides - @Singleton - public EnvironmentMetadata environmentMetadata() { - final Optional bucketName = findBucket(environmentName); - final EnvironmentMetadata environmentMetadata = new EnvironmentMetadata(environmentName, regionName); - - if (bucketName.isPresent()) { - environmentMetadata.setBucketName(bucketName.get()); - } else { - logger.warn("Unable to determine the environment bucket for {}.", environmentName); - } - - return environmentMetadata; - } - @Provides @Singleton public MustacheFactory mustacheFactory() { return new DefaultMustacheFactory(); } - @Provides - @Singleton - public TokenSupplier tokenSupplier() { - return new TokenSupplier(); - } - @Provides @Singleton public UuidSupplier uuidSupplier() { @@ -208,47 +186,4 @@ public Proxy proxy() { return new Proxy(type, new InetSocketAddress(host, port)); } - private Optional findBucket(final String environmentName) { - AmazonS3Client s3Client = new AmazonS3Client(); - List buckets = s3Client.listBuckets(); - - String envBucket = null; - for (final Bucket bucket : buckets) { - if (StringUtils.contains(bucket.getName(), ConfigConstants.CONFIG_BUCKET_KEY)) { - String[] parts = bucket.getName().split("-"); - if (StringUtils.equalsIgnoreCase(environmentName, parts[0])) { - envBucket = bucket.getName(); - break; - } - } - } - - return Optional.ofNullable(envBucket); - } - - private static M createAmazonClientInstance(Class clientClass, Region region) { - return region.createClient(clientClass, getAWSCredentialsProviderChain(), new ClientConfiguration()); - } - - public static AWSCredentialsProviderChain getAWSCredentialsProviderChain() { - String cerberusRoleToAssume = System.getenv(CERBERUS_ASSUME_ROLE_ARN) != null ? - System.getenv(CERBERUS_ASSUME_ROLE_ARN) : ""; - String cerberusRoleToAssumeExternalId = System.getenv(CERBERUS_ASSUME_ROLE_EXTERNAL_ID) != null ? - System.getenv(CERBERUS_ASSUME_ROLE_EXTERNAL_ID) : ""; - - STSAssumeRoleSessionCredentialsProvider sTSAssumeRoleSessionCredentialsProvider = - new STSAssumeRoleSessionCredentialsProvider - .Builder(cerberusRoleToAssume, UUID.randomUUID().toString()) - .withExternalId(cerberusRoleToAssumeExternalId) - .build(); - - AWSCredentialsProviderChain chain = new AWSCredentialsProviderChain( - new EnvironmentVariableCredentialsProvider(), - new SystemPropertiesCredentialsProvider(), - new ProfileCredentialsProvider(), - sTSAssumeRoleSessionCredentialsProvider, - new InstanceProfileCredentialsProvider()); - - return chain; - } } diff --git a/src/main/java/com/nike/cerberus/operation/certificates/DeleteOldestCertificatesOperation.java b/src/main/java/com/nike/cerberus/operation/certificates/DeleteOldestCertificatesOperation.java new file mode 100644 index 00000000..5ee5dee0 --- /dev/null +++ b/src/main/java/com/nike/cerberus/operation/certificates/DeleteOldestCertificatesOperation.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.operation.certificates; + +import com.google.inject.Inject; +import com.nike.cerberus.command.certificates.DeleteOldestCertificatesCommand; +import com.nike.cerberus.domain.environment.CertificateInformation; +import com.nike.cerberus.operation.Operation; +import com.nike.cerberus.service.CertificateService; +import com.nike.cerberus.store.ConfigStore; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +/** + * Operation for the Delete Oldest Certificates Command + * + * Deletes the oldest certificates from S3 and the identity management service. + */ +public class DeleteOldestCertificatesOperation implements Operation { + + protected final Logger log = LoggerFactory.getLogger(getClass()); + + private final ConfigStore configStore; + private final CertificateService certificateService; + + @Inject + public DeleteOldestCertificatesOperation(ConfigStore configStore, + CertificateService certificateService) { + + this.configStore = configStore; + this.certificateService = certificateService; + } + + @Override + public void run(DeleteOldestCertificatesCommand command) { + List certificateInformationList = configStore.getCertificationInformationList(); + int indexBeforeLast = certificateInformationList.size() - 1; + certificateInformationList.subList(0, indexBeforeLast).forEach(certificateInformation -> { + certificateService.deleteCertificate(certificateInformation.getCertificateName(), + command.getDeleteParametersDelegate().isRevokeCertificates(), + command.getDeleteParametersDelegate().getAcmeUrl()); + }); + } + + @Override + public boolean isRunnable(DeleteOldestCertificatesCommand command) { + List certificateInformationList = configStore.getCertificationInformationList(); + + boolean isRunnable = true; + + if (certificateInformationList.size() < 2) { + log.error("The certificate list did not have at least 2 certs, " + + "cannot delete oldest certs, aborting..."); + isRunnable = false; + } + + if (command.getDeleteParametersDelegate().isRevokeCertificates() && + StringUtils.isBlank(command.getDeleteParametersDelegate().getAcmeUrl())) { + log.error("You must provide an ACME api url if you wish to revoke the cert"); + isRunnable = false; + } + + return isRunnable; + } +} diff --git a/src/main/java/com/nike/cerberus/operation/certificates/GenerateAndRotateCertificatesOperation.java b/src/main/java/com/nike/cerberus/operation/certificates/GenerateAndRotateCertificatesOperation.java new file mode 100644 index 00000000..d76fe2e3 --- /dev/null +++ b/src/main/java/com/nike/cerberus/operation/certificates/GenerateAndRotateCertificatesOperation.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.operation.certificates; + +import com.google.common.collect.Lists; +import com.nike.cerberus.command.certificates.DeleteOldestCertificatesCommandParametersDelegate; +import com.nike.cerberus.command.certificates.GenerateAndRotateCertificatesCommand; +import com.nike.cerberus.command.certificates.RotateCertificatesCommand; +import com.nike.cerberus.command.core.GenerateCertificateFilesCommand; +import com.nike.cerberus.command.core.GenerateCertificateFilesCommandParametersDelegate; +import com.nike.cerberus.domain.environment.Stack; +import com.nike.cerberus.operation.composite.ChainableCommand; +import com.nike.cerberus.operation.composite.CompositeOperation; +import com.nike.cerberus.service.CloudFormationService; +import com.nike.cerberus.store.ConfigStore; +import org.apache.commons.lang3.StringUtils; + +import javax.inject.Inject; +import javax.inject.Named; +import java.util.List; + +import static com.nike.cerberus.command.certificates.DeleteOldestCertificatesCommandParametersDelegate.REVOKE_LONG_ARG; +import static com.nike.cerberus.command.certificates.UploadCertificateFilesCommandParametersDelegate.CERT_PATH_LONG_ARG; +import static com.nike.cerberus.command.core.GenerateCertificateFilesCommandParametersDelegate.*; +import static com.nike.cerberus.module.CerberusModule.ENV_NAME; + +public class GenerateAndRotateCertificatesOperation extends CompositeOperation { + + private final CloudFormationService cloudFormationService; + private final String environmentName; + private final ConfigStore configStore; + + @Inject + public GenerateAndRotateCertificatesOperation(CloudFormationService cloudFormationService, + @Named(ENV_NAME) String environmentName, + ConfigStore configStore) { + + this.cloudFormationService = cloudFormationService; + this.environmentName = environmentName; + this.configStore = configStore; + } + + + @Override + protected List getCompositeCommandChain(GenerateAndRotateCertificatesCommand compositeCommand) { + GenerateCertificateFilesCommandParametersDelegate parameters = compositeCommand + .getGenerateCertificateFilesCommandParametersDelegate(); + + ChainableCommand.Builder generateCertificateFilesCommandBuilder = ChainableCommand.Builder.create() + .withCommand(new GenerateCertificateFilesCommand()) + .withOption( + HOSTED_ZONE_ID_LONG_ARG, + parameters.getHostedZoneId() + ) + .withOption( + CERT_FOLDER_LONG_ARG, + parameters.getCertDir() + ) + .withOption( + ACME_API_LONG_ARG, + parameters.getAcmeApiUrl() + ) + .withOption( + CONTACT_EMAIL_LONG_ARG, + parameters.getContactEmail() + ); + + if (parameters.enableLetsEncryptCertfix()) { + generateCertificateFilesCommandBuilder.withAdditionalArg(ENABLE_LE_CERTFIX_LONG_ARG); + } + + if (StringUtils.isNotBlank(parameters.getBaseDomainName())) { + generateCertificateFilesCommandBuilder.withOption( + BASE_DOMAIN_LONG_ARG, + parameters.getBaseDomainName() + ); + } + + if (StringUtils.isNotBlank(parameters.getEdgeDomainNameOverride())) { + generateCertificateFilesCommandBuilder.withOption( + EDGE_DOMAIN_NAME_OVERRIDE_LONG_ARG, + parameters.getEdgeDomainNameOverride() + ); + } + + if (StringUtils.isNotBlank(parameters.getOriginDomainNameOverride())) { + generateCertificateFilesCommandBuilder.withOption( + ORIGIN_DOMAIN_NAME_OVERRIDE_LONG_ARG, + parameters.getOriginDomainNameOverride() + ); + } + + if (StringUtils.isNotBlank(parameters.getLoadBalancerDomainNameOverride())) { + generateCertificateFilesCommandBuilder.withOption( + LOAD_BALANCER_DOMAIN_NAME_OVERRIDE_LONG_ARG, + parameters.getLoadBalancerDomainNameOverride() + ); + } + + if (parameters.isAutoAcceptAcmeTos()) { + generateCertificateFilesCommandBuilder.withAdditionalArg(ACCEPT_ACME_TOS); + } + + parameters.getSubjectAlternativeNames().forEach(name -> { + generateCertificateFilesCommandBuilder.withOption(SUBJECT_ALT_NAME_LONG_ARG, name); + }); + + return Lists.newArrayList( + // Generate the Certificates + generateCertificateFilesCommandBuilder.build(), + + // Rotate the certificate files + ChainableCommand.Builder.create().withCommand(new RotateCertificatesCommand()) + .withOption( + CERT_PATH_LONG_ARG, + parameters.getCertDir()) + .withAdditionalArg(REVOKE_LONG_ARG) + .withOption( + DeleteOldestCertificatesCommandParametersDelegate.ACME_API_LONG_ARG, + parameters.getAcmeApiUrl()) + .build() + ); + } + + @Override + public boolean isRunnable(GenerateAndRotateCertificatesCommand command) { + boolean isRunnable = true; + + if (! cloudFormationService.isStackPresent(configStore.getPrimaryRegion(), Stack.LOAD_BALANCER.getFullName(environmentName))) { + log.error("The load-balancer stack must be present in order to rotate certificates"); + isRunnable = false; + } + + if (! cloudFormationService.isStackPresent(configStore.getPrimaryRegion(), Stack.CMS.getFullName(environmentName))) { + log.error("The cms stack must be present to rotate certificates"); + isRunnable = false; + } + + return isRunnable; + } + + @Override + public boolean isEnvironmentConfigRequired() { + return false; + } +} diff --git a/src/main/java/com/nike/cerberus/operation/certificates/RotateAcmeAccountPrivateKeyOperation.java b/src/main/java/com/nike/cerberus/operation/certificates/RotateAcmeAccountPrivateKeyOperation.java new file mode 100644 index 00000000..9cf493e6 --- /dev/null +++ b/src/main/java/com/nike/cerberus/operation/certificates/RotateAcmeAccountPrivateKeyOperation.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.operation.certificates; + +import com.nike.cerberus.command.certificates.RotateAcmeAccountPrivateKeyCommand; +import com.nike.cerberus.operation.Operation; +import com.nike.cerberus.service.CertificateService; +import com.nike.cerberus.store.ConfigStore; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; + +public class RotateAcmeAccountPrivateKeyOperation implements Operation { + + private final Logger log = LoggerFactory.getLogger(getClass()); + + private final ConfigStore configStore; + private final CertificateService certificateService; + + @Inject + public RotateAcmeAccountPrivateKeyOperation(ConfigStore configStore, + CertificateService certificateService) { + this.configStore = configStore; + this.certificateService = certificateService; + } + + @Override + public void run(RotateAcmeAccountPrivateKeyCommand command) { + log.info("Preparing to rotate ACME account private key."); + + + // Enable the use of the hard coded lets encrypt cert if enabled + if (command.isEnableLetsEncryptCertfix()) { + log.warn("Setting acme4j.le.certfix system property to 'true', this only works if you use the special " + + "acme:// lets encrypt addr. See: https://shredzone.org/maven/acme4j/usage/session.html" + + " and https://shredzone.org/maven/acme4j/provider.html"); + + System.setProperty("acme4j.le.certfix", "true"); + } + + certificateService.rotateAcmeAccountKeyPair(command.getAcmeUrl()); + log.info("ACME account private key rotated."); + } + + @Override + public boolean isRunnable(RotateAcmeAccountPrivateKeyCommand command) { + boolean isRunnable = true; + + if (!configStore.getAcmeAccountKeyPair().isPresent()) { + log.error("There is no saved private key to rotate"); + isRunnable = false; + } + + return isRunnable; + } +} diff --git a/src/main/java/com/nike/cerberus/operation/certificates/RotateCertificatesOperation.java b/src/main/java/com/nike/cerberus/operation/certificates/RotateCertificatesOperation.java new file mode 100644 index 00000000..fa0c4090 --- /dev/null +++ b/src/main/java/com/nike/cerberus/operation/certificates/RotateCertificatesOperation.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.operation.certificates; + +import com.google.common.collect.Lists; +import com.google.inject.Inject; +import com.nike.cerberus.command.cms.UpdateCmsConfigCommand; +import com.nike.cerberus.command.certificates.RotateCertificatesCommand; +import com.nike.cerberus.command.certificates.DeleteOldestCertificatesCommand; +import com.nike.cerberus.command.core.RebootCmsCommand; +import com.nike.cerberus.command.core.UpdateStackCommand; +import com.nike.cerberus.command.certificates.UploadCertificateFilesCommand; +import com.nike.cerberus.command.certificates.UploadCertificateFilesCommandParametersDelegate; +import com.nike.cerberus.domain.environment.Stack; +import com.nike.cerberus.operation.composite.ChainableCommand; +import com.nike.cerberus.operation.composite.CompositeOperation; +import com.nike.cerberus.service.CloudFormationService; +import com.nike.cerberus.store.ConfigStore; +import org.apache.commons.lang3.StringUtils; + +import javax.inject.Named; +import java.util.List; + +import static com.nike.cerberus.command.certificates.DeleteOldestCertificatesCommandParametersDelegate.ACME_API_LONG_ARG; +import static com.nike.cerberus.command.certificates.DeleteOldestCertificatesCommandParametersDelegate.REVOKE_LONG_ARG; +import static com.nike.cerberus.module.CerberusModule.ENV_NAME; + +/** + * Operation for the certificate rotation command + */ +public class RotateCertificatesOperation extends CompositeOperation { + + private final CloudFormationService cloudFormationService; + private final String environmentName; + private final ConfigStore configStore; + + @Inject + public RotateCertificatesOperation(CloudFormationService cloudFormationService, + @Named(ENV_NAME) String environmentName, + ConfigStore configStore) { + + this.cloudFormationService = cloudFormationService; + this.environmentName = environmentName; + this.configStore = configStore; + } + + @Override + protected List getCompositeCommandChain(RotateCertificatesCommand compositeCommand) { + ChainableCommand.Builder delete = ChainableCommand.Builder.create().withCommand(new DeleteOldestCertificatesCommand()); + if (compositeCommand.getDeleteParametersDelegate().isRevokeCertificates()) { + delete + .withAdditionalArg(REVOKE_LONG_ARG) + .withOption(ACME_API_LONG_ARG, compositeCommand.getDeleteParametersDelegate().getAcmeUrl()); + } + + return Lists.newArrayList( + // Add the cert and key files to S3 + ChainableCommand.Builder.create().withCommand(new UploadCertificateFilesCommand()) + .withAdditionalArg(UploadCertificateFilesCommandParametersDelegate.CERT_PATH_LONG_ARG) + .withAdditionalArg(compositeCommand.getUploadParametersDelegate() + .getCertPath().toString()) + .build(), + + // Update the Load Balancer stack to use the new cert + ChainableCommand.Builder.create().withCommand(new UpdateStackCommand()) + .withAdditionalArg(UpdateStackCommand.STACK_NAME_LONG_ARG) + .withAdditionalArg(Stack.LOAD_BALANCER.getName()) + .build(), + + // Generate new CMS config that points to the new cert + ChainableCommand.Builder.create().withCommand(new UpdateCmsConfigCommand()).build(), + + // Do a rolling reboot of the management service + ChainableCommand.Builder.create().withCommand(new RebootCmsCommand()).build(), + + // Delete all certs except the latest (there should just be the one) + delete.build() + ); + } + + @Override + public boolean isRunnable(RotateCertificatesCommand command) { + boolean isRunnable = true; + + if (! cloudFormationService.isStackPresent(configStore.getPrimaryRegion(), Stack.LOAD_BALANCER.getFullName(environmentName))) { + log.error("The load-balancer stack must be present in order to rotate certificates"); + isRunnable = false; + } + + if (! cloudFormationService.isStackPresent(configStore.getPrimaryRegion(), Stack.CMS.getFullName(environmentName))) { + log.error("The cms stack must be present to rotate certificates"); + isRunnable = false; + } + + if (command.getDeleteParametersDelegate().isRevokeCertificates() && + StringUtils.isBlank(command.getDeleteParametersDelegate().getAcmeUrl())) { + log.error("You must provide an ACME api url if you wish to revoke the cert while rotating"); + isRunnable = false; + } + + return isRunnable; + } + + @Override + public boolean isEnvironmentConfigRequired() { + return false; + } +} diff --git a/src/main/java/com/nike/cerberus/operation/certificates/UploadCertificatesFilesOperation.java b/src/main/java/com/nike/cerberus/operation/certificates/UploadCertificatesFilesOperation.java new file mode 100644 index 00000000..ccd2c4db --- /dev/null +++ b/src/main/java/com/nike/cerberus/operation/certificates/UploadCertificatesFilesOperation.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.operation.certificates; + +import com.nike.cerberus.command.certificates.UploadCertificateFilesCommand; +import com.nike.cerberus.operation.Operation; +import com.nike.cerberus.service.CertificateService; +import com.nike.cerberus.store.ConfigStore; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; + +/** + * Handles uploading of certificate files to IAM and the config store. + */ +public class UploadCertificatesFilesOperation implements Operation { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private final ConfigStore configStore; + private final CertificateService certificateService; + + @Inject + public UploadCertificatesFilesOperation(ConfigStore configStore, + CertificateService certificateService) { + + this.configStore = configStore; + this.certificateService = certificateService; + } + + @Override + public void run(final UploadCertificateFilesCommand command) { + certificateService.uploadCertFiles(command.getUploadCertificatesPathParametersDelegate().getCertPath().toFile()); + } + + @Override + public boolean isRunnable(final UploadCertificateFilesCommand command) { + boolean isRunnable = true; + if (configStore.getConfigEnabledRegions().isEmpty()) { + logger.error("The environment has not been initialized"); + isRunnable = false; + } + + return isRunnable; + } +} diff --git a/src/main/java/com/nike/cerberus/operation/cms/CreateCmsClusterOperation.java b/src/main/java/com/nike/cerberus/operation/cms/CreateCmsClusterOperation.java index 7e341893..71b5c0d1 100644 --- a/src/main/java/com/nike/cerberus/operation/cms/CreateCmsClusterOperation.java +++ b/src/main/java/com/nike/cerberus/operation/cms/CreateCmsClusterOperation.java @@ -16,32 +16,27 @@ package com.nike.cerberus.operation.cms; -import com.amazonaws.services.cloudformation.model.StackStatus; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.collect.Sets; -import com.nike.cerberus.ConfigConstants; import com.nike.cerberus.command.cms.CreateCmsClusterCommand; -import com.nike.cerberus.domain.EnvironmentMetadata; -import com.nike.cerberus.domain.cloudformation.BaseOutputs; import com.nike.cerberus.domain.cloudformation.CmsParameters; -import com.nike.cerberus.domain.environment.StackName; +import com.nike.cerberus.domain.cloudformation.VpcOutputs; +import com.nike.cerberus.domain.environment.CertificateInformation; +import com.nike.cerberus.domain.environment.Stack; import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.operation.UnexpectedCloudFormationStatusException; +import com.nike.cerberus.service.AmiTagCheckService; import com.nike.cerberus.service.CloudFormationService; import com.nike.cerberus.service.Ec2UserDataService; -import com.nike.cerberus.service.AmiTagCheckService; import com.nike.cerberus.store.ConfigStore; -import com.nike.cerberus.util.UuidSupplier; +import com.nike.cerberus.util.CloudFormationObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Named; +import java.util.List; import java.util.Map; import java.util.Optional; -import static com.nike.cerberus.module.CerberusModule.CF_OBJECT_MAPPER; +import static com.nike.cerberus.module.CerberusModule.ENV_NAME; /** * Operation for creating the CMS cluster. @@ -50,106 +45,90 @@ public class CreateCmsClusterOperation implements Operation cmsServerCertificateArn = configStore.getServerCertificateArn(StackName.CMS); - final Optional pubKey = configStore.getCertPart(StackName.CMS, ConfigConstants.CERT_PART_PUBKEY); - final String internalElbCname = configStore.getInternalElbCname(StackName.CMS); - - if (!cmsServerCertificateArn.isPresent() || !pubKey.isPresent()) { - throw new IllegalStateException("CMS certificate has not been uploaded!"); + public void run(CreateCmsClusterCommand command) { + VpcOutputs vpcOutputs = configStore.getVpcStackOutputs(); + List certInfoListForStack = configStore.getCertificationInformationList(); + + Map tags = command.getStackDelegate().getTagParameters().getTags(); + + if (certInfoListForStack.isEmpty()) { + throw new IllegalStateException("Certificate for cerberus environment has not been uploaded!"); } // Make sure the given AmiId is for CMS component. Check if it contains required tag - if ( !command.isSkipAmiTagCheck() ) { - amiTagCheckService.validateAmiTagForStack(command.getStackDelegate().getAmiId(), StackName.CMS); + if (!command.isSkipAmiTagCheck()) { + amiTagCheckService.validateAmiTagForStack(command.getStackDelegate().getAmiId(), Stack.CMS); } - final CmsParameters cmsParameters = new CmsParameters() - .setInstanceProfileName(baseOutputs.getCmsInstanceProfileName()) - .setCmsElbSgId(baseOutputs.getCmsElbSgId()) - .setCmsSgId(baseOutputs.getCmsSgId()) - .setToolsIngressSgId(baseOutputs.getToolsIngressSgId()) - .setVpcId(baseOutputs.getVpcId()) - .setVpcSubnetIdForAz1(baseOutputs.getVpcSubnetIdForAz1()) - .setVpcSubnetIdForAz2(baseOutputs.getVpcSubnetIdForAz2()) - .setVpcSubnetIdForAz3(baseOutputs.getVpcSubnetIdForAz3()) - .setHostedZoneId(baseOutputs.getVpcHostedZoneId()) - .setCname(internalElbCname); - - cmsParameters.getSslConfigParameters().setCertPublicKey(pubKey.get()); - cmsParameters.getSslConfigParameters().setSslCertificateArn(cmsServerCertificateArn.get()); + CmsParameters cmsParameters = new CmsParameters() + .setVpcSubnetIdForAz1(vpcOutputs.getVpcSubnetIdForAz1()) + .setVpcSubnetIdForAz2(vpcOutputs.getVpcSubnetIdForAz2()) + .setVpcSubnetIdForAz3(vpcOutputs.getVpcSubnetIdForAz3()) + .setBaseStackName(Stack.IAM_ROLES.getFullName(environmentName)) + .setLoadBalancerStackName(Stack.LOAD_BALANCER.getFullName(environmentName)) + .setSgStackName(Stack.SECURITY_GROUPS.getFullName(environmentName)); cmsParameters.getLaunchConfigParameters().setAmiId(command.getStackDelegate().getAmiId()); cmsParameters.getLaunchConfigParameters().setInstanceSize(command.getStackDelegate().getInstanceSize()); cmsParameters.getLaunchConfigParameters().setKeyPairName(command.getStackDelegate().getKeyPairName()); - cmsParameters.getLaunchConfigParameters().setUserData( - ec2UserDataService.getUserData(StackName.CMS, command.getStackDelegate().getOwnerGroup())); - cmsParameters.getLaunchConfigParameters().setDesiredInstances(command.getStackDelegate().getDesiredInstances()); - cmsParameters.getLaunchConfigParameters().setMinimumInstances(command.getStackDelegate().getMinimumInstances()); - cmsParameters.getLaunchConfigParameters().setMaximumInstances(command.getStackDelegate().getMaximumInstances()); + cmsParameters.getLaunchConfigParameters().setUserData(ec2UserDataService.getUserData( + configStore.getPrimaryRegion(), Stack.CMS, + Optional.ofNullable(tags.getOrDefault("ownerGroup", null)))); - cmsParameters.getTagParameters().setTagEmail(command.getStackDelegate().getOwnerEmail()); - cmsParameters.getTagParameters().setTagName(ConfigConstants.ENV_PREFIX + environmentMetadata.getName()); - cmsParameters.getTagParameters().setTagCostcenter(command.getStackDelegate().getCostcenter()); + Map parameters = cloudFormationObjectMapper.convertValue(cmsParameters); - final TypeReference> typeReference = new TypeReference>() {}; - final Map parameters = cloudformationObjectMapper.convertValue(cmsParameters, typeReference); + // allow user to overwrite CloudFormation parameters with -P option + parameters.putAll(command.getStackDelegate().getDynamicParameters()); - final String stackId = cloudFormationService.createStack(cloudFormationService.getEnvStackName(uniqueStackName), - parameters, ConfigConstants.CMS_STACK_TEMPLATE_PATH, true); - - logger.info("Uploading data to the configuration bucket."); - configStore.storeStackId(StackName.CMS, stackId); - logger.info("Uploading complete."); - - final StackStatus endStatus = - cloudFormationService.waitForStatus(stackId, - Sets.newHashSet(StackStatus.CREATE_COMPLETE, StackStatus.ROLLBACK_COMPLETE)); - - if (endStatus != StackStatus.CREATE_COMPLETE) { - final String errorMessage = String.format("Unexpected end status: %s", endStatus.name()); - logger.error(errorMessage); - - throw new UnexpectedCloudFormationStatusException(errorMessage); - } + cloudFormationService.createStackAndWait( + configStore.getPrimaryRegion(), + Stack.CMS, + parameters, true, + tags); } @Override - public boolean isRunnable(final CreateCmsClusterCommand command) { - return configStore.getCmsEnvConfig().isPresent(); + public boolean isRunnable(CreateCmsClusterCommand command) { + try { + cloudFormationService.getStackId(configStore.getPrimaryRegion(), Stack.LOAD_BALANCER.getFullName(environmentName)); + cloudFormationService.getStackId(configStore.getPrimaryRegion(), Stack.SECURITY_GROUPS.getFullName(environmentName)); + cloudFormationService.getStackId(configStore.getPrimaryRegion(), Stack.IAM_ROLES.getFullName(environmentName)); + } catch (IllegalArgumentException iae) { + logger.error("Could not create the CMS cluster." + + "Make sure the load balancer, security group, and base stacks have all been created.", iae); + return false; + } + + return configStore.getCmsEnvConfig().isPresent() && + !cloudFormationService.isStackPresent(configStore.getPrimaryRegion(), Stack.CMS.getFullName(environmentName)); } } diff --git a/src/main/java/com/nike/cerberus/operation/cms/CreateCmsConfigOperation.java b/src/main/java/com/nike/cerberus/operation/cms/CreateCmsConfigOperation.java index 8a68bccf..4bca8e3c 100644 --- a/src/main/java/com/nike/cerberus/operation/cms/CreateCmsConfigOperation.java +++ b/src/main/java/com/nike/cerberus/operation/cms/CreateCmsConfigOperation.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Nike, Inc. + * Copyright (c) 2017 Nike, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,8 +43,6 @@ public CreateCmsConfigOperation(final ConfigStore configStore) { @Override public void run(final CreateCmsConfigCommand command) { - configStore.storeCmsAdminGroup(command.getAdminGroup()); - logger.info("Retrieving configuration data from the configuration bucket."); final Properties cmsConfigProperties = configStore.getCmsSystemProperties(); @@ -68,7 +66,7 @@ public void run(final CreateCmsConfigCommand command) { public boolean isRunnable(final CreateCmsConfigCommand command) { boolean isRunnable = !configStore.getCmsEnvConfig().isPresent(); - if (! isRunnable) { + if (!isRunnable) { logger.warn("CMS config already exists, use 'update-cms-config' command."); } diff --git a/src/main/java/com/nike/cerberus/operation/cms/UpdateCmsConfigOperation.java b/src/main/java/com/nike/cerberus/operation/cms/UpdateCmsConfigOperation.java index 058abe41..2a2c6b7e 100644 --- a/src/main/java/com/nike/cerberus/operation/cms/UpdateCmsConfigOperation.java +++ b/src/main/java/com/nike/cerberus/operation/cms/UpdateCmsConfigOperation.java @@ -27,8 +27,8 @@ import java.util.Optional; import java.util.Properties; -import static com.nike.cerberus.ConfigConstants.SYSTEM_CONFIGURED_CMS_PROPERTIES; import static com.nike.cerberus.ConfigConstants.CMS_ADMIN_GROUP_KEY; +import static com.nike.cerberus.ConfigConstants.SYSTEM_CONFIGURED_CMS_PROPERTIES; /** * Gathers all of the CMS environment configuration and puts it in the config bucket. @@ -51,14 +51,14 @@ public void run(final UpdateCmsConfigCommand command) { final Properties newProperties = configStore.getCmsSystemProperties(); final Properties existingCustomProperties = configStore.getExistingCmsUserProperties(); - if (! command.getOverwrite()) { + if (!command.getOverwrite()) { // keep existing custom properties newProperties.putAll(existingCustomProperties); } // update existing custom properties, add new ones command.getAdditionalProperties().forEach((k, v) -> { - if (! SYSTEM_CONFIGURED_CMS_PROPERTIES.contains(k)) { + if (!SYSTEM_CONFIGURED_CMS_PROPERTIES.contains(k) || command.isForce()) { newProperties.put(k, v); } else { logger.warn("Ignoring additional property that would override system configured property, " + k); @@ -71,7 +71,6 @@ public void run(final UpdateCmsConfigCommand command) { if (shouldOverwriteAdminGroup(existingAdminGroup, adminGroupParameter)) { logger.warn(String.format("Updating CMS admin group from '%s' to '%s'", existingAdminGroup, adminGroupParameter)); - configStore.storeCmsAdminGroup(adminGroupParameter); newAdminGroupValue = adminGroupParameter; // overwrite admin group } newProperties.put(CMS_ADMIN_GROUP_KEY, newAdminGroupValue); @@ -84,15 +83,9 @@ public void run(final UpdateCmsConfigCommand command) { @Override public boolean isRunnable(final UpdateCmsConfigCommand command) { - boolean isRunnable = true; - final Optional cmsVaultToken = configStore.getCmsVaultToken(); + boolean isRunnable = configStore.getCmsEnvConfig().isPresent(); final Optional cmsDatabasePassword = configStore.getCmsDatabasePassword(); - if (!cmsVaultToken.isPresent()) { - logger.error("CMS Vault token not present for specified environment."); - isRunnable = false; - } - if (!cmsDatabasePassword.isPresent()) { logger.error("CMS database password not present for specified environment."); isRunnable = false; diff --git a/src/main/java/com/nike/cerberus/operation/composite/ChainableCommand.java b/src/main/java/com/nike/cerberus/operation/composite/ChainableCommand.java new file mode 100644 index 00000000..23482d94 --- /dev/null +++ b/src/main/java/com/nike/cerberus/operation/composite/ChainableCommand.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.operation.composite; + +import com.nike.cerberus.command.Command; +import com.nike.cerberus.operation.Operation; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import java.util.LinkedList; +import java.util.List; + +@SuppressFBWarnings( + value = "EI_EXPOSE_REP", + justification = "We don't care" +) +/** + * Wrapper object to represent a chainable command. + * + * @See CompositeOperation + */ +public class ChainableCommand { + + private Command command; + private String[] additionalArgs; + + + public ChainableCommand() { + } + + public ChainableCommand(Command command) { + this.command = command; + } + + public ChainableCommand(Command command, String[] additionalArgs) { + this.command = command; + this.additionalArgs = additionalArgs; + } + + public Command getCommand() { + return command; + } + + public String[] getAdditionalArgs() { + return additionalArgs; + } + + public static final class Builder { + private Command command; + private List additionalArgs = new LinkedList<>(); + + private Builder() { + } + + public static Builder create() { + return new Builder(); + } + + public Builder withCommand(Command command) { + this.command = command; + return this; + } + + public Builder withAdditionalArg(String additionalArg) { + additionalArgs.add(additionalArg); + return this; + } + + public Builder withOption(String key, String value) { + additionalArgs.add(key); + additionalArgs.add(value); + return this; + } + + public ChainableCommand build() { + ChainableCommand chainableCommand = new ChainableCommand(); + chainableCommand.command = this.command; + chainableCommand.additionalArgs = this.additionalArgs.toArray(new String[additionalArgs.size()]); + return chainableCommand; + } + } +} diff --git a/src/main/java/com/nike/cerberus/operation/composite/CompositeOperation.java b/src/main/java/com/nike/cerberus/operation/composite/CompositeOperation.java new file mode 100644 index 00000000..33110bd9 --- /dev/null +++ b/src/main/java/com/nike/cerberus/operation/composite/CompositeOperation.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.operation.composite; + +import com.beust.jcommander.JCommander; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.nike.cerberus.cli.EnvironmentConfigToArgsMapper; +import com.nike.cerberus.command.Command; +import com.nike.cerberus.domain.input.EnvironmentConfig; +import com.nike.cerberus.operation.Operation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nullable; +import java.util.List; + +/** + * This abstract class can be extended by any composite operation to enable chaining of commands + * together to perform complex operations. + * + * @param The command that this operation implements + */ +public abstract class CompositeOperation implements Operation { + + protected final Logger log = LoggerFactory.getLogger(getClass()); + + private Injector injector; + + protected EnvironmentConfig environmentConfig; + + @Inject + public void setInjector(Injector injector) { + this.injector = injector; + } + + @Inject + public void setEnvironmentConfig(@Nullable EnvironmentConfig environmentConfig) { + this.environmentConfig = environmentConfig; + } + + private Operation getOperationInstance(Command command) { + return injector.getInstance(command.getOperationClass()); + } + + /** + * {@inheritDoc} + *

+ * Runs all the chained commands in order that are defined in the runnable chained command list, + * which gets populated when isRunnable is executed + */ + @SuppressWarnings("unchecked") + public void run(T compositeCommand) { + if (isEnvironmentConfigRequired() && environmentConfig == null) { + throw new RuntimeException(String.format("The %s command requires that -f or --file must be supplied as a global option with " + + "a path to a valid environment yaml", compositeCommand.getCommandName())); + } + + for (ChainableCommand chainableCommand : getCompositeCommandChain(compositeCommand)) { + Command chainedCommand = chainableCommand.getCommand(); + String[] additionalArgs = chainableCommand.getAdditionalArgs(); + + // Parse the yaml and additional args to get args to pass to jcommander + List argsList = EnvironmentConfigToArgsMapper.getArgsForCommand( + environmentConfig, chainedCommand.getCommandName(), additionalArgs); + + // If the mapper doesn't have a mapping for a given command it will return an empty list + // in this case we will just use the args manually provided by the chainable command + String[] args; + if (argsList.size() > 0) { + args = argsList.toArray(new String[argsList.size()]); + } else { + args = additionalArgs; + } + + // Use jcommander to bind the resolved args to the command object + new JCommander(chainedCommand).parse(args); + + // Get the instance of the operation from guice + Operation operation = getOperationInstance(chainedCommand); + + // If the given command is not runnable return false for the whole chain + //noinspection unchecked + boolean isRunnable = operation.isRunnable(chainedCommand); + if (!isRunnable) { + if (!skipOnNotRunnable()) { + throw new RuntimeException("The command: " + chainedCommand.getCommandName() + " is not runnable, stopping..."); + } else { + log.info("The command {} reports that it is not runnable, skipping...", chainedCommand.getCommandName()); + continue; + } + } + + log.info("Attempting to run command: {}, with args: {}", chainedCommand.getCommandName(), args); + try { + operation.run(chainedCommand); + } catch (Throwable e) { + throw new RuntimeException("Failed to execute chained command: " + chainedCommand.getCommandName(), e); + } + log.info("Finished command: {}\n", chainedCommand.getCommandName()); + } + } + + /** + * Implement this method to define the ordered list of chained commands that will get executed + * + * @return An ordered list of ChainableCommand's + * @param compositeCommand + */ + protected abstract List getCompositeCommandChain(T compositeCommand); + + /** + * If your command doesn't require that the environment yaml be supplied, you can override this to false. + * + * @return boolean of whether or not the environment yaml is required. + */ + public boolean isEnvironmentConfigRequired() { + return true; + } + + /** + * If your chain of commands doesn't need every command to run to succeed you can override this to tru. + *

+ * For example you could have a chain of commands that is long and complicated and commands that have completed will + * return isRunnable: false on future runs, setting this to return true would just mean that the command that is + * already run will be skipped moving on to commands that are still needing to be ran. Allowing you to run a + * complicated composite command as many times as needed to succeed + * + * @return boolean of whether or not to fail the command if a command in the chain returns false for isRunnable + */ + public boolean skipOnNotRunnable() { + return false; + } +} diff --git a/src/main/java/com/nike/cerberus/operation/composite/CreateEnvironmentOperation.java b/src/main/java/com/nike/cerberus/operation/composite/CreateEnvironmentOperation.java new file mode 100644 index 00000000..629cef3b --- /dev/null +++ b/src/main/java/com/nike/cerberus/operation/composite/CreateEnvironmentOperation.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.operation.composite; + +import com.google.common.collect.Lists; +import com.nike.cerberus.command.cms.CreateCmsClusterCommand; +import com.nike.cerberus.command.cms.CreateCmsConfigCommand; +import com.nike.cerberus.command.composite.CreateEnvironmentCommand; +import com.nike.cerberus.command.core.InitializeEnvironmentCommand; +import com.nike.cerberus.command.rds.CreateDatabaseCommand; +import com.nike.cerberus.command.core.CreateEdgeDomainRecordCommand; +import com.nike.cerberus.command.core.CreateLoadBalancerCommand; +import com.nike.cerberus.command.core.CreateRoute53Command; +import com.nike.cerberus.command.core.CreateSecurityGroupsCommand; +import com.nike.cerberus.command.core.CreateVpcCommand; +import com.nike.cerberus.command.core.CreateWafCommand; +import com.nike.cerberus.command.core.GenerateCertificateFilesCommand; +import com.nike.cerberus.command.certificates.UploadCertificateFilesCommand; +import com.nike.cerberus.command.core.WhitelistCidrForVpcAccessCommand; + +import java.util.List; + +/** + * Operation class for CreateCerberusEnvironmentCommand + */ +public class CreateEnvironmentOperation extends CompositeOperation { + + /** + * {@inheritDoc} + * @param compositeCommand + */ + @Override + protected List getCompositeCommandChain(CreateEnvironmentCommand compositeCommand) { + List list = Lists.newArrayList( + // Step 1 Create the Base Cloud Formation Stack that creates S3 Buckets, Iam Roles and KMS keys needed for config + new ChainableCommand(new InitializeEnvironmentCommand()), + + // Step 2 Create the VPC Cloud Formation Stack that Cerberus will use + new ChainableCommand(new CreateVpcCommand()), + + // Step 3 Create the Security Group Cloud Formation Stack + new ChainableCommand(new CreateSecurityGroupsCommand()), + + // Step 3.5 Add the vpc whitelist CIDRs + new ChainableCommand(new WhitelistCidrForVpcAccessCommand()), + + // Step 4 Create the RDS Database Cloud Formation Stack + new ChainableCommand(new CreateDatabaseCommand()) + ); + + // Step 5 Generate the PKCS private and public keys as well as the x509 certificates needed to enable https + if (environmentConfig.isGenerateKeysAndCerts()) { + list.add(new ChainableCommand(new GenerateCertificateFilesCommand())); + } + + list.addAll(Lists.newArrayList( + // Step 6 Upload the certs and keys to S3 and the IAM Cert Management service so that the ALB and CMS can use the certs + new ChainableCommand(new UploadCertificateFilesCommand()), + + // Step 7 Create the Application Load Balancer Cloud Formation Stack + new ChainableCommand(new CreateLoadBalancerCommand()), + + // Step 8 Generate the CMS config with org specific setting and first secrets encrypt, + // Upload to S3 for CMS to download at service start + new ChainableCommand(new CreateCmsConfigCommand()), + + // Step 9 Create the CMS Cluster Stack + new ChainableCommand(new CreateCmsClusterCommand()), + + // Step 10 Create the Web Application Fire wall stack + new ChainableCommand(new CreateWafCommand()), + + // Step 11 Create the Route 53 DNS Record Stack for origin and the load balancer + new ChainableCommand(new CreateRoute53Command()), + + // Step 12 Create the outer most domain name record that will point to the origin record + new ChainableCommand(new CreateEdgeDomainRecordCommand()) + )); + + return list; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isRunnable(CreateEnvironmentCommand command) { + // Always return true and depend on isRunnable of the chained commands to skip commands that cannot be re-ran + // we want this command to be able to be run more than once to complete an environment for example say temporary + // aws creds expire half way through environment creation + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean skipOnNotRunnable() { + // Since we always return true for isRunnable on this command sub of the sub commands may have already been run + // we want this command to be able to be run more than once to complete an environment for example say temporary + // aws creds expire half way through environment creation + return true; + } +} diff --git a/src/main/java/com/nike/cerberus/operation/composite/DeleteEnvironmentOperation.java b/src/main/java/com/nike/cerberus/operation/composite/DeleteEnvironmentOperation.java new file mode 100644 index 00000000..2f819e76 --- /dev/null +++ b/src/main/java/com/nike/cerberus/operation/composite/DeleteEnvironmentOperation.java @@ -0,0 +1,95 @@ +package com.nike.cerberus.operation.composite; + +import com.google.common.collect.ImmutableList; +import com.nike.cerberus.command.composite.DeleteEnvironmentCommand; +import com.nike.cerberus.command.core.DeleteStackCommand; +import com.nike.cerberus.service.ConsoleService; +import com.nike.cerberus.store.ConfigStore; + +import javax.inject.Inject; +import javax.inject.Named; +import java.util.LinkedList; +import java.util.List; + +import static com.nike.cerberus.domain.environment.Stack.*; +import static com.nike.cerberus.module.CerberusModule.ENV_NAME; + +public class DeleteEnvironmentOperation extends CompositeOperation { + + private final ConsoleService consoleService; + + private final String environmentName; + + private final ConfigStore configStore; + + @Inject + public DeleteEnvironmentOperation(ConsoleService consoleService, + @Named(ENV_NAME) String environmentName, + ConfigStore configStore) { + + this.consoleService = consoleService; + this.environmentName = environmentName; + this.configStore = configStore; + } + + @Override + protected List getCompositeCommandChain(DeleteEnvironmentCommand compositeCommand) { + List chainableCommandList = new LinkedList<>(); + + // todo delete the kms keys generated by cms + ImmutableList.of( + ROUTE53, + WAF, + CMS, + LOAD_BALANCER, + DATABASE, + SECURITY_GROUPS, + VPC + ).forEach(stack -> + chainableCommandList.add(ChainableCommand.Builder.create() + .withCommand(new DeleteStackCommand()) + .withOption(DeleteStackCommand.STACK_NAME_LONG_ARG, stack.getName()) + .build()) + ); + + chainableCommandList.add(ChainableCommand.Builder.create() + .withCommand(new DeleteStackCommand()) + .withAdditionalArg(DeleteStackCommand.STACK_NAME_LONG_ARG) + .withAdditionalArg(IAM_ROLES.getName()) + .build()); + + configStore.getConfigEnabledRegions().forEach(region -> { + chainableCommandList.add(ChainableCommand.Builder.create() + .withCommand(new DeleteStackCommand()) + .withOption(DeleteStackCommand.STACK_NAME_LONG_ARG, CONFIG.getName()) + .withOption(DeleteStackCommand.REGION_LONG_ARG, region.getName()) + .build()); + }); + + return chainableCommandList; + } + + @Override + public boolean isRunnable(DeleteEnvironmentCommand command) { + try { + String warning = String.format( + "This will delete the environment '%s' including all the secure data.", + environmentName + ); + consoleService.askUserToProceed(warning, ConsoleService.DefaultAction.NO); + } catch (RuntimeException e) { + return false; + } + return true; + } + + @Override + public boolean isEnvironmentConfigRequired() { + return false; + } + + @Override + public boolean skipOnNotRunnable() { + return true; + } +} diff --git a/src/main/java/com/nike/cerberus/operation/composite/PrintAllStackInformationOperation.java b/src/main/java/com/nike/cerberus/operation/composite/PrintAllStackInformationOperation.java new file mode 100644 index 00000000..318fe73c --- /dev/null +++ b/src/main/java/com/nike/cerberus/operation/composite/PrintAllStackInformationOperation.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.operation.composite; + +import com.nike.cerberus.command.composite.PrintAllStackInformationCommand; +import com.nike.cerberus.command.core.PrintStackInfoCommand; +import com.nike.cerberus.domain.environment.Stack; + +import java.util.LinkedList; +import java.util.List; + +/** + * Composite Command for printing stack info for entire Cerberus Environment + */ +public class PrintAllStackInformationOperation extends CompositeOperation { + + @Override + protected List getCompositeCommandChain(PrintAllStackInformationCommand compositeCommand) { + List commandList = new LinkedList<>(); + + for (Stack stack : Stack.ALL_STACKS) { + commandList.add( + ChainableCommand.Builder.create() + .withCommand(new PrintStackInfoCommand()) + .withAdditionalArg(PrintStackInfoCommand.STACK_NAME_LONG_ARG) + .withAdditionalArg(stack.toString()) + .build() + ); + } + + return commandList; + } + + @Override + public boolean isEnvironmentConfigRequired() { + return false; + } + + @Override + public boolean isRunnable(PrintAllStackInformationCommand command) { + return true; + } +} diff --git a/src/main/java/com/nike/cerberus/operation/consul/CreateConsulClusterOperation.java b/src/main/java/com/nike/cerberus/operation/consul/CreateConsulClusterOperation.java deleted file mode 100644 index a0c07339..00000000 --- a/src/main/java/com/nike/cerberus/operation/consul/CreateConsulClusterOperation.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (c) 2017 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.operation.consul; - -import com.amazonaws.services.cloudformation.model.StackStatus; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.collect.Sets; -import com.nike.cerberus.ConfigConstants; -import com.nike.cerberus.command.consul.CreateConsulClusterCommand; -import com.nike.cerberus.domain.EnvironmentMetadata; -import com.nike.cerberus.domain.cloudformation.BaseOutputs; -import com.nike.cerberus.domain.cloudformation.ConsulParameters; -import com.nike.cerberus.domain.environment.StackName; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.operation.UnexpectedCloudFormationStatusException; -import com.nike.cerberus.service.CloudFormationService; -import com.nike.cerberus.service.Ec2UserDataService; -import com.nike.cerberus.service.AmiTagCheckService; -import com.nike.cerberus.store.ConfigStore; -import com.nike.cerberus.util.UuidSupplier; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; -import javax.inject.Named; -import java.util.Map; - -import static com.nike.cerberus.module.CerberusModule.CF_OBJECT_MAPPER; - -/** - * Operation to create the Consul cluster. - */ -public class CreateConsulClusterOperation implements Operation { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private final EnvironmentMetadata environmentMetadata; - - private final CloudFormationService cloudFormationService; - - private final Ec2UserDataService ec2UserDataService; - - private final AmiTagCheckService amiTagCheckService; - - private final UuidSupplier uuidSupplier; - - private final ConfigStore configStore; - - private final ObjectMapper cloudformationObjectMapper; - - @Inject - public CreateConsulClusterOperation(final EnvironmentMetadata environmentMetadata, - final CloudFormationService cloudFormationService, - final Ec2UserDataService ec2UserDataService, - final AmiTagCheckService amiTagCheckService, - final UuidSupplier uuidSupplier, - final ConfigStore configStore, - @Named(CF_OBJECT_MAPPER) final ObjectMapper cloudformationObjectMapper) { - this.environmentMetadata = environmentMetadata; - this.cloudFormationService = cloudFormationService; - this.ec2UserDataService = ec2UserDataService; - this.amiTagCheckService = amiTagCheckService; - this.uuidSupplier = uuidSupplier; - this.configStore = configStore; - this.cloudformationObjectMapper = cloudformationObjectMapper; - } - - @Override - public void run(final CreateConsulClusterCommand command) { - final String uniqueStackName = String.format("%s-%s", StackName.CONSUL.getName(), uuidSupplier.get()); - final BaseOutputs baseOutputs = configStore.getBaseStackOutputs(); - - // Make sure the given AmiId is for Consul component. Check if it contains required tag - if ( !command.isSkipAmiTagCheck() ) { - amiTagCheckService.validateAmiTagForStack(command.getStackDelegate().getAmiId(), StackName.CONSUL); - } - - final ConsulParameters consulParameters = new ConsulParameters() - .setInstanceProfileName(baseOutputs.getConsulInstanceProfileName()) - .setConsulClientSgId(baseOutputs.getConsulClientSgId()) - .setConsulServerSgId(baseOutputs.getConsulServerSgId()) - .setToolsIngressSgId(baseOutputs.getToolsIngressSgId()) - .setVpcId(baseOutputs.getVpcId()) - .setVpcSubnetIdForAz1(baseOutputs.getVpcSubnetIdForAz1()) - .setVpcSubnetIdForAz2(baseOutputs.getVpcSubnetIdForAz2()) - .setVpcSubnetIdForAz3(baseOutputs.getVpcSubnetIdForAz3()); - - consulParameters.getLaunchConfigParameters().setAmiId(command.getStackDelegate().getAmiId()); - consulParameters.getLaunchConfigParameters().setInstanceSize(command.getStackDelegate().getInstanceSize()); - consulParameters.getLaunchConfigParameters().setKeyPairName(command.getStackDelegate().getKeyPairName()); - consulParameters.getLaunchConfigParameters().setKeyPairName(command.getStackDelegate().getKeyPairName()); - consulParameters.getLaunchConfigParameters().setUserData( - ec2UserDataService.getUserData(StackName.CONSUL, command.getStackDelegate().getOwnerGroup())); - consulParameters.getLaunchConfigParameters().setDesiredInstances( - command.getStackDelegate().getDesiredInstances()); - consulParameters.getLaunchConfigParameters().setMinimumInstances( - command.getStackDelegate().getMinimumInstances()); - consulParameters.getLaunchConfigParameters().setMaximumInstances( - command.getStackDelegate().getMaximumInstances()); - - consulParameters.getTagParameters().setTagEmail(command.getStackDelegate().getOwnerEmail()); - consulParameters.getTagParameters().setTagName(ConfigConstants.ENV_PREFIX + environmentMetadata.getName()); - consulParameters.getTagParameters().setTagCostcenter(command.getStackDelegate().getCostcenter()); - - final TypeReference> typeReference = new TypeReference>() {}; - final Map parameters = cloudformationObjectMapper.convertValue(consulParameters, typeReference); - - final String stackId = cloudFormationService.createStack(cloudFormationService.getEnvStackName(uniqueStackName), - parameters, ConfigConstants.CONSUL_STACK_TEMPLATE_PATH, true); - - logger.info("Uploading data to the configuration bucket."); - configStore.storeStackId(StackName.CONSUL, stackId); - logger.info("Uploading complete."); - - final StackStatus endStatus = - cloudFormationService.waitForStatus(stackId, - Sets.newHashSet(StackStatus.CREATE_COMPLETE, StackStatus.ROLLBACK_COMPLETE)); - - if (endStatus != StackStatus.CREATE_COMPLETE) { - final String errorMessage = String.format("Unexpected end status: %s", endStatus.name()); - logger.error(errorMessage); - - throw new UnexpectedCloudFormationStatusException(errorMessage); - } - } - - @Override - public boolean isRunnable(final CreateConsulClusterCommand command) { - boolean isRunnable = true; - final String baseStackId = configStore.getStackId(StackName.BASE); - final boolean hasConsulConfig = configStore.hasConsulConfig(); - final boolean hasVaultAcl = configStore.hasVaultAclEntry(); - - if (StringUtils.isBlank(baseStackId) || !cloudFormationService.isStackPresent(baseStackId)) { - logger.error("No base stack defined for this environment!"); - isRunnable = false; - } - - if (!hasConsulConfig) { - logger.error("No configuration for Consul exists for this environment!"); - isRunnable = false; - } - - if (!hasVaultAcl) { - logger.error("Vault ACL has not been generated for this environment!"); - isRunnable = false; - } - - return isRunnable; - } -} diff --git a/src/main/java/com/nike/cerberus/operation/consul/CreateConsulConfigOperation.java b/src/main/java/com/nike/cerberus/operation/consul/CreateConsulConfigOperation.java deleted file mode 100644 index 45508947..00000000 --- a/src/main/java/com/nike/cerberus/operation/consul/CreateConsulConfigOperation.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.operation.consul; - -import com.nike.cerberus.ConfigConstants; -import com.nike.cerberus.command.consul.CreateConsulConfigCommand; -import com.nike.cerberus.domain.configuration.ConsulConfiguration; -import com.nike.cerberus.generator.ConsulConfigGenerator; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.store.ConfigStore; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; - -/** - * Generates the configuration files for Consul and uploads them to the config store. - */ -public class CreateConsulConfigOperation implements Operation { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private final ConsulConfigGenerator consulConfigGenerator; - - private final ConfigStore configStore; - - @Inject - public CreateConsulConfigOperation(final ConsulConfigGenerator consulConfigGenerator, - final ConfigStore configStore) { - this.consulConfigGenerator = consulConfigGenerator; - this.configStore = configStore; - } - - @Override - public void run(final CreateConsulConfigCommand command) { - logger.info("Generating the Consul configuration."); - final ConsulConfiguration consulConfiguration = - consulConfigGenerator.generate(ConfigConstants.CONSUL_DATACENTER); - - logger.info("Uploading Consul configuration to the configuration bucket."); - configStore.storeConsulConfig(consulConfiguration); - - logger.info("Uploading complete."); - } - - @Override - public boolean isRunnable(final CreateConsulConfigCommand command) { - final boolean hasConsulConfig = configStore.hasConsulConfig(); - - if (hasConsulConfig) { - // you don't want to just overwrite the config here because it contains - // tokens that need to be maintained for the cluster to keep working - logger.error("Consul configuration present for specified environment, use the update command."); - } - - return !hasConsulConfig; - } -} diff --git a/src/main/java/com/nike/cerberus/operation/consul/CreateVaultAclOperation.java b/src/main/java/com/nike/cerberus/operation/consul/CreateVaultAclOperation.java deleted file mode 100644 index ad773b03..00000000 --- a/src/main/java/com/nike/cerberus/operation/consul/CreateVaultAclOperation.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.operation.consul; - -import com.nike.cerberus.command.consul.CreateVaultAclCommand; -import com.nike.cerberus.domain.configuration.VaultAclEntry; -import com.nike.cerberus.generator.VaultAclGenerator; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.store.ConfigStore; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; - -/** - * Operation that will generate the Vault ACL token for accessing Consul and upload it to the config store. - */ -public class CreateVaultAclOperation implements Operation { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private final VaultAclGenerator vaultAclGenerator; - - private final ConfigStore configStore; - - @Inject - public CreateVaultAclOperation(final VaultAclGenerator vaultAclGenerator, - final ConfigStore configStore) { - this.vaultAclGenerator = vaultAclGenerator; - this.configStore = configStore; - } - - @Override - public void run(final CreateVaultAclCommand command) { - logger.info("Generating the Vault ACL entry."); - final VaultAclEntry vaultAclEntry = - vaultAclGenerator.generate(); - - logger.info("Uploading the Vault ACL entry to the configuration bucket."); - configStore.storeVaultAclEntry(vaultAclEntry); - - logger.info("Uploading complete."); - } - - @Override - public boolean isRunnable(final CreateVaultAclCommand command) { - final boolean hasVaultAclEntry = configStore.hasVaultAclEntry(); - - if (hasVaultAclEntry) { - logger.error("Vault ACL entry is present for specified environment, use the update command."); - } - - return !hasVaultAclEntry; - } -} diff --git a/src/main/java/com/nike/cerberus/operation/consul/UpdateConsulConfigOperation.java b/src/main/java/com/nike/cerberus/operation/consul/UpdateConsulConfigOperation.java deleted file mode 100644 index b465ad06..00000000 --- a/src/main/java/com/nike/cerberus/operation/consul/UpdateConsulConfigOperation.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.operation.consul; - -import com.nike.cerberus.ConfigConstants; -import com.nike.cerberus.command.consul.UpdateConsulConfigCommand; -import com.nike.cerberus.domain.configuration.ConsulConfiguration; -import com.nike.cerberus.generator.ConsulConfigGenerator; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.store.ConfigStore; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; - -/** - * Generates the configuration files for Consul and uploads them to the config store. - */ -public class UpdateConsulConfigOperation implements Operation { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private final ConsulConfigGenerator consulConfigGenerator; - - private final ConfigStore configStore; - - @Inject - public UpdateConsulConfigOperation(final ConsulConfigGenerator consulConfigGenerator, - final ConfigStore configStore) { - this.consulConfigGenerator = consulConfigGenerator; - this.configStore = configStore; - } - - @Override - public void run(final UpdateConsulConfigCommand command) { - logger.info("Regenerating Consul configuration"); - - String aclMasterToken = command.getAclMasterToken(); - if (aclMasterToken == null) { - logger.info("Regenerating Consul configuration: maintaining existing ACL Master Token"); - aclMasterToken = configStore.getAclMasterToken(); - } - String gossipEncryptionToken = command.getGossipEncryptionToken(); - if (gossipEncryptionToken == null) { - logger.info("Regenerating Consul configuration: maintaining existing Gossip Encryption Token"); - gossipEncryptionToken = configStore.getGossipEncryptionToken(); - } - - final ConsulConfiguration consulConfiguration = consulConfigGenerator.generate( - ConfigConstants.CONSUL_DATACENTER, - aclMasterToken, - gossipEncryptionToken - ); - - logger.info("Uploading Consul configuration to the configuration bucket."); - configStore.storeConsulConfig(consulConfiguration); - - logger.info("Uploading complete."); - } - - @Override - public boolean isRunnable(final UpdateConsulConfigCommand command) { - final boolean hasConsulConfig = configStore.hasConsulConfig(); - - if (!hasConsulConfig) { - logger.error("Consul configuration is NOT present for specified environment, use the create command."); - } - - return hasConsulConfig; - } -} diff --git a/src/main/java/com/nike/cerberus/operation/core/CreateBaseOperation.java b/src/main/java/com/nike/cerberus/operation/core/CreateBaseOperation.java deleted file mode 100644 index fcae3aa4..00000000 --- a/src/main/java/com/nike/cerberus/operation/core/CreateBaseOperation.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.operation.core; - -import com.amazonaws.services.cloudformation.model.StackStatus; -import com.beust.jcommander.internal.Maps; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.collect.Sets; -import com.nike.cerberus.ConfigConstants; -import com.nike.cerberus.command.core.CreateBaseCommand; -import com.nike.cerberus.domain.EnvironmentMetadata; -import com.nike.cerberus.domain.cloudformation.BaseParameters; -import com.nike.cerberus.domain.cloudformation.BaseOutputs; -import com.nike.cerberus.domain.environment.StackName; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.operation.UnexpectedCloudFormationStatusException; -import com.nike.cerberus.service.CloudFormationService; -import com.nike.cerberus.service.Ec2Service; -import com.nike.cerberus.store.ConfigStore; -import com.nike.cerberus.util.RandomStringGenerator; -import com.nike.cerberus.util.UuidSupplier; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; -import javax.inject.Named; -import java.util.List; -import java.util.Map; - -import static com.nike.cerberus.ConfigConstants.MINIMUM_AZS; -import static com.nike.cerberus.module.CerberusModule.CF_OBJECT_MAPPER; - -/** - * Creates the base components via CloudFormation used by all of Cerberus. - */ -public class CreateBaseOperation implements Operation { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private final EnvironmentMetadata environmentMetadata; - - private final CloudFormationService cloudFormationService; - - private final Ec2Service ec2Service; - - private final UuidSupplier uuidSupplier; - - private final ConfigStore configStore; - - private final ObjectMapper cloudformationObjectMapper; - - private final RandomStringGenerator passwordGenerator = new RandomStringGenerator(); - - @Inject - public CreateBaseOperation(final EnvironmentMetadata environmentMetadata, - final CloudFormationService cloudFormationService, - final Ec2Service ec2Service, - final UuidSupplier uuidSupplier, - final ConfigStore configStore, - @Named(CF_OBJECT_MAPPER) final ObjectMapper cloudformationObjectMapper) { - this.environmentMetadata = environmentMetadata; - this.cloudFormationService = cloudFormationService; - this.ec2Service = ec2Service; - this.uuidSupplier = uuidSupplier; - this.configStore = configStore; - this.cloudformationObjectMapper = cloudformationObjectMapper; - } - - @Override - public void run(final CreateBaseCommand command) { - final String uniqueStackName = String.format("%s-%s", ConfigConstants.BASE_STACK_NAME, uuidSupplier.get()); - final Map azByIdentifier = mapAvailabilityZones(); - final String dbMasterPassword = passwordGenerator.get(); - - final BaseParameters baseParameters = new BaseParameters(); - baseParameters.setAccountAdminArn(command.getAdminRoleArn()) - .setAz1(azByIdentifier.get(1)) - .setAz2(azByIdentifier.get(2)) - .setAz3(azByIdentifier.get(3)) - .setCmsDbMasterUsername(ConfigConstants.DEFAULT_CMS_DB_NAME) - .setCmsDbMasterPassword(dbMasterPassword) - .setCmsDbName(ConfigConstants.DEFAULT_CMS_DB_NAME) - .setVpcHostedZoneName(command.getVpcHostedZoneName()); - - baseParameters.getTagParameters().setTagEmail(command.getOwnerEmail()); - baseParameters.getTagParameters().setTagName(ConfigConstants.ENV_PREFIX + environmentMetadata.getName()); - baseParameters.getTagParameters().setTagCostcenter(command.getCostcenter()); - - final TypeReference> typeReference = new TypeReference>() {}; - final Map parameters = cloudformationObjectMapper.convertValue(baseParameters, typeReference); - - final String stackId = cloudFormationService.createStack(cloudFormationService.getEnvStackName(uniqueStackName), - parameters, ConfigConstants.BASE_STACK_TEMPLATE_PATH, true); - - final StackStatus endStatus = - cloudFormationService.waitForStatus(stackId, - Sets.newHashSet(StackStatus.CREATE_COMPLETE, StackStatus.ROLLBACK_COMPLETE)); - - if (StackStatus.CREATE_COMPLETE == endStatus) { - final Map stackOutputs = cloudFormationService.getStackOutputs(stackId); - final BaseOutputs outputParameters = - cloudformationObjectMapper.convertValue(stackOutputs, BaseOutputs.class); - - logger.info("Stack creation complete, uploading data to the configuration bucket."); - - // ORDER IS IMPORTANT! - - // 1. First update the environment metadata with the new config bucket name. - environmentMetadata.setBucketName(outputParameters.getConfigBucketName()); - - // 2. Initialize the environment config. - configStore.initEnvironmentData(); - - // 3. Write the first configuration values to the config bucket. - configStore.storeAzs(azByIdentifier.get(1), azByIdentifier.get(2), azByIdentifier.get(3)); - configStore.storeStackId(StackName.BASE, stackId); - configStore.storeConfigKeyId(outputParameters.getConfigFileKeyId()); - - // 4. Initialize the secrets config. - configStore.initSecretsData(); - - // 5. Write the first secret value to the config bucket once the config key id has been saved. - configStore.storeCmsDatabasePassword(dbMasterPassword); - - logger.info("Uploading complete."); - } else { - final String errorMessage = String.format("Unexpected end status: %s", endStatus.name()); - logger.error(errorMessage); - - throw new UnexpectedCloudFormationStatusException(errorMessage); - } - } - - @Override - public boolean isRunnable(final CreateBaseCommand command) { - if (StringUtils.isNotBlank(environmentMetadata.getBucketName())) { - try { - final String stackId = configStore.getStackId(StackName.BASE); - - if (cloudFormationService.isStackPresent(stackId)) { - logger.warn("Operation has already run for the specified environment."); - return false; - } - } catch (IllegalStateException ise) { //NOPMD - // Don't care, fall through. - } - } - - return true; - } - - private Map mapAvailabilityZones() { - List zones = ec2Service.getAvailabilityZones(); - - if (zones.size() < MINIMUM_AZS) { - throw new IllegalStateException("Not enough availability zones for the selected region."); - } - - Map azByIdentifier = Maps.newHashMap(); - - for (int i = 1; i <= MINIMUM_AZS; i++) { - azByIdentifier.put(i, zones.get(i - 1)); - } - - return azByIdentifier; - } -} diff --git a/src/main/java/com/nike/cerberus/operation/core/CreateCerberusBackupOperation.java b/src/main/java/com/nike/cerberus/operation/core/CreateCerberusBackupOperation.java deleted file mode 100644 index 736144db..00000000 --- a/src/main/java/com/nike/cerberus/operation/core/CreateCerberusBackupOperation.java +++ /dev/null @@ -1,389 +0,0 @@ -/* - * Copyright (c) 2017 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.operation.core; - -import com.amazonaws.auth.policy.Policy; -import com.amazonaws.auth.policy.Principal; -import com.amazonaws.auth.policy.Resource; -import com.amazonaws.auth.policy.Statement; -import com.amazonaws.auth.policy.actions.KMSActions; -import com.amazonaws.regions.Region; -import com.amazonaws.regions.Regions; -import com.amazonaws.services.kms.AWSKMS; -import com.amazonaws.services.kms.AWSKMSClient; -import com.amazonaws.services.kms.model.CreateKeyRequest; -import com.amazonaws.services.kms.model.CreateKeyResult; -import com.amazonaws.services.kms.model.Tag; -import com.amazonaws.services.s3.AmazonS3; -import com.amazonaws.services.s3.AmazonS3ClientBuilder; -import com.amazonaws.services.s3.AmazonS3Encryption; -import com.amazonaws.services.s3.AmazonS3EncryptionClientBuilder; -import com.amazonaws.services.s3.model.Bucket; -import com.amazonaws.services.s3.model.CryptoConfiguration; -import com.amazonaws.services.s3.model.KMSEncryptionMaterialsProvider; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.PropertyNamingStrategy; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.databind.util.ISO8601DateFormat; -import com.google.common.collect.ImmutableMap; -import com.nike.cerberus.client.CerberusAdminClient; -import com.nike.cerberus.client.CerberusAdminClientFactory; -import com.nike.cerberus.command.core.SetBackupAdminPrincipalsCommand; -import com.nike.cerberus.domain.cms.SafeDepositBox; -import com.nike.cerberus.command.core.CreateCerberusBackupCommand; -import com.nike.cerberus.domain.EnvironmentMetadata; -import com.nike.cerberus.domain.backup.CerberusBackupMetadata; -import com.nike.cerberus.domain.backup.CerberusSdbMetadata; -import com.nike.cerberus.domain.environment.BackupRegionInfo; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.service.MetricsService; -import com.nike.cerberus.service.S3StoreService; -import com.nike.cerberus.store.ConfigStore; -import com.nike.vault.client.model.VaultListResponse; -import com.nike.vault.client.model.VaultResponse; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; - -import static com.nike.cerberus.module.CerberusModule.getAWSCredentialsProviderChain; - - -public class CreateCerberusBackupOperation implements Operation { - - private final Logger log = LoggerFactory.getLogger(getClass()); - - private static final String AWS_PROVIDER = "AWS"; - private static final int MAX_BUCKET_NAME_LENGTH = 63; - - private final ObjectMapper objectMapper; - private final ConfigStore configStore; - private final CerberusAdminClient cerberusAdminClient; - private final MetricsService metricsService; - private final EnvironmentMetadata environmentMetadata; - - private final Map regionToEncryptedStoreServiceMap = new HashMap<>(); - - @Inject - public CreateCerberusBackupOperation(CerberusAdminClientFactory cerberusAdminClientFactory, - ConfigStore configStore, - MetricsService metricsService, - EnvironmentMetadata environmentMetadata) { - - objectMapper = new ObjectMapper(); - objectMapper.findAndRegisterModules(); - objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); - objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); - objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); - objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); - objectMapper.enable(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS); - objectMapper.setDateFormat(new ISO8601DateFormat()); - - this.configStore = configStore; - this.metricsService = metricsService; - this.environmentMetadata = environmentMetadata; - - cerberusAdminClient = cerberusAdminClientFactory.createCerberusAdminClient( - configStore.getCerberusBaseUrl()); - } - - @Override - public void run(CreateCerberusBackupCommand command) { - log.info("Starting Cerberus Backup"); - - List regionsToStoreBackups = command.getBackupRegions(); - validateRegions(regionsToStoreBackups); - - Date now = new Date(); - String prefix = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(now); - List sdbMetadataList = cerberusAdminClient.getAllSdbMetadata(); - - int sdbCount = sdbMetadataList.size(); - if (sdbCount < 1) { - throw new RuntimeException("CMS returned no data when listing SDB metadata"); - } else { - log.info(String.format("CMS returned that there are %s SDBs to back up.", sdbMetadataList.size())); - } - - CerberusSdbMetadata cerberusSdbMetadata = new CerberusSdbMetadata(); - for (SafeDepositBox sdb : sdbMetadataList) { - log.info(String.format("Backing up %s", sdb.getName())); - Map> vaultData = recurseVault(sdb.getPath(), new HashMap<>()); - sdb.setData(vaultData); - String key = sdb.getName().toLowerCase().replaceAll("\\W+", "-"); - saveDataToS3(sdb, prefix, key, regionsToStoreBackups); - cerberusSdbMetadata = processMetadata(sdb, cerberusSdbMetadata); - } - - // save metadata - CerberusBackupMetadata metadata = new CerberusBackupMetadata() - .setCerberusUrl(configStore.getCerberusBaseUrl()) - .setBackupDate(now) - .setNumberOfSdbs(sdbMetadataList.size()) - .setNumberOfDataNodes(cerberusSdbMetadata.getNumberOfDataNodes()) - .setNumberOfKeyValuePairs(cerberusSdbMetadata.getNumberOfKeyValuePairs()) - .setNumberOfUniqueOwnerGroups(cerberusSdbMetadata.getUniqueOwnerGroups().size()) - .setNumberOfUniqueIamRoles(cerberusSdbMetadata.getUniqueIamRoles().size()) - .setNumberOfUniqueNonOwnerGroups(cerberusSdbMetadata.getUniqueNonOwnerGroups().size()); - - if (metadata.getNumberOfKeyValuePairs() < 1) { - throw new RuntimeException("The number of backed up key value pairs was less than 1, this probably means something bad is going on"); - } - - String key = "cerberus-backup-metadata.json"; - - saveDataToS3(metadata, prefix, key, regionsToStoreBackups); - - trackMetadataMetrics(metadata); - - regionsToStoreBackups.forEach(region -> { - String bucket = configStore.getBackupInfoForRegion(region).get().getS3Bucket(); - log.info("Backup for region: {} complete, bucket: {}, prefix: {}", region, bucket, prefix); - }); - } - - /** - * Vaildates that the user inputed regions are valid AWS regions - * - * @param regionsToStoreBackups The user inputed list of regions - */ - private void validateRegions(List regionsToStoreBackups) { - regionsToStoreBackups.forEach(region -> { - try { - Regions.fromName(region); - } catch (IllegalArgumentException e) { - throw new RuntimeException(String.format("The region %s is not a valid region", region)); - } - }); - } - - private void trackMetadataMetrics(CerberusBackupMetadata metadata) { - Map dimensions = ImmutableMap.of( - "cerberusUrl", metadata.getCerberusUrl(), - "env", "cerberus-" + environmentMetadata.getName(), - "region", environmentMetadata.getRegionName() - ); - - metricsService.trackGauge("numberOfSdbs", metadata.getNumberOfSdbs(), dimensions); - metricsService.trackGauge("numberOfDataNodes", metadata.getNumberOfDataNodes(), dimensions); - metricsService.trackGauge("numberOfKeyValuePairs", metadata.getNumberOfKeyValuePairs(), dimensions); - metricsService.trackGauge("numberOfUniqueOwnerGroups", metadata.getNumberOfUniqueOwnerGroups(), dimensions); - metricsService.trackGauge("numberOfUniqueIamRoles", metadata.getNumberOfUniqueIamRoles(), dimensions); - metricsService.trackGauge("numberOfUniqueNonOwnerGroups", metadata.getNumberOfUniqueNonOwnerGroups(), dimensions); - } - - /** - * Method to keep track of metadata - */ - private CerberusSdbMetadata processMetadata(SafeDepositBox sdb, final CerberusSdbMetadata currentMetadata) { - CerberusSdbMetadata newMetadata = new CerberusSdbMetadata( - currentMetadata.getNumberOfKeyValuePairs(), - currentMetadata.getNumberOfDataNodes(), - currentMetadata.getUniqueOwnerGroups(), - currentMetadata.getUniqueIamRoles(), - currentMetadata.getUniqueNonOwnerGroups() - ); - - newMetadata.getUniqueOwnerGroups().add(sdb.getOwner()); - - sdb.getIamRolePermissions().forEach((iamRole, permission) -> { - newMetadata.getUniqueIamRoles().add(iamRole); - }); - sdb.getUserGroupPermissions().forEach((userGroup, permission) -> { - newMetadata.getUniqueNonOwnerGroups().add(userGroup); - }); - - Map> vaultNodes = sdb.getData(); - newMetadata.setNumberOfDataNodes(newMetadata.getNumberOfDataNodes() + vaultNodes.size()); - vaultNodes.forEach((path, kvPairs) -> { - newMetadata.setNumberOfKeyValuePairs(newMetadata.getNumberOfKeyValuePairs() + kvPairs.size()); - }); - return newMetadata; - } - - /** - * Recurse a Vault path for data. - * - * @param path The path to recurse - * @return Map of Vault path Strings to Maps of String, String containing the secret kv pairs - */ - private Map> recurseVault(String path, Map> data) { - List keys = getKeys(path); - - keys.forEach(key -> { - String compositeKey = path + key; - if (key.endsWith("/")) { - recurseVault( compositeKey, data); - } else { - data.put(compositeKey, getData(compositeKey)); - } - }); - - return data; - } - - /** - * Lists keys for a vault path. - * - * @param path The path in Vault to list keys for - * @return List of keys, sub folders with have trailing / - */ - private List getKeys(String path) { - VaultListResponse vaultListResponse = cerberusAdminClient.list(path); - return vaultListResponse.getKeys(); - } - - /** - * Downloads Vault data for a given path. - * - * @param path The path of data to download - * @return The data map - */ - private Map getData(String path) { - CerberusAdminClient.GenericVaultResponse response = cerberusAdminClient.readDataGenerically(path); - return response.getData(); - } - - /** - * Using an S3 Encryption client saves the sdb data to the backup bucket / kms key - * @param object The sdb data to back up - * @param prefix The prefix / virtual folder to store the encrypted json - */ - private void saveDataToS3(Object object, String prefix, String key, List regions) { - String json = null; - try { - json = objectMapper.writeValueAsString(object); - } catch (JsonProcessingException e) { - throw new RuntimeException(String.format("Failed to serialized SDB data, Prefix: %s, Key: %s", prefix, key), e); - } - - for (String region : regions) { - S3StoreService storeService = regionToEncryptedStoreServiceMap.getOrDefault(region, - getEncryptedStoreServiceForRegion(region)); - - storeService.put(prefix + '/' + key, json); - } - } - - private S3StoreService getEncryptedStoreServiceForRegion(String region) { - Optional backupRegionInfo = configStore.getBackupInfoForRegion(region); - - if (! backupRegionInfo.isPresent()) { - String kmsCmkId = provisionKmsCmkForBackupRegion(region); - String backupBucket = provisionBackupBucketForRegion(region); - configStore.storeBackupInfoForRegion(region, backupBucket, kmsCmkId); - backupRegionInfo = Optional.of(new BackupRegionInfo(backupBucket, kmsCmkId)); - } - - KMSEncryptionMaterialsProvider materialProvider = - new KMSEncryptionMaterialsProvider(backupRegionInfo.get().getKmsCmkId()); - - AmazonS3Encryption encryptionClient = - AmazonS3EncryptionClientBuilder.standard() - .withCredentials(getAWSCredentialsProviderChain()) - .withEncryptionMaterials(materialProvider) - .withCryptoConfiguration(new CryptoConfiguration() - .withAwsKmsRegion(Region.getRegion(Regions.fromName(region)))) - .withRegion(region) - .build(); - - S3StoreService storeService = new S3StoreService(encryptionClient, backupRegionInfo.get().getS3Bucket(), ""); - regionToEncryptedStoreServiceMap.put(region, storeService); - return storeService; - } - - private String provisionBackupBucketForRegion(String region) { - AmazonS3 s3 = AmazonS3ClientBuilder.standard() - .withCredentials(getAWSCredentialsProviderChain()) - .withRegion(region) - .build(); - - Bucket bucket = s3.createBucket(getBackupBucketName(region)); - String bucketName = bucket.getName(); - log.info("Created bucket in region: {} with name: {}", region, bucketName); - return bucketName; - } - - private String getBackupBucketName(String region) { - String name = String.format("%s-%s-backup-%s", environmentMetadata.getName(), region, UUID.randomUUID().toString()); - StringUtils.truncate(name, MAX_BUCKET_NAME_LENGTH); - StringUtils.strip(name, "-"); - return name; - } - - private String provisionKmsCmkForBackupRegion(String region) { - Policy kmsPolicy = new Policy(); - final List statements = new LinkedList<>(); - // allow the configured admin iam principals all permissions - configStore.getBackupAdminIamPrincipals().forEach( principal -> { - log.debug("Adding principal: {} to the CMK Policy for region {}", principal, region); - statements.add(new Statement(Statement.Effect.Allow) - .withId("Principal " + principal + " Has All Actions") - .withPrincipals(new Principal(AWS_PROVIDER, principal, false)) - .withActions(KMSActions.AllKMSActions) - .withResources(new Resource("*"))); - }); - - kmsPolicy.setStatements(statements); - - String policyString = kmsPolicy.toJson(); - - log.debug("Creating key for region {} with policy {}", region, policyString); - - AWSKMS kms = AWSKMSClient.builder().withCredentials(getAWSCredentialsProviderChain()).withRegion(region).build(); - CreateKeyResult createKeyResult = kms.createKey( - new CreateKeyRequest() - .withPolicy(policyString) - .withBypassPolicyLockoutSafetyCheck(true) - .withDescription(String.format("Cerberus Backup Encryption key for env: %S region: %s", - environmentMetadata.getName(), region)) - .withTags( - new Tag().withTagKey("env").withTagValue(environmentMetadata.getName()), - new Tag().withTagKey("region").withTagValue(region), - new Tag().withTagKey("cerberus-backup-key").withTagValue("true") - - ) - ); - - String keyId = createKeyResult.getKeyMetadata().getKeyId(); - - log.info("Created new backup KMS CMK with id: {} for region: {}", keyId, region); - - return keyId; - } - - @Override - public boolean isRunnable(CreateCerberusBackupCommand command) { - if (configStore.getBackupAdminIamPrincipals().isEmpty()) { - log.error("Backup Admin Principals have not been set please run " + SetBackupAdminPrincipalsCommand.COMMAND_NAME); - return false; - } - return true; - } - -} diff --git a/src/main/java/com/nike/cerberus/operation/core/CreateEdgeDomainRecordOperation.java b/src/main/java/com/nike/cerberus/operation/core/CreateEdgeDomainRecordOperation.java new file mode 100644 index 00000000..5eeef06c --- /dev/null +++ b/src/main/java/com/nike/cerberus/operation/core/CreateEdgeDomainRecordOperation.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.operation.core; + +import com.amazonaws.services.route53.model.RRType; +import com.amazonaws.services.route53.model.ResourceRecord; +import com.amazonaws.services.route53.model.ResourceRecordSet; +import com.nike.cerberus.command.core.CreateEdgeDomainRecordCommand; +import com.nike.cerberus.domain.environment.Stack; +import com.nike.cerberus.operation.Operation; +import com.nike.cerberus.service.CloudFormationService; +import com.nike.cerberus.service.ConsoleService; +import com.nike.cerberus.service.Route53Service; +import com.nike.cerberus.store.ConfigStore; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javax.inject.Named; +import java.util.Optional; +import java.util.stream.Collectors; + +import static com.nike.cerberus.module.CerberusModule.ENV_NAME; + +/** + * Creates the edge domain Route53 record for Cerberus + */ +public class CreateEdgeDomainRecordOperation implements Operation { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private static final String RESOURCE_RECORD_TTL = "30"; // in seconds + + private final CloudFormationService cloudFormationService; + + private final ConfigStore configStore; + + private final String environmentName; + + private final Route53Service route53Service; + + private final ConsoleService consoleService; + + @Inject + public CreateEdgeDomainRecordOperation(CloudFormationService cloudFormationService, + ConfigStore configStore, + @Named(ENV_NAME) String environmentName, + Route53Service route53Service, + ConsoleService consoleService) { + + this.cloudFormationService = cloudFormationService; + this.configStore = configStore; + this.environmentName = environmentName; + this.route53Service = route53Service; + this.consoleService = consoleService; + } + + @Override + public void run(CreateEdgeDomainRecordCommand command) { + String recordValue = configStore.getRoute53StackOutputs().getOriginDomainName(); + String recordSetName = getEdgeDomainName(command.getBaseDomainName(), command.getEdgeDomainNameOverride()); + + route53Service.createRoute53RecordSet(command.getHostedZoneId(), + recordSetName, + recordValue, + RRType.CNAME, + RESOURCE_RECORD_TTL); + } + + @Override + public boolean isRunnable(CreateEdgeDomainRecordCommand command) { + String recordSetName = getEdgeDomainName(command.getBaseDomainName(), command.getEdgeDomainNameOverride()); + + boolean isRunnable = true; + + if (!cloudFormationService.isStackPresent(configStore.getPrimaryRegion(), Stack.ROUTE53.getFullName(environmentName))) { + logger.error("The route53 stack must be present"); + return false; + } + + Optional edgeRecordOptional = route53Service + .getRecordSetByName(recordSetName, command.getHostedZoneId()); + + if (edgeRecordOptional.isPresent()) { + String msg = String.format( + "The edge domain: '%s' already has a record set in hosted zone: '%s' with type: '%s' and value: " + + "'%s', if you proceed this will be overridden to value = '%s'", + recordSetName, + command.getHostedZoneId(), edgeRecordOptional.get().getType(), + edgeRecordOptional.get().getResourceRecords() + .stream().map(ResourceRecord::getValue) + .collect(Collectors.joining(", ")), + configStore.getRoute53StackOutputs().getOriginDomainName() + ); + + try { + consoleService.askUserToProceed(msg, ConsoleService.DefaultAction.NO); + } catch (RuntimeException e) { + isRunnable = false; + } + } + return isRunnable; + } + + private String getEdgeDomainName(String baseDomainName, String edgeDomainNameOverride) { + String defaultEdgeDomainName = String.format("%s.%s", + environmentName, + baseDomainName); + + return StringUtils.isBlank(edgeDomainNameOverride) ? + defaultEdgeDomainName : edgeDomainNameOverride; + } +} diff --git a/src/main/java/com/nike/cerberus/operation/core/CreateLoadBalancerOperation.java b/src/main/java/com/nike/cerberus/operation/core/CreateLoadBalancerOperation.java new file mode 100644 index 00000000..3c9d10bd --- /dev/null +++ b/src/main/java/com/nike/cerberus/operation/core/CreateLoadBalancerOperation.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.operation.core; + +import com.amazonaws.regions.Regions; +import com.google.common.collect.ImmutableMap; +import com.nike.cerberus.command.core.CreateLoadBalancerCommand; +import com.nike.cerberus.domain.cloudformation.LoadBalancerParameters; +import com.nike.cerberus.domain.cloudformation.VpcOutputs; +import com.nike.cerberus.domain.environment.Stack; +import com.nike.cerberus.operation.Operation; +import com.nike.cerberus.service.CloudFormationService; +import com.nike.cerberus.store.ConfigStore; +import com.nike.cerberus.util.CloudFormationObjectMapper; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javax.inject.Named; +import java.util.Map; + +import static com.nike.cerberus.module.CerberusModule.ENV_NAME; +import static java.util.stream.Collectors.joining; + +/** + * Creates the base components via CloudFormation used by all of Cerberus. + */ +public class CreateLoadBalancerOperation implements Operation { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + // https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/enable-access-logs.html + private final Map regionToAwsElbAccountIdMap = ImmutableMap.builder() + .put("us-east-1", "127311923021") + .put("us-east-2", "033677994240") + .put("us-west-1", "027434742980") + .put("us-west-2", "797873946194") + .put("ca-central-1", "985666609251") + .put("eu-central-1", "054676820928") + .put("eu-west-1", "156460612806") + .put("eu-west-2", "652711504416") + .put("eu-west-3", "009996457667") + .put("ap-northeast-1", "582318560864") + .put("ap-northeast-2", "600734575887") + .put("ap-southeast-1", "114774131450") + .put("ap-southeast-2", "783225319266") + .put("ap-south-1", "718504428378") + .put("sa-east-1", "507241528517") + .put("us-gov-west-1", "048591011584") + .put("cn-north-1", "638102146993") + .put("cn-northwest-1", "037604701340") + .build(); + + private final String environmentName; + + private final CloudFormationService cloudFormationService; + + private final ConfigStore configStore; + + private final CloudFormationObjectMapper cloudFormationObjectMapper; + + @Inject + public CreateLoadBalancerOperation(@Named(ENV_NAME) String environmentName, + CloudFormationService cloudFormationService, + ConfigStore configStore, + CloudFormationObjectMapper cloudFormationObjectMapper) { + + this.environmentName = environmentName; + this.cloudFormationService = cloudFormationService; + this.configStore = configStore; + this.cloudFormationObjectMapper = cloudFormationObjectMapper; + } + + @Override + public void run(CreateLoadBalancerCommand command) { + Regions loadBalancerRegion = configStore.getPrimaryRegion(); + + VpcOutputs vpcOutputs = configStore.getVpcStackOutputs(); + + // Use latest cert, if there happens to be more than one for some reason + String sslCertificateArn = configStore.getCertificationInformationList() + .getLast().getIdentityManagementCertificateArn(); + + if (!regionToAwsElbAccountIdMap.containsKey(loadBalancerRegion.getName())) { + throw new RuntimeException( + String.format("The region: %s was not in the region to AWS ELB Account Id map: [ %s ]", + loadBalancerRegion.getName(), + regionToAwsElbAccountIdMap.entrySet().stream() + .map(entry -> entry.getKey() + "->" + entry.getValue()) + .collect(joining(", "))) + ); + } + + LoadBalancerParameters loadBalancerParameters = new LoadBalancerParameters() + .setVpcId(vpcOutputs.getVpcId()) + .setSslCertificateArn(sslCertificateArn) + .setSgStackName(Stack.SECURITY_GROUPS.getFullName(environmentName)) + .setVpcSubnetIdForAz1(vpcOutputs.getVpcSubnetIdForAz1()) + .setVpcSubnetIdForAz2(vpcOutputs.getVpcSubnetIdForAz2()) + .setVpcSubnetIdForAz3(vpcOutputs.getVpcSubnetIdForAz3()) + .setElasticLoadBalancingAccountId(regionToAwsElbAccountIdMap.get(loadBalancerRegion.getName())); + + + if (StringUtils.isNotBlank(command.getLoadBalancerSslPolicyOverride())) { + loadBalancerParameters.setSslPolicy(command.getLoadBalancerSslPolicyOverride()); + } + + Map parameters = cloudFormationObjectMapper.convertValue(loadBalancerParameters); + + cloudFormationService.createStackAndWait( + loadBalancerRegion, + Stack.LOAD_BALANCER, + parameters, + true, + command.getTagsDelegate().getTags()); + } + + @Override + public boolean isRunnable(CreateLoadBalancerCommand command) { + boolean isRunnable = true; + + if (!cloudFormationService.isStackPresent(configStore.getPrimaryRegion(), + Stack.SECURITY_GROUPS.getFullName(environmentName))) { + logger.error("The security group stack must exist to create the load balancer!"); + isRunnable = false; + } + + if (configStore.getCertificationInformationList().isEmpty()) { + logger.error("TLS Certificate files have not been uploaded for environment"); + isRunnable = false; + } + + if (cloudFormationService.isStackPresent(configStore.getPrimaryRegion(), + Stack.LOAD_BALANCER.getFullName(environmentName))) { + logger.error("The load balancer stack already exists"); + isRunnable = false; + } + + return isRunnable; + } +} diff --git a/src/main/java/com/nike/cerberus/operation/core/CreateRoute53Operation.java b/src/main/java/com/nike/cerberus/operation/core/CreateRoute53Operation.java new file mode 100644 index 00000000..23a2a8a5 --- /dev/null +++ b/src/main/java/com/nike/cerberus/operation/core/CreateRoute53Operation.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.operation.core; + +import com.nike.cerberus.command.core.CreateRoute53Command; +import com.nike.cerberus.domain.cloudformation.Route53Parameters; +import com.nike.cerberus.domain.environment.Stack; +import com.nike.cerberus.operation.Operation; +import com.nike.cerberus.service.CloudFormationService; +import com.nike.cerberus.service.Route53Service; +import com.nike.cerberus.store.ConfigStore; +import com.nike.cerberus.util.CloudFormationObjectMapper; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javax.inject.Named; +import java.util.Map; + +import static com.nike.cerberus.module.CerberusModule.ENV_NAME; + +/** + * Creates the origin and load balancer Route53 records for Cerberus + */ +public class CreateRoute53Operation implements Operation { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private final String environmentName; + + private final CloudFormationService cloudFormationService; + + private final Route53Service route53Service; + + private final CloudFormationObjectMapper cloudFormationObjectMapper; + + private final ConfigStore configStore; + + @Inject + public CreateRoute53Operation(@Named(ENV_NAME) String environmentName, + CloudFormationService cloudFormationService, + Route53Service route53Service, + CloudFormationObjectMapper cloudFormationObjectMapper, + ConfigStore configStore) { + + this.environmentName = environmentName; + this.cloudFormationService = cloudFormationService; + this.route53Service = route53Service; + this.cloudFormationObjectMapper = cloudFormationObjectMapper; + this.configStore = configStore; + } + + @Override + public void run(CreateRoute53Command command) { + Route53Parameters route53Parameters = new Route53Parameters() + .setHostedZoneId(command.getHostedZoneId()) + .setLoadBalancerDomainName(getLoadBalancerDomainName(command.getBaseDomainName(), command.getLoadBalancerDomainNameOverride())) + .setLoadBalancerStackName(Stack.LOAD_BALANCER.getFullName(environmentName)) + .setOriginDomainName(getOriginDomainName(command.getBaseDomainName(), command.getOriginDomainNameOverride())); + + Map parameters = cloudFormationObjectMapper.convertValue(route53Parameters); + + cloudFormationService.createStackAndWait( + configStore.getPrimaryRegion(), + Stack.ROUTE53, + parameters, + true, + command.getTagsDelegate().getTags()); + } + + @Override + public boolean isRunnable(CreateRoute53Command command) { + String loadBalancerDomainName = getLoadBalancerDomainName(command.getBaseDomainName(), command.getLoadBalancerDomainNameOverride()); + String originDomainName = getOriginDomainName(command.getBaseDomainName(), command.getOriginDomainNameOverride()); + + boolean isRunnable = true; + if (!cloudFormationService.isStackPresent(configStore.getPrimaryRegion(), + Stack.LOAD_BALANCER.getFullName(environmentName))) { + logger.error("The load balancer stack must exist to create the Route53 record!"); + isRunnable = false; + } + if (cloudFormationService.isStackPresent(configStore.getPrimaryRegion(), + Stack.ROUTE53.getFullName(environmentName))) { + logger.error("Route53 stack already exists."); + isRunnable = false; + } + if (route53Service.getRecordSetByName(loadBalancerDomainName, command.getHostedZoneId()).isPresent()) { + logger.error("The load balancer name is already registered"); + isRunnable = false; + } + if (route53Service.getRecordSetByName(originDomainName, command.getHostedZoneId()).isPresent()) { + logger.error("The origin name is already registered"); + isRunnable = false; + } + + return isRunnable; + } + + private String getLoadBalancerDomainName(String baseDomainName, String loadBalancerDomainNameOverride) { + String defaultLoadBalancerDomainName = String.format("%s.%s.%s", + environmentName, + configStore.getPrimaryRegion().getName(), + baseDomainName); + + return StringUtils.isBlank(loadBalancerDomainNameOverride) ? + defaultLoadBalancerDomainName : loadBalancerDomainNameOverride; + } + + private String getOriginDomainName(String baseDomainName, String originDomainNameOverride) { + String defaultOriginDomainName = String.format("origin.%s.%s", + environmentName, + baseDomainName); + + return StringUtils.isBlank(originDomainNameOverride) ? + defaultOriginDomainName : originDomainNameOverride; + } +} diff --git a/src/main/java/com/nike/cerberus/operation/core/CreateSecurityGroupsOperation.java b/src/main/java/com/nike/cerberus/operation/core/CreateSecurityGroupsOperation.java new file mode 100644 index 00000000..bbf2f568 --- /dev/null +++ b/src/main/java/com/nike/cerberus/operation/core/CreateSecurityGroupsOperation.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.operation.core; + +import com.nike.cerberus.command.core.CreateSecurityGroupsCommand; +import com.nike.cerberus.domain.cloudformation.SecurityGroupParameters; +import com.nike.cerberus.domain.cloudformation.VpcOutputs; +import com.nike.cerberus.domain.environment.Stack; +import com.nike.cerberus.operation.Operation; +import com.nike.cerberus.service.CloudFormationService; +import com.nike.cerberus.store.ConfigStore; +import com.nike.cerberus.util.CloudFormationObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javax.inject.Named; +import java.util.Map; + +import static com.nike.cerberus.module.CerberusModule.ENV_NAME; + +/** + * Creates the base components via CloudFormation used by all of Cerberus. + */ +public class CreateSecurityGroupsOperation implements Operation { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private final String environmentName; + + private final CloudFormationService cloudFormationService; + + private final ConfigStore configStore; + + private final CloudFormationObjectMapper cloudFormationObjectMapper; + + @Inject + public CreateSecurityGroupsOperation(@Named(ENV_NAME) String environmentName, + CloudFormationService cloudFormationService, + ConfigStore configStore, + CloudFormationObjectMapper cloudFormationObjectMapper) { + this.environmentName = environmentName; + this.cloudFormationService = cloudFormationService; + this.configStore = configStore; + this.cloudFormationObjectMapper = cloudFormationObjectMapper; + } + + @Override + public void run(CreateSecurityGroupsCommand command) { + VpcOutputs vpcOutputs = configStore.getVpcStackOutputs(); + + SecurityGroupParameters securityGroupParameters = new SecurityGroupParameters() + .setVpcId(vpcOutputs.getVpcId()); + + Map parameters = cloudFormationObjectMapper.convertValue(securityGroupParameters); + + cloudFormationService.createStackAndWait( + configStore.getPrimaryRegion(), + Stack.SECURITY_GROUPS, + parameters, + true, + command.getTagParameters().getTags()); + } + + @Override + public boolean isRunnable(CreateSecurityGroupsCommand command) { + return !cloudFormationService.isStackPresent(configStore.getPrimaryRegion(), + Stack.SECURITY_GROUPS.getFullName(environmentName)); + } + +} diff --git a/src/main/java/com/nike/cerberus/operation/core/CreateVpcOperation.java b/src/main/java/com/nike/cerberus/operation/core/CreateVpcOperation.java new file mode 100644 index 00000000..21313661 --- /dev/null +++ b/src/main/java/com/nike/cerberus/operation/core/CreateVpcOperation.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.operation.core; + +import com.beust.jcommander.internal.Maps; +import com.nike.cerberus.command.core.CreateVpcCommand; +import com.nike.cerberus.domain.cloudformation.VpcParameters; +import com.nike.cerberus.domain.environment.Stack; +import com.nike.cerberus.operation.Operation; +import com.nike.cerberus.service.CloudFormationService; +import com.nike.cerberus.service.Ec2Service; +import com.nike.cerberus.store.ConfigStore; +import com.nike.cerberus.util.CloudFormationObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javax.inject.Named; +import java.util.List; +import java.util.Map; + +import static com.nike.cerberus.ConfigConstants.MINIMUM_AZS; +import static com.nike.cerberus.module.CerberusModule.ENV_NAME; + +/** + * Creates the base components via CloudFormation used by all of Cerberus. + */ +public class CreateVpcOperation implements Operation { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private final String environmentName; + + private final CloudFormationService cloudFormationService; + + private final Ec2Service ec2Service; + + private final CloudFormationObjectMapper cloudFormationObjectMapper; + + private final ConfigStore configStore; + + @Inject + public CreateVpcOperation(@Named(ENV_NAME) String environmentName, + CloudFormationService cloudFormationService, + Ec2Service ec2Service, + CloudFormationObjectMapper cloudFormationObjectMapper, + ConfigStore configStore) { + + this.environmentName = environmentName; + this.cloudFormationService = cloudFormationService; + this.ec2Service = ec2Service; + this.cloudFormationObjectMapper = cloudFormationObjectMapper; + this.configStore = configStore; + } + + @Override + public void run(CreateVpcCommand command) { + Map azByIdentifier = mapAvailabilityZones(); + + VpcParameters vpcParameters = new VpcParameters() + .setAz1(azByIdentifier.get(1)) + .setAz2(azByIdentifier.get(2)) + .setAz3(azByIdentifier.get(3)) + .setEnvironmentName(environmentName); + + Map parameters = cloudFormationObjectMapper.convertValue(vpcParameters); + + cloudFormationService.createStackAndWait( + configStore.getPrimaryRegion(), + Stack.VPC, + parameters, + true, + command.getTagsDelegate().getTags()); + } + + @Override + public boolean isRunnable(CreateVpcCommand command) { + return !cloudFormationService.isStackPresent(configStore.getPrimaryRegion(), + Stack.VPC.getFullName(environmentName)); + } + + private Map mapAvailabilityZones() { + List zones = ec2Service.getAvailabilityZones(); + + if (zones.size() < MINIMUM_AZS) { + throw new IllegalStateException("Not enough availability zones for the selected region."); + } + + Map azByIdentifier = Maps.newHashMap(); + + for (int i = 1; i <= MINIMUM_AZS; i++) { + azByIdentifier.put(i, zones.get(i - 1)); + } + + return azByIdentifier; + } +} diff --git a/src/main/java/com/nike/cerberus/operation/core/CreateWafOperation.java b/src/main/java/com/nike/cerberus/operation/core/CreateWafOperation.java new file mode 100644 index 00000000..e89331a5 --- /dev/null +++ b/src/main/java/com/nike/cerberus/operation/core/CreateWafOperation.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.operation.core; + +import com.nike.cerberus.command.core.CreateWafCommand; +import com.nike.cerberus.domain.cloudformation.WafParameters; +import com.nike.cerberus.domain.environment.Stack; +import com.nike.cerberus.operation.Operation; +import com.nike.cerberus.service.CloudFormationService; +import com.nike.cerberus.store.ConfigStore; +import com.nike.cerberus.util.CloudFormationObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javax.inject.Named; +import java.util.Map; + +import static com.nike.cerberus.module.CerberusModule.ENV_NAME; + +/** + * Creates the base components via CloudFormation used by all of Cerberus. + */ +public class CreateWafOperation implements Operation { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private final String environmentName; + + private final CloudFormationService cloudFormationService; + + private final CloudFormationObjectMapper cloudFormationObjectMapper; + + private final ConfigStore configStore; + + @Inject + public CreateWafOperation(@Named(ENV_NAME) String environmentName, + CloudFormationService cloudFormationService, + CloudFormationObjectMapper cloudFormationObjectMapper, + ConfigStore configStore) { + + this.environmentName = environmentName; + this.cloudFormationService = cloudFormationService; + this.cloudFormationObjectMapper = cloudFormationObjectMapper; + this.configStore = configStore; + } + + @Override + public void run(CreateWafCommand command) { + WafParameters wafParameters = new WafParameters() + .setLoadBalancerStackName(Stack.LOAD_BALANCER.getFullName(environmentName)) + .setWafName("cerberus-" + environmentName + "-waf"); + + Map parameters = cloudFormationObjectMapper.convertValue(wafParameters); + + cloudFormationService.createStackAndWait( + configStore.getPrimaryRegion(), + Stack.WAF, + parameters, + true, + command.getTagsDelegate().getTags() + ); + } + + @Override + public boolean isRunnable(CreateWafCommand command) { + try { + cloudFormationService.getStackId(configStore.getPrimaryRegion(), Stack.LOAD_BALANCER.getFullName(environmentName)); + } catch (IllegalArgumentException iae) { + logger.error("The load balancer stack must exist to create the WAF!"); + return false; + } + + return !cloudFormationService.isStackPresent(configStore.getPrimaryRegion(), + Stack.ROUTE53.getFullName(environmentName)); + } +} diff --git a/src/main/java/com/nike/cerberus/operation/core/DeleteStackOperation.java b/src/main/java/com/nike/cerberus/operation/core/DeleteStackOperation.java new file mode 100644 index 00000000..9390b417 --- /dev/null +++ b/src/main/java/com/nike/cerberus/operation/core/DeleteStackOperation.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.operation.core; + +import com.amazonaws.regions.Regions; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.iterable.S3Versions; +import com.amazonaws.services.s3.model.AmazonS3Exception; +import com.amazonaws.services.s3.model.S3VersionSummary; +import com.google.inject.Inject; +import com.nike.cerberus.command.core.DeleteStackCommand; +import com.nike.cerberus.operation.Operation; +import com.nike.cerberus.service.AwsClientFactory; +import com.nike.cerberus.service.CloudFormationService; +import com.nike.cerberus.store.ConfigStore; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Named; + +import static com.nike.cerberus.module.CerberusModule.ENV_NAME; + +public class DeleteStackOperation implements Operation { + + private final Logger log = LoggerFactory.getLogger(getClass()); + + private final String environmentName; + + private final CloudFormationService cloudFormationService; + + private final AwsClientFactory amazonS3Factory; + + private final ConfigStore configStore; + + @Inject + public DeleteStackOperation(@Named(ENV_NAME) String environmentName, + CloudFormationService cloudFormationService, + AwsClientFactory amazonS3Factory, + ConfigStore configStore) { + + this.environmentName = environmentName; + this.cloudFormationService = cloudFormationService; + this.amazonS3Factory = amazonS3Factory; + this.configStore = configStore; + } + + @Override + public void run(DeleteStackCommand command) { + Regions region = getRegion(command); + + String stackName = command.getStack().getFullName(environmentName); + cloudFormationService.getStackResources(region, stackName) + .forEach(stackResourceSummary -> { + if (stackResourceSummary.getResourceType().equals("AWS::S3::Bucket")) { + try { + String bucketName = stackResourceSummary.getPhysicalResourceId(); + log.info("Detected S3 Bucket: {}, emptying contents before deleting stack", bucketName); + AmazonS3 amazonS3 = amazonS3Factory.getClient(region); + for (S3VersionSummary version : S3Versions.inBucket(amazonS3, bucketName)) { + String key = version.getKey(); + String versionId = version.getVersionId(); + amazonS3.deleteVersion(bucketName, key, versionId); + } + } catch (AmazonS3Exception e) { + if (! e.getErrorCode().equals("NoSuchBucket")) { + throw e; + } + } + } + }); + + log.info("Deleting stack: {} in region: {}", stackName, region); + cloudFormationService.deleteStackAndWait(region, stackName); + log.info("Finished deleting stack: {} in region: {}", stackName, region); + } + + private Regions getRegion(DeleteStackCommand command) { + return command.getRegion() == null ? configStore.getPrimaryRegion() : Regions.fromName(command.getRegion()); + } + + @Override + public boolean isRunnable(DeleteStackCommand command) { + Regions region = getRegion(command); + if (! cloudFormationService.isStackPresent(region, command.getStack().getFullName(environmentName))) { + log.error("The stack: {} does not exist in region: {}", command.getStack(), region.getName()); + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/src/main/java/com/nike/cerberus/operation/core/GenerateCertificateFilesOperation.java b/src/main/java/com/nike/cerberus/operation/core/GenerateCertificateFilesOperation.java new file mode 100644 index 00000000..2dc66462 --- /dev/null +++ b/src/main/java/com/nike/cerberus/operation/core/GenerateCertificateFilesOperation.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.operation.core; + +import com.github.tomaslanger.chalk.Chalk; +import com.nike.cerberus.command.core.GenerateCertificateFilesCommand; +import com.nike.cerberus.command.core.GenerateCertificateFilesCommandParametersDelegate; +import com.nike.cerberus.operation.Operation; +import com.nike.cerberus.service.CertificateService; +import com.nike.cerberus.service.ConsoleService; +import com.nike.cerberus.store.ConfigStore; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javax.inject.Named; +import java.io.File; +import java.util.HashSet; +import java.util.Set; + +import static com.nike.cerberus.module.CerberusModule.ENV_NAME; +import static com.nike.cerberus.module.CerberusModule.IS_TTY; +import static com.nike.cerberus.service.ConsoleService.DefaultAction.NO; +import static com.nike.cerberus.service.ConsoleService.DefaultAction.YES; + +/** + * Operation that uses the cert service to generate the certificates needed to enable https in a Cerberus Env. + */ +public class GenerateCertificateFilesOperation implements Operation { + + private final Logger log = LoggerFactory.getLogger(getClass()); + + private final ConfigStore configStore; + private final CertificateService certService; + private final String environmentName; + private final ConsoleService consoleService; + private final boolean isTty; + + @Inject + public GenerateCertificateFilesOperation(ConfigStore configStore, + CertificateService certService, + @Named(ENV_NAME) String environmentName, + ConsoleService consoleService, + @Named(IS_TTY) boolean isTty) { + + this.configStore = configStore; + this.certService = certService; + this.environmentName = environmentName; + this.consoleService = consoleService; + this.isTty = isTty; + } + + @SuppressFBWarnings( + value = "REC_CATCH_EXCEPTION", + justification = "I do not want to catch 1000 individual exceptions" + ) + @Override + public void run(GenerateCertificateFilesCommand command) { + GenerateCertificateFilesCommandParametersDelegate parameters = + command.getGenerateCertificateFilesCommandParametersDelegate(); + + // The common name ex: demo.example.com + String commonName = StringUtils.isNotBlank(parameters.getEdgeDomainNameOverride()) ? + parameters.getEdgeDomainNameOverride() : + String.format("%s.%s", environmentName, parameters.getBaseDomainName()); + + // origin name san ex: origin.demo.example.com + String originName = StringUtils.isNotBlank(parameters.getOriginDomainNameOverride()) ? + parameters.getOriginDomainNameOverride() : + String.format("origin.%s.%s", environmentName, parameters.getBaseDomainName()); + + // The region specific subject alternate name for the load balancer. EX demo.us-west-2.example.com + String loadBalancerName = StringUtils.isNotBlank(parameters.getLoadBalancerDomainNameOverride()) ? + parameters.getLoadBalancerDomainNameOverride() : + String.format("%s.%s.%s", environmentName, configStore.getPrimaryRegion().getName(), + parameters.getBaseDomainName()); + + Set subjectAlternativeNames = new HashSet<>(); + subjectAlternativeNames.addAll(parameters.getSubjectAlternativeNames()); + subjectAlternativeNames.add(originName); + subjectAlternativeNames.add(loadBalancerName); + + // Enable the use of the hard coded lets encrypt cert if enabled + if (parameters.enableLetsEncryptCertfix()) { + log.warn("Setting acme4j.le.certfix system property to 'true', this only works if you use the special " + + "acme:// lets encrypt addr. See: https://shredzone.org/maven/acme4j/usage/session.html" + + " and https://shredzone.org/maven/acme4j/provider.html"); + + System.setProperty("acme4j.le.certfix", "true"); + } + + try { + // Use a temp dir or local dir if provided + File certDir = new File(parameters.getCertDir()); + + // check that we can write to the provided dir + FileUtils.forceMkdir(certDir); + if (!certDir.isDirectory() || !certDir.canWrite()) { + throw new RuntimeException("The certificate directory is not a directory or is not writable, path: " + + certDir.getAbsolutePath()); + } + + // confirm with user + String msg = String.format("Preparing to generate certs in %s with Common Name: %s and Subject Alternative Names: %s", + certDir.getAbsolutePath(), commonName, String.join(", ", subjectAlternativeNames)); + if (isTty) { + consoleService.askUserToProceed(msg, NO); + } else { + log.info(msg); + } + + // generate the certs + certService.generateCerts( + certDir, + parameters.getAcmeApiUrl(), + commonName, + subjectAlternativeNames, + parameters.getHostedZoneId(), + parameters.getContactEmail(), + command.getGenerateCertificateFilesCommandParametersDelegate().isAutoAcceptAcmeTos() + ); + } catch (Exception e) { + throw new RuntimeException("Failed to generate certs", e); + } + } + + @Override + public boolean isRunnable(GenerateCertificateFilesCommand command) { + boolean isRunnable = true; + if (isTty) { + boolean filesAlreadyPreset = true; + File certDir = new File(command.getGenerateCertificateFilesCommandParametersDelegate().getCertDir()); + try { + certService.checkForRequiredFiles(certDir); + } catch (RuntimeException e) { + filesAlreadyPreset = false; + } + if (filesAlreadyPreset) { + try { + consoleService.askUserToProceed( + Chalk.on(String.format("The required files already exists in %s would you like generate " + + "new files over these?", certDir.getAbsolutePath())).green().toString(), YES); + } catch (RuntimeException e) { + isRunnable = false; + } + } + } + return isRunnable; + } +} \ No newline at end of file diff --git a/src/main/java/com/nike/cerberus/operation/core/InitializeEnvironmentOperation.java b/src/main/java/com/nike/cerberus/operation/core/InitializeEnvironmentOperation.java new file mode 100644 index 00000000..d79b2e69 --- /dev/null +++ b/src/main/java/com/nike/cerberus/operation/core/InitializeEnvironmentOperation.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.operation.core; + +import com.amazonaws.regions.Regions; +import com.nike.cerberus.command.core.InitializeEnvironmentCommand; +import com.nike.cerberus.command.validator.EnvironmentNameValidator; +import com.nike.cerberus.domain.cloudformation.ConfigParameters; +import com.nike.cerberus.domain.cloudformation.ConfigOutputs; +import com.nike.cerberus.domain.environment.Stack; +import com.nike.cerberus.operation.Operation; +import com.nike.cerberus.service.CloudFormationService; +import com.nike.cerberus.store.ConfigStore; +import com.nike.cerberus.util.CloudFormationObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javax.inject.Named; +import java.util.HashMap; +import java.util.Map; + +import static com.nike.cerberus.module.CerberusModule.ENV_NAME; + +/** + * Creates the base components via CloudFormation used by all of Cerberus. + */ +public class InitializeEnvironmentOperation implements Operation { + + private final Logger log = LoggerFactory.getLogger(getClass()); + + private final String environmentName; + + private final CloudFormationService cloudFormationService; + + private final ConfigStore configStore; + + private final CloudFormationObjectMapper cloudFormationObjectMapper; + + @Inject + public InitializeEnvironmentOperation(@Named(ENV_NAME) String environmentName, + CloudFormationService cloudFormationService, + ConfigStore configStore, + CloudFormationObjectMapper cloudFormationObjectMapper) { + + this.environmentName = environmentName; + this.cloudFormationService = cloudFormationService; + this.configStore = configStore; + this.cloudFormationObjectMapper = cloudFormationObjectMapper; + } + + @Override + public void run(InitializeEnvironmentCommand command) { + Regions primaryRegion = Regions.fromName(command.getPrimaryRegion()); + + // create the global cms iam role + cloudFormationService.createStackAndWait(primaryRegion, Stack.IAM_ROLES, new HashMap<>(), true, + command.getTagsDelegate().getTags()); + + String cmsIamRoleArn = configStore.getCmsIamRoleOutputs(primaryRegion).getCmsIamRoleArn(); + + ConfigParameters configParameters = new ConfigParameters() + .setAccountAdminArn(command.getAdminRoleArn()) + .setCmsIamRoleArn(cmsIamRoleArn) + .setEnvironmentName(environmentName); + Map parameters = cloudFormationObjectMapper.convertValue(configParameters); + + Map regionConfigOutputsMap = new HashMap<>(); + // for each region, create a config bucket and kms cmk for encrypting environment data + command.getRegions().forEach(regionName -> { + Regions region = Regions.fromName(regionName); + cloudFormationService.createStackAndWait( + region, + Stack.CONFIG, parameters, true, + command.getTagsDelegate().getTags() + ); + regionConfigOutputsMap.put(region, configStore.getConfigBucketStackOutputs(region)); + }); + + configStore.initializeEnvironment(command.getAdminRoleArn(), cmsIamRoleArn, primaryRegion, regionConfigOutputsMap); + } + + @Override + public boolean isRunnable(InitializeEnvironmentCommand command) { + boolean isRunnable = true; + + if (!EnvironmentNameValidator.VALID_NAME_PATTERN.matcher(environmentName).matches()) { + throw new RuntimeException(EnvironmentNameValidator.ALLOWED_DESCRIPTION); + } + + if (command.getRegions().size() < 2) { + log.error("You must supply at least 2 regions so that config and secure data can be encrypted " + + "in a highly available manner"); + isRunnable = false; + } + + if (! command.getRegions().contains(command.getPrimaryRegion())) { + log.error("The primary region: {} must be in the region collection passed to the command. regions passed: {}", + command.getPrimaryRegion(), String.join(", ", command.getRegions())); + isRunnable = false; + } + + if (cloudFormationService.isStackPresent(Regions.fromName(command.getPrimaryRegion()), + Stack.IAM_ROLES.getFullName(environmentName))) { + + log.error("The IAM Role stack has already been created"); + isRunnable = false; + } + + + + return isRunnable; + } + +} diff --git a/src/main/java/com/nike/cerberus/operation/core/PrintStackInfoOperation.java b/src/main/java/com/nike/cerberus/operation/core/PrintStackInfoOperation.java index 4e6b1d46..e127f39b 100644 --- a/src/main/java/com/nike/cerberus/operation/core/PrintStackInfoOperation.java +++ b/src/main/java/com/nike/cerberus/operation/core/PrintStackInfoOperation.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Nike, Inc. + * Copyright (c) 2017 Nike, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,11 +30,11 @@ import javax.inject.Inject; import javax.inject.Named; - import java.util.List; import java.util.Map; import static com.nike.cerberus.module.CerberusModule.CONFIG_OBJECT_MAPPER; +import static com.nike.cerberus.module.CerberusModule.ENV_NAME; /** * Prints the parameters and outputs for the specified component. @@ -45,7 +45,7 @@ public class PrintStackInfoOperation implements Operation private final Logger logger = LoggerFactory.getLogger(getClass()); - private final ConfigStore configStore; + private final String environmentName; private final CloudFormationService cloudFormationService; @@ -53,34 +53,41 @@ public class PrintStackInfoOperation implements Operation private final ObjectMapper objectMapper; + private final ConfigStore configStore; + @Inject - public PrintStackInfoOperation(final ConfigStore configStore, - final CloudFormationService cloudFormationService, - final AutoScalingService autoScalingService, - @Named(CONFIG_OBJECT_MAPPER) final ObjectMapper objectMapper) { - this.configStore = configStore; + public PrintStackInfoOperation(@Named(ENV_NAME) String environmentName, + CloudFormationService cloudFormationService, + AutoScalingService autoScalingService, + @Named(CONFIG_OBJECT_MAPPER) ObjectMapper objectMapper, + ConfigStore configStore) { + + this.environmentName = environmentName; this.cloudFormationService = cloudFormationService; this.autoScalingService = autoScalingService; this.objectMapper = objectMapper; + this.configStore = configStore; } @Override public void run(final PrintStackInfoCommand command) { - final String stackId = configStore.getStackId(command.getStackName()); + String stackId = command.getStack().getFullName(environmentName); + + if (StringUtils.isBlank(stackId) || + !cloudFormationService.isStackPresent(configStore.getPrimaryRegion(), stackId)) { - if (StringUtils.isBlank(stackId) || !cloudFormationService.isStackPresent(stackId)) { - logger.error("The specified environment doesn't contain a stack for " + command.getStackName().getName()); + logger.error("The specified environment doesn't contain a stack for " + command.getStack().getName()); return; } - final Map stackParameters = cloudFormationService.getStackParameters(stackId); - final Map stackOutputs = cloudFormationService.getStackOutputs(stackId); - final StackInfo stackInfo = new StackInfo(); + Map stackParameters = cloudFormationService.getStackParameters(configStore.getPrimaryRegion(), stackId); + Map stackOutputs = cloudFormationService.getStackOutputs(configStore.getPrimaryRegion(), stackId); + StackInfo stackInfo = new StackInfo(); stackInfo.setStackId(stackId).setStackParameters(stackParameters).setStackOutputs(stackOutputs); - final String autoscalingGroupLogicalId = stackOutputs.get(ASG_LOGICAL_ID_OUTPUT_NAME); + String autoscalingGroupLogicalId = stackOutputs.get(ASG_LOGICAL_ID_OUTPUT_NAME); if (StringUtils.isNotBlank(autoscalingGroupLogicalId)) { - final List publicDnsForAutoScalingGroup = + List publicDnsForAutoScalingGroup = autoScalingService.getPublicDnsForAutoScalingGroup(autoscalingGroupLogicalId); stackInfo.setPublicDnsForInstances(publicDnsForAutoScalingGroup); } diff --git a/src/main/java/com/nike/cerberus/operation/core/RebootCmsOperation.java b/src/main/java/com/nike/cerberus/operation/core/RebootCmsOperation.java new file mode 100644 index 00000000..6bb6302d --- /dev/null +++ b/src/main/java/com/nike/cerberus/operation/core/RebootCmsOperation.java @@ -0,0 +1,349 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.operation.core; + +import com.amazonaws.services.ec2.AmazonEC2; +import com.amazonaws.services.ec2.AmazonEC2Client; +import com.amazonaws.services.ec2.model.*; +import com.github.tomaslanger.chalk.Chalk; +import com.google.inject.Inject; +import com.nike.cerberus.client.HttpClientFactory; +import com.nike.cerberus.command.core.RebootCmsCommand; +import com.nike.cerberus.domain.environment.Stack; +import com.nike.cerberus.operation.Operation; +import com.nike.cerberus.service.AutoScalingService; +import com.nike.cerberus.service.AwsClientFactory; +import com.nike.cerberus.service.CloudFormationService; +import com.nike.cerberus.service.Ec2Service; +import com.nike.cerberus.store.ConfigStore; +import com.nike.vault.client.http.HttpStatus; +import okhttp3.Call; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.net.util.SubnetUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Named; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.TimeUnit; + +import static com.nike.cerberus.module.CerberusModule.ENV_NAME; +import static com.nike.cerberus.service.CloudFormationService.MIN_INSTANCES_STACK_PARAMETER_KEY; +import static com.nike.cerberus.service.Ec2Service.EC2_ASG_GROUP_NAME_TAG_KEY; +import static com.nike.cerberus.service.Ec2Service.INSTANCE_STATE_FILTER_NAME; +import static com.nike.cerberus.service.Ec2Service.INSTANCE_STATE_RUNNING_FILTER_VALUE; + +/** + * Reboots all EC2 instances in the given cluster. + */ +public class RebootCmsOperation implements Operation { + + private final Logger log = LoggerFactory.getLogger(getClass()); + + private final static String CMS_HEALTH_CHECK_URL = "https://%s:%s/healthcheck"; + + private final static Integer CMS_HEALTH_CHECK_PORT = 8443; + + private final static int NUM_SECS_BETWEEN_HEALTH_CHECKS = 5; + + private final static int EXPECTED_NUM_SUCCESSES_AFTER_REBOOT = 10; + + private final static int EXPECTED_NUM_FAILURES_AFTER_REBOOT = 3; + + private final static int EXPECTED_NUM_SUCCESSES_BEFORE_REBOOT = 1; + + private final static int HEALTH_CHECK_FAILED_CODE = -1; + + private final ConfigStore configStore; + + private final CloudFormationService cloudFormationService; + + private final Ec2Service ec2Service; + + private final AutoScalingService autoScalingService; + + private final String environmentName; + + private final AmazonEC2 ec2Client; + + private final HttpClientFactory httpClientFactory; + + @Inject + public RebootCmsOperation(ConfigStore configStore, + CloudFormationService cloudFormationService, + Ec2Service ec2Service, + AutoScalingService autoScalingService, + @Named(ENV_NAME) String environmentName, + AwsClientFactory amazonS3ClientFactory, + HttpClientFactory httpClientFactory) { + + this.configStore = configStore; + this.cloudFormationService = cloudFormationService; + this.ec2Service = ec2Service; + this.autoScalingService = autoScalingService; + this.environmentName = environmentName; + this.ec2Client = amazonS3ClientFactory.getClient(configStore.getPrimaryRegion()); + this.httpClientFactory = httpClientFactory; + } + + @Override + public void run(final RebootCmsCommand command) { + log.warn(Chalk.on( + "If this command fails: the minimum instance size may need to be increased and an EC2 instance" + + " may need to be set to 'in-service' state on the auto scaling group").yellow().toString()); + + log.warn(Chalk.on("This command will lookup the current public ip of the system it is running on temporary add it to the " + + "vpc whitelist security group if it is not already on the list. If this command fails you may need to " + + "ensure that the white list security group get cleaned via the whitelist-cidr-for-vpc-access command").red().toString()); + + // white list the current public ip if needed + Optional tempCidrToWhitelist = getCidrIfWhitelistNeeded(); + tempCidrToWhitelist.ifPresent(this::whitelistCurrentIpCidr); + + try { + final Stack stack = Stack.CMS; + final String stackId = stack.getFullName(environmentName); + final Map stackOutputs = cloudFormationService.getStackOutputs(configStore.getPrimaryRegion(), stackId); + + final Map stackParameters = + cloudFormationService.getStackParameters(configStore.getPrimaryRegion(), stackId); + final int minInstances = Integer.parseInt(stackParameters.get(MIN_INSTANCES_STACK_PARAMETER_KEY)); + + final String autoScalingGroupId = stackOutputs.get(CloudFormationService.AUTO_SCALING_GROUP_LOGICAL_ID_OUTPUT_KEY); + log.debug("Found auto scaling group id for stack: {}", stackId); + + final Filter isRunningFilter = new Filter(INSTANCE_STATE_FILTER_NAME).withValues(INSTANCE_STATE_RUNNING_FILTER_VALUE); + final List instances = ec2Service.getInstancesByTag(EC2_ASG_GROUP_NAME_TAG_KEY, autoScalingGroupId, isRunningFilter); + log.debug("Found {} instances by tag: '{}:{}'", instances.size(), EC2_ASG_GROUP_NAME_TAG_KEY, autoScalingGroupId); + + log.info("Temporarily decreasing min instances for ASG: {}", autoScalingGroupId); + autoScalingService.updateMinInstancesForAutoScalingGroup(autoScalingGroupId, minInstances - 1); + + instances.forEach(instance -> rebootInstance(autoScalingGroupId, instance)); + + log.info("Increasing min instances for ASG: {}", autoScalingGroupId); + autoScalingService.updateMinInstancesForAutoScalingGroup(autoScalingGroupId, minInstances); + } catch (Throwable e) { + throw new RuntimeException("failed to do rolling reboot, will attempt to remove current ip from whitelist if needed", e); + } finally { + // if we whitelisted the current ip then remove it since we are done + tempCidrToWhitelist.ifPresent(this::removeCurrentIpCidrFromWhitelist); + } + } + + /** + * Reboot an instance and make sure it comes back healthy + */ + private void rebootInstance(String autoScalingGroupId, Instance instance) { + + final String healthCheckUrl = String.format(CMS_HEALTH_CHECK_URL, instance.getPublicIpAddress(), CMS_HEALTH_CHECK_PORT); + + log.info("Checking that instance health check is reachable..."); + waitForHealthCheckStatusCode(healthCheckUrl, HttpStatus.OK, EXPECTED_NUM_SUCCESSES_BEFORE_REBOOT); + + final String instanceId = instance.getInstanceId(); + log.info("Setting instance state to standby: {}", instanceId); + autoScalingService.setInstanceStateToStandby(autoScalingGroupId, instanceId); + + log.info("Rebooting instance: {}", instanceId); + ec2Service.rebootEc2Instance(instanceId); + + // wait for health check fail to confirm box reboot + log.info("Waiting for health check failure to confirm reboot..."); + waitForHealthCheckStatusCode(healthCheckUrl, HEALTH_CHECK_FAILED_CODE, EXPECTED_NUM_FAILURES_AFTER_REBOOT); + + log.info("Waiting for health check to pass again to confirm instance is healthy..."); + waitForHealthCheckStatusCode(healthCheckUrl, HttpStatus.OK, EXPECTED_NUM_SUCCESSES_AFTER_REBOOT); + + log.info("Setting instance state to in-service: {}", instanceId); + autoScalingService.setInstanceStateToInService(autoScalingGroupId, instanceId); + } + + /** + * Poll the health check 'n' times, looking for the given response + * + * @param healthCheckUrl - The health check URL + * @param numConsecutiveResponsesExpected - The number of times to poll health check + */ + private void waitForHealthCheckStatusCode(final String healthCheckUrl, + final long expectedStatusCode, + final int numConsecutiveResponsesExpected) { + + int responseCode; + int consecutiveResponses = 0; + + OkHttpClient healthCheckClient = httpClientFactory.getGenericClientWithCustomTruststore(); + + while (consecutiveResponses < numConsecutiveResponsesExpected) { + + responseCode = executeHealthCheck(healthCheckUrl, healthCheckClient); + + if (responseCode == expectedStatusCode) { + consecutiveResponses++; + } else if (consecutiveResponses > 0) { + final String message = Chalk.on("Instance health check did not repeat response code ({}), {} times").red().bold().toString(); + log.debug(message, expectedStatusCode, numConsecutiveResponsesExpected); + consecutiveResponses = 0; + } + + try { + TimeUnit.SECONDS.sleep(NUM_SECS_BETWEEN_HEALTH_CHECKS); + } catch (InterruptedException ie) { + log.error(Chalk.on("Timeout between health checks has been interrupted").red().bold().toString()); + return; + } + } + } + + /** + * Execute the given health check + * + * @param healthCheckUrl - Name of that EC2 instance belongs to + * @return - Response code of the health check + */ + private int executeHealthCheck(String healthCheckUrl, OkHttpClient okHttpClient) { + + final Request request = new Request.Builder() + .url(healthCheckUrl) + .get() + .build(); + + log.debug("Performing to the following request: {}", request); + + final Call healthCheckCall = okHttpClient.newCall(request); + + try (final Response response = healthCheckCall.execute()) { + log.debug("Health check returned status: {}, URL: {}", response.code(), healthCheckUrl); + return response.code(); + } catch (Exception e) { + if (StringUtils.contains(e.getMessage(), "PKIX path building failed")) { + throw new RuntimeException("Failed to validate certificate, this shouldn't happen because we use a" + + " custom trust store for this call using the ca chain you uploaded, so you ca chain might be malformed"); + } + final String message = Chalk.on("Health check failed, Cause: \"{}\", URL: {}").red().toString(); + log.debug(message, e.getMessage(), healthCheckUrl); + } + + return HEALTH_CHECK_FAILED_CODE; + } + + private String getCurrentPublicIpAddress() { + String whatIsMyIp = "http://checkip.amazonaws.com"; + try (Response response = httpClientFactory.getGenericClient() + .newCall(new Request.Builder().url(whatIsMyIp).get().build()).execute()) { + return StringUtils.trim(response.body().string()); + } catch (IOException e) { + throw new RuntimeException("Failed to lookup current ip", e); + } + } + + @Override + public boolean isRunnable(final RebootCmsCommand command) { + final Stack stack = Stack.CMS; + final String stackId = stack.getFullName(environmentName); + final Map stackParameters = + cloudFormationService.getStackParameters(configStore.getPrimaryRegion(), stackId); + + if (!stackParameters.containsKey(MIN_INSTANCES_STACK_PARAMETER_KEY)) { + log.error("Could not find parameter 'minInstances' on stack: {}", stackId); + return false; + } else { + return true; + } + } + + /** + * Looks up the current ip converts it to a CIDR and checks the vpc whitelist sg created by the whitelist vpc for ingress command + * to see if the current ip is already whitelisted to have access to the CMS Health Check Port. + * + * @return an optional of a the current public ip in CIDR form if it needs to be whitelisted. + */ + private Optional getCidrIfWhitelistNeeded() { + String currentIp = getCurrentPublicIpAddress(); + + String vpcIngressWhitelistSecurityGroup = configStore.getSecurityGroupStackOutputs().getWhitelistIngressSgId(); + + DescribeSecurityGroupsResult securityGroupsResult = ec2Client.describeSecurityGroups( + new DescribeSecurityGroupsRequest().withGroupIds(vpcIngressWhitelistSecurityGroup)); + + for (SecurityGroup securityGroup : securityGroupsResult.getSecurityGroups()) { + for (IpPermission ipPermission : securityGroup.getIpPermissions()) { + for (IpRange ipRange : ipPermission.getIpv4Ranges()) { + SubnetUtils subnetUtils = new SubnetUtils(ipRange.getCidrIp()); + if (isIpInCidrRange(currentIp, subnetUtils) && + Objects.equals(ipPermission.getFromPort(), CMS_HEALTH_CHECK_PORT) && + Objects.equals(ipPermission.getToPort(), CMS_HEALTH_CHECK_PORT)) { + + log.info("detected that ip: '{}' already has permission to ingress on port: {}, no need to whitelist", + currentIp, CMS_HEALTH_CHECK_PORT); + return Optional.empty(); + } + } + } + } + return Optional.of(currentIp + "/32"); + } + + private boolean isIpInCidrRange(String ip, SubnetUtils subnetUtils) { + String cidr = subnetUtils.getInfo().getCidrSignature(); + if (cidr.endsWith("/32") && StringUtils.removeEnd(cidr, "/32").equals(ip)) { + return true; + } + + return subnetUtils.getInfo().isInRange(ip); + } + + private void whitelistCurrentIpCidr(String cidr) { + log.info("Adding new ip permission to the vpc ingress sg, IP: '{}' From and To Port: {}", cidr, CMS_HEALTH_CHECK_PORT); + + String vpcIngressWhitelistSecurityGroup = configStore.getSecurityGroupStackOutputs().getWhitelistIngressSgId(); + AuthorizeSecurityGroupIngressRequest ingressRequest = new AuthorizeSecurityGroupIngressRequest() + .withGroupId(vpcIngressWhitelistSecurityGroup) + .withIpPermissions(getIpPermissionForCidr(cidr)); + ec2Client.authorizeSecurityGroupIngress(ingressRequest); + + try { + log.info("Sleeping for 1 minute to let sg changes be eventually consistent"); + Thread.sleep(TimeUnit.MINUTES.toMillis(1)); + } catch (InterruptedException e) { + log.error("Failed to wait for sg to be eventually consistent"); + } + } + + private void removeCurrentIpCidrFromWhitelist(String cidr) { + log.info("Removing new ip permission to the vpc ingress sg, IP: '{}' From and To Port: {}", cidr, CMS_HEALTH_CHECK_PORT); + + String vpcIngressWhitelistSecurityGroup = configStore.getSecurityGroupStackOutputs().getWhitelistIngressSgId(); + RevokeSecurityGroupIngressRequest revokeIngressRequest = new RevokeSecurityGroupIngressRequest() + .withGroupId(vpcIngressWhitelistSecurityGroup) + .withIpPermissions(getIpPermissionForCidr(cidr)); + ec2Client.revokeSecurityGroupIngress(revokeIngressRequest); + } + + private IpPermission getIpPermissionForCidr(String cidr) { + return new IpPermission() + .withIpv4Ranges(new IpRange().withCidrIp(cidr)) + .withIpProtocol("tcp") + .withFromPort(CMS_HEALTH_CHECK_PORT) + .withToPort(CMS_HEALTH_CHECK_PORT); + } + +} diff --git a/src/main/java/com/nike/cerberus/operation/core/RestoreCerberusBackupOperation.java b/src/main/java/com/nike/cerberus/operation/core/RestoreCerberusBackupOperation.java index 1fac3311..1fb5afaa 100644 --- a/src/main/java/com/nike/cerberus/operation/core/RestoreCerberusBackupOperation.java +++ b/src/main/java/com/nike/cerberus/operation/core/RestoreCerberusBackupOperation.java @@ -25,7 +25,6 @@ import com.amazonaws.services.s3.model.CryptoConfiguration; import com.amazonaws.services.s3.model.KMSEncryptionMaterialsProvider; import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.tomaslanger.chalk.Chalk; import com.google.inject.Inject; @@ -36,10 +35,6 @@ import com.nike.cerberus.operation.Operation; import com.nike.cerberus.service.ConsoleService; import com.nike.cerberus.service.S3StoreService; -import com.nike.vault.client.VaultAdminClient; -import com.nike.vault.client.VaultClientException; -import com.nike.vault.client.model.VaultListResponse; -import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,7 +42,6 @@ import java.io.IOException; import java.text.DecimalFormat; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -76,7 +70,7 @@ public class RestoreCerberusBackupOperation implements Operation backupMetadata; try { - backupMetadata = objectMapper.readValue(backupMetadataJsonString, new TypeReference>() {}); + backupMetadata = objectMapper.readValue(backupMetadataJsonString, new TypeReference>() { + }); } catch (IOException e) { throw new RuntimeException("Failed to deserialize backup metadata", e); } @@ -162,7 +158,7 @@ private void validateRestore(S3StoreService s3StoreService, RestoreCerberusBacku .append("\nYou are attempting to restore this backup to ") .append(Chalk.on(command.getCerberusUrl()).green().toString()); - if (! backupApiUrl.equalsIgnoreCase(command.getCerberusUrl())) { + if (!backupApiUrl.equalsIgnoreCase(command.getCerberusUrl())) { msg.append("\n\n") .append(Chalk.on("Warning: ").red().toString()) .append(Chalk.on("The backup was created for ").red().toString()) @@ -187,7 +183,7 @@ private void validateRestore(S3StoreService s3StoreService, RestoreCerberusBacku throw new RuntimeException("Failed to validate that the user wanted to proceed with backup", e); } - if (! proceed.equalsIgnoreCase("proceed")) { + if (!proceed.equalsIgnoreCase("proceed")) { throw new RuntimeException("User did not confirm to proceed with backup restore"); } } @@ -210,7 +206,7 @@ private S3StoreService getS3EncryptionStoreService(String cmkId, private String getKmsCmkId(String path, S3StoreService s3StoreService) { Map metadata = s3StoreService.getS3ObjectUserMetaData(path); - if (! metadata.containsKey("x-amz-matdesc")) { + if (!metadata.containsKey("x-amz-matdesc")) { throw new RuntimeException("Failed to get Customer Master Key ID from object user metadata. " + "'x-amz-matdesc' not found in metadata for object at path: " + path); } @@ -220,7 +216,8 @@ private String getKmsCmkId(String path, S3StoreService s3StoreService) { Map encryptionContextMap; try { encryptionContextMap = objectMapper.readValue(serializedEncryptionContext, - new TypeReference>() {}); + new TypeReference>() { + }); } catch (IOException e) { throw new RuntimeException("Failed to convert encryption context metadata value into Map"); } @@ -235,76 +232,6 @@ private String getDecryptedJson(String sdbBackupKey, S3StoreService s3StoreServi return json.get(); } - /** - * Process the stored backup json - * Step 1: Restores the metadata to CMS - * Step 2: Restores the secrete data to Vault - * @param sdbBackupJson the json string from s3 - */ - protected void processBackup(String sdbBackupJson, CerberusAdminClient cerberusAdminClient) throws IOException { - - JsonNode sdb = objectMapper.readTree(sdbBackupJson); - - // restore metadata to cms - cerberusAdminClient.restoreMetadata(sdbBackupJson); - deleteAllSecrets(sdb.get("path").asText(), cerberusAdminClient); - // restore secret vault data - JsonNode data = sdb.get("data"); - Map> kvPairs = objectMapper.convertValue(data, - new TypeReference>>() {}); - - kvPairs.forEach((String path, Map secretData) -> { - Map genericDataMap = new HashMap<>(); - secretData.forEach((String key , JsonNode valueNode)-> { - if (valueNode.isObject()) { - genericDataMap.put(key , objectMapper.convertValue(valueNode, - new TypeReference>() {}) - ); - } else if (valueNode.isTextual()) { - genericDataMap.put(key , valueNode.textValue()); - } else if (valueNode.isBoolean()) { - genericDataMap.put(key , valueNode.booleanValue()); - } else { - throw new RuntimeException("Unexpected value type for secret value. Type: " + valueNode.getClass()); - } - }); - cerberusAdminClient.writeJson(path, genericDataMap); - }); - } - - /** - * Deletes all of the secrets from Vault stored at the safe deposit box's path. - * - * @param path path to start deleting at. - */ - protected void deleteAllSecrets(final String path, VaultAdminClient vaultAdminClient) { - try { - String fixedPath = path; - - if (StringUtils.endsWith(path, "/")) { - fixedPath = StringUtils.substring(path, 0, StringUtils.lastIndexOf(path, "/")); - } - - final VaultListResponse listResponse = vaultAdminClient.list(fixedPath); - final List keys = listResponse.getKeys(); - - if (keys == null || keys.isEmpty()) { - return; - } - - for (final String key : keys) { - if (StringUtils.endsWith(key, "/")) { - final String fixedKey = StringUtils.substring(key, 0, key.lastIndexOf("/")); - deleteAllSecrets(fixedPath + "/" + fixedKey, vaultAdminClient); - } else { - vaultAdminClient.delete(fixedPath + "/" + key); - } - } - } catch (VaultClientException vce) { - throw new RuntimeException("Failed to delete secrets from Vault. for path: " + path); - } - } - @Override public boolean isRunnable(RestoreCerberusBackupCommand command) { return true; diff --git a/src/main/java/com/nike/cerberus/operation/core/RollingRebootWithHealthCheckOperation.java b/src/main/java/com/nike/cerberus/operation/core/RollingRebootWithHealthCheckOperation.java deleted file mode 100644 index 94904631..00000000 --- a/src/main/java/com/nike/cerberus/operation/core/RollingRebootWithHealthCheckOperation.java +++ /dev/null @@ -1,235 +0,0 @@ -package com.nike.cerberus.operation.core; - -import com.amazonaws.services.ec2.model.Filter; -import com.amazonaws.services.ec2.model.Instance; -import com.github.tomaslanger.chalk.Chalk; -import com.google.common.collect.ImmutableMap; -import com.google.inject.Inject; -import com.nike.cerberus.command.core.RollingRebootWithHealthCheckCommand; -import com.nike.cerberus.domain.environment.StackName; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.service.AutoScalingService; -import com.nike.cerberus.service.CloudFormationService; -import com.nike.cerberus.service.Ec2Service; -import com.nike.cerberus.store.ConfigStore; -import com.nike.vault.client.http.HttpStatus; -import okhttp3.Call; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import org.apache.http.conn.ssl.NoopHostnameVerifier; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.net.Proxy; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -import static com.nike.cerberus.service.CloudFormationService.MIN_INSTANCES_STACK_PARAMETER_KEY; -import static com.nike.cerberus.service.Ec2Service.EC2_ASG_GROUP_NAME_TAG_KEY; -import static com.nike.cerberus.service.Ec2Service.INSTANCE_STATE_FILTER_NAME; -import static com.nike.cerberus.service.Ec2Service.INSTANCE_STATE_RUNNING_FILTER_VALUE; - -/** - * Reboots all EC2 instances in the given cluster. - */ -public class RollingRebootWithHealthCheckOperation implements Operation { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private final static ImmutableMap HEALTH_CHECK_MAP = ImmutableMap.of( - StackName.CMS.getName(), "http://%s:8080/healthcheck", - StackName.GATEWAY.getName(), "https://%s:443/sys/health", - StackName.VAULT.getName(), "https://%s:8200/v1/sys/health?standbyok" - // TODO: upgrade Consul and use new healthcheck -// StackName.CONSUL.getName(), "http://%s:8580/v1/status/peers" - ); - - private final static int DEFAULT_HTTP_TIMEOUT = 15; - - private final static TimeUnit DEFAULT_HTTP_TIMEOUT_UNIT = TimeUnit.SECONDS; - - private final static int NUM_SECS_BETWEEN_HEALTH_CHECKS = 5; - - private final static int EXPECTED_NUM_SUCCESSES_AFTER_REBOOT = 10; - - private final static int EXPECTED_NUM_FAILURES_AFTER_REBOOT = 3; - - private final static int EXPECTED_NUM_SUCCESSES_BEFORE_REBOOT = 1; - - private final static int HEALTH_CHECK_FAILED_CODE = -1; - - private final ConfigStore configStore; - - private final CloudFormationService cloudFormationService; - - private final Ec2Service ec2Service; - - private final AutoScalingService autoScalingService; - - private final Proxy proxy; - - @Inject - public RollingRebootWithHealthCheckOperation(final ConfigStore configStore, - final CloudFormationService cloudFormationService, - final Ec2Service ec2Service, - final AutoScalingService autoScalingService, - final Proxy proxy) { - this.configStore = configStore; - this.cloudFormationService = cloudFormationService; - this.ec2Service = ec2Service; - this.autoScalingService = autoScalingService; - this.proxy = proxy; - } - - @Override - public void run(final RollingRebootWithHealthCheckCommand command) { - - logger.warn(Chalk.on( - "If this command fails: the minimum instance size may need to be increased and an EC2 instance" + - " may need to be set to 'in-service' state on the auto scaling group").yellow().toString()); - - final StackName stackName = command.getStackName(); - final String stackId = configStore.getStackId(stackName); - final Map stackOutputs = cloudFormationService.getStackOutputs(stackId); - - final Map stackParameters = cloudFormationService.getStackParameters(stackId); - final int minInstances = Integer.parseInt(stackParameters.get(MIN_INSTANCES_STACK_PARAMETER_KEY)); - - final String autoScalingGroupId = stackOutputs.get(CloudFormationService.AUTO_SCALING_GROUP_LOGICAL_ID_OUTPUT_KEY); - logger.debug("Found auto scaling group id for stack: {}", stackId); - - final Filter isRunningFilter = new Filter(INSTANCE_STATE_FILTER_NAME).withValues(INSTANCE_STATE_RUNNING_FILTER_VALUE); - final List instances = ec2Service.getInstancesByTag(EC2_ASG_GROUP_NAME_TAG_KEY, autoScalingGroupId, isRunningFilter); - logger.debug("Found {} instances by tag: '{}:{}'", instances.size(), EC2_ASG_GROUP_NAME_TAG_KEY, autoScalingGroupId); - - logger.info("Temporarily decreasing min instances for ASG: {}", autoScalingGroupId); - autoScalingService.updateMinInstancesForAutoScalingGroup(autoScalingGroupId, minInstances - 1); - - instances.forEach(instance -> { - rebootInstance(stackName, autoScalingGroupId, instance); - }); - - logger.info("Increasing min instances for ASG: {}", autoScalingGroupId); - autoScalingService.updateMinInstancesForAutoScalingGroup(autoScalingGroupId, minInstances); - } - - /** - * Reboot an instance and make sure it comes back healthy - */ - private void rebootInstance(StackName stackName, String autoScalingGroupId, Instance instance) { - - final String healthCheckUrlTmpl = HEALTH_CHECK_MAP.get(stackName.getName()); - final String healthCheckUrl = String.format(healthCheckUrlTmpl, instance.getPublicDnsName()); - - logger.info("Checking that instance health check is reachable..."); - waitForHealthCheckStatusCode(healthCheckUrl, HttpStatus.OK, EXPECTED_NUM_SUCCESSES_BEFORE_REBOOT); - - final String instanceId = instance.getInstanceId(); - logger.info("Setting instance state to standby: {}", instanceId); - autoScalingService.setInstanceStateToStandby(autoScalingGroupId, instanceId); - - logger.info("Rebooting instance: {}", instanceId); - ec2Service.rebootEc2Instance(instanceId); - - // wait for health check fail to confirm box reboot - logger.info("Waiting for health check failure to confirm reboot..."); - waitForHealthCheckStatusCode(healthCheckUrl, HEALTH_CHECK_FAILED_CODE, EXPECTED_NUM_FAILURES_AFTER_REBOOT); - - // wait for health check pass to confirm instance is healthy after reboot - logger.warn(Chalk.on( - "If a proxy is required to talk to the EC2 instance, then make sure it is set up." + - " Otherwise this command will never succeed.").yellow().toString()); - logger.info("Waiting for health check to pass again to confirm instance is healthy..."); - waitForHealthCheckStatusCode(healthCheckUrl, HttpStatus.OK, EXPECTED_NUM_SUCCESSES_AFTER_REBOOT); - - logger.info("Setting instance state to in-service: {}", instanceId); - autoScalingService.setInstanceStateToInService(autoScalingGroupId, instanceId); - } - - /** - * Poll the health check 'n' times, looking for the given response - * @param healthCheckUrl - The health check URL - * @param numConsecutiveResponsesExpected - The number of times to poll health check - */ - private void waitForHealthCheckStatusCode(final String healthCheckUrl, - final long expectedStatusCode, - final int numConsecutiveResponsesExpected) { - - int responseCode; - int consecutiveResponses = 0; - while (consecutiveResponses < numConsecutiveResponsesExpected) { - - responseCode = executeHealthCheck(healthCheckUrl); - - if (responseCode == expectedStatusCode) { - consecutiveResponses++; - } else if (consecutiveResponses > 0) { - final String message = Chalk.on("Instance health check did not repeat response code ({}), {} times").red().bold().toString(); - logger.debug(message, expectedStatusCode, numConsecutiveResponsesExpected); - consecutiveResponses = 0; - } - - try { - TimeUnit.SECONDS.sleep(NUM_SECS_BETWEEN_HEALTH_CHECKS); - } catch (InterruptedException ie) { - logger.error(Chalk.on("Timeout between health checks has been interrupted").red().bold().toString()); - return; - } - } - } - - /** - * Execute the given health check - * @param healthCheckUrl - Name of that EC2 instance belongs to - * @return - Response code of the health check - */ - private int executeHealthCheck(final String healthCheckUrl) { - - final OkHttpClient okHttpClient = new OkHttpClient.Builder() - .hostnameVerifier(new NoopHostnameVerifier()) - .proxy(proxy) - .connectTimeout(DEFAULT_HTTP_TIMEOUT, DEFAULT_HTTP_TIMEOUT_UNIT) - .writeTimeout(DEFAULT_HTTP_TIMEOUT, DEFAULT_HTTP_TIMEOUT_UNIT) - .readTimeout(DEFAULT_HTTP_TIMEOUT, DEFAULT_HTTP_TIMEOUT_UNIT) - .build(); - - final Request requestBuilder = new Request.Builder() - .url(healthCheckUrl) - .get() - .build(); - - final Call healthCheckCall = okHttpClient.newCall(requestBuilder); - - try(final Response response = healthCheckCall.execute()) { - logger.debug("Health check returned status: {}, URL: {}", response.code(), healthCheckUrl); - return response.code(); - } catch (IOException ioe) { - final String message = Chalk.on("Health check failed, Cause: \"{}\", URL: {}").red().toString(); - logger.debug(message, ioe.getMessage(), healthCheckUrl); - } - - return HEALTH_CHECK_FAILED_CODE; - } - - @Override - public boolean isRunnable(final RollingRebootWithHealthCheckCommand command) { - - final StackName stackName = command.getStackName(); - final String stackNameStr = stackName.getName(); - final String stackId = configStore.getStackId(stackName); - final Map stackParameters = cloudFormationService.getStackParameters(stackId); - - if (! HEALTH_CHECK_MAP.containsKey(stackNameStr)) { - logger.error("Cannot reboot cluster: {}. Allowed stacks: {}", stackName, HEALTH_CHECK_MAP.keySet()); - return false; - } else if (! stackParameters.containsKey(MIN_INSTANCES_STACK_PARAMETER_KEY)) { - logger.error("Could not find parameter 'minInstances' on stack: {}", stackId); - return false; - } else { - return true; - } - } -} diff --git a/src/main/java/com/nike/cerberus/operation/core/SetBackupAdminPrincipalsOperation.java b/src/main/java/com/nike/cerberus/operation/core/SetBackupAdminPrincipalsOperation.java deleted file mode 100644 index c80d96b7..00000000 --- a/src/main/java/com/nike/cerberus/operation/core/SetBackupAdminPrincipalsOperation.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (c) 2017 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.operation.core; - -import com.amazonaws.auth.policy.Policy; -import com.amazonaws.auth.policy.Principal; -import com.amazonaws.auth.policy.Resource; -import com.amazonaws.auth.policy.Statement; -import com.amazonaws.auth.policy.actions.KMSActions; -import com.amazonaws.services.kms.AWSKMS; -import com.amazonaws.services.kms.AWSKMSClient; -import com.amazonaws.services.kms.model.PutKeyPolicyRequest; -import com.amazonaws.services.securitytoken.AWSSecurityTokenService; -import com.amazonaws.services.securitytoken.model.GetCallerIdentityRequest; -import com.amazonaws.services.securitytoken.model.GetCallerIdentityResult; -import com.nike.cerberus.command.core.SetBackupAdminPrincipalsCommand; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.store.ConfigStore; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; -import java.util.*; - -import static com.nike.cerberus.module.CerberusModule.getAWSCredentialsProviderChain; - -/** - * Operation to update which principals besides for the root account will have permissions to use the backup cmk, - * AKA create and restore backups - */ -public class SetBackupAdminPrincipalsOperation implements Operation { - - private final Logger log = LoggerFactory.getLogger(getClass()); - - private static final String AWS_PROVIDER = "AWS"; - - private final ConfigStore configStore; - private final AWSSecurityTokenService sts; - - @Inject - public SetBackupAdminPrincipalsOperation(ConfigStore configStore, - AWSSecurityTokenService sts) { - - this.configStore = configStore; - this.sts = sts; - } - - @Override - public void run(SetBackupAdminPrincipalsCommand command) { - GetCallerIdentityResult identityResult = sts.getCallerIdentity(new GetCallerIdentityRequest()); - String accountId = identityResult.getAccount(); - String rootArn = String.format("arn:aws:iam::%s:root", accountId); - String adminRoleArn = configStore.getAccountAdminArn().get(); - - Set principals = new HashSet<>(); - principals.add(rootArn); - principals.add(adminRoleArn); - principals.addAll(command.getAdditionalPrincipals()); - - configStore.storeBackupAdminIamPrincipals(principals); - - if (! configStore.getRegionBackupBucketMap().isEmpty()) { - configStore.getRegionBackupBucketMap().forEach((region, backupRegionInfo) -> { - final List statements = new LinkedList<>(); - principals.forEach( principal -> { - log.debug("Adding principal: {} to the CMK Policy for region {}", principal, region); - statements.add(new Statement(Statement.Effect.Allow) - .withId("Principal " + principal + " Has All Actions") - .withPrincipals(new Principal(AWS_PROVIDER, principal, false)) - .withActions(KMSActions.AllKMSActions) - .withResources(new Resource("*"))); - }); - - Policy kmsPolicy = new Policy(); - kmsPolicy.setStatements(statements); - String policyString = kmsPolicy.toJson(); - - log.debug("Updating key {} for region {} with policy {}", backupRegionInfo.getKmsCmkId(), region, policyString); - - AWSKMS kms = AWSKMSClient.builder().withCredentials(getAWSCredentialsProviderChain()).withRegion(region).build(); - PutKeyPolicyRequest request = new PutKeyPolicyRequest() - .withKeyId(backupRegionInfo.getKmsCmkId()) - .withPolicyName("default") - .withBypassPolicyLockoutSafetyCheck(true) - .withPolicy(policyString); - - kms.putKeyPolicy(request); - - log.info("Successfully updated key {} in region {} to allow the following principals access {}", - backupRegionInfo.getKmsCmkId(), region, String.join(", ", principals)); - }); - } - } - - @Override - public boolean isRunnable(SetBackupAdminPrincipalsCommand command) { - Optional adminIamPrincipalArn = configStore.getAccountAdminArn(); - if (! adminIamPrincipalArn.isPresent()) { - log.error("The admin IAM principal must be set for this environment"); - return false; - } - return true; - } - -} diff --git a/src/main/java/com/nike/cerberus/operation/core/UpdateStackOperation.java b/src/main/java/com/nike/cerberus/operation/core/UpdateStackOperation.java index feb6d8fc..b182dd10 100644 --- a/src/main/java/com/nike/cerberus/operation/core/UpdateStackOperation.java +++ b/src/main/java/com/nike/cerberus/operation/core/UpdateStackOperation.java @@ -17,27 +17,12 @@ package com.nike.cerberus.operation.core; import com.amazonaws.AmazonServiceException; -import com.amazonaws.services.cloudformation.model.StackStatus; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Sets; -import com.nike.cerberus.ConfigConstants; import com.nike.cerberus.command.core.UpdateStackCommand; -import com.nike.cerberus.domain.cloudformation.BaseParameters; -import com.nike.cerberus.domain.cloudformation.CmsParameters; -import com.nike.cerberus.domain.cloudformation.ConsulParameters; -import com.nike.cerberus.domain.cloudformation.GatewayParameters; -import com.nike.cerberus.domain.cloudformation.LaunchConfigParameters; -import com.nike.cerberus.domain.cloudformation.SslConfigParametersDelegate; -import com.nike.cerberus.domain.cloudformation.TagParameters; -import com.nike.cerberus.domain.cloudformation.VaultParameters; -import com.nike.cerberus.domain.environment.StackName; +import com.nike.cerberus.domain.environment.Stack; import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.operation.UnexpectedCloudFormationStatusException; +import com.nike.cerberus.service.AmiTagCheckService; import com.nike.cerberus.service.CloudFormationService; import com.nike.cerberus.service.Ec2UserDataService; -import com.nike.cerberus.service.AmiTagCheckService; import com.nike.cerberus.store.ConfigStore; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -45,108 +30,88 @@ import javax.inject.Inject; import javax.inject.Named; -import java.util.HashMap; -import java.util.List; import java.util.Map; +import java.util.Optional; -import static com.amazonaws.services.cloudformation.model.StackStatus.*; -import static com.nike.cerberus.ConfigConstants.CERT_PART_PUBKEY; -import static com.nike.cerberus.module.CerberusModule.CF_OBJECT_MAPPER; +import static com.nike.cerberus.module.CerberusModule.ENV_NAME; /** * Operation for updating stacks. */ public class UpdateStackOperation implements Operation { - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private final Map> stackParameterMap; - private final Map stackTemplatePathMap; - - private final ConfigStore configStore; + private final Logger logger = LoggerFactory.getLogger(getClass()); private final CloudFormationService cloudFormationService; - - private final ObjectMapper cloudformationObjectMapper; - private final Ec2UserDataService ec2UserDataService; - + private final String environmentName; + private final ConfigStore configStore; private final AmiTagCheckService amiTagCheckService; @Inject - public UpdateStackOperation(final ConfigStore configStore, - final CloudFormationService cloudFormationService, - @Named(CF_OBJECT_MAPPER) final ObjectMapper cloudformationObjectMapper, - final Ec2UserDataService ec2UserDataService, - final AmiTagCheckService amiTagCheckService) { - this.configStore = configStore; + public UpdateStackOperation(CloudFormationService cloudFormationService, + Ec2UserDataService ec2UserDataService, + @Named(ENV_NAME) String environmentName, + AmiTagCheckService amiTagCheckService, + ConfigStore configStore) { + this.cloudFormationService = cloudFormationService; - this.cloudformationObjectMapper = cloudformationObjectMapper; this.ec2UserDataService = ec2UserDataService; + this.environmentName = environmentName; this.amiTagCheckService = amiTagCheckService; - - stackParameterMap = new HashMap<>(); - stackParameterMap.put(StackName.CONSUL, ConsulParameters.class); - stackParameterMap.put(StackName.VAULT, VaultParameters.class); - stackParameterMap.put(StackName.CMS, CmsParameters.class); - stackParameterMap.put(StackName.GATEWAY, GatewayParameters.class); - - stackTemplatePathMap = new HashMap<>(); - stackTemplatePathMap.put(StackName.BASE, ConfigConstants.BASE_STACK_TEMPLATE_PATH); - stackTemplatePathMap.put(StackName.CONSUL, ConfigConstants.CONSUL_STACK_TEMPLATE_PATH); - stackTemplatePathMap.put(StackName.VAULT, ConfigConstants.VAULT_STACK_TEMPLATE_PATH); - stackTemplatePathMap.put(StackName.CMS, ConfigConstants.CMS_STACK_TEMPLATE_PATH); - stackTemplatePathMap.put(StackName.GATEWAY, ConfigConstants.GATEWAY_STACK_TEMPLATE_PATH); + this.configStore = configStore; } @Override - public void run(final UpdateStackCommand command) { - final String stackId = configStore.getStackId(command.getStackName()); - final Class parametersClass = stackParameterMap.get(command.getStackName()); - final Map parameters; + public void run(UpdateStackCommand command) { + String stackId = command.getStack().getFullName(environmentName); - if (parametersClass != null) { - parameters = getUpdateLaunchConfigParameters(command.getStackName(), command, parametersClass); - } else if (StackName.BASE == command.getStackName()) { - parameters = getUpdatedBaseStackParameters(command); - } else { - throw new IllegalArgumentException("The specified stack does not support the update stack command!"); - } + Map parameters = cloudFormationService.getStackParameters(configStore.getPrimaryRegion(), stackId); + Map tags = command.getTagsDelegate().getTags(); - // Make sure the given AmiId is for this component. Check if it contains required tag - // There is no AMI for Base. - if ( !command.isSkipAmiTagCheck() && StackName.BASE != command.getStackName() ) { - amiTagCheckService.validateAmiTagForStack(command.getAmiId(),command.getStackName()); + // only some stacks need user data + if (command.getStack().needsUserData()) { + parameters.put("userData", ec2UserDataService.getUserData(configStore.getPrimaryRegion(), command.getStack(), + Optional.ofNullable(tags.getOrDefault("ownerGroup", null)))); } - parameters.putAll(command.getDynamicParameters()); + if (Stack.CMS.equals(command.getStack())) { + command.getDynamicParameters().forEach((key, value) -> { + if (key.equals("amiId")) { + amiTagCheckService.validateAmiTagForStack(value, Stack.CMS); + } + }); + } else if (Stack.DATABASE.equals(command.getStack())) { + Optional dbPasswordOverwrite = command.getDynamicParameters().entrySet().stream() + .filter(entry -> entry.getKey().equals("cmsDbMasterPassword")) + .map(Map.Entry::getValue) + .findFirst(); - try { - logger.info("Starting the update for {}.", command.getStackName().getName()); + dbPasswordOverwrite.ifPresent(configStore::storeCmsDatabasePassword); - if (command.isOverwriteTemplate()) { - cloudFormationService.updateStack(stackId, parameters, stackTemplatePathMap.get(command.getStackName()), true); - } else { - cloudFormationService.updateStack(stackId, parameters, true); - } + parameters.put("cmsDbMasterPassword", dbPasswordOverwrite.orElseGet(() -> + configStore.getCmsDatabasePassword().orElseThrow(() -> + new RuntimeException("Unable to find current database password, add new one " + + "with -PcmsDbMasterPassword=xxxxxxx")))); - final StackStatus endStatus = - cloudFormationService.waitForStatus(stackId, - Sets.newHashSet( - UPDATE_COMPLETE, - UPDATE_COMPLETE_CLEANUP_IN_PROGRESS, - UPDATE_ROLLBACK_COMPLETE, - UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS, - UPDATE_ROLLBACK_FAILED - )); + } else if (Stack.LOAD_BALANCER.equals(command.getStack())) { + parameters.put("sslCertificateArn", configStore.getCertificationInformationList() + .getLast().getIdentityManagementCertificateArn()); + } + parameters.putAll(command.getDynamicParameters()); - if (! ImmutableList.of(UPDATE_COMPLETE, UPDATE_COMPLETE_CLEANUP_IN_PROGRESS).contains(endStatus)) { - final String errorMessage = String.format("CloudFormation reports that updating the stack was not successful. end status: %s", endStatus.name()); - logger.error(errorMessage); + try { + logger.info("Starting the update for '{}' overwrite:{}.", stackId, command.isOverwriteTemplate()); - throw new UnexpectedCloudFormationStatusException(errorMessage); - } + cloudFormationService.updateStackAndWait( + configStore.getPrimaryRegion(), + command.getStack(), + parameters, + true, + command.isOverwriteTemplate(), + command.getTagsDelegate().getTags() + ); logger.info("Update complete."); } catch (AmazonServiceException ase) { @@ -160,98 +125,20 @@ public void run(final UpdateStackCommand command) { } @Override - public boolean isRunnable(final UpdateStackCommand command) { + public boolean isRunnable(UpdateStackCommand command) { boolean isRunnable = true; - final String stackId = configStore.getStackId(command.getStackName()); - if (StringUtils.isBlank(stackId)) { - logger.error("The stack name specified has not been created for this environment yet!"); - isRunnable = false; - } else if (!cloudFormationService.isStackPresent(stackId)) { - logger.error("CloudFormation doesn't have the specified stack: {}", stackId); + String fullName = command.getStack().getFullName(environmentName); + if (!cloudFormationService.isStackPresent(configStore.getPrimaryRegion(), fullName)) { + logger.error("CloudFormation doesn't have the specified stack: {}", fullName); isRunnable = false; } - return isRunnable; - } - - private Map getUpdateLaunchConfigParameters(final StackName stackName, - final UpdateStackCommand command, - final Class parametersClass) { - final LaunchConfigParameters launchConfigParameters = - configStore.getStackParameters(stackName, parametersClass); - - launchConfigParameters.getLaunchConfigParameters().setUserData( - ec2UserDataService.getUserData(stackName, command.getOwnerGroup())); - - if (StringUtils.isNotBlank(command.getAmiId())) { - launchConfigParameters.getLaunchConfigParameters().setAmiId(command.getAmiId()); - } - - if (StringUtils.isNotBlank(command.getInstanceSize())) { - launchConfigParameters.getLaunchConfigParameters().setInstanceSize(command.getInstanceSize()); - } - - if (StringUtils.isNotBlank(command.getKeyPairName())) { - launchConfigParameters.getLaunchConfigParameters().setKeyPairName(command.getKeyPairName()); - } - - if (StringUtils.isNotBlank(command.getOwnerEmail())) { - launchConfigParameters.getTagParameters().setTagEmail(command.getOwnerEmail()); - } - - if (StringUtils.isNotBlank(command.getCostcenter())) { - launchConfigParameters.getTagParameters().setTagCostcenter(command.getCostcenter()); - } - - if (command.getDesiredInstances() != null) { - launchConfigParameters.getLaunchConfigParameters().setDesiredInstances(command.getDesiredInstances()); - } - - if (command.getMinimumInstances() != null) { - launchConfigParameters.getLaunchConfigParameters().setMinimumInstances(command.getMinimumInstances()); - } - - if (command.getMaximumInstances() != null) { - launchConfigParameters.getLaunchConfigParameters().setMaximumInstances(command.getMaximumInstances()); - } - - updateSslConfigParameters(stackName, launchConfigParameters); - - final TypeReference> typeReference = new TypeReference>() {}; - return cloudformationObjectMapper.convertValue(launchConfigParameters, typeReference); - } - - private void updateSslConfigParameters(final StackName stackName, - final LaunchConfigParameters launchConfigParameters) { - - final SslConfigParametersDelegate sslConfigParameters = launchConfigParameters.getSslConfigParameters(); - - if (StringUtils.isNotBlank(sslConfigParameters.getCertPublicKey())) { - sslConfigParameters.setCertPublicKey(configStore.getCertPart(stackName, CERT_PART_PUBKEY).get()); - } - - if (StringUtils.isNotBlank(sslConfigParameters.getSslCertificateId())) { - sslConfigParameters.setSslCertificateId(configStore.getServerCertificateId(stackName).get()); - } - - if (StringUtils.isNotBlank(sslConfigParameters.getSslCertificateArn())) { - sslConfigParameters.setSslCertificateArn(configStore.getServerCertificateArn(stackName).get()); - } - } - - private Map getUpdatedBaseStackParameters(final UpdateStackCommand command) { - final TagParameters tagParameters = configStore.getStackParameters(command.getStackName(), BaseParameters.class); - - if (StringUtils.isNotBlank(command.getOwnerEmail())) { - tagParameters.getTagParameters().setTagEmail(command.getOwnerEmail()); - } - - if (StringUtils.isNotBlank(command.getCostcenter())) { - tagParameters.getTagParameters().setTagCostcenter(command.getCostcenter()); + if (command.getStack().equals(Stack.LOAD_BALANCER) && configStore.getCertificationInformationList().isEmpty()) { + logger.error("Updating the load balancer requires that a cert has been uploaded by the upload-certificate-files command"); + isRunnable = false; } - final TypeReference> typeReference = new TypeReference>() {}; - return cloudformationObjectMapper.convertValue(tagParameters, typeReference); + return isRunnable; } -} +} \ No newline at end of file diff --git a/src/main/java/com/nike/cerberus/operation/core/UploadCertFilesOperation.java b/src/main/java/com/nike/cerberus/operation/core/UploadCertFilesOperation.java deleted file mode 100644 index 161357dc..00000000 --- a/src/main/java/com/nike/cerberus/operation/core/UploadCertFilesOperation.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.operation.core; - -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Sets; -import com.nike.cerberus.command.core.UploadCertFilesCommand; -import com.nike.cerberus.domain.EnvironmentMetadata; -import com.nike.cerberus.domain.environment.StackName; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.service.IdentityManagementService; -import com.nike.cerberus.store.ConfigStore; -import com.nike.cerberus.util.UuidSupplier; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; -import java.io.File; -import java.io.IOException; -import java.nio.charset.Charset; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Set; - -/** - * Handles uploading of certificate files to IAM and the config store. - */ -public class UploadCertFilesOperation implements Operation { - - private static final String CA_FILE_NAME = "ca.pem"; - private static final String CERT_FILE_NAME = "cert.pem"; - private static final String KEY_FILE_NAME = "key.pem"; - private static final String PUB_KEY_FILE_NAME = "pubkey.pem"; - public static final Set EXPECTED_FILE_NAMES = ImmutableSet.of(CA_FILE_NAME, CERT_FILE_NAME, KEY_FILE_NAME, PUB_KEY_FILE_NAME); - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private final EnvironmentMetadata environmentMetadata; - - private final UuidSupplier uuidSupplier; - - private final ConfigStore configStore; - - private final IdentityManagementService identityManagementService; - - @Inject - public UploadCertFilesOperation(final EnvironmentMetadata environmentMetadata, - final UuidSupplier uuidSupplier, - final ConfigStore configStore, - final IdentityManagementService identityManagementService) { - this.environmentMetadata = environmentMetadata; - this.uuidSupplier = uuidSupplier; - this.configStore = configStore; - this.identityManagementService = identityManagementService; - } - - @Override - public void run(final UploadCertFilesCommand command) { - final StackName stackName = command.getStackName(); - final Path certPath = command.getCertPath(); - final String caContents = getFileContents(certPath, CA_FILE_NAME); - final String certContents = getFileContents(certPath, CERT_FILE_NAME); - final String keyContents = getFileContents(certPath, KEY_FILE_NAME); - final String pubKeyContents = getFileContents(certPath, PUB_KEY_FILE_NAME); - final String certificateName = stackName.getName() + "_" + uuidSupplier.get(); - - logger.info("Uploading certificate to IAM for {}, with name of {}.", stackName.getName(), certificateName); - String id = identityManagementService.uploadServerCertificate(certificateName, getPath(), - certContents, caContents, keyContents); - - logger.info("Cert ID: {}", id); - - logger.info("Uploading certificate parts to the configuration bucket."); - configStore.storeCert(stackName, certificateName, caContents, certContents, keyContents, pubKeyContents); - - logger.info("Uploading certificate completed."); - } - - @Override - public boolean isRunnable(final UploadCertFilesCommand command) { - if (StringUtils.isBlank(environmentMetadata.getBucketName())) { - logger.error("Environment isn't initialized, unable to upload certificate."); - return false; - } - - final String serverCertificateName = configStore.getServerCertificateName(command.getStackName()); - if (StringUtils.isNotBlank(serverCertificateName) && !command.isOverwrite()) { - logger.error("Certificate already uploaded for this stack and environment. Use --overwrite flag to force upload."); - return false; - } - - return true; - } - - private String getFileContents(final Path path, final String filename) { - Preconditions.checkNotNull(path); - Preconditions.checkNotNull(filename); - - File file = new File(path.toString(), filename); - if (file.exists() && file.canRead()) { - try { - return new String(Files.readAllBytes(file.toPath()), Charset.forName("UTF-8")); - } catch (IOException e) { - throw new IllegalStateException("Failed to read the following file: " + file.getAbsolutePath()); - } - } else { - throw new IllegalArgumentException("The file is not readable: " + file.getAbsolutePath()); - } - } - - private String getPath() { - return "/cloudfront/cerberus/" + environmentMetadata.getName() + "/"; - } -} diff --git a/src/main/java/com/nike/cerberus/operation/core/ViewConfigOperation.java b/src/main/java/com/nike/cerberus/operation/core/ViewConfigOperation.java index 50a75228..9baa6c05 100644 --- a/src/main/java/com/nike/cerberus/operation/core/ViewConfigOperation.java +++ b/src/main/java/com/nike/cerberus/operation/core/ViewConfigOperation.java @@ -57,8 +57,7 @@ public void run(final ViewConfigCommand command) { Set keys = configStore.listUnderPartialPath(path); if (!keys.isEmpty()) { logger.info("List under path '{}': {}", path, keys); - } - else { + } else { logger.error(String.format("Failed to load file: '%s'", path)); } } diff --git a/src/main/java/com/nike/cerberus/operation/core/WhitelistCidrForVpcAccessOpertaion.java b/src/main/java/com/nike/cerberus/operation/core/WhitelistCidrForVpcAccessOperation.java similarity index 60% rename from src/main/java/com/nike/cerberus/operation/core/WhitelistCidrForVpcAccessOpertaion.java rename to src/main/java/com/nike/cerberus/operation/core/WhitelistCidrForVpcAccessOperation.java index 349ce774..2ac4ae7a 100644 --- a/src/main/java/com/nike/cerberus/operation/core/WhitelistCidrForVpcAccessOpertaion.java +++ b/src/main/java/com/nike/cerberus/operation/core/WhitelistCidrForVpcAccessOperation.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Nike, Inc. + * Copyright (c) 2017 Nike, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package com.nike.cerberus.operation.core; import com.amazonaws.services.ec2.AmazonEC2; +import com.amazonaws.services.ec2.AmazonEC2Client; import com.amazonaws.services.ec2.model.AuthorizeSecurityGroupIngressRequest; import com.amazonaws.services.ec2.model.DescribeSecurityGroupsRequest; import com.amazonaws.services.ec2.model.DescribeSecurityGroupsResult; @@ -24,22 +25,25 @@ import com.amazonaws.services.ec2.model.RevokeSecurityGroupIngressRequest; import com.google.common.collect.Lists; import com.nike.cerberus.command.core.WhitelistCidrForVpcAccessCommand; -import com.nike.cerberus.domain.cloudformation.BaseOutputs; -import com.nike.cerberus.domain.environment.StackName; +import com.nike.cerberus.domain.cloudformation.SecurityGroupOutputs; +import com.nike.cerberus.domain.environment.Stack; import com.nike.cerberus.operation.Operation; +import com.nike.cerberus.service.AwsClientFactory; import com.nike.cerberus.service.CloudFormationService; import com.nike.cerberus.store.ConfigStore; -import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; +import javax.inject.Named; import java.util.List; +import static com.nike.cerberus.module.CerberusModule.ENV_NAME; + /** * Operation */ -public class WhitelistCidrForVpcAccessOpertaion implements Operation { +public class WhitelistCidrForVpcAccessOperation implements Operation { private final Logger logger = LoggerFactory.getLogger(getClass()); @@ -49,26 +53,31 @@ public class WhitelistCidrForVpcAccessOpertaion implements Operation amazonS3ClientFactory, + @Named(ENV_NAME) String environmentName) { + this.cloudFormationService = cloudFormationService; this.configStore = configStore; - this.ec2Client = ec2Client; + this.ec2Client = amazonS3ClientFactory.getClient(configStore.getPrimaryRegion()); + this.environmentName = environmentName; } @Override - public void run(final WhitelistCidrForVpcAccessCommand command) { - final BaseOutputs baseStackOutputs = configStore.getBaseStackOutputs(); + public void run(WhitelistCidrForVpcAccessCommand command) { + SecurityGroupOutputs securityGroupOutputs = configStore.getSecurityGroupStackOutputs(); logger.info("Revoking the previous ingress rules..."); - final DescribeSecurityGroupsResult securityGroupsResult = ec2Client.describeSecurityGroups( - new DescribeSecurityGroupsRequest().withGroupIds(baseStackOutputs.getToolsIngressSgId())); + DescribeSecurityGroupsResult securityGroupsResult = ec2Client.describeSecurityGroups( + new DescribeSecurityGroupsRequest().withGroupIds(securityGroupOutputs.getWhitelistIngressSgId())); securityGroupsResult.getSecurityGroups().forEach(securityGroup -> { if (!securityGroup.getIpPermissions().isEmpty()) { RevokeSecurityGroupIngressRequest revokeIngressRequest = new RevokeSecurityGroupIngressRequest() - .withGroupId(baseStackOutputs.getToolsIngressSgId()) + .withGroupId(securityGroupOutputs.getWhitelistIngressSgId()) .withIpPermissions(securityGroup.getIpPermissions()); ec2Client.revokeSecurityGroupIngress(revokeIngressRequest); } @@ -76,7 +85,7 @@ public void run(final WhitelistCidrForVpcAccessCommand command) { logger.info("Done."); logger.info("Authorizing the new ingress rules..."); - final List ipPermissionList = Lists.newArrayListWithCapacity(command.getPorts().size()); + List ipPermissionList = Lists.newArrayListWithCapacity(command.getPorts().size()); command.getPorts().forEach(port -> { IpPermission ipPermission = new IpPermission() .withIpRanges(command.getCidrs()) @@ -88,22 +97,22 @@ public void run(final WhitelistCidrForVpcAccessCommand command) { }); AuthorizeSecurityGroupIngressRequest ingressRequest = new AuthorizeSecurityGroupIngressRequest() - .withGroupId(baseStackOutputs.getToolsIngressSgId()) + .withGroupId(securityGroupOutputs.getWhitelistIngressSgId()) .withIpPermissions(ipPermissionList); ec2Client.authorizeSecurityGroupIngress(ingressRequest); logger.info("Done."); } @Override - public boolean isRunnable(final WhitelistCidrForVpcAccessCommand command) { - boolean isRunnable = true; - final String baseStackId = configStore.getStackId(StackName.BASE); - - if (StringUtils.isBlank(baseStackId) || !cloudFormationService.isStackPresent(baseStackId)) { - logger.error("No base stack defined for this environment!"); - isRunnable = false; + public boolean isRunnable(WhitelistCidrForVpcAccessCommand command) { + try { + cloudFormationService.getStackId(configStore.getPrimaryRegion(), Stack.IAM_ROLES.getFullName(environmentName)); + } catch (IllegalArgumentException iae) { + logger.error("Could not create the CMS cluster." + + "Make sure the load balancer, security group, and base stacks have all been created.", iae); + return false; } - return isRunnable; + return true; } } diff --git a/src/main/java/com/nike/cerberus/operation/dashboard/DashboardMetaDataProvider.java b/src/main/java/com/nike/cerberus/operation/dashboard/DashboardMetaDataProvider.java deleted file mode 100644 index 5ad67b01..00000000 --- a/src/main/java/com/nike/cerberus/operation/dashboard/DashboardMetaDataProvider.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.operation.dashboard; - -import com.amazonaws.services.s3.model.ObjectMetadata; -import com.amazonaws.services.s3.transfer.ObjectMetadataProvider; - -import java.io.File; - -/** - * Metadata provider for dashboard assets - * - * We use this to set some headers we want s3 to send with our assets - */ -public class DashboardMetaDataProvider implements ObjectMetadataProvider { - private static final String CACHE_CONTROL = "Cache-Control"; - private static final String CONTENT_TYPE = "Content-Type"; - - @Override - public void provideObjectMetadata(File file, ObjectMetadata metadata) { - metadata.setHeader(CACHE_CONTROL, "private, no-cache, no-store, proxy-revalidate, no-transform"); - if (file.getName().endsWith(".html")) { - metadata.setHeader(CONTENT_TYPE, "text/html; charset=UTF-8"); - } - } -} diff --git a/src/main/java/com/nike/cerberus/operation/dashboard/PublishDashboardOperation.java b/src/main/java/com/nike/cerberus/operation/dashboard/PublishDashboardOperation.java deleted file mode 100644 index 91d1f556..00000000 --- a/src/main/java/com/nike/cerberus/operation/dashboard/PublishDashboardOperation.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.operation.dashboard; - -import com.amazonaws.services.s3.AmazonS3; -import com.amazonaws.services.s3.transfer.MultipleFileUpload; -import com.amazonaws.services.s3.transfer.TransferManager; -import com.google.common.io.Files; -import com.nike.cerberus.command.dashboard.PublishDashboardCommand; -import com.nike.cerberus.domain.cloudformation.BaseOutputs; -import com.nike.cerberus.operation.InvalidEnvironmentConfigException; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.store.ConfigStore; -import org.apache.commons.compress.archivers.ArchiveEntry; -import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; -import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; -import org.apache.commons.compress.utils.IOUtils; -import org.apache.commons.io.FileUtils; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.net.URL; - -/** - * Publishes the dashboard artifact to the s3 bucket hosting the dashboard application. - */ -public class PublishDashboardOperation implements Operation { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private final ConfigStore configStore; - - private final TransferManager transferManager; - - private final AmazonS3 amazonS3; - - @Inject - public PublishDashboardOperation(final ConfigStore configStore, final AmazonS3 amazonS3) { - this.configStore = configStore; - this.transferManager = new TransferManager(amazonS3); - this.amazonS3 = amazonS3; - } - - @Override - public void run(final PublishDashboardCommand command) { - final URL artifactUrl = command.getArtifactUrl(); - final URL overrideArtifactUrl = command.getOverrideArtifactUrl(); - - final BaseOutputs outputParameters = configStore.getBaseStackOutputs(); - final String dashboardBucketName = outputParameters.getDashboardBucketName(); - - if (StringUtils.isBlank(dashboardBucketName)) { - final String errorMessage = "The specified environment isn't configured properly!"; - logger.error(errorMessage); - throw new IllegalStateException(errorMessage); - } - - initClient(dashboardBucketName); - - final File extractedDirectory = extractArtifact(artifactUrl, overrideArtifactUrl); - - try { - final MultipleFileUpload multipleFileUpload = - transferManager.uploadDirectory(dashboardBucketName, "", extractedDirectory, true, new DashboardMetaDataProvider()); - logger.info("Uploading dashboard files."); - multipleFileUpload.waitForCompletion(); - logger.info("Uploading complete."); - } catch (InterruptedException e) { - logger.error("Interrupted while waiting for upload to complete!", e); - } finally { - transferManager.shutdownNow(false); - } - } - - @Override - public boolean isRunnable(final PublishDashboardCommand command) { - try { - this.configStore.getBaseStackOutputs(); - return true; - } catch (IllegalStateException ise) { - logger.error("The dashboard bucket doesn't exist, yet! Aborting..."); - return false; - } - } - - private File extractArtifact(final URL artifactUrl, final URL helpArtifactUrl) { - final File extractionDirectory = Files.createTempDir(); - logger.info("Extracting artifact contents to {}", extractionDirectory.getAbsolutePath()); - - downloadAndExtract(artifactUrl, extractionDirectory); - - if (helpArtifactUrl != null) { - downloadAndExtract(helpArtifactUrl, extractionDirectory); - } - - return extractionDirectory; - } - - private void downloadAndExtract(URL artifactUrl, File extractionDirectory) { - ArchiveEntry entry; - TarArchiveInputStream tarArchiveInputStream = null; - try { - tarArchiveInputStream = new TarArchiveInputStream(new GzipCompressorInputStream(artifactUrl.openStream())); - entry = tarArchiveInputStream.getNextEntry(); - while (entry != null) { - String entryName = entry.getName(); - if (entry.getName().startsWith("./")) { - entryName = entry.getName().substring(1); - } - final File destPath = new File(extractionDirectory, entryName); - if (!entry.isDirectory()) { - final File fileParentDir = new File(destPath.getParent()); - if (!fileParentDir.exists()) { - FileUtils.forceMkdir(fileParentDir); - } - FileOutputStream fileOutputStream = null; - try { - fileOutputStream = new FileOutputStream(destPath); - IOUtils.copy(tarArchiveInputStream, fileOutputStream); - } finally { - IOUtils.closeQuietly(fileOutputStream); - } - } - entry = tarArchiveInputStream.getNextEntry(); - } - } catch (IOException e) { - e.printStackTrace(); - } finally { - IOUtils.closeQuietly(tarArchiveInputStream); - } - } - - private void initClient(final String dashboardBucketName) { - logger.debug("Initializing the S3 client"); - if (!amazonS3.doesBucketExist(dashboardBucketName)) { - final String errorMessage = String.format("The dashboard bucket specified doesn't exist! bucketName: %s", dashboardBucketName); - logger.error(errorMessage); - throw new InvalidEnvironmentConfigException(errorMessage); - } - } -} diff --git a/src/main/java/com/nike/cerberus/operation/gateway/CreateCloudFrontLogProcessingLambdaConfigOperation.java b/src/main/java/com/nike/cerberus/operation/gateway/CreateCloudFrontLogProcessingLambdaConfigOperation.java deleted file mode 100644 index e0fecc70..00000000 --- a/src/main/java/com/nike/cerberus/operation/gateway/CreateCloudFrontLogProcessingLambdaConfigOperation.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.operation.gateway; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.nike.cerberus.command.gateway.CreateCloudFrontLogProcessingLambdaConfigCommand; -import com.nike.cerberus.domain.cloudformation.GatewayOutputs; -import com.nike.cerberus.domain.environment.StackName; -import com.nike.cerberus.domain.environment.CloudFrontLogProcessingLambdaConfig; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.service.CloudFormationService; -import com.nike.cerberus.store.ConfigStore; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; -import javax.inject.Named; -import java.util.Map; - -import static com.nike.cerberus.module.CerberusModule.CF_OBJECT_MAPPER; - -/** - * Operation for creating config needed to drive log processing lambda - */ -public class CreateCloudFrontLogProcessingLambdaConfigOperation implements Operation { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private final ConfigStore configStore; - - private final CloudFormationService cloudFormationService; - - private final ObjectMapper cloudformationObjectMapper; - - @Inject - public CreateCloudFrontLogProcessingLambdaConfigOperation(final ConfigStore configStore, - final CloudFormationService cloudFormationService, - @Named(CF_OBJECT_MAPPER) final ObjectMapper cloudformationObjectMapper) { - this.configStore = configStore; - this.cloudFormationService = cloudFormationService; - this.cloudformationObjectMapper = cloudformationObjectMapper; - } - - @Override - public void run(CreateCloudFrontLogProcessingLambdaConfigCommand command) { - final String gateWayStackId = configStore.getStackId(StackName.GATEWAY); - final Map stackOutputs = cloudFormationService.getStackOutputs(gateWayStackId); - final GatewayOutputs gatewayOutputs = cloudformationObjectMapper.convertValue(stackOutputs, GatewayOutputs.class); - - CloudFrontLogProcessingLambdaConfig config = new CloudFrontLogProcessingLambdaConfig(); - config.setManualBlackListIpSetId(gatewayOutputs.getManualBlockIPSetID()); - config.setManualWhiteListIpSetId(gatewayOutputs.getWhiteListIPSetID()); - config.setRateLimitAutoBlackListIpSetId(gatewayOutputs.getAutoBlockIPSetID()); - config.setRequestPerMinuteLimit(command.getRequestPerMinuteLimit()); - config.setRateLimitViolationBlacklistPeriodInMinutes(command.getRateLimitViolationBlacklistPeriodInMinutes()); - config.setSlackWebHookUrl(command.getSlackWebHookUrl()); - config.setSlackIcon(command.getSlackIcon()); - config.setGoogleAnalyticsId(command.getGoogleAnalyticsId()); - - configStore.saveCFLogProcessorLambdaConfig(config); - - logger.info("Config Created and Uploaded to S3"); - } - - @Override - public boolean isRunnable(CreateCloudFrontLogProcessingLambdaConfigCommand command) { - return StringUtils.isNotBlank(configStore.getStackId(StackName.GATEWAY)); - } -} diff --git a/src/main/java/com/nike/cerberus/operation/gateway/CreateCloudFrontSecurityGroupUpdaterLambdaOperation.java b/src/main/java/com/nike/cerberus/operation/gateway/CreateCloudFrontSecurityGroupUpdaterLambdaOperation.java deleted file mode 100644 index 1490ac1a..00000000 --- a/src/main/java/com/nike/cerberus/operation/gateway/CreateCloudFrontSecurityGroupUpdaterLambdaOperation.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.operation.gateway; - -import com.amazonaws.regions.Region; -import com.amazonaws.regions.Regions; -import com.amazonaws.services.cloudformation.AmazonCloudFormation; -import com.amazonaws.services.cloudformation.AmazonCloudFormationClient; -import com.amazonaws.services.cloudformation.model.StackStatus; -import com.amazonaws.services.lambda.AWSLambda; -import com.amazonaws.services.lambda.model.InvokeRequest; -import com.amazonaws.services.lambda.model.InvokeResult; -import com.amazonaws.services.lambda.model.LogType; -import com.amazonaws.services.s3.AmazonS3; -import com.amazonaws.services.sns.AmazonSNS; -import com.amazonaws.services.sns.AmazonSNSClient; -import com.amazonaws.services.sns.model.SubscribeRequest; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.collect.Sets; -import com.google.inject.Inject; -import com.nike.cerberus.ConfigConstants; -import com.nike.cerberus.command.gateway.CreateCloudFrontSecurityGroupUpdaterLambdaCommand; -import com.nike.cerberus.domain.EnvironmentMetadata; -import com.nike.cerberus.domain.cloudformation.CloudFrontIpSynchronizerOutputs; -import com.nike.cerberus.domain.cloudformation.CloudFrontIpSynchronizerParameters; -import com.nike.cerberus.domain.environment.LambdaName; -import com.nike.cerberus.domain.environment.StackName; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.operation.UnexpectedCloudFormationStatusException; -import com.nike.cerberus.service.CloudFormationService; -import org.apache.commons.io.IOUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Named; -import java.io.IOException; -import java.nio.charset.Charset; -import java.util.Base64; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static com.nike.cerberus.module.CerberusModule.CF_OBJECT_MAPPER; - -/** - * Command for creating lambda needed to sync AWS CloudFront IPs to an SG to limit ingress to only CF IPs - */ -public class CreateCloudFrontSecurityGroupUpdaterLambdaOperation implements Operation { - - private static final String AWS_IP_CHANGE_TOPIC_ARN = "arn:aws:sns:us-east-1:806199016981:AmazonIpSpaceChanged"; - private static final String BAD_HASH = "03a8199d0c03ddfec0e542f8bf650ee7"; - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private final CloudFormationService cloudFormationService; - private final AmazonSNS amazonSNS; - private final ObjectMapper cloudformationObjectMapper; - private final EnvironmentMetadata environmentMetadata; - private final AWSLambda awsLambda; - private final AmazonS3 amazonS3; - - @Inject - public CreateCloudFrontSecurityGroupUpdaterLambdaOperation(final CloudFormationService cloudFormationService, - final EnvironmentMetadata environmentMetadata, - @Named(CF_OBJECT_MAPPER) final ObjectMapper cloudformationObjectMapper, - AWSLambda awsLambda, - AmazonS3 amazonS3) { - - this.cloudFormationService = cloudFormationService; - this.cloudformationObjectMapper = cloudformationObjectMapper; - this.environmentMetadata = environmentMetadata; - this.awsLambda = awsLambda; - this.amazonS3 = amazonS3; - - final Region region = Region.getRegion(Regions.US_EAST_1); - AmazonCloudFormation amazonCloudFormation = new AmazonCloudFormationClient(); - amazonCloudFormation.setRegion(region); - amazonSNS = new AmazonSNSClient(); - amazonSNS.setRegion(region); - } - - @Override - public void run(CreateCloudFrontSecurityGroupUpdaterLambdaCommand command) { - if (! cloudFormationService.isStackPresent(StackName.CLOUD_FRONT_IP_SYNCHRONIZER.getName())) { - createLambda(); - } - - Map outputs = cloudFormationService.getStackOutputs(StackName.CLOUD_FRONT_IP_SYNCHRONIZER.getName()); - CloudFrontIpSynchronizerOutputs cloudFrontIpSynchronizerOutputs = cloudformationObjectMapper - .convertValue(outputs, CloudFrontIpSynchronizerOutputs.class); - - final String arn = cloudFrontIpSynchronizerOutputs.getCloudFrontOriginElbSgIpSyncFunctionArn(); - - // subscribe, if already subscribed it doesn't make a new sub - amazonSNS.subscribe(new SubscribeRequest(AWS_IP_CHANGE_TOPIC_ARN, "lambda", arn)); - - // force any new ELBs that have the tags we care about to be updated to only allow ingress from CloudFront - forceLambdaToUpdateSgs(arn); - } - - /** - * Forces the lambda to run and sync the IPs for CloudFront to be white listed on the origin elb - */ - private void forceLambdaToUpdateSgs(String arn) { - String json; - try { - json = IOUtils.toString(this.getClass().getClassLoader().getResourceAsStream("aws-ip-space-change-sns-sample-event.json")); - } catch (IOException e) { - String msg = "Failed to load mock sns message, to force Lambda first run"; - logger.error(msg, e); - throw new RuntimeException(msg, e); - } - // this will fail - InvokeResult result = awsLambda.invoke(new InvokeRequest().withFunctionName(arn).withPayload(String.format(json, BAD_HASH)).withLogType(LogType.Tail)); - // collect the error so we can parse it for the latest hash - String log = new String(Base64.getDecoder().decode(result.getLogResult()), Charset.forName("UTF-8")); - Pattern pattern = Pattern.compile("MD5 Mismatch: got\\s(.*?)\\sexp.*?"); - Matcher matcher = pattern.matcher(log); - boolean matched = matcher.find(); - if (! matched) { - throw new RuntimeException("failed to extract hash from: " + log); - } - - String realHash = matcher.group(1); - result = awsLambda.invoke(new InvokeRequest().withFunctionName(arn).withPayload(String.format(json, realHash)).withLogType(LogType.Tail)); - - logger.info("Forcing the Lambda to run and update Security Groups"); - logger.info(new String(result.getPayload().array(), Charset.forName("UTF-8"))); - } - - private void createLambda() { - final CloudFrontIpSynchronizerParameters cloudFrontIpSynchronizerParameters = new CloudFrontIpSynchronizerParameters() - .setLambdaBucket(environmentMetadata.getBucketName()) - .setLambdaKey(LambdaName.CLOUD_FRONT_SG_GROUP_IP_SYNC.getBucketKey()); - - final TypeReference> typeReference = new TypeReference>() {}; - final Map parameters = cloudformationObjectMapper.convertValue(cloudFrontIpSynchronizerParameters, typeReference); - - final String stackId = cloudFormationService.createStack(StackName.CLOUD_FRONT_IP_SYNCHRONIZER.getName(), - parameters, ConfigConstants.CF_ELB_IP_SYNC_STACK_TEMPLATE_PATH, true); - - final StackStatus endStatus = - cloudFormationService.waitForStatus(stackId, - Sets.newHashSet(StackStatus.CREATE_COMPLETE, StackStatus.ROLLBACK_COMPLETE)); - - if (endStatus != StackStatus.CREATE_COMPLETE) { - final String errorMessage = String.format("Unexpected end status: %s", endStatus.name()); - logger.error(errorMessage); - - throw new UnexpectedCloudFormationStatusException(errorMessage); - } - } - - /** - * Run the command if the stack already exists from a different Cerberus env or if the publish artifact command - * has been run for this env, so that if were creating the stack the artifact we need exists in s3. - */ - @Override - public boolean isRunnable(CreateCloudFrontSecurityGroupUpdaterLambdaCommand command) { - boolean theStackAlreadyExists = cloudFormationService - .isStackPresent(StackName.CLOUD_FRONT_IP_SYNCHRONIZER.getName()); - - boolean theLambdaArtifactExistsInS3 = amazonS3.doesObjectExist(environmentMetadata.getBucketName(), - LambdaName.CLOUD_FRONT_SG_GROUP_IP_SYNC.getBucketKey()); - - if (theStackAlreadyExists || theLambdaArtifactExistsInS3) { - return true; - } else { - logger.error("failed to detect the lambda at {}/{} you must run the lambda publish command first", - environmentMetadata.getBucketName(), LambdaName.CLOUD_FRONT_SG_GROUP_IP_SYNC.getBucketKey()); - return false; - } - - } -} diff --git a/src/main/java/com/nike/cerberus/operation/gateway/CreateGatewayClusterOperation.java b/src/main/java/com/nike/cerberus/operation/gateway/CreateGatewayClusterOperation.java deleted file mode 100644 index 3893bd9c..00000000 --- a/src/main/java/com/nike/cerberus/operation/gateway/CreateGatewayClusterOperation.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (c) 2017 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.operation.gateway; - -import com.amazonaws.services.cloudformation.model.StackStatus; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.collect.Sets; -import com.nike.cerberus.ConfigConstants; -import com.nike.cerberus.command.gateway.CreateGatewayClusterCommand; -import com.nike.cerberus.domain.EnvironmentMetadata; -import com.nike.cerberus.domain.cloudformation.BaseOutputs; -import com.nike.cerberus.domain.cloudformation.GatewayParameters; -import com.nike.cerberus.domain.environment.LambdaName; -import com.nike.cerberus.domain.environment.StackName; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.operation.UnexpectedCloudFormationStatusException; -import com.nike.cerberus.service.CloudFormationService; -import com.nike.cerberus.service.Ec2UserDataService; -import com.nike.cerberus.service.AmiTagCheckService; -import com.nike.cerberus.store.ConfigStore; -import com.nike.cerberus.util.UuidSupplier; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; -import javax.inject.Named; -import java.util.Map; -import java.util.Optional; - -import static com.nike.cerberus.module.CerberusModule.CF_OBJECT_MAPPER; - -/** - * Operation for creating the gateway cluster. - */ -public class CreateGatewayClusterOperation implements Operation { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private final EnvironmentMetadata environmentMetadata; - - private final CloudFormationService cloudFormationService; - - private final Ec2UserDataService ec2UserDataService; - - private final AmiTagCheckService amiTagCheckService; - - private final UuidSupplier uuidSupplier; - - private final ConfigStore configStore; - - private final ObjectMapper cloudformationObjectMapper; - - @Inject - public CreateGatewayClusterOperation(final EnvironmentMetadata environmentMetadata, - final CloudFormationService cloudFormationService, - final Ec2UserDataService ec2UserDataService, - final AmiTagCheckService amiTagCheckService, - final UuidSupplier uuidSupplier, - final ConfigStore configStore, - @Named(CF_OBJECT_MAPPER) final ObjectMapper cloudformationObjectMapper) { - this.environmentMetadata = environmentMetadata; - this.cloudFormationService = cloudFormationService; - this.ec2UserDataService = ec2UserDataService; - this.amiTagCheckService = amiTagCheckService; - this.uuidSupplier = uuidSupplier; - this.configStore = configStore; - this.cloudformationObjectMapper = cloudformationObjectMapper; - } - - @Override - public void run(final CreateGatewayClusterCommand command) { - final String uniqueStackName = String.format("%s-%s", StackName.GATEWAY.getName(), uuidSupplier.get()); - final BaseOutputs baseOutputs = configStore.getBaseStackOutputs(); - final Optional gatewayServerCertificateArn = configStore.getServerCertificateArn(StackName.GATEWAY); - final Optional gatewayServerCertificateId = configStore.getServerCertificateId(StackName.GATEWAY); - final Optional pubKey = configStore.getCertPart(StackName.GATEWAY, ConfigConstants.CERT_PART_PUBKEY); - - if (!gatewayServerCertificateArn.isPresent() || !pubKey.isPresent()) { - throw new IllegalStateException("Gateway certificate has not been uploaded!"); - } - - // Make sure the given AmiId is for Gateway component. Check if it contains required tag - if ( !command.isSkipAmiTagCheck() ) { - amiTagCheckService.validateAmiTagForStack(command.getStackDelegate().getAmiId(), StackName.GATEWAY); - } - - final GatewayParameters gatewayParameters = new GatewayParameters() - .setVpcId(baseOutputs.getVpcId()) - .setInstanceProfileName(baseOutputs.getGatewayInstanceProfileName()) - .setGatewayServerSgId(baseOutputs.getGatewayServerSgId()) - .setGatewayElbSgId(baseOutputs.getGatewayElbSgId()) - .setToolsIngressSgId(baseOutputs.getToolsIngressSgId()) - .setVpcSubnetIdForAz1(baseOutputs.getVpcSubnetIdForAz1()) - .setVpcSubnetIdForAz2(baseOutputs.getVpcSubnetIdForAz2()) - .setVpcSubnetIdForAz3(baseOutputs.getVpcSubnetIdForAz3()) - .setHostedZoneId(command.getHostedZoneId()) - .setHostname(command.getHostname()) - .setWafLambdaBucket(environmentMetadata.getBucketName()) - .setWafLambdaKey(LambdaName.WAF.getBucketKey()) - .setCloudFrontLogProcessorLambdaIamRoleArn(baseOutputs.getCloudFrontLogProcessorLambdaIamRoleArn()); - - gatewayParameters.getSslConfigParameters().setCertPublicKey(pubKey.get()); - gatewayParameters.getSslConfigParameters().setSslCertificateArn(gatewayServerCertificateArn.get()); - gatewayParameters.getSslConfigParameters().setSslCertificateId(gatewayServerCertificateId.get()); - - gatewayParameters.getLaunchConfigParameters().setAmiId(command.getStackDelegate().getAmiId()); - gatewayParameters.getLaunchConfigParameters().setInstanceSize(command.getStackDelegate().getInstanceSize()); - gatewayParameters.getLaunchConfigParameters().setKeyPairName(command.getStackDelegate().getKeyPairName()); - gatewayParameters.getLaunchConfigParameters().setUserData( - ec2UserDataService.getUserData(StackName.GATEWAY, command.getStackDelegate().getOwnerGroup())); - gatewayParameters.getLaunchConfigParameters().setDesiredInstances( - command.getStackDelegate().getDesiredInstances()); - gatewayParameters.getLaunchConfigParameters().setMinimumInstances( - command.getStackDelegate().getMinimumInstances()); - gatewayParameters.getLaunchConfigParameters().setMaximumInstances( - command.getStackDelegate().getMaximumInstances()); - - gatewayParameters.getTagParameters().setTagEmail(command.getStackDelegate().getOwnerEmail()); - gatewayParameters.getTagParameters().setTagName(ConfigConstants.ENV_PREFIX + environmentMetadata.getName()); - gatewayParameters.getTagParameters().setTagCostcenter(command.getStackDelegate().getCostcenter()); - - final TypeReference> typeReference = new TypeReference>() {}; - final Map parameters = cloudformationObjectMapper.convertValue(gatewayParameters, typeReference); - - final String stackId = cloudFormationService.createStack(cloudFormationService.getEnvStackName(uniqueStackName), - parameters, ConfigConstants.GATEWAY_STACK_TEMPLATE_PATH, true); - - logger.info("Uploading data to the configuration bucket."); - configStore.storeStackId(StackName.GATEWAY, stackId); - logger.info("Uploading complete."); - - final StackStatus endStatus = - cloudFormationService.waitForStatus(stackId, - Sets.newHashSet(StackStatus.CREATE_COMPLETE, StackStatus.ROLLBACK_COMPLETE)); - - if (endStatus != StackStatus.CREATE_COMPLETE) { - final String errorMessage = String.format("Unexpected end status: %s", endStatus.name()); - logger.error(errorMessage); - - throw new UnexpectedCloudFormationStatusException(errorMessage); - } - } - - @Override - public boolean isRunnable(final CreateGatewayClusterCommand command) { - boolean isRunnable = true; - final String cmsStackId = configStore.getStackId(StackName.CMS); - final String certificateName = configStore.getServerCertificateName(StackName.GATEWAY); - final boolean hasGatewayConfig = configStore.hasGatewayConfig(); - - if (StringUtils.isBlank(cmsStackId) || !cloudFormationService.isStackPresent(cmsStackId)) { - logger.error("No CMS stack defined for this environment!"); - isRunnable = false; - } - - if (StringUtils.isBlank(certificateName)) { - logger.error("Certificate has not been uploaded for Gateway!"); - isRunnable = false; - } - - if (!hasGatewayConfig) { - logger.error("No configuration for Gateway exists for this environment!"); - isRunnable = false; - } - - return isRunnable; - } -} diff --git a/src/main/java/com/nike/cerberus/operation/gateway/CreateGatewayConfigOperation.java b/src/main/java/com/nike/cerberus/operation/gateway/CreateGatewayConfigOperation.java deleted file mode 100644 index 25df3138..00000000 --- a/src/main/java/com/nike/cerberus/operation/gateway/CreateGatewayConfigOperation.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.operation.gateway; - -import com.nike.cerberus.command.gateway.CreateGatewayConfigCommand; -import com.nike.cerberus.domain.cloudformation.BaseOutputs; -import com.nike.cerberus.domain.cloudformation.CmsParameters; -import com.nike.cerberus.domain.cloudformation.VaultParameters; -import com.nike.cerberus.domain.configuration.GatewayConfiguration; -import com.nike.cerberus.domain.environment.StackName; -import com.nike.cerberus.domain.template.GatewayConfigurationInput; -import com.nike.cerberus.generator.GatewayConfigGenerator; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.store.ConfigStore; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; - -/** - * Operation to create the gateway configuration. - */ -public class CreateGatewayConfigOperation implements Operation { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private final GatewayConfigGenerator gatewayConfigGenerator; - - private final ConfigStore configStore; - - @Inject - public CreateGatewayConfigOperation(final GatewayConfigGenerator gatewayConfigGenerator, - final ConfigStore configStore) { - this.gatewayConfigGenerator = gatewayConfigGenerator; - this.configStore = configStore; - } - - @Override - public void run(final CreateGatewayConfigCommand command) { - final BaseOutputs baseOutputs = configStore.getBaseStackOutputs(); - final CmsParameters cmsParameters = configStore.getCmsStackParamters(); - final VaultParameters vaultParameters = configStore.getVaultStackParamters(); - final GatewayConfigurationInput input = new GatewayConfigurationInput() - .setDashboardHost(baseOutputs.getDashboardBucketWebsiteUrl().replaceAll("http://|https://", "")) - .setCmsHost(cnameToHost(cmsParameters.getCname())) - .setVaultHost(cnameToHost(vaultParameters.getCname())) - .setGatewayHost(command.getHostname()); - - logger.info("Generating the Gateway configuration."); - final GatewayConfiguration gatewayConfiguration = gatewayConfigGenerator.generate(input); - - logger.info("Uploading the Gateway configuration to the configuration bucket."); - configStore.storeGatewayConfig(gatewayConfiguration); - - logger.info("Uploading complete."); - } - - @Override - public boolean isRunnable(final CreateGatewayConfigCommand command) { - boolean isRunnable = true; - final String cmsStackId = configStore.getStackId(StackName.CMS); - - if (StringUtils.isBlank(cmsStackId)) { - logger.error("No CMS stack present for the specified environment, please create that first."); - isRunnable = false; - } - - return isRunnable; - } - - /** - * Removes the final '.' from the CNAME. - * - * @param cname The cname to convert - * @return The host derived from the CNAME - */ - private String cnameToHost(final String cname) { - return cname.substring(0, cname.length() - 1); - } -} diff --git a/src/main/java/com/nike/cerberus/operation/gateway/PublishLambdaOperation.java b/src/main/java/com/nike/cerberus/operation/gateway/PublishLambdaOperation.java deleted file mode 100644 index 8757feaf..00000000 --- a/src/main/java/com/nike/cerberus/operation/gateway/PublishLambdaOperation.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.operation.gateway; - -import com.amazonaws.services.s3.AmazonS3; -import com.amazonaws.services.s3.transfer.TransferManager; -import com.amazonaws.services.s3.transfer.Upload; -import com.google.common.io.Files; -import com.nike.cerberus.command.gateway.PublishLambdaCommand; -import com.nike.cerberus.domain.cloudformation.BaseOutputs; -import com.nike.cerberus.operation.InvalidEnvironmentConfigException; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.store.ConfigStore; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.net.URL; -import java.nio.channels.Channels; -import java.nio.channels.ReadableByteChannel; - -/** - * Publishes the lambda artifact to the Cerberus environment's configuration bucket. - */ -public class PublishLambdaOperation implements Operation { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private final ConfigStore configStore; - - private final TransferManager transferManager; - - private final AmazonS3 amazonS3; - - @Inject - public PublishLambdaOperation(final ConfigStore configStore, final AmazonS3 amazonS3) { - this.configStore = configStore; - this.transferManager = new TransferManager(amazonS3); - this.amazonS3 = amazonS3; - } - - @Override - public void run(final PublishLambdaCommand command) { - final URL artifactUrl = command.getArtifactUrl(); - - final BaseOutputs outputParameters = configStore.getBaseStackOutputs(); - final String configBucketName = outputParameters.getConfigBucketName(); - - if (StringUtils.isBlank(configBucketName)) { - final String errorMessage = "The specified environment isn't configured properly!"; - logger.error(errorMessage); - throw new IllegalStateException(errorMessage); - } - - initClient(configBucketName); - final File filePath = downloadArtifact(artifactUrl); - - try { - final Upload upload = - transferManager.upload(configBucketName, command.getLambdaName().getBucketKey(), filePath); - logger.info("Uploading lambda artifact."); - upload.waitForCompletion(); - logger.info("Uploading complete."); - } catch (InterruptedException e) { - logger.error("Interrupted while waiting for upload to complete!", e); - } finally { - transferManager.shutdownNow(false); - } - } - - @Override - public boolean isRunnable(final PublishLambdaCommand command) { - try { - this.configStore.getBaseStackOutputs(); - return true; - } catch (IllegalStateException ise) { - logger.error("The config bucket doesn't exist, yet! Aborting..."); - return false; - } - } - - private File downloadArtifact(final URL artifactUrl) { - final File tempDir = Files.createTempDir(); - final String filePath = tempDir.getPath() + File.pathSeparator + "lambda.artifact"; - logger.debug("Downloading artifact to {}", tempDir.getAbsolutePath()); - - try { - ReadableByteChannel rbc = Channels.newChannel(artifactUrl.openStream()); - FileOutputStream fos = new FileOutputStream(filePath); - fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); - } catch (IOException e) { - logger.error("Failed to download the lambda artifact!", e); - throw new IllegalStateException("Failed to download the lambda artifact.", e); - } - - return new File(filePath); - } - - private void initClient(final String configBucketName) { - logger.debug("Initializing the S3 client"); - if (!amazonS3.doesBucketExist(configBucketName)) { - final String errorMessage = String.format("The bucket specified doesn't exist! bucketName: %s", - configBucketName); - logger.error(errorMessage); - throw new InvalidEnvironmentConfigException(errorMessage); - } - } -} diff --git a/src/main/java/com/nike/cerberus/operation/rds/CleanUpRdsSnapshotsOperation.java b/src/main/java/com/nike/cerberus/operation/rds/CleanUpRdsSnapshotsOperation.java new file mode 100644 index 00000000..40de8c2f --- /dev/null +++ b/src/main/java/com/nike/cerberus/operation/rds/CleanUpRdsSnapshotsOperation.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2018 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.operation.rds; + +import com.amazonaws.regions.Regions; +import com.nike.cerberus.command.rds.CleanUpRdsSnapshotsCommand; +import com.nike.cerberus.domain.environment.Stack; +import com.nike.cerberus.operation.Operation; +import com.nike.cerberus.service.CloudFormationService; +import com.nike.cerberus.service.RdsService; +import com.nike.cerberus.store.ConfigStore; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javax.inject.Named; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Date; + +import static com.nike.cerberus.module.CerberusModule.ENV_NAME; + +public class CleanUpRdsSnapshotsOperation implements Operation { + + private final Logger log = LoggerFactory.getLogger(getClass()); + + private final ConfigStore configStore; + private final String environmentName; + private final RdsService rdsService; + private final CloudFormationService cloudFormationService; + + @Inject + public CleanUpRdsSnapshotsOperation(ConfigStore configStore, + @Named(ENV_NAME) String environmentName, + RdsService rdsService, + CloudFormationService cloudFormationService) { + + this.configStore = configStore; + this.environmentName = environmentName; + this.rdsService = rdsService; + this.cloudFormationService = cloudFormationService; + } + + @Override + public void run(CleanUpRdsSnapshotsCommand command) { + Regions primaryRegion = configStore.getPrimaryRegion(); + Date oldestAcceptableSnapshotCreationDate = Date.from(Instant.now().minus(command.getDays(), ChronoUnit.DAYS)); + + // Go through each config region + configStore.getConfigEnabledRegions().stream() + // and filter out the primary region, because rds cleans those snapshots automatically + .filter(region -> ! region.equals(primaryRegion)) + .forEach(region -> + // in each region list all the snapshots + rdsService.getDbSnapshots(region).stream() + // filter out snapshots that are not from the cluster / environment under question + // and are older than the number of acceptable days + .filter(snapshot -> + rdsService.wasSnapshotGeneratedFromCmsCluster(snapshot) && + snapshot.getSnapshotCreateTime().before(oldestAcceptableSnapshotCreationDate)) + // delete the snapshots + .forEach(snapshot -> { + if (command.isDryRun()) { + log.info("snapshot: {} with creation date: {} in region: {}", + snapshot.getDBClusterSnapshotIdentifier(), snapshot.getSnapshotCreateTime(), region); + } else { + rdsService.deleteSnapshot(snapshot, region); + } + })); + } + + @Override + public boolean isRunnable(CleanUpRdsSnapshotsCommand command) { + boolean isRunnable = true; + + if (! cloudFormationService.isStackPresent(configStore.getPrimaryRegion(), + Stack.DATABASE.getFullName(environmentName))) { + log.error("The Database stuck must exist in the primary region in order to have snapshots to clean up"); + isRunnable = false; + } + + return isRunnable; + } +} diff --git a/src/main/java/com/nike/cerberus/operation/rds/CopyRdsSnapshotsOperation.java b/src/main/java/com/nike/cerberus/operation/rds/CopyRdsSnapshotsOperation.java new file mode 100644 index 00000000..b8110cef --- /dev/null +++ b/src/main/java/com/nike/cerberus/operation/rds/CopyRdsSnapshotsOperation.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.operation.rds; + +import com.amazonaws.regions.Regions; +import com.amazonaws.services.rds.model.DBClusterSnapshot; +import com.nike.cerberus.command.rds.CopyRdsSnapshotsCommand; +import com.nike.cerberus.domain.environment.Stack; +import com.nike.cerberus.operation.Operation; +import com.nike.cerberus.service.CloudFormationService; +import com.nike.cerberus.service.RdsService; +import com.nike.cerberus.store.ConfigStore; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javax.inject.Named; +import java.util.List; +import java.util.stream.Collectors; + +import static com.nike.cerberus.module.CerberusModule.ENV_NAME; + +/** + * Operation for CopyRdsSnapshotsCommand + * + * Copies rds cluster snapshots in the primary for this environment to the secondary regions + */ +public class CopyRdsSnapshotsOperation implements Operation { + + private final Logger log = LoggerFactory.getLogger(getClass()); + + private final ConfigStore configStore; + private final String environmentName; + private final RdsService rdsService; + private final CloudFormationService cloudFormationService; + + @Inject + public CopyRdsSnapshotsOperation(ConfigStore configStore, + @Named(ENV_NAME) String environmentName, + RdsService rdsService, + CloudFormationService cloudFormationService) { + + this.configStore = configStore; + this.environmentName = environmentName; + this.rdsService = rdsService; + this.cloudFormationService = cloudFormationService; + } + + @Override + public void run(CopyRdsSnapshotsCommand command) { + Regions primaryRegion = configStore.getPrimaryRegion(); + List snapshotsInPrimaryRegion = rdsService.getDbSnapshots(primaryRegion).stream() + .filter(dbSnapshot -> + rdsService.wasSnapshotGeneratedFromCmsCluster(dbSnapshot) && rdsService.isSnapshotNewerThanGivenDays(dbSnapshot, command.getDays()) + ).collect(Collectors.toList()); + + configStore.getConfigEnabledRegions().forEach(region -> { + if (region.equals(primaryRegion)) { + return; + } + + List toRegionSnapshots = rdsService.getDbSnapshots(region).stream() + .filter(rdsService::wasSnapshotGeneratedFromCmsCluster) + .collect(Collectors.toList()); + + snapshotsInPrimaryRegion.forEach(fromSnapshot -> { + boolean needsCopying = toRegionSnapshots.stream().noneMatch(toSnapshot -> + rdsService.getIdentifier(fromSnapshot).equals(rdsService.getIdentifier(toSnapshot))); + if (needsCopying) { + log.info("Preparing to initiate copy of RDS DB Snapshot: {} located in region: {} to region: {}", + fromSnapshot.getDBClusterSnapshotIdentifier(), primaryRegion, region); + + rdsService.copySnapshot(fromSnapshot, primaryRegion, region); + } else { + log.info("Snapshot: {} already copied to region: {}, skipping...", rdsService.getIdentifier(fromSnapshot), region); + } + }); + }); + log.info("Finished issuing copy api calls to aws, it can take a long time for copies to finish."); + } + + + + @Override + public boolean isRunnable(CopyRdsSnapshotsCommand command) { + boolean isRunnable = true; + + if (! cloudFormationService.isStackPresent(configStore.getPrimaryRegion(), + Stack.DATABASE.getFullName(environmentName))) { + log.error("The Database stuck must exist in the primary region in order to duplicate snapshots"); + isRunnable = false; + } + + return isRunnable; + } +} diff --git a/src/main/java/com/nike/cerberus/operation/rds/CreateDatabaseOperation.java b/src/main/java/com/nike/cerberus/operation/rds/CreateDatabaseOperation.java new file mode 100644 index 00000000..08e6dac4 --- /dev/null +++ b/src/main/java/com/nike/cerberus/operation/rds/CreateDatabaseOperation.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.operation.rds; + +import com.amazonaws.regions.Regions; +import com.nike.cerberus.command.rds.CreateDatabaseCommand; +import com.nike.cerberus.domain.cloudformation.DatabaseParameters; +import com.nike.cerberus.domain.cloudformation.VpcOutputs; +import com.nike.cerberus.domain.cloudformation.VpcParameters; +import com.nike.cerberus.domain.environment.Stack; +import com.nike.cerberus.operation.Operation; +import com.nike.cerberus.service.CloudFormationService; +import com.nike.cerberus.store.ConfigStore; +import com.nike.cerberus.util.CloudFormationObjectMapper; +import com.nike.cerberus.util.RandomStringGenerator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javax.inject.Named; +import java.util.Map; + +import static com.nike.cerberus.module.CerberusModule.ENV_NAME; + +/** + * Creates the base components via CloudFormation used by all of Cerberus. + */ +public class CreateDatabaseOperation implements Operation { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private final String environmentName; + + private final CloudFormationService cloudFormationService; + + private final ConfigStore configStore; + + private final CloudFormationObjectMapper cloudFormationObjectMapper; + + private RandomStringGenerator passwordGenerator = new RandomStringGenerator(); + + @Inject + public CreateDatabaseOperation(@Named(ENV_NAME) String environmentName, + CloudFormationService cloudFormationService, + ConfigStore configStore, + CloudFormationObjectMapper cloudFormationObjectMapper) { + + this.environmentName = environmentName; + this.cloudFormationService = cloudFormationService; + this.configStore = configStore; + this.cloudFormationObjectMapper = cloudFormationObjectMapper; + } + + @Override + public void run(CreateDatabaseCommand command) { + Regions region = configStore.getPrimaryRegion(); + + VpcParameters vpcParameters = configStore.getVpcStackParameters(); + VpcOutputs vpcOutputs = configStore.getVpcStackOutputs(); + String databasePassword = passwordGenerator.get(); + + DatabaseParameters databaseParameters = new DatabaseParameters() + .setCmsDbMasterPassword(databasePassword) + .setSgStackName(Stack.SECURITY_GROUPS.getFullName(environmentName)) + .setCmsDbInstanceAz1(vpcParameters.getAz1()) + .setCmsDbInstanceAz2(vpcParameters.getAz2()) + .setCmsDbInstanceAz3(vpcParameters.getAz3()) + .setVpcSubnetIdForAz1(vpcOutputs.getVpcSubnetIdForAz1()) + .setVpcSubnetIdForAz2(vpcOutputs.getVpcSubnetIdForAz2()) + .setVpcSubnetIdForAz3(vpcOutputs.getVpcSubnetIdForAz3()) + .setCmsDbInstanceClass(command.getInstanceClass()) + .setSnapshotIdentifier(command.getSnapshotIdentifier()) + .setVpcInternalBaseDomainName(vpcOutputs.getVpcInternalBaseDomainName()) + .setVpcInternalHostedZoneId(vpcOutputs.getVpcInternalHostedZoneId()); + + Map parameters = cloudFormationObjectMapper.convertValue(databaseParameters); + + configStore.storeCmsDatabasePassword(databasePassword); + + cloudFormationService.createStackAndWait( + region, + Stack.DATABASE, + parameters, + true, + command.getTagsDelegate().getTags() + ); + } + + @Override + public boolean isRunnable(CreateDatabaseCommand command) { + boolean isRunnable = true; + + if (!cloudFormationService.isStackPresent(configStore.getPrimaryRegion(), Stack.SECURITY_GROUPS.getFullName(environmentName))) { + isRunnable = false; + logger.error("The security group stack must exist to create the the data base stack!"); + } + + if (cloudFormationService.isStackPresent(configStore.getPrimaryRegion(), Stack.DATABASE.getFullName(environmentName))) { + isRunnable = false; + logger.error("The database stack already exists, use update-stack"); + } + + return isRunnable; + } +} diff --git a/src/main/java/com/nike/cerberus/operation/vault/CreateCmsVaultTokenOperation.java b/src/main/java/com/nike/cerberus/operation/vault/CreateCmsVaultTokenOperation.java deleted file mode 100644 index c8646e87..00000000 --- a/src/main/java/com/nike/cerberus/operation/vault/CreateCmsVaultTokenOperation.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.operation.vault; - -import com.nike.cerberus.command.vault.CreateCmsVaultTokenCommand; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.store.ConfigStore; -import com.nike.cerberus.vault.VaultAdminClientFactory; -import com.nike.vault.client.VaultAdminClient; -import com.nike.vault.client.model.VaultAuthResponse; -import com.nike.vault.client.model.VaultTokenAuthRequest; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; -import java.util.Optional; - -/** - * Operation to create the Vault token used by CMS. - */ -public class CreateCmsVaultTokenOperation implements Operation { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private final String tokenDisplayName = "cerberus-management-service-token"; - - private final ConfigStore configStore; - - private final VaultAdminClientFactory vaultAdminClientFactory; - - @Inject - public CreateCmsVaultTokenOperation(final ConfigStore configStore, - final VaultAdminClientFactory vaultAdminClientFactory) { - this.configStore = configStore; - this.vaultAdminClientFactory = vaultAdminClientFactory; - } - - @Override - public void run(final CreateCmsVaultTokenCommand command) { - logger.info("Getting client for Vault leader."); - final Optional vaultAdminClient = vaultAdminClientFactory.getClientForLeader(); - - if (vaultAdminClient.isPresent()) { - logger.info("Creating CMS Vault token."); - final VaultAuthResponse authResponse = vaultAdminClient.get().createToken( - new VaultTokenAuthRequest().setDisplayName(tokenDisplayName)); - - logger.info("Uploading CMS Vault token to configuration bucket."); - configStore.storeCmsVaultToken(authResponse.getClientToken()); - - logger.info("Uploading complete."); - } else { - throw new IllegalStateException("Unable to determine Vault leader, aborting..."); - } - } - - @Override - public boolean isRunnable(final CreateCmsVaultTokenCommand command) { - - boolean isRunnable = true; - - final Optional cmsVaultToken = configStore.getCmsVaultToken(); - final boolean hasVaultInstances = vaultAdminClientFactory.hasVaultInstances(); - final Optional vaultAdminClient = vaultAdminClientFactory.getClientForLeader(); - - if (command.isForceOverwrite()) { - if (cmsVaultToken.isPresent()) { - logger.warn("WARNING: Since " + CreateCmsVaultTokenCommand.FORCE_OVERWRITE_LONG_ARG + " was used there may be an old CMS Vault token that needs to be manually revoked in Vault. " - + "The old token can be looked up in the previous version of secrets.json stored in S3 in order to revoke it."); - } else { - // We don't have to exit here except this probably means someone doesn't understand --force-overwrite - logger.error(CreateCmsVaultTokenCommand.FORCE_OVERWRITE_LONG_ARG + " was used but there is no existing CMS Vault token. Please re-run without " + CreateCmsVaultTokenCommand.FORCE_OVERWRITE_LONG_ARG + " option."); - isRunnable = false; - } - } else if (cmsVaultToken.isPresent()) { - logger.error("CMS Vault token is already present. Use update command to rotate it (or in a non-production system consider using the " + CreateCmsVaultTokenCommand.FORCE_OVERWRITE_LONG_ARG + " option)"); - isRunnable = false; - } - - if (!hasVaultInstances) { - logger.error("No vault instances present for this environment!"); - isRunnable = false; - } - - if (!vaultAdminClient.isPresent()) { - logger.error("No Vault instance is the current leader!"); - isRunnable = false; - } - - return isRunnable; - } -} diff --git a/src/main/java/com/nike/cerberus/operation/vault/CreateVaultClusterOperation.java b/src/main/java/com/nike/cerberus/operation/vault/CreateVaultClusterOperation.java deleted file mode 100644 index 6a1b696b..00000000 --- a/src/main/java/com/nike/cerberus/operation/vault/CreateVaultClusterOperation.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright (c) 2017 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.operation.vault; - -import com.amazonaws.services.cloudformation.model.StackStatus; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.collect.Sets; -import com.nike.cerberus.ConfigConstants; -import com.nike.cerberus.command.vault.CreateVaultClusterCommand; -import com.nike.cerberus.domain.EnvironmentMetadata; -import com.nike.cerberus.domain.cloudformation.BaseOutputs; -import com.nike.cerberus.domain.cloudformation.VaultParameters; -import com.nike.cerberus.domain.environment.StackName; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.operation.UnexpectedCloudFormationStatusException; -import com.nike.cerberus.service.CloudFormationService; -import com.nike.cerberus.service.Ec2UserDataService; -import com.nike.cerberus.service.AmiTagCheckService; -import com.nike.cerberus.store.ConfigStore; -import com.nike.cerberus.util.UuidSupplier; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; -import javax.inject.Named; -import java.util.Map; -import java.util.Optional; - -import static com.nike.cerberus.module.CerberusModule.CF_OBJECT_MAPPER; - -/** - * Operation to create the Vault cluster. - */ -public class CreateVaultClusterOperation implements Operation { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private final EnvironmentMetadata environmentMetadata; - - private final CloudFormationService cloudFormationService; - - private final Ec2UserDataService ec2UserDataService; - - private final AmiTagCheckService amiTagCheckService; - - private final UuidSupplier uuidSupplier; - - private final ConfigStore configStore; - - private final ObjectMapper cloudformationObjectMapper; - - @Inject - public CreateVaultClusterOperation(final EnvironmentMetadata environmentMetadata, - final CloudFormationService cloudFormationService, - final Ec2UserDataService ec2UserDataService, - final AmiTagCheckService amiTagCheckService, - final UuidSupplier uuidSupplier, - final ConfigStore configStore, - @Named(CF_OBJECT_MAPPER) final ObjectMapper cloudformationObjectMapper) { - this.environmentMetadata = environmentMetadata; - this.cloudFormationService = cloudFormationService; - this.ec2UserDataService = ec2UserDataService; - this.amiTagCheckService = amiTagCheckService; - this.uuidSupplier = uuidSupplier; - this.configStore = configStore; - this.cloudformationObjectMapper = cloudformationObjectMapper; - } - - @Override - public void run(final CreateVaultClusterCommand command) { - final String uniqueStackName = String.format("%s-%s", StackName.VAULT.getName(), uuidSupplier.get()); - final BaseOutputs baseOutputs = configStore.getBaseStackOutputs(); - final Optional vaultServerCertificateArn = configStore.getServerCertificateArn(StackName.VAULT); - final Optional pubKey = configStore.getCertPart(StackName.VAULT, ConfigConstants.CERT_PART_PUBKEY); - final String internalElbCname = configStore.getInternalElbCname(StackName.VAULT); - - if (!vaultServerCertificateArn.isPresent() || !pubKey.isPresent()) { - throw new IllegalStateException("Vault server certificate has not been uploaded!"); - } - - // Make sure the given AmiId is for Vault component. Check if it contains required tag - if ( !command.isSkipAmiTagCheck() ) { - amiTagCheckService.validateAmiTagForStack(command.getStackDelegate().getAmiId(), StackName.VAULT); - } - - final VaultParameters vaultParameters = new VaultParameters() - .setInstanceProfileName(baseOutputs.getVaultInstanceProfileName()) - .setVaultClientSgId(baseOutputs.getVaultClientSgId()) - .setVaultServerSgId(baseOutputs.getVaultServerSgId()) - .setVaultServerElbSgId(baseOutputs.getVaultServerElbSgId()) - .setConsulClientSgId(baseOutputs.getConsulClientSgId()) - .setConsulServerSgId(baseOutputs.getConsulServerSgId()) - .setToolsIngressSgId(baseOutputs.getToolsIngressSgId()) - .setVpcId(baseOutputs.getVpcId()) - .setVpcSubnetIdForAz1(baseOutputs.getVpcSubnetIdForAz1()) - .setVpcSubnetIdForAz2(baseOutputs.getVpcSubnetIdForAz2()) - .setVpcSubnetIdForAz3(baseOutputs.getVpcSubnetIdForAz3()) - .setHostedZoneId(baseOutputs.getVpcHostedZoneId()) - .setCname(internalElbCname); - - vaultParameters.getSslConfigParameters().setCertPublicKey(pubKey.get()); - vaultParameters.getSslConfigParameters().setSslCertificateArn(vaultServerCertificateArn.get()); - - vaultParameters.getLaunchConfigParameters().setAmiId(command.getStackDelegate().getAmiId()); - vaultParameters.getLaunchConfigParameters().setInstanceSize(command.getStackDelegate().getInstanceSize()); - vaultParameters.getLaunchConfigParameters().setKeyPairName(command.getStackDelegate().getKeyPairName()); - vaultParameters.getLaunchConfigParameters().setUserData( - ec2UserDataService.getUserData(StackName.VAULT, command.getStackDelegate().getOwnerGroup())); - vaultParameters.getLaunchConfigParameters().setDesiredInstances( - command.getStackDelegate().getDesiredInstances()); - vaultParameters.getLaunchConfigParameters().setMinimumInstances( - command.getStackDelegate().getMinimumInstances()); - vaultParameters.getLaunchConfigParameters().setMaximumInstances( - command.getStackDelegate().getMaximumInstances()); - - vaultParameters.getTagParameters().setTagEmail(command.getStackDelegate().getOwnerEmail()); - vaultParameters.getTagParameters().setTagName(ConfigConstants.ENV_PREFIX + environmentMetadata.getName()); - vaultParameters.getTagParameters().setTagCostcenter(command.getStackDelegate().getCostcenter()); - - final TypeReference> typeReference = new TypeReference>() {}; - final Map parameters = cloudformationObjectMapper.convertValue(vaultParameters, typeReference); - - final String stackId = cloudFormationService.createStack(cloudFormationService.getEnvStackName(uniqueStackName), - parameters, ConfigConstants.VAULT_STACK_TEMPLATE_PATH, true); - - logger.info("Uploading data to the configuration bucket."); - configStore.storeStackId(StackName.VAULT, stackId); - logger.info("Uploading complete."); - - final StackStatus endStatus = - cloudFormationService.waitForStatus(stackId, - Sets.newHashSet(StackStatus.CREATE_COMPLETE, StackStatus.ROLLBACK_COMPLETE)); - - if (endStatus != StackStatus.CREATE_COMPLETE) { - final String errorMessage = String.format("Unexpected end status: %s", endStatus.name()); - logger.error(errorMessage); - - throw new UnexpectedCloudFormationStatusException(errorMessage); - } - } - - @Override - public boolean isRunnable(final CreateVaultClusterCommand command) { - boolean isRunnable = true; - final String consulStackId = configStore.getStackId(StackName.CONSUL); - final String certificateName = configStore.getServerCertificateName(StackName.VAULT); - final boolean hasVaultConfig = configStore.hasVaultConfig(); - - if (StringUtils.isBlank(consulStackId) || !cloudFormationService.isStackPresent(consulStackId)) { - logger.error("No Consul stack defined for this environment!"); - isRunnable = false; - } - - if (StringUtils.isBlank(certificateName)) { - logger.error("Certificate has not been uploaded for Vault!"); - isRunnable = false; - } - - if (!hasVaultConfig) { - logger.error("No configuration for Vault exists for this environment!"); - isRunnable = false; - } - - return isRunnable; - } -} diff --git a/src/main/java/com/nike/cerberus/operation/vault/CreateVaultConfigOperation.java b/src/main/java/com/nike/cerberus/operation/vault/CreateVaultConfigOperation.java deleted file mode 100644 index cdd92f2f..00000000 --- a/src/main/java/com/nike/cerberus/operation/vault/CreateVaultConfigOperation.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.operation.vault; - -import com.nike.cerberus.ConfigConstants; -import com.nike.cerberus.command.vault.CreateVaultConfigCommand; -import com.nike.cerberus.domain.configuration.VaultConfiguration; -import com.nike.cerberus.generator.VaultConfigGenerator; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.store.ConfigStore; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; - -/** - * Operation that generates and uploads the Vault configuration. - */ -public class CreateVaultConfigOperation implements Operation { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private final VaultConfigGenerator vaultConfigGenerator; - - private final ConfigStore configStore; - - @Inject - public CreateVaultConfigOperation(final VaultConfigGenerator vaultConfigGenerator, - final ConfigStore configStore) { - this.vaultConfigGenerator = vaultConfigGenerator; - this.configStore = configStore; - } - - @Override - public void run(final CreateVaultConfigCommand command) { - logger.info("Retrieving configuration data from the configuration bucket."); - final String vaultAclToken = configStore.getVaultAclToken(); - - logger.info("Generating the Vault configuration."); - final VaultConfiguration vaultConfiguration = - vaultConfigGenerator.generate(ConfigConstants.CONSUL_DATACENTER, vaultAclToken); - - logger.info("Uploading the Vault configuration to the configuration bucket."); - configStore.storeVaultConfig(vaultConfiguration); - - logger.info("Uploading complete."); - } - - @Override - public boolean isRunnable(final CreateVaultConfigCommand command) { - boolean isRunnable = true; - final String vaultAclToken = configStore.getVaultAclToken(); - - if (StringUtils.isBlank(vaultAclToken)) { - logger.error("No Vault ACL token present for Consul, please generate that first."); - isRunnable = false; - } - - return isRunnable; - } -} diff --git a/src/main/java/com/nike/cerberus/operation/vault/DisableAuditBackendOperation.java b/src/main/java/com/nike/cerberus/operation/vault/DisableAuditBackendOperation.java deleted file mode 100644 index 91b95a8e..00000000 --- a/src/main/java/com/nike/cerberus/operation/vault/DisableAuditBackendOperation.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.operation.vault; - -import com.nike.cerberus.command.vault.DisableAuditBackendCommand; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.vault.VaultAdminClientFactory; -import com.nike.vault.client.VaultAdminClient; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; -import java.util.Optional; - -/** - * Handles disabling the audit backend specified in the command. - */ -public class DisableAuditBackendOperation implements Operation { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private final VaultAdminClientFactory vaultAdminClientFactory; - - @Inject - public DisableAuditBackendOperation(final VaultAdminClientFactory vaultAdminClientFactory) { - this.vaultAdminClientFactory = vaultAdminClientFactory; - } - - @Override - public void run(DisableAuditBackendCommand command) { - logger.info("Getting client for Vault leader."); - final Optional client = vaultAdminClientFactory.getClientForLeader(); - - if (client.isPresent()) { - logger.info("Disabling the specified audit backend in Vault."); - client.get().disableAuditBackend(command.getAuditBackend().getType()); - logger.info("Audit disabled."); - } else { - throw new IllegalStateException("Unable to determine Vault leader, aborting..."); - } - } - - @Override - public boolean isRunnable(final DisableAuditBackendCommand command) { - return vaultAdminClientFactory.getClientForLeader().isPresent(); - } -} diff --git a/src/main/java/com/nike/cerberus/operation/vault/EnableAuditBackendOperation.java b/src/main/java/com/nike/cerberus/operation/vault/EnableAuditBackendOperation.java deleted file mode 100644 index 49b874fe..00000000 --- a/src/main/java/com/nike/cerberus/operation/vault/EnableAuditBackendOperation.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.operation.vault; - -import com.google.common.collect.Maps; -import com.nike.cerberus.command.vault.EnableAuditBackendCommand; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.vault.VaultAdminClientFactory; -import com.nike.vault.client.VaultAdminClient; -import com.nike.vault.client.model.VaultEnableAuditBackendRequest; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; -import java.util.Map; -import java.util.Optional; - -/** - * Operation for enabling the audit backend for Vault. - */ -public class EnableAuditBackendOperation implements Operation { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private final VaultAdminClientFactory vaultAdminClientFactory; - - @Inject - public EnableAuditBackendOperation(final VaultAdminClientFactory vaultAdminClientFactory) { - this.vaultAdminClientFactory = vaultAdminClientFactory; - } - - @Override - public void run(final EnableAuditBackendCommand command) { - logger.info("Getting client for Vault leader."); - final Optional client = vaultAdminClientFactory.getClientForLeader(); - - if (client.isPresent()) { - Map options = Maps.newHashMap(); - - switch (command.getAuditBackend()) { - case FILE: - options = getFileOptions(command.getFilePath().toString()); - break; - case SYSLOG: - options = getSyslogOptions(); - break; - } - - final VaultEnableAuditBackendRequest request = new VaultEnableAuditBackendRequest() - .setType(command.getAuditBackend().getType()) - .setDescription("Cerberus configured audit backend.") - .setOptions(options); - - logger.info("Enabling the audit backend in Vault."); - client.get().enableAuditBackend(command.getAuditBackend().getType(), request); - logger.info("Audit enabled."); - } else { - throw new IllegalStateException("Unable to determine Vault leader, aborting..."); - } - } - - @Override - public boolean isRunnable(final EnableAuditBackendCommand command) { - return vaultAdminClientFactory.getClientForLeader().isPresent(); - } - - private Map getFileOptions(final String filePath) { - if (StringUtils.isBlank(filePath)) { - throw new IllegalArgumentException("File path is required for the file audit backend type."); - } - - final Map options = Maps.newHashMap(); - options.put("file_path", filePath); - options.put("log_raw", "false"); - options.put("hmac_accessor", "true"); - return options; - } - - private Map getSyslogOptions() { - final Map options = Maps.newHashMap(); - options.put("facility", "AUTH"); - options.put("tag", "vault"); - options.put("log_raw", "false"); - options.put("hmac_accessor", "true"); - return options; - } -} diff --git a/src/main/java/com/nike/cerberus/operation/vault/InitVaultClusterOperation.java b/src/main/java/com/nike/cerberus/operation/vault/InitVaultClusterOperation.java deleted file mode 100644 index b9ee4a36..00000000 --- a/src/main/java/com/nike/cerberus/operation/vault/InitVaultClusterOperation.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.operation.vault; - -import com.nike.cerberus.ConfigConstants; -import com.nike.cerberus.command.vault.InitVaultClusterCommand; -import com.nike.cerberus.domain.environment.StackName; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.store.ConfigStore; -import com.nike.cerberus.vault.VaultAdminClientFactory; -import com.nike.vault.client.VaultAdminClient; -import com.nike.vault.client.model.VaultInitResponse; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; -import java.util.List; - -/** - * Operation for initializing each Vault instance in the cluster. - */ -public class InitVaultClusterOperation implements Operation { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private final ConfigStore configStore; - - private final VaultAdminClientFactory vaultAdminClientFactory; - - @Inject - public InitVaultClusterOperation(final ConfigStore configStore, - final VaultAdminClientFactory vaultAdminClientFactory) { - this.configStore = configStore; - this.vaultAdminClientFactory = vaultAdminClientFactory; - } - - @Override - public void run(final InitVaultClusterCommand command) { - logger.info("If you just created the Vault cluster, this command will fail until the Vault cluster has a chance to initialize. If that happens, just try again in a few minutes."); - - logger.info("Getting clients for Vault instances."); - final List clients = vaultAdminClientFactory.getClientsForCluster(); - - logger.info("Calling init on one Vault instance."); - final VaultInitResponse initResponse = - clients.get(0).init(ConfigConstants.VAULT_SECRET_SHARES, ConfigConstants.VAULT_SECRET_THRESHOLD); - - logger.info("Uploading Vault root token and keys to configuration bucket."); - configStore.storeVaultRootToken(initResponse.getRootToken()); - configStore.storeVaultKeys(initResponse.getKeys()); - - logger.info("Uploading complete."); - } - - @Override - public boolean isRunnable(final InitVaultClusterCommand command) { - return StringUtils.isNotBlank(configStore.getStackId(StackName.VAULT)); - } -} diff --git a/src/main/java/com/nike/cerberus/operation/vault/LoadDefaultVaultPoliciesOperation.java b/src/main/java/com/nike/cerberus/operation/vault/LoadDefaultVaultPoliciesOperation.java deleted file mode 100644 index 16e25e5e..00000000 --- a/src/main/java/com/nike/cerberus/operation/vault/LoadDefaultVaultPoliciesOperation.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.operation.vault; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.nike.cerberus.command.vault.LoadDefaultVaultPoliciesCommand; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.vault.VaultAdminClientFactory; -import com.nike.vault.client.VaultAdminClient; -import com.nike.vault.client.model.VaultPolicy; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; -import java.io.IOException; -import java.io.InputStream; -import java.util.Map; -import java.util.Optional; - -/** - * Operation for loading the default Vault policies expected by Cerberus. - */ -public class LoadDefaultVaultPoliciesOperation implements Operation { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private final String defaultPoliciesPath = "/vault/default-policies.json"; - - private final VaultAdminClientFactory vaultAdminClientFactory; - - @Inject - public LoadDefaultVaultPoliciesOperation(final VaultAdminClientFactory vaultAdminClientFactory) { - this.vaultAdminClientFactory = vaultAdminClientFactory; - } - - @Override - public void run(final LoadDefaultVaultPoliciesCommand command) { - logger.info("Getting client for Vault leader."); - final Optional leaderClient = vaultAdminClientFactory.getClientForLeader(); - - if (leaderClient.isPresent()) { - for (Map.Entry entry : getDefaultPolicies().entrySet()) { - logger.info("Loading policy, {}.", entry.getKey()); - leaderClient.get().putPolicy(entry.getKey(), entry.getValue()); - } - - logger.info("All default policies loaded."); - } else { - throw new IllegalStateException("Unable to determine Vault leader, aborting..."); - } - } - - @Override - public boolean isRunnable(final LoadDefaultVaultPoliciesCommand command) { - return vaultAdminClientFactory.getClientForLeader().isPresent(); - } - - private Map getDefaultPolicies() { - final ObjectMapper objectMapper = new ObjectMapper(); - final TypeReference> typeReference = new TypeReference>() {}; - - try (InputStream defaultPolicies = getClass().getResourceAsStream(defaultPoliciesPath)) { - return objectMapper.readValue(defaultPolicies, typeReference); - } catch (IOException e) { - throw new IllegalStateException("Unable to read the default policies document from the classpath!", e); - } - } -} diff --git a/src/main/java/com/nike/cerberus/operation/vault/UnsealVaultClusterOperation.java b/src/main/java/com/nike/cerberus/operation/vault/UnsealVaultClusterOperation.java deleted file mode 100644 index f2f2a718..00000000 --- a/src/main/java/com/nike/cerberus/operation/vault/UnsealVaultClusterOperation.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.operation.vault; - -import com.nike.cerberus.command.vault.UnsealVaultClusterCommand; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.store.ConfigStore; -import com.nike.cerberus.vault.VaultAdminClientFactory; -import com.nike.vault.client.VaultAdminClient; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; -import java.util.List; - -/** - * Operation to unseal the Vault cluster. - */ -public class UnsealVaultClusterOperation implements Operation { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private final ConfigStore configStore; - - private final VaultAdminClientFactory vaultAdminClientFactory; - - @Inject - public UnsealVaultClusterOperation(final ConfigStore configStore, - final VaultAdminClientFactory vaultAdminClientFactory) { - this.configStore = configStore; - this.vaultAdminClientFactory = vaultAdminClientFactory; - } - - @Override - public void run(final UnsealVaultClusterCommand command) { - logger.info("Getting Vault keys from configuration bucket."); - final List vaultKeys = configStore.getVaultKeys(); - - logger.info("Getting clients for Vault instances."); - final List clients = vaultAdminClientFactory.getClientsForCluster(); - - logger.info("Unsealing each Vault instance."); - clients.forEach(client -> - vaultKeys.forEach(vaultKey -> client.unseal(vaultKey, false)) - ); - - logger.info("Unsealing complete."); - } - - @Override - public boolean isRunnable(final UnsealVaultClusterCommand command) { - return !vaultAdminClientFactory.getClientsForCluster().isEmpty(); - } -} diff --git a/src/main/java/com/nike/cerberus/operation/vault/VaultHealthCheckOperation.java b/src/main/java/com/nike/cerberus/operation/vault/VaultHealthCheckOperation.java deleted file mode 100644 index ccf6b941..00000000 --- a/src/main/java/com/nike/cerberus/operation/vault/VaultHealthCheckOperation.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.operation.vault; - -import com.beust.jcommander.internal.Lists; -import com.github.tomaslanger.chalk.Ansi; -import com.github.tomaslanger.chalk.Chalk; -import com.google.inject.Inject; -import com.nike.cerberus.command.vault.VaultHealthCheckCommand; -import com.nike.cerberus.operation.Operation; -import com.nike.cerberus.vault.VaultAdminClientFactory; -import com.nike.vault.client.VaultAdminClient; -import com.nike.vault.client.model.VaultHealthResponse; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.List; -import java.util.concurrent.TimeUnit; - -public class VaultHealthCheckOperation implements Operation { - - private final VaultAdminClientFactory vaultAdminClientFactory; - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - @Inject - public VaultHealthCheckOperation(VaultAdminClientFactory vaultAdminClientFactory) { - this.vaultAdminClientFactory = vaultAdminClientFactory; - } - - @Override - public void run(VaultHealthCheckCommand command) { - List msgList = Lists.newLinkedList(); - boolean toggle = false; - do { - final List clients = vaultAdminClientFactory.getClientsForCluster(); - clients.forEach(client -> { - try { - VaultHealthResponse response = client.health(); - msgList.add(String.format("%s: Initialized: %s, Sealed: %s, Standby: %s", - client.getVaultUrl(), - colorify(response.isInitialized()), - colorify(response.isSealed()), - colorify(response.isStandby()))); - } catch (Throwable t) { - msgList.add(String.format("ERROR: %s", t.getMessage())); - } - }); - - String sym = toggle ? "*" : "+"; - sym = StringUtils.repeat(sym, 20); - - if (command.isPoll()) { - logger.info(Ansi.eraseScreen()); - } - - logger.info(String.format("%s - Vault Health Status - %s", sym, sym)); - msgList.forEach(logger::info); - msgList.clear(); - toggle = !toggle; - - try { - Thread.sleep(TimeUnit.SECONDS.toMillis(1)); - } catch (InterruptedException e) { - break; - } - } while (command.isPoll()); - } - - - - private String colorify(boolean status) { - return status ? Chalk.on(Boolean.TRUE.toString()).green().bold().toString() : - Chalk.on(Boolean.FALSE.toString()).red().bold().toString(); - } - - @Override - public boolean isRunnable(VaultHealthCheckCommand command) { - final boolean hasInstances = vaultAdminClientFactory.hasVaultInstances(); - - if (!hasInstances) { - logger.warn("No Vault instances detected for this environment, exiting..."); - } - - return hasInstances; - } -} diff --git a/src/main/java/com/nike/cerberus/service/AmiTagCheckService.java b/src/main/java/com/nike/cerberus/service/AmiTagCheckService.java index e63ece91..6239fb7a 100644 --- a/src/main/java/com/nike/cerberus/service/AmiTagCheckService.java +++ b/src/main/java/com/nike/cerberus/service/AmiTagCheckService.java @@ -17,54 +17,64 @@ package com.nike.cerberus.service; import com.amazonaws.AmazonServiceException; +import com.amazonaws.regions.Regions; import com.amazonaws.services.ec2.AmazonEC2; +import com.amazonaws.services.ec2.AmazonEC2Client; import com.amazonaws.services.ec2.model.DescribeImagesRequest; import com.amazonaws.services.ec2.model.DescribeImagesResult; import com.amazonaws.services.ec2.model.Filter; +import com.nike.cerberus.ConfigConstants; +import com.nike.cerberus.domain.environment.Stack; +import com.nike.cerberus.store.ConfigStore; import javax.inject.Inject; -import java.util.List; import java.util.HashMap; import java.util.Map; -import java.util.stream.Collectors; - -import com.nike.cerberus.domain.environment.StackName; -import com.nike.cerberus.ConfigConstants; /** * Service wrapper for AWS EC2. */ public class AmiTagCheckService { - private final AmazonEC2 ec2Client; + private final AwsClientFactory amazonEC2ClientFactory; + private final ConfigStore configStore; - private final Map stackAmiTagValueMap; + private final Map stackAmiTagValueMap; @Inject - public AmiTagCheckService(final AmazonEC2 ec2Client) { - this.ec2Client = ec2Client; + public AmiTagCheckService(AwsClientFactory amazonEC2ClientFactory, + ConfigStore configStore) { + + this.amazonEC2ClientFactory = amazonEC2ClientFactory; + this.configStore = configStore; stackAmiTagValueMap = new HashMap<>(); - stackAmiTagValueMap.put(StackName.CONSUL, ConfigConstants.CONSUL_AMI_TAG_VALUE); - stackAmiTagValueMap.put(StackName.VAULT, ConfigConstants.VAULT_AMI_TAG_VALUE); - stackAmiTagValueMap.put(StackName.CMS, ConfigConstants.CMS_AMI_TAG_VALUE); - stackAmiTagValueMap.put(StackName.GATEWAY, ConfigConstants.GATEWAY_AMI_TAG_VALUE); + stackAmiTagValueMap.put(Stack.CMS, ConfigConstants.CMS_AMI_TAG_VALUE); + } + /** + * Validates if the given AMI has given tag and value in the primary region. + * + * @return true if matches otherwise false + */ + public boolean isAmiWithTagExist(String amiId, String tagName, String tagValue) { + return isAmiWithTagExist(configStore.getPrimaryRegion(), amiId, tagName, tagValue); } /** - * Validates if the given AMI has given tag and value. + * Validates if the given AMI has given tag and value in the provided region. * * @return true if matches otherwise false */ - public boolean isAmiWithTagExist(final String amiId, final String tagName, final String tagValue) { + public boolean isAmiWithTagExist(Regions region, String amiId, String tagName, String tagValue) { + AmazonEC2 ec2Client = amazonEC2ClientFactory.getClient(region); - final DescribeImagesRequest request = new DescribeImagesRequest() - .withFilters(new Filter().withName(tagName).withValues(tagValue)) - .withFilters(new Filter().withName("image-id").withValues(amiId)); + DescribeImagesRequest request = new DescribeImagesRequest() + .withFilters(new Filter().withName(tagName).withValues(tagValue)) + .withFilters(new Filter().withName("image-id").withValues(amiId)); try { - final DescribeImagesResult result = ec2Client.describeImages(request); + DescribeImagesResult result = ec2Client.describeImages(request); return result.getImages().size() > 0; } catch (final AmazonServiceException ase) { if (ase.getErrorCode() == "InvalidAMIID.NotFound") { @@ -80,8 +90,8 @@ public boolean isAmiWithTagExist(final String amiId, final String tagName, final * * @return void */ - public void validateAmiTagForStack(final String amiId, final StackName stackName) { - if (!isAmiWithTagExist(amiId, ConfigConstants.CERBERUS_AMI_TAG_NAME, stackAmiTagValueMap.get(stackName) )) { + public void validateAmiTagForStack(final String amiId, final Stack stack) { + if (!isAmiWithTagExist(amiId, ConfigConstants.CERBERUS_AMI_TAG_NAME, stackAmiTagValueMap.get(stack))) { throw new IllegalStateException(ConfigConstants.AMI_TAG_CHECK_ERROR_MESSAGE); } } diff --git a/src/main/java/com/nike/cerberus/service/AutoScalingService.java b/src/main/java/com/nike/cerberus/service/AutoScalingService.java index 1e5a19db..e9878141 100644 --- a/src/main/java/com/nike/cerberus/service/AutoScalingService.java +++ b/src/main/java/com/nike/cerberus/service/AutoScalingService.java @@ -16,7 +16,8 @@ package com.nike.cerberus.service; -import com.amazonaws.services.autoscaling.AmazonAutoScaling; +import com.amazonaws.regions.Regions; +import com.amazonaws.services.autoscaling.AmazonAutoScalingClient; import com.amazonaws.services.autoscaling.model.AutoScalingGroup; import com.amazonaws.services.autoscaling.model.DescribeAutoScalingGroupsRequest; import com.amazonaws.services.autoscaling.model.DescribeAutoScalingGroupsResult; @@ -24,9 +25,11 @@ import com.amazonaws.services.autoscaling.model.ExitStandbyRequest; import com.amazonaws.services.autoscaling.model.UpdateAutoScalingGroupRequest; import com.amazonaws.services.ec2.AmazonEC2; +import com.amazonaws.services.ec2.AmazonEC2Client; import com.amazonaws.services.ec2.model.DescribeInstancesRequest; import com.amazonaws.services.ec2.model.DescribeInstancesResult; import com.beust.jcommander.internal.Lists; +import com.nike.cerberus.store.ConfigStore; import javax.inject.Inject; import java.util.List; @@ -37,34 +40,53 @@ */ public class AutoScalingService { - private final AmazonAutoScaling autoScalingClient; - - private final AmazonEC2 ec2Client; + private final AwsClientFactory autoScalingClientFactory; + private final AwsClientFactory amazonEC2ClientFactory; + private final ConfigStore configStore; @Inject - public AutoScalingService(final AmazonAutoScaling autoScalingClient, final AmazonEC2 ec2Client) { - this.autoScalingClient = autoScalingClient; - this.ec2Client = ec2Client; + public AutoScalingService(AwsClientFactory autoScalingClientFactory, + AwsClientFactory amazonEC2ClientFactory, + ConfigStore configStore) { + + this.autoScalingClientFactory = autoScalingClientFactory; + this.amazonEC2ClientFactory = amazonEC2ClientFactory; + this.configStore = configStore; + } + + /** + * For a given AutoScaling group logical id, get the public dns names associated with each instance in the + * primary region + * + * @param logicalId AutoScaling group logical id + * @return List of public dns names + */ + public List getPublicDnsForAutoScalingGroup(String logicalId) { + return getPublicDnsForAutoScalingGroup(configStore.getPrimaryRegion(), logicalId); } /** - * For a given AutoScaling group logical id, get the public dns names associated with each instance. + * For a given AutoScaling group logical id, get the public dns names associated with each instance in the + * provided region * + * @param region The region to use * @param logicalId AutoScaling group logical id * @return List of public dns names */ - public List getPublicDnsForAutoScalingGroup(final String logicalId) { - final List instanceIds = Lists.newLinkedList(); - final Optional autoScalingGroup = describeAutoScalingGroup(logicalId); - final List publicDnsNames = Lists.newLinkedList(); + public List getPublicDnsForAutoScalingGroup(Regions region, String logicalId) { + AmazonEC2 ec2Client = amazonEC2ClientFactory.getClient(region); + + List instanceIds = Lists.newLinkedList(); + Optional autoScalingGroup = describeAutoScalingGroup(region, logicalId); + List publicDnsNames = Lists.newLinkedList(); if (autoScalingGroup.isPresent()) { autoScalingGroup.get() .getInstances().stream().forEach(instance -> instanceIds.add(instance.getInstanceId())); - final DescribeInstancesRequest describeInstancesRequest = new DescribeInstancesRequest() + DescribeInstancesRequest describeInstancesRequest = new DescribeInstancesRequest() .withInstanceIds(instanceIds); - final DescribeInstancesResult describeInstancesResult = + DescribeInstancesResult describeInstancesResult = ec2Client.describeInstances(describeInstancesRequest); describeInstancesResult.getReservations().forEach(reservation -> @@ -76,13 +98,25 @@ public List getPublicDnsForAutoScalingGroup(final String logicalId) { return publicDnsNames; } + /** + * Updates the minimum number of instances allowed in the auto scaling group in the primary region + * + * @param logicalId - Name of the auto scaling group + */ + public void updateMinInstancesForAutoScalingGroup(String logicalId, int minInstances) { + updateMinInstancesForAutoScalingGroup(configStore.getPrimaryRegion(), logicalId, minInstances); + } + /** * Updates the minimum number of instances allowed in the auto scaling group + * + * @param region The region to use * @param logicalId - Name of the auto scaling group */ - public void updateMinInstancesForAutoScalingGroup(final String logicalId, final int minInstances) { + public void updateMinInstancesForAutoScalingGroup(Regions region, String logicalId, int minInstances) { + AmazonAutoScalingClient autoScalingClient = autoScalingClientFactory.getClient(region); - final UpdateAutoScalingGroupRequest request = new UpdateAutoScalingGroupRequest() + UpdateAutoScalingGroupRequest request = new UpdateAutoScalingGroupRequest() .withAutoScalingGroupName(logicalId) .withMinSize(minInstances); @@ -93,11 +127,29 @@ public void updateMinInstancesForAutoScalingGroup(final String logicalId, final * Set an EC2 instance to standby state, so that the desired instance count on the AutoScaling group is decreased * and a new instance is not spun up on instance reboot. This also removes the instance from the ELB, so that the * instance is not terminated when the health check fails. - * @param logicalId - Name of the auto scaling group + * + * uses the primary region + * + * @param logicalId - Name of the auto scaling group * @param instanceId - ID of the EC2 instance */ - public void setInstanceStateToStandby(final String logicalId, final String instanceId) { - final EnterStandbyRequest request = new EnterStandbyRequest() + public void setInstanceStateToStandby(String logicalId, String instanceId) { + setInstanceStateToStandby(configStore.getPrimaryRegion(), logicalId, instanceId); + } + + /** + * Set an EC2 instance to standby state, so that the desired instance count on the AutoScaling group is decreased + * and a new instance is not spun up on instance reboot. This also removes the instance from the ELB, so that the + * instance is not terminated when the health check fails. + * + * @param region The region to use + * @param logicalId - Name of the auto scaling group + * @param instanceId - ID of the EC2 instance + */ + public void setInstanceStateToStandby(Regions region, String logicalId, String instanceId) { + AmazonAutoScalingClient autoScalingClient = autoScalingClientFactory.getClient(region); + + EnterStandbyRequest request = new EnterStandbyRequest() .withAutoScalingGroupName(logicalId) .withInstanceIds(instanceId) .withShouldDecrementDesiredCapacity(true); @@ -107,22 +159,46 @@ public void setInstanceStateToStandby(final String logicalId, final String insta /** * Signify that the EC2 instance is now in service and ready to be re-added to the ELB and AutoScaling group. This - * will also increase the desired instance count for the ASG. - * @param logicalId - Name of the auto scaling group + * will also increase the desired instance count for the ASG in the primary region. + * + * @param logicalId - Name of the auto scaling group + * @param instanceId - ID of the EC2 instance + */ + public void setInstanceStateToInService(String logicalId, String instanceId) { + setInstanceStateToInService(configStore.getPrimaryRegion(), logicalId, instanceId); + } + + /** + * Signify that the EC2 instance is now in service and ready to be re-added to the ELB and AutoScaling group. This + * will also increase the desired instance count for the ASG in the provided region. + * + * @param region The region to use + * @param logicalId - Name of the auto scaling group * @param instanceId - ID of the EC2 instance */ - public void setInstanceStateToInService(final String logicalId, final String instanceId) { - final ExitStandbyRequest request = new ExitStandbyRequest() + public void setInstanceStateToInService(Regions region, String logicalId, String instanceId) { + + AmazonAutoScalingClient autoScalingClient = autoScalingClientFactory.getClient(region); + + ExitStandbyRequest request = new ExitStandbyRequest() .withAutoScalingGroupName(logicalId) .withInstanceIds(instanceId); autoScalingClient.exitStandby(request); } - private Optional describeAutoScalingGroup(final String autoscalingGroupName) { - final DescribeAutoScalingGroupsRequest describeAsg = new DescribeAutoScalingGroupsRequest() - .withAutoScalingGroupNames(autoscalingGroupName); - final DescribeAutoScalingGroupsResult result = autoScalingClient.describeAutoScalingGroups(describeAsg); + /** + * + * @param region The region to look for the ASG + * @param autoScalingGroupName The ASG name to look for. + * @return AutoScalingGroup details + */ + private Optional describeAutoScalingGroup(Regions region, String autoScalingGroupName) { + AmazonAutoScalingClient autoScalingClient = autoScalingClientFactory.getClient(region); + + DescribeAutoScalingGroupsRequest describeAsg = new DescribeAutoScalingGroupsRequest() + .withAutoScalingGroupNames(autoScalingGroupName); + DescribeAutoScalingGroupsResult result = autoScalingClient.describeAutoScalingGroups(describeAsg); return result.getAutoScalingGroups().stream().findFirst(); } diff --git a/src/main/java/com/nike/cerberus/service/AwsClientFactory.java b/src/main/java/com/nike/cerberus/service/AwsClientFactory.java new file mode 100644 index 00000000..ec545b0d --- /dev/null +++ b/src/main/java/com/nike/cerberus/service/AwsClientFactory.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.service; + +import com.amazonaws.AmazonWebServiceClient; +import com.amazonaws.ClientConfiguration; +import com.amazonaws.auth.AWSCredentialsProviderChain; +import com.amazonaws.auth.EnvironmentVariableCredentialsProvider; +import com.amazonaws.auth.InstanceProfileCredentialsProvider; +import com.amazonaws.auth.STSAssumeRoleSessionCredentialsProvider; +import com.amazonaws.auth.SystemPropertiesCredentialsProvider; +import com.amazonaws.auth.profile.ProfileCredentialsProvider; +import com.amazonaws.regions.Region; +import com.amazonaws.regions.Regions; +import com.google.common.collect.Maps; +import com.google.common.reflect.TypeToken; + +import java.util.Map; +import java.util.UUID; + +/** + * Generic AWS Client factory that will create instances of AmazonWebServiceClients configured with the + * proper credentials provider chain and configuration + * + * @param The type of client that will be created. + */ +abstract public class AwsClientFactory { + + public static final String CERBERUS_ASSUME_ROLE_ARN = "CERBERUS_ASSUME_ROLE_ARN"; + public static final String CERBERUS_ASSUME_ROLE_EXTERNAL_ID = "CERBERUS_ASSUME_ROLE_EXTERNAL_ID"; + + /** + * Cache of clients by region + */ + private Map clients = Maps.newHashMap(); + + /** + * Factory that creates and caches Aws clients by region for re-use; + */ + public T getClient(Regions region) { + if (!clients.containsKey(region)) { + clients.put(region, createAmazonClientInstance(getGenericTypeClass(), region)); + } + return clients.get(region); + } + + @SuppressWarnings("unchecked") + private Class getGenericTypeClass() { + TypeToken typeToken = new TypeToken(getClass()) {}; + return (Class) typeToken.getRawType(); + } + + private M createAmazonClientInstance(Class clientClass, Regions region) { + return Region.getRegion(region) + .createClient(clientClass, getAWSCredentialsProviderChain(), getClientConfiguration()); + } + + private ClientConfiguration getClientConfiguration() { + return new ClientConfiguration(); + } + + private AWSCredentialsProviderChain getAWSCredentialsProviderChain() { + String cerberusRoleToAssume = System.getenv(CERBERUS_ASSUME_ROLE_ARN) != null ? + System.getenv(CERBERUS_ASSUME_ROLE_ARN) : ""; + String cerberusRoleToAssumeExternalId = System.getenv(CERBERUS_ASSUME_ROLE_EXTERNAL_ID) != null ? + System.getenv(CERBERUS_ASSUME_ROLE_EXTERNAL_ID) : ""; + + STSAssumeRoleSessionCredentialsProvider sTSAssumeRoleSessionCredentialsProvider = + new STSAssumeRoleSessionCredentialsProvider + .Builder(cerberusRoleToAssume, UUID.randomUUID().toString()) + .withExternalId(cerberusRoleToAssumeExternalId) + .build(); + + AWSCredentialsProviderChain chain = new AWSCredentialsProviderChain( + new EnvironmentVariableCredentialsProvider(), + new SystemPropertiesCredentialsProvider(), + new ProfileCredentialsProvider(), + sTSAssumeRoleSessionCredentialsProvider, + InstanceProfileCredentialsProvider.getInstance()); + + return chain; + } + +} diff --git a/src/main/java/com/nike/cerberus/service/CertificateService.java b/src/main/java/com/nike/cerberus/service/CertificateService.java new file mode 100644 index 00000000..7acdfbb3 --- /dev/null +++ b/src/main/java/com/nike/cerberus/service/CertificateService.java @@ -0,0 +1,724 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.service; + +import com.amazonaws.AmazonServiceException; +import com.amazonaws.regions.Regions; +import com.amazonaws.services.route53.AmazonRoute53; +import com.amazonaws.services.route53.AmazonRoute53Client; +import com.amazonaws.services.route53.model.Change; +import com.amazonaws.services.route53.model.ChangeAction; +import com.amazonaws.services.route53.model.ChangeBatch; +import com.amazonaws.services.route53.model.ChangeResourceRecordSetsRequest; +import com.amazonaws.services.route53.model.RRType; +import com.amazonaws.services.route53.model.ResourceRecord; +import com.amazonaws.services.route53.model.ResourceRecordSet; +import com.amazonaws.util.StringInputStream; +import com.beust.jcommander.internal.Sets; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.gson.Gson; +import com.nike.cerberus.domain.environment.CertificateInformation; +import com.nike.cerberus.store.ConfigStore; +import com.nike.cerberus.util.UuidSupplier; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.apache.commons.io.filefilter.RegexFileFilter; +import org.apache.commons.lang3.StringUtils; +import org.bouncycastle.openssl.jcajce.JcaPEMWriter; +import org.bouncycastle.openssl.jcajce.JcaPKCS8Generator; +import org.bouncycastle.util.io.pem.PemObject; +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; +import org.shredzone.acme4j.Authorization; +import org.shredzone.acme4j.Certificate; +import org.shredzone.acme4j.Registration; +import org.shredzone.acme4j.RegistrationBuilder; +import org.shredzone.acme4j.RevocationReason; +import org.shredzone.acme4j.Session; +import org.shredzone.acme4j.Status; +import org.shredzone.acme4j.challenge.Challenge; +import org.shredzone.acme4j.challenge.Dns01Challenge; +import org.shredzone.acme4j.exception.AcmeConflictException; +import org.shredzone.acme4j.exception.AcmeException; +import org.shredzone.acme4j.util.CSRBuilder; +import org.shredzone.acme4j.util.CertificateUtils; +import org.shredzone.acme4j.util.KeyPairUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xbill.DNS.Lookup; +import org.xbill.DNS.Record; +import org.xbill.DNS.SimpleResolver; +import org.xbill.DNS.Type; + +import javax.inject.Inject; +import javax.inject.Named; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.Writer; +import java.net.URI; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.security.KeyPair; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.StringJoiner; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import static com.nike.cerberus.ConfigConstants.CERT_PART_CERT; +import static com.nike.cerberus.module.CerberusModule.CONFIG_REGION; +import static com.nike.cerberus.module.CerberusModule.ENV_NAME; + +/** + * Service for managing certificates and calls to the ACME cert provider + *

+ * TODO: Update TOS + * TODO: Rotate ACME user private key + * TODO: Support Venafi ACME Impl + */ +@SuppressFBWarnings(value = { + "DM_DEFAULT_ENCODING", + "REC_CATCH_EXCEPTION", + "UC_USELESS_OBJECT" +}) +public class CertificateService { + + private final Logger log = LoggerFactory.getLogger(getClass()); + + // File name of the PKCS #1 private key pem for the Cerberus domain + public static final String DOMAIN_PKCS1_KEY_FILE = "domain-private-key-pkcs1.pem"; + + // File name of the PKCS #8 private key pem for the Cerberus domain + public static final String DOMAIN_PKCS8_KEY_FILE = "domain-private-key-pkcs8.pem"; + + // File name of pub key pem + public static final String DOMAIN_PUBLIC_KEY_FILE = "domain-public-key.pem"; + + // File name of the certificate signing request + public static final String DOMAIN_CSR_FILE = "domain.csr"; + + // File name of the signed certificate with chain + public static final String DOMAIN_FULL_CERT_WITH_CHAIN_CRT_FILE = "domain-full-cert-with-chain.crt"; + + // File name of the signed certificate + public static final String DOMAIN_CERT_FILE = "domain-cert.crt"; + + // File name of the certificate chain + public static final String DOMAIN_CERT_CHAIN_FILE = "chain.crt"; + + // RSA key size of generated key pairs + private static final int KEY_SIZE = 2048; + + protected static final String CHALLENGE_ENTRY_TEMPLATE = "_acme-challenge.%s"; + + protected static final Set EXPECTED_FILE_NAMES = ImmutableSet.of( + DOMAIN_CERT_CHAIN_FILE, + DOMAIN_CERT_FILE, + DOMAIN_PKCS1_KEY_FILE, + DOMAIN_PKCS8_KEY_FILE, + DOMAIN_PUBLIC_KEY_FILE + ); + + private final ConsoleService console; + private final AmazonRoute53 route53; + private final UuidSupplier uuidSupplier; + private final ConfigStore configStore; + private final IdentityManagementService identityManagementService; + private final String environmentName; + + @Inject + public CertificateService(ConsoleService console, + AwsClientFactory route53ClientFactory, + UuidSupplier uuidSupplier, + ConfigStore configStore, + IdentityManagementService identityManagementService, + @Named(ENV_NAME) String environmentName, + @Named(CONFIG_REGION) String configRegion) { + + this.console = console; + // not a region specific AWS service so the config region is fine + this.route53 = route53ClientFactory.getClient(Regions.fromName(configRegion)); + this.uuidSupplier = uuidSupplier; + this.configStore = configStore; + this.identityManagementService = identityManagementService; + this.environmentName = environmentName; + } + + /** + * Finds your {@link Registration} at the ACME server. It will be found by your user's + * public key. If your key is not known to the server yet, a new registration will be + * created. + *

+ * This is a simple way of finding your {@link Registration}. A better way is to get + * the URI of your new registration with {@link Registration#getLocation()} and store + * it somewhere. If you need to get access to your account later, reconnect to it via + * {@link Registration#bind(Session, URI)} by using the stored location. + * + * @param acmeServerUrl The ACME API URL + * @param contactEmail The contact info for the account + * @param autoAcceptTos whether or not to auto accept the TOS from the ACME provider + * @return {@link Registration} connected to your account + */ + protected Registration findOrRegisterAccount(String acmeServerUrl, + String contactEmail, + boolean autoAcceptTos) throws AcmeException, IOException { + + KeyPair userKeyPair = loadOrCreateAcmeAccountKeyPair(); + Session session = new Session(acmeServerUrl, userKeyPair); + + Registration reg; + try { + // Try to create a new Registration. + reg = new RegistrationBuilder().addContact("mailto:" + contactEmail).create(session); + log.info("Registered a new user, URI: " + reg.getLocation()); + + // This is a new account. Let the user accept the Terms of Service. + // We won't be able to authorize domains until the ToS is accepted. + URI agreement = reg.getAgreement(); + + if (!(agreement == null)) { + acceptAgreement(reg, agreement, autoAcceptTos); + } + } catch (AcmeConflictException ex) { + // The Key Pair is already registered. getLocation() contains the + // URL of the existing registration's location. Bind it to the session. + reg = Registration.bind(session, ex.getLocation()); + log.info("Account already exist, URI: {}, no need to create new account", reg.getLocation()); + } + return reg; + } + + /** + * Prompts a user to read and accept or deny a ACME providers TOS + * + * @param reg The registration object + * @param agreement The link to the TOS + */ + protected void acceptAgreement(Registration reg, URI agreement, boolean autoAcceptTos) { + try { + log.info("Please download and review the Terms of Service: " + agreement); + + if (! autoAcceptTos) { + String userResponse = console.readLine("Type \"I Accept\" to accept the Terms of Service, anything else will exit: "); + if (!StringUtils.equalsIgnoreCase("I Accept", userResponse)) { + throw new RuntimeException("User did not accept the Terms of Service"); + } + } else { + log.info("The auto accept flag was passed with the understanding that you have downloaded, " + + "reviewed and agree to the TOS: {},", agreement); + } + + reg.modify().setAgreement(agreement).commit(); + log.info("Updated user's ToS"); + } catch (AcmeException | IOException e) { + throw new RuntimeException("Failed to accept ACME TOS", e); + } + } + + protected void executeRecordSetChanges(String name, String digest, String hostedZoneId, ChangeAction action) { + ResourceRecordSet recordSet = new ResourceRecordSet() + .withName(name) + .withType(RRType.TXT) + .withTTL(10L) + .withResourceRecords( + new ResourceRecord(String.format("\"%s\"", digest)) + ); + + ChangeBatch changeBatch = new ChangeBatch().withChanges( + new Change() + .withAction(action) + .withResourceRecordSet(recordSet) + ); + + route53.changeResourceRecordSets(new ChangeResourceRecordSetsRequest() + .withChangeBatch(changeBatch) + .withHostedZoneId(hostedZoneId)); + } + + /** + * Uses DNS to get a txt record + * + * @param cname the txt record to loop up + * @return Value of record if present + */ + protected Optional getTxtRecordValue(String cname) { + try { + Lookup lookup = new Lookup(cname, Type.TXT); + lookup.setResolver(new SimpleResolver()); + lookup.setCache(null); + Record[] records = lookup.run(); + return Optional.of(records[0].rdataToString()); + } catch (Exception e) { + log.error("Failed to look up txt record for {} reason: {}", cname, e.getMessage()); + return Optional.empty(); + } + } + + /** + * Loads a key pair from specified file. If the file does not exist, + * a new key pair is generated and saved. + * + * @return {@link KeyPair}. + */ + protected KeyPair loadOrCreateKeyPair(File file) throws IOException { + if (file.exists()) { + try (FileReader fr = new FileReader(file)) { + return KeyPairUtils.readKeyPair(fr); + } + } else { + KeyPair domainKeyPair = KeyPairUtils.createKeyPair(KEY_SIZE); + try (FileWriter fw = new FileWriter(file)) { + KeyPairUtils.writeKeyPair(domainKeyPair, fw); + } + return domainKeyPair; + } + } + + /** + * Loads a key pair from specified file. If the file does not exist, + * a new key pair is generated and saved. + * + * @return {@link KeyPair}. + */ + protected KeyPair loadOrCreateAcmeAccountKeyPair() { + return configStore.getAcmeAccountKeyPair().orElseGet(() -> { + KeyPair keyPair = KeyPairUtils.createKeyPair(KEY_SIZE); + configStore.storeAcmeUserKeyPair(keyPair); + return keyPair; + }); + } + + /** + * Retrieves an account that should already exist for the private key stored in the config store. + * @param acmeServerUrl The ACME server url. + * @return The registration object for the already registered account + */ + protected Registration getExistingAccount(String acmeServerUrl) { + KeyPair currentKeyPair = configStore.getAcmeAccountKeyPair().orElseThrow(() -> + new RuntimeException("There is no current ACME account key pair to rotate")); + + Session session = new Session(acmeServerUrl, currentKeyPair); + + Registration account = null; + try { + // Try to create a new Registration. + new RegistrationBuilder().create(session); + throw new RuntimeException("An account for the specified ACME user account private key does not exist"); + } catch (AcmeConflictException ex) { + account = Registration.bind(session, ex.getLocation()); + } catch (AcmeException e) { + log.error("Failed to lookup account", e); + } + + return account; + } + + /** + * Rotates the ACME account private key. + * @param acmeServerUrl The ACME server url. + */ + public void rotateAcmeAccountKeyPair(String acmeServerUrl) { + Registration account = getExistingAccount(acmeServerUrl); + + KeyPair newKeyPair = KeyPairUtils.createKeyPair(KEY_SIZE); + try { + account.changeKey(newKeyPair); + } catch (AcmeException e) { + throw new RuntimeException("Failed to rotate keypair on ACME account", e); + } + configStore.storeAcmeUserKeyPair(newKeyPair); + } + + /** + * Revokes a certificate from the ACME Provider. + * @param certificateName The certificate name of the cert, that was saved when the cert was generated + * @param acmeServerUrl The ACME server url. + */ + protected void revokeCertificate(String certificateName, String acmeServerUrl) { + String certificateContents = configStore.getCertPart(certificateName, CERT_PART_CERT).orElseThrow(() -> + new RuntimeException("Failed to download the requested cert")); + + X509Certificate certificateToRevoke; + try { + certificateToRevoke = CertificateUtils.readX509Certificate(new StringInputStream(certificateContents)); + } catch (IOException e) { + throw new RuntimeException("Failed to parse x509 cert", e); + } + + Registration account = getExistingAccount(acmeServerUrl); + try { + for (Iterator it = account.getCertificates(); it.hasNext(); ) { + Certificate certificate = it.next(); + + if (certificate.download().equals(certificateToRevoke)) { + certificate.revoke(RevocationReason.UNSPECIFIED); + break; + } + } + } catch (AcmeException e) { + throw new RuntimeException("Failed to revoke certificate", e); + } + } + + /** + * Generates the certs needed for a Cerberus Environment + * + * @param certDir The folder to store the generated files + * @param commonName The primary CNAME for the cert + * @param subjectAlternativeNames Additional CNAMEs for the cert + * @param hostedZoneId The hosted zone id that can be used to create txt records for the ACME ownership challenges + */ + public void generateCerts(File certDir, + String acmeServerUrl, + String commonName, + Set subjectAlternativeNames, + String hostedZoneId, + String contactEmail, + boolean autoAcceptTos) { + + try { + List names = new LinkedList<>(); + names.add(commonName); // the first entry counts as the common name + names.addAll(subjectAlternativeNames); + + Registration registration = findOrRegisterAccount(acmeServerUrl, contactEmail, autoAcceptTos); + + Map> domainChallengeCollectionMap = new HashMap<>(); + for (String name : names) { + Authorization authorization = registration.authorizeDomain(name); + domainChallengeCollectionMap.put(name, getChallenges(authorization)); + } + + ThreadPoolExecutor challengeExecutorService = (ThreadPoolExecutor) Executors.newFixedThreadPool(names.size()); + domainChallengeCollectionMap.forEach((name, challengeCollection) -> { + challengeExecutorService.execute(() -> + challengeCollection.forEach(challenge -> doChallenge(challenge, name, hostedZoneId))); + }); + + do { + log.info("Waiting for all challenges to complete before continuing, current active challenge threads: {}", + challengeExecutorService.getActiveCount()); + Thread.sleep(TimeUnit.SECONDS.toMillis(5)); + } while (challengeExecutorService.getActiveCount() > 0); + challengeExecutorService.shutdown(); + + KeyPair domainKeyPair = loadOrCreateKeyPair(new File(certDir.getAbsolutePath() + File.separator + DOMAIN_PKCS1_KEY_FILE)); + createPKCS8PrivateKeyPemFileFromKeyPair(domainKeyPair, certDir); + createPKCS1PublicKeyPem(domainKeyPair, certDir); + CSRBuilder csrb = new CSRBuilder(); + csrb.addDomains(names); + csrb.sign(domainKeyPair); + + try (Writer csrWriter = new FileWriter(new File(certDir.getAbsolutePath() + File.separator + DOMAIN_CSR_FILE))) { + csrb.write(csrWriter); + } + + // Now request a signed certificate. + Certificate certificate = registration.requestCertificate(csrb.getEncoded()); + + log.info("Success! The certificate has been generated!"); + log.info("Certificate URI: " + certificate.getLocation()); + + // Download the leaf certificate and certificate chain. + X509Certificate cert = certificate.download(); + X509Certificate[] chain = certificate.downloadChain(); + + // Write a combined file containing the certificate and chain. + try (FileWriter fullCertWriter = new FileWriter(new File(certDir.getAbsolutePath() + File.separator + DOMAIN_FULL_CERT_WITH_CHAIN_CRT_FILE))) { + CertificateUtils.writeX509CertificateChain(fullCertWriter, cert, chain); + } + // write just the cert + try (FileWriter certWriter = new FileWriter(new File(certDir.getAbsolutePath() + File.separator + DOMAIN_CERT_FILE))) { + CertificateUtils.writeX509Certificate(cert, certWriter); + } + // write just the chain + try (FileWriter chainWriter = new FileWriter(new File(certDir.getAbsolutePath() + File.separator + DOMAIN_CERT_CHAIN_FILE))) { + CertificateUtils.writeX509CertificateChain(chainWriter, null, chain); + } + + } catch (Exception e) { + throw new RuntimeException("Failed to generate certs", e); + } + } + + /** + * Takes a challenge and performs it + * + * @param challenge The challenge + * @param domainName The domain that is being verified + * @param hostedZone The hosted zone id if applicable + */ + protected void doChallenge(Challenge challenge, String domainName, String hostedZone) { + switch (challenge.getType()) { + case Dns01Challenge.TYPE: + doDns01Challenge((Dns01Challenge) challenge, domainName, hostedZone); + break; + default: + throw new RuntimeException("Unsupported challenge type: " + challenge.getType()); + } + } + + /** + * Attempts to get an acceptable challenge from the ACME provider. + *

+ * This CLI currently only supports DNS-01 Challenges + * + * @param authorization The authorization object from the ACME provider + * @return a Collection of challenges to complete. + */ + protected Collection getChallenges(Authorization authorization) { + List desiredChallengeCombos = ImmutableList.of( + new String[]{Dns01Challenge.TYPE} + ); + + for (String[] desiredChallengeCombo : desiredChallengeCombos) { + Collection challenges = authorization.findCombination(desiredChallengeCombo); + if (!challenges.isEmpty()) { + return challenges; + } + } + + String apiSupportedChallenges = new Gson().toJson(authorization.getCombinations()); + String cliSupportedMethods = new Gson().toJson(desiredChallengeCombos); + throw new RuntimeException("Failed to find a supportable combination of domain verification challenges. " + + "Supported challenge combos by the API: " + apiSupportedChallenges + + ", challenge combos supported by the CLI: " + cliSupportedMethods); + } + + /** + * Performs the ACME DNS 01 Challenge by creating txt records in Route 53 + * + * @param challenge The ACME challenge info with digest + * @param domainName The domain name that is being verified + * @param hostedZoneId The hosted zone id that has permissions to create dns records for the domain name + */ + protected void doDns01Challenge(Dns01Challenge challenge, String domainName, String hostedZoneId) { + String digest = challenge.getDigest(); + String name = String.format(CHALLENGE_ENTRY_TEMPLATE, domainName); + try { + executeRecordSetChanges(name, digest, hostedZoneId, ChangeAction.UPSERT); + Optional recordValue; + do { + log.info("Waiting for name: '{}' to have txt record digest value: '{}' before triggering challenge", name, digest); + Thread.sleep(TimeUnit.SECONDS.toMillis(60)); + recordValue = getTxtRecordValue(name); + } while (!recordValue.isPresent() || !recordValue.get().equals(String.format("\"%s\"", digest))); + } catch (InterruptedException e) { + throw new RuntimeException("Failed to complete DNS 01 challenge", e); + } + + triggerChallenge(challenge, 0); + + log.info("Deleting record: {}", name); + executeRecordSetChanges(name, digest, hostedZoneId, ChangeAction.DELETE); + } + + /** + * Triggers the ACME challenge polling for its status to be complete + * + * @param challenge The challenge that is ready to be triggered + */ + protected void triggerChallenge(Challenge challenge, int retryCount) { + try { + challenge.trigger(); + + // TODO better polling is possible, this can cause infinite loops + while (challenge.getStatus() != Status.VALID) { + log.info("Waiting for challenge to complete"); + Thread.sleep(3000); + challenge.update(); + } + log.info("challenge accepted"); + } catch (AcmeException | InterruptedException e) { + log.error("failed to trigger and wait for challenge to be accepted msg: {}", e.getMessage()); + // parallelizing challenges causes a race condition, retrying works past it an drastically improves overall time + if (e.getMessage().startsWith("JWS has invalid anti-replay nonce")) { + final int maxRetries = 10; + if (retryCount < maxRetries) { + log.info("Retrying {} out of {}", retryCount + 1, maxRetries); + triggerChallenge(challenge, retryCount + 1); + } + } + } + } + + protected void createPKCS1PublicKeyPem(KeyPair keyPair, File certDir) { + try { + FileWriter pubKeyWriter = new FileWriter(new File(certDir.getAbsolutePath() + File.separator + DOMAIN_PUBLIC_KEY_FILE)); + try (JcaPEMWriter jw = new JcaPEMWriter(pubKeyWriter)) { + jw.writeObject(keyPair.getPublic()); + } + } catch (IOException e) { + throw new RuntimeException("Failed to create pub key pem", e); + } + } + + /** + * Netty, which is what CMS is using requires the private key to be in PKCS8 format + * http://netty.io/wiki/sslcontextbuilder-and-private-key.html + */ + protected void createPKCS8PrivateKeyPemFileFromKeyPair(KeyPair keyPair, File certDir) { + try { + JcaPKCS8Generator pkcs8Generator = new JcaPKCS8Generator(keyPair.getPrivate(), null); + PemObject pemObject = pkcs8Generator.generate(); + FileWriter fileWriter = new FileWriter(new File(certDir.getAbsolutePath() + + File.separator + DOMAIN_PKCS8_KEY_FILE)); + try (JcaPEMWriter pemWriter = new JcaPEMWriter(fileWriter)) { + pemWriter.writeObject(pemObject); + } + } catch (Exception e) { + throw new RuntimeException("Failed to create PKCS8 private key pem", e); + } + } + + /** + * Uploads the required files to enable tls to identity management and s3 + * + * @param certDir The directory containing the required files + */ + public void uploadCertFiles(File certDir) { + + checkForRequiredFiles(certDir); + + final String caContents = getFileContents(certDir, DOMAIN_CERT_CHAIN_FILE); + final String certContents = getFileContents(certDir, DOMAIN_CERT_FILE); + final String keyContents = getFileContents(certDir, DOMAIN_PKCS1_KEY_FILE); + final String pkcs8KeyContents = getFileContents(certDir, DOMAIN_PKCS8_KEY_FILE); + final String pubKeyContents = getFileContents(certDir, DOMAIN_PUBLIC_KEY_FILE); + final String certificateName = String.format("cerberus_%s_%s", environmentName, uuidSupplier.get()); + + log.info("Uploading certificate files to IAM with name of {}.", certificateName); + String certificateId = identityManagementService.uploadServerCertificate(certificateName, getPath(), + certContents, caContents, keyContents); + log.info("Identity Management Cert Name: {}", certificateName); + + log.info("Sleeping to let iam cert become eventually consistent"); + try { + Thread.sleep(TimeUnit.MINUTES.toMillis(1)); + } catch (InterruptedException e) { + throw new RuntimeException("Failed to wait for iam cert to become eventually consistent"); + } + + log.info("Uploading certificate parts to the configuration bucket."); + X509Certificate certificate; + try { + certificate = CertificateUtils.readX509Certificate(new StringInputStream(certContents)); + } catch (IOException e) { + throw new RuntimeException("Failed to parse x509 cert", e); + } + + List sans = new LinkedList<>(); + try { + certificate.getSubjectAlternativeNames().forEach(o -> sans.add(o.get(1).toString())); + } catch (Exception e) { + log.error("Failed to parse subject alternative names from x509 cert"); + } + + CertificateInformation certificateInformation = CertificateInformation.Builder.create() + .withCertificateName(certificateName) + .withCertificateId(certificateId) + .withIdentityManagementCertificateArn(identityManagementService.getServerCertificateArn(certificateName).get()) + .withCommonName(StringUtils.removeStart(certificate.getSubjectX500Principal().getName(), "CN=")) + .withSubjectAlternateNames(sans) + .withNotBefore(new DateTime(certificate.getNotBefore(), DateTimeZone.UTC)) + .withNotAfter(new DateTime(certificate.getNotAfter(), DateTimeZone.UTC)) + .withUploaded(DateTime.now(DateTimeZone.UTC)) + .build(); + + configStore.storeCert(certificateInformation, caContents, certContents, keyContents, pkcs8KeyContents, pubKeyContents); + log.info("Successfully uploaded Certificate: {}", certificateInformation); + } + + /** + * Validates that the requires files to enable https for Cerberus are present see the constants at the top of this file. + * + * @param certDir The directory that contains the required files + */ + public void checkForRequiredFiles(File certDir) { + + final Set filenames = Sets.newHashSet(); + + final FilenameFilter filter = new RegexFileFilter("^.*\\.(pem|crt)$"); + final File[] files = certDir.listFiles(filter); + Arrays.stream(files).forEach(file -> filenames.add(file.getName())); + + if (!filenames.containsAll(EXPECTED_FILE_NAMES)) { + final StringJoiner sj = new StringJoiner(", ", "[", "]"); + EXPECTED_FILE_NAMES.forEach(sj::add); + throw new RuntimeException("Not all expected files are present! Expected: " + sj.toString()); + } + } + + /** + * Reads the data from the file for uploading + */ + private String getFileContents(final File certDir, final String filename) { + Preconditions.checkNotNull(certDir); + Preconditions.checkNotNull(filename); + + File file = new File(certDir.getAbsolutePath() + File.separator + filename); + if (file.exists() && file.canRead()) { + try { + return new String(Files.readAllBytes(file.toPath()), Charset.forName("UTF-8")); + } catch (IOException e) { + throw new IllegalStateException("Failed to read the following file: " + file.getAbsolutePath()); + } + } else { + throw new IllegalArgumentException("The file is not readable: " + file.getAbsolutePath()); + } + } + + private String getPath() { + return "/cerberus/" + environmentName + "/"; + } + + /** + * Deletes a certificate by name from S3 and from the identity management service and from the environment config + * + * @param certificateName The certificate to delete + * @param revokeCertificates Flag to revoke the certificate with an ACME server + * @param acmeServerUrl The ACME server to use when revoking a certificate + */ + public void deleteCertificate(String certificateName, boolean revokeCertificates, String acmeServerUrl) { + log.info("Attempting to delete certificate with name: {}", certificateName); + try { + identityManagementService.deleteServerCertificate(certificateName); + } catch (AmazonServiceException e) { + log.error("Failed to delete the certificate: {} from the identity management service," + + " you may need to manually delete. MSG: {}", certificateName, e.getMessage()); + } + + if (revokeCertificates) { + revokeCertificate(certificateName, acmeServerUrl); + } + + configStore.deleteCertificate(certificateName); + } +} diff --git a/src/main/java/com/nike/cerberus/service/CloudFormationService.java b/src/main/java/com/nike/cerberus/service/CloudFormationService.java index a1c3c1bf..5a91f7cc 100644 --- a/src/main/java/com/nike/cerberus/service/CloudFormationService.java +++ b/src/main/java/com/nike/cerberus/service/CloudFormationService.java @@ -17,7 +17,10 @@ package com.nike.cerberus.service; import com.amazonaws.AmazonServiceException; +import com.amazonaws.AmazonWebServiceRequest; +import com.amazonaws.regions.Regions; import com.amazonaws.services.cloudformation.AmazonCloudFormation; +import com.amazonaws.services.cloudformation.AmazonCloudFormationClient; import com.amazonaws.services.cloudformation.model.CreateStackRequest; import com.amazonaws.services.cloudformation.model.CreateStackResult; import com.amazonaws.services.cloudformation.model.DeleteStackRequest; @@ -25,20 +28,30 @@ import com.amazonaws.services.cloudformation.model.DescribeStackEventsResult; import com.amazonaws.services.cloudformation.model.DescribeStacksRequest; import com.amazonaws.services.cloudformation.model.DescribeStacksResult; +import com.amazonaws.services.cloudformation.model.ListStackResourcesRequest; +import com.amazonaws.services.cloudformation.model.ListStackResourcesResult; import com.amazonaws.services.cloudformation.model.Output; import com.amazonaws.services.cloudformation.model.Parameter; -import com.amazonaws.services.cloudformation.model.Stack; import com.amazonaws.services.cloudformation.model.StackEvent; +import com.amazonaws.services.cloudformation.model.StackResourceSummary; import com.amazonaws.services.cloudformation.model.StackStatus; +import com.amazonaws.services.cloudformation.model.Tag; +import com.amazonaws.services.cloudformation.model.TemplateParameter; import com.amazonaws.services.cloudformation.model.UpdateStackRequest; +import com.amazonaws.services.cloudformation.model.ValidateTemplateRequest; +import com.amazonaws.services.cloudformation.waiters.AmazonCloudFormationWaiters; +import com.amazonaws.waiters.Waiter; +import com.amazonaws.waiters.WaiterHandler; +import com.amazonaws.waiters.WaiterParameters; import com.beust.jcommander.internal.Maps; import com.github.tomaslanger.chalk.Chalk; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.nike.cerberus.ConfigConstants; -import com.nike.cerberus.domain.EnvironmentMetadata; -import org.apache.commons.io.IOUtils; +import com.nike.cerberus.domain.environment.Stack; +import com.nike.cerberus.operation.UnexpectedCloudFormationStatusException; +import com.nike.cerberus.store.ConfigStore; import org.apache.commons.lang3.StringUtils; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; @@ -47,138 +60,232 @@ import javax.annotation.Nullable; import javax.inject.Inject; -import java.io.IOException; -import java.io.InputStream; +import javax.inject.Named; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import static com.nike.cerberus.module.CerberusModule.ENV_NAME; + /** * Service wrapper for AWS CloudFormation. */ public class CloudFormationService { - private final Logger logger = LoggerFactory.getLogger(getClass()); + private final Logger log = LoggerFactory.getLogger(getClass()); public final static String AUTO_SCALING_GROUP_LOGICAL_ID_OUTPUT_KEY = "autoscalingGroupLogicalId"; public final static String MIN_INSTANCES_STACK_PARAMETER_KEY = "minimumInstances"; - private final AmazonCloudFormation cloudFormationClient; - private final EnvironmentMetadata environmentMetadata; + private final AwsClientFactory cloudFormationClientFactory; + + private final String environmentName; @Inject - public CloudFormationService(final AmazonCloudFormation cloudFormationClient, - final EnvironmentMetadata environmentMetadata) { - this.cloudFormationClient = cloudFormationClient; - this.environmentMetadata = environmentMetadata; + public CloudFormationService(AwsClientFactory cloudFormationClientFactory, + @Named(ENV_NAME) String environmentName) { + + this.cloudFormationClientFactory = cloudFormationClientFactory; + this.environmentName = environmentName; } /** - * Creates a new stack. + * Creates a new stack in the provided region. * - * @param name Stack name. - * @param parameters Input parameters. - * @param templatePath Classpath to the JSON template of the stack. + * @param region The region for the stack + * @param stack the stack that is being updated + * @param parameters the parameters to send to the cloud formation + * @param iamCapabilities flag for iam capabilities + * @param globalTags map of tags to apply to all resources created/updated * @return Stack ID */ - public String createStack(final String name, - final Map parameters, - final String templatePath, - final boolean iamCapabilities) { - logger.info(String.format("Executing the Cloud Formation: %s, Stack Name: %s", templatePath, name)); - - final CreateStackRequest request = new CreateStackRequest() - .withStackName(name) + public String createStackAndWait(Regions region, + Stack stack, + Map parameters, + boolean iamCapabilities, + Map globalTags) { + + String stackName = stack.getFullName(environmentName); + + log.info("Creating: Cloud Formation Template: {}, Stack Name: {}, Region: {}", stack.getTemplatePath(), stackName, region.getName()); + + CreateStackRequest request = new CreateStackRequest() + .withStackName(stack.getFullName(environmentName)) .withParameters(convertParameters(parameters)) - .withTemplateBody(getTemplateText(templatePath)); + .withTemplateBody(stack.getTemplateText()) + .withTags(getTags(globalTags)); if (iamCapabilities) { request.getCapabilities().add("CAPABILITY_IAM"); } - final CreateStackResult result = cloudFormationClient.createStack(request); + AmazonCloudFormation cloudFormationClient = cloudFormationClientFactory.getClient(region); + CreateStackResult result = cloudFormationClient.createStack(request); + waitAndPrintCFEvents(region, stackName, new AmazonCloudFormationWaiters(cloudFormationClient).stackCreateComplete()); + return result.getStackId(); } /** - * Updates an existing stack by name. + * Uses AWS CF Aync Waiters to wait for Cloud Formation actions to complete, while logging events and verifying success * - * @param stackId - * @param parameters - * @param iamCapabilities + * @param region The Region to use + * @param stackName The stack that is having an action performed + * @param waiter The Amazon waiter */ - public void updateStack(final String stackId, - final Map parameters, - final boolean iamCapabilities) { - updateStack(stackId, parameters, null, iamCapabilities); + private void waitAndPrintCFEvents(Regions region, String stackName, Waiter waiter) { + SuccessTrackingWaiterHandler handler = new SuccessTrackingWaiterHandler(); + + @SuppressWarnings("unchecked") + Future future = waiter + .runAsync(new WaiterParameters<>(new DescribeStacksRequest().withStackName(stackName)), handler); + + Set recordedStackEvents = Sets.newHashSet(); + do { + try { + DateTime now = DateTime.now(DateTimeZone.UTC).minusSeconds(10); + + TimeUnit.SECONDS.sleep(5); + + StackStatus stackStatus = getStackStatus(region, stackName); + if (stackStatus != null) { + List stackEvents = getStackEvents(region, stackName); + stackEvents.sort(Comparator.comparing(StackEvent::getTimestamp)); + + for (StackEvent stackEvent : stackEvents) { + DateTime eventTime = new DateTime(stackEvent.getTimestamp()); + if (!recordedStackEvents.contains(stackEvent.getEventId()) && now.isBefore(eventTime)) { + log.info( + String.format("TS: %s, Status: %s, Type: %s, Reason: %s", + Chalk.on(stackEvent.getTimestamp().toString()).yellow(), + getStatusColor(stackEvent.getResourceStatus()), + Chalk.on(stackEvent.getResourceType()).yellow(), + Chalk.on(stackEvent.getResourceStatusReason()).yellow())); + + recordedStackEvents.add(stackEvent.getEventId()); + } + } + } + } catch (InterruptedException e) { + log.error("Polling interrupted", e); + Thread.currentThread().interrupt(); + } catch (Exception e) { + log.error("Failed to poll and print stack", e); + } + } while (!future.isDone()); + + if (!handler.wasSuccess) { + throw new UnexpectedCloudFormationStatusException( + String.format("Failed to create stack: %s, msg: %s", stackName, handler.getErrorMessage())); + } } /** - * Updates an existing stack by name. - * - * @param stackId Stack ID. - * @param parameters Input parameters. - * @param templatePath Path to the JSON template of the stack. + * Updates an existing stack in the provided region + * @param region The region for the stack + * @param stack the stack that is being updated + * @param parameters the parameters to send to the cloud formation + * @param iamCapabilities flag for iam capabilities + * @param overwrite overwrite the deployed template with the current template in the cli + * @param globalTags map of tags to apply to all resources created/updated */ - public void updateStack(final String stackId, - final Map parameters, - final String templatePath, - final boolean iamCapabilities) { - final UpdateStackRequest request = new UpdateStackRequest() - .withStackName(stackId) - .withParameters(convertParameters(parameters)); - - if (StringUtils.isNotBlank(templatePath)) { - request.withTemplateBody(getTemplateText(templatePath)); + public void updateStackAndWait(Regions region, + Stack stack, + Map parameters, + boolean iamCapabilities, + boolean overwrite, + Map globalTags) { + + String stackName = stack.getFullName(environmentName); + + log.info("Updating: Cloud Formation Template: {}, Stack Name: {}, Region: {}", stack.getTemplatePath(), stackName, region.getName()); + + UpdateStackRequest request = new UpdateStackRequest() + .withStackName(stackName); + + if (overwrite) { + String template = stack.getTemplateText(); + request.withTemplateBody(template); + // filter out params that are no longer in the template + parameters.keySet().retainAll(validateTemplateAndRetrieveParameters(template, region)); } else { request.withUsePreviousTemplate(true); } + request.withParameters(convertParameters(parameters)); + if (iamCapabilities) { request.getCapabilities().add("CAPABILITY_IAM"); } + request.setTags(getTags(globalTags)); + + AmazonCloudFormation cloudFormationClient = cloudFormationClientFactory.getClient(region); cloudFormationClient.updateStack(request); + + waitAndPrintCFEvents(region, stackName, new AmazonCloudFormationWaiters(cloudFormationClient).stackUpdateComplete()); + } /** - * Deletes an existing stack by name. + * Validates and retrieves the set of parameter keys for a template. + * @param templateText The template to validate + * @param region The region that is being used + * @return The set of parameters that a template has. + */ + private Set validateTemplateAndRetrieveParameters(String templateText, Regions region) { + return cloudFormationClientFactory.getClient(region).validateTemplate( + new ValidateTemplateRequest().withTemplateBody(templateText) + ).getParameters().stream().map(TemplateParameter::getParameterKey).collect(Collectors.toSet()); + } + + /** + * Deletes an existing stack by name in the region provided. * - * @param stackId Stack ID. + * @param region The Region to use + * @param stackName Stack ID. */ - public void deleteStack(final String stackId) { - final DeleteStackRequest request = new DeleteStackRequest().withStackName(stackId); + public void deleteStackAndWait(Regions region, String stackName) { + log.info("Deleting the Stack Name: {}, Region: {}", stackName, region.getName()); + DeleteStackRequest request = new DeleteStackRequest().withStackName(stackName); + AmazonCloudFormation cloudFormationClient = cloudFormationClientFactory.getClient(region); cloudFormationClient.deleteStack(request); + waitAndPrintCFEvents(region, stackName, new AmazonCloudFormationWaiters(cloudFormationClient).stackDeleteComplete()); } /** * Returns the current status of the named stack. * - * @param stackId Stack ID. + * @param region The Region to use + * @param stackName Stack ID. * @return Stack status data. */ @Nullable - public StackStatus getStackStatus(final String stackId) { - final DescribeStacksRequest request = new DescribeStacksRequest().withStackName(stackId); + public StackStatus getStackStatus(Regions region, String stackName) { + AmazonCloudFormation cloudFormationClient = cloudFormationClientFactory.getClient(region); + DescribeStacksRequest request = new DescribeStacksRequest().withStackName(stackName); try { - final DescribeStacksResult result = cloudFormationClient.describeStacks(request); + DescribeStacksResult result = cloudFormationClient.describeStacks(request); if (result.getStacks().size() > 0) { - final String status = result.getStacks().get(0).getStackStatus(); + String status = result.getStacks().get(0).getStackStatus(); if (StringUtils.isNotBlank(status)) { return StackStatus.fromValue(status); } } - } catch (final AmazonServiceException ase) { + } catch (AmazonServiceException ase) { // Stack doesn't exist, just return with no status if (ase.getStatusCode() != 400) { throw ase; @@ -189,15 +296,17 @@ public StackStatus getStackStatus(final String stackId) { } /** - * Returns the current status of the named stack. + * Returns the current status of the named stack in the provided region. * - * @param stackId Stack name. + * @param region The Region to use + * @param stackName Stack name. * @return Stack outputs data. */ - public Map getStackParameters(final String stackId) { - final DescribeStacksRequest request = new DescribeStacksRequest().withStackName(stackId); - final DescribeStacksResult result = cloudFormationClient.describeStacks(request); - final Map parameters = Maps.newHashMap(); + public Map getStackParameters(Regions region, String stackName) { + AmazonCloudFormation cloudFormationClient = cloudFormationClientFactory.getClient(region); + DescribeStacksRequest request = new DescribeStacksRequest().withStackName(stackName); + DescribeStacksResult result = cloudFormationClient.describeStacks(request); + Map parameters = Maps.newHashMap(); if (result.getStacks().size() > 0) { parameters.putAll(result.getStacks().get(0).getParameters().stream().collect( @@ -211,13 +320,15 @@ public Map getStackParameters(final String stackId) { /** * Returns the current status of the named stack. * - * @param stackId Stack name. + * @param region The Region to use + * @param stackName Stack name. * @return Stack outputs data. */ - public Map getStackOutputs(final String stackId) { - final DescribeStacksRequest request = new DescribeStacksRequest().withStackName(stackId); - final DescribeStacksResult result = cloudFormationClient.describeStacks(request); - final Map outputs = Maps.newHashMap(); + public Map getStackOutputs(Regions region, String stackName) { + AmazonCloudFormation cloudFormationClient = cloudFormationClientFactory.getClient(region); + DescribeStacksRequest request = new DescribeStacksRequest().withStackName(stackName); + DescribeStacksResult result = cloudFormationClient.describeStacks(request); + Map outputs = Maps.newHashMap(); if (result.getStacks().size() > 0) { outputs.putAll(result.getStacks().get(0).getOutputs().stream().collect( @@ -228,19 +339,42 @@ public Map getStackOutputs(final String stackId) { return outputs; } + /** + * Returns list of StackResourceSummary for stack provided in region provided + * + * @param region The region to use + * @param stackName The stack name to use + * @return list of StackResourceSummary + */ + public List getStackResources(Regions region, String stackName) { + AmazonCloudFormation cloudFormationClient = cloudFormationClientFactory.getClient(region); + List stackResourceSummaries = new LinkedList<>(); + ListStackResourcesResult result; + do { + result = cloudFormationClient.listStackResources( + new ListStackResourcesRequest().withStackName(stackName) + ); + stackResourceSummaries.addAll(result.getStackResourceSummaries()); + } while (result.getNextToken() != null); + + return stackResourceSummaries; + } + /** * Returns the events for a named stack. * - * @param stackId Stack ID. + * @param stackName Stack ID. + * @param region The Region to use * @return Collection of events */ - public List getStackEvents(final String stackId) { - final DescribeStackEventsRequest request = new DescribeStackEventsRequest().withStackName(stackId); + public List getStackEvents(Regions region, String stackName) { + AmazonCloudFormation cloudFormationClient = cloudFormationClientFactory.getClient(region); + DescribeStackEventsRequest request = new DescribeStackEventsRequest().withStackName(stackName); try { - final DescribeStackEventsResult result = cloudFormationClient.describeStackEvents(request); + DescribeStackEventsResult result = cloudFormationClient.describeStackEvents(request); return result.getStackEvents(); - } catch (final AmazonServiceException ase) { + } catch (AmazonServiceException ase) { // Stack doesn't exist, just return with no status if (ase.getStatusCode() != 400) { throw ase; @@ -250,16 +384,44 @@ public List getStackEvents(final String stackId) { return Collections.emptyList(); } + /** + * Get stack id for stack in region provided. + * + * @param stackName The stack logical id / full stack name + * @param region The Region to use + * @return The full ID of the stack + */ + public String getStackId(Regions region, String stackName) { + AmazonCloudFormation cloudFormationClient = cloudFormationClientFactory.getClient(region); + Preconditions.checkArgument(StringUtils.isNotBlank(stackName), "Stack name cannot be blank"); + + DescribeStacksResult result = cloudFormationClient.describeStacks( + new DescribeStacksRequest() + .withStackName(stackName)); + + List stacks = result.getStacks(); + if (stacks.isEmpty()) { + throw new IllegalArgumentException("No stack found with name: " + stackName); + } else if (stacks.size() > 1) { + log.warn("Found more than stack with name: {}. Selecting the first one: stack id: {}", + stackName, + stacks.get(0).getStackId()); + } + + return stacks.get(0).getStackId(); + } + /** * Checks if a stack exists with the specified ID. * - * @param stackId Stack ID. + * @param region The Region to use + * @param stackName Stack ID. * @return boolean */ - public boolean isStackPresent(final String stackId) { - Preconditions.checkArgument(StringUtils.isNotBlank(stackId), "Stack ID can not be blank"); + public boolean isStackPresent(Regions region, String stackName) { + Preconditions.checkArgument(StringUtils.isNotBlank(stackName), "Stack ID cannot be blank"); - return getStackStatus(stackId) != null; + return getStackStatus(region, stackName) != null; } /** @@ -268,11 +430,11 @@ public boolean isStackPresent(final String stackId) { * @param parameterMap Map to be converted. * @return Collection of parameters. */ - public Collection convertParameters(final Map parameterMap) { - final List parameterList = Lists.newArrayListWithCapacity(parameterMap.size()); + public Collection convertParameters(Map parameterMap) { + List parameterList = Lists.newArrayListWithCapacity(parameterMap.size()); for (Map.Entry entry : parameterMap.entrySet()) { - final Parameter param = new Parameter() + Parameter param = new Parameter() .withParameterKey(entry.getKey()) .withParameterValue(entry.getValue()); parameterList.add(param); @@ -282,126 +444,58 @@ public Collection convertParameters(final Map paramet } /** - * Gets the template contents from the file on the classpath. + * Takes a map of key values and converts them to a collection of tags adding more global tags * - * @param templatePath Classpath for the template to be read - * @return Template contents + * @param globalTags a map of user supplied global tags + * @return a collection of user and global tags */ - public String getTemplateText(final String templatePath) { - final InputStream templateStream = getClass().getResourceAsStream(templatePath); - - if (templateStream == null) { - throw new IllegalStateException( - String.format("The CloudFormation JSON template doesn't exist on the classpath. path: %s", templatePath)); - } - - try { - return IOUtils.toString(templateStream, ConfigConstants.DEFAULT_ENCODING); - } catch (final IOException e) { - final String errorMessage = String.format("Unable to read input stream from %s", templatePath); - logger.error(errorMessage); - throw new RuntimeException(errorMessage, e); - } + private Collection getTags(Map globalTags) { + Set tags = new HashSet<>(); + globalTags.forEach((k, v) -> { + tags.add(new Tag().withKey(k).withValue(v)); + }); + + tags.add(new Tag() + .withKey("Name") + .withValue(ConfigConstants.ENV_PREFIX + environmentName) + ); + + return tags; } - /** - * Prepends the name with the environment name. ([environment]-[name]) - * - * @param name Custom stack name - * @return Formatted name with environment prepended - */ - public String getEnvStackName(final String name) { - return String.format("%s-%s", environmentMetadata.getName(), name); - } - - /** - * Since there doesn't appear to be a first class way through the SDK at this time to get a CF export. We can - * iterate through the stacks for a given output key and return the value. - * @param outputKey The exported CF variable to search and retrieve the value of. - * @return The value for the export if found - */ - public Optional searchStacksForOutput(String outputKey) { - DescribeStacksResult describeStacksResult = null; - do { - DescribeStacksRequest request = new DescribeStacksRequest(); - if (describeStacksResult != null && describeStacksResult.getNextToken() != null) { - request.withNextToken(describeStacksResult.getNextToken()); - } - describeStacksResult = cloudFormationClient.describeStacks(); - for (Stack stack : describeStacksResult.getStacks()) { - for (Output output : stack.getOutputs()) { - if (StringUtils.equals(output.getOutputKey(), outputKey)) { - return Optional.of(output.getOutputValue()); - } - } - } - - } while (describeStacksResult.getNextToken() != null); - - return Optional.empty(); + private String getStatusColor(String status) { + if (status.endsWith("PROGRESS")) { + return Chalk.on(status).yellow().toString(); + } else if (status.endsWith("COMPLETE")) { + return Chalk.on(status).green().bold().toString(); + } else if (status.endsWith("FAILED")) { + return Chalk.on(status).red().bold().toString(); + } + return status; } /** - * Blocking call that waits for a stack change to complete. Times out if waiting more than 30 minutes. - * - * @param endStatuses Status to end on - * @return The final status + * Waiter Handler that keeps track of status */ - public StackStatus waitForStatus(final String stackId, final HashSet endStatuses) { - DateTime now = DateTime.now(DateTimeZone.UTC).minusSeconds(10); - DateTime timeoutDateTime = now.plusMinutes(90); - Set recordedStackEvents = Sets.newHashSet(); - boolean isRunning = true; - StackStatus stackStatus = null; - - while (isRunning) { - try { - TimeUnit.SECONDS.sleep(5); - } catch (InterruptedException e) { - logger.warn("Thread sleep interrupted. Continuing...", e); - } - stackStatus = getStackStatus(stackId); + private static class SuccessTrackingWaiterHandler extends WaiterHandler { - if (endStatuses.contains(stackStatus) || stackStatus == null) { - isRunning = false; - } + boolean wasSuccess = false; - if (stackStatus != null) { - List stackEvents = getStackEvents(stackId); - stackEvents.sort((o1, o2) -> o1.getTimestamp().compareTo(o2.getTimestamp())); - - for (StackEvent stackEvent : stackEvents) { - DateTime eventTime = new DateTime(stackEvent.getTimestamp()); - if (!recordedStackEvents.contains(stackEvent.getEventId()) && now.isBefore(eventTime)) { - logger.info( - String.format("TS: %s, Status: %s, Type: %s, Reason: %s", - Chalk.on(stackEvent.getTimestamp().toString()).yellow(), - getStatusColor(stackEvent.getResourceStatus()), - Chalk.on(stackEvent.getResourceType()).yellow(), - Chalk.on(stackEvent.getResourceStatusReason()).yellow())); - - recordedStackEvents.add(stackEvent.getEventId()); - } - } - } + Exception e; - if (timeoutDateTime.isBeforeNow()) { - logger.error("Timed out waiting for CloudFormation completion status."); - isRunning = false; - } + @Override + public void onWaitSuccess(AmazonWebServiceRequest request) { + wasSuccess = true; } - return stackStatus; - } + @Override + public void onWaitFailure(Exception e) { + this.e = e; + wasSuccess = false; + } - private String getStatusColor(String status) { - if (status.endsWith("PROGRESS")) { - return Chalk.on(status).yellow().toString(); - } else if (status.endsWith("COMPLETE")) { - return Chalk.on(status).green().bold().toString(); - } else if (status.endsWith("FAILED")) { - return Chalk.on(status).red().bold().toString(); + public Object getErrorMessage() { + return e == null ? "unknown" : e.getMessage(); } - return status; } } diff --git a/src/main/java/com/nike/cerberus/service/ConsoleService.java b/src/main/java/com/nike/cerberus/service/ConsoleService.java index 31f7bac2..b54e615c 100644 --- a/src/main/java/com/nike/cerberus/service/ConsoleService.java +++ b/src/main/java/com/nike/cerberus/service/ConsoleService.java @@ -17,6 +17,7 @@ package com.nike.cerberus.service; import com.google.inject.Singleton; +import org.apache.commons.lang3.StringUtils; import java.io.BufferedReader; import java.io.IOException; @@ -45,4 +46,45 @@ public char[] readPassword(String format, Object... args) throws IOException { } return readLine(format, args).toCharArray(); } + + public void askUserToProceed(String additionalMessage, DefaultAction defaultAction) { + String userInput; + try { + StringBuilder sb = new StringBuilder(); + if (StringUtils.isNotBlank(additionalMessage)) { + sb.append(additionalMessage).append('\n'); + } + sb.append("Would you like to proceed? ") + .append(defaultAction.isYesDefault() ? "(Y/n)" : "(y/N)") + .append(": "); + userInput = readLine(sb.toString()); + } catch (IOException e) { + throw new RuntimeException("Failed to ask user to proceed", e); + } + + if (StringUtils.isBlank(userInput) && defaultAction.isYesDefault()) { + return; + } + + if (StringUtils.isNotBlank(userInput) && (userInput.equalsIgnoreCase("y") || userInput.equalsIgnoreCase("yes"))) { + return; + } + + throw new RuntimeException("User declined to proceed"); + } + + public enum DefaultAction { + YES(true), + NO(false); + + private boolean yesIsDefault; + + protected boolean isYesDefault() { + return yesIsDefault; + } + + DefaultAction(boolean yesIsDefault) { + this.yesIsDefault = yesIsDefault; + } + } } diff --git a/src/main/java/com/nike/cerberus/service/Ec2Service.java b/src/main/java/com/nike/cerberus/service/Ec2Service.java index f2476882..2a6adaab 100644 --- a/src/main/java/com/nike/cerberus/service/Ec2Service.java +++ b/src/main/java/com/nike/cerberus/service/Ec2Service.java @@ -17,11 +17,12 @@ package com.nike.cerberus.service; import com.amazonaws.AmazonServiceException; +import com.amazonaws.regions.Regions; import com.amazonaws.services.ec2.AmazonEC2; +import com.amazonaws.services.ec2.AmazonEC2Client; import com.amazonaws.services.ec2.model.AvailabilityZone; import com.amazonaws.services.ec2.model.AvailabilityZoneState; import com.amazonaws.services.ec2.model.DescribeAvailabilityZonesResult; -import com.amazonaws.services.ec2.model.DescribeInstanceStatusRequest; import com.amazonaws.services.ec2.model.DescribeInstancesRequest; import com.amazonaws.services.ec2.model.DescribeInstancesResult; import com.amazonaws.services.ec2.model.DescribeKeyPairsRequest; @@ -30,15 +31,14 @@ import com.amazonaws.services.ec2.model.ImportKeyPairRequest; import com.amazonaws.services.ec2.model.ImportKeyPairResult; import com.amazonaws.services.ec2.model.Instance; -import com.amazonaws.services.ec2.model.InstanceStatus; import com.amazonaws.services.ec2.model.RebootInstancesRequest; -import com.amazonaws.services.ec2.model.Reservation; import com.google.common.collect.Lists; import com.google.common.collect.Sets; +import com.nike.cerberus.store.ConfigStore; +import org.apache.commons.lang3.StringUtils; import javax.inject.Inject; import java.util.List; -import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -55,40 +55,70 @@ public class Ec2Service { protected final static String FILTER_NAME_TEMPL_FOR_EC2_TAGS = "tag:%s"; - private final AmazonEC2 ec2Client; + private final AwsClientFactory amazonEC2ClientFactory; + + private final ConfigStore configStore; @Inject - public Ec2Service(final AmazonEC2 ec2Client) { - this.ec2Client = ec2Client; + public Ec2Service(AwsClientFactory amazonEC2ClientFactory, + ConfigStore configStore) { + + this.amazonEC2ClientFactory = amazonEC2ClientFactory; + this.configStore = configStore; + } + + /** + * Import a key pair to AWS EC2 in the primary region. + * + * @param keyName Friendly name for the key + * @param publicKeyMaterial Public key + * @return Key name + */ + public String importKey(String keyName, String publicKeyMaterial) { + return importKey(configStore.getPrimaryRegion(), keyName, publicKeyMaterial); } /** * Import a key pair to AWS EC2. * - * @param keyName Friendly name for the key + * @param region The region to use. + * @param keyName Friendly name for the key * @param publicKeyMaterial Public key * @return Key name */ - public String importKey(final String keyName, final String publicKeyMaterial) { - final ImportKeyPairRequest request = new ImportKeyPairRequest(keyName, publicKeyMaterial); - final ImportKeyPairResult result = ec2Client.importKeyPair(request); + public String importKey(Regions region, String keyName, String publicKeyMaterial) { + AmazonEC2 ec2Client = amazonEC2ClientFactory.getClient(region); + ImportKeyPairRequest request = new ImportKeyPairRequest(keyName, publicKeyMaterial); + ImportKeyPairResult result = ec2Client.importKeyPair(request); return result.getKeyName(); } /** - * Checks if a key pair is present in AWS EC2. + * Checks if a key pair is present in AWS EC2 in the primary region * * @param keyName Friendly name for the key * @return If present */ - public boolean isKeyPairPresent(final String keyName) { - final DescribeKeyPairsRequest request = new DescribeKeyPairsRequest().withKeyNames(keyName); + public boolean isKeyPairPresent(String keyName) { + return isKeyPairPresent(configStore.getPrimaryRegion(), keyName); + } + + /** + * Checks if a key pair is present in AWS EC2 in the provided region + * + * @param region The region to use. + * @param keyName Friendly name for the key + * @return If present + */ + public boolean isKeyPairPresent(Regions region, String keyName) { + AmazonEC2 ec2Client = amazonEC2ClientFactory.getClient(region); + DescribeKeyPairsRequest request = new DescribeKeyPairsRequest().withKeyNames(keyName); try { - final DescribeKeyPairsResult result = ec2Client.describeKeyPairs(request); + DescribeKeyPairsResult result = ec2Client.describeKeyPairs(request); return result.getKeyPairs().size() > 0; - } catch (final AmazonServiceException ase) { - if (ase.getErrorCode() == "InvalidKeyPair.NotFound") { + } catch (AmazonServiceException ase) { + if (StringUtils.equals(ase.getErrorCode(),"InvalidKeyPair.NotFound")) { return false; } @@ -97,12 +127,23 @@ public boolean isKeyPairPresent(final String keyName) { } /** - * Determines all availability zones for a region that are marked as available. + * Determines all availability zones for a region that are marked as available in the primary region. * * @return List of availability zones */ public List getAvailabilityZones() { - final DescribeAvailabilityZonesResult result = ec2Client.describeAvailabilityZones(); + return getAvailabilityZones(configStore.getPrimaryRegion()); + } + + /** + * Determines all availability zones for a region that are marked as available, in the provided region. + * + * @param region The region to use. + * @return List of availability zones + */ + public List getAvailabilityZones(Regions region) { + AmazonEC2 ec2Client = amazonEC2ClientFactory.getClient(region); + DescribeAvailabilityZonesResult result = ec2Client.describeAvailabilityZones(); return result.getAvailabilityZones() .stream() @@ -110,20 +151,35 @@ public List getAvailabilityZones() { .map(AvailabilityZone::getZoneName).collect(Collectors.toList()); } + /** + * Gets all EC2 instances with the given tag key/value pair in the primary region + * + * @param tagKey - Key of the tag + * @param tagValue - Value of the tag + * @param filters - Array of EC2 filters + * @return - List of instances with the given tag + */ + public List getInstancesByTag(String tagKey, String tagValue, Filter... filters) { + return getInstancesByTag(configStore.getPrimaryRegion(), tagKey, tagValue, filters); + } + /** * Gets all EC2 instances with the given tag key/value pair - * @param tagKey - Key of the tag + * + * @param region The region to use. + * @param tagKey - Key of the tag * @param tagValue - Value of the tag - * @param filters - Array of EC2 filters + * @param filters - Array of EC2 filters * @return - List of instances with the given tag */ - public List getInstancesByTag(final String tagKey, final String tagValue, final Filter... filters) { - final String filterName = String.format(FILTER_NAME_TEMPL_FOR_EC2_TAGS, tagKey); - final Filter tagFilter = new Filter().withName(filterName).withValues(tagValue); + public List getInstancesByTag(Regions region, String tagKey, String tagValue, Filter... filters) { + AmazonEC2 ec2Client = amazonEC2ClientFactory.getClient(region); + String filterName = String.format(FILTER_NAME_TEMPL_FOR_EC2_TAGS, tagKey); + Filter tagFilter = new Filter().withName(filterName).withValues(tagValue); - final Set filterSet = Sets.newHashSet(filters); + Set filterSet = Sets.newHashSet(filters); filterSet.add(tagFilter); - final DescribeInstancesRequest request = new DescribeInstancesRequest().withFilters(filterSet); + DescribeInstancesRequest request = new DescribeInstancesRequest().withFilters(filterSet); DescribeInstancesResult result = ec2Client.describeInstances(request); List instances = Lists.newArrayList(); @@ -137,12 +193,22 @@ public List getInstancesByTag(final String tagKey, final String tagVal /** * Reboots the EC2 instance with the given ID + * * @param instanceId - EC2 instance ID */ - public void rebootEc2Instance(final String instanceId) { - - final RebootInstancesRequest request = new RebootInstancesRequest().withInstanceIds(instanceId); + public void rebootEc2Instance(String instanceId) { + rebootEc2Instance(configStore.getPrimaryRegion(), instanceId); + } + /** + * Reboots the EC2 instance with the given ID + * + * @param region The region to use. + * @param instanceId - EC2 instance ID + */ + public void rebootEc2Instance(Regions region, String instanceId) { + AmazonEC2 ec2Client = amazonEC2ClientFactory.getClient(region); + RebootInstancesRequest request = new RebootInstancesRequest().withInstanceIds(instanceId); ec2Client.rebootInstances(request); } diff --git a/src/main/java/com/nike/cerberus/service/Ec2UserDataService.java b/src/main/java/com/nike/cerberus/service/Ec2UserDataService.java index 78eb7b69..ecacc08c 100644 --- a/src/main/java/com/nike/cerberus/service/Ec2UserDataService.java +++ b/src/main/java/com/nike/cerberus/service/Ec2UserDataService.java @@ -16,91 +16,68 @@ package com.nike.cerberus.service; +import com.amazonaws.regions.Regions; import com.google.common.collect.Maps; import com.nike.cerberus.ConfigConstants; -import com.nike.cerberus.domain.EnvironmentMetadata; -import com.nike.cerberus.domain.environment.StackName; +import com.nike.cerberus.domain.environment.Stack; import com.nike.cerberus.store.ConfigStore; -import org.apache.commons.io.IOUtils; import javax.inject.Inject; -import java.io.IOException; +import javax.inject.Named; import java.nio.charset.Charset; import java.util.Base64; import java.util.Map; +import java.util.Optional; + +import static com.nike.cerberus.module.CerberusModule.ENV_NAME; /** * Service for generating EC2 user data for Cerberus instances. */ public class Ec2UserDataService { - private static final String nginxWriteResolverConfPath = "/write-nginx-resolver-conf"; - - private final EnvironmentMetadata environmentMetadata; - + private final String environmentName; private final ConfigStore configStore; @Inject - public Ec2UserDataService(final EnvironmentMetadata environmentMetadata, - final ConfigStore configStore) { - this.environmentMetadata = environmentMetadata; + public Ec2UserDataService(@Named(ENV_NAME) String environmentName, + ConfigStore configStore) { + + this.environmentName = environmentName; this.configStore = configStore; } - public String getUserData(final StackName stackName, final String ownerGroup) { - switch (stackName) { - case CMS: - return getCmsUserData(ownerGroup); - case GATEWAY: - return getGatewayUserData(ownerGroup); - case VAULT: - case CONSUL: - return getConsulAndVaultUserData(stackName, ownerGroup); - default: - throw new IllegalArgumentException("The stack specified does not support user data. stack: " - + stackName.getName()); + public String getUserData(Regions region, Stack stack, Optional group) { + if (stack.equals(Stack.CMS)) { + return getCmsUserData(region, group); + } else { + throw new IllegalArgumentException("The stack specified does not support user data. stack: " + + stack.getName()); } } - private String getGatewayUserData(final String ownerGroup) { - final Map userDataMap = Maps.newHashMap(); - addStandardEnvironmentVariables(userDataMap, StackName.GATEWAY.getName(), ownerGroup); - - final StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append(writeExportEnvVars(userDataMap)); - stringBuilder.append(getFileContentsFromClasspath(nginxWriteResolverConfPath)); - return encodeUserData(stringBuilder.toString()); - } - - private String getCmsUserData(final String ownerGroup) { - final Map userDataMap = Maps.newHashMap(); - addStandardEnvironmentVariables(userDataMap, StackName.CMS.getName(), ownerGroup); + private String getCmsUserData(Regions region, Optional group) { + Map userDataMap = Maps.newHashMap(); + addStandardEnvironmentVariables(region, userDataMap, Stack.CMS.getName(), group); return encodeUserData(writeExportEnvVars(userDataMap)); } - private String getConsulAndVaultUserData(final StackName stackName, final String ownerGroup) { - final Map userDataMap = Maps.newHashMap(); - addStandardEnvironmentVariables(userDataMap, stackName.getName(), ownerGroup); - - userDataMap.put("CONSUL_DC", ConfigConstants.CONSUL_DATACENTER); + private void addStandardEnvironmentVariables(Regions region, + Map userDataMap, + String appName, + Optional group) { - return encodeUserData(writeExportEnvVars(userDataMap)); - } - - private void addStandardEnvironmentVariables(final Map userDataMap, - final String appName, - final String ownerGroup) { - userDataMap.put("CLOUD_ENVIRONMENT", ConfigConstants.ENV_PREFIX + environmentMetadata.getName()); + userDataMap.put("CLOUD_ENVIRONMENT", ConfigConstants.ENV_PREFIX + environmentName); userDataMap.put("CLOUD_MONITOR_BUCKET", appName); userDataMap.put("CLOUD_APP", appName); - userDataMap.put("CLOUD_APP_GROUP", ownerGroup); + userDataMap.put("CLOUD_APP_GROUP", group.orElse("cerberus")); userDataMap.put("CLOUD_CLUSTER", appName); userDataMap.put("CLASSIFICATION", "Gold"); - userDataMap.put("EC2_REGION", environmentMetadata.getRegionName()); - userDataMap.put("AWS_REGION", environmentMetadata.getRegionName()); - userDataMap.put("CONFIG_S3_BUCKET", environmentMetadata.getBucketName()); - userDataMap.put("CONFIG_KEY_ID", configStore.getBaseStackOutputs().getConfigFileKeyId()); + userDataMap.put("EC2_REGION", region.getName()); + userDataMap.put("AWS_REGION", region.getName()); + userDataMap.put("CONFIG_S3_BUCKET", configStore.getConfigBucketForRegion(region)); + userDataMap.put("CONFIG_KEY_ID", configStore.getEnvironmentDataSecureDataKmsCmkRegion(region)); } private String writeExportEnvVars(Map userDataMap) { @@ -117,28 +94,4 @@ private String writeExportEnvVars(Map userDataMap) { private String encodeUserData(String userData) { return Base64.getEncoder().encodeToString(userData.getBytes(Charset.forName("UTF-8"))); } - - /** - * Removes the final '.' from the CNAME. - * - * @param cname The cname to convert - * @return The host derived from the CNAME - */ - private String cnameToHost(final String cname) { - return cname.substring(0, cname.length() - 1); - } - - /** - * Reads the contents of a file on the classpath. - * - * @param path Path to the resource - * @return Contents of resource - */ - private String getFileContentsFromClasspath(final String path) { - try { - return IOUtils.toString(getClass().getResourceAsStream(path), ConfigConstants.DEFAULT_ENCODING); - } catch (IOException e) { - throw new IllegalStateException("Unable to read file that should be in classpath!", e); - } - } } diff --git a/src/main/java/com/nike/cerberus/service/EncryptionService.java b/src/main/java/com/nike/cerberus/service/EncryptionService.java new file mode 100644 index 00000000..abe374ae --- /dev/null +++ b/src/main/java/com/nike/cerberus/service/EncryptionService.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.service; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.MasterKeyProvider; +import com.amazonaws.encryptionsdk.ParsedCiphertext; +import com.amazonaws.encryptionsdk.kms.KmsMasterKey; +import com.amazonaws.encryptionsdk.kms.KmsMasterKeyProvider; +import com.amazonaws.encryptionsdk.multi.MultipleProviderFactory; +import com.nike.cerberus.util.CiphertextUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.stream.Collectors; + +public class EncryptionService { + + private final Logger log = LoggerFactory.getLogger(getClass()); + + private final AwsCrypto awsCrypto; + + @Inject + public EncryptionService(AwsCrypto awsCrypto) { + this.awsCrypto = awsCrypto; + } + + /** + * Encrypt the plainTextPayload. + *

+ * Generates a Base64 encoded String the the 'AWS Encryption SDK Message Format' + *

+ * http://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/message-format.html + * + * @param plainTextPayload the secrets to encrypt + */ + public String encrypt(MasterKeyProvider encryptProvider, String plainTextPayload) { + return awsCrypto.encryptString(encryptProvider, plainTextPayload).getResult(); + } + + /** + * Decrypt the encryptedPayload. + *

+ * Expects a Base64 encoded String the the 'AWS Encryption SDK Message Format'. + *

+ * http://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/message-format.html + * + * @param encryptedPayload encryptedPayload + */ + public String decrypt(String encryptedPayload) { + ParsedCiphertext parsedCiphertext = CiphertextUtils.parse(encryptedPayload); + // Parses the ARNs out of the encryptedPayload so that you can manually rotate the CMKs, if desired + // Whatever CMKs were used in the encrypt operation will be used to decrypt + try { + List cmkArns = CiphertextUtils.getCustomerMasterKeyArns(parsedCiphertext); + MasterKeyProvider decryptProvider = initializeKeyProvider(cmkArns); + return new String(awsCrypto.decryptData(decryptProvider, parsedCiphertext).getResult(), StandardCharsets.UTF_8); + } catch (RuntimeException e) { + log.error("Decrypt operation failed " + CiphertextUtils.toJson(parsedCiphertext), e); + throw e; + } + } + + /** + * Initialize a Multi-KMS-MasterKeyProvider. + *

+ * For encrypt, KMS in all regions must be available. + * For decrypt, KMS in at least one region must be available. + */ + protected MasterKeyProvider initializeKeyProvider(List cmkArns) { + List> providers = cmkArns.stream() + .map(KmsMasterKeyProvider::new) + .collect(Collectors.toList()); + return (MasterKeyProvider) MultipleProviderFactory.buildMultiProvider(providers); + } +} diff --git a/src/main/java/com/nike/cerberus/service/IdentityManagementService.java b/src/main/java/com/nike/cerberus/service/IdentityManagementService.java index e148773a..4a3f8b8d 100644 --- a/src/main/java/com/nike/cerberus/service/IdentityManagementService.java +++ b/src/main/java/com/nike/cerberus/service/IdentityManagementService.java @@ -16,7 +16,9 @@ package com.nike.cerberus.service; +import com.amazonaws.regions.Regions; import com.amazonaws.services.identitymanagement.AmazonIdentityManagement; +import com.amazonaws.services.identitymanagement.AmazonIdentityManagementClient; import com.amazonaws.services.identitymanagement.model.DeleteServerCertificateRequest; import com.amazonaws.services.identitymanagement.model.GetServerCertificateRequest; import com.amazonaws.services.identitymanagement.model.GetServerCertificateResult; @@ -25,8 +27,11 @@ import com.amazonaws.services.identitymanagement.model.UploadServerCertificateResult; import javax.inject.Inject; +import javax.inject.Named; import java.util.Optional; +import static com.nike.cerberus.module.CerberusModule.CONFIG_REGION; + /** * Wrapper for AWS IAM. */ @@ -35,34 +40,37 @@ public class IdentityManagementService { private final AmazonIdentityManagement client; @Inject - public IdentityManagementService(final AmazonIdentityManagement client) { - this.client = client; + public IdentityManagementService(AwsClientFactory identityManagementClientFactory, + @Named(CONFIG_REGION) String configRegion) { + + // IAM is not region specific, the config region will suffice. + client = identityManagementClientFactory.getClient(Regions.fromName(configRegion)); } /** * Uploads a server certificate to AWS IAM. * - * @param name The server certificate name. No spaces. - * @param path Path to store the certificate under. - * @param body PEM-encoded certificate body. + * @param name The server certificate name. No spaces. + * @param path Path to store the certificate under. + * @param body PEM-encoded certificate body. * @param chain PEM-encoded certificate chain. - * @param key PEM-encoded certificate key. + * @param key PEM-encoded certificate key. * @return The server certificate ID of the uploaded certificate. */ - public String uploadServerCertificate( - final String name, - final String path, - final String body, - final String chain, - final String key) { - final UploadServerCertificateRequest request = new UploadServerCertificateRequest() + public String uploadServerCertificate(String name, + String path, + String body, + String chain, + String key) { + + UploadServerCertificateRequest request = new UploadServerCertificateRequest() .withServerCertificateName(name) .withPath(sanitizePath(path)) .withCertificateBody(body) .withCertificateChain(chain) .withPrivateKey(key); - final UploadServerCertificateResult result = client.uploadServerCertificate(request); + UploadServerCertificateResult result = client.uploadServerCertificate(request); return result.getServerCertificateMetadata().getServerCertificateId(); } @@ -72,54 +80,22 @@ public String uploadServerCertificate( * * @param name The server certificate name */ - public void deleteServerCertificate(final String name) { + public void deleteServerCertificate(String name) { client.deleteServerCertificate(new DeleteServerCertificateRequest().withServerCertificateName(name)); } - /** - * Checks if the server certificate is present. - * - * @param name The server certificate name - * @return If present - */ - public boolean isServerCertificatePresent(final String name) { - try { - client.getServerCertificate(new GetServerCertificateRequest().withServerCertificateName(name)); - return true; - } catch (final NoSuchEntityException nsee) { - return false; - } - } - /** * Gets the ARN for the specified server certificate name. * * @param name The server certificate name * @return ARN */ - public Optional getServerCertificateArn(final String name) { + public Optional getServerCertificateArn(String name) { try { - final GetServerCertificateResult serverCertificateResult = + GetServerCertificateResult serverCertificateResult = client.getServerCertificate(new GetServerCertificateRequest().withServerCertificateName(name)); return Optional.of(serverCertificateResult.getServerCertificate().getServerCertificateMetadata().getArn()); - } catch (final NoSuchEntityException nsee) { - return Optional.empty(); - } - } - - /** - * Gets the ID for the specified server certificate name. - * - * @param name The server certificate name - * @return ID - */ - public Optional getServerCertificateId(final String name) { - try { - final GetServerCertificateResult serverCertificateResult = - client.getServerCertificate(new GetServerCertificateRequest().withServerCertificateName(name)); - return Optional.of(serverCertificateResult.getServerCertificate() - .getServerCertificateMetadata().getServerCertificateId()); - } catch (final NoSuchEntityException nsee) { + } catch (NoSuchEntityException nsee) { return Optional.empty(); } } diff --git a/src/main/java/com/nike/cerberus/service/MetricsService.java b/src/main/java/com/nike/cerberus/service/MetricsService.java deleted file mode 100644 index 36e0b3ad..00000000 --- a/src/main/java/com/nike/cerberus/service/MetricsService.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (c) 2017 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.service; - -import com.amazonaws.services.sns.AmazonSNS; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.inject.Inject; -import com.nike.cerberus.store.ConfigStore; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Map; -import java.util.Optional; - -/** - * A Service for sending messages to the Cerberus Metrics SNS Topic, if the topic has been created. - */ -public class MetricsService { - - private final Logger log = LoggerFactory.getLogger(getClass()); - - private final AmazonSNS sns; - private final Optional metricsTopicArn; - private final ObjectMapper objectMapper; - - @Inject - public MetricsService(AmazonSNS sns, ObjectMapper objectMapper, ConfigStore configStore) { - this.sns = sns; - this.objectMapper = objectMapper; - - metricsTopicArn = configStore.getMetricsTopicArn(); - } - - public void trackMetric(MetricType metricType, String key, Integer value, Map dimensions) { - - log.info("Attempting to track metric, TYPE: {}, KEY: {}, VALUE: {}, DIMENSIONS: {}", - metricType, key, value, dimensions); - - if (! metricsTopicArn.isPresent()) { - log.warn("No metrics topic arn set, CLI will not track metric"); - return; - } - - CerberusMetricMessage message = new CerberusMetricMessage() - .setMetricType(metricType.toString()) - .setMetricKey(key) - .setMetricValue(value) - .setDimensions(dimensions); - - try { - sns.publish(metricsTopicArn.get(), objectMapper.writeValueAsString(message)); - } catch (Throwable t) { - log.error("Failed to track metric", t); - } - } - - public void trackGauge(String key, Integer value, Map dimensions) { - trackMetric(MetricType.GAUGE, key, value, dimensions); - } - - public void trackCounter(String key, Integer value, Map dimensions) { - trackMetric(MetricType.COUNTER, key, value, dimensions); - } - - enum MetricType { - GAUGE("gauge"), - COUNTER("counter"); - - private final String stringValue; - - MetricType(String stringValue) { - this.stringValue = stringValue; - } - - @Override - public String toString() { - return stringValue; - } - } - - private static class CerberusMetricMessage { - private String metricKey; - private Integer metricValue; - private String metricType; - private Map dimensions; - - public String getMetricKey() { - return metricKey; - } - - public CerberusMetricMessage setMetricKey(String metricKey) { - this.metricKey = metricKey; - return this; - } - - public Integer getMetricValue() { - return metricValue; - } - - public CerberusMetricMessage setMetricValue(Integer metricValue) { - this.metricValue = metricValue; - return this; - } - - public String getMetricType() { - return metricType; - } - - public CerberusMetricMessage setMetricType(String metricType) { - this.metricType = metricType; - return this; - } - - public Map getDimensions() { - return dimensions; - } - - public CerberusMetricMessage setDimensions(Map dimensions) { - this.dimensions = dimensions; - return this; - } - } - -} diff --git a/src/main/java/com/nike/cerberus/service/RdsService.java b/src/main/java/com/nike/cerberus/service/RdsService.java new file mode 100644 index 00000000..7cab8191 --- /dev/null +++ b/src/main/java/com/nike/cerberus/service/RdsService.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2018 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.service; + +import com.amazonaws.regions.Regions; +import com.amazonaws.services.rds.AmazonRDS; +import com.amazonaws.services.rds.AmazonRDSClient; +import com.amazonaws.services.rds.model.CopyDBClusterSnapshotRequest; +import com.amazonaws.services.rds.model.DBClusterSnapshot; +import com.amazonaws.services.rds.model.DeleteDBClusterSnapshotRequest; +import com.amazonaws.services.rds.model.DescribeDBClusterSnapshotsRequest; +import com.amazonaws.services.rds.model.DescribeDBClusterSnapshotsResult; +import com.amazonaws.services.rds.model.Tag; +import com.nike.cerberus.store.ConfigStore; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javax.inject.Named; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.LinkedList; +import java.util.List; + +import static com.nike.cerberus.module.CerberusModule.ENV_NAME; + +public class RdsService { + + private final Logger log = LoggerFactory.getLogger(getClass()); + + private final AwsClientFactory amazonRDSClientFactory; + private final ConfigStore configStore; + private final String environmentName; + + @Inject + public RdsService(AwsClientFactory amazonRDSClientFactory, + ConfigStore configStore, + @Named(ENV_NAME) String environmentName) { + + this.amazonRDSClientFactory = amazonRDSClientFactory; + this.configStore = configStore; + this.environmentName = environmentName; + } + + /** + * Copies a snapshot from one region to another + * @param fromSnapshot The snapshot that is getting copied + * @param fromRegion The from region + * @param toRegion The to region + */ + public void copySnapshot(DBClusterSnapshot fromSnapshot, Regions fromRegion, Regions toRegion) { + AmazonRDS rds = amazonRDSClientFactory.getClient(toRegion); + rds.copyDBClusterSnapshot(new CopyDBClusterSnapshotRequest() + .withCopyTags(true) + .withSourceDBClusterSnapshotIdentifier(fromSnapshot.getDBClusterSnapshotArn()) + .withTargetDBClusterSnapshotIdentifier(getIdentifier(fromSnapshot)) + .withSourceRegion(fromRegion.getName()) + .withKmsKeyId(configStore.getEnvironmentData().getRegionData().get(toRegion).getConfigCmkArn().get()) + .withTags( + new Tag().withKey("created_by").withValue("cerberus_cli") + ) + ); + } + + /** + * Strips the rds: that aws prepends to automatic snapshots + * @param dbSnapshot the snapshot + * @return a string that can be used to identify automatic and manual copies rds:foo will return foo + */ + public String getIdentifier(DBClusterSnapshot dbSnapshot) { + return dbSnapshot.getDBClusterSnapshotIdentifier().replace("rds:", ""); + } + + /** + * @return True if the snapshot was created for the cms database cluster for this environment + */ + public boolean wasSnapshotGeneratedFromCmsCluster(DBClusterSnapshot dbSnapshot) { + String identifier = getIdentifier(dbSnapshot); + return identifier.startsWith(environmentName) && identifier.contains("cmsdatabasecluster"); + } + + /** + * @param dbSnapshot The snapshot under question + * @param days How many days to compare to + * @return True if the snapshot under question is newer than the days passed in + */ + public boolean isSnapshotNewerThanGivenDays(DBClusterSnapshot dbSnapshot, long days) { + return ZonedDateTime.ofInstant(dbSnapshot.getSnapshotCreateTime().toInstant(), ZoneId.of("UTC")) + .isAfter(Instant.now().atZone(ZoneId.of("UTC")).minus(days, ChronoUnit.DAYS)); + } + + /** + * @return All the RDS Cluster Snapshots for a region + */ + public List getDbSnapshots(Regions region) { + AmazonRDS rds = amazonRDSClientFactory.getClient(region); + List cmsDatabaseClusterSnapshots = new LinkedList<>(); + String next = null; + do { + DescribeDBClusterSnapshotsRequest req = new DescribeDBClusterSnapshotsRequest(); + + if (next != null) { + req.withMarker(next); + } + + DescribeDBClusterSnapshotsResult res = rds.describeDBClusterSnapshots(req); + + cmsDatabaseClusterSnapshots.addAll(res.getDBClusterSnapshots()); + next = res.getMarker(); + } while (next != null); + + return cmsDatabaseClusterSnapshots; + } + + /** + * Deletes a RDS DB Cluster Snapshot in a given region. + * + * @param snapshot The snapshot to delete + * @param region The region that the snapshot is in + */ + public void deleteSnapshot(DBClusterSnapshot snapshot, Regions region) { + log.info("preparing to delete snapshot: {} with creation date: {} in region: {}", + snapshot.getDBClusterSnapshotIdentifier(), snapshot.getSnapshotCreateTime(), region); + + amazonRDSClientFactory.getClient(region).deleteDBClusterSnapshot(new DeleteDBClusterSnapshotRequest() + .withDBClusterSnapshotIdentifier(snapshot.getDBClusterSnapshotIdentifier())); + } +} diff --git a/src/main/java/com/nike/cerberus/service/Route53Service.java b/src/main/java/com/nike/cerberus/service/Route53Service.java new file mode 100644 index 00000000..315ba04b --- /dev/null +++ b/src/main/java/com/nike/cerberus/service/Route53Service.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.service; + +import com.amazonaws.regions.Regions; +import com.amazonaws.services.route53.AmazonRoute53; +import com.amazonaws.services.route53.AmazonRoute53Client; +import com.amazonaws.services.route53.model.Change; +import com.amazonaws.services.route53.model.ChangeAction; +import com.amazonaws.services.route53.model.ChangeBatch; +import com.amazonaws.services.route53.model.ChangeResourceRecordSetsRequest; +import com.amazonaws.services.route53.model.ListResourceRecordSetsRequest; +import com.amazonaws.services.route53.model.ListResourceRecordSetsResult; +import com.amazonaws.services.route53.model.RRType; +import com.amazonaws.services.route53.model.ResourceRecord; +import com.amazonaws.services.route53.model.ResourceRecordSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javax.inject.Named; +import java.util.Optional; + +import static com.nike.cerberus.module.CerberusModule.CONFIG_REGION; + +/** + * Service wrapper for AWS CloudFormation. + */ +public class Route53Service { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private final AmazonRoute53 route53Client; + + @Inject + public Route53Service(AwsClientFactory route53ClientFactory, + @Named(CONFIG_REGION) String configRegion) { + + // not region specific config region works + this.route53Client = route53ClientFactory.getClient(Regions.fromName(configRegion)); + } + + public void createRoute53RecordSet(String hostedZoneId, + String recordSetName, + String recordValue, + RRType recordSetType, + String resourceRecordTtl) { + logger.info("Creating Route53 record name: {}, value: {}", recordSetName, recordValue); + + ResourceRecord record = new ResourceRecord().withValue(recordValue); + ResourceRecordSet recordSet = new ResourceRecordSet() + .withResourceRecords(record) + .withName(recordSetName) + .withType(recordSetType) + .withTTL(Long.parseLong(resourceRecordTtl)); + + ChangeBatch recordSetChangeBatch = new ChangeBatch() + .withChanges(new Change() + .withAction(ChangeAction.UPSERT) + .withResourceRecordSet(recordSet)); + + route53Client.changeResourceRecordSets(new ChangeResourceRecordSetsRequest() + .withHostedZoneId(hostedZoneId) + .withChangeBatch(recordSetChangeBatch)); + } + + public Optional getRecordSetByName(String recordSetName, String hostedZoneId) { + ListResourceRecordSetsResult recordSets = route53Client.listResourceRecordSets( + new ListResourceRecordSetsRequest() + .withHostedZoneId(hostedZoneId)); + + for (ResourceRecordSet recordSet : recordSets.getResourceRecordSets()) { + if (recordSet.getName().equals(recordSetName + ".")) { + return Optional.of(recordSet); + } + } + + return Optional.empty(); + } +} diff --git a/src/main/java/com/nike/cerberus/service/S3StoreService.java b/src/main/java/com/nike/cerberus/service/S3StoreService.java index 86f85fd8..a9564a82 100644 --- a/src/main/java/com/nike/cerberus/service/S3StoreService.java +++ b/src/main/java/com/nike/cerberus/service/S3StoreService.java @@ -74,7 +74,7 @@ public void put(String path, String value) { public Optional get(String path) { Optional s3ObjectOptional = getS3Object(path); - if (! s3ObjectOptional.isPresent()) { + if (!s3ObjectOptional.isPresent()) { return Optional.empty(); } @@ -94,8 +94,7 @@ protected Optional getS3Object(String path) { GetObjectRequest request = new GetObjectRequest(s3Bucket, getFullPath(path)); try { return Optional.of(s3Client.getObject(request)); - } - catch (AmazonServiceException ase) { + } catch (AmazonServiceException ase) { if (StringUtils.equalsIgnoreCase(ase.getErrorCode(), "NoSuchKey")) { logger.debug(String.format("The S3 object doesn't exist. Bucket: %s, Key: %s", s3Bucket, request.getKey())); return Optional.empty(); @@ -108,7 +107,7 @@ protected Optional getS3Object(String path) { public Map getS3ObjectUserMetaData(String path) { Optional encryptedObjectOptional = getS3Object(path); - if (! encryptedObjectOptional.isPresent()) { + if (!encryptedObjectOptional.isPresent()) { throw new RuntimeException("Cannot get metadata for an S3 Object that is not present"); } S3Object s3Object = encryptedObjectOptional.get(); @@ -119,10 +118,10 @@ public Set getKeysInPartialPath(String path) { ObjectListing objectListing = s3Client.listObjects(s3Bucket, getFullPath(path)); return objectListing - .getObjectSummaries() - .stream() - .map(objectSummary -> StringUtils.stripStart(objectSummary.getKey(), s3Prefix + "/")) - .collect(Collectors.toSet()); + .getObjectSummaries() + .stream() + .map(objectSummary -> StringUtils.stripStart(objectSummary.getKey(), s3Prefix + "/")) + .collect(Collectors.toSet()); } /** diff --git a/src/main/java/com/nike/cerberus/service/SaltGenerator.java b/src/main/java/com/nike/cerberus/service/SaltGenerator.java new file mode 100644 index 00000000..72d98bac --- /dev/null +++ b/src/main/java/com/nike/cerberus/service/SaltGenerator.java @@ -0,0 +1,26 @@ +package com.nike.cerberus.service; + +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Base64; +import java.util.Random; + +/** + * Generate a Salt + */ +public class SaltGenerator { + + private Random random = SecureRandom.getInstanceStrong(); + + public SaltGenerator() throws NoSuchAlgorithmException { + } + + /** + * Generate a Salt as a Base64 encoded String we can store in a properties file + */ + public String generateSalt() { + byte[] salt = new byte[64]; + random.nextBytes(salt); + return Base64.getEncoder().encodeToString(salt); + } +} diff --git a/src/main/java/com/nike/cerberus/service/UnexpectedDataEncodingException.java b/src/main/java/com/nike/cerberus/service/UnexpectedDataEncodingException.java index 700b0fc1..ad1e1dde 100644 --- a/src/main/java/com/nike/cerberus/service/UnexpectedDataEncodingException.java +++ b/src/main/java/com/nike/cerberus/service/UnexpectedDataEncodingException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Nike, Inc. + * Copyright (c) 2017 Nike, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/nike/cerberus/store/ConfigStore.java b/src/main/java/com/nike/cerberus/store/ConfigStore.java index 353e4ab6..6f34cf83 100644 --- a/src/main/java/com/nike/cerberus/store/ConfigStore.java +++ b/src/main/java/com/nike/cerberus/store/ConfigStore.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Nike, Inc. + * Copyright (c) 2017 Nike, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,532 +16,256 @@ package com.nike.cerberus.store; -import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; -import com.amazonaws.regions.Region; -import com.amazonaws.services.s3.AmazonS3; -import com.amazonaws.services.s3.AmazonS3EncryptionClient; -import com.amazonaws.services.s3.model.CryptoConfiguration; -import com.amazonaws.services.s3.model.KMSEncryptionMaterialsProvider; +import com.amazonaws.encryptionsdk.MasterKeyProvider; +import com.amazonaws.encryptionsdk.kms.KmsMasterKey; +import com.amazonaws.encryptionsdk.kms.KmsMasterKeyProvider; +import com.amazonaws.encryptionsdk.multi.MultipleProviderFactory; +import com.amazonaws.regions.Regions; +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.model.AmazonS3Exception; +import com.amazonaws.services.s3.model.Bucket; import com.amazonaws.services.securitytoken.AWSSecurityTokenService; +import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClient; import com.amazonaws.services.securitytoken.model.GetCallerIdentityRequest; import com.amazonaws.services.securitytoken.model.GetCallerIdentityResult; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.tomaslanger.chalk.Chalk; import com.nike.cerberus.ConfigConstants; -import com.nike.cerberus.domain.EnvironmentMetadata; -import com.nike.cerberus.domain.cloudformation.BaseOutputs; -import com.nike.cerberus.domain.cloudformation.BaseParameters; -import com.nike.cerberus.domain.cloudformation.CmsOutputs; -import com.nike.cerberus.domain.cloudformation.CmsParameters; -import com.nike.cerberus.domain.cloudformation.ConsulOutputs; -import com.nike.cerberus.domain.cloudformation.ConsulParameters; -import com.nike.cerberus.domain.cloudformation.GatewayOutputs; -import com.nike.cerberus.domain.cloudformation.GatewayParameters; -import com.nike.cerberus.domain.cloudformation.VaultOutputs; -import com.nike.cerberus.domain.cloudformation.VaultParameters; -import com.nike.cerberus.domain.configuration.ConsulConfiguration; -import com.nike.cerberus.domain.configuration.GatewayConfiguration; -import com.nike.cerberus.domain.configuration.VaultAclEntry; -import com.nike.cerberus.domain.configuration.VaultConfiguration; -import com.nike.cerberus.domain.environment.BackupRegionInfo; -import com.nike.cerberus.domain.environment.Environment; -import com.nike.cerberus.domain.environment.Secrets; -import com.nike.cerberus.domain.environment.StackName; -import com.nike.cerberus.domain.environment.CloudFrontLogProcessingLambdaConfig; +import com.nike.cerberus.domain.cloudformation.IamRolesOutputs; +import com.nike.cerberus.domain.cloudformation.DatabaseOutputs; +import com.nike.cerberus.domain.cloudformation.ConfigOutputs; +import com.nike.cerberus.domain.cloudformation.Route53Outputs; +import com.nike.cerberus.domain.cloudformation.SecurityGroupOutputs; +import com.nike.cerberus.domain.cloudformation.VpcOutputs; +import com.nike.cerberus.domain.cloudformation.VpcParameters; +import com.nike.cerberus.domain.environment.EnvironmentData; +import com.nike.cerberus.domain.environment.CertificateInformation; +import com.nike.cerberus.domain.environment.RegionData; +import com.nike.cerberus.domain.environment.Stack; +import com.nike.cerberus.service.AwsClientFactory; import com.nike.cerberus.service.CloudFormationService; -import com.nike.cerberus.service.IdentityManagementService; +import com.nike.cerberus.service.EncryptionService; import com.nike.cerberus.service.S3StoreService; +import com.nike.cerberus.service.SaltGenerator; import com.nike.cerberus.service.StoreService; +import com.nike.cerberus.util.CloudFormationObjectMapper; import org.apache.commons.lang3.StringUtils; +import org.bouncycastle.openssl.jcajce.JcaPEMWriter; +import org.shredzone.acme4j.util.KeyPairUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Named; +import javax.inject.Singleton; import java.io.IOException; import java.io.StringReader; +import java.io.StringWriter; +import java.security.KeyPair; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Properties; import java.util.Set; +import java.util.LinkedList; +import java.util.stream.Collectors; -import static com.nike.cerberus.ConfigConstants.ADMIN_ROLE_ARN_KEY; -import static com.nike.cerberus.ConfigConstants.CERT_PART_CA; -import static com.nike.cerberus.ConfigConstants.CERT_PART_CERT; -import static com.nike.cerberus.ConfigConstants.CERT_PART_KEY; -import static com.nike.cerberus.ConfigConstants.CERT_PART_PUBKEY; -import static com.nike.cerberus.ConfigConstants.CMS_ROLE_ARN_KEY; -import static com.nike.cerberus.ConfigConstants.JDBC_PASSWORD_KEY; -import static com.nike.cerberus.ConfigConstants.JDBC_URL_KEY; -import static com.nike.cerberus.ConfigConstants.JDBC_USERNAME_KEY; -import static com.nike.cerberus.ConfigConstants.ROOT_USER_ARN_KEY; -import static com.nike.cerberus.ConfigConstants.SYSTEM_CONFIGURED_CMS_PROPERTIES; -import static com.nike.cerberus.ConfigConstants.VAULT_ADDR_KEY; -import static com.nike.cerberus.ConfigConstants.VAULT_TOKEN_KEY; -import static com.nike.cerberus.module.CerberusModule.CF_OBJECT_MAPPER; +import static com.nike.cerberus.ConfigConstants.*; import static com.nike.cerberus.module.CerberusModule.CONFIG_OBJECT_MAPPER; +import static com.nike.cerberus.module.CerberusModule.CONFIG_REGION; +import static com.nike.cerberus.module.CerberusModule.ENV_NAME; /** - * Abstraction for accessing the configuration storage bucket. + * Abstraction for accessing the configuration storage bucket and the environment state stored in it. */ +@Singleton public class ConfigStore { private final Logger logger = LoggerFactory.getLogger(getClass()); - private static final String CERBERUS_METRICS_TOPIC_ARN_STACK_OUTPUT_KEY = "CerberusMetricsTopicArn"; + private final AwsClientFactory amazonS3ClientFactory; private final CloudFormationService cloudFormationService; private final ObjectMapper configObjectMapper; - private final ObjectMapper cloudFormationObjectMapper; + private final CloudFormationObjectMapper cloudFormationObjectMapper; - private final IdentityManagementService iamService; + private final SaltGenerator saltGenerator; - private final AmazonS3 s3Client; + private final AwsClientFactory securityTokenServiceFactory; - private final EnvironmentMetadata environmentMetadata; + private final String environmentName; - private final Object envDataLock = new Object(); + private final Regions configRegion; - private final Object secretsDataLock = new Object(); + private final EncryptionService encryptionService; - private StoreService encryptedConfigStoreService; - - private StoreService configStoreService; - - private AWSSecurityTokenService securityTokenService; + private Map storeServiceMap = new HashMap<>(); @Inject - public ConfigStore(final AmazonS3 s3Client, - final CloudFormationService cloudFormationService, - final IdentityManagementService iamService, - final AWSSecurityTokenService securityTokenService, - final EnvironmentMetadata environmentMetadata, - @Named(CONFIG_OBJECT_MAPPER) final ObjectMapper configObjectMapper, - @Named(CF_OBJECT_MAPPER) final ObjectMapper cloudFormationObjectMapper) { + public ConfigStore(AwsClientFactory amazonS3ClientFactory, + CloudFormationService cloudFormationService, + AwsClientFactory securityTokenServiceFactory, + SaltGenerator saltGenerator, + @Named(CONFIG_OBJECT_MAPPER) ObjectMapper configObjectMapper, + CloudFormationObjectMapper cloudFormationObjectMapper, + @Named(ENV_NAME) String environmentName, + @Named(CONFIG_REGION) String configRegion, + EncryptionService encryptionService) { this.cloudFormationService = cloudFormationService; - this.iamService = iamService; this.configObjectMapper = configObjectMapper; this.cloudFormationObjectMapper = cloudFormationObjectMapper; - this.s3Client = s3Client; - this.environmentMetadata = environmentMetadata; - this.securityTokenService = securityTokenService; - } - - /** - * Writes the AZs to the configuration store. This is a one time operation. Subsequent calls will result in - * an {@link IllegalStateException} being thrown. - * - * @param az1 AZ 1 - * @param az2 AZ 2 - * @param az3 AZ 3 - */ - public void storeAzs(final String az1, final String az2, final String az3) { - synchronized (envDataLock) { - final Environment environment = getEnvironmentData(); - - if (StringUtils.isNotBlank(environment.getAz1()) - || StringUtils.isNotBlank(environment.getAz2()) - || StringUtils.isNotBlank(environment.getAz3())) { - throw new IllegalStateException("AZs are already defined for this environment! Aborting save..."); - } - - environment.setAz1(az1); - environment.setAz2(az2); - environment.setAz3(az3); - saveEnvironmentData(environment); - } - } - - /** - * Stores the stack ID for a specific component. - * - * @param stackName Stack component - * @param stackId Stack ID - */ - public void storeStackId(final StackName stackName, final String stackId) { - synchronized (envDataLock) { - final Environment environment = getEnvironmentData(); - environment.getStackMap().put(stackName, stackId); - saveEnvironmentData(environment); - } - } - - /** - * Get the specific stack ID by component name. - * - * @param stackName Stack name - * @return Stack ID - */ - public String getStackId(final StackName stackName) { - synchronized (envDataLock) { - final Environment environment = getEnvironmentData(); - return environment.getStackMap().get(stackName); - } - } - - /** - * Gets the server certificate name from the config store. - * - * @param stackName Stack name - */ - public String getServerCertificateName(final StackName stackName) { - synchronized (envDataLock) { - final Environment environment = getEnvironmentData(); - return environment.getServerCertificateIdMap().get(stackName); - } - } - - /** - * Gets the server certificate ARN for the stack name. - * - * @param stackName Stack name - * @return ARN - */ - public Optional getServerCertificateArn(final StackName stackName) { - final String certificateName = getServerCertificateName(stackName); - return iamService.getServerCertificateArn(certificateName); - } - - public Optional getServerCertificateId(final StackName stackName) { - final String certificateName = getServerCertificateName(stackName); - return iamService.getServerCertificateId(certificateName); - } - - /** - * Stores the KMS key ID used to encrypt files in the config bucket. This is a one time operation that will result - * in {@link IllegalStateException} being thrown on subsequent calls. - * - * @param configKeyId The KMS key ID - */ - public void storeConfigKeyId(final String configKeyId) { - synchronized (envDataLock) { - final Environment environment = getEnvironmentData(); - - if (StringUtils.isNotBlank(environment.getConfigKeyId())) { - throw new IllegalStateException("Config Key ID is already defined for this environment! Aborting save..."); - } - - environment.setConfigKeyId(configKeyId); - saveEnvironmentData(environment); - } - } - - /** - * Checks if the consul configuration is present in the config store. - * - * @return If present - */ - public boolean hasConsulConfig() { - synchronized (secretsDataLock) { - final Secrets secrets = getSecretsData(); - - return StringUtils.isNotBlank(secrets.getConsul().getAclMasterToken()) - && StringUtils.isNotBlank(secrets.getConsul().getGossipEncryptionToken()); - } - } - - /** - * Uploads the configuration files and saves the secrets to the config store. - * - * @param consulConfiguration Consul configuration to upload - */ - public void storeConsulConfig(final ConsulConfiguration consulConfiguration) { - saveEncryptedObject(ConfigConstants.CONSUL_SERVER_CONFIG_FILE, consulConfiguration.getServerConfiguration()); - saveEncryptedObject(ConfigConstants.CONSUL_CLIENT_CONFIG_FILE, consulConfiguration.getClientConfiguration()); - - synchronized (secretsDataLock) { - final Secrets secrets = getSecretsData(); - secrets.getConsul().setAclMasterToken(consulConfiguration.getInput().getAclMasterToken()); - secrets.getConsul().setGossipEncryptionToken(consulConfiguration.getInput().getGossipEncryptionToken()); - saveSecretsData(secrets); - } - } - - /** - * Retrieves the AclMasterToken for Consul from the config store. - */ - public String getAclMasterToken() { - synchronized (secretsDataLock) { - final Secrets secrets = getSecretsData(); - return secrets.getConsul().getAclMasterToken(); - } - } - - /** - * Retrieves the GossipEncryptionToken for Consul from the config store. - */ - public String getGossipEncryptionToken() { - synchronized (secretsDataLock) { - final Secrets secrets = getSecretsData(); - return secrets.getConsul().getGossipEncryptionToken(); - } - } - - /** - * Checks if the Vault ACL entry JSON is present in the config store. - * - * @return If present - */ - public boolean hasVaultAclEntry() { - synchronized (secretsDataLock) { - final Secrets secrets = getSecretsData(); - - return StringUtils.isNotBlank(secrets.getConsul().getVaultAclToken()); - } + this.amazonS3ClientFactory = amazonS3ClientFactory; + this.saltGenerator = saltGenerator; + this.securityTokenServiceFactory = securityTokenServiceFactory; + this.environmentName = environmentName; + this.configRegion = Regions.fromName(configRegion); + this.encryptionService = encryptionService; } /** - * Uploads the Vault ACL entry JSON and saves the secrets to the config store. + * Retrieves the CMS database password from the config store. * - * @param vaultAclEntry Vaul ACL entry to upload + * @return CMS database password */ - public void storeVaultAclEntry(final VaultAclEntry vaultAclEntry) { - saveEncryptedObject(ConfigConstants.VAULT_ACL_ENTRY_FILE, vaultAclEntry.getEntry()); - - synchronized (secretsDataLock) { - final Secrets secrets = getSecretsData(); - secrets.getConsul().setVaultAclToken(vaultAclEntry.getAclToken()); - saveSecretsData(secrets); - } + public Optional getCmsDatabasePassword() { + EnvironmentData environmentData = getDecryptedEnvironmentData(); + return Optional.ofNullable(environmentData.getDatabasePassword()); } /** - * Retrieves the Vault ACL token for accessing Consul from the config store. + * Stores the CMS database password. * - * @return Vault ACL token + * @param databasePassword Database password */ - public String getVaultAclToken() { - synchronized (secretsDataLock) { - final Secrets secrets = getSecretsData(); - return secrets.getConsul().getVaultAclToken(); - } + public void storeCmsDatabasePassword(String databasePassword) { + EnvironmentData environmentData = getDecryptedEnvironmentData(); + environmentData.setDatabasePassword(databasePassword); + saveEnvironmentData(environmentData); } /** - * Checks if the Vault configuration file has already been uploaded to the config store. + * Stores the certificate files encrypted and adds the certificate name to the environment data. * - * @return If present + * @param certificateInformation Certificate information for cert + * @param caContents CA chain + * @param certContents Certificate body + * @param keyContents Certificate key + * @param pubKeyContents Certificate public key */ - public boolean hasVaultConfig() { - final Optional vaultConfig = getEncryptedObject(ConfigConstants.VAULT_CONFIG_FILE); - return vaultConfig.isPresent(); - } + public void storeCert(CertificateInformation certificateInformation, + String caContents, + String certContents, + String keyContents, + String pkcs8KeyContents, + String pubKeyContents) { - /** - * Uploads the Vault configuration to the config store. - * - * @param vaultConfiguration Vault configuration to upload. - */ - public void storeVaultConfig(VaultConfiguration vaultConfiguration) { - saveEncryptedObject(ConfigConstants.VAULT_CONFIG_FILE, vaultConfiguration.getConfig()); - } + EnvironmentData environmentData = getDecryptedEnvironmentData(); - /** - * Checks if the Gateway configuration file has already been uploaded to the config store. - * - * @return If present - */ - public boolean hasGatewayConfig() { - final Optional gatewaySiteConfig = getEncryptedObject(ConfigConstants.GATEWAY_SITE_CONFIG_FILE); - final Optional gatewayGlobalConfig = getEncryptedObject(ConfigConstants.GATEWAY_GLOBAL_CONFIG_FILE); - return gatewaySiteConfig.isPresent() && gatewayGlobalConfig.isPresent(); - } + String name = certificateInformation.getCertificateName(); + encryptAndSaveObject(buildCertFilePath(name, CERT_PART_CA), caContents, environmentData); + encryptAndSaveObject(buildCertFilePath(name, CERT_PART_CERT), certContents, environmentData); + encryptAndSaveObject(buildCertFilePath(name, CERT_PART_KEY), keyContents, environmentData); + encryptAndSaveObject(buildCertFilePath(name, CERT_PART_PKCS8_KEY), pkcs8KeyContents, environmentData); + encryptAndSaveObject(buildCertFilePath(name, CERT_PART_PUBKEY), pubKeyContents, environmentData); - /** - * Uploads the Gateway configuration to the config store. - * - * @param gatewayConfiguration Gateway configuration to upload. - */ - public void storeGatewayConfig(GatewayConfiguration gatewayConfiguration) { - saveEncryptedObject(ConfigConstants.GATEWAY_SITE_CONFIG_FILE, gatewayConfiguration.getSiteConfig()); - saveEncryptedObject(ConfigConstants.GATEWAY_GLOBAL_CONFIG_FILE, gatewayConfiguration.getGlobalConfig()); - } + environmentData.addNewCertificateData(certificateInformation); - /** - * Retrieves the CMS adming group from the config store. - * - * @return CMS admin group - */ - public Optional getCmsAdminGroup() { - synchronized (secretsDataLock) { - final Secrets secrets = getSecretsData(); - return Optional.ofNullable(secrets.getCms().getAdminGroup()); - } + saveEnvironmentData(environmentData); } - /** - * Stores the CMS admin group. - * - * @param adminGroup Admin Group - */ - public void storeCmsAdminGroup(final String adminGroup) { - synchronized (secretsDataLock) { - final Secrets secrets = getSecretsData(); - secrets.getCms().setAdminGroup(adminGroup); - saveSecretsData(secrets); - } - } + public Optional getAcmeAccountKeyPair() { + Optional serializedKeyPair = + getEncryptedObject(CERT_ACME_ACCOUNT_PRIVATE_KEY).map(encryptionService::decrypt); - /** - * Retrieves the CMS database password from the config store. - * - * @return CMS database password - */ - public Optional getCmsDatabasePassword() { - synchronized (secretsDataLock) { - final Secrets secrets = getSecretsData(); - return Optional.ofNullable(secrets.getCms().getDatabasePassword()); + if (!serializedKeyPair.isPresent()) { + return Optional.empty(); } - } - /** - * Stores the CMS database password. - * - * @param databasePassword Database password - */ - public void storeCmsDatabasePassword(final String databasePassword) { - synchronized (secretsDataLock) { - final Secrets secrets = getSecretsData(); - secrets.getCms().setDatabasePassword(databasePassword); - saveSecretsData(secrets); + try { + return Optional.of(KeyPairUtils.readKeyPair(new StringReader(serializedKeyPair.get()))); + } catch (IOException e) { + throw new RuntimeException("Failed to read keypair from serialized data", e); } } - /** - * Retrieves the CMS Vault token from the config store. - * - * @return CMS Vault token - */ - public Optional getCmsVaultToken() { - synchronized (secretsDataLock) { - final Secrets secrets = getSecretsData(); - return Optional.ofNullable(secrets.getCms().getVaultToken()); + public void storeAcmeUserKeyPair(KeyPair keyPair) { + EnvironmentData environmentData = getDecryptedEnvironmentData(); + StringWriter stringWriter = new StringWriter(); + try (JcaPEMWriter jw = new JcaPEMWriter(stringWriter)) { + jw.writeObject(keyPair); + } catch (IOException e) { + throw new RuntimeException("Failed to write key pair", e); } + encryptAndSaveObject(CERT_ACME_ACCOUNT_PRIVATE_KEY, stringWriter.toString(), environmentData); } - /** - * Stores the CMS Vault token. - * - * @param cmsVaultToken CMS Vault token - */ - public void storeCmsVaultToken(final String cmsVaultToken) { - synchronized (secretsDataLock) { - final Secrets secrets = getSecretsData(); - secrets.getCms().setVaultToken(cmsVaultToken); - saveSecretsData(secrets); - } + public LinkedList getCertificationInformationList() { + return getDecryptedEnvironmentData().getCertificateData(); } - /** - * Store just the Vault unseal keys. - * - * @param vaultKeys Vault unseal keys - */ - public void storeVaultKeys(final List vaultKeys) { - synchronized (secretsDataLock) { - final Secrets secrets = getSecretsData(); - secrets.getVault().setKeys(vaultKeys); - saveSecretsData(secrets); + public Optional getLastCert() { + EnvironmentData environmentData = getDecryptedEnvironmentData(); + if (environmentData.getCertificateData().isEmpty()) { + return Optional.empty(); } + return Optional.of(environmentData.getCertificateData().getLast()); } /** - * Gets the Vault keys from the config store. - * - * @return List of Vault keys + * Deletes a set of cert and key files by certificate name + * @param certificateName the name of the cert file bundle to delete */ - public List getVaultKeys() { - synchronized (secretsDataLock) { - final Secrets secrets = getSecretsData(); - return secrets.getVault().getKeys(); - } - } + public void deleteCertificate(String certificateName) { + String path = "certificates/" + certificateName; + getDecryptedEnvironmentData().getConfigRegions().forEach(region -> { + try { + getStoreServiceForRegion(region, getDecryptedEnvironmentData()).deleteAllKeysOnPartialPath(path); + } catch (Exception e) { + logger.error(Chalk.on( + String.format("Failed to delete object at path: %s for region: %s, cross region data may be " + + "out of sync and require manual fixing", path, region) + ).bold().red().toString()); + } + }); - /** - * Store just the Vault root token. - * - * @param vaultRootToken Vault root token - */ - public void storeVaultRootToken(final String vaultRootToken) { - synchronized (secretsDataLock) { - final Secrets secrets = getSecretsData(); - secrets.getVault().setRootToken(vaultRootToken); - saveSecretsData(secrets); - } - } + EnvironmentData environment = getDecryptedEnvironmentData(); + environment.removeCertificateInformationByName(certificateName); - /** - * Gets the Vault root token from the config store. - * - * @return Vault root token - */ - public String getVaultRootToken() { - synchronized (secretsDataLock) { - final Secrets secrets = getSecretsData(); - final String rootToken = secrets.getVault().getRootToken(); - return (rootToken != null) ? rootToken : ""; - } + saveEnvironmentData(environment); } /** * Returns the contents of a specific certificate part that's been uploaded for a stack. * - * @param stackName * @param part * @return */ - public Optional getCertPart(final StackName stackName, final String part) { - return getEncryptedObject(buildCertFilePath(stackName, part)); - } - - /** - * Stores the certificate files encrypted and adds the certificate name to the environment data. - * - * @param stackName Stack that the cert is for - * @param certificateName Certificate name in IAM - * @param caContents CA chain - * @param certContents Certificate body - * @param keyContents Certificate key - * @param pubKeyContents Certificate public key - */ - public void storeCert(final StackName stackName, - final String certificateName, - final String caContents, - final String certContents, - final String keyContents, - final String pubKeyContents) { - saveEncryptedObject(buildCertFilePath(stackName, CERT_PART_CA), caContents); - saveEncryptedObject(buildCertFilePath(stackName, CERT_PART_CERT), certContents); - saveEncryptedObject(buildCertFilePath(stackName, CERT_PART_KEY), keyContents); - saveEncryptedObject(buildCertFilePath(stackName, CERT_PART_PUBKEY), pubKeyContents); - - synchronized (envDataLock) { - final Environment environment = getEnvironmentData(); - environment.getServerCertificateIdMap().put(stackName, certificateName); - saveEnvironmentData(environment); - } + public Optional getCertPart(String certName, String part) { + return getEncryptedObject(buildCertFilePath(certName, part)).map(encryptionService::decrypt); } - public void storeCmsEnvConfig(final Properties cmsConfigMap) { - final StringBuilder cmsConfigContents = new StringBuilder(); + public void storeCmsEnvConfig(Properties cmsConfigMap) { + StringBuilder cmsConfigContents = new StringBuilder(); - cmsConfigMap.keySet().stream().forEach(key -> { + cmsConfigMap.keySet().forEach(key -> { cmsConfigContents.append(key).append('=').append(cmsConfigMap.get(key)).append('\n'); }); - saveEncryptedObject(ConfigConstants.CMS_ENV_CONFIG_PATH, cmsConfigContents.toString()); + encryptAndSaveObject(ConfigConstants.CMS_ENV_CONFIG_PATH, cmsConfigContents.toString(), getDecryptedEnvironmentData()); } public Optional getCmsEnvConfig() { - return getEncryptedObject(ConfigConstants.CMS_ENV_CONFIG_PATH); + return getEncryptedObject(ConfigConstants.CMS_ENV_CONFIG_PATH).map(encryptionService::decrypt); } /** * Get the CMS environment properties + * * @return CMS properties */ public Properties getAllExistingCmsEnvProperties() { @@ -555,53 +279,65 @@ public Properties getAllExistingCmsEnvProperties() { } catch (IOException ioe) { throw new IllegalStateException("Failed to read CMS properties"); } - } else { - throw new IllegalStateException("Failed to read CMS properties"); } return properties; } /** - * Get generated CMS properties that are not set by the user + * Generate the global CMS properties that are not set by the user + * * @return - System configured properties */ - public Properties getCmsSystemProperties() { + private Properties generateBaseCmsSystemProperties() { + EnvironmentData data = getDecryptedEnvironmentData(); + String cmsDatabasePassword = data.getDatabasePassword(); + String cmsIamRoleArn = data.getCmsIamRoleArn(); + String adminRoleArn = data.getAdminIamRoleArn(); + String rootUserArn = data.getRootIamRoleArn(); + String jdbcUrl = getDatabaseStackOutputs(getPrimaryRegion()).getCmsDbJdbcConnectionString(); - final BaseOutputs baseOutputs = getBaseStackOutputs(); - final BaseParameters baseParameters = getBaseStackParameters(); - final VaultParameters vaultParameters = getVaultStackParamters(); - final Optional cmsVaultToken = getCmsVaultToken(); - final Optional cmsDatabasePassword = getCmsDatabasePassword(); - - final GetCallerIdentityResult callerIdentity = securityTokenService.getCallerIdentity( - new GetCallerIdentityRequest()); - final String rootUserArn = String.format("arn:aws:iam::%s:root", callerIdentity.getAccount()); - - final Properties properties = new Properties(); - properties.put(VAULT_ADDR_KEY, String.format("https://%s", cnameToHost(vaultParameters.getCname()))); - properties.put(VAULT_TOKEN_KEY, cmsVaultToken.get()); + Properties properties = new Properties(); properties.put(ROOT_USER_ARN_KEY, rootUserArn); - properties.put(ADMIN_ROLE_ARN_KEY, baseParameters.getAccountAdminArn()); - properties.put(CMS_ROLE_ARN_KEY, baseOutputs.getCmsIamRoleArn()); - properties.put(JDBC_URL_KEY, baseOutputs.getCmsDbJdbcConnectionString()); + properties.put(ADMIN_ROLE_ARN_KEY, adminRoleArn); + properties.put(CMS_ROLE_ARN_KEY, cmsIamRoleArn); + properties.put(JDBC_URL_KEY, jdbcUrl); properties.put(JDBC_USERNAME_KEY, ConfigConstants.DEFAULT_CMS_DB_NAME); - properties.put(JDBC_PASSWORD_KEY, cmsDatabasePassword.get()); + properties.put(JDBC_PASSWORD_KEY, cmsDatabasePassword); + properties.put(CMS_ENV_NAME, environmentName); + properties.put(CMS_CERTIFICATE_TO_USE, getCertificationInformationList().getLast().getCertificateName()); + properties.put(CMK_ARNS_KEY, StringUtils.join(data.getManagementServiceCmkArns(), ",")); return properties; } - public Optional getAccountAdminArn() { - final BaseParameters baseParameters = getBaseStackParameters(); - return Optional.ofNullable(baseParameters.getAccountAdminArn()); - } + /** + * System properties not set with -P param + */ + public Properties getCmsSystemProperties() { + + Properties properties = new Properties(); + Properties existingProperties = getAllExistingCmsEnvProperties(); + + // overwrite any of the automatically generated properties that may have changed + existingProperties.putAll(generateBaseCmsSystemProperties()); + + existingProperties.forEach((key, value) -> { + if (SYSTEM_CONFIGURED_CMS_PROPERTIES.contains(key)) { + properties.put(key, value); + } + }); + + if (!properties.containsKey(HASH_SALT)) { + properties.put(HASH_SALT, saltGenerator.generateSalt()); + } - public String getCerberusBaseUrl() { - return String.format("https://%s", getGatewayStackParamters().getHostname()); + return properties; } /** * Get existing CMS properties configured by the user + * * @return - User configured properties */ public Properties getExistingCmsUserProperties() { @@ -610,7 +346,7 @@ public Properties getExistingCmsUserProperties() { Properties existingProperties = getAllExistingCmsEnvProperties(); existingProperties.forEach((key, value) -> { - if (! SYSTEM_CONFIGURED_CMS_PROPERTIES.contains(key)) { + if (!SYSTEM_CONFIGURED_CMS_PROPERTIES.contains(key)) { properties.put(key, value); } }); @@ -620,228 +356,173 @@ public Properties getExistingCmsUserProperties() { /** * Return configuration file contents + * * @param path - Path to configuration file (e.g. 'config/environment.properties') */ public Optional getConfigProperties(String path) { - - return getEncryptedObject(path); + return getEncryptedObject(path).map(encryptionService::decrypt); } /** - * Get the base stack parameters. - * - * @return Base parameters + * returns the complete stack name */ - public BaseParameters getBaseStackParameters() { - return getStackParameters(StackName.BASE, BaseParameters.class); + public String getCloudFormationStackName(Stack stack) { + return stack.getFullName(environmentName); } /** - * Get the base stack outputs. + * Get the cms iam role stack outputs for primary region * * @return Base outputs */ - public BaseOutputs getBaseStackOutputs() { - return getStackOutputs(StackName.BASE, BaseOutputs.class); + public IamRolesOutputs getCmsIamRoleOutputs() { + return getStackOutputs(getPrimaryRegion(), getCloudFormationStackName(Stack.IAM_ROLES), IamRolesOutputs.class); } /** - * Get the Consul stack parameters. + * Get the cms iam role stack outputs for provided region * - * @return Consul parameters + * @return Base outputs */ - public ConsulParameters getConsulStackParameters() { - return getStackParameters(StackName.CONSUL, ConsulParameters.class); + public IamRolesOutputs getCmsIamRoleOutputs(Regions region) { + return getStackOutputs(region, getCloudFormationStackName(Stack.IAM_ROLES), IamRolesOutputs.class); } /** - * Get the Consul stack outputs. + * Get the cms iam role stack outputs. * - * @return Consul outputs + * @return Base outputs */ - public ConsulOutputs getConsulStackOutputs() { - return getStackOutputs(StackName.CONSUL, ConsulOutputs.class); + public ConfigOutputs getConfigBucketStackOutputs(Regions region) { + return getStackOutputs(region, getCloudFormationStackName(Stack.CONFIG), ConfigOutputs.class); } /** - * Get the Vault stack parameters. + * Get the base stack parameters for primary region. * - * @return Vault parameters + * @return Base parameters */ - public VaultParameters getVaultStackParamters() { - return getStackParameters(StackName.VAULT, VaultParameters.class); + public VpcParameters getVpcStackParameters() { + return getStackParameters(getPrimaryRegion(), getCloudFormationStackName(Stack.VPC), VpcParameters.class); } /** - * Get the Vault stack outputs. + * Get the base stack parameters. * - * @return Vault outputs + * @return Base parameters */ - public VaultOutputs getVaultStackOutputs() { - return getStackOutputs(StackName.VAULT, VaultOutputs.class); + public VpcParameters getVpcStackParameters(Regions region) { + return getStackParameters(region, getCloudFormationStackName(Stack.VPC), VpcParameters.class); } /** - * Get the CMS stack parameters. + * Get the base stack outputs for primary region. * - * @return CMS parameters + * @return Base outputs */ - public CmsParameters getCmsStackParamters() { - return getStackParameters(StackName.CMS, CmsParameters.class); + public VpcOutputs getVpcStackOutputs() { + return getStackOutputs(getPrimaryRegion(), getCloudFormationStackName(Stack.VPC), VpcOutputs.class); } /** - * Get the CMS stack outputs. + * Get the base stack outputs. * - * @return CMS outputs + * @return Base outputs */ - public CmsOutputs getCmsStackOutputs() { - return getStackOutputs(StackName.CMS, CmsOutputs.class); + public VpcOutputs getVpcStackOutputs(Regions region) { + return getStackOutputs(region, getCloudFormationStackName(Stack.VPC), VpcOutputs.class); } /** - * Get the Gateway stack parameters. + * Get the base stack outputs. * - * @return Gateway parameters + * @return Base outputs */ - public GatewayParameters getGatewayStackParamters() { - return getStackParameters(StackName.GATEWAY, GatewayParameters.class); + public SecurityGroupOutputs getSecurityGroupStackOutputs() { + return getStackOutputs(getPrimaryRegion(), getCloudFormationStackName(Stack.SECURITY_GROUPS), SecurityGroupOutputs.class); } /** - * Get the Gateway stack outputs. + * Get the base stack outputs. * - * @return Gateway outputs + * @return Base outputs */ - public GatewayOutputs getGatewayStackOutputs() { - return getStackOutputs(StackName.GATEWAY, GatewayOutputs.class); + public SecurityGroupOutputs getSecurityGroupStackOutputs(Regions region) { + return getStackOutputs(region, getCloudFormationStackName(Stack.SECURITY_GROUPS), SecurityGroupOutputs.class); } /** - * Get the stack outputs for a specific stack name. + * Get the base stack parameters. * - * @param stackName Stack name - * @param outputClass Outputs class - * @param Outputs type - * @return Outputs + * @return Base parameters */ - public M getStackOutputs(final StackName stackName, final Class outputClass) { - synchronized (envDataLock) { - final Environment environment = getEnvironmentData(); - final String stackId = environment.getStackMap().get(stackName); - - if (!cloudFormationService.isStackPresent(stackId)) { - throw new IllegalStateException("The specified stack doesn't exist for specified environment."); - } - - final Map stackOutputs = cloudFormationService.getStackOutputs(stackId); - return cloudFormationObjectMapper.convertValue(stackOutputs, outputClass); - } + public Route53Outputs getRoute53StackOutputs() { + return getStackOutputs(getPrimaryRegion(), getCloudFormationStackName(Stack.ROUTE53), Route53Outputs.class); } /** - * Get the stack parameters for a specific stack name. + * Get the base stack parameters. * - * @param stackName Stack name - * @param parameterClass Parameters class - * @param Parameters type - * @return Parameters + * @return Base parameters */ - public M getStackParameters(final StackName stackName, final Class parameterClass) { - synchronized (envDataLock) { - final Environment environment = getEnvironmentData(); - final String stackId = environment.getStackMap().get(stackName); - - if (!cloudFormationService.isStackPresent(stackId)) { - throw new IllegalStateException("The specified stack doesn't exist for the specified environment"); - } - - final Map stackParameters = cloudFormationService.getStackParameters(stackId); - return cloudFormationObjectMapper.convertValue(stackParameters, parameterClass); - } + public Route53Outputs getRoute53StackOutputs(Regions region) { + return getStackOutputs(region, getCloudFormationStackName(Stack.ROUTE53), Route53Outputs.class); } /** - * Constructs the standard format for CNAMEs used by internal ELBs. + * Get the base stack outputs. * - * @param stackName Stack name the CNAME is for - * @return CNAME + * @return Base outputs */ - public String getInternalElbCname(final StackName stackName) { - final BaseParameters baseParameters = getBaseStackParameters(); - return String.format("%s.%s.%s.", - stackName.getName(), - environmentMetadata.getRegionName(), - baseParameters.getVpcHostedZoneName()); + public DatabaseOutputs getDatabaseStackOutputs(Regions region) { + return getStackOutputs(region, getCloudFormationStackName(Stack.DATABASE), DatabaseOutputs.class); } /** - * Initializes the environment data in the config bucket. + * Get the stack outputs for a specific stack name. + * + * @param stackName Full stack name + * @param outputClass Outputs class + * @param Outputs type + * @return Outputs */ - public void initEnvironmentData() { - synchronized (envDataLock) { - try { - getEnvironmentData(); - final String errorMessage = "Attempting to initialize environment data, but it already exists!"; - logger.error(errorMessage); - throw new RuntimeException(errorMessage); - } catch (IllegalStateException ise) { - final Environment environment = new Environment(); - saveEnvironmentData(environment); - } + public M getStackOutputs(Regions region, String stackName, Class outputClass) { + if (!cloudFormationService.isStackPresent(region, stackName)) { + throw new IllegalStateException("Failed to get CloudFormation output for stack: '" + stackName + "'. Stack does not exist."); } + + Map stackOutputs = cloudFormationService.getStackOutputs(region, stackName); + return cloudFormationObjectMapper.convertValue(stackOutputs, outputClass); } /** - * Initializes the secrets data in the config bucket. + * Get the stack parameters for a specific stack name. + * + * @param stackName Full stack name + * @param parameterClass Parameters class + * @param Parameters type + * @return Parameters */ - public void initSecretsData() { - synchronized (secretsDataLock) { - try { - getSecretsData(); - final String errorMessage = "Attempting to initialize secrets data, but it already exists!"; - logger.error(errorMessage); - throw new RuntimeException(errorMessage); - } catch (IllegalStateException ise) { - final Secrets secrets = new Secrets(); - saveSecretsData(secrets); - } + public M getStackParameters(Regions region, String stackName, Class parameterClass) { + if (!cloudFormationService.isStackPresent(region, stackName)) { + throw new IllegalStateException("Failed to get CloudFormation parameters for stack: '" + stackName + "'. Stack does not exist."); } - } - - private Secrets getSecretsData() { - initEncryptedConfigStoreService(); - final Optional secretsData = encryptedConfigStoreService.get(ConfigConstants.SECRETS_DATA_FILE); - - if (secretsData.isPresent()) { - try { - return configObjectMapper.readValue(secretsData.get(), Secrets.class); - } catch (IOException e) { - throw new IllegalStateException("Unable to read the secrets data!", e); - } - } else { - throw new IllegalStateException("No secrets data available!"); - } + Map stackOutputs = cloudFormationService.getStackParameters(region, stackName); + return cloudFormationObjectMapper.convertValue(stackOutputs, parameterClass); } - private void saveSecretsData(final Secrets secrets) { - try { - final String secretsData = configObjectMapper.writeValueAsString(secrets); - saveEncryptedObject(ConfigConstants.SECRETS_DATA_FILE, secretsData); - } catch (JsonProcessingException e) { - throw new RuntimeException("Unable to convert the environment data to JSON. Aborting save...", e); + private EnvironmentData getDecryptedEnvironmentData() { + if (! storeServiceMap.containsKey(configRegion)) { + String bucket = findConfigBucketInSuppliedConfigRegion(); + storeServiceMap.put(configRegion, new S3StoreService(amazonS3ClientFactory.getClient(configRegion), bucket, "")); } - } - private Environment getEnvironmentData() { - initConfigStoreService(); + Optional environmentData = storeServiceMap.get(configRegion).get(ConfigConstants.ENVIRONMENT_DATA_FILE); - final Optional envData = configStoreService.get(ConfigConstants.ENV_DATA_FILE); - - if (envData.isPresent()) { + if (environmentData.isPresent()) { try { - return configObjectMapper.readValue(envData.get(), Environment.class); + return configObjectMapper.readValue(encryptionService.decrypt(environmentData.get()), EnvironmentData.class); } catch (IOException e) { throw new IllegalStateException("Unable to read the environment data!", e); } @@ -850,153 +531,189 @@ private Environment getEnvironmentData() { } } - private void saveEnvironmentData(final Environment environment) { + private void saveEnvironmentData(EnvironmentData environmentData) { try { - final String envData = configObjectMapper.writeValueAsString(environment); - saveObject(ConfigConstants.ENV_DATA_FILE, envData); + String serializedPlainTextEnvironmentData = configObjectMapper.writeValueAsString(environmentData); + encryptAndSaveObject(ConfigConstants.ENVIRONMENT_DATA_FILE, serializedPlainTextEnvironmentData, environmentData); } catch (JsonProcessingException e) { throw new RuntimeException("Unable to convert the environment data to JSON. Aborting save...", e); } } - public void saveCFLogProcessorLambdaConfig(final CloudFrontLogProcessingLambdaConfig config) { - try { - final String configData = configObjectMapper.writeValueAsString(config); - saveObject(ConfigConstants.CF_LOG_PROCESSOR_LAMBDA_CONFIG_FILE, configData); - } catch (JsonProcessingException e) { - throw new RuntimeException("Unable to convert the config data to JSON. Aborting save...", e); - } + /** + * List keys in config bucket under a path as-if it were a folder + */ + public Set listUnderPartialPath(String path) { + return getStoreServiceForRegion(configRegion, getDecryptedEnvironmentData()).listUnderPartialPath(path); } - private void saveObject(final String path, final String value) { - initConfigStoreService(); + private void encryptAndSaveObject(String path, + String plaintextSerializedObject, + EnvironmentData environmentData) { - configStoreService.put(path, value); - } + List environmentDataKmsCmkArns = new LinkedList<>(); + environmentData.getRegionData().forEach((region, regionData) -> + regionData.getConfigCmkArn().ifPresent(environmentDataKmsCmkArns::add)); + MasterKeyProvider encryptProvider = initializeKeyProvider(environmentDataKmsCmkArns); - private Optional getObject(final String path) { - initConfigStoreService(); + String encryptedObject = encryptionService.encrypt(encryptProvider, plaintextSerializedObject); - return configStoreService.get(path); + environmentData.getConfigRegions().forEach(region -> { + try { + getStoreServiceForRegion(region, environmentData).put(path, encryptedObject); + } catch (Exception e) { + logger.error(Chalk.on( + String.format("Failed to save object at path: %s for region: %s, cross region data may be " + + "out of sync and require manual fixing", path, region) + ).bold().red().toString()); + } + }); + } + + private StoreService getStoreServiceForRegion(Regions region, EnvironmentData environmentData) { + if (!storeServiceMap.containsKey(region)) { + String bucket = environmentData.getRegionData().get(region).getConfigBucket() + .orElseThrow(() -> new RuntimeException("bucket not configured for region: " + region)); + storeServiceMap.put(region, new S3StoreService(amazonS3ClientFactory.getClient(region), bucket, "")); + } + return storeServiceMap.get(region); } /** - * List under a path as-if it were a folder + * @param path the path to the encrypted text + * @return The serialized cipher text from s3 */ - public Set listUnderPartialPath(final String path) { - initConfigStoreService(); - - return configStoreService.listUnderPartialPath(path); + private Optional getEncryptedObject(String path) { + return getStoreServiceForRegion(configRegion, getDecryptedEnvironmentData()).get(path); } - private void saveEncryptedObject(final String path, final String value) { - initEncryptedConfigStoreService(); - - encryptedConfigStoreService.put(path, value); + private String buildCertFilePath(String identityManagementCertName, String filename) { + return String.format("certificates/%s/%s", identityManagementCertName, filename); } - private Optional getEncryptedObject(final String path) { - initEncryptedConfigStoreService(); - - return encryptedConfigStoreService.get(path); - } + private String findConfigBucketInSuppliedConfigRegion() { + AmazonS3Client s3Client = amazonS3ClientFactory.getClient(configRegion); + List buckets = s3Client.listBuckets(); + String envBucket = null; + String tokenizedEnvName = StringUtils.replaceAll(environmentName, "_", "-"); + for (Bucket bucket : buckets) { + String bucketName = bucket.getName(); + if (StringUtils.contains(bucket.getName(), ConfigConstants.CONFIG_BUCKET_KEY)) { + String bucketLocationResult = null; + try { + bucketLocationResult = s3Client.getBucketLocation(bucketName); + } catch (AmazonS3Exception e) { + logger.debug("Failed to get bucket location for bucket: {}, msg: {}", bucketName, e.getMessage()); + if (e.getErrorCode().equals("AuthorizationHeaderMalformed") && + e.getAdditionalDetails().containsKey("Region")) { - private void initEncryptedConfigStoreService() { - if (encryptedConfigStoreService == null) { - final Environment environment = getEnvironmentData(); + bucketLocationResult = e.getAdditionalDetails().get("Region"); + logger.debug("Determined region from S3 Error object, region: {}", bucketLocationResult); + } + if (bucketLocationResult == null) { + logger.debug("Failed to determine region for bucket: {} skipping...", bucketName); + } + } + Regions bucketRegion = StringUtils.equals("US", bucketLocationResult) ? Regions.US_EAST_1 : Regions.fromName(bucketLocationResult); - KMSEncryptionMaterialsProvider materialProvider = - new KMSEncryptionMaterialsProvider(environment.getConfigKeyId()); + logger.debug("Checking that bucket: {} in region: {} Starts with: {} and is in region: {}", bucketName, bucketRegion.getName(), tokenizedEnvName, configRegion.getName()); - AmazonS3EncryptionClient encryptionClient = - new AmazonS3EncryptionClient( - new DefaultAWSCredentialsProviderChain(), - materialProvider, - new CryptoConfiguration() - .withAwsKmsRegion(Region.getRegion(environmentMetadata.getRegions()))) - .withRegion(Region.getRegion(environmentMetadata.getRegions())); + if (configRegion.equals(bucketRegion) && StringUtils.startsWith(bucketName, tokenizedEnvName)) { + logger.info("Found config bucket: {}", bucketName); + envBucket = bucketName; + break; + } + } + } - encryptedConfigStoreService = new S3StoreService(encryptionClient, environmentMetadata.getBucketName(), ""); + if (StringUtils.isBlank(envBucket)) { + throw new RuntimeException("Failed to find config bucket for region: " + configRegion); } + + return envBucket; } - private void initConfigStoreService() { - if (configStoreService == null) { - configStoreService = new S3StoreService(s3Client, environmentMetadata.getBucketName(), ""); - } + @SuppressWarnings("unchecked") + private MasterKeyProvider initializeKeyProvider(List cmkArns) { + List> providers = cmkArns.stream() + .map(KmsMasterKeyProvider::new) + .collect(Collectors.toList()); + return (MasterKeyProvider) MultipleProviderFactory.buildMultiProvider(providers); } - private String buildCertFilePath(final StackName stackName, final String suffix) { - return "data/" + stackName.getName() + "/" + stackName.getName() + "-" + suffix; + public Regions getPrimaryRegion() { + return getDecryptedEnvironmentData().getPrimaryRegion(); + } + + public List getConfigEnabledRegions() { + return getDecryptedEnvironmentData().getConfigRegions(); } /** - * Removes the final '.' from the CNAME. + * Initializes the config state for a Cerberus Environment, called once at environment creation * - * @param cname The cname to convert - * @return The host derived from the CNAME + * @param adminRoleArn The admin role arn for the Cerberus environment, needed for KMS policies + * @param cmsIamRoleArn The CMS role arn for the Cerberus environment, needed for KMS policies + * @param primaryRegion The primary region that will serve traffic and have the data store and cms asg + * @param regionConfigOutputsMap The outputs that have the config buckets and KMS CMKs for encrypting sensitive config */ - private String cnameToHost(final String cname) { - return cname.substring(0, cname.length() - 1); - } + public void initializeEnvironment(String adminRoleArn, + String cmsIamRoleArn, + Regions primaryRegion, + Map regionConfigOutputsMap) { - public Optional getBackupInfoForRegion(String region) { - synchronized (envDataLock) { - final Environment environment = getEnvironmentData(); - return Optional.ofNullable(environment.getRegionBackupBucketMap().get(region)); - } - } - - public Map getRegionBackupBucketMap() { - synchronized (envDataLock) { - final Environment environment = getEnvironmentData(); - return environment.getRegionBackupBucketMap(); - } - } + AWSSecurityTokenService securityTokenService = securityTokenServiceFactory.getClient(configRegion); + GetCallerIdentityResult callerIdentity = securityTokenService.getCallerIdentity( + new GetCallerIdentityRequest()); + String rootUserArn = String.format("arn:aws:iam::%s:root", callerIdentity.getAccount()); + + EnvironmentData environmentData = new EnvironmentData(); + environmentData.setEnvironmentName(environmentName); + environmentData.setAdminIamRoleArn(adminRoleArn); + environmentData.setCmsIamRoleArn(cmsIamRoleArn); + environmentData.setRootIamRoleArn(rootUserArn); + regionConfigOutputsMap.forEach((region, output) -> { + RegionData regionData = new RegionData(); + if (region.equals(primaryRegion)) { + regionData.setPrimary(true); + } + regionData.setConfigBucket(output.getConfigBucketName()); + regionData.setConfigCmkArn(output.getConfigCmkArn()); + regionData.setManagementServiceCmkArn(output.getManagementServiceCmkArn()); + environmentData.addRegionData(region, regionData); + }); - public void storeBackupInfoForRegion(String region, String bucket, String kmsCmkId) { - synchronized (envDataLock) { - final Environment environment = getEnvironmentData(); - environment.getRegionBackupBucketMap().put(region, new BackupRegionInfo(bucket, kmsCmkId)); - saveEnvironmentData(environment); - } + saveEnvironmentData(environmentData); } - public Set getBackupAdminIamPrincipals() { - synchronized (envDataLock) { - final Environment environment = getEnvironmentData(); - return environment.getBackupAdminIamPrincipals(); + public String getConfigBucketForRegion(Regions region) { + if (!getDecryptedEnvironmentData().getRegionData().containsKey(region)) { + throw new RuntimeException("There is no region data for region: " + region.getName()); } + RegionData data = getDecryptedEnvironmentData().getRegionData().get(region); + return data.getConfigBucket().orElseThrow(() -> + new RuntimeException("There is no config bucket configured for region: " + region.getName())); } - public void storeBackupAdminIamPrincipals(Set principals) { - synchronized (envDataLock) { - final Environment environment = getEnvironmentData(); - environment.setBackupAdminIamPrincipals(principals); - saveEnvironmentData(environment); + public String getCmsCmkForRegion(Regions region) { + if (!getDecryptedEnvironmentData().getRegionData().containsKey(region)) { + throw new RuntimeException("There is no region data for region: " + region.getName()); } + RegionData data = getDecryptedEnvironmentData().getRegionData().get(region); + return data.getManagementServiceCmkArn().orElseThrow(() -> + new RuntimeException("There is no cms cmk configured for region: " + region.getName())); } - public Optional getMetricsTopicArn() { - synchronized (envDataLock) { - final Environment environment = getEnvironmentData(); - if (StringUtils.isNoneBlank(environment.getMetricsTopicArn())) { - return Optional.of(environment.getMetricsTopicArn()); - } + public String getEnvironmentDataSecureDataKmsCmkRegion(Regions region) { + if (!getDecryptedEnvironmentData().getRegionData().containsKey(region)) { + throw new RuntimeException("There is no region data for region: " + region.getName()); } - - Optional metricsTopicArn = cloudFormationService.searchStacksForOutput(CERBERUS_METRICS_TOPIC_ARN_STACK_OUTPUT_KEY); - metricsTopicArn.ifPresent(this::storeMetricsTopicArn); - - return metricsTopicArn; + RegionData data = getDecryptedEnvironmentData().getRegionData().get(region); + return data.getConfigCmkArn().orElseThrow(() -> + new RuntimeException("There is no cms cmk configured for region: " + region.getName())); } - private void storeMetricsTopicArn(String arn) { - synchronized (envDataLock) { - final Environment environment = getEnvironmentData(); - environment.setMetricsTopicArn(arn); - saveEnvironmentData(environment); - } + public EnvironmentData getEnvironmentData() { + return getDecryptedEnvironmentData(); } - } diff --git a/src/main/java/com/nike/cerberus/util/CiphertextUtils.java b/src/main/java/com/nike/cerberus/util/CiphertextUtils.java new file mode 100644 index 00000000..e452d4af --- /dev/null +++ b/src/main/java/com/nike/cerberus/util/CiphertextUtils.java @@ -0,0 +1,88 @@ +package com.nike.cerberus.util; + +import com.amazonaws.encryptionsdk.ParsedCiphertext; +import com.amazonaws.encryptionsdk.model.KeyBlob; +import com.google.common.collect.Lists; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +import java.io.UnsupportedEncodingException; +import java.util.Base64; +import java.util.List; + +/** + * Utility methods for working with the 'AWS Encryption SDK Message Format'. + * + * http://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/message-format.html + */ +public class CiphertextUtils { + + private static final String KMS_PROVIDER_ID = "aws-kms"; + + /** + * Parse a Ciphertext in the 'AWS Encryption SDK Message Format' to an object. + */ + public static ParsedCiphertext parse(String ciphertext) { + byte[] bytes = Base64.getDecoder().decode(ciphertext); + return new ParsedCiphertext(bytes); + } + + /** + * Parse CMK ARNs out of the supplied ciphertext in the 'AWS Encryption SDK Message Format'. + */ + public static List getCustomerMasterKeyArns(ParsedCiphertext parsedCiphertext) { + List cmkArns = Lists.newArrayList(); + for (KeyBlob keyBlob : parsedCiphertext.getEncryptedKeyBlobs()) { + if (KMS_PROVIDER_ID.equals(keyBlob.getProviderId())) { + try { + cmkArns.add(new String(keyBlob.getProviderInformation(), "UTF-8")); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("Failed to create new string from key blob provider information", e); + } + } + + } + return cmkArns; + } + + /** + * Convert a ParsedCiphertext in the 'AWS Encryption SDK Message Format' to pretty usage JSON. + */ + public static String toJson(ParsedCiphertext parsedCiphertext) { + Gson gson = new GsonBuilder() + .setPrettyPrinting() + .create(); + + return gson.toJson(toJsonObject(parsedCiphertext)); + } + + /** + * Convert a ParsedCiphertext in the 'AWS Encryption SDK Message Format' to a JsonObject. + */ + public static JsonObject toJsonObject(ParsedCiphertext parsedCiphertext) { + JsonObject o = new JsonObject(); + o.addProperty("version", parsedCiphertext.getVersion()); + o.addProperty("type", parsedCiphertext.getType().toString()); + o.addProperty("cryptoAlgoId", parsedCiphertext.getCryptoAlgoId().toString()); + o.addProperty("messageId", Base64.getEncoder().encodeToString(parsedCiphertext.getMessageId())); + o.add("encryptionContext", new Gson().toJsonTree(parsedCiphertext.getEncryptionContextMap())); + o.addProperty("keyBlobCount", parsedCiphertext.getEncryptedKeyBlobCount()); + JsonArray keyBlobs = new JsonArray(); + for (KeyBlob keyBlob : parsedCiphertext.getEncryptedKeyBlobs()) { + JsonObject blob = new JsonObject(); + blob.addProperty("providerId", keyBlob.getProviderId()); + try { + blob.addProperty("providerInfo", new String(keyBlob.getProviderInformation(), "UTF-8")); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("Failed to create new string from key blob provider information", e); + } + blob.addProperty("isComplete:", keyBlob.isComplete()); + keyBlobs.add(blob); + } + o.add("keyBlobs", keyBlobs); + o.addProperty("contentType", parsedCiphertext.getContentType().toString()); + return o; + } +} diff --git a/src/main/java/com/nike/cerberus/util/CloudFormationObjectMapper.java b/src/main/java/com/nike/cerberus/util/CloudFormationObjectMapper.java new file mode 100644 index 00000000..2ac57cae --- /dev/null +++ b/src/main/java/com/nike/cerberus/util/CloudFormationObjectMapper.java @@ -0,0 +1,36 @@ +package com.nike.cerberus.util; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; + +import javax.inject.Singleton; +import java.util.Map; + +/** + * Convert objects to {@code Map} to pass to CloudFormation + */ +@Singleton +public class CloudFormationObjectMapper { + + private final ObjectMapper om; + + public CloudFormationObjectMapper() { + om = new ObjectMapper(); + om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + om.setSerializationInclusion(JsonInclude.Include.NON_ABSENT); + } + + public Map convertValue(Object object) { + return om.convertValue( + object, + new TypeReference>() { + } + ); + } + + public T convertValue(Map stackOutputs, Class outputClass) { + return om.convertValue(stackOutputs, outputClass); + } +} diff --git a/src/main/java/com/nike/cerberus/util/StackConverter.java b/src/main/java/com/nike/cerberus/util/StackConverter.java new file mode 100644 index 00000000..1360bd20 --- /dev/null +++ b/src/main/java/com/nike/cerberus/util/StackConverter.java @@ -0,0 +1,15 @@ +package com.nike.cerberus.util; + +import com.beust.jcommander.IStringConverter; +import com.nike.cerberus.domain.environment.Stack; + +/** + * JCommander converter + */ +public class StackConverter implements IStringConverter { + + @Override + public Stack convert(String value) { + return Stack.fromName(value); + } +} \ No newline at end of file diff --git a/src/main/java/com/nike/cerberus/util/TokenSupplier.java b/src/main/java/com/nike/cerberus/util/TokenSupplier.java deleted file mode 100644 index 86fb07ae..00000000 --- a/src/main/java/com/nike/cerberus/util/TokenSupplier.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.util; - -import java.security.SecureRandom; -import java.util.Base64; -import java.util.function.Supplier; - -/** - * Supplier for a Base64 encoded string of a random 16 bytes. - */ -public class TokenSupplier implements Supplier { - - SecureRandom secureRandom = new SecureRandom(); - - /** - * Supplies a Base64 encoded string of a random 16 bytes. - * - * @return Base64 encoded string - */ - @Override - public String get() { - byte[] bytes = new byte[16]; - secureRandom.nextBytes(bytes); - return Base64.getEncoder().encodeToString(bytes); - } -} diff --git a/src/main/java/com/nike/cerberus/vault/VaultAdminClientFactory.java b/src/main/java/com/nike/cerberus/vault/VaultAdminClientFactory.java deleted file mode 100644 index 82ba4426..00000000 --- a/src/main/java/com/nike/cerberus/vault/VaultAdminClientFactory.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.vault; - -import com.google.common.collect.Lists; -import com.nike.cerberus.domain.cloudformation.VaultOutputs; -import com.nike.cerberus.service.AutoScalingService; -import com.nike.cerberus.store.ConfigStore; -import com.nike.vault.client.StaticVaultUrlResolver; -import com.nike.vault.client.VaultAdminClient; -import com.nike.vault.client.auth.TokenVaultCredentials; -import com.nike.vault.client.auth.VaultCredentials; -import com.nike.vault.client.auth.VaultCredentialsProvider; -import com.nike.vault.client.model.VaultHealthResponse; -import okhttp3.CipherSuite; -import okhttp3.ConnectionSpec; -import okhttp3.OkHttpClient; -import okhttp3.TlsVersion; -import org.apache.commons.lang3.StringUtils; -import org.apache.http.conn.ssl.NoopHostnameVerifier; - -import javax.inject.Inject; -import java.net.Proxy; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.TimeUnit; - -/** - * Handles constructing a Vault admin client that can communicate with each Vault instance directly. - */ -public class VaultAdminClientFactory { - - private static final int DEFAULT_TIMEOUT = 15; - - private static final TimeUnit DEFAULT_TIMEOUT_UNIT = TimeUnit.SECONDS; - - private final Proxy proxy; - - private final AutoScalingService autoScalingService; - - private final ConfigStore configStore; - - @Inject - public VaultAdminClientFactory(final Proxy proxy, - final AutoScalingService autoScalingService, - final ConfigStore configStore) { - this.proxy = proxy; - this.autoScalingService = autoScalingService; - this.configStore = configStore; - } - - /** - * Looks up the running instances from the Vault AutoScaling group and attempts to determine who the leader is - * by using the health endpoint. - * - * @return Client for leader - */ - public Optional getClientForLeader() { - VaultAdminClient leaderClient = null; - final String vaultRootToken = configStore.getVaultRootToken(); - final VaultOutputs vaultOutputs = configStore.getVaultStackOutputs(); - final List instanceDnsNames = autoScalingService.getPublicDnsForAutoScalingGroup( - vaultOutputs.getAutoscalingGroupLogicalId()); - - for (final String instanceDnsName : instanceDnsNames) { - final VaultAdminClient vaultAdminClient = getClient(vaultRootToken, instanceDnsName); - - final VaultHealthResponse healthResponse = vaultAdminClient.health(); - - if (healthResponse.isInitialized() && !healthResponse.isSealed() && !healthResponse.isStandby()) { - leaderClient = vaultAdminClient; - break; - } - } - - return Optional.ofNullable(leaderClient); - } - - /** - * Creates clients for each Vault instance in the AutoScaling group. - * - * @return List of Vault clients - */ - public List getClientsForCluster() { - final List clients = Lists.newLinkedList(); - String vaultRootToken = configStore.getVaultRootToken(); - - final VaultOutputs vaultOutputs = configStore.getVaultStackOutputs(); - final List instanceDnsNames = autoScalingService.getPublicDnsForAutoScalingGroup( - vaultOutputs.getAutoscalingGroupLogicalId()); - instanceDnsNames.forEach(instanceDnsName -> { - if (StringUtils.isNotBlank(instanceDnsName)) { - clients.add(getClient(vaultRootToken, instanceDnsName)); - } - }); - return clients; - } - - /** - * Determines if any Vault instances are present in the AutoScaling group. - * - * @return If instances running - */ - public boolean hasVaultInstances() { - final VaultOutputs vaultOutputs = configStore.getVaultStackOutputs(); - final List instanceDnsNames = autoScalingService.getPublicDnsForAutoScalingGroup( - vaultOutputs.getAutoscalingGroupLogicalId()); - return !instanceDnsNames.isEmpty(); - } - - public VaultAdminClient getClient(final String vaultRootToken, final String hostname) { - final ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) - .tlsVersions(TlsVersion.TLS_1_2) - .cipherSuites( - CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256) - .build(); - - final OkHttpClient httpClient = new OkHttpClient.Builder() - .hostnameVerifier(new NoopHostnameVerifier()) - .connectionSpecs(Lists.newArrayList(spec)) - .proxy(proxy) - .connectTimeout(DEFAULT_TIMEOUT, DEFAULT_TIMEOUT_UNIT) - .writeTimeout(DEFAULT_TIMEOUT, DEFAULT_TIMEOUT_UNIT) - .readTimeout(DEFAULT_TIMEOUT, DEFAULT_TIMEOUT_UNIT) - .build(); - - return new VaultAdminClient( - new StaticVaultUrlResolver(toVaultUrl(hostname)), - new RootCredentialsProvider(vaultRootToken), - httpClient); - } - - private String toVaultUrl(final String hostname) { - return String.format("https://%s:8200", hostname); - } - - - public static class RootCredentialsProvider implements VaultCredentialsProvider { - - private final String rootToken; - - public RootCredentialsProvider(final String rootToken) { - this.rootToken = rootToken; - } - - @Override - public VaultCredentials getCredentials() { - return new TokenVaultCredentials(rootToken); - } - } -} diff --git a/src/main/resources/cloudformation/.gitkeep b/src/main/resources/cloudformation/.gitkeep deleted file mode 100644 index 010921d6..00000000 --- a/src/main/resources/cloudformation/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -# Generated CF templates go here during builds. \ No newline at end of file diff --git a/src/main/resources/cloudformation/cms-cluster.yaml b/src/main/resources/cloudformation/cms-cluster.yaml new file mode 100644 index 00000000..a8ae817a --- /dev/null +++ b/src/main/resources/cloudformation/cms-cluster.yaml @@ -0,0 +1,103 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: Launches the CMS cluster in the Cerberus VPC +Outputs: + autoscalingGroupLogicalId: + Value: !Ref 'CmsAutoScalingGroup' + launchConfigurationLogicalId: + Value: !Ref 'CmsLaunchConfiguration' + cmsInstanceProfileName: + Value: !Ref 'CmsInstanceProfile' +Parameters: + baseStackName: + Description: The name of the Cerberus base CloudFormation stack + Type: String + amiId: + Description: The AMI ID for the CMS instances + Type: String + desiredInstances: + Default: 3 + Description: Desired Number of Auto Scaling Instances + Type: Number + instanceSize: + Default: 'm3.medium' + Description: The instance size for the CMS instances + Type: String + keyPairName: + Description: The key pair to be associated with the EC2 instances + Type: String + loadBalancerStackName: + Description: The name of the Cerberus ALB CloudFormation stack + Type: String + maximumInstances: + Default: 4 + Description: Maximum Number of Auto Scaling Instances (must be larger than min) + Type: Number + minimumInstances: + Default: 3 + Description: Minimum Number of Auto Scaling Instances + Type: Number + pauseTime: + Default: PT15M + Description: Pause time for AutoScalingRollingUpdate e.g PT15M + Type: String + sgStackName: + Description: The name of the Cerberus Security Groups CloudFormation stack + Type: String + userData: + Description: CMS user data + Type: String + vpcSubnetIdForAz1: + Description: The subnet for the first availability zone + Type: String + vpcSubnetIdForAz2: + Description: The subnet for the second availability zone + Type: String + vpcSubnetIdForAz3: + Description: The subnet for the third availability zone + Type: String + waitOnResourceSignals: + Default: 'True' + Description: Enabling WaitOnResourceSignals allows CloudFormation to wait until + you have received a success signal before performing the next scaling action. + Type: String +Resources: + CmsAutoScalingGroup: + Properties: + DesiredCapacity: !Ref 'desiredInstances' + HealthCheckGracePeriod: 900 + HealthCheckType: ELB + LaunchConfigurationName: !Ref 'CmsLaunchConfiguration' + TargetGroupARNs: + - Fn::ImportValue: !Sub "${loadBalancerStackName}-cmsTargetGroupArn" + MaxSize: !Ref 'maximumInstances' + MinSize: !Ref 'minimumInstances' + VPCZoneIdentifier: + - Ref: 'vpcSubnetIdForAz1' + - Ref: 'vpcSubnetIdForAz2' + - Ref: 'vpcSubnetIdForAz3' + Type: AWS::AutoScaling::AutoScalingGroup + UpdatePolicy: + AutoScalingRollingUpdate: + MaxBatchSize: 1 + MinInstancesInService: !Ref 'minimumInstances' + PauseTime: !Ref 'pauseTime' + WaitOnResourceSignals: !Ref 'waitOnResourceSignals' + CmsInstanceProfile: + Properties: + Path: / + Roles: + - Fn::ImportValue: !Sub "${baseStackName}-cmsIamRoleName" + Type: AWS::IAM::InstanceProfile + CmsLaunchConfiguration: + Properties: + AssociatePublicIpAddress: 'true' + IamInstanceProfile: !Ref 'CmsInstanceProfile' + ImageId: !Ref 'amiId' + InstanceMonitoring: 'true' + InstanceType: !Ref 'instanceSize' + KeyName: !Ref 'keyPairName' + SecurityGroups: + - Fn::ImportValue: !Sub "${sgStackName}-whitelistIngressSgId" + - Fn::ImportValue: !Sub "${sgStackName}-cmsSgId" + UserData: !Ref 'userData' + Type: AWS::AutoScaling::LaunchConfiguration diff --git a/src/main/resources/cloudformation/config.yaml b/src/main/resources/cloudformation/config.yaml new file mode 100644 index 00000000..feaba0fc --- /dev/null +++ b/src/main/resources/cloudformation/config.yaml @@ -0,0 +1,144 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: Creates the S3 config bucket and KMS CMK for Cerberus +Outputs: + configBucketDomainName: + Value: !GetAtt 'CerberusConfigBucket.DomainName' + configBucketName: + Value: !Ref 'CerberusConfigBucket' + configCmkArn: + Value: !GetAtt 'ConfigCmk.Arn' + managementServiceCmkArn: + Value: !GetAtt 'ManagementServiceCmk.Arn' +Parameters: + cmsIamRoleArn: + Description: The ARN for for the management service IAM Role. + Type: String + accountAdminArn: + Description: The ARN for a IAM user, group or role that can create this stack. + Type: String + environmentName: + Description: The Cerberus environment name. + Type: String +Resources: + CerberusConfigBucket: + Properties: + AccessControl: Private + VersioningConfiguration: + Status: Enabled + Type: AWS::S3::Bucket + CerberusConfigBucketAccessPolicy: + Type: AWS::S3::BucketPolicy + Properties: + Bucket: !Ref 'CerberusConfigBucket' + PolicyDocument: + Statement: + - Action: + - s3:ListBucket + Effect: Allow + Principal: + AWS: + !Ref 'cmsIamRoleArn' + Resource: + - !Join ['', ['arn:aws:s3:::', !Ref 'CerberusConfigBucket']] + - !Join ['', ['arn:aws:s3:::', !Ref 'CerberusConfigBucket', /*]] + Sid: Allow-ListBucket-Access + - Action: + - s3:* + Effect: Allow + Principal: + AWS: + !Ref 'cmsIamRoleArn' + Resource: + - !Join ['', ['arn:aws:s3:::', !Ref 'CerberusConfigBucket', /certificates/*]] + - !Join ['', ['arn:aws:s3:::', !Ref 'CerberusConfigBucket', /certificates]] + - !Join ['', ['arn:aws:s3:::', !Ref 'CerberusConfigBucket', /cms/*]] + - !Join ['', ['arn:aws:s3:::', !Ref 'CerberusConfigBucket', /cms]] + Sid: Allow-Bucket-Access-For-CMS + Version: '2012-10-17' + # KMS Customer Master Key for this CLI to use when saving config + ConfigCmk: + Type: AWS::KMS::Key + Properties: + Description: !Join ['', ['Environment: ', !Ref 'environmentName', ' Cerberus encryption key for storing config files in s3 buckets.']] + Enabled: 'true' + EnableKeyRotation: 'true' + KeyPolicy: + Statement: + - + Sid: Allow-Root-User + Action: + - kms:* + Effect: Allow + Principal: + AWS: + - !Join ['', ['arn:aws:iam::', !Ref 'AWS::AccountId', ':root']] + Resource: '*' + - + Sid: Allow-Decrypt-From-Instances + Action: + - kms:Decrypt + Effect: Allow + Principal: + AWS: + - !Ref 'cmsIamRoleArn' + Resource: '*' + - + Sid: Allow-Account-Admin + Action: + - kms:* + Effect: Allow + Principal: + AWS: + !Ref 'accountAdminArn' + Resource: '*' + Version: '2012-10-17' + Tags: + - Key: created_by + Value: cerberus_cli + - Key: created_for + Value: cerberus_cli + + # Cerberus Management Service KMS Customer Master Key + ManagementServiceCmk: + Type: AWS::KMS::Key + Properties: + Description: !Join ['', ['Environment: ', !Ref 'environmentName', ' CMS KMS CMK for storing secure data in RDS.']] + Enabled: 'true' + EnableKeyRotation: 'true' + KeyPolicy: + Statement: + - Sid: Allow-Root-User + Action: + - kms:* + Effect: Allow + Principal: + AWS: + - !Join ['', ['arn:aws:iam::', !Ref 'AWS::AccountId', ':root']] + Resource: '*' + - Sid: Allow-Decrypt-From-Cms-Instances + Action: + - kms:Encrypt + - kms:Decrypt + - kms:ReEncryptTo + - kms:ReEncryptFrom + - kms:GenerateDataKey + - kms:GenerateDataKeyWithoutPlaintext + Effect: Allow + Principal: + AWS: + - !Ref 'cmsIamRoleArn' + Resource: '*' + - Sid: Allow-Account-Admin + Action: + - kms:* + Effect: Allow + Principal: + AWS: + !Ref 'accountAdminArn' + Resource: '*' + Version: '2012-10-17' + Tags: + - Key: created_by + Value: cerberus_cli + - Key: created_for + Value: cerberus_cms diff --git a/src/main/resources/cloudformation/database.yaml b/src/main/resources/cloudformation/database.yaml new file mode 100644 index 00000000..671f2cac --- /dev/null +++ b/src/main/resources/cloudformation/database.yaml @@ -0,0 +1,134 @@ +AWSTemplateFormatVersion: '2010-09-09' +Conditions: + HasSnapshotIdentifier: !Not [!Equals [!Ref 'snapshotIdentifier', '']] +Description: Creates the database for use by the Cerberus Management Service (CMS) +Outputs: + cmsDbAddress: + Value: !GetAtt 'CmsDatabaseCluster.Endpoint.Address' + cmsDbInstanceId1: + Value: !Ref 'CmsDbInstance1' + cmsDbInstanceId2: + Value: !Ref 'CmsDbInstance2' + cmsDbJdbcConnectionString: + Description: JDBC connection string for cms database + Value: !Join ['', ['jdbc:mysql://', !Ref 'DatabaseCnameRecordSet', ':', + !GetAtt 'CmsDatabaseCluster.Endpoint.Port', /, !Ref 'cmsDbName', '?useUnicode=true&characterEncoding=utf8&useLegacyDatetimeCode=false&serverTimezone=UTC']] +Parameters: + cmsDbInstanceAz1: + Default: 'us-west-2a' + AllowedPattern: '[a-z]{2}-[a-z]+-\d\w' + Description: The first availability zone for the DB Instances + Type: String + cmsDbInstanceAz2: + Default: 'us-west-2b' + AllowedPattern: '[a-z]{2}-[a-z]+-\d\w' + Description: The second availability zone for the DB Instances + Type: String + cmsDbInstanceAz3: + Default: 'us-west-2c' + AllowedPattern: '[a-z]{2}-[a-z]+-\d\w' + Description: The third availability zone for the DB Instances + Type: String + cmsDbInstanceClass: + Default: db.r3.large + Description: MySQL DB instance class + Type: String + cmsDbMasterPassword: + Description: Master password for the cms RDS instance + Type: String + NoEcho: true + cmsDbMasterUsername: + Default: 'cms' + Description: Master username for the cms RDS instance + Type: String + cmsDbName: + Default: 'cms' + Description: The name of the database initially create on the RDS instance + Type: String + sgStackName: + Description: The name of the Cerberus Security Groups CloudFormation stack + Type: String + vpcSubnetIdForAz1: + Description: The subnet for the first availability zone + Type: String + vpcSubnetIdForAz2: + Description: The subnet for the second availability zone + Type: String + vpcSubnetIdForAz3: + Description: The subnet for the third availability zone + Type: String + snapshotIdentifier: + Description: If supplied the database cluster will be created with this snapshot, if supplied on an update then a new RDS cluster will be created and replace the existing RDS Cluster + Type: String + Default: '' + vpcInternalBaseDomainName: + Description: The base domain name for the internal hosted zone for the VPC that the database will be accessed from + Type: String + vpcInternalHostedZoneId: + Description: The hosted zone id for the interal hosted zone for the VPC that the database will be accessed from + Type: String +Resources: + CmsDatabaseCluster: + Type: AWS::RDS::DBCluster + Properties: + SnapshotIdentifier: !If [HasSnapshotIdentifier, !Ref 'snapshotIdentifier', !Ref 'AWS::NoValue'] + BackupRetentionPeriod: 14 + DatabaseName: !Ref 'cmsDbName' + DBSubnetGroupName: !Ref 'CmsDatabaseSubnetGroup' + Engine: aurora + MasterUserPassword: !Ref 'cmsDbMasterPassword' + MasterUsername: !If [HasSnapshotIdentifier, !Ref 'AWS::NoValue', !Ref 'cmsDbMasterUsername'] + Port: + Fn::ImportValue: !Sub "${sgStackName}-cmsDbPort" + PreferredBackupWindow: 13:14-13:44 + PreferredMaintenanceWindow: tue:06:48-tue:07:18 + StorageEncrypted: 'true' + VpcSecurityGroupIds: + - Fn::ImportValue: !Sub "${sgStackName}-cmsDbSgId" + # Create two DB instances one primary/write instance, and the other a read replica. + # The Aurora CloudFormation does not let you choose which is read or write. + # Aurora decides automatically for you behind the scenes. + CmsDbInstance1: + Type: AWS::RDS::DBInstance + Properties: + DBClusterIdentifier: !Ref CmsDatabaseCluster + DBInstanceClass: !Ref 'cmsDbInstanceClass' + DBParameterGroupName: !Ref 'CmsDatabaseParamGroup' + DBSubnetGroupName: !Ref 'CmsDatabaseSubnetGroup' + Engine: aurora + PubliclyAccessible: 'false' + CmsDbInstance2: + Type: AWS::RDS::DBInstance + Properties: + DBClusterIdentifier: !Ref CmsDatabaseCluster + DBInstanceClass: !Ref 'cmsDbInstanceClass' + DBParameterGroupName: !Ref 'CmsDatabaseParamGroup' + DBSubnetGroupName: !Ref 'CmsDatabaseSubnetGroup' + Engine: aurora + PubliclyAccessible: 'false' + CmsDatabaseParamGroup: + Type: AWS::RDS::DBParameterGroup + Properties: + Description: Default parameters for the cms DB + Family: aurora5.6 + Parameters: + log_output: TABLE + slow_query_log: 1 + CmsDatabaseSubnetGroup: + Type: AWS::RDS::DBSubnetGroup + Properties: + DBSubnetGroupDescription: DB Subnet Group for management DB + SubnetIds: + - Ref: 'vpcSubnetIdForAz1' + - Ref: 'vpcSubnetIdForAz2' + - Ref: 'vpcSubnetIdForAz3' + DatabaseCnameRecordSet: + Type: AWS::Route53::RecordSet + Properties: + HostedZoneId: + Ref: 'vpcInternalHostedZoneId' + Name: !Join [., ['rds', !Ref 'vpcInternalBaseDomainName']] + ResourceRecords: + - !GetAtt 'CmsDatabaseCluster.Endpoint.Address' + TTL: 30 + Type: CNAME diff --git a/src/main/resources/cloudformation/iam-roles.yaml b/src/main/resources/cloudformation/iam-roles.yaml new file mode 100644 index 00000000..7eacff2a --- /dev/null +++ b/src/main/resources/cloudformation/iam-roles.yaml @@ -0,0 +1,58 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: Creates the necessary IAM roles for Cerberus +Outputs: + cmsIamRoleArn: + Value: !GetAtt 'CmsIamRole.Arn' + Export: + Name: !Sub "${AWS::StackName}-cmsIamRoleArn" + cmsIamRoleName: + Value: !Ref 'CmsIamRole' + Export: + Name: !Sub "${AWS::StackName}-cmsIamRoleName" +Resources: + CmsIamRole: + Properties: + AssumeRolePolicyDocument: + Statement: + - Action: + - sts:AssumeRole + Effect: Allow + Principal: + Service: + - ec2.amazonaws.com + Version: '2012-10-17' + Path: / + Policies: + - PolicyName: "cms-cf-signals-policy" + PolicyDocument: + Statement: + - Action: + - EC2:Describe* + - cloudformation:SignalResource + Effect: Allow + Resource: '*' + - PolicyName: "cms-kms-policy" + PolicyDocument: + Statement: + - Action: + - kms:CreateAlias + - kms:CreateKey + - kms:DeleteAlias + - kms:DescribeKey + - kms:DisableKey + - kms:DisableKeyRotation + - kms:EnableKey + - kms:EnableKeyRotation + - kms:GetKeyPolicy + - kms:GetKeyRotationStatus + - kms:ListAliases + - kms:ListKeyPolicies + - kms:ListKeys + - kms:PutKeyPolicy + - kms:TagResource + - kms:UpdateAlias + - kms:UpdateKeyDescription + Effect: Allow + Resource: + - '*' + Type: AWS::IAM::Role \ No newline at end of file diff --git a/src/main/resources/cloudformation/load-balancer.yaml b/src/main/resources/cloudformation/load-balancer.yaml new file mode 100644 index 00000000..5aa9f0a6 --- /dev/null +++ b/src/main/resources/cloudformation/load-balancer.yaml @@ -0,0 +1,113 @@ +AWSTemplateFormatVersion: '2010-09-09' +Conditions: + RegionEqualsEastOne: !Equals [!Ref 'AWS::Region', us-east-1] +Description: Creates the application Load Balancer for the Cerberus environment +Outputs: + cmsTargetGroup: + Value: !Ref 'CmsTargetGroup' + Export: + Name: !Sub "${AWS::StackName}-cmsTargetGroupArn" + loadBalancerAccessLogBucket: + Value: !Ref 'ALBLogBucket' + loadBalancerDnsName: + Value: !GetAtt 'ApplicationLoadBalancer.DNSName' + Export: + Name: !Sub "${AWS::StackName}-loadBalancerDnsName" + loadBalancerPhysicalId: + Value: !Ref 'ApplicationLoadBalancer' + Export: + Name: !Sub "${AWS::StackName}-loadBalancerPhysicalId" +Parameters: + sgStackName: + Description: The name of the stack containing Cerberus IAM roles and SGs stack + Type: String + sslCertificateArn: + Description: TLS certificate ARN for CMS + Type: String + vpcId: + Description: The VPC in which the EC2 instances will be run + Type: String + vpcSubnetIdForAz1: + Description: The subnet for the first availability zone + Type: String + vpcSubnetIdForAz2: + Description: The subnet for the second availability zone + Type: String + vpcSubnetIdForAz3: + Description: The subnet for the third availability zone + Type: String + sslPolicy: + Description: > + The SSL Policy that will get applied to the application load balancer, + see http://docs.aws.amazon.com/elasticloadbalancing/latest/classic/elb-security-policy-table.html + for more information + Type: String + Default: ELBSecurityPolicy-TLS-1-2-2017-01 + elasticLoadBalancingAccountId: + Description: The account id for the AWS ELB Account. See https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/enable-access-logs.html + Type: String +Resources: + ALBListener: + Type: AWS::ElasticLoadBalancingV2::Listener + Properties: + Certificates: + - CertificateArn: !Ref sslCertificateArn + DefaultActions: + - Type: forward + TargetGroupArn: !Ref 'CmsTargetGroup' + LoadBalancerArn: !Ref 'ApplicationLoadBalancer' + Port: 443 + Protocol: HTTPS + SslPolicy: !Ref 'sslPolicy' + ALBLogBucket: + Properties: + AccessControl: BucketOwnerFullControl + Type: AWS::S3::Bucket + ALBLogBucketPolicy: + Properties: + Bucket: !Ref 'ALBLogBucket' + PolicyDocument: + Statement: + - Action: + - s3:* + Effect: Allow + Principal: + AWS: + - !Ref 'elasticLoadBalancingAccountId' + Resource: + - !Join ['', ['arn:aws:s3:::', !Ref 'ALBLogBucket', /*]] + Sid: Allow-ALB-Log-Access + Version: '2012-10-17' + Type: AWS::S3::BucketPolicy + ApplicationLoadBalancer: + Properties: + LoadBalancerAttributes: + - Key: access_logs.s3.enabled + Value: true + - Key: access_logs.s3.bucket + Value: !Ref 'ALBLogBucket' + Scheme: internet-facing + SecurityGroups: + - Fn::ImportValue: !Sub "${sgStackName}-loadBalancerSgId" + Subnets: + - Ref: 'vpcSubnetIdForAz1' + - Ref: 'vpcSubnetIdForAz2' + - Ref: 'vpcSubnetIdForAz3' + Type: AWS::ElasticLoadBalancingV2::LoadBalancer + CmsTargetGroup: + Type: "AWS::ElasticLoadBalancingV2::TargetGroup" + Properties: + HealthCheckIntervalSeconds: 5 + HealthCheckPath: /healthcheck + HealthCheckPort: 8443 + HealthCheckProtocol: HTTPS + HealthCheckTimeoutSeconds: 2 + HealthyThresholdCount: 2 + Port: 8443 + Protocol: HTTPS + TargetGroupAttributes: + - Key: deregistration_delay.timeout_seconds + Value: 10 + UnhealthyThresholdCount: 2 + VpcId: + Ref: 'vpcId' diff --git a/src/main/resources/cloudformation/route53.yaml b/src/main/resources/cloudformation/route53.yaml new file mode 100644 index 00000000..8e12783c --- /dev/null +++ b/src/main/resources/cloudformation/route53.yaml @@ -0,0 +1,43 @@ +AWSTemplateFormatVersion: '2010-09-09' +Conditions: + RegionEqualsEastOne: !Equals [!Ref 'AWS::Region', us-east-1] +Description: Creates the Route53 record for Cerberus +Outputs: + loadBalancerDomainName: + Value: !Ref 'CerberusLoadBalancerRecordSet' + originDomainName: + Value: !Ref 'CerberusOriginRecordSet' +Parameters: + hostedZoneId: + Description: The base hosted zone in which the load balancer and origin records will be created + Type: String + loadBalancerDomainName: + Description: The domain name for the Cerberus load balancer (e.g. demo.us-west-2.cerberus.example.com) + Type: String + loadBalancerStackName: + Description: The name of the Cerberus load balancer CloudFormation stack + Type: String + originDomainName: + Description: The origin domain name of the Cerberus environment (e.g. origin.demo.cerberus.example.com) + Type: String +Resources: + CerberusLoadBalancerRecordSet: + Properties: + HostedZoneId: + Ref: hostedZoneId + Name: !Join [., [!Ref 'loadBalancerDomainName', '']] + ResourceRecords: + - Fn::ImportValue: !Sub "${loadBalancerStackName}-loadBalancerDnsName" + TTL: 30 + Type: CNAME + Type: AWS::Route53::RecordSet + CerberusOriginRecordSet: + Properties: + HostedZoneId: + Ref: hostedZoneId + Name: !Join [., [!Ref 'originDomainName', '']] + ResourceRecords: + - Ref: CerberusLoadBalancerRecordSet + TTL: 30 + Type: CNAME + Type: AWS::Route53::RecordSet \ No newline at end of file diff --git a/src/main/resources/cloudformation/security-groups.yaml b/src/main/resources/cloudformation/security-groups.yaml new file mode 100644 index 00000000..3aab2f55 --- /dev/null +++ b/src/main/resources/cloudformation/security-groups.yaml @@ -0,0 +1,83 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: Launches the Security Groups required for Cerberus +Outputs: + cmsDbPort: + Value: !Ref 'cmsDbPort' + Export: + Name: !Sub "${AWS::StackName}-cmsDbPort" + cmsSgId: + Value: !GetAtt 'CmsSg.GroupId' + Export: + Name: !Sub "${AWS::StackName}-cmsSgId" + cmsDbSgId: + Value: !GetAtt 'CmsDbSg.GroupId' + Export: + Name: !Sub "${AWS::StackName}-cmsDbSgId" + loadBalancerSgId: + Value: !GetAtt 'AlbSg.GroupId' + Export: + Name: !Sub "${AWS::StackName}-loadBalancerSgId" + whitelistIngressSgId: + Value: !GetAtt 'WhitelistIngressSg.GroupId' + Export: + Name: !Sub "${AWS::StackName}-whitelistIngressSgId" +Parameters: + cmsDbPort: + Default: '3306' + Description: Port for the cms DB instance + Type: Number + vpcId: + Description: ID of the VPC to associate with the security groups + Type: String +Resources: + AlbSg: + Properties: + GroupDescription: ALB Security Group + Tags: + - Key: AutoUpdate + Value: 'true' + VpcId: + Ref: 'vpcId' + Type: AWS::EC2::SecurityGroup + AlbIngressFromInternetSg443: + Properties: + CidrIp: 0.0.0.0/0 + FromPort: 443 + GroupId: !Ref 'AlbSg' + IpProtocol: tcp + ToPort: 443 + Type: AWS::EC2::SecurityGroupIngress + CmsDbFromCms: + Properties: + FromPort: !Ref 'cmsDbPort' + GroupId: !Ref 'CmsDbSg' + IpProtocol: tcp + SourceSecurityGroupId: !Ref 'CmsSg' + ToPort: !Ref 'cmsDbPort' + Type: AWS::EC2::SecurityGroupIngress + CmsDbSg: + Properties: + GroupDescription: Management Server Database Security Group + VpcId: + Ref: 'vpcId' + Type: AWS::EC2::SecurityGroup + CmsIngressFromAlb8443: + Properties: + FromPort: 8443 + GroupId: !Ref 'CmsSg' + IpProtocol: tcp + SourceSecurityGroupId: !Ref 'AlbSg' + ToPort: 8443 + Type: AWS::EC2::SecurityGroupIngress + CmsSg: + Properties: + GroupDescription: Management Server Instance Security Group + VpcId: + Ref: 'vpcId' + Type: AWS::EC2::SecurityGroup + WhitelistIngressSg: + Properties: + GroupDescription: Administration ingress from tools NAT boxes + VpcId: + Ref: 'vpcId' + Type: AWS::EC2::SecurityGroup \ No newline at end of file diff --git a/src/main/resources/cloudformation/vpc.yaml b/src/main/resources/cloudformation/vpc.yaml new file mode 100644 index 00000000..5f684730 --- /dev/null +++ b/src/main/resources/cloudformation/vpc.yaml @@ -0,0 +1,164 @@ +AWSTemplateFormatVersion: '2010-09-09' +Conditions: + RegionEqualsEastOne: !Equals [!Ref 'AWS::Region', us-east-1] +Description: Creates the Cerberus VPC and foundational resources +Outputs: + subnetCidrBlockForAz1: + Description: Cidr block for subnet in AZ '1' + Value: !Ref 'subnetCidrBlockForAz1' + subnetCidrBlockForAz2: + Description: Cidr block for subnet in AZ '2' + Value: !Ref 'subnetCidrBlockForAz2' + subnetCidrBlockForAz3: + Description: Cidr block for subnet in AZ '3' + Value: !Ref 'subnetCidrBlockForAz3' + vpcSubnetIdForAz1: + Description: The VPC subnet in AZ '1' + Value: !Ref 'CerberusSubnetForAz1' + vpcSubnetIdForAz2: + Description: The VPC subnet in AZ '2' + Value: !Ref 'CerberusSubnetForAz2' + vpcSubnetIdForAz3: + Description: The VPC subnet in AZ '3' + Value: !Ref 'CerberusSubnetForAz3' + vpcId: + Description: ID of the created VPC + Value: !Ref 'CerberusVpc' + vpcInternalBaseDomainName: + Description: The base domain name for the internal hosted zone for this VPC + Value: !Join [., ['internal', !Ref 'AWS::Region', !Ref 'environmentName', 'cerberus.com', '']] + vpcInternalHostedZoneId: + Description: The hosted zone id for the interal hosted zone for this VPC + Value: !Ref 'InternalHostedZone' +Parameters: + az1: + AllowedPattern: '[a-z]{2}-[a-z]+-\d\w' + Description: An availability zone identifier for zone '1' + Type: String + az2: + AllowedPattern: '[a-z]{2}-[a-z]+-\d\w' + Description: An availability zone identifier for zone '2' + Type: String + az3: + AllowedPattern: '[a-z]{2}-[a-z]+-\d\w' + Description: An availability zone identifier for zone '3' + Type: String + internetGatewayCidrBlock: + AllowedPattern: '[0-9./]*' + Default: 0.0.0.0/0 + Description: The application load balancer CIDR block for where traffic is allowed from + MaxLength: '20' + MinLength: '9' + Type: String + subnetCidrBlockForAz1: + AllowedPattern: '[0-9./]*' + Default: 172.20.0.0/24 + Description: Cidr block for subnet in AZ '1' + MaxLength: '20' + MinLength: '9' + Type: String + subnetCidrBlockForAz2: + AllowedPattern: '[0-9./]*' + Default: 172.20.4.0/24 + Description: Cidr block for subnet in AZ '2' + MaxLength: '20' + MinLength: '9' + Type: String + subnetCidrBlockForAz3: + AllowedPattern: '[0-9./]*' + Default: 172.20.8.0/24 + Description: Cidr block for subnet in AZ '3' + MaxLength: '20' + MinLength: '9' + Type: String + vpcCidrBlock: + AllowedPattern: '[0-9./]*' + Default: 172.20.0.0/20 + Description: The VPC's CIDR block for internal IPv4 addressing + MaxLength: '20' + MinLength: '9' + Type: String + environmentName: + Description: The Cerberus environment name + Type: String +Resources: + CerberusDhcpOptions: + Properties: + DomainName: !If [RegionEqualsEastOne, ec2.internal, !Join [., [!Ref 'AWS::Region', + compute.internal]]] + DomainNameServers: + - AmazonProvidedDNS + Type: AWS::EC2::DHCPOptions + CerberusInternetGateway: + Type: AWS::EC2::InternetGateway + CerberusRouteInternetGateway: + Properties: + DestinationCidrBlock: !Ref 'internetGatewayCidrBlock' + GatewayId: !Ref 'CerberusInternetGateway' + RouteTableId: !Ref 'CerberusRouteTable' + Type: AWS::EC2::Route + CerberusRouteTable: + Properties: + VpcId: !Ref 'CerberusVpc' + Type: AWS::EC2::RouteTable + CerberusSubnetForAz1: + Properties: + AvailabilityZone: !Ref 'az1' + CidrBlock: !Ref 'subnetCidrBlockForAz1' + MapPublicIpOnLaunch: 'true' + VpcId: !Ref 'CerberusVpc' + Type: AWS::EC2::Subnet + CerberusSubnetForAz2: + Properties: + AvailabilityZone: !Ref 'az2' + CidrBlock: !Ref 'subnetCidrBlockForAz2' + MapPublicIpOnLaunch: 'true' + VpcId: !Ref 'CerberusVpc' + Type: AWS::EC2::Subnet + CerberusSubnetForAz3: + Properties: + AvailabilityZone: !Ref 'az3' + CidrBlock: !Ref 'subnetCidrBlockForAz3' + MapPublicIpOnLaunch: 'true' + VpcId: !Ref 'CerberusVpc' + Type: AWS::EC2::Subnet + CerberusSubnetRouteTableAssociationForAz1: + Properties: + RouteTableId: !Ref 'CerberusRouteTable' + SubnetId: !Ref 'CerberusSubnetForAz1' + Type: AWS::EC2::SubnetRouteTableAssociation + CerberusSubnetRouteTableAssociationForAz2: + Properties: + RouteTableId: !Ref 'CerberusRouteTable' + SubnetId: !Ref 'CerberusSubnetForAz2' + Type: AWS::EC2::SubnetRouteTableAssociation + CerberusSubnetRouteTableAssociationForAz3: + Properties: + RouteTableId: !Ref 'CerberusRouteTable' + SubnetId: !Ref 'CerberusSubnetForAz3' + Type: AWS::EC2::SubnetRouteTableAssociation + CerberusVpc: + Properties: + CidrBlock: !Ref 'vpcCidrBlock' + EnableDnsHostnames: 'true' + EnableDnsSupport: 'true' + Type: AWS::EC2::VPC + CerberusVpcDhcpOptionsAssociation: + Properties: + DhcpOptionsId: !Ref 'CerberusDhcpOptions' + VpcId: !Ref 'CerberusVpc' + Type: AWS::EC2::VPCDHCPOptionsAssociation + CerberusVpcGatewayAttachment: + Properties: + InternetGatewayId: !Ref 'CerberusInternetGateway' + VpcId: !Ref 'CerberusVpc' + Type: AWS::EC2::VPCGatewayAttachment + InternalHostedZone: # move to vpc stack + Type: 'AWS::Route53::HostedZone' + Properties: + HostedZoneConfig: + Comment: !Join [' ', ['Internal Hosted Zone for custom internal cname records for', !Ref 'environmentName']] + Name: !Join [., ['internal', !Ref 'AWS::Region', !Ref 'environmentName', 'cerberus.com', '']] + VPCs: + - VPCId: !Ref 'CerberusVpc' + VPCRegion: !Ref 'AWS::Region' diff --git a/src/main/resources/cloudformation/web-app-firewall.yaml b/src/main/resources/cloudformation/web-app-firewall.yaml new file mode 100644 index 00000000..dd48b824 --- /dev/null +++ b/src/main/resources/cloudformation/web-app-firewall.yaml @@ -0,0 +1,168 @@ +AWSTemplateFormatVersion: '2010-09-09' +Conditions: + RegionEqualsEastOne: !Equals [!Ref 'AWS::Region', us-east-1] +Description: Creates the Web Application Firewall (WAF) for the Cerberus ALB +Outputs: + autoBlockIPSetID: + Value: !Ref 'WAFAutoBlockSet' + manualBlockIPSetID: + Value: !Ref 'WAFManualBlockSet' + whitelistIPSetID: + Value: !Ref 'WAFWhitelistSet' +Parameters: + loadBalancerStackName: + Description: The name of the Cerberus load balancer CloudFormation stack + Type: String + wafName: + Description: The name for the Web Application Firewall that will be created + Type: String +Resources: + CerberusWAFWebAcl: + DependsOn: + - WAFManualBlockRule + - WAFAutoBlockRule + Properties: + DefaultAction: + Type: ALLOW + MetricName: CerberusWAF + Name: !Ref 'wafName' + Rules: + - Action: + Type: BLOCK + Priority: 1 + RuleId: !Ref 'CerberusWafSizeConstraintRule' + - Action: + Type: BLOCK + Priority: 2 + RuleId: !Ref 'CerberusWafSqlInjectionRule' + - Action: + Type: BLOCK + Priority: 3 + RuleId: !Ref 'CerberusWafXssRule' + - Action: + Type: ALLOW + Priority: 4 + RuleId: !Ref 'WAFWhiteListRule' + - Action: + Type: BLOCK + Priority: 5 + RuleId: !Ref 'WAFManualBlockRule' + - Action: + Type: BLOCK + Priority: 6 + RuleId: !Ref 'WAFAutoBlockRule' + Type: AWS::WAFRegional::WebACL + CerberusWebACLAssociation: + Type: "AWS::WAFRegional::WebACLAssociation" + Properties: + ResourceArn: + Fn::ImportValue: !Sub "${loadBalancerStackName}-loadBalancerPhysicalId" + WebACLId: + !Ref 'CerberusWAFWebAcl' + CerberusWafSizeConstraintRule: + Properties: + MetricName: CerberusWafSizeConstraint + Name: CerberusWafSizeConstraintRule + Predicates: + - DataId: !Ref 'CerberusWafSizeConstraintSet' + Negated: 'false' + Type: SizeConstraint + Type: AWS::WAFRegional::Rule + CerberusWafSizeConstraintSet: + Properties: + Name: CerberusWafSizeConstraintSet + SizeConstraints: + - ComparisonOperator: GE + FieldToMatch: + Type: BODY + Size: 256000 + TextTransformation: NONE + Type: AWS::WAFRegional::SizeConstraintSet + CerberusWafSqlInjectionMatchSet: + Properties: + Name: CerberusWafSqlInjectionMatchSet + SqlInjectionMatchTuples: + - FieldToMatch: + Type: URI + TextTransformation: NONE + - FieldToMatch: + Type: QUERY_STRING + TextTransformation: NONE + - FieldToMatch: + Type: BODY + TextTransformation: NONE + Type: AWS::WAFRegional::SqlInjectionMatchSet + CerberusWafSqlInjectionRule: + Properties: + MetricName: CerberusWafSqlInjection + Name: CerberusWafSqlInjectionRule + Predicates: + - DataId: !Ref 'CerberusWafSqlInjectionMatchSet' + Negated: 'false' + Type: SqlInjectionMatch + Type: AWS::WAFRegional::Rule + CerberusWafXssMatchSet: + Properties: + Name: CerberusWafXssMatchSet + XssMatchTuples: + - FieldToMatch: + Type: URI + TextTransformation: NONE + - FieldToMatch: + Type: QUERY_STRING + TextTransformation: NONE + - FieldToMatch: + Type: BODY + TextTransformation: NONE + Type: AWS::WAFRegional::XssMatchSet + CerberusWafXssRule: + Properties: + MetricName: CerberusWafXss + Name: CerberusWafXssRule + Predicates: + - DataId: !Ref 'CerberusWafXssMatchSet' + Negated: 'false' + Type: XssMatch + Type: AWS::WAFRegional::Rule + WAFAutoBlockRule: + DependsOn: WAFAutoBlockSet + Properties: + MetricName: AutoBlockRule + Name: Auto Block Rule + Predicates: + - DataId: !Ref 'WAFAutoBlockSet' + Negated: 'false' + Type: IPMatch + Type: AWS::WAFRegional::Rule + WAFAutoBlockSet: + Properties: + Name: Auto Block Set + Type: AWS::WAFRegional::IPSet + WAFManualBlockRule: + DependsOn: WAFManualBlockSet + Properties: + MetricName: ManualBlockRule + Name: Manual Block Rule + Predicates: + - DataId: !Ref 'WAFManualBlockSet' + Negated: 'false' + Type: IPMatch + Type: AWS::WAFRegional::Rule + WAFManualBlockSet: + Properties: + Name: Manual Block Set + Type: AWS::WAFRegional::IPSet + WAFWhiteListRule: + DependsOn: WAFWhitelistSet + Properties: + MetricName: WhiteListRule + Name: White List Rule + Predicates: + - DataId: !Ref 'WAFWhitelistSet' + Negated: 'false' + Type: IPMatch + Type: AWS::WAFRegional::Rule + WAFWhitelistSet: + Properties: + Name: White List Set + Type: AWS::WAFRegional::IPSet \ No newline at end of file diff --git a/src/main/resources/example-standup.yaml b/src/main/resources/example-standup.yaml index 3d4ccd80..5c7341be 100644 --- a/src/main/resources/example-standup.yaml +++ b/src/main/resources/example-standup.yaml @@ -19,7 +19,7 @@ cost-center: 11111 # The e-mail for who owns the provisioned resources. Will be tagged on all resources. owner-email: obvisouly.fake@nike.com # The owning group for the provision resources. Will be tagged on all resources. -owner-group: cloud platform engineering +owner-group: engineering team name # A IAM role ARN that will be given elevated privileges for the KMS CMK created., # If you don't separate root access from admins just use the root role here @@ -44,25 +44,6 @@ vpc-access-whitelist: cidrs: - 50.39.106.150/32 -# Consul-specific parameters -consul: - cert-path: /path/to/example/certs/ # directory containing pubkey.pem, key.pem, cert.pem, and ca.pem - ami-id: ami-1111 - instance-size: m3.medium - key-pair-name: example-key - # optional: desired, min, and max instance values - desired-instances: 3 - max-instances: 4 - min-instances: 3 - -# Vault-specific parameters -vault: - cert-path: /path/to/example/certs/ - ami-id: ami-2222 - instance-size: m3.medium - key-pair-name: example-key - # optional: desired, min, and max instance values - # Cerberus Management Service-specific parameters management-service: cert-path: /path/to/example/certs/ @@ -80,36 +61,4 @@ management-service: - auth.connector.onelogin.client_id=123 - auth.connector.onelogin.client_secret=312 - auth.connector.onelogin.subdomain=example # example.onelogin.com ~> 'example' - # optional: desired, min, and max instance values - - -gateway: - cert-path: /path/to/example/certs/ - ami-id: ami-4444 - instance-size: m3.medium - key-pair-name: cerberus-test - # optional: desired, min, and max instance values - -dashboard: - # The url of the dashboard artifact, probably the newest release on the dashboard Github release page - artifact-url: https://github.com/Nike-Inc/cerberus-management-dashboard/releases/download/v0.8.0/cerberus-dashboard.tar.gz - # The secondary artifact that will get merged into the above artifact before getting uploaded to s3, we use this for custom help page. - override-artifact-url: https://someplace.com/where/you/want/to/store/this.tar.gz - -# By default The gateway ELB for Cerberus is exposed to the public internet on port 443 -# We configure CloudFront and WAF to enable manual black and white lists, and auto blacklisting -edge-security: - # The artifact url for the CloudFront Lambda that process CloudFront Logs to enable things like rate limiting and KPI reporting - cloudfront-lambda-artifact-url: https://github.com/Nike-Inc/cerberus-cloudfront-lambda/releases/download/v1.1.0/cerberus-cloudfront-lambda.jar - # The artifact url to the lambda that ensures only CloudFront IPs are white listed to talk to the origin ELB - cloudfront-security-group-ip-sync-lambda-artifact-url: https://github.com/Nike-Inc/cerberus-lifecycle-cli/raw/master/update_security_groups.zip - # The maximum number of requests from an IP per minute that can flow through the gateway before being auto blocked. - rate-limit-per-minute: 100 - # Time in minutes to block an ip that violates the rate limit. - rate-limit-violation-block-period-in-minutes: 60 - # [Optional delete if you do not need] If you provide a Google Analytics tracking id, the KPI processor will send events to that GA Account. - google-analytics-tracking-id: abc123 - # [Optional delete if you do not need] If you provide a web hook url for slack the cloudfront lambda will send messages on errors and summary info. - slack-web-hook-url: https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX - # [Optional delete if you do not need] If you provide an emoji or an icon url the lambda will use it when sending messages. - slack-icon: https://raw.githubusercontent.com/Nike-Inc/cerberus/master/images/cerberus-github-logo-black-filled-circle%40500px.png \ No newline at end of file + # optional -P parameters: desired, min, and max instance values diff --git a/src/main/resources/templates/consul-client.json.mustache b/src/main/resources/templates/consul-client.json.mustache deleted file mode 100644 index 1a733390..00000000 --- a/src/main/resources/templates/consul-client.json.mustache +++ /dev/null @@ -1,33 +0,0 @@ -{ - "data_dir": "/var/consul/data", - "ui_dir": "/var/consul/webui", - "log_level": "info", - "leave_on_terminate": true, - "client_addr": "0.0.0.0", - "disable_update_check": true, - "server": false, - "encrypt": "{{{gossipEncryptionToken}}}", - "ports": { - "dns": -1, - "http": 8580, - "https": 8500, - "rpc": 8400, - "serf_lan": 8301, - "serf_wan": 8302, - "server": 8300 - }, - "verify_outgoing": true, - "verify_incoming": true, - "ca_file": "/etc/consul/ca.pem", - "cert_file": "/etc/consul/cert.pem", - "key_file": "/etc/consul/key.pem", - "disable_remote_exec": true, - "acl_datacenter": "{{{datacenter}}}", - "acl_master_token": "{{{aclMasterToken}}}", - "acl_default_policy": "deny", - "acl_down_policy": "deny", - "acl_enforce_version_8": false, - "reconnect_timeout": "16h", - "reconnect_timeout_wan": "16h", - "raft_protocol": 3 -} \ No newline at end of file diff --git a/src/main/resources/templates/consul-server.json.mustache b/src/main/resources/templates/consul-server.json.mustache deleted file mode 100644 index ea52c16d..00000000 --- a/src/main/resources/templates/consul-server.json.mustache +++ /dev/null @@ -1,39 +0,0 @@ -{ - "data_dir": "/var/consul/data", - "ui_dir": "/var/consul/webui", - "log_level": "info", - "leave_on_terminate": true, - "skip_leave_on_interrupt": false, - "client_addr": "0.0.0.0", - "disable_update_check": true, - "bootstrap_expect": 3, - "retry_interval": "15s", - "server": true, - "encrypt": "{{{gossipEncryptionToken}}}", - "ports": { - "dns": -1, - "http": 8580, - "https": 8500, - "rpc": 8400, - "serf_lan": 8301, - "serf_wan": 8302, - "server": 8300 - }, - "performance": { - "raft_multiplier": 1 - }, - "verify_outgoing": true, - "verify_incoming": true, - "ca_file": "/etc/consul/ca.pem", - "cert_file": "/etc/consul/cert.pem", - "key_file": "/etc/consul/key.pem", - "disable_remote_exec": true, - "acl_datacenter": "{{{datacenter}}}", - "acl_master_token": "{{{aclMasterToken}}}", - "acl_default_policy": "deny", - "acl_down_policy": "deny", - "acl_enforce_version_8": false, - "reconnect_timeout": "16h", - "reconnect_timeout_wan": "16h", - "raft_protocol": 3 -} diff --git a/src/main/resources/templates/nginx.conf.mustache b/src/main/resources/templates/nginx.conf.mustache deleted file mode 100644 index 4d4c21ab..00000000 --- a/src/main/resources/templates/nginx.conf.mustache +++ /dev/null @@ -1,69 +0,0 @@ -user www-data; -worker_processes auto; -pid /run/nginx.pid; - -events { - worker_connections 20000; -} - -http { - - ## - # Basic Settings - ## - - sendfile on; - tcp_nopush on; - tcp_nodelay on; - - client_body_buffer_size 10K; - client_header_buffer_size 1k; - client_max_body_size 8m; - large_client_header_buffers 2 1k; - client_body_timeout 12; - client_header_timeout 12; - keepalive_timeout 15; - send_timeout 10; - types_hash_max_size 2048; - - server_tokens off; - - include /etc/nginx/mime.types; - default_type application/octet-stream; - - ## - # SSL Settings - ## - - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; - ssl_prefer_server_ciphers on; - - ## - # Logging Settings - ## - - log_format access_format '[$time_iso8601] source_ip="$remote_addr" request="$request" request_size="$request_length" request_time="$request_time" response_code="$status" response_size="$bytes_sent" http_referer="$http_referer" http_user_agent="$http_user_agent"'; - - access_log /var/log/nginx/access.log access_format; - error_log /var/log/nginx/error.log info; - - ## - # Gzip Settings - ## - - gzip on; - gzip_comp_level 2; - gzip_min_length 1000; - gzip_proxied expired no-cache no-store private auth; - gzip_buffers 16 8k; - gzip_http_version 1.1; - gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; - gzip_disable "msie6"; - - ## - # Virtual Host Configs - ## - - include /etc/nginx/conf.d/*.conf; - include /etc/nginx/sites-enabled/*; -} \ No newline at end of file diff --git a/src/main/resources/templates/site-gateway.conf.mustache b/src/main/resources/templates/site-gateway.conf.mustache deleted file mode 100644 index 917226c4..00000000 --- a/src/main/resources/templates/site-gateway.conf.mustache +++ /dev/null @@ -1,76 +0,0 @@ -server { - listen 443 default_server; - - ssl on; - ssl_certificate /etc/ssl/gateway.pem; - ssl_certificate_key /etc/ssl/gateway.key; - ssl_protocols TLSv1.2; - - server_name {{{gatewayHost}}}; - - proxy_redirect off; - proxy_set_header Host $host; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - - ### - # - # Request Routing. ORDER IS IMPORTANT. - # - ### - - # Expose Nginx status on loopback. - location /sys/status { - stub_status on; - access_log off; - allow 127.0.0.1; - deny all; - } - - # Simple health response - location = /sys/health { - access_log off; - add_header Content-Type text/plain; - return 200 "Gateway is Healthy"; - } - - location /robots.txt { - add_header Content-Type text/plain; - return 200 "User-agent: *\nDisallow: /\n"; - } - - # Dashboard - location /dashboard { - add_header X-Frame-Options deny; - add_header Content-Security-Policy "frame-ancestors 'none'"; - add_header X-Content-Security-Policy "frame-ancestors 'none'"; - set $dashboard "{{{dashboardHost}}}"; - rewrite ^/dashboard/(.*) /$1 break; - proxy_set_header Host $dashboard; - proxy_pass http://$dashboard; - } - - # Allow lookup-self call for a token to go straight to Vault - location ~ /v1/auth/token/lookup-self { - set $vault "{{{vaultHost}}}"; - proxy_pass https://$vault; - } - - # Everything else under /v1/secret goes to Vault - location ~ /v1/secret { - set $vault "{{{vaultHost}}}"; - proxy_pass https://$vault; - } - - # Everything goes to CMS - location ~ /v\d { - set $cms "{{{cmsHost}}}"; - proxy_pass https://$cms; - } - - # Redirect root to the dashboard - location / { - return 301 https://{{{gatewayHost}}}/dashboard/; - } -} diff --git a/src/main/resources/templates/vault-acl.json.mustache b/src/main/resources/templates/vault-acl.json.mustache deleted file mode 100644 index d685808c..00000000 --- a/src/main/resources/templates/vault-acl.json.mustache +++ /dev/null @@ -1,6 +0,0 @@ -{ - "ID": "{{{aclToken}}}", - "Name": "vault", - "Type": "client", - "Rules": "key \"vault\" {policy = \"write\"}" -} \ No newline at end of file diff --git a/src/main/resources/templates/vault.json.mustache b/src/main/resources/templates/vault.json.mustache deleted file mode 100644 index 8af3efcb..00000000 --- a/src/main/resources/templates/vault.json.mustache +++ /dev/null @@ -1,24 +0,0 @@ -{ - "listener": { - "tcp": { - "address": "0.0.0.0:8200", - "tls_cert_file": "/etc/vault/cert.pem", - "tls_key_file": "/etc/vault/key.pem", - "tls_min_version": "tls12" - } - }, - "backend": { - "consul": { - "scheme": "https", - "address": "127.0.0.1:8500", - "path": "vault", - "datacenter": "{{{datacenter}}}", - "tls_ca_file": "/etc/consul/ca.pem", - "tls_cert_file": "/etc/consul/cert.pem", - "tls_key_file": "/etc/consul/key.pem", - "tls_skip_verify": "true", - "token": "{{{aclToken}}}" - } - }, - "default_lease_ttl": "1h", -} \ No newline at end of file diff --git a/src/main/resources/vault/default-policies.json b/src/main/resources/vault/default-policies.json deleted file mode 100644 index ed7c47c4..00000000 --- a/src/main/resources/vault/default-policies.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "lookup-self": { - "rules": "path \"auth/token/lookup\" {policy = \"read\"}" - } -} \ No newline at end of file diff --git a/src/test/java/com/nike/cerberus/cli/EnvironmentConfigToArgsMapperTest.java b/src/test/java/com/nike/cerberus/cli/EnvironmentConfigToArgsMapperTest.java index 9c8b380d..f0aa3b0c 100644 --- a/src/test/java/com/nike/cerberus/cli/EnvironmentConfigToArgsMapperTest.java +++ b/src/test/java/com/nike/cerberus/cli/EnvironmentConfigToArgsMapperTest.java @@ -22,16 +22,10 @@ import com.nike.cerberus.command.StackDelegate; import com.nike.cerberus.command.cms.CreateCmsClusterCommand; import com.nike.cerberus.command.cms.CreateCmsConfigCommand; -import com.nike.cerberus.command.consul.CreateConsulClusterCommand; -import com.nike.cerberus.command.core.CreateBaseCommand; -import com.nike.cerberus.command.core.UpdateStackCommand; -import com.nike.cerberus.command.core.UploadCertFilesCommand; +import com.nike.cerberus.command.core.InitializeEnvironmentCommand; +import com.nike.cerberus.command.certificates.UploadCertificateFilesCommand; +import com.nike.cerberus.command.certificates.UploadCertificateFilesCommandParametersDelegate; import com.nike.cerberus.command.core.WhitelistCidrForVpcAccessCommand; -import com.nike.cerberus.command.dashboard.PublishDashboardCommand; -import com.nike.cerberus.command.gateway.CreateCloudFrontLogProcessingLambdaConfigCommand; -import com.nike.cerberus.command.gateway.CreateGatewayClusterCommand; -import com.nike.cerberus.command.gateway.PublishLambdaCommand; -import com.nike.cerberus.command.vault.CreateVaultClusterCommand; import com.nike.cerberus.domain.input.EnvironmentConfig; import org.apache.commons.lang3.StringUtils; import org.junit.Before; @@ -40,6 +34,7 @@ import java.io.InputStream; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; public class EnvironmentConfigToArgsMapperTest { @@ -54,6 +49,11 @@ public void before() throws Exception { environmentConfig = mapper.readValue(yamlStream, EnvironmentConfig.class); } + @Test + public void test_that_the_example_yaml_can_be_deserialized() { + assertNotNull(environmentConfig); + } + @Test public void test_that_mapper_copies_pre_command_flags_with_flag() { String commandName = "I-don't-exist"; @@ -68,7 +68,7 @@ public void test_that_mapper_copies_pre_command_flags_with_flag() { String[] actual = EnvironmentConfigToArgsMapper.getArgs(environmentConfig, userInput); - assertArgsAreEqual(expected, actual,commandName); + assertArgsAreEqual(expected, actual, commandName); } @Test @@ -84,41 +84,43 @@ public void test_that_mapper_copies_pre_command_flags_without_flag() { String[] actual = EnvironmentConfigToArgsMapper.getArgs(environmentConfig, userInput); - assertArgsAreEqual(expected, actual,commandName); + assertArgsAreEqual(expected, actual, commandName); } @Test - public void test_create_base() { - String commandName = CreateBaseCommand.COMMAND_NAME; + public void test_initialize_environment() { + String commandName = InitializeEnvironmentCommand.COMMAND_NAME; String[] userInput = {"-f", "/path/to/environment.yaml", commandName}; String[] expected = { "-f", "/path/to/environment.yaml", commandName, - "--admin-role-arn", "arn:aws:iam::111111111:role/onelogin-roles-OneLoginAdminRole-2222222222", - "--vpc-hosted-zone-name", "demo.internal.cerberus-oss.io", - "--owner-email", "obvisouly.fake@nike.com", - "--costcenter", "11111" + InitializeEnvironmentCommand.ADMIN_ROLE_ARN_LONG_ARG, "arn:aws:iam::111111111:role/admin", + InitializeEnvironmentCommand.REGION_LONG_ARG, "us-west-2", + InitializeEnvironmentCommand.REGION_LONG_ARG, "us-east-1", + InitializeEnvironmentCommand.PRIMARY_REGION, "us-west-2", + "-TownerEmail=obvisouly.fake@nike.com", + "-TcostCenter=11111", + "-TownerGroup=engineering-team-name" }; String[] actual = EnvironmentConfigToArgsMapper.getArgs(environmentConfig, userInput); - assertArgsAreEqual(expected, actual,commandName); + assertArgsAreEqual(expected, actual, commandName); } @Test public void test_upload_cert_without_overwrite() { - String commandName = UploadCertFilesCommand.COMMAND_NAME; + String commandName = UploadCertificateFilesCommand.COMMAND_NAME; String[] userInput = {"-f", "/path/to/environment.yaml", commandName, "--stack-name", "cms"}; String[] expected = { "-f", "/path/to/environment.yaml", commandName, - "--stack-name", "cms", - "--cert-path", "/home/fieldju/development/cerberus_environments/demo/certs/" + UploadCertificateFilesCommandParametersDelegate.CERT_PATH_LONG_ARG, "/path/to/certs/" }; String[] actual = EnvironmentConfigToArgsMapper.getArgs(environmentConfig, userInput); @@ -128,66 +130,14 @@ public void test_upload_cert_without_overwrite() { @Test public void test_upload_cert_with_overwrite() { - String commandName = UploadCertFilesCommand.COMMAND_NAME; - - String[] userInput = {"-f", "/path/to/environment.yaml", commandName, "--stack-name", "consul", "--overwrite"}; - - String[] expected = { - "-f", "/path/to/environment.yaml", - commandName, - "--stack-name", "consul", - "--cert-path", "/home/fieldju/development/cerberus_environments/demo/certs/", - "--overwrite" - }; - - String[] actual = EnvironmentConfigToArgsMapper.getArgs(environmentConfig, userInput); - - assertArgsAreEqual(expected, actual, commandName); - } - - @Test - public void test_create_consul_cluster() { - String commandName = CreateConsulClusterCommand.COMMAND_NAME; + String commandName = UploadCertificateFilesCommand.COMMAND_NAME; String[] userInput = {"-f", "/path/to/environment.yaml", commandName}; String[] expected = { "-f", "/path/to/environment.yaml", commandName, - StackDelegate.AMI_ID_LONG_ARG, "ami-1111", - StackDelegate.INSTANCE_SIZE_LONG_ARG, "m3.medium", - StackDelegate.KEY_PAIR_NAME_LONG_ARG, "cerberus-test", - StackDelegate.COST_CENTER_LONG_ARG, "11111", - StackDelegate.OWNER_EMAIL_LONG_ARG, "obvisouly.fake@nike.com", - StackDelegate.OWNER_GROUP_LONG_ARG, "cloud platform engineering", - StackDelegate.DESIRED_INSTANCES_LONG_ARG, "2", - StackDelegate.MAX_INSTANCES_LONG_ARG, "4", - StackDelegate.MIN_INSTANCES_LONG_ARG, "2" - }; - - String[] actual = EnvironmentConfigToArgsMapper.getArgs(environmentConfig, userInput); - - assertArgsAreEqual(expected, actual, commandName); - } - - @Test - public void test_create_vault_cluster() { - String commandName = CreateVaultClusterCommand.COMMAND_NAME; - - String[] userInput = {"-f", "/path/to/environment.yaml", commandName}; - - String[] expected = { - "-f", "/path/to/environment.yaml", - commandName, - StackDelegate.AMI_ID_LONG_ARG, "ami-2222", - StackDelegate.INSTANCE_SIZE_LONG_ARG, "m3.medium", - StackDelegate.KEY_PAIR_NAME_LONG_ARG, "cerberus-test", - StackDelegate.COST_CENTER_LONG_ARG, "11111", - StackDelegate.OWNER_EMAIL_LONG_ARG, "obvisouly.fake@nike.com", - StackDelegate.OWNER_GROUP_LONG_ARG, "cloud platform engineering", - StackDelegate.DESIRED_INSTANCES_LONG_ARG, "2", - StackDelegate.MAX_INSTANCES_LONG_ARG, "4", - StackDelegate.MIN_INSTANCES_LONG_ARG, "2" + "--cert-dir-path", "/path/to/certs/", }; String[] actual = EnvironmentConfigToArgsMapper.getArgs(environmentConfig, userInput); @@ -207,53 +157,9 @@ public void test_create_management_service_cluster() { StackDelegate.AMI_ID_LONG_ARG, "ami-3333", StackDelegate.INSTANCE_SIZE_LONG_ARG, "m3.medium", StackDelegate.KEY_PAIR_NAME_LONG_ARG, "cerberus-test", - StackDelegate.COST_CENTER_LONG_ARG, "11111", - StackDelegate.OWNER_EMAIL_LONG_ARG, "obvisouly.fake@nike.com", - StackDelegate.OWNER_GROUP_LONG_ARG, "cloud platform engineering" - }; - - String[] actual = EnvironmentConfigToArgsMapper.getArgs(environmentConfig, userInput); - - assertArgsAreEqual(expected, actual, commandName); - } - - @Test - public void test_create_gateway_cluster() { - String commandName = CreateGatewayClusterCommand.COMMAND_NAME; - - String[] userInput = {"-f", "/path/to/environment.yaml", commandName}; - - String[] expected = { - "-f", "/path/to/environment.yaml", - commandName, - StackDelegate.AMI_ID_LONG_ARG, "ami-4444", - StackDelegate.INSTANCE_SIZE_LONG_ARG, "m3.medium", - StackDelegate.KEY_PAIR_NAME_LONG_ARG, "cerberus-test", - StackDelegate.COST_CENTER_LONG_ARG, "11111", - StackDelegate.OWNER_EMAIL_LONG_ARG, "obvisouly.fake@nike.com", - StackDelegate.OWNER_GROUP_LONG_ARG, "cloud platform engineering", - CreateGatewayClusterCommand.HOSTNAME_LONG_ARG, "demo.cerberis-oss.io", - CreateGatewayClusterCommand.HOSTED_ZONE_ID_LONG_ARG, "X5CT6JROG9F2DR" - }; - - String[] actual = EnvironmentConfigToArgsMapper.getArgs(environmentConfig, userInput); - - assertArgsAreEqual(expected, actual, commandName); - } - - @Test - public void test_publish_dashboard() { - String commandName = PublishDashboardCommand.COMMAND_NAME; - - String[] userInput = {"-f", "/path/to/environment.yaml", commandName}; - - String[] expected = { - "-f", "/path/to/environment.yaml", - commandName, - PublishDashboardCommand.ARTIFACT_URL_LONG_ARG, - "https://github.com/Nike-Inc/cerberus-management-dashboard/releases/download/v0.8.0/cerberus-dashboard.tar.gz", - PublishDashboardCommand.OVERRIDE_ARTIFACT_URL_LONG_ARG, - "https://someplace.com/where/you/want/to/store/this.tar.gz" + "-TownerEmail=obvisouly.fake@nike.com", + "-TcostCenter=11111", + "-TownerGroup=engineering-team-name" }; String[] actual = EnvironmentConfigToArgsMapper.getArgs(environmentConfig, userInput); @@ -271,11 +177,7 @@ public void test_vpc_access_whitelist() { "-f", "/path/to/environment.yaml", commandName, WhitelistCidrForVpcAccessCommand.CIDR_LONG_ARG, "50.39.106.150/32", - WhitelistCidrForVpcAccessCommand.PORT_LONG_ARG, "443", - WhitelistCidrForVpcAccessCommand.PORT_LONG_ARG, "8080", - WhitelistCidrForVpcAccessCommand.PORT_LONG_ARG, "8200", - WhitelistCidrForVpcAccessCommand.PORT_LONG_ARG, "8500", - WhitelistCidrForVpcAccessCommand.PORT_LONG_ARG, "8400", + WhitelistCidrForVpcAccessCommand.PORT_LONG_ARG, "8443", WhitelistCidrForVpcAccessCommand.PORT_LONG_ARG, "22" }; @@ -293,117 +195,12 @@ public void test_create_cms_config() { String[] expected = { "-f", "/path/to/environment.yaml", commandName, - CreateCmsConfigCommand.ADMIN_GROUP_LONG_ARG, "lst-cerberus-admins", + CreateCmsConfigCommand.ADMIN_GROUP_LONG_ARG, "cerberus-admins", CreateCmsConfigCommand.PROPERTY_SHORT_ARG, "cms.auth.connector=com.nike.cerberus.auth.connector.onelogin.OneLoginAuthConnector", CreateCmsConfigCommand.PROPERTY_SHORT_ARG, "auth.connector.onelogin.api_region=us", CreateCmsConfigCommand.PROPERTY_SHORT_ARG, "auth.connector.onelogin.client_id=123", CreateCmsConfigCommand.PROPERTY_SHORT_ARG, "auth.connector.onelogin.client_secret=312", - CreateCmsConfigCommand.PROPERTY_SHORT_ARG, "auth.connector.onelogin.subdomain=nike" - }; - - String[] actual = EnvironmentConfigToArgsMapper.getArgs(environmentConfig, userInput); - - assertArgsAreEqual(expected, actual, commandName); - } - - @Test - public void test_update_stack_with_no_overwrite_flag_or_dyn_props() { - String commandName = UpdateStackCommand.COMMAND_NAME; - - String[] userInput = {"-f", "/path/to/environment.yaml", commandName, EnvironmentConfigToArgsMapper.STACK_NAME_KEY, "gateway"}; - - String[] expected = { - "-f", "/path/to/environment.yaml", - commandName, - EnvironmentConfigToArgsMapper.STACK_NAME_KEY, "gateway", - StackDelegate.AMI_ID_LONG_ARG, "ami-4444", - StackDelegate.INSTANCE_SIZE_LONG_ARG, "m3.medium", - StackDelegate.KEY_PAIR_NAME_LONG_ARG, "cerberus-test", - StackDelegate.COST_CENTER_LONG_ARG, "11111", - StackDelegate.OWNER_EMAIL_LONG_ARG, "obvisouly.fake@nike.com", - StackDelegate.OWNER_GROUP_LONG_ARG, "cloud platform engineering", - }; - - String[] actual = EnvironmentConfigToArgsMapper.getArgs(environmentConfig, userInput); - - assertArgsAreEqual(expected, actual, commandName); - } - - @Test - public void test_update_stack_with_overwrite_flag_and_dyn_props() { - String commandName = UpdateStackCommand.COMMAND_NAME; - - String[] userInput = {"-f", "/path/to/environment.yaml", commandName, EnvironmentConfigToArgsMapper.STACK_NAME_KEY, "gateway", "--overwrite-template", "-P", "k=v"}; - - String[] expected = { - "-f", "/path/to/environment.yaml", - commandName, - EnvironmentConfigToArgsMapper.STACK_NAME_KEY, "gateway", - StackDelegate.AMI_ID_LONG_ARG, "ami-4444", - StackDelegate.INSTANCE_SIZE_LONG_ARG, "m3.medium", - StackDelegate.KEY_PAIR_NAME_LONG_ARG, "cerberus-test", - StackDelegate.COST_CENTER_LONG_ARG, "11111", - StackDelegate.OWNER_EMAIL_LONG_ARG, "obvisouly.fake@nike.com", - StackDelegate.OWNER_GROUP_LONG_ARG, "cloud platform engineering", - UpdateStackCommand.OVERWRITE_TEMPLATE_LONG_ARG, - UpdateStackCommand.PARAMETER_SHORT_ARG, "k=v", - }; - - String[] actual = EnvironmentConfigToArgsMapper.getArgs(environmentConfig, userInput); - - assertArgsAreEqual(expected, actual, commandName); - } - - @Test - public void test_publish_lambda_cf_sg_ip() { - String commandName = PublishLambdaCommand.COMMAND_NAME; - - String[] userInput = {"-f", "/path/to/environment.yaml", PublishLambdaCommand.COMMAND_NAME, PublishLambdaCommand.LAMBDA_NAME_LONG_ARG, "CLOUD_FRONT_SG_GROUP_IP_SYNC"}; - - String[] expected = { - "-f", "/path/to/environment.yaml", - commandName, - PublishLambdaCommand.LAMBDA_NAME_LONG_ARG, "CLOUD_FRONT_SG_GROUP_IP_SYNC", - PublishLambdaCommand.ARTIFACT_URL_LONG_ARG, "https://github.com/Nike-Inc/cerberus-lifecycle-cli/raw/master/update_security_groups.zip" - }; - - String[] actual = EnvironmentConfigToArgsMapper.getArgs(environmentConfig, userInput); - - assertArgsAreEqual(expected, actual, commandName); - } - - @Test - public void test_create_cloud_front_log_processor_lambda_config() { - String commandName = CreateCloudFrontLogProcessingLambdaConfigCommand.COMMAND_NAME; - - String[] userInput = {"-f", "/path/to/environment.yaml", commandName}; - - String[] expected = { - "-f", "/path/to/environment.yaml", - commandName, - CreateCloudFrontLogProcessingLambdaConfigCommand.RATE_LIMIT_PER_MINUTE_LONG_ARG, "100", - CreateCloudFrontLogProcessingLambdaConfigCommand.RATE_LIMIT_VIOLATION_BLOCK_PERIOD_IN_MINUTES_LONG_ARG, "60", - CreateCloudFrontLogProcessingLambdaConfigCommand.SLACK_WEB_HOOK_URL_LONG_ARG, "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX", - CreateCloudFrontLogProcessingLambdaConfigCommand.SLACK_ICON_LONG_ARG, "https://raw.githubusercontent.com/Nike-Inc/cerberus/master/images/cerberus-github-logo-black-filled-circle%40500px.png", - CreateCloudFrontLogProcessingLambdaConfigCommand.GOOGLE_ANALYTICS_TRACKING_ID_LONG_ARG, "abc123" - }; - - String[] actual = EnvironmentConfigToArgsMapper.getArgs(environmentConfig, userInput); - - assertArgsAreEqual(expected, actual, commandName); - } - - @Test - public void test_publish_lambda_waf() { - String commandName = PublishLambdaCommand.COMMAND_NAME; - - String[] userInput = {"-f", "/path/to/environment.yaml", PublishLambdaCommand.COMMAND_NAME, PublishLambdaCommand.LAMBDA_NAME_LONG_ARG, "WAF"}; - - String[] expected = { - "-f", "/path/to/environment.yaml", - commandName, - PublishLambdaCommand.LAMBDA_NAME_LONG_ARG, "WAF", - PublishLambdaCommand.ARTIFACT_URL_LONG_ARG, "https://github.com/Nike-Inc/cerberus-cloudfront-lambda/releases/download/v1.1.0/cerberus-cloudfront-lambda.jar" + CreateCmsConfigCommand.PROPERTY_SHORT_ARG, "auth.connector.onelogin.subdomain=example" }; String[] actual = EnvironmentConfigToArgsMapper.getArgs(environmentConfig, userInput); @@ -437,8 +234,8 @@ private void assertArgsAreEqual(String[] expected, String[] actual, String comma boolean isPairE = false; String eVal = ""; if (i < expected.length - 1) { - eVal = expected[i+1]; - if (! StringUtils.startsWith(eVal, "-")) { + eVal = expected[i + 1]; + if (!StringUtils.startsWith(eVal, "-")) { isPairE = true; } } @@ -454,8 +251,8 @@ private void assertArgsAreEqual(String[] expected, String[] actual, String comma boolean isPairA = false; String aVal = ""; if (n < actual.length - 1) { - aVal = actual[n+1]; - if (! StringUtils.startsWith(aVal, "-")) { + aVal = actual[n + 1]; + if (!StringUtils.startsWith(aVal, "-")) { isPairA = true; } } @@ -467,12 +264,12 @@ private void assertArgsAreEqual(String[] expected, String[] actual, String comma if (isPairE && isPairA && eKey.equals(aKey) && eVal.equals(aVal)) { found = true; break; - } else if (! isPairE && ! isPairA && eKey.equals(aKey)) { + } else if (!isPairE && !isPairA && eKey.equals(aKey)) { found = true; break; } } - if (! found) { + if (!found) { if (isPairE) { fail(String.format("Failed to find [ %s %s ] in actual args \nactual args: %s\nexpected args: %s\nactual size: %s\nexpected size: %s", eKey, eVal, String.join(" ", actual), String.join(" ", expected), actual.length, expected.length)); } else { diff --git a/src/test/java/com/nike/cerberus/command/CerberusCommandTest.java b/src/test/java/com/nike/cerberus/command/CerberusCommandTest.java index 9852c51d..a0bfb56b 100644 --- a/src/test/java/com/nike/cerberus/command/CerberusCommandTest.java +++ b/src/test/java/com/nike/cerberus/command/CerberusCommandTest.java @@ -19,11 +19,7 @@ import com.beust.jcommander.JCommander; import org.junit.Test; -import java.io.File; import java.net.Proxy; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.file.Paths; import static org.junit.Assert.assertEquals; @@ -52,52 +48,4 @@ public void test_that_command_line_poxy_args_are_honored() { assertEquals(Proxy.Type.SOCKS, proxyDelegate.getProxyType()); } - @Test - public void test_that_yaml_proxy_args_are_honored() throws URISyntaxException { - URL url = getClass().getClassLoader().getResource("environment.yaml"); - File file = Paths.get(url.toURI()).toFile(); - String yamlFilePath = file.getAbsolutePath(); - String[] userInput = { - "-f", yamlFilePath, - "COMMAND", - "--some-opt", "some-value" - }; - - CerberusCommand cerberusCommand = new CerberusCommand(); - JCommander commander = new JCommander(cerberusCommand); - commander.setProgramName("cerberus"); - commander.setAcceptUnknownOptions(true); - commander.parseWithoutValidation(userInput); - - ProxyDelegate proxyDelegate = cerberusCommand.getProxyDelegate(); - - assertEquals("localhost", proxyDelegate.getProxyHost()); - assertEquals((Integer) 9000, proxyDelegate.getProxyPort()); - assertEquals(Proxy.Type.SOCKS, proxyDelegate.getProxyType()); - } - - @Test - public void test_that_command_line_proxy_args_overwrite_yaml_honored() throws URISyntaxException { - URL url = getClass().getClassLoader().getResource("environment.yaml"); - File file = Paths.get(url.toURI()).toFile(); - String yamlFilePath = file.getAbsolutePath(); - String[] userInput = { - "--proxy-host", "10.1.1.1", - "-f", yamlFilePath, - "COMMAND", - "--some-opt", "some-value" - }; - - CerberusCommand cerberusCommand = new CerberusCommand(); - JCommander commander = new JCommander(cerberusCommand); - commander.setProgramName("cerberus"); - commander.setAcceptUnknownOptions(true); - commander.parseWithoutValidation(userInput); - - ProxyDelegate proxyDelegate = cerberusCommand.getProxyDelegate(); - - assertEquals("10.1.1.1", proxyDelegate.getProxyHost()); - assertEquals((Integer) 9000, proxyDelegate.getProxyPort()); - assertEquals(Proxy.Type.SOCKS, proxyDelegate.getProxyType()); - } } diff --git a/src/test/java/com/nike/cerberus/command/validator/EnvironmentNameValidatorTest.java b/src/test/java/com/nike/cerberus/command/validator/EnvironmentNameValidatorTest.java index 0ff2fdbd..8639913d 100644 --- a/src/test/java/com/nike/cerberus/command/validator/EnvironmentNameValidatorTest.java +++ b/src/test/java/com/nike/cerberus/command/validator/EnvironmentNameValidatorTest.java @@ -19,8 +19,6 @@ import com.beust.jcommander.ParameterException; import org.junit.Test; -import javax.print.attribute.standard.MediaSize; - public class EnvironmentNameValidatorTest { private final static String NAME = "NAME"; diff --git a/src/test/java/com/nike/cerberus/util/TokenSupplierTest.java b/src/test/java/com/nike/cerberus/domain/StackTest.java similarity index 50% rename from src/test/java/com/nike/cerberus/util/TokenSupplierTest.java rename to src/test/java/com/nike/cerberus/domain/StackTest.java index 1f195d34..f9bd3c3e 100644 --- a/src/test/java/com/nike/cerberus/util/TokenSupplierTest.java +++ b/src/test/java/com/nike/cerberus/domain/StackTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Nike, Inc. + * Copyright (c) 2017 Nike, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,35 +14,28 @@ * limitations under the License. */ -package com.nike.cerberus.util; +package com.nike.cerberus.domain; -import com.beust.jcommander.internal.Sets; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.nike.cerberus.domain.environment.Stack; import org.junit.Test; -import java.util.Set; +import java.util.HashMap; +import java.util.Map; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -public class TokenSupplierTest { +public class StackTest { @Test - public void sanityTestGet() { + public void test_that_stack_can_go_into_serialized_map() throws JsonProcessingException { + Map map = new HashMap<>(); + map.put(Stack.CMS, "foo"); - TokenSupplier supplier = new TokenSupplier(); + ObjectMapper objectMapper = new ObjectMapper(); + String json = objectMapper.writeValueAsString(map); - Set previous = Sets.newHashSet(); - for (int i = 0; i < 1000; i++) { - String current = supplier.get(); - - // not the same - assertFalse(previous.contains(current)); - - // check length - assertEquals(24, current.length()); - - previous.add(current); - } + assertEquals("{\"cms\":\"foo\"}", json); } - -} \ No newline at end of file +} diff --git a/src/test/java/com/nike/cerberus/domain/environment/EnvironmentDataTest.java b/src/test/java/com/nike/cerberus/domain/environment/EnvironmentDataTest.java new file mode 100644 index 00000000..e5b40077 --- /dev/null +++ b/src/test/java/com/nike/cerberus/domain/environment/EnvironmentDataTest.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.domain.environment; + +import com.amazonaws.regions.Regions; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.nike.cerberus.module.CerberusModule; +import org.junit.Test; + +import java.io.IOException; + +import static org.junit.Assert.assertEquals; + +public class EnvironmentDataTest { + + @Test + public void test_that_environment_data_can_be_serialized_and_deserialized() throws IOException { + EnvironmentData expected = new EnvironmentData(); + expected.setEnvironmentName("test-env-name"); + expected.setAdminIamRoleArn("admin-role"); + expected.setCmsIamRoleArn("cms-role"); + expected.setRootIamRoleArn("root-role"); + RegionData regionData = new RegionData(); + regionData.setPrimary(true); + regionData.setConfigBucket("foo"); + regionData.setConfigCmkArn("bar"); + regionData.setManagementServiceCmkArn("bam"); + expected.addRegionData(Regions.US_WEST_2, regionData); + + ObjectMapper mapper = CerberusModule.configObjectMapper(); + + String serializedEnvData = mapper.writeValueAsString(expected); + + EnvironmentData actual = mapper.readValue(serializedEnvData, EnvironmentData.class); + + assertEquals(expected.getEnvironmentName(), actual.getEnvironmentName()); + assertEquals(expected.getAdminIamRoleArn(), actual.getAdminIamRoleArn()); + assertEquals(expected.getCmsIamRoleArn(), actual.getCmsIamRoleArn()); + assertEquals(expected.getRootIamRoleArn(), actual.getRootIamRoleArn()); + assertEquals(expected.getRegionData().size(), actual.getRegionData().size()); + assertEquals(expected.getPrimaryRegion(), actual.getPrimaryRegion()); + } + +} diff --git a/src/test/java/com/nike/cerberus/generator/VaultAclGeneratorTest.java b/src/test/java/com/nike/cerberus/generator/VaultAclGeneratorTest.java deleted file mode 100644 index 3caf303e..00000000 --- a/src/test/java/com/nike/cerberus/generator/VaultAclGeneratorTest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2016 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.generator; - -import com.github.mustachejava.Mustache; -import com.github.mustachejava.MustacheFactory; -import com.nike.cerberus.domain.configuration.VaultAclEntry; -import com.nike.cerberus.domain.template.VaultAclInput; -import com.nike.cerberus.util.UuidSupplier; -import org.junit.Test; -import org.mockito.ArgumentCaptor; - -import java.io.IOException; -import java.io.Writer; - -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class VaultAclGeneratorTest { - - @Test - public void testGenerate() throws IOException { - UuidSupplier uuidSupplier = mock(UuidSupplier.class); - MustacheFactory mustacheFactory = mock(MustacheFactory.class); - Mustache mustache = mock(Mustache.class); - - String aclToken = "acl-token"; - when(uuidSupplier.get()).thenReturn(aclToken); - when(mustacheFactory.compile("templates/vault-acl.json.mustache")).thenReturn(mustache); - - ArgumentCaptor writer = ArgumentCaptor.forClass(Writer.class); - ArgumentCaptor input = ArgumentCaptor.forClass(VaultAclInput.class); - - when(mustache.execute(writer.capture(), input.capture())).thenReturn(mock(Writer.class)); - - VaultAclGenerator generator = new VaultAclGenerator(uuidSupplier, mustacheFactory); - - // invoke method under test - VaultAclEntry result = generator.generate(); - - assertEquals(aclToken, input.getValue().getAclToken()); - assertEquals(aclToken, result.getAclToken()); - assertEquals(writer.getValue().toString(), result.getEntry()); // test could be improved, compares empty Strings here - } - -} \ No newline at end of file diff --git a/src/test/java/com/nike/cerberus/operation/core/RestoreCompleteCerberusDataFromS3BackupOperationTest.java b/src/test/java/com/nike/cerberus/operation/core/RestoreCompleteCerberusDataFromS3BackupOperationTest.java deleted file mode 100644 index 77d71e28..00000000 --- a/src/test/java/com/nike/cerberus/operation/core/RestoreCompleteCerberusDataFromS3BackupOperationTest.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2017 Nike, Inc. - * - * 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. - */ - -package com.nike.cerberus.operation.core; - -import com.nike.cerberus.client.CerberusAdminClient; -import com.nike.cerberus.client.CerberusAdminClientFactory; -import com.nike.cerberus.module.CerberusModule; -import com.nike.cerberus.service.ConsoleService; -import com.nike.vault.client.VaultAdminClient; -import org.apache.commons.io.IOUtils; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; - -import java.io.IOException; - -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.MockitoAnnotations.initMocks; - -public class RestoreCompleteCerberusDataFromS3BackupOperationTest { - - private RestoreCerberusBackupOperation operation; - - @Mock - private CerberusAdminClientFactory adminClientFactory; - - @Mock - private ConsoleService consoleService; - - @Before - public void before() { - initMocks(this); - operation = new RestoreCerberusBackupOperation(CerberusModule.configObjectMapper(), consoleService, adminClientFactory); - } - - @Test - public void test_that_process_backup_can_handle_nested_maps() throws IOException { - String serializedDecryptedBackupJson = IOUtils.toString( - getClass().getClassLoader() - .getResourceAsStream("com/nike/cerberus/operation/core/nested-map-sdb-backup.json") - ); - - CerberusAdminClient client = mock(CerberusAdminClient.class); - RestoreCerberusBackupOperation operationSpy = spy(operation); - doNothing().when(operationSpy).deleteAllSecrets(anyString(), any(VaultAdminClient.class)); - operationSpy.processBackup(serializedDecryptedBackupJson, client); - - - } - - - - -} diff --git a/src/test/java/com/nike/cerberus/operation/rds/RdsServiceTest.java b/src/test/java/com/nike/cerberus/operation/rds/RdsServiceTest.java new file mode 100644 index 00000000..5d9cab5b --- /dev/null +++ b/src/test/java/com/nike/cerberus/operation/rds/RdsServiceTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.operation.rds; + +import com.amazonaws.services.rds.AmazonRDSClient; +import com.amazonaws.services.rds.model.DBClusterSnapshot; +import com.nike.cerberus.service.AwsClientFactory; +import com.nike.cerberus.service.RdsService; +import com.nike.cerberus.store.ConfigStore; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.temporal.ChronoUnit; +import java.util.Date; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class RdsServiceTest { + + @Mock + private ConfigStore configStore; + + private final static String envName = "test"; + + private RdsService rdsService; + + @Mock + private AwsClientFactory rdsClientFactory; + + @Before + public void before() { + rdsService = new RdsService(rdsClientFactory, configStore, envName); + } + + @Test + public void test_that_wasSnapshotGeneratedFromCmsCluster_returns_true_if_id_matches() { + boolean actual = rdsService.wasSnapshotGeneratedFromCmsCluster(new DBClusterSnapshot() + .withDBClusterSnapshotIdentifier("rds:test-cerberus-database-cmsdatabasecluster-jns1lasdf9d-2017-12-12-18-07")); + + assertTrue(actual); + } + + @Test + public void test_that_wasSnapshotGeneratedFromCmsCluster_returns_false_for_a_different_env() { + boolean actual = rdsService.wasSnapshotGeneratedFromCmsCluster(new DBClusterSnapshot() + .withDBClusterSnapshotIdentifier("rds:dev-cerberus-database-cmsdatabasecluster-jns1lasdf9d-2017-12-12-18-07")); + + assertFalse(actual); + } + + @Test + public void test_that_wasSnapshotGeneratedFromCmsCluster_returns_false_if_id_does_not_matche() { + boolean actual = rdsService.wasSnapshotGeneratedFromCmsCluster(new DBClusterSnapshot() + .withDBClusterSnapshotIdentifier("rds:some-other-db-cluster-jns1lasdf9d-2017-12-12-18-07")); + + assertFalse(actual); + } + + @Test + public void test_that_isSnapshotNewerThan24Hours_returns_true_if_snapshot_is_newer_than_24h() { + DBClusterSnapshot ss = new DBClusterSnapshot().withSnapshotCreateTime(Date.from(Instant.now().atZone(ZoneId.of("UTC")).toInstant())); + + boolean actual = rdsService.isSnapshotNewerThanGivenDays(ss, 1); + + assertTrue(actual); + } + + @Test + public void test_that_isSnapshotNewerThan24Hours_returns_false_if_snapshot_is_older_than_24h() { + DBClusterSnapshot ss = new DBClusterSnapshot().withSnapshotCreateTime(Date.from(Instant.now().atZone(ZoneId.of("UTC")) + .minus(1, ChronoUnit.WEEKS).toInstant())); + + boolean actual = rdsService.isSnapshotNewerThanGivenDays(ss, 1); + + assertFalse(actual); + } + +} diff --git a/src/test/java/com/nike/cerberus/service/AmiTagCheckServiceTest.java b/src/test/java/com/nike/cerberus/service/AmiTagCheckServiceTest.java index 10993704..2ad8e631 100644 --- a/src/test/java/com/nike/cerberus/service/AmiTagCheckServiceTest.java +++ b/src/test/java/com/nike/cerberus/service/AmiTagCheckServiceTest.java @@ -18,27 +18,45 @@ import com.amazonaws.AmazonServiceException; import com.amazonaws.services.ec2.AmazonEC2; +import com.amazonaws.services.ec2.AmazonEC2Client; import com.amazonaws.services.ec2.model.DescribeImagesRequest; import com.amazonaws.services.ec2.model.DescribeImagesResult; import com.amazonaws.services.ec2.model.Filter; import com.amazonaws.services.ec2.model.Image; +import com.nike.cerberus.store.ConfigStore; +import org.junit.Before; import org.junit.Test; - -import java.util.List; +import org.mockito.Mock; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.initMocks; public class AmiTagCheckServiceTest { + @Mock + private ConfigStore configStore; + + @Mock + private AmazonEC2Client ec2Client; + + @Mock + private AwsClientFactory amazonEC2ClientFactory; + + @Before + public void before() { + initMocks(this); + when(amazonEC2ClientFactory.getClient(any())).thenReturn(ec2Client); + } + @Test public void isAmiWithTagExistTrue() { - AmazonEC2 ec2Client = mock(AmazonEC2.class); - AmiTagCheckService amiTagCheckService = new AmiTagCheckService(ec2Client); + AmiTagCheckService amiTagCheckService = new AmiTagCheckService(amazonEC2ClientFactory, configStore); String amiId = "ami-1234abcd"; String tagName = "sometag"; @@ -51,7 +69,7 @@ public void isAmiWithTagExistTrue() { ) ).thenReturn( new DescribeImagesResult().withImages(new Image()) - ); + ); // invoke method under test assertTrue(amiTagCheckService.isAmiWithTagExist(amiId, tagName, tagValue)); @@ -60,7 +78,7 @@ public void isAmiWithTagExistTrue() { @Test public void isAmiWithTagExistFalse() { AmazonEC2 ec2Client = mock(AmazonEC2.class); - AmiTagCheckService amiTagCheckService = new AmiTagCheckService(ec2Client); + AmiTagCheckService amiTagCheckService = new AmiTagCheckService(amazonEC2ClientFactory, configStore); String amiId = "ami-1234abcd"; String tagName = "sometag"; @@ -73,7 +91,7 @@ public void isAmiWithTagExistFalse() { ) ).thenReturn( new DescribeImagesResult() - ); + ); // invoke method under test assertFalse(amiTagCheckService.isAmiWithTagExist(amiId, tagName, tagValue)); @@ -82,7 +100,7 @@ public void isAmiWithTagExistFalse() { @Test public void isAmiWithTagExistNotFound() { AmazonEC2 ec2Client = mock(AmazonEC2.class); - AmiTagCheckService amiTagCheckService = new AmiTagCheckService(ec2Client); + AmiTagCheckService amiTagCheckService = new AmiTagCheckService(amazonEC2ClientFactory, configStore); String amiId = "ami-1234abcd"; String tagName = "sometag"; @@ -105,7 +123,7 @@ public void isAmiWithTagExistNotFound() { @Test public void isAmiWithTagExistThrowException() { AmazonEC2 ec2Client = mock(AmazonEC2.class); - AmiTagCheckService amiTagCheckService = new AmiTagCheckService(ec2Client); + AmiTagCheckService amiTagCheckService = new AmiTagCheckService(amazonEC2ClientFactory, configStore); String amiId = "ami-1234abcd"; String tagName = "sometag"; diff --git a/src/test/java/com/nike/cerberus/service/AutoScalingServiceTest.java b/src/test/java/com/nike/cerberus/service/AutoScalingServiceTest.java index 197dc98c..7b6101f9 100644 --- a/src/test/java/com/nike/cerberus/service/AutoScalingServiceTest.java +++ b/src/test/java/com/nike/cerberus/service/AutoScalingServiceTest.java @@ -16,7 +16,7 @@ package com.nike.cerberus.service; -import com.amazonaws.services.autoscaling.AmazonAutoScaling; +import com.amazonaws.services.autoscaling.AmazonAutoScalingClient; import com.amazonaws.services.autoscaling.model.AutoScalingGroup; import com.amazonaws.services.autoscaling.model.DescribeAutoScalingGroupsRequest; import com.amazonaws.services.autoscaling.model.DescribeAutoScalingGroupsResult; @@ -24,34 +24,49 @@ import com.amazonaws.services.autoscaling.model.ExitStandbyRequest; import com.amazonaws.services.autoscaling.model.Instance; import com.amazonaws.services.autoscaling.model.UpdateAutoScalingGroupRequest; -import com.amazonaws.services.ec2.AmazonEC2; +import com.amazonaws.services.ec2.AmazonEC2Client; import com.amazonaws.services.ec2.model.DescribeInstancesRequest; import com.amazonaws.services.ec2.model.DescribeInstancesResult; import com.amazonaws.services.ec2.model.Reservation; +import com.nike.cerberus.store.ConfigStore; import org.junit.Before; import org.junit.Test; +import org.mockito.Mock; import java.util.List; import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.mock; +import static org.mockito.Matchers.any; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.initMocks; public class AutoScalingServiceTest { - private AmazonAutoScaling autoScalingClient; + @Mock + private ConfigStore configStore; - private AmazonEC2 ec2Client; + @Mock + private AmazonAutoScalingClient autoScalingClient; + + @Mock + private AmazonEC2Client ec2Client; + + @Mock + private AwsClientFactory amazonEC2ClientFactory; + + @Mock + private AwsClientFactory amazonAutoScalingClientFactory; private AutoScalingService autoScalingService; @Before public void setup() { - autoScalingClient = mock(AmazonAutoScaling.class); - ec2Client = mock(AmazonEC2.class); + initMocks(this); + when(amazonEC2ClientFactory.getClient(any())).thenReturn(ec2Client); + when(amazonAutoScalingClientFactory.getClient(any())).thenReturn(autoScalingClient); - autoScalingService = new AutoScalingService(autoScalingClient, ec2Client); + autoScalingService = new AutoScalingService(amazonAutoScalingClientFactory, amazonEC2ClientFactory, configStore); } @Test @@ -132,8 +147,8 @@ public void testIncrementMinInstancesForAsgHappy() { .withAutoScalingGroups( new AutoScalingGroup().withInstances( new Instance().withInstanceId(instanceId)) - .withMinSize(minSize) - ) + .withMinSize(minSize) + ) ); autoScalingService.updateMinInstancesForAutoScalingGroup(logicalId, minSize - 1); diff --git a/src/test/java/com/nike/cerberus/service/CertificateServiceTest.java b/src/test/java/com/nike/cerberus/service/CertificateServiceTest.java new file mode 100644 index 00000000..aa9197ff --- /dev/null +++ b/src/test/java/com/nike/cerberus/service/CertificateServiceTest.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2017 Nike, Inc. + * + * 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. + */ + +package com.nike.cerberus.service; + +import com.amazonaws.services.route53.AmazonRoute53; +import com.amazonaws.services.route53.AmazonRoute53Client; +import com.nike.cerberus.store.ConfigStore; +import com.nike.cerberus.util.UuidSupplier; +import org.apache.commons.io.FileUtils; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; + +import java.io.File; +import java.io.IOException; +import java.security.KeyPair; +import java.util.UUID; + +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.initMocks; + +public class CertificateServiceTest { + + @Mock private ConsoleService console; + @Mock private AmazonRoute53Client route53; + @Mock AwsClientFactory route53ClientFactory; + @Mock private UuidSupplier uuidSupplier; + @Mock private ConfigStore configStore; + @Mock private IdentityManagementService identityManagementService; + + private CertificateService certificateService; + + @Before + public void before() { + initMocks(this); + + when(route53ClientFactory.getClient(any())).thenReturn(route53); + certificateService = new CertificateService( + console, + route53ClientFactory, + uuidSupplier, + configStore, + identityManagementService, + "test", + "us-west-2" + ); + } + + /** + * @throws IOException + */ + @Test + public void test_that_createPKCS8PrivateKeyPemFileFromKeyPair_creates_pkcs8_file() throws IOException { + File certDir = new File("build/certs/" + UUID.randomUUID().toString()); + FileUtils.forceMkdir(certDir); + KeyPair privateKey = certificateService.loadOrCreateKeyPair( + new File(certDir.getAbsolutePath() + File.separator + CertificateService.DOMAIN_PKCS1_KEY_FILE)); + + certificateService.createPKCS8PrivateKeyPemFileFromKeyPair(privateKey, certDir); + + File pkcs8 = new File(certDir.getAbsolutePath() + File.separator + CertificateService.DOMAIN_PKCS8_KEY_FILE); + + assertTrue(pkcs8.exists()); + } + +} diff --git a/src/test/java/com/nike/cerberus/service/Ec2ServiceTest.java b/src/test/java/com/nike/cerberus/service/Ec2ServiceTest.java index 19976453..44db04ca 100644 --- a/src/test/java/com/nike/cerberus/service/Ec2ServiceTest.java +++ b/src/test/java/com/nike/cerberus/service/Ec2ServiceTest.java @@ -18,6 +18,7 @@ import com.amazonaws.AmazonServiceException; import com.amazonaws.services.ec2.AmazonEC2; +import com.amazonaws.services.ec2.AmazonEC2Client; import com.amazonaws.services.ec2.model.AvailabilityZone; import com.amazonaws.services.ec2.model.AvailabilityZoneState; import com.amazonaws.services.ec2.model.DescribeAvailabilityZonesResult; @@ -32,8 +33,10 @@ import com.amazonaws.services.ec2.model.KeyPairInfo; import com.amazonaws.services.ec2.model.RebootInstancesRequest; import com.amazonaws.services.ec2.model.Reservation; +import com.nike.cerberus.store.ConfigStore; import org.junit.Before; import org.junit.Test; +import org.mockito.Mock; import java.util.List; @@ -44,21 +47,30 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.initMocks; public class Ec2ServiceTest { - private AmazonEC2 ec2Client; + @Mock + ConfigStore configStore; + + @Mock + AmazonEC2Client ec2Client; + + @Mock + AwsClientFactory amazonEC2ClientFactory; private Ec2Service ec2Service; @Before public void setup() { - ec2Client = mock(AmazonEC2.class); - - ec2Service = new Ec2Service(ec2Client); + initMocks(this); + when(amazonEC2ClientFactory.getClient(any())).thenReturn(ec2Client); + ec2Service = new Ec2Service(amazonEC2ClientFactory, configStore); } @Test diff --git a/src/test/java/com/nike/cerberus/service/SaltGeneratorTest.java b/src/test/java/com/nike/cerberus/service/SaltGeneratorTest.java new file mode 100644 index 00000000..4681a98d --- /dev/null +++ b/src/test/java/com/nike/cerberus/service/SaltGeneratorTest.java @@ -0,0 +1,22 @@ +package com.nike.cerberus.service; + +import org.apache.commons.lang3.StringUtils; +import org.junit.Test; + +import java.security.NoSuchAlgorithmException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +public class SaltGeneratorTest { + + @Test + public void sanityTestSalt() throws NoSuchAlgorithmException { + SaltGenerator generator = new SaltGenerator(); + String s1 = generator.generateSalt(); + String s2 = generator.generateSalt(); + assertFalse(StringUtils.equals(s1, s2)); + assertEquals(88, s1.length()); + assertEquals(88, s2.length()); + } +} \ No newline at end of file diff --git a/src/test/resources/environment.yaml b/src/test/resources/environment.yaml index d817cdf1..f68fd295 100644 --- a/src/test/resources/environment.yaml +++ b/src/test/resources/environment.yaml @@ -1,107 +1,149 @@ -version: 1 -# proxy information -# You only need to configure this, if you plan on white listing proxy boxes and will need to jump -# through them to talk to the instances that will live in the Cerberus VPC -# If your not using a proxy either delete this section or make type = DIRECT for direct connecting -proxy-config: - host: localhost - port: 9000 - # [DIRECT, HTTP, SOCKS] - type: SOCKS - # The name of the environment environment-name: demo -# The region the environment will run in -region: us-west-2 -# Costcenter for where to bill provisioned resources. Will be tagged on all resources. -cost-center: 11111 -# The e-mail for who owns the provisioned resources. Will be tagged on all resources. -owner-email: obvisouly.fake@nike.com -# The owning group for the provision resources. Will be tagged on all resources. -owner-group: cloud platform engineering -# A IAM role ARN that will be given elevated privileges for the KMS CMK created., -# If you don't separate root access from admins just use the root role here -admin-role-arn: arn:aws:iam::111111111:role/onelogin-roles-OneLoginAdminRole-2222222222 -# The Route 53 hosted zone name that will be created for CNAME records used by internal ELBs. -vpc-hosted-zone-name: demo.internal.cerberus-oss.io -# The hostname that will be exposed for Cerberus -hostname: demo.cerberis-oss.io -# The hostedZoneId that will allow the registration of the hostname +# Optional map of tags that will be applied to any resource created by the CloudFormation Service in this CLI +global-tags: + # Name: cerberus-{env} is automatically added to this map + costCenter: 11111 + ownerEmail: obvisouly.fake@nike.com + ownerGroup: engineering-team-name + +# A IAM role ARN that will be given elevated privileges for the KMS CMK created. +admin-role-arn: arn:aws:iam::111111111:role/admin + +# This is used to create DNS records and TLS certificates +# By default the following records will be created +# {environment-name}.{base-domain} ex: demo.example.com +# origin.{environment-name}.{base-domain} ex: origin.demo.example.com +# {environment-name}.{region}.{base-domain} ex: demo.us-west-2.example.com +# In this example: +# demo.example.com points to origin.demo.example.com +# origin.demo.example points to demo.us-west-2.example.com +# demo.us-west-2.example.com points to the ALB +# This extra hop allows for more flexibility in future infrastructure operations +base-domain-name: example.com + +# Edge domain name defaults to {environment}.{base-domain} +edge-domain-name-override: new-demo.example.com + +# Origin domain name defaults to origin.{environment-name}.{base-domain} override it here +origin-domain-name-override: origin.new-demo.demo.com + +# Add additional subject names to the generated cert +# For example if your standing up a new architecture edition of Cerberus and you still have the existing env up you can add those domains +# here so your certificates are still valid when you cut over the edge domain name to the old domain name. +additional-subject-names: + - demo.example.com + - origin.demo.example.com + - demo.us-west-2.example.com + - demo.us-east-1.example.com + +# The hosted zone id that will allow the registration of the above domain names hosted-zone-id: X5CT6JROG9F2DR -# Here we can define the ports and CIDRs that we want to allow ingress into the Cerberus env VPC +# The SSL Policy that will get applied to the application load balancer +# see http://docs.aws.amazon.com/elasticloadbalancing/latest/classic/elb-security-policy-table.html +# If this is not defined than we default to 'TLS-1-2-2017-01' in the old cerberus arch we defaulted to +# 'ELBSecurityPolicy-2016-08' if you have clients running on deprecated Java versions and need to maintain tls 1.0 and +# 1.1 support use ELBSecurityPolicy-2016-08 +# load-balancer-ssl-policy-override: ELBSecurityPolicy-2016-08 + +# If you wish to use your own certs set this to false or else fill out the ACME api information +# Else if you are providing your own keys and certs the following files are required. +# +# domain-private-key-pkcs1.pem - Used by the ALB +# The private key in pkcs1 pem format used to sign your csr when generating your certs. +# PKCS #1 keys are human readable files that look like the following +# -----BEGIN RSA PRIVATE KEY----- +# ... +# -----END RSA PRIVATE KEY----- +# +# domain-private-key-pkcs8.pem - Used by CMS to secure the connection between the ALB and the Java Netty web server +# The same private key as above but in pkcs8 pem format +# PKCS #8 keys are human readable files that look like the following +# -----BEGIN PRIVATE KEY----- +# ... +# -----END PRIVATE KEY----- +# +# domain-public-key.pem - Used by the ALB +# The public key for the above private key in PKCS1 pem format +# PKCS #1 pem keys are human readable files that look like the following +# -----BEGIN RSA PUBLIC KEY----- +# ... +# -----END RSA PUBLIC KEY----- +# +# domain-cert.crt - Used by the ALB and CMS +# The x509 certificate +# +# chain.crt - Used by the ALB +# The x509 certificate chain +# +generate-keys-and-certs: true +# API URL to an ACME provider, if not a special ACME url, should end in /directory +# The ACME provider must support DNS-01 domain verification challenges such as the LetsEncrypt Boulder ACME V1 impl. +# LetsEncrypt is a free, automated, and open Certificate Authority, visit https://letsencrypt.org/ for more info. +#acme-api-url: acme://letsencrypt.org/staging #For testing. +acme-api-url: acme://letsencrypt.org +# If you use LetsEncrypt and don't have their certs installed in your trust store you can set this +# to true to use a hardcoded cert provided by the acme4j client, or visit https://letsencrypt.org/certificates/ +enable-le-cert-fix: true +# Contact email to send to ACME provider when creating certs +acme-contact-email: justin.field@example.com +# The path to the certs, if you are generating the certs using an ACME provider +# If you wish to provide your own certs set generate to false and +# see the comment header above generate-keys-and-certs for required files that must be in this folder. +certificate-directory: /path/to/certs/ + +# Here we can define the ports and CIDRs that we want to allow ingress into the Cerberus VPC for this environment # You can add your current ip or proxy boxes here and use the above proxy config vpc-access-whitelist: ports: - - 443 - - 8080 - - 8200 - - 8500 - - 8400 - - 22 + - 8443 #CMS SSL Port for health check rolling reboot command + - 22 #SSH cidrs: - 50.39.106.150/32 -consul: - cert-path: /home/fieldju/development/cerberus_environments/demo/certs/ - ami-id: ami-1111 - instance-size: m3.medium - key-pair-name: cerberus-test - desired-instances: 2 - max-instances: 4 - min-instances: 2 - -vault: - cert-path: /home/fieldju/development/cerberus_environments/demo/certs/ - ami-id: ami-2222 - instance-size: m3.medium - key-pair-name: cerberus-test - desired-instances: 2 - max-instances: 4 - min-instances: 2 - +# Global Cerberus Management Service config management-service: - cert-path: /home/fieldju/development/cerberus_environments/demo/certs/ - ami-id: ami-3333 - instance-size: m3.medium - key-pair-name: cerberus-test # Group that has admin privileges in CMS. - admin-group: lst-cerberus-admins + admin-group: cerberus-admins # Dynamic parameters for setting additional properties in the CMS + # See https://github.com/Nike-Inc/cerberus-management-service#configurable-properties + # Also see https://github.com/Nike-Inc/cerberus-management-service/blob/master/src/main/resources/cms.conf + # In general any property can be overridden here for the initial setup, some of the config in cms.conf cannot be overridden. + # Future changes can be made with the update-cms-config command. + # To view the current config you can use cerberus -e env -r us-west-2 view-config --config-path data/cms/environment.properties properties: - cms.auth.connector=com.nike.cerberus.auth.connector.onelogin.OneLoginAuthConnector - auth.connector.onelogin.api_region=us - auth.connector.onelogin.client_id=123 - auth.connector.onelogin.client_secret=312 - - auth.connector.onelogin.subdomain=nike - -gateway: - cert-path: /home/fieldju/development/cerberus_environments/demo/certs/ - ami-id: ami-4444 - instance-size: m3.medium - key-pair-name: cerberus-test + - auth.connector.onelogin.subdomain=example -dashboard: - # The url of the dashboard artifact, probably the newest release on the dashboard Github release page - artifact-url: https://github.com/Nike-Inc/cerberus-management-dashboard/releases/download/v0.8.0/cerberus-dashboard.tar.gz - # The secondary artifact that will get merged into the above artifact before getting uploaded to s3, we use this for custom help page. - override-artifact-url: https://someplace.com/where/you/want/to/store/this.tar.gz -# By default The gateway elb for Cerberus is exposed to the public internet on port 443 -# We configure CloudFront and WAF to enable things like manual black and white lists as well as auto black listing -edge-security: - # The artifact url for the CloudFront Lambda that process CloudFront Logs to enable things like rate limiting and KPI reporting - cloudfront-lambda-artifact-url: https://github.com/Nike-Inc/cerberus-cloudfront-lambda/releases/download/v1.1.0/cerberus-cloudfront-lambda.jar - # The artifact url to the lambda that ensures only CloudFront IPs are white listed to talk to the origin ELB - cloudfront-security-group-ip-sync-lambda-artifact-url: https://github.com/Nike-Inc/cerberus-lifecycle-cli/raw/master/update_security_groups.zip - # The maximum number of requests from an IP per minute that can flow through the gateway before being auto blocked. - rate-limit-per-minute: 100 - # Time in minutes to block an ip that violates the rate limit. - rate-limit-violation-block-period-in-minutes: 60 - # [Optional delete if you do not need] If you provide a Google Analytics tracking id, the KPI processor will send events to that GA Account. - google-analytics-tracking-id: abc123 - # [Optional delete if you do not need] If you provide a web hook url for slack the cloudfront lambda will send messages on errors and summary info. - slack-web-hook-url: https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX - # [Optional delete if you do not need] If you provide an emoji or an icon url the lambda will use it when sending messages. - slack-icon: https://raw.githubusercontent.com/Nike-Inc/cerberus/master/images/cerberus-github-logo-black-filled-circle%40500px.png \ No newline at end of file +region-specific-configuration: + us-west-2: + primary: true + rds: + size: db.r3.large + # To create a new environment from a rds backup snapshot uncomment below and supply the db cluster snapshot identifier + # db-cluster-identifier: foo-bar + management-service: + # The ami id to use for CMS, see https://github.com/Nike-Inc/cerberus-util-scripts to bake an AMI + ami-id: ami-3333 + # The instance size to use for the CMS ASG + instance-size: m3.medium + # The ssh key pair to use for the instances + key-pair-name: cerberus-test + # The desired instance count for the ASG, defaults to 3 + #desired-instances: 3 + # The max instance count for the ASG, defaults to 3 + #max-instances: 4 + # The desired instance count for the ASG, defaults to 3 + #min-instances: 3 + # Load balancer domain names default to {environment-name}.{region}.{base-domain} override it here + load-balancer-domain-name-override: new-demo.us-west-2.demo.com + # Cerberus uses the Amazon encryption library backed by KMS to encrypt secure data, this library supports encrypting + # data with data keys created by multiple KMS CMKs to make the data more highly available in case of region outage. + # Cerberus Management Service requires at least 2 regions for CMK creation. + us-east-1: ~ \ No newline at end of file