Google Cloud: sign a JWT while impersonating a service account - python

Goal:
I have a cloud function that impersonates a service account based on the service account email provided to it. I then want to use the impersonated service account to sign a jwt.
What I tried:
I have set up the impersonation of the service account like this:
credentials = google.auth.impersonated_credentials.Credentials(
source_credentials=SOURCE_CREDENTIALS,
target_principal=SA_EMAIL,
target_scopes=TARGET_SCOPES,
lifetime=300)
This brings me to a sub question: What target scopes do I need to specify?
After creating the impersonated credentials, I try to use signer (docs) to sign the payload of the JWT:
payload = {
'iat': now,
"exp": now + expiry_length,
[...]
}
signer = target_credentials.signer
jwt = google.auth.jwt.encode(signer, payload)
The error message I receive is:
AttributeError: 'Credentials' object has no attribute 'key_id'
Question:
I suspect I am not on the right track with my approach using signer.
How is it possible to achieve my goal?

In your example code, you have only create the payload section. A signed JWT also requires a header.
The header looks like this:
header = {
'kid': pkey_id, # Signing Private Key ID
"alg": "RS256", # Google uses SHA256withRSA
"typ": "JWT"
}
The header and payload are base64 encoded and then concatenated with a dot (period symbol) in between. You sign the combined data.
The problem that you will have is that you need to private key id (pkey_id in my example). That value is located in the service account JSON key file. However, Google does not publish the JSON key file that is used for signing. The key used is private to Google. You can get the private key by signing some data first as the pkey_id is returned as part of the response. Then sign your JWT.
I recommend that you switch from using the Sign Blob API and use the SignJwt API instead. The SignJwt API does not require that you create a JWT header.
IAM Service Account Credentials API
Method: projects.serviceAccounts.signJwt

Related

Google Analytics API authentication failure using JWT

I want to connect to Google Analytics Reporting API to fetch data, For which I have been provided with details of the service account in JSON format which contains details like (type, project_id, client_email, client_id, private_key,client_x509_cert_url, auth_provider_x509_cert_url)
Using this JSON file I am able to connect and fetch required report data via Python code Reporting API quickstart. However, due to business requirements, I have to establish the connection using HTTP Rest API.
So based on the details of the same service account I am trying to authenticate using JWT by referring to Google Docs Addendum: Service account authorization without OAuth so that once I get the signature I can use that as a static token and make next API call to get report data https://analyticsreporting.googleapis.com/v4/reports:batchGet and passing Request Body
But while testing in https://jwt.io/ & also in https://irrte.ch/jwt-js-decode/index.html signature creation is failing with an Invalid Signature error.
Can someone please help to understand what am I missing here?
Header
{
"alg": "RS256",
"typ": "JWT",
"kid": "<service account's private key ID>"
}
Payload
{
"iss": "<service account's email address i.e. client_email>",
"sub": "<service account's email address i.e. client_email>",
"aud": "https://oauth2.googleapis.com/token",
"iat": <UTC Unix Epoch Start Time>,
"exp": <UTC Unix Epoch End Time>
}
And then pasted X.509 PUBLIC CERTIFICATE generated for the service account and in Private key pasted Private key again which is from JSON file of service account
-----BEGIN CERTIFICATE-----\nMIrrhherhehrgssreheheherhehehehrhrdsfsfzcsvsvsvscssvsrjnfvdsfsscscscshgeferttyyuuuuuuuuuuudDxE/Q\n-----END CERTIFICATE-----\n
-----BEGIN PRIVATE KEY-----\nLDSVSVSCSYUNJKOLPDFSSSFSFSFSSFSFSFSFSFSFSSFSFSFSFSFSFSFSSSSSSSSSGGGGGGGGGGGGGGGGGGGGGGUUUUUUUUUUUUUUUUUM=\n-----END PRIVATE KEY-----\n
PS: I have tried to test with \n without \n as well as with prefixes BEGIN, END and without it still have the same result.

How to decode opaque Access Token with Oauth2.0

