require 'base64'
require 'openssl'
require 'digest/sha1'
require 'digest/md5'
require 'net/https'
module ConfigureS3Website
class S3Client
def self.configure_website(config_source)
begin
enable_website_configuration(config_source)
make_bucket_readable_to_everyone(config_source)
rescue NoSuchBucketError
create_bucket(config_source)
retry
end
end
private
def self.enable_website_configuration(config_source)
body = %|
index.html
error.html
|
call_s3_api(
path = "/#{config_source.s3_bucket_name}/?website",
method = Net::HTTP::Put,
body = body,
config_source = config_source
)
puts "Bucket #{config_source.s3_bucket_name} now functions as a website"
end
def self.make_bucket_readable_to_everyone(config_source)
policy_json = %|{
"Version":"2008-10-17",
"Statement":[{
"Sid":"PublicReadForGetBucketObjects",
"Effect":"Allow",
"Principal": { "AWS": "*" },
"Action":["s3:GetObject"],
"Resource":["arn:aws:s3:::#{config_source.s3_bucket_name}/*"]
}]
}|
call_s3_api(
path = "/#{config_source.s3_bucket_name}/?policy",
method = Net::HTTP::Put,
body = policy_json,
config_source = config_source
)
puts "Bucket #{config_source.s3_bucket_name} is now readable to the whole world"
end
def self.create_bucket(config_source)
call_s3_api(
path = "/#{config_source.s3_bucket_name}",
method = Net::HTTP::Put,
body = '',
config_source = config_source
)
puts "Created bucket #{config_source.s3_bucket_name}"
end
def self.call_s3_api(path, method, body, config_source)
date = Time.now.strftime("%a, %d %b %Y %H:%M:%S %Z")
digest = create_digest(path, method, config_source, date)
url = "https://s3.amazonaws.com#{path}"
uri = URI.parse(url)
req = method.new(uri.to_s)
req.initialize_http_header({
'Date' => date,
'Content-Type' => '',
'Content-Length' => body.length.to_s,
'Authorization' => "AWS %s:%s" % [config_source.s3_access_key_id, digest]
})
req.body = body
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
res = http.request(req)
if res.code.to_i.between? 200, 299
res
else
raise ConfigureS3Website::ErrorParser.create_error res.body
end
end
def self.create_digest(path, method, config_source, date)
digest = OpenSSL::Digest::Digest.new('sha1')
method_string = method.to_s.match(/Net::HTTP::(\w+)/)[1].upcase
can_string = "#{method_string}\n\n\n#{date}\n#{path}"
hmac = OpenSSL::HMAC.digest(digest, config_source.s3_secret_access_key, can_string)
signature = Base64.encode64(hmac).strip
end
end
end
private
module ConfigureS3Website
class ErrorParser
def self.create_error(amazon_error_xml)
error_code = amazon_error_xml.delete('\n').match(/(.*?)<\/Code>/)[1]
begin
Object.const_get("#{error_code}Error").new
rescue NameError
GenericS3Error.new(amazon_error_xml)
end
end
end
end
class NoSuchBucketError < StandardError
end
class GenericS3Error < StandardError
def initialize(error_message)
super("AWS API call failed:\n#{error_message}")
end
end