-
Notifications
You must be signed in to change notification settings - Fork 5.7k
Narrowing the Serverless IAM Deployment Policy #1439
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Thanks for the great work, I'll look into that, I also need to narrow down the IAM policy. |
Thanks for sharing. I'm going to give it a go a provide some feedback. |
This is really good, thanks |
Here is one change that I did to make it work for me: {
"Effect": "Allow",
"Action": [
"apigateway:GET"
],
"Resource": [
"arn:aws:apigateway:*::/restapis"
]
},
{
"Effect": "Allow",
"Action": [
"apigateway:GET",
"apigateway:POST",
"apigateway:PUT",
"apigateway:DELETE"
],
"Resource": [
"arn:aws:apigateway:*::/restapis/*/*"
]
}, {
"Effect": "Allow",
"Action": [
"apigateway:GET",
"apigateway:HEAD",
"apigateway:OPTIONS"
"apigateway:PATCH",
"apigateway:POST",
"apigateway:PUT",
"apigateway:DELETE"
],
"Resource": [
"arn:aws:apigateway:*::/restapis",
"arn:aws:apigateway:*::/restapis/*"
]
}, 2nd-level POST /api/v1/resource didn't work with your IAM but the above would handle it. Going further with the restrictions, adding ${region} to all Resources with the exception of
This one is particularly nasty but it doesn't seem to work without it. |
You may be able to change PassRole to just |
This is great, thanks. I got it working except one bit. We are using Anyone knows what the minimum set of permissions are for
I scanned the plugin source code to come up with the s3 function list and added them into the policy. But I received this error:
This line seems to yield Access Denied:
even though I had listBuckets in my permission list. What am I missing? note: I shall refine Resource further once I get it working.. |
I think try |
I think I have the serverless deployment policy nailed at this point. A bit more testing is in order, but mostly there now. In this second iteration, I've taken a slightly different approach. Instead of putting everything into 1 giant policy, I've broken it down into 5 separate put related policies:
FILE: s-policy-base.json {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "NonDestructiveCoreReaderActionsUsedThroughoutServerless",
"Effect": "Allow",
"Action": [
"cloudformation:Describe*",
"cloudformation:List*",
"cloudformation:Get*",
"cloudformation:PreviewStackUpdate",
"lambda:Get*",
"lambda:List*",
"ec2:DescribeSecurityGroups",
"ec2:DescribeSubnets",
"s3:GetObject",
"s3:List*",
"apigateway:GET",
"iam:List*",
"iam:Get*",
"iam:Simulate*",
"kinesis:Describe*",
"kinesis:List*",
"dynamodb:Describe*",
"dynamodb:List*",
"sqs:List*"
],
"Resource": "*"
}
]
} FILE: s-policy-resources.json {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ResourceBoundCloudFormationWritersForSlsResourcesDeploy",
"Effect": "Allow",
"Action": "cloudformation:*",
"Resource": "arn:aws:cloudformation:${region}:*:stack/${project}-*"
},
{
"Sid": "ResourceDeniedCloudFormationWritersForSlsResourcesDeploy",
"Effect": "Deny",
"Action": "cloudformation:*",
"Resource": "arn:aws:cloudformation:*:*:stack/${project}-${prod}*"
},
{
"Sid": "ResourceBoundIamWritersForSlsFunctionDeploy",
"Effect": "Allow",
"Action": "iam:*",
"Resource": "arn:aws:iam::*:role/${project}-*"
},
{
"Sid": "ResourceDeniedIamWritersForSlsFunctionDeploy",
"Effect": "Deny",
"Action": "iam:*",
"Resource": "arn:aws:iam::*:role/${project}-${prod}*"
},
{
"Sid": "ResourceBoundKinesisStreamWritersForSlsResourcesDeploy",
"Effect": "Allow",
"Action": "kinesis:*",
"Resource": "arn:aws:kinesis:${region}:*:stream/${project}-*"
},
{
"Sid": "ResourceDeniedKinesisStreamWritersForSlsResourcesDeploy",
"Effect": "Deny",
"Action": "kinesis:*",
"Resource": "arn:aws:kinesis:*:*:stream/${project}-${prod}*"
},
{
"Sid": "ResourceBoundDynamoDbStreamWritersForSlsResourcesDeploy",
"Effect": "Allow",
"Action": "dynamodb:*",
"Resource": "arn:aws:dynamodb:${region}:*:table/${project}-*"
},
{
"Sid": "ResourceDeniedDynamoDbStreamWritersForSlsResourcesDeploy",
"Effect": "Deny",
"Action": "dynamodb:*",
"Resource": "arn:aws:dynamodb:*:*:table/${project}-${prod}*"
},
{
"Sid": "ResourceBoundSqsClientForSqsResourcesDeploy",
"Effect": "Allow",
"Action": "sqs:*",
"Resource": "arn:aws:sqs:${region}:*:${project}-*"
},
{
"Sid": "ResourceDeniedSqsClientForSqsResourcesDeploy",
"Effect": "Deny",
"Action": "sqs:*",
"Resource": "arn:aws:sqs:*:*:${project}-${prod}*"
}
]
} FILE: s-policy-function.json {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PotentiallyDestructiveLambdaActionsRequiringAllAccessResourceSpecForSlsFunctionDeploy",
"Effect": "Allow",
"Action": [
"lambda:CreateFunction"
],
"Resource": "*"
},
{
"Sid": "ResourceBoundLambdaExecutionRolePassingForSlsFunctionDeploy",
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "arn:aws:iam::*:role/${project}-*"
},
{
"Sid": "ResourceDeniedLambdaExecutionRolePassingForSlsFunctionDeploy",
"Effect": "Deny",
"Action": "iam:PassRole",
"Resource": "arn:aws:iam::*:role/${project}-${prod}*"
},
{
"Sid": "ResourceBoundLambdaWritersForSlsFunctionDeploy",
"Effect": "Allow",
"Action": "lambda:*",
"Resource": "arn:aws:lambda:${region}:*:function:${project}-*"
},
{
"Sid": "ResourceDeniedLambdaWritersForSlsFunctionDeploy",
"Effect": "Deny",
"Action": "lambda:*",
"Resource": "arn:aws:lambda:${region}:*:function:${project}-${prod}*"
}
]
} FILE: s-policy-endpoint.json {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ResourceBoundApiGatewayWritersForSlsEndpointDeploy",
"Effect": "Allow",
"Action": "apigateway:*",
"Resource": [
"arn:aws:apigateway:${region}::/restapis",
"arn:aws:apigateway:${region}::/restapis/${api_id}*"
]
},
{
"Sid": "ResourceDeniedApiGatewayWritersForSlsEndpointDeploy",
"Effect": "Deny",
"Action": "apigateway:*",
"Resource": [
"arn:aws:apigateway:*::/restapis/${prod_api_id}/*"
]
}
]
} FILE: s-policy-event.json {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PotentiallyDestructiveLambdaActionsRequiringAllAccessResourceSpecForSlsEventDeploy",
"Effect": "Allow",
"Action": [
"lambda:CreateEventSourceMapping",
"lambda:DeleteEventSourceMapping",
"lambda:UpdateEventSourceMapping"
],
"Resource": "*"
},
{
"Sid": "ResourceBoundEventsInvokingLambdaForSlsEventDeploy",
"Effect": "Allow",
"Action": "events:*",
"Resource": "arn:aws:events:${region}:*:rule/${project}-*"
},
{
"Sid": "ResourceDeniedEventsInvokingLambdaForSlsEventDeploy",
"Effect": "Deny",
"Action": "events:*",
"Resource": "arn:aws:events:*:*:rule/${project}-${prod}*"
},
{
"Sid": "ResourceBoundLambdaWritersForSlsEventDeploy",
"Effect": "Allow",
"Action": "lambda:AddPermission",
"Resource": "arn:aws:lambda:${region}:*:function:${project}-*"
},
{
"Sid": "ResourceDeniedLambdaWritersForSlsEventDeploy",
"Effect": "Deny",
"Action": "lambda:AddPermission",
"Resource": "arn:aws:lambda:*:*:function:${project}-${prod}*"
}
]
} To use, manually replace all ${key} variables for your specific project. Then copy all 5 variable-expanded policy documents above into separate AWS managed policies. Then attach all 5 polices to a user account needing serverless deployment capability. (Note: you could limit users whom you don't want to deploy resources, for example, by excluding the s-policy-resources.json policy from their user, if that is useful to you.) First caveat, these policies enable you to deny deploying to production, while allowing deployment to other stages. You may want only a designated user (or Jenkins) to be the only party privileged enough to deploy to production. In which case, these "Deny" blocks that exclude "${prod}*" resource access become very useful. So, set ${prod} equal to whatever you call production. I.e. prod="prod" to protect production from harm. But, you ask, "what if you want to allow deployment to production?" To do that, set prod="open_season_on_prod". This will deny deployment to a stage named 'open_season_on_prod', but will allow deployment to 'prod' via the prior Allow blocks. So, set prod="prod" to generate policies that can't harm production. And set prod='open_season_on_prod' to allow production to be targeted via the policy. (Note: alternatively a template with conditional inclusion could be used to remove the Deny blocks when generating a production policy. But that assumes a serverless plugin.. more on that later.) Second caveat, after much testing and research, I finally figured out why some of my Actions were failing for ARNs I knew I specified correctly in the policy. The reason is, there are 4 actions that Serverless uses that do not allow ARN-limited resource specifications, even though intuitively they should. They are: {
"Effect": "Allow",
"Action": [
"lambda:CreateEventSourceMapping",
"lambda:DeleteEventSourceMapping",
"lambda:UpdateEventSourceMapping",
"lambda:CreateFunction"
],
"Resource": "*"
}, These 4 lambda actions only work if you pass '*' for the resource. Trying to scope limit them to a ${project} ARN (as I was initially attempting) breaks the deployment. So, these are potentially destructive actions that these policies allow, because AWS gives us no choice but to allow them without appropriate limitation, as documented here: http://docs.aws.amazon.com/lambda/latest/dg/lambda-api-permissions-ref.html Third caveat, the s-policy-endpoint.json policy document has a special case: the Api Gateway "api-id" is not generated until after you deploy the API. This leaves 2 choices: 1) allow full access to all ApiGateway APIs, even APIs you shouldn't touch, or 2) generate the rest API api-id separately, and include it in the s-policy-endpoint.json. This ensures you only have write permissions to the APIs you should. You can do the latter using this command:
Citation: http://codurance.com/2016/05/25/aws-api-gateway/ "But As you might imagine, a Serverless "policy" plugin to manage all this would make a lot of sense. And I've actually started building a Serverless "policy" plugin to automate the variable substitution, api-gateway api-id generation, ${prod} variable setting, and aws deployment of these 5 policies. However, legal red-tape must be cut before I can share. |
This is absolutely outstanding research and I think it makes a lot of sense to split into multiple policies for multi-user deployments. I yet need to try 1.0-alpha but do you think these policies would also be appropriate for CloudFormation-based deployments? |
Thanks @WooDzu! I haven't tried 1.0-alpha yet either - been too heads down on this thing. I hope to get the chance in the next couple weeks to give 1.0-alpha a good tire kicking. Meanwhile, I don't really know how well these policies will translate. |
So we are using these permissions for the s3 client: Permissionss3:GetObject |
Are these S3 policies needed for runtime, deployment time, or both? This thread is really only concerned with permissions required to deploy a serverless application. Are you finding these S3 policies are also necessary to deploy your serverless app? |
Hi Ron, Yes, we need the s3 permissions to deploy our serverless application as we are using the serverless-client-s3 plugin to deploy files to s3, along with the lambda code. I think this is a quite common use case. So having these permissions helps us. cheers, |
Thanks @str3tch for the clarification. This has got me thinking out loud a bit, so forgive the stream of consciousness style... but I'm questioning whether it would be best to co-locate all possible permissions used by serverless, its plugins, your application code etc. That would certainly be simpler, but that sort of union problem might be hard to maintain, and would force everyone to use the union of all possible permissions which would often be too broad. There is a distinction between the permissions required by "serverless-core" versus incremental permissions required by serverless plugins and your own serverless application code. And even those incremental permissions could be further delineated according to which phase of deployment requires them -- though that might be going too far and introducing complexity. Sounding more and more like the domain of a serverless plugin and a template engine to dynamically generate and deploy the IAM policies you need based on the contents of your CloudFormation and plugin configurations. @str3tch and @ploopaltar, what are you thoughts? |
I guess one of the challenges we had and still have is that there are some permissions which we feel serverless does not need but seems to be required for the deployment process using the s3-client. As a starting point clarity around why each permission is needed and at which part of the deployment process would help get us to the point where we can have permissions grouped according to the phase of deployments. |
@xtenix That's an interesting idea, but the problem I would face with getting that through here at work is that the engine would require IAM role create privileges to dynamically create new policies. And we're trying really hard here to restrict those, and not have service accounts have those elevated rights. I think the approach that you've taken is fantastic, split them up into separate policies. The plugin you mention to do the substituting would be awesome too.. how's it going on being able to share that work? Fantastic work though, you've saved me and others countless hours of testing! 👍 |
Hi @str3tch
Yes, I agree and I consider that as a challenging workflow issue for how policy changes are routed to proper authorities for review and deployment. In short, the AWS profile you are using to deploy your serverless app cannot be the same one used to deploy these policies. That would fundamentally defeat the purpose, because that AWS profile could choose to create an admin policy, and that opens up a back door. To solve this problem, I've thought of 2 distinct but related approaches:
Don't automatically deploy any policies, only create and write them locally (similar to the -c option on 'resources'). The policy templates would be expanded (variable substitution), either manually or automatically via plugin. A plugin could expand the policy templates and pass these intermediate files to an AWS admin for manual deployment. This will, of course, imply some communication overhead, but it works. For example:
Similar to option #1, but the expanded policy is automatically deployed via a separate and more privileged AWS profile. To afford this, introduce a "--profile" option to the plugin's "deploy" command that expects an admin key/secret. Serverless' standard (pre-existing) AWS profile would not be able to deploy a policy, but the one supplied here could. But only Operations team members would be able to invoke that option, and supply the requisite AWS admin profile. So, the workflow becomes... developers can commit policy changes to Git, but they can't deploy them - only issue a PR about them. Then the Operations team receives a PR request through Github, where they review the proposed policy changes, and if they agree, merge the branch and issue the following plugin command to actually deploy them: `$ sls policy deploy -s dev --profile <aws_admin_profile>`` Anyway, this is what I was thinking about for the 'policy' plugin I had in mind, but these ideas are tentative and may not be completely thought through.
Not allowed at this time. Hopefully later this year...
Thanks, glad it has been helpful to you. |
I'm exploring a use case with multi-tenant requirements: resources in a single AWS account, managed by teams from different customers / companies. From the above research (thanks so much, by the way), it's looking increasingly like there's not really a safe way to offer HTTP-mapped Lambda functions in such a multi-tenant scenario. Assuming we instead create separate AWS sub-accounts for each tenant, and use the IAM Policy templates provided further up, are there any other gotchas relating to using Serverless this way? |
I cannot figure out how |
I realize this is closed, but just a word of warning: Most IAM policies that can create and attach IAM policies are nearly indistinguishable from a super user policy. The privilege escalation path is pretty straight:
|
@ProTip is exactly right. Has anyone figured out a good way to narrow the iam:* policy so that privilege escalation isn't possible. A potential fix would be by better utilizing the resources section similar to what Rhino did. Is there a clever way to say iam:* but only for resources provisioned in the template? |
@lamarrh IAM permission boundaries slipped in under my radar but may provide a reasonable path forward: https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html . By requiring a specific boundary be attached create users and roles, or be applied when attaching policies, it effectively limits the maximum permissions an entity can grant to another entity. |
What about trust relationships? That's something I just learned of that I don't yet fully understand, but seems like it could help. |
At one time we defined a deployment role that was only able to create narrowly defined (i.e. named) IAM roles and only had exactly the rights a particular set of serverless projects needed to deploy and therefore could only pass those rights. The changes on that separate serverless project were minimal and targeted so the division of risk was fairly good. Only allowing the deployment pipeline to use that role was effective. |
Thought I'd post this here. If anyone gets an error which mentions
Note: the addition of:
I hope it helps someone. |
So i thought i'd come back and share how i solved this for my use case. This is a policy we created that only allows people to create and manage their own resources with serverless.
Not 100% complete, but it's easily extensible for other services. |
I keep finding new needed permissions. I realize now why servleress recommends to just give full admin access. |
@uclaeagit that recommendation is meant to reduce friction in getting started, not as production ready guidance. |
Well then the path to get to production-ready is a lot of trial and error as you find out what permissions you need. |
I'm a little surprised a project with this many commits and contributions lacks ops documentation for production hardening. production policy support should ideally be capable of:
Allowing a service account |
Is there any further improvements on this? The majority of policies seem to include things like I'm tempted to say it should be documented that it cannot be secured effectively when using CloudFormation - and that a dedicated Sub Organisation with reasonable limits imposed should be used to limit impact. I guess the best approach could be for the CloudFormation template to be uploaded manually initially, and for that template to automatically include necessary permissions for UPDATE of resources only, as I would assume that can then bypass things like This is my reference for the escalation issues: https://github.com/RhinoSecurityLabs/AWS-IAM-Privilege-Escalation/blob/master/README.md |
@driskell -it's good that you're keeping an eye out for security. You might be pleased to know that you are able to restrict the roles that can be passed when using the PassRole provision. See: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_passrole.html (if you don't want to read all the text, search for "example 1"). |
@hybby Thanks mate! I followed your suggestions and created two main roles: Deploy Role: https://gist.github.com/enricop89/80a36d6b73417d3ef40bfe00c09a8a35#file-serverless-deploy-role-json CFRole: https://gist.github.com/enricop89/80a36d6b73417d3ef40bfe00c09a8a35#file-cloudformation-role-json |
It would be great if some plugin can generate the policies from a serverless service. |
There is this tool https://github.com/dancrumb/generator-serverless-policy It's mentioned in a blog from Serverless.com Blog but its not perfect https://serverless.com/blog/abcs-of-iam-permissions/ |
We also created a tool that will help to generate AWS IAM policies: https://github.com/Open-SL/serverless-permission-generator |
Awesome, this has been working great for me @sachintha97 Direct link to the working policy generator tool: https://open-sl.github.io/serverless-permission-generator/ |
@joshuaquek @sachintha97 The generator appears to grant iam PassRole for all roles - along with CreateFunction for any function name - so it seems to offer little additional security as one can use the role to create function with Administrator permissions. Granted, it will make exploitation a little more time-consuming. Ideally that PassRole would be limited as @erikerikson pointed out to only allow passing of the roles created by the CloudFormation. Additionally it is not limiting policy attachments so could still attach an Administrator policy. There's also no limitation on the API Gateway modifications allowing potential hijacking of other gateways. Ideally the tool should note that this does not provide secure CloudFormation deployments. I don't think it wise to put such a tool into the public domain as it is as I worry many people will assume it secures the CloudFormation deployment in ways it does not. It does make privilege escalation harder, but by no means secured. |
I successfully used the instructions on this article after making these modifications. |
@BrandonE The modifications made are adding IAM permissions that allow creation of administrator users. So may be defeating the purpose of the original article. It looks like you'd just be able to add a new administrator to serverless.yml and the deployment would still accept it and create that user. I sometimes think it may be more secure to just use the Administrator user, as it's clear what access is given, where with many of these minimal privileges policies it hides the fact that it provides little protection from a genuine attack, so could lead into false sense of security. |
@driskell The additions allow you to manage roles, not manage IAM users, no? |
@BrandonE I guess depending on the restrictions put on place on them but I'm not sure it can be restricted sufficiently. With a "*" as Resource for sure it would let you attach the Administrator policy to any role using PutRolePolicy and the IAM user already is able to assume the deployment role. There might be a way around it with permissions boundaries etc but none of that is mentioned in the articles and I haven't personally worked it out, as I think even with boundaries on all the 3 roles, the CloudFormation still needs to create the Lambda Role and a Function to run as that and so the serverless.yml can inject anything with any permissions still. I think closest I got was allowing only updates to existing stuff since CloudFormation won't need permissions to things that don't change but it adds a barrier to adding new functions and things. |
I’ve been spending time recently trying to remove Admin rights as a requirement for sls deployments. Still a work in progress, but so far I have this policy that I can attach to any “serverless-agent” AWS user, so that the serverless-agent user is empowered enough to deploy:
Right now, I'm focused on a single policy that can deploy to all stages. But some enterprises may need this IAM policy to allow dev and staging deployments, but limit who can deploy to production. So, I've also been experimenting with adding "${stage}" to some of the resource ARNs, but don't have it fully worked out yet. For example:
There are still a few places where the permissions could be narrowed further. Specifically, the REST API section allows delete of ALL apis right now. And the lambda permissions are too broad. But I’ve had some annoying technical issues trying to narrow those two sections.
The API Gateway policy is still broad because you must have the 'api-id' in the ARN. But you don't know that until a deployment generates it. So on the surface, seems like a chicken/egg problem to me, but maybe there is a way to supply that api-id, instead of having AWS generate it.
And the lambda permissions are still broad because I can't see the particular Arn it is trying to manipulate to add an event mapping to a lambda, and the obvious ARNs don't work. Maybe there is a way to show the ARN being accessed in serverless, when the deployment fails so that I can add it to the policy, but no luck so far.
As I said, still a work in progress, so use with caution. Will post back any further 'narrowing' as I figure it.
The text was updated successfully, but these errors were encountered: