0

ANSWER: The two environment variables were mixed up in Heroku. CERT pointed to the private key and PRIVATE_KEY pointed to cert. Fixed that. Authorization now works.

Will mark this question answered when I can.


I am hooking into the Google Analytics Reporting API with a services account from my Rails 4 application. I have set up the authorization process and got it working in development. Upon deployment, I realized that using the .p12 key file was a problem - I either had to commit it to my repository or find a way around it. So I found a way around it, by breaking the key file into two .PEM files and storing the contents of those files as environment variables.

Works great in development (am using dotenv-rails to store environment variables there). But something must be happening to the variables on Heroku, because when I try to use my authorization method in production, I get the error ArgumentError: Could not parse PKey: no start line.

I've searched around for some clue about what's going on here - I've seen that related errors can happen due to version problems (someone downgraded from Ruby 2.2 to 1.9.8 and fixed a similar problem, although theirs just flat out didn't work anywhere, and mine works in development). Downgrading my Ruby version is not really an option. :/

Here's the related code:

def authorize_with_services
  begin
  self.client = Google::APIClient.new(application_name: 'Playground', application_version: '1')

    p12 = OpenSSL::PKCS12.create('notasecret', 'descriptor',
      problem spot -->         OpenSSL::PKey.read(ENV['PRIVATE_KEY']),
                               OpenSSL::X509::Certificate.new(ENV['CERT']))

    p12_binary = p12.to_der

  self.client.authorization = Signet::OAuth2::Client.new(
    token_credential_uri: 'https://accounts.google.com/o/oauth2/token',
    audience: 'https://accounts.google.com/o/oauth2/token',
    scope: 'https://www.googleapis.com/auth/analytics.readonly',
    issuer: GA_EMAIL,
    signing_key: Google::APIClient::PKCS12.load_key(p12_binary, 'notasecret')
  ).tap { |auth| auth.fetch_access_token! }
  rescue Signet::AuthorizationError
    self.client = nil
  end

  self.client
end

And then the ENV variables as stored in .env:

PRIVATE_KEY: "Bag Attributes\n    friendlyName: privatekey\n    localKeyID: 54 69 6D 65 20 31 34 33 35 36 38 34 30 33 31 30 32 39\nKey Attributes: <No Attributes>\n-----BEGIN RSA PRIVATE KEY-----\nKEY\n-----END RSA PRIVATE KEY-----\n"

CERT: "Bag Attributes\nfriendlyName: privatekey\nlocalKeyID: 54 69 6D 65 20 31 34 33 35 36 38 34 30 33 31 30 32 39\nsubject=/CN=636085506886-096q9j1uotf9kp1tv4evhn2crip6dec8.apps.googleusercontent.com\nissuer=/CN=636085506886-096q9j1uotf9kp1tv4evhn2crip6dec8.apps.googleusercontent.com\n-----BEGIN CERTIFICATE-----\nCERT\n-----END CERTIFICATE-----\n"

And I've set them exactly the same in heroku config:set PRIVATE_KEY and heroku config:set CERT.

Edit

Here's the session in the Heroku console which leads to the error:

