8

I have been spending a few hours trying to set my Google Firebase credentials without using a Json file path as recommended in setup guide and have no luck.

What I am trying to do

I store the content of the service-account Json file in AWS SSM parameter store, encrypted as SecureString. During runtime, my app will get the Json string and decrypt it. Then, I turn it in a Python dictionary and pass it on to credentials.Certificate(service_acc_dict).

Problem

The above does not work. This is my code.

# `secret` is the Json string I get back after decrypting the google service account credentials from aws ssm

secret = '{\\n  \"type\": \"service_account\",\\n  \"project_id\": \"some-project\",\\n  \"private_key_id\": \"11z11111z1z111111b1111111z11z11z1111111z\",\\n  \"private_key\": \"-----BEGIN PRIVATE KEY-----\\\\nZZZZzZZZZZZZZzzzzzzZ1z1ZZZZZZZZZZZzzzzZzZzEAAoIBAQC7+0fFwMo6Pbz7\\\\nyk3Fxae5/Ebq56KSzezKk30+wKPymSW/uXBlIZZXlFJdKZNTFI5UdbPsKSypp+cW\\\\nNoAq06ojJ727j25ygMAOILeFJD1fb6c0TrDHsBiw0ECmPT9EOHddjHfF8Oj/gbg+\\\\n2EyRPZiT8238QfbZHnbZ35RpsnasNfk0n0qdB5///w1iFjzfZZbf+9UX6wE6ht7q\\\\nJlBOZan104saXi4UbmAmnz3fX/RVJ4ubO9XE4iDzQbljNONBNJvSbX9GuOgiTmCw\\\\nCK/x23rihABCZ6c9Q/3rkLsJEVqHYZkVHwaGcBF4V44qUrsd3GrHryCHLawhhz8l\\\\nDd3rBDIZAgMBAAECggEAFGgebgLUUUdDfUgEclxXNGAVTdMojGxLcOBa/9V01tC2\\\\nTt5oK6peQkqpOFDbm/DG1LdkXVZI8W/3P6uR9VQ+C4v0ZmiXMln0v3PgyFTbTsF1\\\\nstF6Emt0+rjY09MhS5wfpSmrFAQcd2oasMPVaAz6Q9Fw1qoojIBooZVKbMEBbgdM\\\\nUcs9tuCnYAOggNwgYoGsldAlkjrAOx1iopyHVhBo+cHbYW03Bgncvpq7fLLL056H\\\\aaaaAAAmVDoBFnvfS2SfT9DBPLeFC0JSgc6U6b1v9lzjzRWNdG6OfBFYJIwTZ8sCd\\\\n6wT7twniL0gBPN/y3TM5Skbo0c7IYo2LVyWcFy6j4wKBgQDb85FWMchXkRWLQDRJ\\\\npJm2JcFT3TATX7RMcGD4XRk/YXDD3pgwDi6zjqYcCFtYLHGTScc2wFAvkX4xUayX\\\\zz1ZZzzzZ939DQ/S2rPkJmUJpwY3hHd3mkAMV0CzzAAf1wvLeCO0g8N1AsX2YjhJb\\\\n81yLbV44EpERTOsbMkPpEXrsbwKBgQDaylzmnYCCwWKBQoTV7x07jZmIa2vCWCol\\\\nKO3zuhwk1r6HkdKtk1wN8kp8auMGf72REU4KRvsUQ6b+IzhP7kFcDYegNwv+JdB1\\\\n2FM0ZzpFmiIIHfgHGJhb2O1z58n5m01CrhpyD39y36MDTmC6zxmS1dHry0puV/fG\\\\nS8/wZTad9wKBgQCZw9U+5N6iGRNunhvvv9qVtB9Leb46TRXGummQN8WGwaALznmm\\\\nXsPXU0pdHpp9MdTUmydh52AnYRdPc0GtWzCrxvOZMGUPqVpSuun/A92iAWYmkrKm\\\\nlNcTUM2bs9HcxcTz5FmiRcDnkS20kN3U2nVAI91SZeh0p8lU4fcH4OiGkQKBgD7b\\\\nHE10ulLWVAJmpdsAUxmk2JMEqXSv94utco8uzJ8YwqwYDLqpNy0aiqOr4YUgdcmT\\\\neyQguEleFj+0xpzQCh70FB7HMb7WBkmU2HKZpXgRi+1hDrybKEpay/0cfj4ji9K4\\\\nSgiywx6xeReeENQaY3J301M2mC+TPi/N3/NkYIiJAoGAfBwdbptV/U7hXXgTnFps\\\\nZHdpAH4SddVBe2Ki6DLIiZPliXUSKcClrhy4evl2f4mA4sy7ovlBiXRA+IoNfkTT\\\\nH1Resx2kTlrkpT5+gsmDiY5HMNBHWuPjPIWNPwxzBSU3KK64TwPLFD0FBdfC6maS\\\\nVqyeIaHUW59ExHN5+FOfmWY=\\\\n-----END PRIVATE KEY-----\\\\n\",\\n  \"client_email\": \"firebase-adminsdk-zz1zz@some-project.iam.gserviceaccount.com\",\\n  \"client_id\": \"111111111111111111111\",\\n  \"auth_uri\": \"https://accounts.google.com/o/oauth2/auth\",\\n  \"token_uri\": \"https://oauth2.googleapis.com/token\",\\n  \"auth_provider_x509_cert_url\": \"https://www.googleapis.com/oauth2/v1/certs\",\\n  \"client_x509_cert_url\": \"https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-zz1zz%40some-project.iam.gserviceaccount.com\"\\n}\\n'

js = secret.replace('\\n  ', '').replace('\\\\n', '').replace('\\n', '')
js_dict = json.loads(js)

cred = credentials.Certificate(js_dict)

Results in the following error.

Traceback (most recent call last):
  File "/Users/joe/something/project/venv/lib/python3.7/site-packages/firebase_admin/credentials.py", line 97, in __init__
    json_data, scopes=_scopes)
  File "/Users/joe/something/project/venv/lib/python3.7/site-packages/google/oauth2/service_account.py", line 201, in from_service_account_info
    info, require=["client_email", "token_uri"]
  File "/Users/joe/something/project/venv/lib/python3.7/site-packages/google/auth/_service_account_info.py", line 55, in from_dict
    signer = crypt.RSASigner.from_service_account_info(data)
  File "/Users/joe/something/project/venv/lib/python3.7/site-packages/google/auth/crypt/base.py", line 114, in from_service_account_info
    info[_JSON_FILE_PRIVATE_KEY], info.get(_JSON_FILE_PRIVATE_KEY_ID)
  File "/Users/joe/something/project/venv/lib/python3.7/site-packages/google/auth/crypt/_cryptography_rsa.py", line 147, in from_string
    key, password=None, backend=_BACKEND
  File "/Users/joe/something/project/venv/lib/python3.7/site-packages/cryptography/hazmat/primitives/serialization/base.py", line 16, in load_pem_private_key
    return backend.load_pem_private_key(data, password)
  File "/Users/joe/something/project/venv/lib/python3.7/site-packages/cryptography/hazmat/backends/openssl/backend.py", line 1089, in load_pem_private_key
    password,
  File "/Users/joe/something/project/venv/lib/python3.7/site-packages/cryptography/hazmat/backends/openssl/backend.py", line 1315, in _load_key
    self._handle_key_loading_error()
  File "/Users/joe/something/project/venv/lib/python3.7/site-packages/cryptography/hazmat/backends/openssl/backend.py", line 1373, in _handle_key_loading_error
    raise ValueError("Could not deserialize key data.")
ValueError: Could not deserialize key data.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/joe/Library/Preferences/PyCharmCE2019.2/scratches/scratch.py", line 15, in <module>
    cred = credentials.Certificate(js)
  File "/Users/joe/something/project/venv/lib/python3.7/site-packages/firebase_admin/credentials.py", line 100, in __init__
    'Caused by: "{0}"'.format(error))
ValueError: Failed to initialize a certificate credential. Caused by: "Could not deserialize key data."

Process finished with exit code 1

I looked into /Users/joe/something/project/venv/lib/python3.7/site-packages/firebase_admin/credentials.py and could see that it looks for client_email and token_uri, both of which can be retrieved from js_dict as js_dict[client_email] and js_dict[token_uri].

What am I missing here? Why doesn't this work?

Edit

Original Json file

This is how the original Json file looks like.

{
  2   "type": "service_account",
  3   "project_id": "some-project",
  4   "private_key_id": "11z11111z1z111111b1111111z11z11z1111111z",
  5   "private_key": "-----BEGIN PRIVATE KEY-----\nZZZZzZZZZZZZZzzzzzzZ1z1ZZZZZZZZZZZzzzzZzZzEAAoIBAQC7+0fFwMo6Pbz7\nyk3Fxae5/Ebq56KSzezKk30+wKPymSW/uXBlIZZXlFJdKZNTFI5UdbPsKSypp+cW\nNoAq06ojJ727j25ygMAOILeFJD1fb6c0TrDHsBiw0ECmPT9EOHddjHfF8Oj/gbg+\n2EyRPZiT8238QfbZHnbZ35RpsnasNfk0n0qdB5///w1iFjzfZZbf+9UX6wE6ht7q\nJlBOZan104saXi4UbmAmnz3fX/RVJ4ubO9XE4iDzQbljNONBNJvSbX9GuOgiTmCw\nCK/x23rihABCZ6c9Q/3rkLsJEVqHYZkVHwaGcBF4V44qUrsd3GrHryCHLawhhz8l\nDd3rBDIZAgMBAAECggEAFGgebgLUUUdDfUgEclxXNGAVTdMojGxLcOBa/9V01tC2\nTt5oK6peQkqpOFDbm/DG1LdkXVZI8W/3P6uR9VQ+C4v0ZmiXMln0v3PgyFTbTsF1\nstF6Emt0+rjY09MhS5wfpSmrFAQcd2oasMPVaAz6Q9Fw1qoojIBooZVKbMEBbgdM\nUcs9tuCnYAOggNwgYoGsldAlkjrAOx1iopyHVhBo+cHbYW03Bgncvpq7fLLL056H\naaaAAAmVDoBFnvfS2SfT9DBPLeFC0JSgc6U6b1v9lzjzRWNdG6OfBFYJIwTZ8sCd\n6wT7twniL0gBPN/y3TM5Skbo0c7IYo2LVyWcFy6j4wKBgQDb85FWMchXkRWLQDRJ\npJm2JcFT3TATX7RMcGD4XRk/YXDD3pgwDi6zjqYcCFtYLHGTScc2wFAvkX4xUayX\nz1ZZzzzZ939DQ/S2rPkJmUJpwY3hHd3mkAMV0CzzAAf1wvLeCO0g8N1AsX2YjhJb\n81yLbV44EpERTOsbMkPpEXrsbwKBgQDaylzmnYCCwWKBQoTV7x07jZmIa2vCWCol\nKO3zuhwk1r6HkdKtk1wN8kp8auMGf72REU4KRvsUQ6b+IzhP7kFcDYegNwv+JdB1\n2FM0ZzpFmiIIHfgHGJhb2O1z58n5m01CrhpyD39y36MDTmC6zxmS1dHry0puV/fG\nS8/wZTad9wKBgQCZw9U+5N6iGRNunhvvv9qVtB9Leb46TRXGummQN8WGwaALznmm\nXsPXU0pdHpp9MdTUmydh52AnYRdPc0GtWzCrxvOZMGUPqVpSuun/A92iAWYmkrKm\nlNcTUM2bs9HcxcTz5FmiRcDnkS20kN3U2nVAI91SZeh0p8lU4fcH4OiGkQKBgD7b\nHE10ulLWVAJmpdsAUxmk2JMEqXSv94utco8uzJ8YwqwYDLqpNy0aiqOr4YUgdcmT\neyQguEleFj+0xpzQCh70FB7HMb7WBkmU2HKZpXgRi+1hDrybKEpay/0cfj4ji9K4\nSgiywx6xeReeENQaY3J301M2mC+TPi/N3/NkYIiJAoGAfBwdbptV/U7hXXgTnFps\nZHdpAH4SddVBe2Ki6DLIiZPliXUSKcClrhy4evl2f4mA4sy7ovlBiXRA+IoNfkTT\nH1Resx2kTlrkpT5+gsmDiY5HMNBHWuPjPIWNPwxzBSU3KK64TwPLFD0FBdfC6maS\nVqyeIaHUW59ExHN5+FOfmWY=\n-----END PRIVATE KEY-----\n",
  6   "client_email": "firebase-adminsdk-zz1zz@some-project.iam.gserviceaccount.com",
  7   "client_id": "111111111111111111111",
  8   "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  9   "token_uri": "https://oauth2.googleapis.com/token",
 10   "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
 11   "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-zz1zz%40some-project.iam.gserviceaccount.com"
 12 }

From the above, I put it in AWS SSM parameter store like this.

$ aws ssm put-parameter --name "/firebase/credentials.json" --type SecureString --key-id 11111111-123a-12a1-1111-aaaa1a1a111z --description "Firebase credentials" --value file:///Users/joe/something/some-project-firebase-adminsdk-zq1xa-12g12345a3.json

On the app, I do get-parameter with no decryption. And during runtime, I decrypt the base64 encoded string using AWS kms like this.

import json
import os
import base64
import boto3

kms = boto3.client('kms')

encryption_context = {"PARAMETER_ARN": os.environ.get('ENCRYPTION_CONTEXT')}
credentials_encrypted = os.environ.get('ENCRYPTED_CREDENTIALS')
credentials_blob = base64.b64decode(credentials_encrypted)
credentials = kms.decrypt(CiphertextBlob=credentials_blob, EncryptionContext=encryption_context)['Plaintext'].decode('utf-8')
billydh
  • 975
  • 11
  • 27
  • 2
    On the first look, the problem might be with the replaces. Can you try this `js = secret.replace('\\n', '\n').replace('\\\\n', '\\n')` – Emil Gi Dec 02 '19 at 15:38
  • `js = secret.replace('\\n ', '\n').replace('\\\\n', '\\n')`, typoed – Emil Gi Dec 02 '19 at 15:58
  • @EmilGi I got this error `json.decoder.JSONDecodeError: Expecting ',' delimiter: line 1 column 2305 (char 2304)`. I did `print(js[2304:2306])` and it is this character `\n`. – billydh Dec 02 '19 at 20:34
  • @EmilGi It is because the last few characters after the `replace`'s are `\n}\n`. If I add another `replace` like `.replace(r'\n}\n', '}')` then it works. But i just feel like it's a bit unclean of a solution. – billydh Dec 02 '19 at 20:40
  • To me these artifact newlines and escapes are added during encryption-decryption. You either deal with them after, or they should be dealt with inside library. – Emil Gi Dec 03 '19 at 12:19

2 Answers2

15

In the Firebase docs, they don't mention much that dicts are a valid arg to credentials.Credential:

creds_dict = json.loads(decrypt(os.environ.get(
    ("FIREBASE_SERVICE_ACCOUNT_CREDENTIAL"))))

creds = credentials.Certificate(creds_dict)

firebase_admin.initialize_app(
    creds, {'databaseURL': os.environ.get(("FIREBASE_DB_URL"))})
db = firestore.client()

So yes, you can use a dict instead of specifying a path to your key file. See source code.

logicalicy
  • 857
  • 1
  • 9
  • 19
  • but then I get an error: File "/usr/local/lib/python3.7/site-packages/firebase_admin/credentials.py", line 82, in __init__ with open(cert) as json_file: FileNotFoundError: [Errno 2] No such file or directory: '' – Baruch Gans Aug 25 '21 at 13:03
  • It is mentioned in the `Certificate` constructor doc as `cert: Path to a certificate file or a dict representing the contents of a certificate.`. --> https://github.com/firebase/firebase-admin-python/blob/master/firebase_admin/credentials.py – Henshal B Oct 28 '22 at 03:42
0

I'm also confirming that I successfully load from dict based on my .env instead from json file directly.

Only thing that you need to take care, if you load for example serviceAcountKeys.json values from .env, is to replace double "//" with single one. Otherwise it will trhow the ValueError. Here is example:

service_account_key = {
    "type": env("TYPE"),
    "project_id": env("PROJECT_ID"),
    "private_key_id": env("PRIVATE_KEY_ID"),
    "private_key": env("PRIVATE_KEY").replace("\\n", "\n"),
    "client_email": env("CLIENT_EMAIL"),
    "client_id": env("CLIENT_ID"),
    "auth_uri": env("AUTH_URI"),
    "token_uri": env("TOKEN_URI"),
    "auth_provider_x509_cert_url": env("AUTH_PROVIDER_X509_CERT_URL"),
    "client_x509_cert_url": env("CLIENT_X509_CERT_URL"),
}

cred = credentials.Certificate(service_account_key)
FIREBASE = firebase_admin.initialize_app(cred)
Ilija
  • 11
  • 1
  • 4