7

I have been trying to upload a photo to my AWS bucket, but running into the error mentioned in the title. I understand that it most likely has to do with my OpenSSL certificates, but any suggested solution that I have tried has failed thus far.

I am running into this issue with ruby 2.3.1, Rails 4.1.8, aws-sdk-core 2.3.4, and carrierwave 0.11.0 on OSX Yosemite.

I have tried all available found at this similar issue as well, as others (this one being with Windows): https://github.com/aws/aws-sdk-core-ruby/issues/166#issuecomment-111603660

Here are some of my files:

carrierwave.rb

CarrierWave.configure do |config|                     # required
  config.aws_credentials = {
    access_key_id:     Rails.application.secrets.aws_access_key_id, # required
    secret_access_key: Rails.application.secrets.aws_access_key,    # required
    region:            'eu-west-2'                  # optional, defaults to 'us-east-1'
  }

  config.aws_bucket = Rails.application.secrets.aws_bucket                        # required
  config.fog_attributes = { 'Cache-Control' => "max-age=#{365.day.to_i}" } # optional, defaults to {}
end

avatar_uploader.rb

class AvatarUploader < CarrierWave::Uploader::Base

  storage :aws

  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end
end

EDIT (more info):

stack trace:

    Seahorse::Client::NetworkingError - SSL_connect returned=1 errno=0 state=error: certificate verify failed:
  /Users/stevenharlow/.rbenv/versions/2.3.1/lib/ruby/2.3.0/net/http.rb:933:in `connect_nonblock'
  /Users/stevenharlow/.rbenv/versions/2.3.1/lib/ruby/2.3.0/net/http.rb:933:in `connect'
  /Users/stevenharlow/.rbenv/versions/2.3.1/lib/ruby/2.3.0/net/http.rb:863:in `do_start'
  /Users/stevenharlow/.rbenv/versions/2.3.1/lib/ruby/2.3.0/net/http.rb:858:in `start'
  /Users/stevenharlow/.rbenv/versions/2.3.1/lib/ruby/2.3.0/delegate.rb:83:in `method_missing'
  aws-sdk-core (2.3.4) lib/seahorse/client/net_http/connection_pool.rb:292:in `start_session'
  aws-sdk-core (2.3.4) lib/seahorse/client/net_http/connection_pool.rb:104:in `session_for'
  aws-sdk-core (2.3.4) lib/seahorse/client/net_http/handler.rb:109:in `session'

Solutions tried:

  • Aws.use_bundled_cert!
  • Download cert and reference manually
  • I tried using Fog instead of carrierwave-aws
  • Tried reinstalling ruby after upgrading rbenv

Here's the result of

CONNECTED(00000003)
depth=1 /C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Baltimore CA-2 G2
verify error:num=20:unable to get local issuer certificate
verify return:0
---
Certificate chain
 0 s:/C=US/ST=Washington/L=Seattle/O=Amazon.com Inc./CN=*.s3-us-west-2.amazonaws.com
   i:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Baltimore CA-2 G2
 1 s:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Baltimore CA-2 G2
   i:/C=IE/O=Baltimore/OU=CyberTrust/CN=Baltimore CyberTrust Root
---

<certificate info>

No client certificate CA names sent
---
SSL handshake has read 2703 bytes and written 456 bytes
---
New, TLSv1/SSLv3, Cipher is AES128-SHA
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
SSL-Session:
    Protocol  : TLSv1
    Cipher    : AES128-SHA
    Session-ID: <session-id>
    Session-ID-ctx: 
    Master-Key: <master-key>
    Key-Arg   : None
    Start Time: 1463697130
    Timeout   : 300 (sec)
    Verify return code: 0 (ok)
