AWS::Elasticsearch::Domain-DomainEndpointOptions
Analytics
Duplicate of #201
Support for AdvancedSecurityOptions is not a duplicate of #201, it just depends on it.
Hi, any news on the release of this feature, fine grained access is an important feature which skyrockets the use cases of Amazon Elasticsearch Service. Depending on custom resources is a decision which creates maintenance difficulties.
Three years have passed, still no actions. Any workaround for me to be able to still use Cloudformation?
@valentine-calabrio yes. You can resort to a custom resource. Here's what I have. I _think_ this is full featured (eg: All the !Ref's are accounted for in the code).
# The variables used in the elasticsearch template are named with the following convention
# Variables that start with a c are conditions
# Variables that start with a r are resources
# Variables that start with a p are parameters
# Variables that start with a o are outputs
Resources:
rElasticsearchDomain:
Type: AWS::Elasticsearch::Domain
Properties:
# SET YOUR ES PROPERTIES HERE
#############################################################################################
#
# Creates a custom ES UpdateDomainConfig so we can update ES where CloudFormation is lacking
#
# Note: Disable this once DomainEndpointOptions are part of CFN
# https://github.com/aws-cloudformation/aws-cloudformation-coverage-roadmap/issues/201
#
#############################################################################################
rEsUpdateDomainConfig:
Type: Custom::EsUpdatedomainConfig
Properties:
ServiceToken: !GetAtt rEsUpdateDomainConfigFunction.Arn
ElasticsearchClusterName: !Ref rElasticsearchDomain
# ElasticsearchClusterConfig: # disabled for now, may extend this function to use this at some point, notably for UltraWarm
DomainEndpointOptions:
EnforceHTTPS: true
TLSSecurityPolicy: Policy-Min-TLS-1-2-2019-07
rEsUpdateDomainConfigRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Path: /
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: root
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- lambda:InvokeFunction
Resource: !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:*'
- Effect: Allow
Action:
- es:UpdateElasticsearchDomainConfig
Resource:
- !GetAtt rElasticsearchDomain.Arn
rEsUpdateDomainConfigFunction:
Type: AWS::Lambda::Function
Properties:
Description: "Updates Elasticsearch Domain Config"
Handler: index.handler
Runtime: python3.7
MemorySize: 128
Timeout: 120
Role: !GetAtt rEsUpdateDomainConfigRole.Arn
Code:
ZipFile: |
import boto3
import cfnresponse
import json
import logging
LOG = logging.getLogger()
LOG.setLevel(logging.INFO)
def get_es_client():
"""es client"""
return boto3.client("es")
def put_es_config(elasticsearch_cluster_name, domain_endpoint_options):
get_es_client().update_elasticsearch_domain_config(
DomainName=elasticsearch_cluster_name,
DomainEndpointOptions=domain_endpoint_options
)
def handler(event, context):
"""primary lambda handler"""
LOG.info(json.dumps(event))
elasticsearch_cluster_name = event['ResourceProperties']['ElasticsearchClusterName']
custom_resource_name = "{}-updatedomainconfig".format(elasticsearch_cluster_name)
domain_endpoint_options = event['ResourceProperties']['DomainEndpointOptions']
# set this to a bool cause it comes across as a string
if 'EnforceHTTPS' in domain_endpoint_options:
domain_endpoint_options['EnforceHTTPS'] = (domain_endpoint_options['EnforceHTTPS'].lower() == "true")
try:
if event['RequestType'] in ["Create", "Update"]:
put_es_config(elasticsearch_cluster_name, domain_endpoint_options)
except Exception as err: # pylint: disable=broad-except,undefined-variable
LOG.error(err)
cfnresponse.send(event, context, cfnresponse.FAILED, {"Data": str(err)}, custom_resource_name)
else:
cfnresponse.send(event, context, cfnresponse.SUCCESS, {"Data": "Success"}, custom_resource_name)
@natefox have you confirmed this works because Advanced Security Options like FGAC cannot be added other than at creation time. Other options like Logging Options I鈥檝e done with custom resources like you suggested but the Advanced Security ones don鈥檛 allow for Update so this won鈥檛 work.
@natefox you cannot update Advanced Securiy options after creation Time.
I have ended up creating a AWS Custom resource with Create update and delete conditions in CDK. The biggest gap is the fact that this fire and forget. I have to handle the waiting behaviour via Code pipeline to deploy dashboards and Kibana security roles to Elasticsearch after the cluster is really active.
import { Aws, Construct,CfnOutput} from '@aws-cdk/core';
import { PolicyStatement } from '@aws-cdk/aws-iam';
import { AwsCustomResource } from '@aws-cdk/custom-resources';
export interface ElasticSearchResourceProps {
domainName: string;
identityPoolId: string;
roleArn: string;
userPoolId: string;
kmsKeyId: string;
stackName: string;
accountId: string;
masterUserARN: string;
SubnetIds: string[];
SecurityGroupId: string;
}
export class ElasticSearchResource extends Construct {
public readonly searchClusterResource: AwsCustomResource;
constructor (scope: Construct, id: string, props: ElasticSearchResourceProps) {
super(scope, id);
const dailyIngestedIotDataVolume=40;
const totalDataRetention=90;
const requiredIotDataStorage=dailyIngestedIotDataVolume*totalDataRetention*1.1*1.15;
const instanceCount=Math.ceil(requiredIotDataStorage/1024); //4,554/1,024= 5 instances
const requiredVolumeSize=Math.ceil(requiredIotDataStorage/instanceCount);
var createESParams = {
DomainName: props.domainName,
AccessPolicies: '{\"Version\": \"2012-10-17\",\"Statement\": [{\"Effect\": \"Allow\",\"Principal\": {\"AWS\": \"*\"},\"Action\": [\"es:*\"],\"Resource\": \"arn:aws:es:' + Aws.REGION+ ':'+ Aws.ACCOUNT_ID+ ':domain/'+props.domainName+'/*\"}]}',
AdvancedOptions: {
'rest.action.multi.allow_explicit_index':'true'
},
AdvancedSecurityOptions: {
Enabled: true,
InternalUserDatabaseEnabled: false,
MasterUserOptions: {
MasterUserARN: props.masterUserARN
}
},
CognitoOptions: {
Enabled: true,
IdentityPoolId: props.identityPoolId,
RoleArn: props.roleArn,
UserPoolId: props.userPoolId
},
DomainEndpointOptions: {
EnforceHTTPS: true,
TLSSecurityPolicy: 'Policy-Min-TLS-1-2-2019-07'
},
EBSOptions: {
EBSEnabled: true,
VolumeSize: requiredVolumeSize.toString(),
VolumeType: 'gp2'
},
ElasticsearchClusterConfig: {
DedicatedMasterCount: '3',
DedicatedMasterEnabled: true ,
DedicatedMasterType: 'c5.large.elasticsearch',
InstanceCount: instanceCount.toString(),
InstanceType: 'r5.large.elasticsearch',
WarmEnabled: false,
ZoneAwarenessConfig: {
AvailabilityZoneCount: '3'
},
ZoneAwarenessEnabled: true
},
ElasticsearchVersion: '7.4',
EncryptionAtRestOptions: {
Enabled: true,
KmsKeyId: props.kmsKeyId
},
NodeToNodeEncryptionOptions: {
Enabled: true
},
VPCOptions: {
SecurityGroupIds: [
props.SecurityGroupId
],
SubnetIds: props.SubnetIds
}
};
var updateESParams={...createESParams}
delete updateESParams.EncryptionAtRestOptions;
delete updateESParams.ElasticsearchVersion;
delete updateESParams.NodeToNodeEncryptionOptions;
this.searchClusterResource=new AwsCustomResource(this,'esCreationAwsCustomResource',{
policy:{statements:[new PolicyStatement({
actions: ['es:createElasticsearchDomain','es:deleteElasticsearchDomain','es:updateElasticsearchDomainConfig'],
resources:['*']
}),
new PolicyStatement({
actions: ['iam:PassRole'],
resources:[props.masterUserARN,props.roleArn]
}),
new PolicyStatement({
actions: ['kms:*'],
resources:['arn:aws:kms:'+Aws.REGION+':'+Aws.ACCOUNT_ID+':key/'+props.kmsKeyId]
})
]},
onCreate: {
physicalResourceId:{id:props.domainName},
service: "ES",
action: "createElasticsearchDomain",
parameters: createESParams
},
onUpdate: {
physicalResourceId:{id:props.domainName},
service: "ES",
action: "updateElasticsearchDomainConfig",
parameters: updateESParams
},
onDelete: {
service: "ES",
action: "deleteElasticsearchDomain",
parameters: {
DomainName: props.domainName
}
}
}
);
}
}
You know what, I missed that AdvancedSecurityOptions isnt a dupe of #201. This code applies to DomainEndpointOptions.
Would like to see this build at we rely on CF template. We will end up custom resource only to throw it away. Any idea on timing of this ?
We need to re-write an entire existing resource ouselves as a custom resource , that creates an entire E ES because of this one missing functionality that MUST be done at CREATION time. This product can not be used by enterprise like this.
Any update on this functionality?
Please implement this. Fine grained access controls finally makes AWS Elasticsearch a real viable solution for our security minded ELK stack. However, the inability to launch it using an infrastructure as code approach goes against our principles. Making our own custom resource is just painful and a big waste of effort.
I am implementing this using Amazon CloudFormation Custom Resource at:
https://github.com/valentine-dev/aws-cloudformation.
Will notify when I finish.
Please implement this. Fine grained access controls finally makes AWS Elasticsearch a real viable solution for our security minded ELK stack. However, the inability to launch it using an infrastructure as code approach goes against our principles. Making our own custom resource is just painful and a big waste of effort.
Here is my implementation using custom resource + lambda function + node.js:
https://github.com/valentine-dev/aws-cloudformation/tree/master/custom-resource/create-aes-with-fgac
Shipped on Aug 11 :)
Most helpful comment
Shipped on Aug 11 :)