13

I have a serverless app where I would like to deploy an elasticsearch cluster. I have configured it like this:

PostsSearch:
      Type: AWS::Elasticsearch::Domain
      Properties:
        ElasticsearchVersion: '6.3'
        DomainName: images-search-${self:provider.stage}
        ElasticsearchClusterConfig:
          DedicatedMasterEnabled: false
          InstanceCount: 1
          ZoneAwarenessEnabled: false
          InstanceType: t2.small.elasticsearch
        EBSOptions:
          EBSEnabled: true
          Iops: 0
          VolumeSize: 10
          VolumeType: 'gp2'
        AccessPolicies:
          Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Principal:
                AWS: '*'
              Action: 'es:ESHttp*'
              Resource: '*'

But, I get an error:

An error occurred: PostsSearch - Enable fine-grained access control or apply a restrictive access policy to your domain (Service: AWSElasticsearch; Status Code: 400; Error Code: ValidationException; Request ID: be0eca95-23ae-4ac9-be81-67cab37ccd70; Proxy: null).

How should I fix this?

Leff
  • 1,968
  • 24
  • 97
  • 201

1 Answers1

20

Based on the extra discussion in the comments.

It is not possible to make an ES domain totally public. CloudFormation will not allow for that. Thus, there are three options to choose from. Below I present three of them with in a sample serverless application. This is just basic hello-world application, it does not use the ES domain in any capacity, but I use it to verify that each choice works and can be deployed using serverless framework without errors.

Apply IP-based condition

This will make your domain open for access to only individual IP address or IP CIDR range. The example below limits access to one, single IP address.

service: estest

provider:
  name: aws
  runtime: python3.8

  stage: ${opt:stage, 'dev'}
  region: ${opt:region, 'us-east-1'}  

functions:
  hello:
    handler: handler.hello

resources:
  Resources:
    PostsSearch:
      Type: AWS::Elasticsearch::Domain
      Properties:
        ElasticsearchVersion: '6.3'
        DomainName: images-search-${self:provider.stage}
        ElasticsearchClusterConfig:
          DedicatedMasterEnabled: false
          InstanceCount: 1
          ZoneAwarenessEnabled: false
          InstanceType: t2.small.elasticsearch
        EBSOptions:
          EBSEnabled: true
          Iops: 0
          VolumeSize: 10
          VolumeType: 'gp2'
        AccessPolicies:
          Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Principal:
                AWS: '*'
              Action: 'es:ESHttp*'
              Resource: !Sub "arn:aws:es:${self:provider.region}:${AWS::AccountId}:domain/images-search-${self:provider.stage}/*"
              Condition:
                IpAddress:
                   aws:SourceIp: ["12.13.14.15"] 

Restrict principal

You can restrict access to your ES domain to selected IAM user or role. This way, only the given IAM user/role will be able to access the ES domain. In the below I use lambda existing IAM role as a principle. The function and its role must already exist.

service: estest

provider:
  name: aws
  runtime: python3.8

  stage: ${opt:stage, 'dev'}
  region: ${opt:region, 'us-east-1'}  

functions:
  hello:
    handler: handler.hello

resources:
  Resources:
    PostsSearch:
      Type: AWS::Elasticsearch::Domain
      Properties:
        ElasticsearchVersion: '6.3'
        DomainName: images-search-${self:provider.stage}
        ElasticsearchClusterConfig:
          DedicatedMasterEnabled: false
          InstanceCount: 1
          ZoneAwarenessEnabled: false
          InstanceType: t2.small.elasticsearch
        EBSOptions:
          EBSEnabled: true
          Iops: 0
          VolumeSize: 10
          VolumeType: 'gp2'
        AccessPolicies:
          Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Action: 'es:ESHttp*'            
              Principal:
                AWS: !Sub "arn:aws:iam::${AWS::AccountId}:role/service-role/lambda-function-es-role-b44mvudf"
              Resource: !Sub "arn:aws:es:${self:provider.region}:${AWS::AccountId}:domain/images-search-${self:provider.stage}/*"


Use fine-grained access control

The example here creates publicly accessible ES domain with fine-grained controls that requires username and password. This does not work in free-tier. I also hard-coded username and password, which obviously would need to be modified and provided as a parameter from from SSM Parameter store in real application.

service: estest

provider:
  name: aws
  runtime: python3.8

  stage: ${opt:stage, 'dev'}
  region: ${opt:region, 'us-east-1'}  

functions:
  hello:
    handler: handler.hello

