Create Cognito Identity Pool using Pulumi - roles aren't attached - amazon-web-services

I followed the Pulumi Cognito.IdentityPool docs but could not link the Identity Pool with the Role using an attachment. This should be very easy: create Identity Pool, create Role, attach Role to Identity Pool. Simple. Unfortunately the code in the Pulumi docs does not reach this end, there is something missing. Here is my code:
import * as aws from '#pulumi/aws'
import { Stack, cognito, region } from '../../config'
const userPool = cognito.userPools[REDACTED]
const providerName: string = `cognito-idp.${region}.amazonaws.com/${userPool.poolId}`
export const swimmingPool = new aws.cognito.IdentityPool(REDACTED, {
identityPoolName: 'stuff!',
allowUnauthenticatedIdentities: false,
allowClassicFlow: false,
cognitoIdentityProviders: [{
providerName,
clientId: userPool.clientId,
serverSideTokenCheck: false,
}],
})
export const role = new aws.iam.Role(REDACTED, {
assumeRolePolicy: aws.iam.assumeRolePolicyForPrincipal(
{ Federated: 'cognito-identity.amazonaws.com' },
),
})
export const policy = new aws.iam.RolePolicy(REDACTED, {
role: role.id,
policy: {
Version: '2012-10-17',
Statement: [
{
Action: [
'cognito-sync:*',
'cognito-identity:*',
's3:PutObject',
's3:GetObject',
],
Effect: 'Allow',
Resource: '*',
},
],
},
})
export const roleAttachment = new aws.cognito.IdentityPoolRoleAttachment(REDACTED, {
identityPoolId: swimmingPool.id,
roles: { authenticated: role.arn },
roleMappings: [{
identityProvider: `cognito-idp.${region}.amazonaws.com/${userPool.poolId}:${userPool.clientId}`,
ambiguousRoleResolution: 'AuthenticatedRole',
type: 'Rules',
mappingRules: [{
claim: 'isAdmin',
matchType: 'Equals',
roleArn: role.arn,
value: 'paid',
}],
}],
})
I was expecting to see the Role to be attached in the AWS Console when I view the Identity Pool, but You have not specified roles for this identity pool. Click here to fix it. appears instead. What has to be done to attach the attachment that I attached?

Related

Unable to auth into AppSync GraphQL api from Lambda using IAM and AppSyncClient

