Pubsub signs the JWT token for push with a wrong key - google-cloud-platform

I'm using the following stack for my backend:
Cloud Endpoints
Cloud Run Gateway with ESPv2 => check API keys and validate JWTs
Cloud Run gRPC server implementation
When a change of interest happen I publish a message on pubsub
Now, attached to that pubsub topic I have a push subscription that triggers a Cloud Run method with a service account and a specified audience.
Setup:
Now I need to configure the Cloud Run Gateway to validate the JWT that pubsub generates for the specified service account. This is done in the api_config.yaml as per documentation here: https://cloud.google.com/pubsub/docs/push#jwt_format
The issuer is https://accounts.google.com and the audience in the one I specified in the pubsub subscription.
authentication:
providers:
- id: gcloud
jwks_uri: https://www.googleapis.com/robot/v1/metadata/x509/875517523825-compute#developer.gserviceaccount.com
issuer: https://accounts.google.com
audiences: project.clounrun.admin
rules:
- selector: project.v2.Events.*
requirements:
- provider_id: gcloud
The jwks for this service account can be found here:
https://www.googleapis.com/robot/v1/metadata/x509/SERVICE-ACCOUNT-ID
In my case:
https://www.googleapis.com/robot/v1/metadata/x509/875517523825-compute#developer.gserviceaccount.com
https://cloud.google.com/endpoints/docs/grpc/service-account-authentication#set_up_authentication_in_the_grpc_api_configuration
I've activated debug logs for the ESPv2 and this is what I get when it tries to validate the JWT:
13:07:37.027 request headers complete (end_stream=false):\n\':authority\', \'project-gateway-v2-dev-cuvfttrlpq-de.a.run.app\'\n\':path\', \'/v2/sessions:event\'\n\':method\', \'POST\'\n\'content-type\', \'application/json\'\n\'authorization\', \'Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImQwNWVmMjBjNDUxOTFlZmY2NGIyNWQzODBkNDZmZGU1NWFjMjI5ZDEiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJ5aXRub3cuY2xvdW5ydW4uYWRtaW4iLCJhenAiOiIxMDk0NDIwMDAxNjU1ODQ3Nzc4MDciLCJlbWFpbCI6Ijg3NTUxNzUyMzgyNS1jb21wdXRlQGRldmVsb3Blci5nc2VydmljZWFjY291bnQuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MTYwMzgwNzAwMSwiaWF0IjoxNjAzODAzNDAxLCJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJzdWIiOiIxMDk0NDIwMDAxNjU1ODQ3Nzc4MDcifQ.PwLvaBz-_dM3_5VjgzVlaueoRhacUE39FdFuVSfQI2w4V3OD79tIA6t_0cuvE1-kIpNrfDda5RSavVcs4SV4Y5P8AvW5jDtHdCELP3yb8HzVT9nCJlLac-v5ZKuv06syBN9F2Ve7VtZHHZOE2VS4B7uw0Q__1rIVzIllYWBYHkYGUBP2mZ3VhRw9VXARMr-EICanXfETe_MMfoKsX4202L_O4LffPdv16pA5hYtwzKi67gFYuubKI1XNkVQVatQieYQrhkz5jMyNyhVKy8ZY5a2UXagQL7erdsm-uPJo6ujoq0Yxtl8iKMdRv4XfrQSLyFZHCYdO6n2LHJle_FQzCQ\'\n\'content-length\', \'335\'\n\'accept\', \'application/json\'\n\'from\', \'noreply#google.com\'\n\'user-agent\', \'APIs-Google; (+https://developers.google.com/webmasters/APIs-Google.html)\'\n\'x-cloud-trace-context\', \'c3027b54c6aad09d85ce75f6dcaf07d5/8778433264575104852\'\n\'x-forwarded-for\', \'66.249.82.169\'\n\'x-forwarded-proto\', \'https\'\n\'forwarded\', \'for=\"66.249.82.169\";proto=https\'\n\'accept-encoding\', \'gzip,deflate,br\'
13:07:37.027 Called Filter : setDecoderFilterCallbacks
13:07:37.027 matched operation: project.v2.Events.OnSessionEvent
13:07:37.027 Called Filter : decodeHeaders
13:07:37.027 use filter state value project.v2.Events.OnSessionEvent to find verifier.
13:07:37.028 extract authorizationBearer
13:07:37.028 extract x-goog-iap-jwt-assertion
13:07:37.028 gcloud: JWT authentication starts (allow_failed=false), tokens size=1
13:07:37.028 gcloud: startVerify: tokens size 1
13:07:37.028 gcloud: Verifying JWT token of issuer https://accounts.google.com
13:07:37.028 gcloud: JWT token verification completed with: Jwks doesn\'t have key to match kid or alg from Jwt
13:07:37.028 Called Filter : check complete Jwks doesn\'t have key to match kid or alg from Jwt
13:07:37.028 Sending local reply with details jwt_authn_access_denied
As you can see the jwks doesn't contain the JWT kid. And this is true.
The header of this JWT is this:
{
"alg": "RS256",
"kid": "d05ef20c45191eff64b25d380d46fde55ac229d1",
"typ": "JWT"
}
and the payload is this:
{
"aud": "project.clounrun.admin",
"azp": "109442000165584777807",
"email": "875517523825-compute#developer.gserviceaccount.com",
"email_verified": true,
"exp": 1603807001,
"iat": 1603803401,
"iss": "https://accounts.google.com",
"sub": "109442000165584777807"
}
This indicates that pubsub does indeed use that service account but it looks like that specific kid is missing from the jwks response.
{
"4366ae10d4a79728de14f6f89a628b4fe640140f": "-----BEGIN CERTIFICATE-----\nMIIC+jCCAeKgAwIBAgIIapShdpj8y6QwDQYJKoZIhvcNAQEFBQAwIDEeMBwGA1UE\nAxMVMTA5NDQyMDAwMTY1NTg0Nzc3ODA3MB4XDTIwMDQzMDIyNDE0N1oXDTMwMDQy\nODIyNDE0N1owIDEeMBwGA1UEAxMVMTA5NDQyMDAwMTY1NTg0Nzc3ODA3MIIBIjAN\nBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArvpvYFbpJiXk4cEOo2xXzIFjLZEq\nIe1753Oe00IXmq5u/Glf6H0TdShqSn/mHd283UOeDGyjcz/AZO3iKyGv+GndSfiq\ny9TbXfeinCUoVtLUU500P32Ciej/t8Hf4UZYl6XlBVSMZK5ZVCqdWHs9vfPH8k6w\nSJm456BwjL3xty5AjuBooTSHec92SGe2DYSpMJL9NHGELdSnNRoxEaXpEUBV93vr\nTKfbBKa/1WaumVvIn54rAIMkaFq7dJRFr98U2yfHFvUhMtqAwX7HkdvgM74sjfV0\nduVfVz3T/m/oG/7lCllpI4LhVHpxxuNhimFo5quXjShJLPNEjFbESndMaQIDAQAB\nozgwNjAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIHgDAWBgNVHSUBAf8EDDAK\nBggrBgEFBQcDAjANBgkqhkiG9w0BAQUFAAOCAQEAZqBZCbFswKBxa4S+fvC6qb5c\nF/29sxM4WijC4e+/Ti+e6tcRrf3LyOF0jIpHbHWcwjTiYXX+D+UUmMrgh0ZVLCOR\nTDj29JSIUEhzYsGmBzBBcPLfMO2zl6c0aMdUkO+3vXQoTHUNjcs8UoN6BlPo3oIG\n2BjOXhEmuuUA3BQVDsMIM5g4G5r28WaprV7GaTa4fsyCh9oRquTtqL34CZLxiLXv\ncqK+oRMFU4tsLKvZjcfTeKp3fbXDpo7R1R7/+SyxAJTQOe1uPeAc5qhlVK6Ky/Zy\nTv3SUzAifJ3BDz1eNKYTqQNWiXi3QnX6qwebHgHKcJ01qnbCqMqbv6HicpULdQ==\n-----END CERTIFICATE-----\n",
"e0b3368c1646eb88d93b9d0b2d65e2d6fbae27b7": "-----BEGIN CERTIFICATE-----\nMIIDNDCCAhygAwIBAgIIQ2si/YIYDg0wDQYJKoZIhvcNAQEFBQAwPTE7MDkGA1UE\nAxMyODc1NTE3NTIzODI1LWNvbXB1dGUuZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3Vu\ndC5jb20wHhcNMjAwOTMwMTM1NjM5WhcNMjIxMDEzMjAxNTU3WjA9MTswOQYDVQQD\nEzI4NzU1MTc1MjM4MjUtY29tcHV0ZS5kZXZlbG9wZXIuZ3NlcnZpY2VhY2NvdW50\nLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALejPchRlDY8EE5D\nvD1CuPyGsbCoXHDnJpaA4P23CLNUmooBydxxzfV611vTUTBCFjq5Pcg3fpGusoMt\nyF9TqQVq4bGZxrXv+yxVs24uFdHAB7mY9JUE8GKN5i7IMP3egDcns4LmNWsB0iKN\niK1gK5q7gZIISjo3igLrup1G6wM02qym2VS6raKn/12WY+pa/PiZrO79eAkYPyqr\n73AvLdcLsik9U7lNDfxiev3/IE+tP0B68Uo5Ff+Wai+RDnNmDX3Fy50hv8vfniRe\nB/b57Kn6SMtMz4IMD4BeNQpOXdZe780cubuwS1oPLcRLHCXTqEzsGVIMLoMxrxF9\nhRK743kCAwEAAaM4MDYwDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYD\nVR0lAQH/BAwwCgYIKwYBBQUHAwIwDQYJKoZIhvcNAQEFBQADggEBAKF+dNSK+GFN\ndKFBs5PZufLUMlyaIXYysDnZgCk9oWMBiBpa6WDg00SllAz5z9koVYFr3ySQq6mK\nddkJLUXVt+NkR6Jh4k4OYshBV4v6q/BOXEjpP6/+jNV9qD5vVuq+w1eReouCl8eO\ng/rfUAivAPpU1srXSmhs6Uw2E4jDf8ArJWLsPfHjoqWLJEICGBG0i1nlJBRpGNWj\nyBfN1cEp64pYynpnVY9kuIEKdBCue8QEXVhsKURHGcOfCWP9+vVerhlyDatL1tQW\n5RAjvzS8NGLe8QnMdy+63TwP4qKGkWSEPTxP0fpQpxuLbqKHHsSeA7WL6nS6zSwx\nRXHDLiUfU0A=\n-----END CERTIFICATE-----\n",
"c0191d2d00f89eb1905886a04335a79a124885e1": "-----BEGIN CERTIFICATE-----\nMIIDNDCCAhygAwIBAgIIaTwbvrNhTkYwDQYJKoZIhvcNAQEFBQAwPTE7MDkGA1UE\nAxMyODc1NTE3NTIzODI1LWNvbXB1dGUuZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3Vu\ndC5jb20wHhcNMjAxMDI4MDA0NDEwWhcNMjAxMTEzMTI1OTEwWjA9MTswOQYDVQQD\nEzI4NzU1MTc1MjM4MjUtY29tcHV0ZS5kZXZlbG9wZXIuZ3NlcnZpY2VhY2NvdW50\nLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKJZDpcJAHyot7z9\n0EAOOEC6+dwlkxduThH5FQXlaCUoHRAoCtTHrxeITSsZg55cXGXKzTTf1Nv+UE9E\nTY5nhU70nEK77B5FbxwzX9Q/OkOhP3NQnl0U0O6nedfCJvOtCMdopHnrRa+ZIWhG\nPoW2RKQTv7gr4bGFJnQshcYDtrahH6Xv/RfyyTnI9AUzCX6eVO3g7odLSdcv+qnV\niqsZLCqz2lflKky+Rti/1f8LGXySTs+r7bdvqCdpbIm6f79WqQ720hmx+4JaIuS3\nYmYztK5J6JwsDLqULyyMvZs9vVA6cK1+580Vb76pkF9MHqTZdZ2gPP1qoKCO0ezI\nK3lYfGECAwEAAaM4MDYwDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYD\nVR0lAQH/BAwwCgYIKwYBBQUHAwIwDQYJKoZIhvcNAQEFBQADggEBAHB+6GiylFKU\nq9nyzrvlTRe0tph7kT6D8WhlnxGDgnDOn9xAB360xh+KPczq7+8DJczSb5145No/\nefrCUpvgkraVe/eTsu8EAjDjk/XPOeV4117EU+PRVV5nitYiVGJ0Z+3V3kHCnqLT\ntTxQlKDRpJ02GjhQRkW03fhLEFr7eu79fiwfjfaFSoAAIrNxBi3mtrhcidu43zD1\n/VXAReH26S129UoYz7fPlsnULX/oUyZPidaKIZ2Fl5N2QTgXnd1PnU3HgesU4HWs\nYJeiq6Z1pFxTw9192VD3MgDSDVQAeHXs+wTQsUHftMcNl7nPWl9YfiPjzD4sNWL3\n8whVkhIN8r4=\n-----END CERTIFICATE-----\n"
}
We have:
4366ae10d4a79728de14f6f89a628b4fe640140f
e0b3368c1646eb88d93b9d0b2d65e2d6fbae27b7
c0191d2d00f89eb1905886a04335a79a124885e1
But no
d05ef20c45191eff64b25d380d46fde55ac229d1
If pubsub is using a different key that where can I find the jwks?
UPDATE:
I verified all my services accounts JWKS links and none of them contain that kid. So this is very strange.

