How to validate API Key in AWS Lambda function - amazon-web-services

I have read quite a few articles like this one and it looks like currently in AWS API Gateway you cannot send API Key in query string.
We also have few legacy clients that will need passing api-key in query string.
So i thought of two options
1>Create lambda function as Integration Type and validate API in key inside function handler. But i am not able to figure out how to validate it against keys in aws. Something like
public async Task<JObject> FunctionHandler(JObject request, ILambdaContext context)
{
// i know how to get apikey from queryStringParameters here
// but how do i validate it against api keys in aws
}
2> Create new custom authorizer, but again not sure how do i do it.
Which would be prefered option. I am using .NET core. Are there any nuget packages already available?
Please note querystring support is required and not debatable even if it is not recommended for security reason

You can pass security key in query param use header and pass key
curl -X PUT \
https://XXXX.XXXXX-api.ca-central-1.amazonaws.com/PROD/XXX-microservice \
-H 'Content-Type: application/json' \
-H 'x-api-key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' \
-d '{
"initData": "HI",
"name": "vaquar khan",
"likes": "Java"
}'
Security key validation taken care by API getaway so no lambda
authorizer required
If you are passing in query string
URL:
https://XXXX.XXXXX-api.ca-central-1.amazonaws.com/PROD/XXX-microservice ?x-api-key=XXXXXXXXXXXXXXXX &uid=5
Python 2.7
from __future__ import print_function
import boto3
import json
print('Loading function')
def lambda_handler(event, context):
print(event['params']['querystring']['x-api-key'])
print(event['params']['querystring']['uid'])

Related

Authenticating API Call to Google ML Engine with an API Key

I have a saved model in Google AI Platform that works when I test the predictions in the AI Platform UI.
However, when I try and access the API via REST, I keep getting a response with a 401 status. I would like to know how to do this successfully.
My api URL looks like the following:
'https://ml.googleapis.com/v1/projects/ml-project-name/models/my-model-names/versions/v2:predict
I would like to be able to access this endpoint in an external app that's residing on any platform to generate predictions with it.
Google Cloud recommends service account authorization, however, all of the directions for it require setting environment variables so the app can automatically authenticate you. I'd prefer to supply them directly in the request to make things more portable and congruent with how it's done elsewhere at work.
So instead I tried getting an API key.
According to this page: https://cloud.google.com/docs/authentication/api-keys you can authenticate a request in the following way:
POST https://language.googleapis.com/v1/documents:analyzeEntities?key=API_KEY
However, when I run the following code, my request status is 401:
import requests
api_key = my_sample_api_key
url = f'https://ml.googleapis.com/v1/projects/project-name/models/model-name/versions/v2:predict?key={api_key}'
json = {"instances": [ {"input_1": ["Please predict this text"]}]}
res = request.post(url, json=json)
Any help would be appreciated, thank you.
Auto ML does not support the use of API keys when sending requests. I suggest to use the auth token on your request or use the available client libraries to send predictions.
Here is a code snippet using its python client library for sending prediction request:
# Create the AI Platform service object.
# To authenticate set the environment variable
# GOOGLE_APPLICATION_CREDENTIALS=<path_to_service_account_file>
service = googleapiclient.discovery.build('ml', 'v1')
def predict_json(project, model, instances, version=None):
"""Send json data to a deployed model for prediction.
Args:
project (str): project where the AI Platform Model is deployed.
model (str): model name.
instances ([Mapping[str: Any]]): Keys should be the names of Tensors
your deployed model expects as inputs. Values should be datatypes
convertible to Tensors, or (potentially nested) lists of datatypes
convertible to tensors.
version: str, version of the model to target.
Returns:
Mapping[str: any]: dictionary of prediction results defined by the
model.
"""
name = 'projects/{}/models/{}'.format(project, model)
if version is not None:
name += '/versions/{}'.format(version)
response = service.projects().predict(
name=name,
body={'instances': instances}
).execute()
if 'error' in response:
raise RuntimeError(response['error'])
return response['predictions']
Here is a sample of sending a POST request using curl with the auth token:
curl -X POST \
-H "Authorization: Bearer "$(gcloud auth application-default print-access-token) \
-H "Content-Type: application/json; charset=utf-8" \
-d #request.json \
https://ml.googleapis.com/v1/projects/your-project/models/you-model-name/versions/your-version-name:predict

how to call sagemaker endpoint from another server

I try to call aws sagemaker endpoint. but I don't use lambda function. only, I want to acess endpoint ARN, URL.
if impossible method, I want to know lambda function
my endpoint based keras model. I don't know json.dumps
import json
import boto3
client = boto3.client('runtime.sagemaker')
import numpy as np
test = np.zeros((1, 1, 4325))
test[0][0][1] = 1
data = {"instances": test.tolist()}
response = client.invoke_endpoint(EndpointName=endpoint_name,
Body=json.dumps(data))
response_body = response['Body']
print(response_body.read())
Your question It's a little bit confusing. I understand you want to call a deployed endpoint outside Sagemaker and You can do this as shown here call-sagemaker-endpoint-using-lambda-function
You can also call it using awscurl like that:
awscurl --profile aws-profile -XPOST -H 'Content-Type: application/json' --data #input_file_name https://runtime.sagemaker.us-east-1.amazonaws.com/endpoints/your-endpoint-name/invocations --service sagemaker
You may want to also try with enviornment variable
export AWS_PROFILE=aws-profile; before the call to be sure you don't have something else set.

Presigned URL for DynamoDB put_item