I'm using an amplify stack and need to perform some actions to my graphql api which has dynamodb behind it. The request in my lambda function returns an Unauthorized error: "Not Authorized to access getSourceSync on type SourceSync", where getSourceSync is the gql query and SourceSync is the model name.
My schema.grapqhl for this particular model is set up as following. Note auth rule allow private provider iam:
type SourceSync #model (subscriptions: { level: off }) #auth(rules: [
{allow: private, provider: iam}
{allow: groups, groups: ["Admins"], provider: userPools},
{allow: groups, groups: ["Users"], operations: [create], provider: userPools},
{allow: groups, groupsField: "readGroups", operations: [create, read], provider: userPools},
{allow: groups, groupsField: "editGroups", provider: userPools}]) {
id: ID! #primaryKey
name: String
settings_id: ID #index(name: "bySettingsId", queryField: "sourceSyncBySettingsId")
settings: Settings #hasOne(fields: ["settings_id"])
childLookup: String
createdAt: AWSDateTime!
updatedAt: AWSDateTime!
_createdBy: String
_lastChangedBy: String
_localChanges: AWSJSON
readGroups: [String]
editGroups: [String]
}
My lambda function's role has the following inline policy attached to it. (Actual ID values have been omitted for security purposes on this post):
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"appsync:GraphQL"
],
"Resource": [
"arn:aws:appsync:us-east-1:111myaccountID:apis/11mygraphqlapiID/*"
],
"Effect": "Allow"
},
{
"Action": [
"appsync:GetType"
],
"Resource": [
"*"
],
"Effect": "Allow"
}
]
}
And finally my lambda function is set up as follows with a simple query test:
/* stuff */
"use strict";
const axios = require("axios");
const awsAppSync = require("aws-appsync").default;
const gql = require("graphql-tag");
require("cross-fetch/polyfill");
const { PassThrough } = require("stream");
const aws = require("aws-sdk");
aws.config.update({
region: process.env.AWS_REGION,
});
const appSync = new aws.AppSync();
const graphqlClient = new awsAppSync({
url: process.env.API_GRAPHQLAPIENDPOINTOUTPUT,
region: process.env.AWS_REGION,
auth: {
type: "AWS_IAM",
credentials: aws.config.credentials,
},
disableOffline: true
});
exports.handler = async (event, context) => {
console.log('context :: '+JSON.stringify(context));
console.log('aws config :: '+JSON.stringify(aws.config));
const sourceSyncTypes = await appSync
.getType({
apiId: process.env.API_GRAPHQLAPIIDOUTPUT,
format: "JSON",
typeName: "SourceSync",
})
.promise();
console.log('ss = '+JSON.stringify(sourceSyncTypes));
try {
const qs = gql`query GetSourceSync {
getSourceSync(id: "ov3") {
id
name
}
}`;
const res = await graphqlClient.query({query: qs, fetchPolicy: 'no-cache'});
console.log(JSON.stringify(res));
}
catch(e) {
console.log('ERR :: '+e);
console.log(JSON.stringify(e));
}
};
Found the solution, there seems to be an issue with triggering a rebuild of the resolvers on the api after permitting a function to access the graphql api. However there is a distinction to note:
If the graphql api is part of an amplify app stack, then only functions created through the amplify cli for that app (ex: amplify add function) and that are given access to the api through there will be able to access the api.
additonally during the update when you either create, or update the function to give it permissions, you must ensure that during the amplify push operation, the api stack will also be updating. you can trigger this by simply adding or removing a space in a comment inside of your amplify/backend/api//schema.graphql file.
If the function was created "adhoc" directly through the aws console, but it is trying to access a graphql api that was created as part of an amplify app stack, then you will need to put that function's role in amplify/backend/api/< apiname>/custom-roles.json in the format
{
"adminRoleNames": ["<role name>", "<role name 2>", ...]
}
Documentation references here.
If neither your api or lambda function were created with the amplify cli as part of an app stack, then just need to give access to the graphql resources for query, mutation and subscription to the lambda's role in IAM, via inline policies or a pre-defined policy.

Call Lambda using CustomResource

I created a CustomResource to call a lambda function when the CloudFormation stack is created. It fails with the following error:
Received response status [FAILED] from custom resource. Message returned: User: arn:aws:sts::<account>:assumed-role/stack-role is not authorized to perform: lambda:InvokeFunction on resource: arn:aws:lambda:us-east-1:<account>:function:<lambda> because no identity-based policy allows the lambda:InvokeFunction action
This is the code in the CDK:
import * as cr from '#aws-cdk/custom-resources';
const callLambda = new cr.AwsCustomResource(this, 'MyCustomResource', {
onCreate: {
service: 'Lambda',
action: 'invoke',
region: 'us-east-1',
physicalResourceId: cr.PhysicalResourceId.of(Date.now.toString()),
parameters: {
FunctionName: `my-function`,
Payload: '{}'
},
},
policy: cr.AwsCustomResourcePolicy.fromSdkCalls({
resources: cr.AwsCustomResourcePolicy.ANY_RESOURCE,
})
});
How can I grant permissions to the stack's assumed role so that it can perform lambda:InvokeFunction?
I solved the issue by creating a role that assumes the lambda service principal, and adding a policy statement allowing the lambda:InvokeFunction.
import * as cr from '#aws-cdk/custom-resources';
import * as iam from "#aws-cdk/aws-iam";
let role = new iam.Role(this, `my-role`, {
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
});
role.addToPolicy(new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ['lambda:InvokeFunction'],
resources: ['*']
}));
const callLambda = new cr.AwsCustomResource(this, 'MyCustomResource', {
onCreate: {
service: 'Lambda',
action: 'invoke',
region: 'us-east-1',
physicalResourceId: cr.PhysicalResourceId.of(Date.now.toString()),
parameters: {
FunctionName: `my-function`,
Payload: '{}'
},
},
policy: cr.AwsCustomResourcePolicy.fromSdkCalls({
resources: cr.AwsCustomResourcePolicy.ANY_RESOURCE,
}),
role: role as any
});
I find fromStatements works...must be some issues with fromSdkCalls
new cr.AwsCustomResource(this, 'MyCustomResource', {
onCreate: {
service: 'Lambda',
action: 'invoke',
region: 'us-east-1',
physicalResourceId: cr.PhysicalResourceId.of(Date.now.toString()),
parameters: {
FunctionName: `my-function`,
Payload: '{}'
},
},
policy: cr.AwsCustomResourcePolicy.fromStatements([
new PolicyStatement({
effect: Effect.ALLOW,
actions: ["lambda:InvokeFunction"],
resources: ["*"],
}),
])
});
Add a ResourcePolicy to your construct.
// infer the required permissions; fine-grained controls also available
policy: AwsCustomResourcePolicy.fromSdkCalls({resources: AwsCustomResourcePolicy.ANY_RESOURCE})

