aws - is it possible to use Cognito user pool custom field in lambda function? - amazon-web-services

I have created a custom field "permissions" in my user pool.
I wonder if it is possible to use this field in my lambda function, so that I can do some permission control for calling the corresponding lambda function.
For example
if((**custom.permissions**).includes("admin")){
// execute the lambda function
}

For example if your lambda function was written using python boto3 you can get the user Attributes like this:
import boto3
client = boto3.client('cognito-idp')
response = client.get_user(
AccessToken='string'
)
The response structure contains
UserAttributes (list) --
An array of name-value pairs representing user attributes.
For custom attributes, you must prepend the custom: prefix to the attribute name.

Related

How can I change default parameter values for lambda?

I'm playing with AWS lambda and I am unable to change the default parameters that are used in the lambda. Is there a workaround for this?
Setup:
Lambda "iAmInvoked" is created by a stack in cloudformation which has default parameter values set (I set these defaults thinking that, these will be used in case invoker doesn't provide values for the parameters required and can be overridden). I'm invoking this iAmInvoked lambda asynchronously using a lambda called "iWillInvoke" and providing the payload which contains new values for parameters to be used by iAmInvoked instead of its defaults.
iWillInvoke code:
import json
import boto3
client = boto3.client('lambda')
def lambda_handler(event, context):
payloadForLambda = { 'parameter1' : 'abc,def' , 'parameter2' : '123456' , 'parameter3' : '987654' }
client.invoke(
FunctionName='arn:aws:lambda:us-west-2:123456789:function:iAmInvoked',
InvocationType='Event',
Payload=json.dumps(payloadForLambda)
)
iAmInvoked Code:
AWSTemplateFormatVersion: 2010-09-09
Description: |
"Creates required IAM roles to give permission to get and put SSM parameters and creates lambda function that shares the parameter(s)."
Parameters:
parameter1:
Type: String
Default: parameterValueThatShallBeOverridden1
parameter2:
Type: String
Default: parameterValueThatShallBeOverridden2
parameter3:
Type: String
Default: parameterValueThatShallBeOverridden3
Question/Issue:
Doesn't matter what I provide in the payload of iWillInvoke, iAmInvoked is using its default values. Is there a way I can override the defaults?
iAmInvoked Code is not your function code nor its parameters. Its CloudFormation template and parameters for the template. Using client.invoke does not affect in any form and shape the CloudFormation template.
To work with CloudFormation in boto3, there is cloudformation SDK.

IAM permission boundary for CDK apps with conditions

I am writing an IAM role for a CI/CD user which deploys our Cloud Development Kit (CDK) app. The CDK app consists of lambda functions, Fargate etc. The problem is, that CDK does not allow me to specify all the roles it needs. Instead it creates some of then on its own.
Couple of examples:
Each lambda function with log retention has another lambda created by CDK which sets log retention to the log group and log streams.
CloudTrail event executing a step function needs a role with states:StartExecution permission.
CDK creates these roles automatically and also puts inline policies to them. Which forces me to give my CI/CD role permissions to create roles and attach policies. So if anybody gets access to the CI/CD user (for example if our GitHub credentials leak), the attacker could create new roles and give them admin permissions.
I tried creating all the roles myself in a separate stack and then using these roles in CDK app. But as I mentioned above (see the examples above), it's not possible everywhere...
I also tried IAM permission boundary for the deployer role, but I can't figure out how to limit permissions for iam:PutRolePolicy. CDK essentially does the following:
iam:CreateRole
iam:PutRolePolicy
According to AWS documentation, conditions are quite basic string comparisons. I need to be able to select, which actions are allowed in the policy document passed to iam:PutRolePolicy.
This is a sample of my permission boundary allowing the principal to create roles and put role policies. See the condition comment.
permission_boundary = aws_iam.ManagedPolicy(
scope=self,
id='DeployerPermissionBoundary',
managed_policy_name='DeployerPermissionBoundary',
statements=[
aws_iam.PolicyStatement(
actions=['iam:CreateRole'],
effect=aws_iam.Effect.ALLOW,
resources=[f'arn:aws:iam::{core.Aws.ACCOUNT_ID}:role/my-project-lambda-role']
),
aws_iam.PolicyStatement(
actions=['iam:PutRolePolicy'],
effect=aws_iam.Effect.ALLOW,
resources=[f'arn:aws:iam::{core.Aws.ACCOUNT_ID}:role/my-project-lambda-role'],
conditions=Conditions([
StringLike('RoleName', 'Required-role-name'),
StringLike('PolicyName', 'Required-policy-name'),
StringEquals('PolicyDocument', '') # I want to allow only specified actions like logs:CreateLogStream and logs:PutLogEvents
])
)
]
)
deployer_role = aws_iam.Role(
scope=self,
id='DeployerRole',
assumed_by=aws_iam.AccountRootPrincipal(),
permissions_boundary=permission_boundary,
inline_policies={
'Deployer': aws_iam.PolicyDocument(
statements=[
aws_iam.PolicyStatement(
actions=['iam:PutRolePolicy'],
effect=aws_iam.Effect.ALLOW,
resources=[f'arn:aws:iam::{core.Aws.ACCOUNT_ID}:role/my-project-lambda-role']
),
...
...
]
)
}
)
What is the correct way of limiting the PutRolePolicy to selected actions only? I want to allow logs:CreateLogStream and logs:PutLogEvents and nothing else.
I've been fighting with this for quite some time and I don't want to fall back to giving out more permissions than necessary. Thanks everyone in advance!
Here's a solution in Python for CDK 1.4.0 inspired by #matthewtapper's code on GitHub. This allows you to set permission boundary to all the roles in your stack.
Needless to say it's very ugly, since python CDK does not provide construct objects in aspects. We have to dig deep into JSII to resolve the objects. Hope it helps someone.
from jsii._reference_map import _refs
from jsii._utils import Singleton
import jsii
#jsii.implements(core.IAspect)
class PermissionBoundaryAspect:
def __init__(self, permission_boundary: Union[aws_iam.ManagedPolicy, str]) -> None:
"""
:param permission_boundary: Either aws_iam.ManagedPolicy object or managed policy's ARN as string
"""
self.permission_boundary = permission_boundary
def visit(self, construct_ref: core.IConstruct) -> None:
"""
construct_ref only contains a string reference to an object. To get the actual object, we need to resolve it using JSII mapping.
:param construct_ref: ObjRef object with string reference to the actual object.
:return: None
"""
kernel = Singleton._instances[jsii._kernel.Kernel]
resolve = _refs.resolve(kernel, construct_ref)
def _walk(obj):
if isinstance(obj, aws_iam.Role):
cfn_role = obj.node.find_child('Resource')
policy_arn = self.permission_boundary if isinstance(self.permission_boundary, str) else self.permission_boundary.managed_policy_arn
cfn_role.add_property_override('PermissionsBoundary', policy_arn)
else:
if hasattr(obj, 'permissions_node'):
for c in obj.permissions_node.children:
_walk(c)
if obj.node.children:
for c in obj.node.children:
_walk(c)
_walk(resolve)
Usage:
stack.node.apply_aspect(PermissionBoundaryAspect(managed_policy_arn))
Here is the solution for CDK version 1.9.0 + with added and extra try_find_child() to prevent nested child errors on the node, also the stack.node.apply_aspect() method is depreceated by AWS, so there is a new usage implementation.
from aws_cdk import (
aws_iam as iam,
core,
)
import jsii
from jsii._reference_map import _refs
from jsii._utils import Singleton
from typing import Union
#jsii.implements(core.IAspect)
class PermissionBoundaryAspect:
"""
This aspect finds all aws_iam.Role objects in a node (ie. CDK stack) and sets
permission boundary to the given ARN.
"""
def __init__(self, permission_boundary: Union[iam.ManagedPolicy, str]) -> None:
"""
This initialization method sets the permission boundary attribute.
:param permission_boundary: The provided permission boundary
:type permission_boundary: iam.ManagedPolicy|str
"""
self.permission_boundary = permission_boundary
print(self.permission_boundary)
def visit(self, construct_ref: core.IConstruct) -> None:
"""
construct_ref only contains a string reference to an object.
To get the actual object, we need to resolve it using JSII mapping.
:param construct_ref: ObjRef object with string reference to the actual object.
:return: None
"""
if isinstance(construct_ref, jsii._kernel.ObjRef) and hasattr(
construct_ref, "ref"
):
kernel = Singleton._instances[
jsii._kernel.Kernel
] # The same object is available as: jsii.kernel
resolve = _refs.resolve(kernel, construct_ref)
else:
resolve = construct_ref
def _walk(obj):
if obj.node.try_find_child("Resource") is not None:
if isinstance(obj, iam.Role):
cfn_role = obj.node.find_child("Resource")
policy_arn = (
self.permission_boundary
if isinstance(self.permission_boundary, str)
else self.permission_boundary.managed_policy_arn
)
cfn_role.add_property_override("PermissionsBoundary", policy_arn)
else:
if hasattr(obj, "permissions_node"):
for c in obj.permissions_node.children:
_walk(c)
if hasattr(obj, "node") and obj.node.children:
for c in obj.node.children:
_walk(c)
_walk(resolve)
And the new implementation API for the stack is:
core.Aspects.of(stack).add(
PermissionBoundaryAspect(
f"arn:aws:iam::{target_environment.account}:policy/my-permissions-boundary"
)
)
Anyone still struggling in certain cases or wants a Java example:
#Slf4j
public class PermissionBoundaryRoleAspect implements IAspect {
private static final String BOUNDED_PATH = "/bounded/";
#Override
public void visit(final #NotNull IConstruct node) {
node.getNode().findAll().stream().filter(iConstruct -> CfnResource.isCfnResource(iConstruct) && iConstruct.toString().contains("AWS::IAM::Role")).forEach(iConstruct -> {
var resource = (CfnResource) iConstruct;
resource.addPropertyOverride("PermissionsBoundary", "arn:aws:iam::xxx:policy/BoundedPermissionsPolicy");
resource.addPropertyOverride("Path", BOUNDED_PATH);
});
if (node instanceof CfnInstanceProfile) {
var instanceProfile = (CfnInstanceProfile) node;
instanceProfile.setPath(BOUNDED_PATH);
}
}
}
Why I am doing it this way, is because I was faced with a case where not all Roles being created was of type CfnRole
In my case I had to create a CfnCloudFormationProvisionedProduct
This constructor had a weird way of creating Roles. Roles in this constructor is of type CfnResource and cannot be casted to "CfnRole"
Thus I am using iConstruct.toString().contains("AWS::IAM::Role") which works for every resource if its type AWS::IAM::Role and for any CfnRole

How use Filters with boto3 vpc endpoint services?

I need to get vpc endpoint service ID from python script, but I don't understand how use boto3, filters from vpc-id or a subnet
How do I use Filters?
This part of boto3
> (dict) --
A filter name and value pair that is used to return a more specific list of results from a describe operation. Filters can be used to match a set of resources by specific criteria, such as tags, attributes, or IDs. The filters supported by a describe operation are documented with the describe operation. For example:
DescribeAvailabilityZones
DescribeImages
DescribeInstances
DescribeKeyPairs
DescribeSecurityGroups
DescribeSnapshots
DescribeSubnets
DescribeTags
DescribeVolumes
DescribeVpcs
Name (string) --
The name of the filter. Filter names are case-sensitive.
Values (list) --
The filter values. Filter values are case-sensitive.
(string) --
The easiest method would be to call it with no filters, and observe what comes back:
import boto3
ec2_client = boto3.client('ec2', region_name='ap-southeast-2')
response = ec2_client.describe_vpc_endpoint_services()
for service in response['ServiceDetails']:
print(service['ServiceId'])
You can then either filter the results within your Python code, or use the Filters capability of the Describe command.
Feel free to print(response) to see the data that comes back.
It depends on what you want to filter the results with. In my case, I use below to filter it for a specific vpc-endpoint-id.
import boto3
vpc_client = boto3.client('ec2')
vpcEndpointId = "vpce-###"
vpcEndpointDetails = vpc_client.describe_vpc_endpoints(
VpcEndpointIds=[vpcEndpointId],
Filters=[
{
'Name': 'vpc-endpoint-id',
'Values': [vpcEndpointId]
},
])

How to upload to AWS S3 with Object Tagging

Is there a way to upload file to AWS S3 with Tags(not add Tags to an existing File/Object in S3). I need to have the file appear in S3 with my Tags , ie in a single API call.
I need this because I use a Lambda Function (that uses these S3 object Tags) is triggered by S3 ObjectCreation
You can inform the Tagging attribute on the put operation.
Here's an example using Boto3:
import boto3
client = boto3.client('s3')
client.put_object(
Bucket='bucket',
Key='key',
Body='bytes',
Tagging='Key1=Value1'
)
As per the docs, the Tagging attribute must be encoded as URL Query parameters. (For example, "Key1=Value1")
Tagging — (String) The tag-set for the object. The tag-set must be
encoded as URL Query parameters. (For example, "Key1=Value1")
EDIT: I only noticed the boto3 tag after a while, so I edited my answer to match boto3's way of doing it accordingly.
Tagging directive is now supported by boto3. You can do the following to add tags if you are using upload_file()
import boto3
from urllib import parse
s3 = boto3.client("s3")
tags = {"key1": "value1", "key2": "value2"}
s3.upload_file(
"file_path",
"bucket",
"key",
ExtraArgs={"Tagging": parse.urlencode(tags)},
)
If you're uploading a file using client.upload_file() or other methods that have the ExtraArgs parameter, you specify the tags differently you need to add tags in a separate request. You can add metadata as follows, but this is not the same thing. For an explanation of the difference, see this SO question:
import boto3
client = boto3.client('s3')
client.upload_file(
Filename=path_to_your_file,
Bucket='bucket',
Key='key',
ExtraArgs={"Metadata": {"mykey": "myvalue"}}
)
There's an example of this on the S3 docs, but you have to know that "metadata" corresponds to tags be aware that metadata is not exactly the same thing as tags though it can function similarly.
s3.upload_file(
"tmp.txt", "bucket-name", "key-name",
ExtraArgs={"Metadata": {"mykey": "myvalue"}}
)

Send an SMS via AWS SNS using boto3 in an AWS Lambda function?

I would like to send an SMS message from an AWS Lambda function using the boto3 publish method to notify the user of issues via SMS. My lambda function is written in Python and I am using the boto3 module. My lambda function has full rights to SNS. I have this code,
sns = boto3.client('sns')
sns.publish(
PhoneNumber = '+11234567890',
Message = 'Simple text message'
)
According to the boto3 documentation, the publish method accepts the following parameters,
response = client.publish(
TopicArn='string',
TargetArn='string',
PhoneNumber='string',
Message='string',
Subject='string',
MessageStructure='string',
MessageAttributes={
'string': {
'DataType': 'string',
'StringValue': 'string',
'BinaryValue': b'bytes'
}
}
)
It requires a "Message" parameter and one of the following three parameters as described in the docs:
TopicArn (string) -- The topic you want to publish to.
If you don't specify a value for the TopicArn parameter, you must
specify a value for the PhoneNumber or TargetArn parameters.
TargetArn (string) -- Either TopicArn or EndpointArn, but not both.
If you don't specify a value for the TargetArn parameter, you must
specify a value for the PhoneNumber or TopicArn parameters.
PhoneNumber (string) -- The phone number to which you want to deliver
an SMS message. Use E.164 format.
If you don't specify a value for the PhoneNumber parameter, you must
specify a value for the TargetArn or TopicArn parameters.
When my code is executed a parameter validation error is returned. It states,
Unknown parameter in input: "PhoneNumber", must be one of: TopicArn,
TargetArn, >Message, Subject, MessageStructure, MessageAttributes".
So the documentation seems to indicate that PhoneNumber is a valid parameter, but when used, an error occurs and the feedback from the error indicates that PhoneNumber is not a possible parameter. I suspect I am missing something obvious and simple, but could use some help.
I know there are other avenues to send SMS messages such as email gateways and other vendor supplied solutions like Twilio, but I would like to pursue the SNS based route and understand where I have gone wrong.
Actually your example looks right. Here is what I tried
import boto3
sns = boto3.client('sns')
number = '+17702233322'
sns.publish(PhoneNumber = number, Message='example text message' )
Worked like a charm. I recommend using awscli configured with your root account credentials first and take the code for a test drive. Once its working either create a new user with just the rights you need, or apply it to an Instance role.
You need to create a policy that allows SNS:Publish on resource:* (allow texting to everyone) or resource: '+17702233322' (allow text to a specific number).