There are a few examples for the way to pre-sign the URL of an S3 request, but I couldn't find any working example to pre-sign other services in AWS.
I'm trying to write an item to DynamoDB using the Python SDK botos. The SDK included the option to generate the pre-signed URL here. I'm trying to make it work and I'm getting a URL, but the URL is responding with 404 and the Item is not appearing in the DynamoDB table.
import json
ddb_client = boto3.client('dynamodb')
response = ddb_client.put_item(
TableName='mutes',
Item={
'email': {'S':'g#g.c'},
'until': {'N': '123'}
}
)
print("PutItem succeeded:")
print(json.dumps(response, indent=4))
This code is working directly. But when I try to presign it:
ddb_client = boto3.client('dynamodb')
params = {
'TableName':'mutes',
'Item':
{
'email': {'S':'g#g.c'},
'until' : {'N': '1234'}
}
}
response = ddb_client.generate_presigned_url('put_item', Params = params)
and check the URL:
import requests
r = requests.post(response)
r
I'm getting: Response [404]
Any hint on how to get it working? I checked the IAM permissions, and they are giving full access to DynamoDB.
Please note that you can sign a request to DynamoDB using python, as you can see here: https://docs.aws.amazon.com/general/latest/gr/sigv4-signed-request-examples.html#sig-v4-examples-post . But for some reasons, the implementation in the boto3 library doesn't do that. Using the boto3 library is much easier than the code above, as I don't need to provide the credentials for the function.
You send an empty post request. You should add the data to the request:
import requests
r = requests.post(response, data = params)
I think you are having this issue, that's why you are recieving a 404.
They recommend using Cognito for authentication instead of IAM for this cases.

How use segmented URL in AWS API Gateway?

I have a Lambda Function that it is accessible by an API Gateway. I can handle all POST and GET submitted requests to API endpoint (https://XXXXXXX.execute-api.us-east-1.amazonaws.com/default/myapi) inside my Lambda, but I need to use some segments at end of my URL when I am using PUT requests.
My Python code to call the API is here and it is working correctly:
import requests
import json
url = 'https://XXXXXXX.execute-api.us-east-1.amazonaws.com/default/myapi'
token = "my token"
data = {
"first_name": "Reza",
"birthday": "1986-09-12"
}
headers = {"Content-Type" : "application/json", "x-api-key":"MY_API_KEY"}
response = requests.put(url, data=json.dumps(data), headers=headers)
print(response.text)
But if I add users segment to end of the URL like this:
url = 'https://XXXXXXX.execute-api.us-east-1.amazonaws.com/default/myapi/users'
it will show this error:
{"message":"Missing Authentication Token"}
I need to add some static segments like users to return the list of all users and some dynamic segments like users/USER_ID (when USER_ID is a dynamic number) to return the information for a special user.
can you please guide me how I can use segmented URL in my AWS API Gateway?
The term you are using segmented URL might have caused your confusion. It is called path parameters with AWS. There is more than one way to do it. ANY+ integration is the easiest to handle.
Integrate with ANY+ integration to your lambda and you are good to go. All the path parameters will be delivered to your lambda.
http://www.1strategy.com/blog/2017/06/06/how-to-use-amazon-api-gateway-proxy/
Additional path parameter documentation,
https://docs.aws.amazon.com/apigateway/latest/developerguide/integrating-api-with-aws-services-lambda.html#api-as-lambda-proxy-expose-get-method-with-path-parameters-to-call-lambda-function
Good luck.

How can I print the Canonical String which aws-requests-auth sends?

I want to have a lambda calling a Sagemaker instance in another region. If both are in the same region, everything works fine. If they are not, I get the following error:
The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.
The Canonical String for this request should have been
'POST
/endpoints/foo-endpoint/invocations
host:runtime.sagemaker.us-east-1.amazonaws.com
x-amz-date:20180406T082536Z
host;x-amz-date
1234567890foobarfoobarfoobarboofoobarfoobarfoobarfoobarfoobarfoo'
The String-to-Sign should have been
'AWS4-HMAC-SHA256
20180406T082536Z
20180406/us-east-1/sagemaker/aws4_request
987654321abcdeffoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarf'
I use aws-requests-auth (0.4.1) with boto3 (1.5.15 - updating to 1.7.1 didn't change anything, changelog) like this:
import requests
from aws_requests_auth.aws_auth import AWSRequestsAuth
auth = AWSRequestsAuth(aws_access_key=config['AWS']['ACCESS_KEY'],
aws_secret_access_key=(
config['AWS']['SECRET_ACCESS_KEY']),
aws_host=config['AWS']['HOST'],
aws_region=config['AWS']['REGION'],
aws_service=config['AWS']['SERVICE'])
payload = {'foo': 'bar'}
response = requests.post(post_url,
data=json.dumps(payload),
headers={'content-type': 'application/json'},
auth=auth)
printing auth only gives <aws_requests_auth.aws_auth.AWSRequestsAuth object at 0x7f9d00c98390>.
Is there a way to print the "Canonical String" mentioned in the error message?
(Any other ideas how to fix this are appreciated as well)
A work-around for the asked question:
req = requests.request('POST', 'http://httpbin.org/get')
req.body = b''
req.method = ''
print(auth.get_aws_request_headers(req,
aws_access_key=auth.aws_access_key,
aws_secret_access_key=auth.aws_secret_access_key,
aws_token=auth.aws_token))
The problem is not solved, though. And now I wonder what the first argument of auth.get_aws_request_headers is.