You are using a Google signing key (Google is signing for you).
This means you need to lookup the KID from Google's published public keys.
You will find the public certificate kid here: https://www.googleapis.com/oauth2/v1/certs
For some added context, each service account has a sort of "hidden` key. Google manages and controls this key. When you request an Identity Token from Google, the JWT is signed using this special key. That is why you need to look this up in Google's public certificate list instead of your service account's certificate list.

Related

How to import Google Cloud credentials into a Firebase Cloud Function?

I'm trying to set up Google Cloud Translation in a Firebase Cloud Function. I'm using the demo code provided by Google Cloud Translation:
// Instantiates a client
const translationClient = new TranslationServiceClient();
const projectId = 'languagetwo-cd94d';
const location = 'global';
const text = 'Hello, world!';
async function translateText() {
// Construct request
const request = {
parent: `projects/${projectId}/locations/${location}`,
contents: [text],
mimeType: 'text/plain', // mime types: text/plain, text/html
sourceLanguageCode: 'en',
targetLanguageCode: 'es',
};
// Run request
const [response] = await translationClient.translateText(request);
for (const translation of response.translations) {
console.log(`Translation: ${translation.translatedText}`);
}
}
translateText();
This demo tutorial makes a second file called key.json:
{
"type": "service_account",
"project_id": "myAwesomeApp",
"private_key_id": "1234567890",
"private_key": "-----BEGIN PRIVATE KEY-----\noPeeking=\n-----END PRIVATE KEY-----\n",
"client_email": "translation-quickstart#myAwesomeApp.iam.gserviceaccount.com",
"client_id": "1234567890",
"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/translation-quickstart%40myAwesomeApp.iam.gserviceaccount.com"
}
I uploaded my credentials from the CLI:
gcloud auth login
gcloud iam service-accounts create translation-quickstart --project myAwesomeApp
gcloud projects add-iam-policy-binding myAwesomeApp
gcloud iam service-accounts keys \
create key.json --iam-account \
translation-quickstart#myAwesomeApp.iam.gserviceaccount.com
export GOOGLE_APPLICATION_CREDENTIALS=key.json
I then entered node app.js at the CLI and it runs perfectly. ¡Hola Mundo!
How do I import my credentials into a Firebase Cloud Function? I tried this:
exports.ENtranslateES = functions.firestore.document('Users/{userID}/English/Translation_Request').onUpdate((change) => { // triggers when browser writes a request word to the database
// Google Cloud
const { TranslationServiceClient } = require('#google-cloud/translate');
// Instantiates a client
const translationClient = new TranslationServiceClient();
const projectId = 'languagetwo-cd94d';
const location = 'global';
const text = 'Hello, world!';
async function translateText() {
// Construct request
const request = {
parent: `projects/${projectId}/locations/${location}`,
contents: [text],
mimeType: 'text/plain', // mime types: text/plain, text/html
sourceLanguageCode: 'en',
targetLanguageCode: 'es',
};
// Run request
const [response] = await translationClient.translateText(request);
for (const translation of response.translations) {
console.log(`Translation: ${translation.translatedText}`);
}
}
return translateText()
});
I added only a return at the bottom because Firebase Cloud Functions require that something has to be returned.
The result is that the function triggers and translateText() fires. Then I get an error message:
Error: 7 PERMISSION_DENIED: Cloud IAM permission
That looks like the credentials weren't imported. How do I import the key.json credentials into the Firebase Cloud Function?
Normally, you do not import a service account into a Google compute service such as Cloud Functions. Those services have an attached service account. There are methods of securely storing a service account using services like Google Cloud Secret Manager. In your case there is a better solution.
The following line in your source code uses the Cloud Function attached service account, which defaults to the App Engine default service account PROJECT_ID#appspot.gserviceaccount.com.
const translationClient = new TranslationServiceClient();
Since you did not specify a credential when creating the translationClient, ADC (Application Default Credentials) searches for credentials. In your example, the search found valid credentials from the Cloud Function service account.
The solution is to add the required role to that service account.
If you want to use the service account that you created, then attach the service account identity (email address) to the Cloud Function link.
Access control with IAM
I got Google Cloud Translate to work in Postman. This is a step towards an answer, not an answer. (Google has its own version of Postman, called Google Cloud API, but it doesn't work with Translate,)
I followed this blog post to set up Google Cloud API in Postman. I started at my Google Cloud Console. I selected my project. I clicked APIs & Services, then Credentials, then + Create Credentials, then OAuth client ID. Under Application type I selected Web application. I named the client ID Postman. Lastly I added an Authorized redirect URI: https://console.cloud.google.com/. Now when I click on my Postman API in my Google Cloud Console I see a Client ID and a Client secret.
In Postman, I changed GET to POST and entered the URL from the Google Cloud Translation page:
https://cloud.google.com/translate/docs/reference/rest/v2/translate
Under the Authorization tab I put in:
Token Name: GCP Token
Grant Type: Authorization Code
Callback URL: https://www.getpostman.com/oauth2/callback
Auth URL: https://accounts.google.com/o/oauth2/auth
Access Token URL: https://accounts.google.com/o/oauth2/token
Client ID: 1234567890abc.apps.googleusercontent.com
Client Secret: ABCDE-NoPeeking-1234567890
Scope: https://www.googleapis.com/auth/cloud-platform
State:
Client Authorization: Send as Basic Auth header
I then clicked Get New Access Token and an access token appeared at the top of all this. The token is good for one hour.
Under Params I entered:
q: rain
target: es
source: en
Google Cloud Translate returned lluvia.
Now I know what the auth properties and query parameters are. I don't know how to put them into a Firebase Cloud Function.

Why can't I get more attributes from google provider via cognito UserInfo endpoint?

I have configured google provider via cognito user pool and I am able to login through google and get user information. And I have added many attributes on the attribute mapping page as shown in below screenshot.
The endpoint I am using to get user info is https://docs.aws.amazon.com/cognito/latest/developerguide/userinfo-endpoint.html.
The response data for user info is always:
data: {
identities: '[{"userId":"xxxx","providerName":"Google","providerType":"Google","issuer":null,"primary":true,"dateCreated":1587772412295}]',
email_verified: 'true',
email: 'xxxx#gmail.com',
username: 'Google_1xxxx'
}
Regardless how I update the attribute mappings, I always see above response. Why can't I get additional attributes like picture, given_name, birthday etc. Do I need to set any permission on google side?
In app client setting, I have below configuration:
In authorized scope, I have set: email openid profile

Sign google cloud storage blob using access token

Goal: Generate Signed-URL Using OAuth2.0 Access Token
The examples and source codes I find for signing Google Cloud Storage blobs all require service account credentials file (the private key to be specific). For instance:
https://cloud.google.com/storage/docs/access-control/signing-urls-with-helpers#storage-signed-url-get-object-python
However, since I follow the authorization flow discussed here, I only have OAuth2.0 access token (and I do NOT have the credentials file and private key of a service account with access to GCS bucket/object). Hence, I was wondering how I can sign blobs using OAuth2.0 access tokens.
The Code Used:
I use the following to sign blob:
# First, get access token:
service_account = "<email address of a service account>"
access_token = build(
serviceName='iamcredentials',
version='v1',
http=http
).projects().serviceAccounts().generateAccessToken(
name="projects/{}/serviceAccounts/{}".format(
"-",
service_account),
body=body
).execute()["accessToken"]
credentials = AccessTokenCredentials(access_token, "MyAgent/1.0", None)
# Second, use the access token to sign a blob
url = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/{}:signBlob".format(service_account)
encoded = base64.b64encode(blob)
sign_blob_request_body = {"payload": encoded}
response = requests.post(url,
data=json.dumps(sign_blob_request_body),
headers={
'Content-Type': 'application/json',
'Authorization': 'Bearer {}'.format(credentials.access_token)})
signature = response.json()["signedBlob"]
# Third, use the signature to create signed URL:
encoded_signature = base64.b64encode(signature)
signed_url = "https://storage.googleapis.com/<BUCKET>/<OBJECT>?" \
"GoogleAccessId={}&" \
"Expires={}&" \
"Signature={}".format(service_account,
expiration,
encoded_signature)
The Error Message Received:
<Error>
<Code>SignatureDoesNotMatch</Code>
<Message>
The request signature we calculated does not match the signature you provided. Check your Google secret key and signing method.
</Message>
<StringToSign>GET 1561832204 /<BUCKET>/<OBJECT></StringToSign>
</Error>
In case you do NOT want to use API secret key, follow procedure described in this sample that is using iamcredentials.signBlob() API signing URL 'remotely' for a service account with no need to distribute API secret key.
Signature string (that has to be signed) has this format:
signature_string = ('{verb}\n'
'{content_md5}\n'
'{content_type}\n'
'{expiration}\n'
'{resource}')

Cloud Endpoints Authentication

I am using App Engine Standard with the Python 2 runtime and Endpoints Frameworks.
When making a request, the app just returns "Successful" if the request was completed. I am trying to implement authentication so unauthenticated users are not able to complete the request. I've done the following:
Modified my main.py decorator to include issuers and audience:
issuers={'serviceAccount': endpoints.Issuer('[MYSERVICEACCOUNT]', 'https://www.googleapis.com/robot/v1/metadata/x509/[MYSERVICEACCOUNT]')},
audiences={'serviceAccount': ['[MYSERVICENAME]-dot-[MYPROJECT].appspot.com']}
Modifed my main.py method to check for a valid user:
user = endpoints.get_current_user()
if user is None:
raise endpoints.UnauthorizedException('You must authenticate first.')
Regenerated and redeployed my openAPI document. It now has security and securityDefinitions sections.
Updated my app.yaml to reference that Endpoints version.
Redeployed my app
To make an authorized request to my app, I have done the following:
I gave the service account the Service Consumer role on my Endpoints service.
Generate a signed jwt using the generate_jwt function from Google's documentation. I am passing in credentials using the service account's json key file.
payload = json.dumps({
"iat": now,
"exp": now + 3600,
"iss": [MYSERVICEACCOUNT],
"sub": [MYSERVICEACCOUNT],
"aud": [MYSERVICENAME]-dot-[MYPROJECT].appspot.com
})
Make the request using make_jwt_request function from Google's documentation.
headers = {
'Authorization': 'Bearer {}'.format(signed_jwt),
'content-type': 'application/json'}
I am getting 401 Client Error: Unauthorized for url error. Am I missing something?
Your audiences don't match; in your code, you are requiring an audience of [MYSERVICEACCOUNT], but when generating the JWT, your audience is [MYSERVICENAME]-dot-[MYPROJECT].appspot.com. These need to match.
There are few details, which might be worth checking:
The list of allowed audiences should contain the value of aud claim of a client-generated JWT token. This is what Rose has pointed out.
All of the JWT claims presented in sample client documentation are present. Your code is missing the email claim in the JWT payload dictionary.
The method you're accessing requires no specific OAuth scopes. The scopes are set as the scopes field of #endpoints.method decorator.
After opening a support ticket with Google, it turns out Google's documentation was incorrect. The main.py function needs to check for an authenticated user in the below manner:
providers=[{
'issuer': '[YOUR-SERVICE-ACCOUNT]',
'cert_uri': 'https://www.googleapis.com/service_accounts/v1/metadata/raw/[YOUR-SERVICE-ACCOUNT]',
}]
audiences = ['[YOUR-SERVICE-NAME]-dot-[YOUR-PROJECT-NAME].appspot.com']
user = endpoints.get_verified_jwt(providers, audiences, request=request)
if not user:
raise endpoints.UnauthorizedException
After making that change, I got the following error when trying to make an authenticated request:
Encountered unexpected error from ProtoRPC method implementation: AttributeError ('unicode' object has no attribute 'get')
This was caused by how I was generating the payload with json.dumps(). I generated without json.dumps() like below:
payload = {
"iat": now,
"exp": now + 3600,
"iss": [MYSERVICEACCOUNT],
"sub": [MYSERVICEACCOUNT],
"aud": [MYSERVICENAME]-dot-[MYPROJECT].appspot.com
}
These two changes fixed my issue.

AWS IOT - Credential should be scoped to correct service

I am trying to access a simple AWS IOT REST service but I have not been able to do so successfully yet. Here is what I did.
I created an iam user in my aws and downloaded the access key and secret key
Logged into AWS IOT with that user and created a "thing"
From the thing's property I found the REST URL for the shadow
Used Postman with the new "aws signature" feature and provided it with the access key, secret key, region (us-east-1) and service name (iot)
Tried to "GET" the endpoint and this is what I got -
{
"message": "Credential should be scoped to correct service. ",
"traceId": "be056198-d202-455f-ab85-805defd1260d"
}
I thought there is something wrong with postman so I tried using aws-sdk-sample example of connecting to S3 and changed it to connect to the IOT URL.
Here is my program snippet (Java)
String awsAccessKey = "fasfasfasdfsdafs";
String awsSecretKey = "asdfasdfasfasdfasdfasdf/asdfsdafsd/fsdafasdf";
URL endpointUrl = null;
String regionName = "us-east-1";
try {
endpointUrl = new URL("https://dasfsdfasdf.iot.us-east-1.amazonaws.com/things/SOMETHING/shadow");
}catch (Exception e){
e.printStackTrace();
}
Map<String, String> headers = new HashMap<String, String>();
headers.put("x-amz-content-sha256", AWSSignerBase.EMPTY_BODY_SHA256);
AWSSignerForAuthorizationHeader signer = new AWSSignerForAuthorizationHeader(
endpointUrl, "GET", "iot", regionName);
String authorization = signer.computeSignature(headers,
null, // no query parameters
AWSSignerBase.EMPTY_BODY_SHA256,
awsAccessKey,
awsSecretKey);
// place the computed signature into a formatted 'Authorization' header
// and call S3
headers.put("Authorization", authorization);
String response = HttpUtils.invokeHttpRequest(endpointUrl, "GET", headers, null);
System.out.println("--------- Response content ---------");
System.out.println(response);
System.out.println("------------------------------------");
This gives me the same error -
--------- Request headers ---------
x-amz-content-sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
Authorization: AWS4-HMAC-SHA256 Credential=fasfasfasdfsdafs/20160212/us-east-1/iot/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=3b2194051a8dde8fe617219c78c2a79b77ec92338028e9e917a74e8307f4e914
x-amz-date: 20160212T182525Z
Host: dasfsdfasdf.iot.us-east-1.amazonaws.com
--------- Response content ---------
{"message":"Credential should be scoped to correct service. ","traceId":"cd3e0d96-82fa-4da5-a4e1-b736af6c5e34"}
------------------------------------
Can someone tell me what I am doing wrong please? AWS documentation does not have much information on this error. Please help
Sign your request with iotdata instead if iot
example:
AWSSignerForAuthorizationHeader signer = new AWSSignerForAuthorizationHeader(
endpointUrl, "GET", "iotdata", regionName);
In your 4th step, don't fill anything for Service Name. Postman will default the value with execute-api.
Hope this works!
Its basically due to Service name is not given correctly you can use service Name = 'iotdata' instead of iot.
If you user Key management then Service Name would be kms.
For EC2 Service Name would be ec2 etc.
Use the AWS IoT SDK for Node.js instead. Download the IoT Console generated private key and client cert as well as the CA Root cert from here. Start with the scripts in the examples directory.