Provide cross account delegation from Route53 - amazon-web-services

I have an AWS account holding my Route53 Parent Zone (example.com) and am trying to create a subdomain (beta.example.com) and (prod.example.com). After reading the documents, I realized, that I would need to create a CrossAcountDelegationRole in each of my subdomain accounts. To do this, I added the following:
//CDK Package for Parent Domain:
this.crossDelegationRole= new Role(this, 'CrossAccountRole', {
// The role name must be predictable
roleName: 'MyDelegationRole',
// The other account
assumedBy: new CompositePrincipal(new AccountPrincipal('Account A#'), new AccountPrincipal('Account B#')),
});
//CDK Package for Subdomain accounts:
const delegationRoleArn = Stack.of(this).formatArn({
region: '', // IAM is global in each partition
service: 'iam',
account: 'Parent Account #',
resource: 'role',
resourceName: 'MyDelegationRole',
});
const delegationRole = Role.fromRoleArn(this, 'DelegationRole', delegationRoleArn);
// create the record
const x =new CrossAccountZoneDelegationRecord(this, 'delegate', {
delegatedZone: this.hostedZone,
parentHostedZoneName: 'example.com', // or you can use parentHostedZoneId
delegationRole,
});
After deploying this to my cloudformation, I get an error saying:
Received response status [FAILED] from custom resource. Message returned: AccessDenied: User: arn:aws:sts::ParentAccount#:assumed-role/MyDelegationRole/cross-account-zone-delegation-1675020358038 is not authorized to perform: route53:ListHostedZonesByName because no identity-based policy allows the route53:ListHostedZonesByName action at Request.extractError (/var/runtime/node_modules/aws-
Is there something I am missing on this?
Update 1: I think this is because although I am creating the role, I am not giving my role any permissions. I need to provide some permissions to the crossDelegationRole. I'm unsure how I can give it permissions to my hostedZone (which isn't a Public Hosted Zone and just a hostedZone so I can't do a parentZone.grantDelegation(crossAccountRole); command)

Cross-account zone delegation is not possible with private hosted zones. That's why only the PublicHostedZone construct has the grantDelegation method, which is what you're missing.

Related

Using role in aws-auth config-map

I'm using this terraform module to create eks cluster: https://github.com/terraform-aws-modules/terraform-aws-eks
Then I create an additional role and added to map_roles input similar to the example in the repo
(my role is to use CloudWatch)
{
rolearn = "arn:aws:iam::66666666666:role/role1"
username = "role1"
groups = ["system:masters"]
}
I can verify that the role is added to the aws-auth config map together with a role created by the module.
I got this error when the app trying to use CloudWatch:
User: arn:aws:sts::xxx:assumed-role/yyy/zzz is not authorized to perform: logs:DescribeLogGroups on resource: arn:aws:logs:xxx:yyy:log-group::log-stream
the User arn in the error message has the yyy part match the role arn created by the module. So I thought I'm using the wrong role? if so how can I choose the correct credential? (I'm using .NETcore, create AmazonCloudWatchLogsClient without specify any credential)
When I manually edit that role and add the log's permission, the app works. Not sure if it's the right way, if so how can I add the permission in terraforming?
I ended up pulling the eks module to local and add more policies to the existing role:
resource "aws_iam_policy" "my_new_policy" {
name_prefix = "eks-worker-my_new_policy-${aws_eks_cluster.this.name}"
description = "EKS worker node my_new_policy policy for cluster ${aws_eks_cluster.this.name}"
policy = data.aws_iam_policy_document.my_new_policy.json
path = var.iam_path
}
data "aws_iam_policy_document" "my_new_policy" {
statement {
sid = "my_new_policy"
effect = "Allow"
actions = [
"logs:DescribeLogGroups"
]
resources = ["*"]
}
}

How to get AWS account id as custom variable in serverless framework?

