0

I have an application deployed on Heroku that I want to connect to GCS using ActiveStorage. I am explicitly specifying credentials in config/storage.yml as specified in the JSON key file and the Rails docs asfollows, where I've replaced my actual project name with my-project and redacted my client ID:

google:
  service: GCS
  project: my-project
  bucket: my-project-<%= Rails.env %>
  credentials:
    type: service_account
    project_id: my-project
    private_key_id: "<%= ENV['GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY_ID'] %>"
    private_key: "<%= ENV['GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY'] %>"
    client_email: "[email protected]"
    client_id: "<redacted>"
    auth_uri: "https://accounts.google.com/o/oauth2/auth"
    token_uri: "https://oauth2.googleapis.com/token"
    auth_provider_x509_cert_url: "https://www.googleapis.com/oauth2/v1/certs"
    client_x509_cert_url: "https://www.googleapis.com/robot/v1/metadata/x509/my-project-service-account%40my-project.iam.gserviceaccount.com"
    universe_domain: "googleapis.com"

Again, the above is derived from a JSON key file I created and download for the service account specified. That service account has Owner permissions.

I have verified from the production Rails console that these settings are appearing as specified under Rails.application.config.active_storage.service_configurations['google'], and that the ENV vars are filled in correctly in the environment, per the JSON key file.

However, when I try to upload a file in production, I receive the following error and stack trace:

2025-04-09T23:29:11.090818+00:00 app[web.1]: E, [2025-04-09T23:29:11.090754 #11] ERROR -- : [eeabb3d7-6d82-45ea-813a-716c33a5e1df]
2025-04-09T23:29:11.090819+00:00 app[web.1]: [eeabb3d7-6d82-45ea-813a-716c33a5e1df] OpenSSL::PKey::RSAError (Neither PUB key nor PRIV key):
2025-04-09T23:29:11.090819+00:00 app[web.1]: [eeabb3d7-6d82-45ea-813a-716c33a5e1df]
2025-04-09T23:29:11.090820+00:00 app[web.1]: [eeabb3d7-6d82-45ea-813a-716c33a5e1df] vendor/ruby-3.4.2/lib/ruby/3.4.0/openssl/pkey.rb:356:in 'OpenSSL::PKey::RSA#initialize'
2025-04-09T23:29:11.090820+00:00 app[web.1]: [eeabb3d7-6d82-45ea-813a-716c33a5e1df] vendor/ruby-3.4.2/lib/ruby/3.4.0/openssl/pkey.rb:356:in 'Class#new'
2025-04-09T23:29:11.090821+00:00 app[web.1]: [eeabb3d7-6d82-45ea-813a-716c33a5e1df] vendor/ruby-3.4.2/lib/ruby/3.4.0/openssl/pkey.rb:356:in 'OpenSSL::PKey::RSA.new'
2025-04-09T23:29:11.090821+00:00 app[web.1]: [eeabb3d7-6d82-45ea-813a-716c33a5e1df] googleauth (1.14.0) lib/googleauth/service_account.rb:79:in 'Google::Auth::ServiceAccountCredentials.make_creds'
2025-04-09T23:29:11.090821+00:00 app[web.1]: [eeabb3d7-6d82-45ea-813a-716c33a5e1df] googleauth (1.14.0) lib/googleauth/default_credentials.rb:50:in 'Google::Auth::DefaultCredentials.make_creds'
2025-04-09T23:29:11.090822+00:00 app[web.1]: [eeabb3d7-6d82-45ea-813a-716c33a5e1df] googleauth (1.14.0) lib/googleauth/credentials.rb:567:in 'Google::Auth::Credentials#init_client'
2025-04-09T23:29:11.090823+00:00 app[web.1]: [eeabb3d7-6d82-45ea-813a-716c33a5e1df] googleauth (1.14.0) lib/googleauth/credentials.rb:610:in 'Google::Auth::Credentials#update_from_hash'
2025-04-09T23:29:11.090823+00:00 app[web.1]: [eeabb3d7-6d82-45ea-813a-716c33a5e1df] googleauth (1.14.0) lib/googleauth/credentials.rb:404:in 'Google::Auth::Credentials#initialize'
2025-04-09T23:29:11.090823+00:00 app[web.1]: [eeabb3d7-6d82-45ea-813a-716c33a5e1df] google-cloud-storage (1.55.0) lib/google/cloud/storage.rb:114:in 'Class#new'
2025-04-09T23:29:11.090824+00:00 app[web.1]: [eeabb3d7-6d82-45ea-813a-716c33a5e1df] google-cloud-storage (1.55.0) lib/google/cloud/storage.rb:114:in 'Google::Cloud::Storage.new'

I've manually verified that OpenSSL::PKey::RSA.new(ENV['GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY']) returns successfully in the production console.

I'm out of ideas, other than to bag on GCS and use S3 instead, which is trivial to set up by comparison.

8
  • I'm not a Heroku nor Ruby (!?) developer. You don't include any code but I assume (!?) that you're using a Google SDK. These default to using Application Default Credentials. Your approach is fine (I assume there's no WIF w/ Heroku), using a Service Account and grabbing the key. However, I suspect (!?) that you're approach of transcribing the key into storage.yml is incorrect. I am unable to find your approach documented?
    – DazWilkin
    Commented Apr 10 at 1:35
  • I would expect (but can't find documented) GOOGLE_APPLICATION_CREDENTIALS being set to point to the key's location or revising your application code to configure the use of the key directly.
    – DazWilkin
    Commented Apr 10 at 1:35
  • In this example the client is created with an explicit reference to the key file.
    – DazWilkin
    Commented Apr 10 at 1:36
  • Thanks for your comments!! My approach is documented here: guides.rubyonrails.org/… . The problem with pointing to a file is that I would have to check the file in to git, which both Google and that Rails doc expressly forbid. If I didn't, because the Heroku filesystem is ephemeral (it's recycled every 24 hours), any file I create on the filesystem (e.g. by upload) would be blown away every day, which is not the way to run a prod website. So, I was forced to try the second approach documented on that link to Rails docs.
    – aec
    Commented Apr 10 at 1:55
  • Also, I considered switching to Docker for deployment (rather than just deploying using the git repo) and including the file in app images, but Docker images can be deserialized. So, same problem, hence the hash approach, which is documented to work, but doesn't. I'll try the hash approach locally now (rather than using the file store locally), so at least I can try different things quickly, and debug more readily. Gotta time box it though: I know S3 is trivial to use, so if I can't figure it out in an hour or so, I'll have to kiss GCP goodby.
    – aec
    Commented Apr 10 at 2:00