# 11:15:36 (ruby-2.1.5@yt-ga-playground) ~/projects/confreaks/yt-ga-playground (master)$ heroku run console
Running `console` attached to terminal... up, run.8357
Loading production environment (Rails 4.2.0)
irb(main):001:0> querier = AnalyticsQuerier.new
=> #<AnalyticsQuerier:0x007f81fc6f5358 @options={"ids"=>"ga:104082366", "start-date"=>"2015-01-01", "end-date"=>"2015-07-04", "metrics"=>"ga:totalEvents"}, @ids="ga:104082366", @start_date="2015-01-01", @end_date="2015-07-04", @metrics="ga:totalEvents">
irb(main):002:0> querier.authorize_with_services
ArgumentError: Could not parse PKey: no start line
  from /app/app/services/analytics_querier.rb:56:in `read'
  from /app/app/services/analytics_querier.rb:56:in `authorize_with_services'
  from (irb):2
  from /app/vendor/bundle/ruby/2.1.0/gems/railties-4.2.0/lib/rails/commands/console.rb:110:in `start'
  from /app/vendor/bundle/ruby/2.1.0/gems/railties-4.2.0/lib/rails/commands/console.rb:9:in `start'
  from /app/vendor/bundle/ruby/2.1.0/gems/railties-4.2.0/lib/rails/commands/commands_tasks.rb:68:in `console'
  from /app/vendor/bundle/ruby/2.1.0/gems/railties-4.2.0/lib/rails/commands/commands_tasks.rb:39:in `run_command!'
  from /app/vendor/bundle/ruby/2.1.0/gems/railties-4.2.0/lib/rails/commands.rb:17:in `<top (required)>'
  from bin/rails:8:in `require'
  from bin/rails:8:in `<main>'

And the equivalent session in my development console:

# 11:17:47 (ruby-2.1.5@yt-ga-playground) ~/projects/confreaks/yt-ga-playground (master)$ be rails c
Loading development environment (Rails 4.2.0)
2.1.5 :001 > querier = AnalyticsQuerier.new
=> #<AnalyticsQuerier:0x007fe97d380928 @options={"ids"=>"ga:104082366", "start-date"=>"2015-01-01", "end-date"=>"2015-07-04", "metrics"=>"ga:totalEvents"}, @ids="ga:104082366", @start_date="2015-01-01", @end_date="2015-07-04", @metrics="ga:totalEvents">
2.1.5 :002 > querier.authorize_with_services
=> #<Google::APIClient:0x007fe97d36bc80 @host="www.googleapis.com", @port=443, @discovery_path="/discovery/v1", @user_agent="Playground/1 google-api-ruby-client/0.8.2 Mac OS X/10.10.3\n (gzip)", etc.>

And I have a controller method which uses the AnalyticsQuerier. I pushed it to production when I thought that it would work. Here's how that plays out in the logs:

[...] app[web.1]: Started POST "/events" for 104.231.12.227 at 2015-07-04 15:14:11 +0000
[...] app[web.1]: Completed 500 Internal Server Error in 16ms
[...] app[web.1]: Processing by EventsController#create as HTML
[...] app[web.1]:   Parameters: {"authenticity_token"=>"1fvzDI1n+AlTssDybGFHAyiG4M+kir2UDLsA4dZGiAJvrWXdK6OnlAf1wK0V+/dI4QXz3lxonmRw15XbFjpOAQ=="}
[...] app[web.1]:    (0.8ms)  SELECT COUNT(*) FROM "analytics_calls"
[...] app[web.1]:
[...] app[web.1]: ArgumentError (Could not parse PKey: no start line):
[...] app[web.1]:   app/services/analytics_querier.rb:56:in `read'
[...] app[web.1]:   app/services/analytics_querier.rb:56:in `authorize_with_services'
[...] app[web.1]:   app/controllers/events_controller.rb:10:in `create'
2015-07-04T15:14:11.542398+00:00 app[web.1]:
[...] app[web.1]:   app/models/analytics_call.rb:19:in `get_batch'
[...] app[web.1]:
[...] heroku[router]: at=info method=POST path="/events" host=desolate-fortress-9280.herokuapp.com request_id=3822f668-6b8c-4cb2-9764-c171fc0dc67b fwd="104.231.12.227" dyno=web.1 connect=3ms service=33ms status=500 bytes=1754

Edit 2 - Possible lead in the right direction?

So, as pointed out by Val Asensio, there's a problem with the SSH setup provided by Heroku (this is more or less unfamiliar territory for me, but I'll try to word things as clearly as possible, given my understanding needs some fleshing out here).

I've found a gem called heroku-buildback-ssh, which definitely suggests that you need to do more than just load a private key into an env variable in order to use it properly. However, the readme specifies that the private cannot have a passphrase in order to be accessed. It also seems to assume you're using a file called something rsa, with no indication that this works for .pem files?

I found another discussion here about ssh tunneling from Heroku. I think this is the route to take. My understanding is that I need to be able to 'rebuild' the private key file using the env variable PRIVATE_KEY behind the scenes, and that I can write a script to do this when Heroku starts up.

However, my understanding here is so shaky, and the use-case described in the answer script is different enough that I'm not sure how to extrapolate how to go about building a solution using this kind of method.

What I want to know now is, can I build the .pem key file using a bash script that Heroku runs behind the scenes, and if so, how do I then make sure that that file is what is being read when my script runs OpenSSL::PKey.read? I guess I could also try to build the original .pk12 file itself instead?

ALSO - big question: am I going to screw anything up so terribly that I'm left in a tangled knot of confused code and have to trash the whole application (it's a code spike application, but I still want to know if anyone can provide an answer here) if I start poking around with writing scripts that create files and manage permissions on those files in my production environment?

Community
  • 1
  • 1
wendybeth
  • 414
  • 4
  • 15

2 Answers2

1

For non-Heroku deployments

Although I've not used this particular Google API, this seems like an SSH key problem on the server- the PKey error tells us that. The error indicating "no start line" leads me to think that a key part of the SSH setup is missing on the server side.

App side

Make sure you have this in your deploy.rb

ssh_options[:forward_agent] = true 

Server side

The server side SSH must be setup to access the Google API. Do you have an ~/.ssh/config file? Do you have the Google API SSH set there?

# Google API
Host hostservingAPIname.com
User yourusername
IdentityFile ~/.ssh/svn_id_rsa 

# Your app
Host yourappsdomian.com
User yourrailsappusername
IdentityFile ~/.ssh/id_rsa
ControlMaster auto

I'm not sure this info will be helpful, but since it doesn't seem like anyone who has the definitive answer is going to chime in, I hope my suggestion will be helpful in moving you towards a solution.


Note: Someone may search for this question and their app is not hosted on Heroku, so they may find this answer useful, so I'll leave it here.

Elvn
  • 2,781
  • 1
  • 12
  • 27
  • I neither have a deploy.rb file or an ~/.ssh/config file in my app. This is definitely getting into territory I am completely unfamiliar with. However, I've done some googling and found a few things which *may* be the right direction, but I have more questions. Editing original question now with info. Thanks for providing a new direction! – wendybeth Jul 05 '15 at 15:22
  • Please see my comment to your solution posted below. – Elvn Jul 05 '15 at 16:37
0

I had the two environment variables mixed up.

That's it.

That's all it was.

wendybeth
  • 414
  • 4
  • 15
  • I'm glad you got it resolved. Heroku does a lot of behind the scenes setup so you don't have to worry about deploy details. I should have caught that; on Heroku I don't think you don't have access to ~/.ssh/config file. SSH config is relevant if you are running your own server and or have access to the server config files. In any case, I hope my answer was useful in some respect. – Elvn Jul 05 '15 at 16:33
  • Thank you. Any research done is a good thing - that's knowledge I may find useful later down the line - even if it doesn't end up being directly applicable. At least I learned something. :) – wendybeth Jul 05 '15 at 18:02