class MU::Cloud::AWS::CDN

A scheduled task facility as configured in {MU::Config::BasketofKittens::cdns}

Public Class Methods

cleanup(noop: false, deploy_id: MU.deploy_id, ignoremaster: false, credentials: nil, flags: {}) click to toggle source

Remove all cdns associated with the currently loaded deployment. @param noop [Boolean]: If true, will only print what would be done @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server @return [void]

# File modules/mu/providers/aws/cdn.rb, line 145
def self.cleanup(noop: false, deploy_id: MU.deploy_id, ignoremaster: false, credentials: nil, flags: {})

  resp = MU::Cloud::AWS.cloudfront(credentials: credentials).list_distributions
  if resp and resp.distribution_list and resp.distribution_list.items
    delete_threads = []
    ids = Hash[resp.distribution_list.items.map { |distro| [distro.arn, distro] }]

    ids.each_key { |arn|
      tags = MU::Cloud::AWS.cloudfront(credentials: credentials).list_tags_for_resource(resource: arn).tags.items

      found_muid = found_master = false
      name = nil
      tags.each { |tag|
        name = tag.value if tag.key == "Name"
        found_muid = true if tag.key == "MU-ID" && tag.value == deploy_id
        found_master = true if tag.key == "MU-MASTER-IP" && tag.value == MU.mu_public_ip
      }

      if found_muid and (ignoremaster or found_master)
        delete_threads << Thread.new(arn, name) { |my_arn, my_name|
          current = MU::Cloud::AWS.cloudfront(credentials: credentials).get_distribution_config(id: ids[my_arn].id)
          etag = current.etag

          if !noop

            if current.distribution_config.enabled
              newcfg = MU.structToHash(current.distribution_config)
              newcfg[:enabled] = false
              MU.log "Disabling CloudFront distribution #{my_name ? my_name : ids[my_arn].id})", MU::NOTICE
              updated = MU::Cloud::AWS.cloudfront(credentials: credentials).update_distribution(id: ids[my_arn].id, distribution_config: newcfg, if_match: etag)
              etag = updated.etag
            end

          end

          MU.log "Deleting CloudFront distribution #{my_name ? my_name : ids[my_arn].id})"
          if !noop
            ready?(ids[my_arn].id, credentials: credentials)
            MU::Cloud::AWS.cloudfront(credentials: credentials).delete_distribution(id: ids[my_arn].id, if_match: etag)
          end
        }
      end
    }
    delete_threads.each { |t| t.join }
  end

  resp = MU::Cloud::AWS.cloudfront(credentials: credentials).list_cloud_front_origin_access_identities
  if resp and resp.cloud_front_origin_access_identity_list and
     resp.cloud_front_origin_access_identity_list.items.each and
     deploy_id =~ /-\d{10}-[A-Z]{2}/
    resp.cloud_front_origin_access_identity_list.items.each { |ident|
      if ident.comment =~ /^#{Regexp.quote(deploy_id)}-/
        MU.log "Deleting CloudFront origin access identity #{ident.id} (#{ident.comment})"
        if !noop
          getresp = MU::Cloud::AWS.cloudfront(credentials: credentials).get_cloud_front_origin_access_identity(id: ident.id)
          begin
            MU::Cloud::AWS.cloudfront(credentials: credentials).delete_cloud_front_origin_access_identity(id: ident.id, if_match: getresp.etag)
          rescue ::Aws::CloudFront::Errors::CloudFrontOriginAccessIdentityInUse => e
            MU.log "Got #{e.message} deleting #{ident.id}; it likely belongs to a distribution we can't to delete", MU::WARN, details: ident
          end
        end
      end
    }
  end
end
find(**args) click to toggle source

Locate an existing event. @return [Hash<String,OpenStruct>]: The cloud provider's complete descriptions of matching CloudWatch Event

# File modules/mu/providers/aws/cdn.rb, line 213
def self.find(**args)
  found = {}

  MU::Cloud::AWS.cloudfront(credentials: args[:credentials]).list_distributions.distribution_list.items.each { |d|
    next if args[:cloud_id] and ![d.id, d.arn].include?(args[:cloud_id])
    found[d.id] = d
  }

  found
end
isGlobal?() click to toggle source