How to limit SSM based on user starting the command

I have an EC2 that I am connecting to using the AWS Systems manager, the EC2 has a role of AmazonSSMManagedInstanceCore attached and I am able to use ssm startSession from the CLI.
Without adding permissions to the users themselves am I able to limit which users are allowed to initiate a session to the EC2s?
I have tried adding a second policy to the EC2s where I block access to ssm:StartSession (which works when I apply it with no condition) with a condition containing aws.userid and aws:ssmmessages:session-id but neither of these blocked access.
I am using federated users in this account.
Below is an example of the most recent policy attempting to block access to that specific email but not others (which does not work).
const myPolicy = new ManagedPolicy(this, "sendAndBlockPoicy", {
statements: [
new PolicyStatement({
sid: "AllowSendCommand",
effect: Effect.ALLOW,
resources: [`arn:aws:ec2:${Aws.REGION}:${Aws.ACCOUNT_ID}:*`],
actions: ["ssm:SendCommand"],
}),
new PolicyStatement({
sid: "blockUsers",
effect: Effect.DENY,
resources: ["*"],
actions: ["ssm:*", "ssmmessages:*", "ec2messages:*"],
conditions: {
StringLike: {
"aws:ssmmessages:session-id":
"ABCDEFGHIJKLMNOPQRSTUV:me#email.com",
},
},
}),
],
});
const managedSSMPolicy = ManagedPolicy.fromAwsManagedPolicyName(
"AmazonSSMManagedInstanceCore",
);
const role = new Role(this, 'ec2Role', {
assumedBy: new ServicePrincipal('ec2.amazonaws.com')
managedPolicies: [managedSSMPolicy, myPolicy ]
}

AWS CDK - How to setup Cognito User Pool with "Authorization Code" flow

I'm currently trying to create an Amazon Cognito User Pool with OAuth flow "Authorization Code" via the AWS CDK as described in the documentation aws-cognito module.
Here is the typescript code of my stack:
import * as cdk from '#aws-cdk/core';
import { UserPool, VerificationEmailStyle, OAuthScope } from '#aws-cdk/aws-cognito';
import { Duration } from '#aws-cdk/core';
export class UserPoolStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const userPool = new UserPool(this, 'stackoverflow-userpool', {
userPoolName: 'stackoverflow-userpool',
selfSignUpEnabled: true,
signInCaseSensitive: false,
userVerification: {
emailSubject: 'Verify your email!',
emailBody: 'Hello, Thanks for signing up! {##Verify Email##}',
emailStyle: VerificationEmailStyle.LINK
},
signInAliases: {
username: true,
email: true
},
requiredAttributes: {
email: true
},
passwordPolicy: {
minLength: 12,
requireLowercase: true,
requireUppercase: true,
requireDigits: true,
requireSymbols: true,
tempPasswordValidity: Duration.days(7)
}
});
const client = userPool.addClient('stackoverflow-userpool-localhost-client', {
userPoolClientName: 'stackoverflow-localhost-client',
oAuth: {
flows: { authorizationCodeGrant: true },
scopes: [OAuthScope.OPENID],
callbackUrls: ['http://localhost:4200/callback']
}
});
userPool.addDomain('stackoverflow-userpool-domain-prefix', {
cognitoDomain: {
domainPrefix: 'stackoverflow'
}
});
}
}
For whatever reason my "Cognito User Pool" is not enabled as an "Identity Provider" in my "App client". (see screenshot)
Is my configuration wrong? I can't find any property that would enable this.
Is it a bug or because the module is still experimental? What confuses me is why would they describe how to set up the "Authorization Code" flow in the documentation if it doesn't work...
at the present date, looks good and worked for me. Maybe an old version of aws-cdk? In any case, you can explicitly enable Cognito User Pool on the client
const client = userPool.addClient('stackoverflow-userpool-localhost-client', {
userPoolClientName: 'stackoverflow-localhost-client',
oAuth: {
flows: { authorizationCodeGrant: true },
scopes: [OAuthScope.OPENID],
callbackUrls: ['http://localhost:4200/callback']
},
supportedIdentityProviders: [
UserPoolClientIdentityProvider.AMAZON,
UserPoolClientIdentityProvider.COGNITO,
]
});