Steven Harlow
  • 631
  • 2
  • 11
  • 26
  • what happens when you add "use_ssl: true" to the aws_credentials hash? – ruby_newbie May 16 '16 at 15:33
  • invalid configuration option `:use_ssl' I'm using the gem "carrierwave-aws" and not fog – Steven Harlow May 16 '16 at 15:53
  • Apparently you have seen the related [github issue](https://github.com/aws/aws-sdk-core-ruby/issues/166#issuecomment-219312958), so you have tried the `Aws.use_bundled_cert!`. Can you please add info about your attempts to the question? And can you add a full stack trace of the error? Also, what triggers the upload - is it file upload from the web or something more complex like a background job? Thanks. – Matouš Borák May 16 '16 at 16:01
  • @BoraMa I added some edits. This is just a small .png upload – Steven Harlow May 16 '16 at 16:53
  • Does your bucket name have any `.` dot characters in it? – Michael - sqlbot May 16 '16 at 19:22
  • @Michael-sqlbot It does not. It's one word like "bucket" (it's not bucket and doesn't have quotations) – Steven Harlow May 16 '16 at 20:23
  • In that case, the diagnosis that I had in mind can be scratched from the list. – Michael - sqlbot May 16 '16 at 20:43
  • Could you try the require + `Aws.use_bundled_cert!` again, this time in an initializer? If that does not help, could you open up the aws-sdk `connection_pool.rb` file on [this line](https://github.com/aws/aws-sdk-ruby/blob/v2.3.4/aws-sdk-core/lib/seahorse/client/net_http/connection_pool.rb#L292) and add a `Rails.logger.warn "ca bundle: #{http.ca_bundle.inspect}"` before the line (see the stack trace for precise file location) and paste what gets logged? – Matouš Borák May 16 '16 at 21:03
  • @BoraMa I had the require and use_bundled_cert! in an initializer (it was the carrierwave.rb initializer), tried again and nothing. As for the adding the logging, I've forked both carrierwave-aws and aws-sdk gems and am forcing my application to install from my source, but the logging isn't showing up. Gemfile.lock confirms they are references my github source...not sure if there's something wrong I did or if it just didn't hit the loggers that I put in. The are before the http.start line... – Steven Harlow May 16 '16 at 22:39
  • urls for the forked repos just fyi: https://github.com/StevenHarlow/aws-sdk-ruby https://github.com/StevenHarlow/carrierwave-aws Only changes are version and logging in sdk, and required version in gemspec of carrierwave-aws – Steven Harlow May 16 '16 at 22:45
  • what is the time on the server that you are trying to run this on? is it possible that the time is off and it believes the cert has expired? – Mircea May 17 '16 at 03:44
  • @StevenHarlow, hmm, perhaps you need to put the logging to [version 2.3.4](https://github.com/StevenHarlow/aws-sdk-ruby/blob/v2.3.4/aws-sdk-core/lib/seahorse/client/net_http/connection_pool.rb#L292), not master? – Matouš Borák May 17 '16 at 06:52
  • @Mircea This is just being run on localhost so far, this is actually working on the production server (Heroku), which i'd expect since Heroku would be sure their OpenSSL cert was working and valid. I need this for testing and development however. @BoraMa, logging started working this morning, but: undefined method `ca_bundle' for #.s3.us-west-2.amazonaws.com:443 open=false> – Steven Harlow May 17 '16 at 10:22
  • 1
    What does openssl s_client -connect s3-us-west-2.amazonaws.com:443 return? – Rodrigo Murillo May 19 '16 at 14:25
  • 1
    You say you tried "Download cert and reference manually". Can you show or explain that? What cert and to what reference? How did you make that reference as I do not see it in the code. – Rodrigo Murillo May 19 '16 at 16:32
  • @RodrigoM 1) I posted the result of the openssl connection. 2) In another suggested fix, you could download a correct certification (it was assumed the cert didn't bundle with ruby 2+) and place it somewhere in your application that could be referenced, and identify it in the carrierwave.config initializer. i did that. – Steven Harlow May 19 '16 at 22:44
  • Thanks Steven that makes it very clear. Please see my answer below. I think it is the correct one. – Rodrigo Murillo May 20 '16 at 02:24

4 Answers4

6

With the investigative help of @RodrigoM and your question update, it all started to make sense. There are actually two distinct problems that contribute to the error you observe:

  • Your openssl installation does not have the certificate chain needed to verify the Amazon server in its trusted certs store...
  • ...which is the exact situation that should be solved by adding Aws.use_bundled_cert! to an initializer, according to the docs. But in this case it does not work because even though this command instructs the ruby openssl library to add various CA certs to the trusted store from the aws-sdk-core gem's CA bundle file, the file also does not contain the proper CA certificate as it is itself almost 2 years old and outdated. The intermediate CA cert CN=DigiCert Baltimore CA-2 G2 has been published Dec 8, 2015, so no wonder that the CA bundle file does not contain it.

Now, you have two options:

  • You can try to install this intermediate CA certificate, probably including the root CA cert (CN=Baltimore CyberTrust Root), to your openssl trusted certs store. This should make the s_client command work. But you might still run into issues using these trusted certs from ruby code. For concrete steps for making it work under ruby on OSX, refer to the Solution section of this SO question.

  • Also, since you are using a forked aws-sdk-ruby gem repository anyway, you may as well update the ca-bundle.crt file in your repo by adding the intermediate CA cert yourself (the root CA cert seems to be already present in the bundle). For this you need to do the following:

    • download the intermediate CA cert from the official page of the DigicertCA certificates (you can as well use the direct link above, but to obey security rules precisely you should also check the fingerprints)
    • convert it to the PEM format (it gets downloaded in DER format) and add it to the cert bundle using the following openssl command:

      openssl x509 -in DigiCertBaltimoreCA-2G2.crt -inform DER >> ca-bundle.crt
      

      after running this command, your ca-bundle.crt should contain the intermediate CA certificate at the end of the file.

    • Now simply push this updated bundle file to your repo and the Aws.use_bundled_cert! should start working!

    • If you care, perhaps the best would be also to start a github issue at the aws-sdk-ruby gem so that they update the cert bundle in their repo too...
Community
  • 1
  • 1
Matouš Borák
  • 15,606
  • 1
  • 42
  • 53
  • running: openssl s_client -connect s3-us-west-2.amazonaws.com:443 -CAfile DigiCertBaltimoreCA-2G2.crt from the directory I added that cert to results in the same openssl response... – Steven Harlow May 21 '16 at 17:57
  • I guess that this time it fails due to the root certificate. Try with the whole bundle: `-CAfile ca-bundle.crt` after you've added the intermediate cert to it. – Matouš Borák May 21 '16 at 18:00
  • Not only did I not have the DigiCertBaltimoreCA, but I didn't even have the original ca-bundle.crt. I grabbed a root cert, and appended the DCBCA cert to it and now things are working great! Life is good again :) – Steven Harlow May 21 '16 at 21:36
  • Good follow up -BoraMa. Wish we could split bounties @Steven Harlow! I believe I nailed the missing CA cert problem -; ) please consider http://meta.stackexchange.com/questions/2786/accept-multiple-answers-or-split-bounty-among-several-users – Rodrigo Murillo May 22 '16 at 01:24
  • @RodrigoM I was thinking exactly the same thing earlier. Definitely tag teamed this one and I appreciate both of your's help. – Steven Harlow May 22 '16 at 02:48
  • @RodrigoM, sure you found the root cause of the problem! I thought I could start another bounty for you for 50 points and thus technically split the bounty but it does not allow me to go below 100, so I gave you an upvote, at least... :) – Matouš Borák May 22 '16 at 06:46
  • Thanks @Steven Harlow and BoraMa - good work glad I could help – Rodrigo Murillo May 23 '16 at 14:13