resources:
  Resources:
    PostsSearch:    
      Type: AWS::Elasticsearch::Domain
      Properties: 
        DomainName: images-search-${self:provider.stage}        
        AccessPolicies: !Sub |
          {
            "Version": "2012-10-17",
            "Statement": [
              {
                "Effect": "Allow",
                "Principal": {
                  "AWS": "*"
                },
                "Action": "es:*",
                "Resource": "*"
              }
            ]
          }
        AdvancedSecurityOptions:
            Enabled: true
            InternalUserDatabaseEnabled: true
            MasterUserOptions: 
              MasterUserName: admin
              MasterUserPassword: fD343sfdf!3rf
        EncryptionAtRestOptions: 
          Enabled: true
        NodeToNodeEncryptionOptions:
          Enabled: true
        DomainEndpointOptions:
          EnforceHTTPS: true
        EBSOptions: 
          EBSEnabled: true
          VolumeSize: 20
          VolumeType: gp2
        ElasticsearchClusterConfig: 
          DedicatedMasterEnabled: false
          InstanceCount: 1
          InstanceType: c4.large.elasticsearch
          ZoneAwarenessEnabled: false
        ElasticsearchVersion: 7.7
Marcin
  • 215,873
  • 14
  • 235
  • 294
  • Where can I find my IP address/range for it? – Leff Oct 19 '20 at 11:27
  • @Leff Any `whatismyip` type website should be enough. – Marcin Oct 19 '20 at 11:34
  • So that will only allow my machine to do search queries? If so, I would like that to be public – Leff Oct 19 '20 at 11:54
  • @Leff To clarify, you would like your ES domain to be fully open to public - no VPC, no passwords, no any kind of protection? – Marcin Oct 22 '20 at 00:01
  • yes, this one is just for testing, I guess password could go, but that is the most I need for this – Leff Oct 22 '20 at 00:12
  • @Leff I can amend the example to use fine-grain control with password, but fine-grain control ES is not supported on t2 instances. Need bigger instance, which obviously will cost more. – Marcin Oct 22 '20 at 00:15
  • then I guess public will do if it is for free? – Leff Oct 22 '20 at 00:16
  • @Leff Yes, t2.small ES is part of free tier as explained [here](https://aws.amazon.com/about-aws/whats-new/2017/01/amazon-elasticsearch-service-free-tier-now-available-on-t2-small-elasticsearch-instances/). By the way, can you updated your question with minimal example of using serverless-framework with your existing ES domain setup? I would allow me to copy-and-paste your example to re-reproduce the issue on my end. Subsequently, it should enable providing more detailed answer. – Marcin Oct 22 '20 at 00:20
  • I have added the whole serverless.yml file if that was what you needed, if you need more, let me know – Leff Oct 22 '20 at 07:20
  • @Leff I did some more tests and reading, and there seem to be not possible to have fully open ES domain. You either have to limit public access to some IP range, or use fine-grain control to setup proper username and password for the domain. The only workaround would be to proxy everything through some intermediate server which would be open to the public. – Marcin Oct 22 '20 at 11:28
  • I apologise for such a newbie question but when you are talking about IP access for ES, are you talking about the IP of the machine that will be calling ES for search queries or for synchronization of ES with dynamodb? If first, that would mean that such queries are not possible from client js script? Do queries need be on a server side? – Leff Oct 22 '20 at 12:09
  • @Leff No need to apologise. Yes, its for the first. Basically, who/what IP can invoke it. Such queries are possible from client side, but you need to have fine-grain control enabled. The alternative is, as you wrote, to have a dedicated backend accessing your ES. For dynamodb stream, the data are written using a lambda function, so you can use IAM credentials to enable access to ES as shown [here](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-aws-integrations.html#es-aws-integrations-dynamodb-es). – Marcin Oct 22 '20 at 12:21
  • thank you for the great explanation. So, then I could set up lambda for search queries as well. I think that might be the easiest and cheapest solution. Then I guess I need to set the IAM credentials for 2 lambda functions, one for sync and the other for queries – Leff Oct 22 '20 at 12:40
  • @Leff No problem. Yes. An example of lambda for syncing is given by aws in the link I provided earlier. The lambda for queries cloud be based on this as well. – Marcin Oct 22 '20 at 21:48
  • @Leff I updated my answer with the examples of ES domain for 3 cases which are supported. – Marcin Oct 24 '20 at 09:24
  • @Leff No problem. Glad I could help out:-) – Marcin Oct 24 '20 at 10:11