Does this resource type exist as a global (cloud-wide) artifact, or is it localized to a region/zone? @return [Boolean]

# File modules/mu/providers/aws/cdn.rb, line 131
def self.isGlobal?
  true
end
new(**args) click to toggle source

Initialize this cloud resource object. Calling super will invoke the initializer defined under {MU::Cloud}, which should set the attribtues listed in {MU::Cloud::PUBLIC_ATTRS} as well as applicable dependency shortcuts, like +@vpc+, for us. @param args [Hash]: Hash of named arguments passed via Ruby's double-splat

Calls superclass method
# File modules/mu/providers/aws/cdn.rb, line 23
def initialize(**args)
  super
  @mu_name ||= @deploy.getResourceName(@config["name"])
end
quality() click to toggle source

Denote whether this resource implementation is experiment, ready for testing, or ready for production use.

# File modules/mu/providers/aws/cdn.rb, line 137
def self.quality
  MU::Cloud::ALPHA
end
ready?(id, credentials: nil) click to toggle source

Wait until a distribution is ready (status is Deployed) @param id [String] @param credentials [String]

# File modules/mu/providers/aws/cdn.rb, line 121
def self.ready?(id, credentials: nil)
  desc = nil
  MU.retrier([], loop_if: Proc.new { !desc or desc.status != "Deployed" }, wait: 30, max:60) {
    desc = MU::Cloud::AWS.cloudfront(credentials: credentials).get_distribution(id: id).distribution
  }
end
schema(_config) click to toggle source

Cloud-specific configuration properties. @param _config [MU::Config]: The calling MU::Config object @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource

# File modules/mu/providers/aws/cdn.rb, line 340
def self.schema(_config)
  toplevel_required = []

  schema = {
    "disabled" => {
      "type" => "boolean",
      "description" => "Flag this CloudFront distribution as disabled",
      "default" => false
    },
    "certificate" => MU::Config::Ref.schema(type: "certificate", desc: "Required if any domains have been specified with +aliases+; parser will attempt to autodetect a valid ACM or IAM certificate if not specified.", omit_fields: ["cloud", "tag", "deploy_id"]),
    "behaviors" => {
      "items" => {
        "properties" => {
          "min_ttl" => {
            "type" => "integer",
            "description" => "The minimum amount of time that you want objects to stay in CloudFront caches before CloudFront forwards another request to your origin to determine whether the object has been updated.",
            "default" => 0
          },
          "default_ttl" => {
            "type" => "integer",
            "description" => "The default amount of time that you want objects to stay in CloudFront caches before CloudFront forwards another request to your origin to determine whether the object has been updated.",
            "default" => 86400
          },
          "max_ttl" => {
            "type" => "integer",
            "description" => "The maximum amount of time that you want objects to stay in CloudFront caches before CloudFront forwards another request to your origin to determine whether the object has been updated.",
            "default" => 31536000
          },
          "protocol_policy" => {
            "type" => "string",
            "enum" => %w{allow-all https-only redirect-to-https},
            "default" => "redirect-to-https"
          },
          "functions" => {
            "type" => "array",
            "items" => MU::Config::Ref.schema(type: "functions", desc: "Add a Lambda function which can be invoked on requests or responses through this distribution.")
          },
          "forwarded_values" => {
            "type" => "object",
            "description" => "HTTP request artifacts to include in requests passed to our back-end +origin+",
            "default" => {
              "query_string" => false
            },
            "properties" => {
              "query_string" => {
                "type" => "boolean",
                "description" => "Indicates whether you want CloudFront to forward query strings to the origin that is associated with this cache behavior and cache based on the query string parameters.",
                "default" => false
              },
              "cookies" => {
                "type" => "object",
                "description" => "A complex type that specifies whether you want CloudFront to forward cookies to the origin and, if so, which ones.",
                "default" => {
                  "forward" => "none"
                },
                "properties" => {
                  "forward" => {
                    "type" => "string",
                    "description" => "Specifies which cookies to forward to the origin for this cache behavior: all, none, or the list of cookies specified in +whitelisted_names+",
                    "enum" => %w{none whitelist all}
                  },
                  "whitelisted_names" => {
                    "type" => "array",
                    "items" => {
                      "description" => "Required if you specify whitelist for the value of +forward+",
                      "type" => "string"
                    }
                  },
                }
              },
              "headers" => {
                "type" => "array",
                "items" => {
                  "description" => "Specifies the headers, if any, that you want CloudFront to forward to the origin for this cache behavior (whitelisted headers).",
                  "type" => "string"
                }
              },
              "query_string_cache_keys" => {
                "type" => "array",
                "items" => {
                  "description" => "Indicates whether you want CloudFront to forward query strings to the origin that is associated with this cache behavior and cache based on the query string parameters",
                  "type" => "string"
                }
              }
            }
          }
        }
      }
    },
    "origins" => {
      "items" => {
        "properties" => {
          "bucket" => MU::Config::Ref.schema(type: "buckets", desc: "Reference an S3 bucket for use as an origin"),
          "endpoint" => MU::Config::Ref.schema(type: "endpoints", desc: "Reference an API Gateway for use as an origin"),
          "loadbalancer" => MU::Config::Ref.schema(type: "loadbalancers", desc: "Reference a Load Balancer for use as an origin"),
          "connection_attempts" => {
            "type" => "integer",
            "default" => 3
          },
          "connection_timeout" => {
            "type" => "integer",
            "default" => 10
          },
          "protocol_policy" => {
            "type" => "string",
            "enum" => %w{http-only https-only match-viewer},
            "default" => "match-viewer"
          },
          "ssl_protocols" => {
            "type" => "array",
            "default" => ["TLSv1.2"],
            "items" => {
              "type" => "string",
              "enum" => %w{SSLv3 TLSv1 TLSv1.1 TLSv1.2},
            }
          },
          "http_port" => {
            "type" => "integer",
            "default" => 80
          },
          "https_port" => {
            "type" => "integer",
            "default" => 443
          },
          "custom_headers" => {
            "type" => "array",
            "items" => {
              "description" => "A list of HTTP header names and values that CloudFront adds to requests it sends to the origin.",
              "type" => "object",
              "required" => ["key", "value"],
              "properties" => {
                "key" => {
                  "type" => "string"
                },
                "value" => {
                  "type" => "string"
                },
              }
            }
          }
        }
      }
    }
  }

  schema["behaviors"]["items"]["properties"]["functions"]["items"]["include_body"] = {
    "type" => "boolean",
    "default" => false
  }
  schema["behaviors"]["items"]["properties"]["functions"]["items"]["event_type"] = {
    "type" => "string",
    "enum" => %w{viewer-request viewer-response origin-request origin-response},
    "default" => "viewer-request"
  }

  [toplevel_required, schema]