In serverless framework, I want to set the deployment bucket as
<project_name>-<stage>-<account_id>
I can get the stage using a custom variable, like:
custom:
stage: ${opt:stage, self:provider.stage}
but how can I get the aws account id? I already tried to used serverless-pseudo-parameters, like this below, without success.
custom:
account_id: #{AWS::AccountId}
plugins:
- serverless-pseudo-parameters
Someone could help me to set the account id as a custom variable?
According to the documentation, to get the Account Id, you can use external js files:
// myCustomFile.js
module.exports.getAccountId = async (context) => {
return context.providers.aws.getAccountId();
};
.
# serverless.yml
service: new-service
provider: aws
custom:
accountId: ${file(../myCustomFile.js):getAccountId}
For anyone using Serverless with an "assumed role" where your IAM users are defined in a master AWS account and you're trying to deploy in a child account using a role from that child account: the documented solution - the one in the accepted answer above - does not work.
This setup in described in detail here: https://theithollow.com/2018/04/30/manage-multiple-aws-accounts-with-role-switching/. When using serverless with an --aws-profile that's configured to assume a role defined in another account, sts.getCallerIdentity() returns the account info of your master account from the default profile, and not the account of the assumed role.
To get the account ID of the assumed role (which is where we're deploying to), I did the following:
const { STS } = require('aws-sdk');
module.exports.getAccountId = async (context) => {
// This loads the AWS credentials Serverless is currently using
// They contain the role ARN of the assumed role
const credentials = context.providers.aws.getCredentials();
// init STS using the same credentials
const sts = new STS(credentials);
const identity = await sts.getCallerIdentity().promise();
return identity.Account;
};
Edit:
Found an even better way, that is simpler than the one presented in Serverless docs and also works fine with assumed roles:
module.exports.getAccountId = async (context) => {
return context.providers.aws.getAccountId();
};
You should be able to access them below as per below example https://serverless.com/framework/docs/providers/aws/guide/variables/
Resources:
- 'Fn::Join':
- ':'
- - 'arn:aws:logs'
- Ref: 'AWS::Region'
- Ref: 'AWS::AccountId'
- 'log-group:/aws/lambda/*:*:*'
It seems like your syntax is wrong. Try
custom:
account_id: ${AWS::AccountId}
Because at least in the example that you provided you are using #{AWS::AccountId}
Notice the hashtag in your one?

How to insert a RecordSet to Route53 in another AWS Account

My team is developing a product that launches new EC2 instances with some software in it, and we run a small piece of python code upon the instance's boot up process to register it in our Route53 DNS. The EC2 instances and our Route53 are in different AWS accounts.
While this python code works beautifully, it is my task now to rewrite this piece of software in golang.
I have already created an IAM Role in the Route53's account with the proper policy, and it works fine. I've also allowed it to be invoked by the other AWS account, and it also works nicely. The python code is able to assume-role using this IAM Role and so create a new RecordSet in Route53. However, when I try to perform this using my golang code, I get...
AccessDenied: Access denied
status code: 403, request id: xxxxxxx-yyyyyyyy-zzzzzzzz
This is as far as I can go with my understanding of IAM:
(...)
sess := session.Must(session.NewSession())
svc := sts.New(sess)
input := &sts.AssumeRoleInput{
DurationSeconds: aws.Int64(900),
ExternalId: aws.String("register-dns"),
RoleArn: aws.String(roleARN),
RoleSessionName: aws.String(roleSessionName),
}
result, err := svc.AssumeRole(input)
check(err)
creds := stscreds.NewCredentials(sess, roleARN)
service := route53.New(sess, &aws.Config{Credentials: creds})
inputs := &route53.ChangeResourceRecordSetsInput{
ChangeBatch: &route53.ChangeBatch{
Changes: []*route53.Change{
{
Action: aws.String("UPSERT"),
ResourceRecordSet: &route53.ResourceRecordSet{
Name: aws.String(instanceName + ".mycompany.com"),
ResourceRecords: []*route53.ResourceRecord{
{
Value: aws.String(hostIP),
},
},
TTL: aws.Int64(60),
Type: aws.String("A"),
},
},
},
Comment: aws.String(instanceName),
},
HostedZoneId: aws.String(hostedZoneID),
}
resultR53, err := service.ChangeResourceRecordSets(inputs)
(...)
At this point I was expecting my code to assume IAM Role roleARN and create a RecordSet in Route53 with it. But instead, I've been getting Access Denied constantly.
Is this the right procedure to access some AWS resource in a different AWS account?
Thanks!
What I've used as base:
https://docs.aws.amazon.com/sdk-for-go/api/service/sts/#STS.AssumeRole
https://docs.aws.amazon.com/sdk-for-go/api/aws/credentials/stscreds

Accessing two tables from different accounts within the same lambda function

Is it possible to access two tables within one lambda function while one of the tables is in the same account as the lambda function and the other is in another account?
I've seen articles on cross-account access delegation using IAM roles here and there. But I'm not sure how the code should reflect accessing a resource from another account. This is how I usually access some DynamoDb table:
const dynamodb = new AWS.DynamoDB();
const docClient = new AWS.DynamoDB.DocumentClient({ service: dynamodb });
docClient
.get({
TableName: 'SomeTable',
Key: { id }
});
Looking at the documentation, there's no mention of account ID in the constructor. So I'm not sure how I can have two connections at the same time, one pointing to one account and the other pointing to another account!?
Yes, it's possible to access 2 dynamoDb Tables (1 is in another AWS account) from same lambda function.
It's straight-forward to access the same Account's DynamoDB table.
dynamodb_client_of_same_account = boto3.client('dynamodb', region_name='us-east-1')
To access the Table of another AWS account, a new dynamoDb client needs to be created. You need to create cross account IAM Role and use STS to get temporary credentials. Follow the below steps to create cross account role.
AWS Account A has Lambda-----trying read/write on-----> AWS Account B with DynamoDB
Create an IAM Role in Account B to establish a Trust Relationship with Account A.
Go to IAM Role -------> Create Role ------> Select *Another AWS account *in the widget-----> Type AWS Account A Id and create the role
Don't forget to add DynamoDBAccess policy to this IAM Role.
Attach STS Assume Role policy to your AWS Lambda's Role in Account A
Create a new policy with below JSON and attach this policy to your Lambda Role.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": ""
}
]
}
Use the below code to create a new client to access DynamoDB table in another account.
roleARN = '*<ARN of the role created in Step-1>*'
client = boto3.client('sts')
response = client.assume_role(RoleArn=roleARN,
RoleSessionName='RoleSessionName',
DurationSeconds=900)
dynamodb_client_for_other_account = boto3.client('dynamodb', region_name='us-east-1',
aws_access_key_id=response['Credentials']['AccessKeyId'],
aws_secret_access_key=response['Credentials']['SecretAccessKey'],
aws_session_token = response['Credentials']['SessionToken'])
you could create a IAM user on the second account,
add DynamoDB permissions and get access/key secret for it
and then:
const dynamodb1 = new AWS.DynamoDB();
const dynamodb2 = new AWS.DynamoDB({
accessKeyId: 'x',
secretAccessKey: 'y',
region: 'z',
})
dynamodb1 will work with the role permissions from lambda
and dynamodb2 with the IAM user from the second account

