3

My goal is to reproduce/replicate the functionality of gcloud compute addresses create without depending on the gcloud binary.

I am trying to use python to authenticate a POST to a googleapis compute endpoint per the documentation at https://cloud.google.com/compute/docs/ip-addresses/reserve-static-external-ip-address about reserving a static external ip address

But my POST's return 401 every time.

I have created a JWT from google.auth.jwt python module and when I decode it the JWT has all the strings embedded that I would expect to be there.

I've also tried combinations of the following OAuth scopes to be included in the JWT: - "https://www.googleapis.com/auth/userinfo.email" - "https://www.googleapis.com/auth/compute" - "https://www.googleapis.com/auth/cloud-platform"

this is my function for getting a JWT using the information in my service account's JSON key file

def _generate_jwt( tokenPath, expiry_length=3600 ):
    now = int(time.time())
    tokenData = load_json_data( tokenPath )
    sa_email = tokenData['client_email']
    payload = {
        'iat': now, 
        # expires after 'expiry_length' seconds.
        "exp": now + expiry_length,
        'iss': sa_email,
        "scope": " ".join( [
            "https://www.googleapis.com/auth/cloud-platform",
            "https://www.googleapis.com/auth/compute",
            "https://www.googleapis.com/auth/userinfo.email"
        ] ),
        'aud': "https://www.googleapis.com/oauth2/v4/token",
        'email': sa_email
    }
    # sign with keyfile
    signer = google.auth.crypt.RSASigner.from_service_account_file( tokenPath )
    jwt = google.auth.jwt.encode(signer, payload)

    return jwt

once I have the JWT then I make the following post that fails, 401, ::

    gapiURL = 'https://www.googleapis.com/compute/v1/projects/' + projectID + '/regions/' + region + '/addresses'
    jwtToken = _generate_jwt( servicetoken )
    headers = {  
        'Authorization': 'Bearer {}'.format( jwtToken ),
        'content-type' : 'application/json',
    }    
    post = requests.post( url=gapiURL, headers=headers, data=data ) 
    post.raise_for_status()
    return post.text

I received a 401 no matter how many combinations of scopes I used in the JWT or permissions I provided to my service account. What am I doing wrong?

edit: many thanks to @JohnHanley for pointing out that I'm missing the next/second POST to https://www.googleapis.com/oauth2/v4/token URL in GCP's auth sequence. So, you get a JWT to get an 'access token.'

I've changed my calls to use the python jwt module rather than the google.auth.jwt module in-combo with the google.auth.crypt.RSASigner. So the code is a bit simpler and I put it in a single method

## serviceAccount auth sequence for google :: JWT -> accessToken
def gke_get_token( serviceKeyDict, expiry_seconds=3600 ):

    epoch_time = int(time.time())
    # Generate a claim from the service account file.
    claim = {
        "iss": serviceKeyDict["client_email"],
        "scope": " ".join([
            "https://www.googleapis.com/auth/cloud-platform",
            "https://www.googleapis.com/auth/userinfo.email"
        ]),
        "aud": "https://www.googleapis.com/oauth2/v4/token",
        "exp": epoch_time + expiry_seconds,
        "iat": epoch_time
    }    
    # Sign claim with JWT.
    assertion = jwt.encode( claim, serviceKeyDict["private_key"], algorithm='RS256' ).decode() 
    data = urllib.urlencode( {
        "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
        "assertion": assertion
    } )  
    # Request the access token.
    result = requests.post(
        url="https://www.googleapis.com/oauth2/v4/token",
        headers={
            "Content-Type": "application/x-www-form-urlencoded"
        },
        data=data
    )    
    result.raise_for_status()
    return loadJsonData(result.text)["access_token"]



3
  • 1
    You have not created an Access Token, you have created a Signed JWT. You need to exchange the Signed JWT for an Access Token. I wrote an article on how to do this in Python. jhanley.com/… Commented Jun 28, 2019 at 17:23
  • 1
    @JohnHanley - thank you for the clarification there. I read through your article and I was able to successfully get an authentication token for my service account. Please change your comment to an answer so that I can select it because it was the right solution and I want you to get credit for it. I will edit my question with the changes I've made from reading your article in just a bit. Commented Jul 2, 2019 at 12:55
  • 1
    Answer posted. Thank you. Commented Jul 2, 2019 at 21:41

1 Answer 1

3

In Google Cloud there are three types of "tokens" that grant access:

  • Signed JWT
  • Access Token
  • Identity Token

In your case you created a Signed JWT. A few Google services accept this token. Most do not.

Once you create a Signed JWT, then next step is to call a Google OAuth endpoint and exchange for an Access Token. I wrote an article that describes this in detail:

Google Cloud – Creating OAuth Access Tokens for REST API Calls

Some Google services now accept Identity Tokens. This is called Identity Based Access Control (IBAC). This does not apply to your question but is the trend for the future in Google Cloud Authorization. An example is my article on Cloud Run + Cloud Storage + KMS:

Google Cloud – Go – Identity Based Access Control

The following example Python code shows how to exchange tokens:

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
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.