I have got a token key which contains the logged in person email address as well as the name and other end points.This was actually used in xero API connection.
scope = 'offline_access accounting.reports.read accounting.settings.read openid profile email'
I need to decode this token key and get the logged in email address and the name of the person who is logged in.
For an example my token key is as below.
b9b73c12b40a3bc1441f5bda331c4d7c64c0394956d5105eec61a71de19f8153
How can I decode this opaque Access Token and get the relevant information using python.
Clients should never decode access tokens directly, as jps says. You have these options:
READ USER FIELDS FROM ID TOKEN
The UI reads this JWT directly. An id token always has JWT format and is designed to be read by clients.
USE USER INFO ENDPOINT
The UI can send the access token to the User Info endpoint, using the message from step 24 of the above blog post.
GET USER INFO FROM API
This tends to be the most extensible option, since you can return any info you want, and you are not limited to what is contained in access tokens.
const token = req.headers.authorization.split(" ")[1]; //Bearer +token
const decodedToken = jwt.verify(token, "secret_message_long_string");
req.userData = { email: decodedToken.email, userId: decodedToken.userId };
This example is for decoding data for nodejs.
try the same way in python. Importing jwt and using the method verfiy passing the token and secret string as an argument.

Having trouble using newly generated token making API calls. PARTNER_AUTHENTICATION_FAILED?