end
validateConfig(cdn, configurator) click to toggle source

Cloud-specific pre-processing of {MU::Config::BasketofKittens::cdns}, bare and unvalidated. @param cdn [Hash]: The resource to process and validate @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member @return [Boolean]: True if validation succeeded, False otherwise

# File modules/mu/providers/aws/cdn.rb, line 502
def self.validateConfig(cdn, configurator)
  ok = true

  cdn['origins'].each { |o|
    count = 0
    ['bucket', 'endpoint', 'loadbalancer'].each { |sib_type|
      if o[sib_type]
        if count > 0
          ok = false
          MU.log "Origin in CloudFront distro #{cdn['name']} may specify at most one of bucket, endpoint, or loadbalancer.", MU::ERR
        end
        target_ref = MU::Config::Ref.get(o[sib_type])
        if target_ref.name
          MU::Config.addDependency(cdn, target_ref.name, sib_type, their_phase: "groom")
        end
        count += 1
      end
    }
  }

  cert_domains = nil

  if cdn['certificate']
    cert_arn, cert_domains = MU::Cloud::AWS.resolveSSLCertificate(cdn['certificate'], region: cdn['region'], credentials: cdn['credentials'])
    if !cert_arn
      MU.log "Failed to find an ACM or IAM certificate specified in CloudFront distribution #{cdn['name']}", MU::ERR, details: cdn['certificate'].to_h
      ok = false
    end
  end

  if cdn['aliases']
    cdn['aliases'].each { |a|
      if !cdn['certificate']
        foundcert, cert_domains = MU::Cloud::AWS.findSSLCertificate(name: a, region: cdn['region'], credentials: cdn['credentials'], raise_on_missing: false)
        if !foundcert
          foundcert, cert_domains = MU::Cloud::AWS.findSSLCertificate(name: a.sub(/^[^\.]+\./, '*.'), region: cdn['region'], credentials: cdn['credentials'], raise_on_missing: false)
        end
        if !foundcert
          MU.log "Failed to find an ACM or IAM certificate matching #{a} for CloudFront distribution #{cdn['name']}", MU::ERR
          ok = false
        else
          cdn['certificate'] = {
            "id" => foundcert,
            "credentials" => cdn['credentials']
          }
          MU.log "Auto-detected SSL certificate for CloudFront distribution #{cdn['name']} alias #{a}", MU::NOTICE, details: cdn['certificate']['id']
        end
      else
        if !MU::Cloud::AWS.nameMatchesCertificate(a, cdn['certificate']['id'])
          MU.log "Alias #{a} in CloudFront distro #{cdn['name']} does not appear to fit any domains on our SSL certificate", MU::ERR, details: cert_domains
          ok = false
        end
      end
    }
  end

  if cdn['dns_records'] and cdn['certificate']
    cdn['dns_records'].each { |rec|
      next if !rec['name']
      dnsname = MU::Cloud.resourceClass("AWS", "DNSZone").recordToName(rec)
      if MU::Cloud::AWS.nameMatchesCertificate(dnsname, cdn['certificate']['id'])
        cdn['aliases'] ||= []
        cdn['aliases'] << dnsname if !cdn['aliases'].include?(dnsname)
      end
    }
  end

  path_patterns = {}
  cdn['behaviors'].each { |b|
    b['path_pattern'] ||= "*"
    path_patterns[b['path_pattern']] ||= 0
    path_patterns[b['path_pattern']] += 1
  }
  path_patterns.each_pair { |pattern, origins|
    if origins > 1
      MU.log "CDN #{cdn['name']} has #{origins.to_s} uses of path_pattern '#{pattern}' in its behavior list (must be unique)", MU::ERR, details: cdn['behaviors']
      ok = false
    end
  }

  ok