4

Your Ruby code, AWS SDK etc are all good. This is not a Ruby or SDK issue. The error message you initially describe, and your later post of the OpenSSL connect log both point to the root cause of the problem: A missing root certificate and/or improperly configured CA cert bundle in the OpenSSL framework. A further clue is that the same code works on production. Its not the code.

The original error message itself points to OpenSSL certificate verification errors as you indicate. The stack traces also show Ruby internal lib errors in 2.3.1/lib/ruby/2.3.0/net/http.rb. This is core network library that leverages the OpenSSL framework.

The openssl s_client connect log more clearly shows the extact error number and message:

depth=1 /C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Baltimore CA-2 G2
verify error:num=20:unable to get local issuer certificate

Until you get a Verify return code: 0 (ok) on the DigiCert Baltimore CA-2 CA with the openssl s-client test, your code will not work.

The DigiCert Baltimore CA-2 CA cert is not present or not properly referenced by your OpenSSL setup on that machine. This is very common problem in broken or incomplete OpenSSL installations. You need to download that certificate, convert to PEM format, save it in a ca-certificate.crt file in your OpenSSL certs folder, then reference that file in your config, or in an environment variable SSL_CERT_FILE.

You can see a good solution for your root issue here

NOTE: To futher confirm this solution, you should run the openssl s_client test on your production server. You should see it verifying the same CA without problem. Check the OpenSSL config and CA cert bundle configuration there to see why there is a difference between your production and development environment.

Community
  • 1
  • 1
Rodrigo Murillo
  • 13,080
  • 2
  • 29
  • 50
  • running: openssl s_client -connect s3-us-west-2.amazonaws.com:443 -CAfile DigiCertBaltimoreCA-2G2.crt from the directory I added that cert to results in the same openssl response... – Steven Harlow May 21 '16 at 17:53
  • Odd it should not. Pass that DigiCertBaltimoreCA-2G2.crt file here please so I can test to confirm. That same test is passing here. – Rodrigo Murillo May 22 '16 at 01:14
1

This issue may be due to invalid cert. when you call the URL(API)

You can first cross verify cert. by typing command

openssl s_client -connect <url without https>:443

If you find any issue in the cert. then updating cert. at certificate manager can fix this issue.

Amit Agarwal
  • 396
  • 3
  • 12
0

Try using these gems and this setup instead:

Gemfile

gem "carrierwave", "~> 0.11.0"
gem 'carrierwave-aws', "~> 1.0.1"
gem "unf", "~> 0.1.4"

config/carrierwave.rb

require 'carrierwave'
require 'carrierwave/orm/activerecord'

  CarrierWave.configure do |config|
     config.storage    =  :aws                  # required
     config.aws_bucket =  Rails.application.secrets.aws_bucket       # required
     config.aws_acl    =  :public_read
     config.aws_credentials = {
         access_key_id:      Rails.application.secrets.aws_access_key_id,       # required
         secret_access_key:  Rails.application.secrets.aws_access_key     # required
     }
     config.aws_attributes = { 
         cache_control: 'max-age=31536000',
         expires: 1.year.from_now.httpdate
     }
  end

*_uploader.rb

storage :aws

def store_dir
  "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end

# Use Heroku's temp folder for uploads
def cache_dir
  "#{Rails.root}/tmp/uploads"
end
blnc
  • 4,384
  • 1
  • 28
  • 42