I am currently able to generate a token from Docusign's API.
I am using the python wrapper docusign-esign.
{
'access_token': <JWT token>,
'data': None,
'expires_in': '3600',
'refresh_token': None,
'scope': None,
'token_type': 'Bearer'
}
For Developers: API and Integration Key Information
Using
Implicit Grant
RSA Keypairs
redirect URI set
status LIVE
BASE_URL='https://demo.docusign.net/restapi'
OAUTH_BASE_URL='account-d.docusign.com'
I was using the envelope API to get envelope status with auth header as
'Authorization Bearer <JWT token>'
I get the following 400 error:
{
"errorCode": "PARTNER_AUTHENTICATION_FAILED",
"message": "The specified Integrator Key was not found or is disabled. Invalid account specified for user."
}
The public ID, integrator key, API Account ID and API Username are all separate IDs and are used at a different moment when making API calls. This has caused me a huge amount of frustration when dealing with the API.
To help clarify (this is specific to python's wrapper for Docusign):
when using request_jwt_user_token
it requires
client_id -- integrator key
user_id -- ID you want to impersonate, which can be found in the admin page > users > API Username (which is a UUID)
oauth_host_name -- account-d.docusign.com for dev or account.docusign.com for production
private_key_bytes -- the RSA Keypairs in the file (private key)
scopes -- typically signature impersonate for JWT implicit grant
then when using the EnvelopeAPI.create_envelope it required an account_id, which was not the client_id nor user_id. The account_id for me was the API Account ID for in For Developers: API and Integration Key Information. So either copy and paste the API Account ID from docusign or get it using the JWT grant example here
This was probably totally my fault for not understanding it, but hopefully, this is some info that can help someone else.
do you actually replace with a JWT token? and how did you generate this token?
Please make sure you read and follow the instructions here - https://github.com/docusign/eg-01-python-jwt

YouTube API without user OAuth process

I am trying to fetch captions from YouTube video using YouTube Data API (v3)
https://developers.google.com/youtube/v3/guides/implementation/captions
So, first I tried to retrieve a captions list using this url:
https://www.googleapis.com/youtube/v3/captions?part=snippet&videoId=KK9bwTlAvgo&key={My API KEY}
I could retrieve the caption id that I'd like to download (jEDP-pmNCIqoB8QGlXWQf4Rh3faalD_l) from the above link.
Then, I followed this instruction to download the caption:
https://developers.google.com/youtube/v3/docs/captions/download
However, even though I input the caption id and my api key correctly, it shows "Login Required" error.
I suppose I need OAuth authentication, but what I am trying to do is not related to my users's account, but simply downloading public caption data automatically.
My question is: Is there any way to process OAuth authentication just once to get an access token of my own YouTube account and then reuse it whenever I need it in my application?
I can't speak to the permissions needed for the captions API in particular, but in general, yes, you can OAuth to your app once using your own account and use the access and refresh tokens to make subsequent OAuth'd requests to the API. You can find the details of generating tokens here:
https://developers.google.com/youtube/v3/guides/auth/server-side-web-apps#Obtaining_Access_Tokens
To perform the steps manually (fortunately, you only need to do this once):
If access has already been granted for an app, it needs to be removed so that new auth credentials can be established. Go to https://security.google.com/settings/security/permissions (while logged into your account) and remove access to the app. If the client ID or secret change (or you need to create one), find them at https://console.developers.google.com under API Manager.
To grant access and receive a temporary code, enter this URL in a browser:
https://accounts.google.com/o/oauth2/auth?
client_id=<client_id>&
redirect_uri=http://www.google.com&
scope=https://www.googleapis.com/auth/youtube.force-ssl&
response_type=code&
access_type=offline&
approval_prompt=force
Follow the prompt to grant access to the app.
This will redirect to google.com with a code parameter (e.g.,
https://www.google.com/?code=4/ux5gNj-_mIu4DOD_gNZdjX9EtOFf&gws_rd=ssl#). Save the code.
Send a POST request (e.g., via Postman Chrome plugin) to https://accounts.google.com/o/oauth2/token with the following in the request body:
code=<code>&
client_id=<client_id>&
client_secret=<client_secret>&
redirect_uri=http://www.google.com&
grant_type=authorization_code
The response will contain both an access token and refresh token. Save both, but particularly the refresh token (because the access token will expire in 1 hour).
You can then use the access token to send an OAuth'd request manually, following one of the options here, essentially:
curl -H "Authorization: Bearer ACCESS_TOKEN" https://www.googleapis.com/youtube/v3/captions/<id>
or
curl https://www.googleapis.com/youtube/v3/captions/<id>?access_token=ACCESS_TOKEN
(When I tried the second option for captions, however, I got the message: "The OAuth token was received in the query string, which this API forbids for response formats other than JSON or XML. If possible, try sending the OAuth token in the Authorization header instead.")
You can also use the refresh token in your code to create the credential needed when building your YouTube object. In Java, this looks like the following:
String clientId = <your client ID>
String clientSecret = <your client secret>
String refreshToken = <refresh token>
HttpTransport transport = new NetHttpTransport();
JsonFactory jsonFactory = new JacksonFactory();
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(transport)
.setJsonFactory(jsonFactory)
.setClientSecrets(clientId, clientSecret)
.build()
.setRefreshToken(refreshToken);
try {
credential.refreshToken();
} catch (IOException e) {
e.printStackTrace();
}
youtube = new YouTube.Builder(transport, jsonFactory, credential).build();
I imagine you can do something similar in Python with the API Client Libraries, although I haven't tried Python.

Interacting with Azure Key Vault using python w/ rest api

I am very interested in using the new service recently released for secret management within Azure. I have found a few example guides walking through how to interact with key vault via powershell cmdlets and c#, however haven't found much at all in regards to getting started with using the rest API.
The thing I am particularly confused with is the handling of oauth2 w/ active directory. I have written a oauth2 application listener, built a web application with an AD instance and can now generate a "access_token". It is very unclear to me how to proceed beyond this though, as I seem to consistently receive a 401 HTTP resp code whenever attempting to use my access_token to perform a key vault API call.
Any guides / tips on using azure key vault with python would be greatly appreciated!
Here are some steps you'll need to do before the following code will work... Hopefully I remembered everything!
You'll need to have an application in AD with at least get access
note: you need this to get the CLIENT_ID and CLIENT_SECRET anyway
then run:
azure keyvault set-policy --vault-name 'VAULTNAME' --spn CLIENT_ID --perms-to-secrets '["get"]'
You'll also need the id's for your secrets, which you can get with the Azure CLI using:
azure keyvault secret show [vault] [secret]
or
azure keyvault secret show -h # if this is unclear
Copy the key (last argument in the URL)
Then the following code will allow you to query the key vault using oauth2:
import json
import requests
AUTHORITY_HOST = "login.windows.net"
TENANT_ID = < your tenant id >
CLIENT_ID = < your client id >
CLIENT_SECRET = < your client secret >
VAULT = 'MyVault'
data = { "grant_type" : "client_credentials",
"client_id" : CLIENT_ID,
"client_secret" : CLIENT_SECRET,
"resource" : "https://vault.azure.net"
}
secrets = [( "i_like_pie", "8a7680a2cf5e4d539494aa0ce265297" )]
headers = { "Content-Type" : "application/x-www-form-urlencoded" }
r = requests.post("https://login.windows.net/{}/oauth2/token".format(TENANT_ID), data=data, headers=headers)
access_token = r.json()['access_token']
for secret, secret_id in secrets.iteritems():
headers = {"Authorization":"Bearer {}".format(access_token) }
r = requests.get('https://{}.vault.azure.net/secrets/{}/{}?api-version=2015-06-01'.format(VAULT, secret, secret_id), headers=headers)
print('##### {} #####'.format(secret))
print(r.json())
print('')
Here are a couple of things that you can check:
When you make the request for the Bearer token, make sure that you include the "resource" header, and that it is set to "https://vault.azure.net". If you don't, you'll get a token, but you won't be able to access any vault data with it.
When you make a call to the vault.azure.net URL, make sure you include the correct "api-version" It can be found in the API documentation. The current value is "2015-02-01-preview".
Of course, check that the Key Vault Access Policy is set correctly for the vault you are trying to access.
For working with Key Vault's REST API, there's reference documentation and service documentation that should help.
Using Key Vault with Python is now more easily done with the Azure SDK. There are three Python packages for working with existing vault data, and one for creating/managing vaults:
azure-keyvault-certificates (Migration guide)
azure-keyvault-keys (Migration guide)
azure-keyvault-secrets (Migration guide)
azure-mgmt-keyvault
azure-identity is also the package that should be used with these for authentication.
With the SDK, using an access token to work with an existing vault from an authorized application is as easy as creating a credential and client:
from azure.identity import DefaultAzureCredential
from azure.keyvault.secrets import SecretClient
credential = DefaultAzureCredential()
client = SecretClient("https://{vault-name}.vault.azure.net", credential)
secret = client.get_secret("secret-name")
(I work on the Azure SDK in Python)
When Key Vault returns a 401 response, it includes a www-authenticate header containing authority and resource. You must use both to get a valid bearer token. Then you can redo your request with that token, and if you use the same token on subsequent requests against the same vault, it shouldn't return a 401 until the token expires.
You can know the authority and resource in advance, but it's generally more robust to prepare your code to always handle the 401, specially if you use multiple vaults.
Be sure to only trust on a www-authenticate header of a valid SSL connection, otherwise you might be a victim of spoofing!
I have written a simple python wrapper for the REST APIs for Azure Key Vault.
You can check out out here
AzureKeyVaultPythonSDK
Crust of the logic is here
class AzureKeyVaultManager(object):
section_name="KeyVaultSection"
# Constructor
def __init__(self, fileName="private.properties"):
prop_file=os.path.dirname(os.path.realpath(sys.argv[0])) + "/" + fileName
config = ConfigParser.RawConfigParser()
config.read(prop_file)
self.client_id=config.get(self.section_name,'client.id')
self.client_secret=config.get(self.section_name,'client.secret')
self.tenant_id=config.get(self.section_name,'tenant.id')
self.resource=config.get(self.section_name,'resource')
self.key_vault=config.get(self.section_name,'key.vault')
# Authenticate
def initialize(self):
if self.client_id and self.client_secret and self.tenant_id and self.resource and self.key_vault:
print "Got all the properties from file "
token_url="https://login.windows.net/{0}/oauth2/token".format(self.tenant_id)
payload = {'client_id':self.client_id, 'client_secret':self.client_secret, 'resource':self.resource, 'grant_type':'client_credentials'}
response=requests.post(token_url, data=payload).json()
self.access_token=response['access_token']
else:
raise ValueError("Couldn't get the key vault properties from properties file")
# Get secret from a specific keyvault
def getSecretFromKeyVault(self, secretName, keyVault=None):
if keyVault is None:
keyVault=self.key_vault
endpoint = 'https://{0}.vault.azure.net/secrets/{1}?api-version=2015-06-01'.format(keyVault, secretName)
headers = {"Authorization": 'Bearer ' + self.access_token}
response = requests.get(endpoint,headers=headers).json()
if 'value' in response:
return response['value']
else:
raise ValueError("Value not found in response")

Categories

Resources