end

Public Instance Methods

arn() click to toggle source

Canonical Amazon Resource Number for this resource @return [String]

# File modules/mu/providers/aws/cdn.rb, line 103
def arn
  cloud_desc ? cloud_desc.arn : nil
end
create() click to toggle source

Called automatically by {MU::Deploy#createResources}

# File modules/mu/providers/aws/cdn.rb, line 29
def create
  resp = MU::Cloud::AWS.cloudfront(credentials: @credentials).create_cloud_front_origin_access_identity(
    cloud_front_origin_access_identity_config: {
      caller_reference: @mu_name,
      comment: @mu_name
    }
  )

  @origin_access_identity = "origin-access-identity/cloudfront/"+resp.cloud_front_origin_access_identity.id

  params = get_properties

  begin
    MU.log "Creating CloudFront distribution #{@mu_name}", details: params
    MU.retrier([Aws::CloudFront::Errors::InvalidOrigin], wait: 10, max: 6) {
      resp = MU::Cloud::AWS.cloudfront(credentials: @credentials).create_distribution_with_tags(
        distribution_config_with_tags: {
          distribution_config: params,
          tags: {
            items: @tags.each_key.map { |k| { :key => k, :value => @tags[k] } }
          }
        }
      )
      @cloud_id = resp.distribution.id
    }
    ready?
  rescue ::Aws::CloudFront::Errors::InvalidViewerCertificate => e
    cert_arn, cert_domains = MU::Cloud::AWS.findSSLCertificate(
      name: @config['certificate']["name"],
      id: @config['certificate']["id"],
      region: @config['certificate']['region'],
      credentials: @config['certificate']['credentials']
    )
    raise MuError.new e.message, details: { "aliases" => @config['aliases'], "certificate domains" => cert_domains }
  rescue ::Aws::CloudFront::Errors::InvalidOrigin => e
    raise MuError.new e.message, details: params[:origins]
  rescue ::Aws::CloudFront::Errors::InvalidArgument => e
    raise MuError.new e.message, details: params
  end
end
groom() click to toggle source

Called automatically by {MU::Deploy#createResources}

# File modules/mu/providers/aws/cdn.rb, line 71
        def groom
          params = get_properties

          if !@config['dns_records'].nil?
            if !MU::Cloud::AWS.isGovCloud?
              MU::Cloud.resourceClass("AWS", "DNSZone").createRecordsFromConfig(@config['dns_records'], target: cloud_desc.domain_name)
            end
          end
          MU.log "CloudFront Distribution #{@config['name']} at #{cloud_desc.domain_name}", MU::SUMMARY
          if @config['aliases']
            @config['aliases'].each { |a|
              MU.log "Alias for CloudFront Distribution #{@config['name']}: #{a}", MU::SUMMARY
            }
          end

          # Make sure we show up in the bucket policy of our target bucket,
          # if it's a sibling in this deploy
          cloud_desc(use_cache: false).origins.items.each { |o|
            if o.s3_origin_config
              id = o.s3_origin_config.origin_access_identity.sub(/^origin-access-identity\/cloudfront\//, '')
              bucketref = get_bucketref_from_domain(o.domain_name)
              next if !bucketref or !bucketref.kitten
              resp = MU::Cloud::AWS.cloudfront(credentials: @credentials).get_cloud_front_origin_access_identity(id: id)
#              bucketref.kitten.allowPrincipal("arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity "+id, doc_id: "PolicyForCloudFrontPrivateContent", permissions: ["GetObject"])
              bucketref.kitten.allowPrincipal(resp.cloud_front_origin_access_identity.s3_canonical_user_id, doc_id: "PolicyForCloudFrontPrivateContent", permissions: ["GetObject"], name: @mu_name)
            end
          }

        end
notify() click to toggle source

Return the metadata for this cdn @return [Hash]

# File modules/mu/providers/aws/cdn.rb, line 109
def notify
  MU.structToHash(cloud_desc, stringify_keys: true)
end
ready?() click to toggle source

Wait until the distribution is ready (status is Deployed)

# File modules/mu/providers/aws/cdn.rb, line 114
def ready?
  self.class.ready?(@cloud_id, credentials: @credentials)
end
toKitten(**_args) click to toggle source

Reverse-map our cloud description into a runnable config hash. We assume that any values we have in +@config+ are placeholders, and calculate our own accordingly based on what's live in the cloud.

# File modules/mu/providers/aws/cdn.rb, line 227
def toKitten(**_args)
  bok = {
    "cloud" => "AWS",
    "credentials" => @credentials,
    "cloud_id" => @cloud_id
  }

  if !cloud_desc
    MU.log "toKitten failed to load a cloud_desc from #{@cloud_id}", MU::ERR, details: @config
    return nil
  end

  resp = MU::Cloud::AWS.cloudfront(credentials: @credentials).list_tags_for_resource(resource: arn)
  if resp and resp.tags and resp.tags.items
    tags = MU.structToHash(resp.tags.items, stringify_keys: true)
    bok['name'] = MU::Adoption.tagsToName(tags)
    bok['tags'] = tags if !tags.empty?
  end

  if !bok['name'] 
    bok['name'] = if cloud_desc.domain_name !~ /\.cloudfront\.net$/
      cloud_desc.domain_name.sub(/\..*/, '')
    elsif cloud_desc.aliases and !cloud_desc.aliases.items.empty?
      cloud_desc.aliases.items.first.sub(/\..*/, '')
    # XXX maybe try to guess from the name of an origin resource?
    else
      @cloud_id
    end
  end

  cloud_desc.origins.items.each { |o|
    bok['origins'] ||= []
    origin = {
      "path" => o.origin_path,
      "name" => o.id
    }
    if o.s3_origin_config
      origin["bucket"] = get_bucketref_from_domain(o.domain_name)
    end
    origin["domain_name"] = o.domain_name if !origin["bucket"]
    if o.custom_origin_config
      origin["http_port"] = o.custom_origin_config.http_port
      origin["https_port"] = o.custom_origin_config.https_port
      origin["protocol_policy"] = o.custom_origin_config.origin_protocol_policy
      origin["ssl_protocols"] = o.custom_origin_config.origin_ssl_protocols.items
    end

    if o.custom_headers and !o.custom_headers.empty?
    end

    bok['origins'] << origin
  }

  if cloud_desc.aliases and cloud_desc.aliases.items and
     !cloud_desc.aliases.items.empty?
    bok['aliases'] = cloud_desc.aliases.items
  end

  bok['disabled'] = true if !cloud_desc.enabled

  bok['behaviors'] = []

  add_behavior = Proc.new { |b, default|
    behavior = {}

    behavior["origin"] = b.target_origin_id
    behavior["path_pattern"] = b.path_pattern if b.respond_to?(:path_pattern)
    behavior["protocol_policy"] = b.viewer_protocol_policy
    if b.lambda_function_associations and !b.lambda_function_associations.items.empty?
      b.lambda_function_associations.items.each { |f|
        behavior['functions'] ||= []
        f.lambda_function_arn.match(/^arn:.*?:lambda:([^:]+?):(\d*):function:([^:]+)/)
        region = Regexp.last_match[1]
        acct = Regexp.last_match[2]
        id = Regexp.last_match[3]
        behavior['functions'] << MU::Config::Ref.get(
          id: id,
          region: region,
          type: "functions",
          event_type: f.event_type,
          include_body: f.include_body,
          cloud: "AWS",
          credentials: @credentials,
          habitat: MU::Config::Ref.get(
            id: acct,
            cloud: "AWS",
            credentials: @credentials
          )
        )
      }
      [:min_ttl, :default_ttl, :max_ttl].each { |ttl|
        behavior[ttl.to_s] = b.send(ttl)
      }
    end
    bok['behaviors'] << behavior
  }

  add_behavior.call(cloud_desc.default_cache_behavior, true)

  if cloud_desc.cache_behaviors and
     !cloud_desc.cache_behaviors.items.empty?
    cloud_desc.cache_behaviors.items.each { |b|
      add_behavior.call(b, false)
    }
  end

  bok
end

Private Instance Methods

get_bucketref_from_domain(domain_name) click to toggle source
# File modules/mu/providers/aws/cdn.rb, line 762
def get_bucketref_from_domain(domain_name)
  buckets = MU::Cloud.resourceClass("AWS", "Bucket").find(credentials: @credentials, allregions: true, cloud_id: domain_name.sub(/\..*/, ''))
  if buckets and buckets.size == 1
    return MU::Config::Ref.get(
      id: buckets.keys.first,
      type: "buckets",
      region: buckets.values.first["region"],
      credentials: @credentials,
      cloud: "AWS"
    )
  else
    MU.log "Failed to locate or isolate a bucket object from #{domain_name}", MU::WARN, details: buckets.keys
  end

  nil
end
get_properties() click to toggle source
# File modules/mu/providers/aws/cdn.rb, line 587
        def get_properties
          params = {
            default_root_object: @config['default_object'],
            caller_reference: @mu_name, # eh, probably should be random
            origins: {
              quantity: @config['origins'].size,
              items: []
            },
            comment: @deploy.deploy_id,
            enabled: !(@config['disabled'])
          }

          if @config['certificate']
            params[:viewer_certificate] = {
              ssl_support_method: "sni-only"
            }
            if @config['certificate']['id'] =~ /^arn:aws(?:-us-gov)?:iam/
              params[:viewer_certificate][:iam_certificate_id] = @config['certificate']['id']
              params[:viewer_certificate][:certificate_source] = "iam"
            elsif @config['certificate']['id'] =~ /^arn:aws(?:-us-gov)?:acm/
              params[:viewer_certificate][:acm_certificate_arn] = @config['certificate']['id']
              params[:viewer_certificate][:certificate_source] = "acm"
            end

          end

          @config['origins'].each { |o|
            origin = {
              id: o['name'],
            }
            sib_obj = nil
            ['bucket', 'endpoint', 'loadbalancer'].each { |sib_type|
              if o[sib_type]
                sib_obj = MU::Config::Ref.get(o[sib_type]).kitten(@deploy, cloud: "AWS")
                if !sib_obj
                  raise MuError.new "Failed to resolve #{sib_type} referenced in CloudFront distribution #{@config['name']}", details: o[sib_type].to_h
                end
                break
              end
            }
            if o['bucket']
              origin[:domain_name] = sib_obj.cloud_desc["name"]+".s3.amazonaws.com"
              origin[:origin_path] = o['path'] if o['path']
              origin[:s3_origin_config] = {
                origin_access_identity: @origin_access_identity
              }
            elsif o['endpoint']
              origin[:domain_name] = sib_obj.cloud_id+".execute-api."+sib_obj.config['region']+".amazonaws.com"
              origin[:custom_origin_config] = {
                origin_protocol_policy: "https-only"
              }
              if sib_obj.config['deploy_to']
                origin[:origin_path] ||= "/"+sib_obj.config['deploy_to']
              end
            elsif o['loadbalancer']
              origin[:domain_name] = sib_obj.cloud_desc.dns_name
              origin[:origin_path] = o['path'] if o['path']
            else # XXX make sure parser guarantees these are present
              origin[:domain_name] = o['domain_name']
              origin[:origin_path] = o['path']
            end

            if o['custom_headers']
              origin[:custom_headers] = {
                quantity: o['custom_headers'].size,
                items: o['custom_headers'].map { |h|
                  {
                    header_name: h['key'],
                    header_value: h['value']
                  }
                }
              }
            end

            [:connection_attempts, :connection_timeout].each { |field|
              origin[field] ||= o[field.to_s]
            }
            if !origin[:s3_origin_config]
              maplet = {
                'protocol_policy' => :origin_protocol_policy,
                'ssl_protocols' => :origin_ssl_protocols,
                'http_port' => :http_port,
                'https_port' => :https_port
              }
              maplet.each_pair { |field, paramfield|
                next if !o[field]
                origin[:custom_origin_config] ||= {}
                origin[:custom_origin_config][paramfield] ||= if o[field.to_s].is_a?(Array)
                  {
                    quantity: o[field].size,
                    items: o[field]
                  }
                else
                  o[field]
                end
              }
            end

            params[:origins][:items] << origin
          }

          # if we have any placeholder DNS records that are intended to be
          # filled out with our runtime @mu_name, do so, and add an alias if
          # applicable
          if @config['dns_records']
            @config['dns_records'].each { |rec|
              if !rec['name']
                rec['name'] = @mu_name.downcase
                dnsname = MU::Cloud.resourceClass("AWS", "DNSZone").recordToName(rec)
                if @config['certificate'] and MU::Cloud::AWS.nameMatchesCertificate(dnsname, @config['certificate']['id'])
                  @config['aliases'] ||= []
                  @config['aliases'] << dnsname if !@config['aliases'].include?(dnsname)
                end
              end
            }
          end

          if @config['aliases']
            params[:aliases] = {
              items: @config['aliases'],
              quantity: @config['aliases'].size
            }
          end

          # XXX config parser should guarantee a default behavior
          @config['behaviors'].each { |b|
            b['origin'] ||= @config['origins'].first['name']
            behavior = {
              target_origin_id: b['origin'],
              viewer_protocol_policy: b['protocol_policy'],
              min_ttl: b['min_ttl'],
              max_ttl: b['max_ttl'],
              default_ttl: b['default_ttl'],
            }
            behavior[:trusted_signers] = {
              enabled: false,
              quantity: 0,
#              items: []
            }
            behavior[:forwarded_values] = {
              query_string: b['forwarded_values']['query_string'],
              cookies: {
                forward: b['forwarded_values']['cookies']['forward']
              }
            }
            if b['forwarded_values']['cookies']['whitelisted_names']
              behavior[:forwarded_values][:cookies][:whitelisted_names] = {
                quantity: b['forwarded_values']['cookies']['whitelisted_names'].size,
                items: b['forwarded_values']['cookies']['whitelisted_names']
              }
            end
            ['headers', 'query_string_cache_keys'].each { |field|
              if b['forwarded_values'][field]
                behavior[:forwarded_values][field.to_sym] = {
                  quantity: b['forwarded_values'][field].size,
                  items: b['forwarded_values'][field]
                }
              end
            }

            if @config['behaviors'].size == 1 or b['path_pattern'] == "*"
              params[:default_cache_behavior] = behavior
            else
              behavior[:path_pattern] = b['path_pattern']
              params[:cache_behaviors] ||= {
                quantity: (@config['behaviors'].size-1),
                items: []
              }
              params[:cache_behaviors][:items] << behavior
            end
          }

          params
        end