Greetings Kind Reader,
I'm trying to control access to a lambda function url via a cognito identity pool backed by a cognito user pool. The lambda function creates a report summarizing a dynamoDB database and sends an email to the requestor with the report's contents. I have created an IAM policy, UsageInvokeURL00, which has the following statement:
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "lambda:InvokeFunctionUrl",
"Resource": [
"arn:aws:lambda:us-east-1:<accountid>:function:UsageReport00"
],
"Effect": "Allow",
"Sid": "VisualEditor0"
}
]
}
To test the Lambda function URL I've created an IAM user, UsageUser00, with the UsageInvokeURL00 policy attached. To test invocation of the function URL I've used curl and it works -- the emailed report arrives as expected:
curl --verbose --aws-sigv4 "aws:amz:us-east-1:lambda" -X GET --user "<UsageUser00-access-key>":"<UsageUser00-secret-key>" "https://<function-url>.lambda-url.us-east-1.on.aws/?start=20251101&end=20251114&granularity=day&grouping=computer
Ultimately, I'd like to have Cognito Identity Pool grant access to the function URL and a browser interface to make requesting a report easier. To this end, I've created a Cognito User Pool with me as a user and an identity pool with the user pool as identity provider. I can login to the identity pool via the browser. The authenticated role for the identity pool is service-role/usage-idp-role which has two policies attached, Cognito-authenticated-<random> and UsageInvokeURL00. My understanding is that the Cognito-authenticated-<random> policy allows me to get temporary credentials:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"cognito-identity:GetCredentialsForIdentity"
],
"Resource": [
"*"
]
}
]
}
Everything appears to work fine until I attempt to call the function URL via a signed fetch when I get a CORS error. I create the signed fetch using the credentials returned from GetCredentialsForIdentityCommand using the identity pool's returned identityId.
I've currently set the CORS options on the function URL to be wide open -- Allow origin: *; Expose headers: *, Allow headers: authorization; Allow methods: *, Max age: -, Allow credentials: true. But when it comes time to request the report by invoking the function URL I receive a CORS error on the pre-Flight request due to the lack of a Access-Control-Allow-Origin response header.
Exploring why this was happening I modified my curl request to an OPTIONS request and added an origin header -- I always receive the expected Access-Control-Allow-Origin with my origin as the allowed origin. I've replicated the browsers full set of request headers in my curl command line and it still includes my origin in the Access-Control-Allow-Origin response header.
But when I use the accessKey and secretKey returned in the browser from the call to GetCredentialsForIdentity as the curl --user command-line option, the OPTIONS request fails with a 403 forbidden response with a message, "The security token included in the request is invalid." If I take the accessKey and secretKey associated with the user UsageUser00 and paste them into the createSignedFetcher's options parameter before calling createSignedFetcher, the signedFetch call fails because there's no Access-Control-Allow-Origin header in the response.
I did some searching to try to understand whether I should be able to use temporary credentials on the command line with curl -- everything said I should be able to, but the examples were including the session token in an x-amz-security-token header and I wasn't. I couldn't get the temporary credentials to work using plain curl, so, I downloaded awscurl, a python script that prepares the options for you, and managed to invoke my lambda function from the command line with temporary credentials. That's good news! So, it appears to me that I'm not preparing the signed fetcher properly in my browser code.
Here's the code where i prepare the signed fetcher using the temporary credentials received:
export async function getIdentityInfo(user) {
try {
id_token = user.id_token;
const getIdInput = {
AccountId: ${awsAccountId},
IdentityPoolId: ${cognitoIdentityPoolId},
Logins: {},
};
getIdInput.Logins[cognitoAuthority] = id_token;
const getIdCmd = new GetIdCommand(getIdInput);
let idResponse;
try {
idResponse = await cognitoClient.send(getIdCmd);
} catch (error) {
const { requestId, cfId, extendedRequestId } = error.$metadata;
console.log({ requestId, cfId, extendedRequestId });
throw error;
}
identityId = idResponse.IdentityId;
const getCredsInput = {
IdentityId: `${identityId}`,
Logins: {},
};
//CustomRoleArn: `${computerUsageInvokeRoleArn}`,
getCredsInput.Logins[cognitoAuthority] = id_token;
const getCredsCmd = new GetCredentialsForIdentityCommand(getCredsInput);
const credsResponse = await cognitoClient.send(getCredsCmd);
credentials = credsResponse.Credentials;
//document.cookie = `credentials=${JSON.stringify(credentials)}; HttpOnly; Secure`;
const options = {
service: "lambda",
region: `${awsRegionId}`,
credentials: {
accessKeyId: credentials.AccessKeyId,
secretAccessKey: credentials.SecretKey,
sessionToken: credentials.SessionToken
}
};
signedFetch = createSignedFetcher(options);
} catch (error) {
logger.error(error);
}
}
I'm going to search next to see if there's something wrong with the way I'm creating and calling the signed fetcher. I will post any resolution. If you have any suggestions I would appreciate you sharing them.
Thanks,
Terrence