Using CloudFormation, how can I create an Identity Pool that authorizes based on User Pool roles?

I'm using Serverless Framework to handle my CloudFormation stuff. I'm building a User Pool with groups that have their own roles. I want to build my Identity Pool so that the Cognito provider setting for Authenticated role selection is set to Choose role from token with a Role resolultion of DENY.
This is my relevant CloudFormation - ignore the ${self:custom....} stuff:
IdentityPool:
Type: AWS::Cognito::IdentityPool
Properties:
IdentityPoolName: ${self:custom.identityPoolName}
AllowUnauthenticatedIdentities: false
CognitoIdentityProviders:
- ClientId:
Ref: UserPoolClient
ProviderName:
Fn::GetAtt: ["UserPool", "ProviderName"]
IdentityPoolRoleAttachment:
Type: AWS::Cognito::IdentityPoolRoleAttachment
Properties:
IdentityPoolId:
Ref: IdentityPool
RoleMappings:
CognitoProvider:
IdentityProvider:
Fn::Join:
- ""
- - "cognito-idp."
- Ref: AWS::Region
- ".amazonaws.com/"
- Ref: UserPool
- ":"
- Ref: UserPoolClient
Type: Token
AmbiguousRoleResolution: Deny
This does_not work because the IdentityPoolRoleAttachment is requiring a Roles section. But I do_not want to use the authenticated and unauthenticated roles with the Identity Pool. I want the Identity Pool Cognito provider to only check the tokens being passed in.
This is the error I'm getting:
ServerlessError: An error occurred: IdentityPoolRoleAttachment - 1 validation error detected: Value null at 'roles' failed to satisfy constraint: Member must not be null (Service: AmazonCognitoIdentity; Status Code: 400; Error Code: ValidationException; Request ID: 80026230-eaa9-4045-86d8-6fe4c07cce9d).
How can I do this? Do I need to create an empty role and assigned it to the IdentityPoolRoleAttachment?
I am able to do this without Identity Pool roles in the console.
I was able to get this working without creating an empty role.
roles is not required according to doc but it seems like CFN cant handle null well.
you just need to set "roles": { }.
cdk code
new CfnIdentityPoolRoleAttachment(
this,
'ExampleCognitoIdentityPoolRoleAttachment',
{
identityPoolId: identityPool.ref,
roles: {},
roleMappings: {
mapping: {
type: 'Token',
ambiguousRoleResolution: 'Deny',
identityProvider: `cognito-idp.${cdk.Stack.of(this).region}.amazonaws.com/${userPool.userPoolId}:${cognitoAppClient.ref}`,
},
},
},
);
Cloudformation template output from cdk
"ExampleCognitoIdentityPoolRoleAttachment": {
"Type": "AWS::Cognito::IdentityPoolRoleAttachment",
"Properties": {
"IdentityPoolId": {
"Ref": "ExampleCognitoIdentityPool"
},
"RoleMappings": {
"mapping": {
"AmbiguousRoleResolution": "Deny",
"IdentityProvider": {
"Fn::Join": [
"",
[
"cognito-idp.eu-west-1.amazonaws.com/",
{
"Ref": "<UserPoolRef>"
},
":",
{
"Ref": "<UserPoolAppClientRef>"
}
]
]
},
"Type": "Token"
}
},
"Roles": { }
},
"Metadata": {
"aws:cdk:path": "example-stack/ExampleCognitoIdentityPoolRoleAttachment"
}
}