1 Answer 1

1

Actually, I partially figured this out.

The problem was that the private key string MUST be specified with newlines as \n. Whereas, I had been using actual newlines when setting the private key in an ENV var, but when bash interpolated them into the ENV var, those newlines became spaces. Bah.

Now, the new problem is that I see these two messages, with no stack traces:

2025-04-10T02:22:02.175588+00:00 app[api]: Set GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY config vars by user [email protected]
2025-04-10T02:22:02.175588+00:00 app[api]: Release v80 created by user [email protected]

In the browser, I simply see this in a modal: Error storing "saturn.jpg". Status: 0

No idea what to do now, but hopefully easy to solve.

Oh, and the weird thing is that those logs use my personal gmail email address, not the service account email. (!!) No idea where that would come from: I'm not using my personal email anywhere in app code, and I'm not even logging on with that email, though it does own both the Heroku account and the GCP account, and is the email associated with the GCP service account.

3
  • Your Ruby code would be helpful. Perhaps add some debugging. Status: 0 is unusually vague. Was the object created in GCS?
    – DazWilkin
    Commented Apr 10 at 2:58
  • When I try locally, I see the same useless error modal in the browser, but in the logs I see GCS Storage (2.3ms) Generated URL for file at key: <key redacted> (https://storage.googleapis.com/my-project-development/<lotsa URL stuff redacted>. But, the file isn't uploaded even though the key appears to be generated. I'll try for a few more minutes, but think it's time to switch to AWS S3.
    – aec
    Commented Apr 10 at 2:59
  • OK, solved this one too. This new problem is that Direct Uploads weren't working, for reasons as yet unclear. Since the upload capability is only needed by a couple administrators sporadically using an admin app, I don't need to fix it right now. Eventually though, sure. On to the next blocker ... :-)
    – aec
    Commented Apr 10 at 16:43

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.