ValidationException: Before you can proceed, you must enable a service-linked role to give Amazon ES permissions to access your VPC

I am trying to create a VPC controlled Elastic Search Service on AWS. The problem is I keep getting the error when I run the following code: 'ValidationException: Before you can proceed, you must enable a service-linked role to give Amazon ES permissions to access your VPC'.
const AWS = require('aws-sdk');
AWS.config.update({region:'<aws-datacenter>'});
const accessPolicies = {
Statement: [{
Effect: "Allow",
Principal: {
AWS: "*"
},
Action: "es:*",
Resource: "arn:aws:es:<dc>:<accountid>:domain/<domain-name/*"
}]
};
const params = {
DomainName: '<domain>',
/* required */
AccessPolicies: JSON.stringify(accessPolicies),
AdvancedOptions: {
EBSEnabled: "true",
VolumeType: "io1",
VolumeSize: "100",
Iops: "1000"
},
EBSOptions: {
EBSEnabled: true,
Iops: 1000,
VolumeSize: 100,
VolumeType: "io1"
},
ElasticsearchClusterConfig: {
DedicatedMasterCount: 3,
DedicatedMasterEnabled: true,
DedicatedMasterType: "m4.large.elasticsearch",
InstanceCount: 2,
InstanceType: 'm4.xlarge.elasticsearch',
ZoneAwarenessEnabled: true
},
ElasticsearchVersion: '5.5',
SnapshotOptions: {
AutomatedSnapshotStartHour: 3
},
VPCOptions: {
SubnetIds: [
'<redacted>',
'<redacted>'
],
SecurityGroupIds: [
'<redacted>'
]
}
};
const es = new AWS.ES();
es.createElasticsearchDomain(params, function (err, data) {
if (err) {
console.log(err, err.stack); // an error occurred
} else {
console.log(JSON.stringify(data, null, 4)); // successful response
}
});
The problem is I get this error: ValidationException: Before you can proceed, you must enable a service-linked role to give Amazon ES permissions to access your VPC. I cannot seem to figure out how to create this service linked role for the elastic search service. In the aws.amazon.com IAM console I cannot select that service for a role. I believe it is supposed to be created automatically.
Has anybody ran into this or know the way to fix it?
The service-linked role can be created using the AWS CLI.
aws iam create-service-linked-role --aws-service-name opensearchservice.amazonaws.com
Previous answer: before the service was renamed, you would do the following:
aws iam create-service-linked-role --aws-service-name es.amazonaws.com
You can now create a service-linked role in a CloudFormation template, similar to the Terraform answer by #htaccess. See the documentation for the CloudFormation syntax for Service-Linked Roles for more details
YourRoleNameHere:
Type: 'AWS::IAM::ServiceLinkedRole'
Properties:
AWSServiceName: es.amazonaws.com
Description: 'Role for ES to access resources in my VPC'
For terraform users who hit this error, you can use the aws_iam_service_linked_role resource to create a service linked role for the ES service:
resource "aws_iam_service_linked_role" "es" {
aws_service_name = "es.amazonaws.com"
description = "Allows Amazon ES to manage AWS resources for a domain on your behalf."
}
This resource was added in Release 1.15.0 (April 18, 2018) of the AWS Provider.
Creating a elasticsearch domain with VPC and using aws-sdk/cloudformation is currently not supported. The elasticsearch service requires a special service linked role to create the network interfaces in the specified VPC. This currently possible using console / cli(#Oscar Barrett's answer below).
However, there is a workaround to get this working and it is described as follows:
Create a test elasticsearch domain with VPC access using console.
This will create a service linked role named AWSServiceRoleForAmazonElasticsearchService [Note: You can not create the role with specified name manually or through thr console]
Once this role is created, use aws-sdk or cloudformation to create elasticsearch domain with VPC.
You can delete the test elasticsearch domain later
Update: The more correct way to create the service role is described in #Oscar Barrett's answer. I was thinking to delete my answer; but the other facts about the actual issue is still more relevant, thus keeping my answer here.
Do it yourself in CDK:
const serviceLinkedRole = new cdk.CfnResource(this, "es-service-linked-role", {
type: "AWS::IAM::ServiceLinkedRole",
properties: {
AWSServiceName: "es.amazonaws.com",
Description: "Role for ES to access resources in my VPC"
}
});
const esDomain = new es.CfnDomain(this, "es", { ... });
esDomain.node.addDependency(serviceLinkedRole);