I'm using Terraform to deploy a lambda that needs to keep secrets in the AWS SecretsManager.
I have the following abbreviated lambda:
Lambda
resource "aws_lambda_function" "thisThing" {
function_name = "functionName"
runtime = "python3.8"
handler = "thisThing.handler"
role = aws_iam_role.lambda_exec.arn
}
resource "aws_iam_role" "lambda_exec" {
name = "serverless_lambda"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Sid = ""
Principal = {
Service = "lambda.amazonaws.com"
}
}
]
})
}
resource "aws_iam_role_policy_attachment" "lambda_policy" {
role = aws_iam_role.lambda_exec.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
Here are the secrets
Secrets
# Secrets
resource "aws_secretsmanager_secret" "SECRET" {
name = "SECRET"
recovery_window_in_days = 0
}
resource "aws_secretsmanager_secret_version" "SECRET" {
secret_id = "${aws_secretsmanager_secret.SECRET.id}"
secret_string = "${var.SECRET}"
}
The error I'm getting is:
[ERROR] ClientError: An error occurred (AccessDeniedException) when calling the GetSecretValue operation: User: arn:aws:sts::439791110569:assumed-role/serverless_lambda/thisThing is not authorized to perform: secretsmanager:GetSecretValue on resource: SECRET because no identity-based policy allows the secretsmanager:GetSecretValue action
This is my first time using secrets manager, and I'm not very experienced in AWS, but I think based on the answer here, that I need to add a policy that allows my lambda exec role to have GetSecretValue rights. I've made a few attempts, but my lack of knowledge on how to look up the different policy ARN's is shutting me down.
Here's what I've tried adding (it's wrong, and I know it's wrong.)
resource "aws_iam_role_policy_attachment" "lambda_secretsmanager_role" {
role = aws_iam_role.lambda_exec.name
# ? policy_arn = "arn:aws:iam::aws:policy/SecretsManagerGetSecretValue"
}
That's not the correct ARN, but I'm not sure where to look to find the correct ARN.
You can add the permission using aws_iam_role_policy:
resource "aws_iam_role_policy" "sm_policy" {
name = "sm_access_permissions"
role = aws_iam_role.lambda_exec.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = [
"secretsmanager:GetSecretValue",
]
Effect = "Allow"
Resource = "*"
},
]
})
}
If you want to follow least privileged permissions, then you can change Resource = "*" into Resource = "<arn-of-the-secret>".
Related
My setup is as follows:
iam user --> iam user group --> assume role group policy --> eks cluster role --> eks role bindings
So, I have an assume role policy:
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Sid = ""
Principal = {
AWS = "arn:aws:iam::xxxxxxxxxxxxx:root"
}
},
]
Then I have a user group, with the following group policy:
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = [
"sts:AssumeRole",
]
Sid = "AllowAssumeOrganizationAccountRole"
Effect = "Allow"
Resource = "arn:aws:iam::xxxxxxxxxxxx:role/ro-k8s-role"
},
]
I then have the following cluster role for EKS:
rule {
api_groups = ["apps", "batch", "extensions"]
resources = ["pods", "deployments", "statefulsets", "events", "services", "pods/log", "pods/portforward"]
verbs = ["get", "list", "watch", "describe"]
}
And then a bunch of role bindings which are used for a list of namespaces:
metadata {
name = "k8s-ro-${each.value}"
namespace = each.value
}
role_ref {
api_group = "rbac.authorization.k8s.io"
kind = "Role"
name = "k8s-ro"
}
subject {
kind = "User"
name = "k8s-user"
api_group = "rbac.authorization.k8s.io"
}
If I then use the profile of a user in the group, and check the caller-identity...
{
"UserId": "XXXXXXXXXXXXXXXXXX",
"Account": "xxxxxxxxxxxxxxx",
"Arn": "arn:aws:iam::xxxxxxxxxxxxx:user/test-user"
}
I would expect to see the assume role here, right? Which explains why I can't view anything running on the cluster.
I'll close this as the initial problem is resolved. I hadn't properly configured the AWS Profiles to automatically handle the assume role. Which having done has resolved this issue.
~/.aws/config
[profile test]
cli_pager =
role_arn = arn:aws:iam::xxxxxxxxxxxx:role/k8s-role
source_profile = test
region = eu-west-1
~/.aws/credentials
[test]
aws_access_key_id = XXXXXXXXXXXXXXXXXX
aws_secret_access_key = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
I created a role with policy for Cognito to publish SNS. The problem with this when scanning via terraform security, is it complains of having an overly permissive (AVD-AWS-0057) since I'm using a wildcard in Resource: ["*"].
So, I made a change to this to only add the Cognito user pool ARN and SNS topic ARN, but still complain of the role not having an SNS publish permission. Where in fact, the action below indicates permission.
data "aws_iam_policy_document" "assume_role_policy" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["cognito-idp.amazonaws.com"]
}
condition {
test = "StringEquals"
variable = "sts:ExternalId"
values = [aws_cognito_user_pool.uam_user_pool.arn]
}
}
}
resource "aws_iam_role" "iam_role" {
name = var.role_name
path = var.role_path
assume_role_policy = data.aws_iam_policy_document.assume_role_policy.json
tags = var.default_tags
}
data "aws_iam_policy_document" "role_policy" {
statement {
sid = "AllowSNSPublish"
effect = "Allow"
actions = ["sns:publish"]
resources = [
aws_sns_topic.topic.arn,
aws_cognito_user_pool.uam_user_pool.arn
]
}
}
resource "aws_iam_policy" "managed_policy" {
name = var.role_policy_name
policy = data.aws_iam_policy_document.role_policy.json
tags = var.default_tags
}
resource "aws_iam_role_policy_attachment" "managed_policy_attach" {
role = aws_iam_role.role.name
policy_arn = aws_iam_policy.managed_policy.arn
}
How do you properly set this up?
In your "assume_role_policy" you are referencing the ARN of the user_pool and checking this against the sts:ExternalId - you need to add the external-id here rather than the ARN.
data "aws_iam_policy_document" "assume_role_policy" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["cognito-idp.amazonaws.com"]
}
condition {
test = "StringEquals"
variable = "sts:ExternalId"
values = ["<your-external-id>"]
}
}
}
You also don't need the ARN of the userpool in the "role_policy". This document is stating "whoever has this assigned to them can sns:publish to aws_sns_topic.topic.arn" - there is no need to specifc the userpool here. This is done at the point of attaching the policy to role/user/group (e.g. aws_iam_role.role.name).
I have defined the creation of a StepFunction state machine in Terraform, now I want to set a timer to trigger the state machine everyday, I think probably using cloudwatch event rules is a good choice, I know how to set event rule to trigger a Lambda:
resource "aws_cloudwatch_event_rule" "lambda_event_rule" {
name = xxx
schedule_expression = xxx
description = xxx
}
resource "aws_cloudwatch_event_target" "lambda_event_target" {
target_id = xxx
rule = aws_cloudwatch_event_rule.lambda_event_rule.name
arn = xxx
}
#I must setup the right permissions using 'aws_lambda_permission'
#see: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_event_target
resource "aws_lambda_permission" "lambda_event_permission" {
statement_id = xxx
action = "lambda:InvokeFunction"
function_name = xxx
principal = "events.amazonaws.com"
source_arn = aws_cloudwatch_event_rule.lambda_event_rule.name
}
but how can I setup the permission part for triggerring a state machine? I couldn't find any examples about it, am I missing anything? Is it because we don't need a permission config for state machine? Can someone help please?
Below is what I got to use cloudwatch event rules to trigger state machine so far:
resource "aws_cloudwatch_event_rule" "step_function_event_rule" {
name = xxx
schedule_expression = xxx
description = xxx
}
resource "aws_cloudwatch_event_target" "step_function_event_target" {
target_id = xxx
rule = aws_cloudwatch_event_rule.step_function_event_rule.name
arn = xxx
}
?????What else should I add here?
PS: I found someone else was asking about a similar question here, but no answers yet.
The
resource "aws_lambda_permission" "lambda_event_permission" {
statement_id = xxx
action = "lambda:InvokeFunction"
function_name = xxx
principal = "events.amazonaws.com"
source_arn = aws_cloudwatch_event_rule.lambda_event_rule.name
}
part is not needed at all in your case, only needed as stated "In order to be able to have your AWS Lambda function or SNS topic invoked by an EventBridge rule".
As blr stated in his answer, you need to add the role_arn in the aws_cloudwatch_event_target, set up a role with assume_role_policy which grants access to states.amazonaws.com and events.amazonaws.com, and attach to this role an extra policy as follows:
data "aws_iam_policy_document" "CW2SF_allowexec" {
statement {
actions = [
"sts:AssumeRole"
]
principals {
type = "Service"
identifiers = [
"states.amazonaws.com",
"events.amazonaws.com"
]
}
}
}
resource "aws_iam_role" "CW2SF_allowexec" {
name = "AWS_Events_Invoke-StepFunc"
assume_role_policy = data.aws_iam_policy_document.CW2SF_allowexec.json
}
resource "aws_iam_role_policy" "state-execution" {
name = "CW2SF_allowexec"
role = aws_iam_role.CW2SF_allowexec.id
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"states:StartExecution"
],
"Resource": [
"arn:aws:states:${var.region}:${data.aws_caller_identity.current.account_id}:stateMachine:data-pipeline-incremental"
]
}
]
}
EOF
}
You need to establish the trust between CloudWatch and StepFunctions with the AssumeRole, and then attach an inline or managed policy to the role that specifically allows this role to StartExecution of the state machine.
I'm not well versed with terraform but it seems to follow a similar pattern to the official documentation. For targets; https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_PutTargets.html >> See section "Adds a Step Functions state machine as a target"
{
"Rule": "testrule",
"Targets": [
{
"RoleArn": "arn:aws:iam::123456789012:role/MyRoleToAccessStepFunctions"
"Arn":"arn:aws:states:us-east-1:123456789012:stateMachine:HelloWorld"
}
]
}
This tells me that you need to pass the role and arn. So taking your example, here's the thing you probably need to fill
resource "aws_cloudwatch_event_rule" "step_function_event_rule" {
name = <something unique>
schedule_expression = <syntax described in https://docs.aws.amazon.com/eventbridge/latest/userguide/scheduled-events.html>
description = <something descriptive>
}
resource "aws_cloudwatch_event_target" "step_function_event_target" {
target_id = <something unique>
rule = aws_cloudwatch_event_rule.step_function_event_rule.name
arn = <step function arn>
role_arn = <role that allows eventbridge to start execution on your behalf>
}
Terraform version: 12
We have a legacy, unmanaged by Terraform IAM role that I'd like to reference from an aws_iam_policy_attachment block and I attempted the following:
resource "aws_iam_policy_attachment" "example-attach" {
name = "example-attach"
roles = [
aws_iam_role.managed-role.name,
"arn:aws:iam::1234567890:role/unmanaged-role"
]
policy_arn = aws_iam_policy.example-policy.arn
}
Dry-run works fine but when applying TF says:
– ValidationError: The specified value for roleName is invalid. It must contain only alphanumeric characters and/or the following: +=,.#_-
Is there a way I can just reference the unmanaged role without defining it in TF? Or is there some non-destructive way of declaring it that doesn't change anything to do with the unmanaged role?
In your roles, you are providing role ARN, not role name.
Therefore, instead of ARN, you should use its name:
resource "aws_iam_policy_attachment" "example-attach" {
name = "example-attach"
roles = [
aws_iam_role.managed-role.name,
"unmanaged-role"
]
policy_arn = aws_iam_policy.example-policy.arn
}
You can also use data_source
data "aws_iam_role" "example" {
name = "unmanaged-role"
}
and the reference it in your resource:
resource "aws_iam_policy_attachment" "example-attach" {
name = "example-attach"
roles = [
aws_iam_role.managed-role.name,
data.aws_iam_role.example.name
]
policy_arn = aws_iam_policy.example-policy.arn
}
I'm using terraform to provision an ELB & want to Enable Access logs for ELB in a S3 bucket. When I try to apply the resources, I get the below error - InvalidConfiguration: Access Denied for bucket:
Below are my TF resources with the S3 bucket policy created using the IAM Policy Document.
resource "aws_lb" "this" {
name = var.name
load_balancer_type = "application"
access_logs {
bucket = aws_s3_bucket.this.bucket
prefix = var.name
enabled = true
}
}
resource "aws_s3_bucket" "this" {
bucket = "${var.bucket_name}"
acl = "log-delivery-write"
force_destroy = true
}
resource "aws_s3_bucket_policy" "this" {
bucket = "aws_s3_bucket.this.id"
policy = "${data.aws_iam_policy_document.s3_bucket_lb_write.json}"
}
data "aws_iam_policy_document" "s3_bucket_lb_write" {
policy_id = "s3_bucket_lb_logs"
statement {
actions = [
"s3:PutObject",
]
effect = "Allow"
resources = [
"${aws_s3_bucket.this.arn}/*",
]
principals {
identifiers = ["${data.aws_elb_service_account.main.arn}"]
type = "AWS"
}
}
statement {
actions = [
"s3:PutObject"
]
effect = "Allow"
resources = ["${aws_s3_bucket.this.arn}/*"]
principals {
identifiers = ["delivery.logs.amazonaws.com"]
type = "Service"
}
}
statement {
actions = [
"s3:GetBucketAcl"
]
effect = "Allow"
resources = ["${aws_s3_bucket.this.arn}"]
principals {
identifiers = ["delivery.logs.amazonaws.com"]
type = "Service"
}
}
}
output "bucket_name" {
value = "${aws_s3_bucket.this.bucket}"
}
I get the following error
Error: Error putting S3 policy: NoSuchBucket: The specified bucket does not exist
status code: 404, request id: 5932CFE816059A8D, host id: j5ZBQ2ptHXivx+fu7ai5jbM8PSQR2tCFo4IAvcLkuocxk8rn/r0TG/6YbfRloBFR2WSy8UE7K8Q=
Error: Failure configuring LB attributes: InvalidConfigurationRequest: Access Denied for bucket: test-logs-bucket-xyz. Please check S3bucket permission
status code: 400, request id: ee101cc2-5518-42c8-9542-90dd7bb05e3c
terraform version
Terraform v0.12.23
provider.aws v3.6.0
There is mistake in:
resource "aws_s3_bucket_policy" "this" {
bucket = "aws_s3_bucket.this.id"
policy = "${data.aws_iam_policy_document.s3_bucket_lb_write.json}"
}
it should be:
resource "aws_s3_bucket_policy" "this" {
bucket = aws_s3_bucket.this.id
policy = data.aws_iam_policy_document.s3_bucket_lb_write.json
}
The orginal version (bucket = "aws_s3_bucket.this.id") will just try to look for bucket literally called "aws_s3_bucket.this.id".