【问题标题】:AWS Elasticsearch and Cognito - How to allow Cognito user to create private index?AWS Elasticsearch 和 Cognito - 如何允许 Cognito 用户创建私有索引?
【发布时间】:2020-01-07 18:03:44
【问题描述】:

在我的应用程序中,用户可以创建不同类型的帖子。这些帖子有不同级别的隐私(私人、仅限朋友、公开)。我想在用户处于“草稿”阶段时将所有帖子设为私有。

我的计划是让我的 Cognito 用户池中的授权用户访问仅与其用户 ID 对应的 Elasticsearch 索引(从 Cognito 用户获得的sub 属性)。

首先,用户向 API Gateway 发出请求并通过 Cognito Authorizer。接下来,如果他们通过了身份验证,他们就会到达这个(当前是准系统)Lambda 函数,该函数向与其用户 ID 匹配的索引发出 PUT 请求:

// Runtime: Node.js 10.x

const AWS = require("aws-sdk");
const uuidv4 = require("uuid/v4");
const axios = require("axios");

exports.handler = async (event) => {
    const {userId, draftData} = event; // userId value passed through from API Gateway

    const esEndpoint = `https://<MY_ES_ENDPOINT>/${userId}/drafts`;

    const newDraft = {
        id: uuidv4(),
        content: draftData
    };

    try {
        const result = await axios.put(esEndpoint, newDraft);
        return result.data;
    } catch(err) {
        console.log("Err: ", err);
        return err;
    }
};

在测试函数时,我收到此错误:User: anonymous is not authorized to perform: es:ESHttpPut

我的 Lambda 函数具有以下策略:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "es:ESHttpPut",
            "Resource": "arn:aws:es:<region>:<account-id>:domain/<domain-name>"
        }
    ]
}

我的 Elasticsearch 域具有以下访问策略:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::<account-id>:root"
      },
      "Action": "es:*",
      "Resource": "arn:aws:es:<region>:<account-id>:domain/<domain-name>/*"
    },
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::<account-id>:role/service-role/<lambda-role-name>"
      },
      "Action": "es:ESHttpPut",
      "Resource": "arn:aws:es:<region>:<account-id>:domain/<domain-name>/*"
    }
  ]
}

我知道我需要签署对 Elasticsearch 端点的请求才能解决此问题。但是我怎样才能为每个单独的 Cognito 用户做到这一点?我应该如何签署请求并使用axios 发送?

我应该如何修复我的策略以允许这些用户正确访问我的 Elasticsearch 域?

编辑

有人知道如何仅使用 IAM 用户凭证创建签名吗?我尝试使用aws4 库并得到403 Forbidden 错误,也尝试使用the example in the aws docs themselves 但收到相同的403 错误。

如果我能成功地向 Elasticsearch 发出请求,我会很高兴;

感谢任何帮助。谢谢。

【问题讨论】:

  • 这有什么有效的答案吗?

标签: node.js amazon-web-services elasticsearch amazon-cognito amazon-iam


【解决方案1】:
'use strict';

const path = require('path');
const AWS = require('aws-sdk');

const { AWS_REGION, ELASTICSEARCH_DOMAIN } = process.env;
const endpoint = new AWS.Endpoint(ELASTICSEARCH_DOMAIN);
const httpClient = new AWS.HttpClient();
const credentials = new AWS.EnvironmentCredentials('AWS');

/**
 * Sends a request to Elasticsearch
 *
 * @param {string} httpMethod - The HTTP method, e.g. 'GET', 'PUT', 'DELETE', etc
 * @param {string} requestPath - The HTTP path (relative to the Elasticsearch domain), e.g. '.kibana'
 * @param {Object} [payload] - An optional JavaScript object that will be serialized to the HTTP request body
 * @returns {Promise} Promise - object with the result of the HTTP response
 */
function sendRequest({ httpMethod, requestPath, payload }) {
    const request = new AWS.HttpRequest(endpoint, AWS_REGION);

    request.method = httpMethod;
    request.path = path.join(request.path, requestPath);
    request.body = payload;
    request.headers['Content-Type'] = 'application/json';
    request.headers['Host'] = ELASTICSEARCH_DOMAIN;


    const signer = new AWS.Signers.V4(request, 'es');
    signer.addAuthorization(credentials, new Date());

//     console.log(credentials.accessKeyId);
//  console.log(credentials.secretAccessKey);
//  console.log(credentials.sessionToken);
    //console.log(JSON.stringify(signer));

    return new Promise((resolve, reject) => {
        httpClient.handleRequest(request, null,
            response => {
                const { statusCode, statusMessage, headers } = response;
                let body = '';
                response.on('data', chunk => {
                    body += chunk;
                });
                response.on('end', () => {
                    const data = {
                        statusCode,
                        statusMessage,
                        headers
                    };
                    if (body) {
                        data.body = JSON.parse(body);
                    }
                    resolve(data);
                });
            },
            err => {
                reject(err);
            });
    });
}

module.exports = sendRequest;

使用上述代码请求您的 AWS ES 域。

【讨论】: