Python gCloud billing APIs from CloudRun container instance gives 404 error

Question:

The account that is running cloudrun has been linked to the billing account, but still the cloud billing python apis throw errors. What else needs to be done in the service account ?

#1. Created json token for service account and passed into the access_bills() method

#2. The service account has role for Billing Access View

#3. Copied these methods as advised in comments from John Hanley’s blog:

def load_private_key(json_cred):
    ''' Return the private key from the json credentials '''

    return json_cred['private_key']


def create_signed_jwt(pkey, pkey_id, email, scope):
    '''
    Create a Signed JWT from a service account Json credentials file
    This Signed JWT will later be exchanged for an Access Token
    '''

    # Google Endpoint for creating OAuth 2.0 Access Tokens from Signed-JWT
    auth_url = "https://www.googleapis.com/oauth2/v4/token"
    expires_in = 3600

    issued = int(time.time())
    expires = issued + expires_in   # expires_in is in seconds

    # Note: this token expires and cannot be refreshed. The token must be recreated

    # JWT Headers
    additional_headers = {
            'kid': pkey_id,
            "alg": "RS256", # Google uses SHA256withRSA
            "typ": "JWT"
    }

    # JWT Payload
    payload = {
        "iss": email,       # Issuer claim
        "sub": email,       # Issuer claim
        "aud": auth_url,    # Audience claim
        "iat": issued,      # Issued At claim
        "exp": expires,     # Expire time
        "scope": scope      # Permissions
    }

    # Encode the headers and payload and sign creating a Signed JWT (JWS)
    sig = jwt.encode(payload, pkey, algorithm="RS256", headers=additional_headers)

    return sig


def exchangeJwtForAccessToken(signed_jwt):
    '''
    This function takes a Signed JWT and exchanges it for a Google OAuth Access Token
    '''

    auth_url = "https://www.googleapis.com/oauth2/v4/token"

    params = {
        "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
        "assertion": signed_jwt
    }

    r = requests.post(auth_url, data=params)

    if r.ok:
        return(r.json()['access_token'], '')

    return None, r.text


def access_bills(sa_json):
    cred = json.loads(sa_json)
    private_key = load_private_key(cred)
    # scopes = "https://www.googleapis.com/auth/cloud-platform" # this does not work, gets 404
    scopes = "https://www.googleapis.com/auth/cloud-billing.readonly"

    s_jwt = create_signed_jwt(
            private_key,
            cred['private_key_id'],
            cred['client_email'],
            scopes)

    token, err = exchangeJwtForAccessToken(s_jwt)

    if token is None:
        logger.error("Error: {}".format(err))
        exit(1)

    
    logger.info("Token response: {}".format(token))

    # the token is obtained and prints in the log

    headers = {
        "Host": "www.googleapis.com",
        "Authorization": "Bearer " + token,
        "Content-Type": "application/json"
    }

    try:
        url = "https://cloudbilling.googleapis.com/v1/billingAccounts/01C8DC-336472-E177E1" # account name is "Billing Core"
        response = requests.get(url=url, headers=headers)
        logger.info("Response: {}".format(response))

        # logs -> app - INFO - Response: <Response [404]>

        return {
            'statusCode': 200,
            'body': 'Success'
        }
    except Exception as e:
        logger.error("Error")
        raise e

It gives 404 error as shown in the comment log after trying on that url.

Asked By: PainPoints

||

Answers:

Okay I found that the only way it works as of now is via big query export from billing account, sort out dataViewer permission and run the corresponding sql from python application, it works.

Answered By: PainPoints