modules/mu/providers/aws.rb in cloud-mu-3.2.0 vs modules/mu/providers/aws.rb in cloud-mu-3.3.0
- old
+ new
@@ -842,78 +842,145 @@
}
@@instance_types
end
+ @@certificates = {}
+
# AWS can stash API-available certificates in Amazon Certificate Manager
# or in IAM. Rather than make people crazy trying to get the syntax
# correct in our Baskets of Kittens, let's have a helper that tries to do
# the right thing, and only raise an exception if we need help to
# disambiguate.
# @param name [String]: The name of the cert. For IAM certs this can be any IAM name; for ACM, it's usually the domain name. If multiple matches are found, or no matches, an exception is raised.
# @param id [String]: The ARN of a known certificate. We just validate that it exists. This is ignored if a name parameter is supplied.
# @return [String]: The ARN of a matching certificate that is known to exist. If it is an ACM certificate, we also know that it is not expired.
- def self.findSSLCertificate(name: nil, id: nil, region: myRegion)
- if name.nil? and name.empty? and id.nil? and id.empty?
+ def self.findSSLCertificate(name: nil, id: nil, region: myRegion, credentials: nil, raise_on_missing: true)
+ if (name.nil? or name.empty?) and (id.nil? or id.empty?)
raise MuError, "Can't call findSSLCertificate without specifying either a name or an id"
end
+ if id and @@certificates[id]
+ return [id, @@certificates[id]]
+ end
if !name.nil? and !name.empty?
matches = []
- acmcerts = MU::Cloud::AWS.acm(region: region).list_certificates(
+ acmcerts = MU::Cloud::AWS.acm(region: region, credentials: credentials).list_certificates(
certificate_statuses: ["ISSUED"]
)
acmcerts.certificate_summary_list.each { |cert|
matches << cert.certificate_arn if cert.domain_name == name
}
begin
- iamcert = MU::Cloud::AWS.iam.get_server_certificate(
+ iamcert = MU::Cloud::AWS.iam(credentials: credentials).get_server_certificate(
server_certificate_name: name
)
rescue Aws::IAM::Errors::ValidationError, Aws::IAM::Errors::NoSuchEntity
# valid names for ACM certs can break here, and that's ok to ignore
end
if !iamcert.nil?
matches << iamcert.server_certificate.server_certificate_metadata.arn
end
if matches.size == 1
- return matches.first
+ id = matches.first
elsif matches.size == 0
- raise MuError, "No IAM or ACM certificate named #{name} was found in #{region}"
+ if raise_on_missing
+ raise MuError, "No IAM or ACM certificate named #{name} was found in #{region}"
+ else
+ return nil
+ end
elsif matches.size > 1
raise MuError, "Multiple certificates named #{name} were found in #{region}. Remove extras or use ssl_certificate_id to supply the exact ARN of the one you want to use."
end
end
+ domains = []
+
if id.match(/^arn:aws(?:-us-gov)?:acm/)
- resp = MU::Cloud::AWS.acm(region: region).get_certificate(
+ resp = MU::Cloud::AWS.acm(region: region).describe_certificate(
certificate_arn: id
)
- if resp.nil?
+
+ if resp.nil? or resp.certificate.nil?
raise MuError, "No such ACM certificate '#{id}'"
end
+ domains << resp.certificate.domain_name
+ if resp.certificate.subject_alternative_names
+ domains.concat(resp.certificate.subject_alternative_names)
+ end
elsif id.match(/^arn:aws(?:-us-gov)?:iam/)
resp = MU::Cloud::AWS.iam.list_server_certificates
if resp.nil?
raise MuError, "No such IAM certificate '#{id}'"
end
resp.server_certificate_metadata_list.each { |cert|
+
if cert.arn == id
if cert.expiration < Time.now
MU.log "IAM SSL certificate #{cert.server_certificate_name} (#{id}) is EXPIRED", MU::WARN
end
- return id
+ @@certificates[id] = [cert.server_certificate_name]
+ return [id, [cert.server_certificate_name]]
end
}
raise MuError, "No such IAM certificate '#{id}'"
else
raise MuError, "The format of '#{id}' doesn't look like an ARN for either Amazon Certificate Manager or IAM"
end
- id
+ @@certificates[id] = domains.uniq
+ [id, domains.uniq]
end
+ # Given a domain name and an ACM or IAM certificate identifier, sort out
+ # whether the domain name is "covered" by the certificate
+ # @param name [String]
+ # @param cert_id [String]
+ # @return [Boolean]
+ def self.nameMatchesCertificate(name, cert_id)
+ _id, domains = findSSLCertificate(id: cert_id)
+ return false if !domains
+ domains.each { |dom|
+ if dom == name or
+ (dom =~ /^\*/ and name =~ /.*#{Regexp.quote(dom[1..-1])}/)
+ return true
+ end
+ }
+ false
+ end
+
+ # Given a {MU::Config::Ref} block for an IAM or ACM SSL certificate,
+ # look up and validate the specified certificate. This is intended to be
+ # invoked from resource implementations' +validateConfig+ methods.
+ # @param certblock [Hash,MU::Config::Ref]:
+ # @param region [String]: Default region to use when looking up the certificate, if its configuration block does not specify any
+ # @param credentials [String]: Default credentials to use when looking up the certificate, if its configuration block does not specify any
+ # @return [Boolean]
+ def self.resolveSSLCertificate(certblock, region: nil, credentials: nil)
+ return false if !certblock
+ ok = true
+
+ certblock['region'] ||= region if !certblock['id']
+ certblock['credentials'] ||= credentials
+ cert_arn, cert_domains = MU::Cloud::AWS.findSSLCertificate(
+ name: certblock["name"],
+ id: certblock["id"],
+ region: certblock['region'],
+ credentials: certblock['credentials']
+ )
+
+ if cert_arn
+ certblock['id'] ||= cert_arn
+ end
+
+ ['region', 'credentials'].each { |field|
+ certblock.delete(field) if certblock[field].nil?
+ }
+
+ [cert_arn, cert_domains]
+ end
+
# Amazon Certificate Manager API
def self.acm(region: MU.curRegion, credentials: nil)
region ||= myRegion
@@acm_api[credentials] ||= {}
@@acm_api[credentials][region] ||= MU::Cloud::AWS::AmazonEndpoint.new(api: "ACM", region: region, credentials: credentials)
@@ -1027,10 +1094,18 @@
@@cloudwatchlogs_api[credentials] ||= {}
@@cloudwatchlogs_api[credentials][region] ||= MU::Cloud::AWS::AmazonEndpoint.new(api: "CloudWatchLogs", region: region, credentials: credentials)
@@cloudwatchlogs_api[credentials][region]
end
+ # Amazon's CloudWatchEvents API
+ def self.cloudwatchevents(region: MU.curRegion, credentials: nil)
+ region ||= myRegion
+ @@cloudwatchevents_api[credentials] ||= {}
+ @@cloudwatchevents_api[credentials][region] ||= MU::Cloud::AWS::AmazonEndpoint.new(api: "CloudWatchEvents", region: region, credentials: credentials)
+ @@cloudwatchevents_api[credentials][region]
+ end
+
# Amazon's CloudFront API
def self.cloudfront(region: MU.curRegion, credentials: nil)
region ||= myRegion
@@cloudfront_api[credentials] ||= {}
@@cloudfront_api[credentials][region] ||= MU::Cloud::AWS::AmazonEndpoint.new(api: "CloudFront", region: region, credentials: credentials)
@@ -1115,10 +1190,18 @@
@@dynamo_api[credentials] ||= {}
@@dynamo_api[credentials][region] ||= MU::Cloud::AWS::AmazonEndpoint.new(api: "DynamoDB", region: region, credentials: credentials)
@@dynamo_api[credentials][region]
end
+ # Amazon's DynamoStream API
+ def self.dynamostream(region: MU.curRegion, credentials: nil)
+ region ||= myRegion
+ @@dynamostream_api[credentials] ||= {}
+ @@dynamostream_api[credentials][region] ||= MU::Cloud::AWS::AmazonEndpoint.new(api: "DynamoDBStreams", region: region, credentials: credentials)
+ @@dynamostream_api[credentials][region]
+ end
+
# Amazon's Pricing API
def self.pricing(region: MU.curRegion, credentials: nil)
region ||= myRegion
@@pricing_api[credentials] ||= {}
@@pricing_api[credentials][region] ||= MU::Cloud::AWS::AmazonEndpoint.new(api: "Pricing", region: region, credentials: credentials)
@@ -1163,10 +1246,18 @@
@@kms_api[credentials] ||= {}
@@kms_api[credentials][region] ||= MU::Cloud::AWS::AmazonEndpoint.new(api: "KMS", region: region, credentials: credentials)
@@kms_api[credentials][region]
end
+ # Amazon's CloudFront API
+ def self.cloudfront(region: MU.curRegion, credentials: nil)
+ region ||= myRegion
+ @@cloudfront_api[credentials] ||= {}
+ @@cloudfront_api[credentials][region] ||= MU::Cloud::AWS::AmazonEndpoint.new(api: "CloudFront", region: region, credentials: credentials)
+ @@cloudfront_api[credentials][region]
+ end
+
# Amazon's Organizations API
def self.orgs(credentials: nil)
@@organizations_api ||= {}
# XXX org api doesn't seem to work in many regions
@@organizations_api[credentials] ||= MU::Cloud::AWS::AmazonEndpoint.new(api: "Organizations", credentials: credentials, region: "us-east-1")
@@ -1459,10 +1550,11 @@
require "aws-sdk-core/pricing"
require "aws-sdk-core/apigateway"
require "aws-sdk-core/ecs"
require "aws-sdk-core/eks"
require "aws-sdk-core/cloudwatchlogs"
+ require "aws-sdk-core/cloudwatchevents"
require "aws-sdk-core/elasticloadbalancing"
require "aws-sdk-core/elasticloadbalancingv2"
require "aws-sdk-core/autoscaling"
require "aws-sdk-core/client_waiters"
require "aws-sdk-core/waiters/errors"
@@ -1479,17 +1571,21 @@
@api.method(method_sym).call
end
if !retval.nil?
begin
- page_markers = [:marker, :next_token]
+ page_markers = {
+ :marker => :marker,
+ :next_token => :next_token,
+ :next_marker => :marker
+ }
paginator = nil
new_page = nil
- [:next_token, :marker].each { |m|
+ page_markers.each_key { |m|
if !retval.nil? and retval.respond_to?(m)
paginator = m
- new_page = retval.send(paginator)
+ new_page = retval.send(m)
break
end
}
if paginator and new_page and !new_page.empty?
@@ -1504,16 +1600,16 @@
new_args = arguments ? arguments.dup : [{}]
begin
if new_args.is_a?(Array)
new_args << {} if new_args.empty?
if new_args.size == 1 and new_args.first.is_a?(Hash)
- new_args[0][paginator] = new_page
+ new_args[0][page_markers[paginator]] = new_page
else
MU.log "I don't know how to insert a #{paginator} into these arguments for #{method_sym}", MU::WARN, details: new_args
end
elsif new_args.is_a?(Hash)
- new_args[paginator] = new_page
+ new_args[page_markers[paginator]] = new_page
end
MU.log "Attempting magic pagination for #{method_sym}", MU::DEBUG, details: new_args
# resp = if !arguments.nil? and arguments.size == 1
@@ -1533,11 +1629,11 @@
raise e
end
end
return retval
- rescue Aws::RDS::Errors::Throttling, Aws::EC2::Errors::InternalError, Aws::EC2::Errors::RequestLimitExceeded, Aws::EC2::Errors::Unavailable, Aws::Route53::Errors::Throttling, Aws::ElasticLoadBalancing::Errors::HttpFailureException, Aws::EC2::Errors::Http503Error, Aws::AutoScaling::Errors::Http503Error, Aws::AutoScaling::Errors::InternalFailure, Aws::AutoScaling::Errors::ServiceUnavailable, Aws::Route53::Errors::ServiceUnavailable, Aws::ElasticLoadBalancing::Errors::Throttling, Aws::RDS::Errors::ClientUnavailable, Aws::Waiters::Errors::UnexpectedError, Aws::ElasticLoadBalancing::Errors::ServiceUnavailable, Aws::ElasticLoadBalancingV2::Errors::Throttling, Seahorse::Client::NetworkingError, Aws::IAM::Errors::Throttling, Aws::EFS::Errors::ThrottlingException, Aws::Pricing::Errors::ThrottlingException, Aws::APIGateway::Errors::TooManyRequestsException, Aws::ECS::Errors::ThrottlingException, Net::ReadTimeout, Faraday::TimeoutError, Aws::CloudWatchLogs::Errors::ThrottlingException => e
+ rescue Aws::Lambda::Errors::TooManyRequestsException, Aws::RDS::Errors::Throttling, Aws::EC2::Errors::InternalError, Aws::EC2::Errors::RequestLimitExceeded, Aws::EC2::Errors::Unavailable, Aws::Route53::Errors::Throttling, Aws::ElasticLoadBalancing::Errors::HttpFailureException, Aws::EC2::Errors::Http503Error, Aws::AutoScaling::Errors::Http503Error, Aws::AutoScaling::Errors::InternalFailure, Aws::AutoScaling::Errors::ServiceUnavailable, Aws::Route53::Errors::ServiceUnavailable, Aws::ElasticLoadBalancing::Errors::Throttling, Aws::RDS::Errors::ClientUnavailable, Aws::Waiters::Errors::UnexpectedError, Aws::ElasticLoadBalancing::Errors::ServiceUnavailable, Aws::ElasticLoadBalancingV2::Errors::Throttling, Seahorse::Client::NetworkingError, Aws::IAM::Errors::Throttling, Aws::EFS::Errors::ThrottlingException, Aws::Pricing::Errors::ThrottlingException, Aws::APIGateway::Errors::TooManyRequestsException, Aws::ECS::Errors::ThrottlingException, Net::ReadTimeout, Faraday::TimeoutError, Aws::CloudWatchLogs::Errors::ThrottlingException => e
if e.class.name == "Seahorse::Client::NetworkingError" and e.message.match(/Name or service not known/)
MU.log e.inspect, MU::ERR
raise e
end
retries = retries + 1
@@ -1575,10 +1671,11 @@
@@cloudtrail_api = {}
@@cloudwatch_api = {}
@@wafglobal = {}
@@waf = {}
@@cloudwatchlogs_api = {}
+ @@cloudwatchevents_api = {}
@@cloudfront_api = {}
@@elasticache_api = {}
@@sns_api = {}
@@sqs_api = {}
@@efs_api ={}
@@ -1593,8 +1690,10 @@
@@cognito_ident_api ={}
@@cognito_user_api ={}
@@kms_api ={}
@@organization_api ={}
@@dynamo_api ={}
+ @@dynamostream_api ={}
+ @@cloudfront_api ={}
end
end
end