使用 API 密钥身份验证添加远程集群

编辑

使用 API 密钥身份验证添加远程集群

编辑

API 密钥身份验证允许本地集群通过跨集群 API 密钥来向远程集群进行身份验证。API 密钥需要由远程集群的管理员创建。配置本地集群在每次向远程集群发出请求时提供此 API 密钥。远程集群会验证 API 密钥,并根据 API 密钥的权限授予访问权限。

来自本地集群的所有跨集群请求都受限于 API 密钥的权限,而与请求关联的本地用户无关。例如,如果 API 密钥仅允许对远程集群上的 my-index 进行读取访问,则即使是本地集群的超级用户也会受到此限制。此机制使远程集群的管理员能够完全控制谁可以通过跨集群搜索和/或跨集群复制访问哪些数据。远程集群的管理员可以确信,不会超出 API 密钥明确分配的权限进行任何访问。

在本地集群端,并非每个本地用户都需要访问 API 密钥允许的每部分数据。本地集群的管理员可以进一步配置本地用户的其他权限约束,以便每个用户仅能访问必要的远程数据。请注意,只能进一步减少单个本地用户允许的 API 密钥的权限。无法增加权限以超出 API 密钥允许的范围。

在此模型中,跨集群操作使用专用的服务器端口(远程集群接口)在集群之间进行通信。远程集群必须启用此端口以供本地集群连接。为此端口配置传输层安全性 (TLS) 以最大程度地提高安全性(如建立与远程集群的信任中所述)。

本地集群必须信任远程集群的远程集群接口。这意味着本地集群信任远程集群的证书颁发机构 (CA),该机构对远程集群接口使用的服务器证书进行签名。建立连接时,参与跨集群通信的本地集群中的所有节点都会根据 TLS 信任配置验证另一端节点的证书。

要使用 API 密钥身份验证添加远程集群

如果遇到任何问题,请参阅故障排除

先决条件

编辑
  • 需要在每个节点的两个集群上启用 Elasticsearch 安全功能。默认情况下启用安全性。如果已禁用,请在 elasticsearch.yml 中将 xpack.security.enabled 设置为 true。请参阅常规安全设置
  • 本地和远程集群的节点必须为 8.10 或更高版本。
  • 本地和远程集群必须具有适当的许可证。有关更多信息,请参阅 https://elastic.ac.cn/subscriptions

建立与远程集群的信任

编辑

如果远程集群是 Elasticsearch Service 部署的一部分,则默认情况下它具有有效的证书。因此,您可以跳过这些说明中与证书相关的步骤。

在远程集群上

编辑
  1. 在远程集群的每个节点上启用远程集群服务器。在 elasticsearch.yml

    1. remote_cluster_server.enabled 设置为 true
    2. 配置远程集群服务器流量的绑定和发布地址,例如使用 remote_cluster.host。如果不配置地址,远程集群流量可能会绑定到本地接口,并且在其他计算机上运行的远程集群无法连接。
    3. (可选)使用 remote_cluster.port 配置远程服务器端口(默认为 9443)。
  2. 接下来,生成证书颁发机构 (CA) 和服务器证书/密钥对。在远程集群的其中一个节点上,从 Elasticsearch 的安装目录中

    1. 创建 CA,如果您还没有 CA

      ./bin/elasticsearch-certutil ca --pem --out=cross-cluster-ca.zip --pass CA_PASSWORD

      CA_PASSWORD 替换为您要用于 CA 的密码。如果您不是在生产环境中部署,则可以删除 --pass 选项及其参数。

    2. 解压缩生成的 cross-cluster-ca.zip 文件。此压缩文件包含以下内容

      /ca
      |_ ca.crt
      |_ ca.key
    3. 为远程集群中的节点生成证书和私钥对

      ./bin/elasticsearch-certutil cert --out=cross-cluster.p12 --pass=CERT_PASSWORD --ca-cert=ca/ca.crt --ca-key=ca/ca.key --ca-pass=CA_PASSWORD --dns=example.com --ip=127.0.0.1
      • CA_PASSWORD 替换为上一步中的 CA 密码。
      • CERT_PASSWORD 替换为您要用于生成的私钥的密码。
      • 使用 --dns 选项指定证书的相关 DNS 名称。您可以多次指定它以用于多个 DNS。
      • 使用 --ip 选项指定证书的相关 IP 地址。您可以多次指定它以用于多个 IP 地址。
    4. 如果远程集群有多个节点,您可以

      • 为所有节点创建一个通配符证书;
      • 或者,使用静默模式手动或批量为每个节点创建单独的证书。
  3. 在远程集群的每个节点上

    1. 将上一步中的 cross-cluster.p12 文件复制到 config 目录。如果您没有创建通配符证书,请确保复制正确的节点特定的 p12 文件。
    2. 将以下配置添加到 elasticsearch.yml

      xpack.security.remote_cluster_server.ssl.enabled: true
      xpack.security.remote_cluster_server.ssl.keystore.path: cross-cluster.p12
    3. 将 SSL 密钥库密码添加到 Elasticsearch 密钥库

      ./bin/elasticsearch-keystore add xpack.security.remote_cluster_server.ssl.keystore.secure_password

      出现提示时,输入上一步中的 CERT_PASSWORD

  4. 重新启动远程集群。
  5. 在远程集群上,生成一个跨集群 API 密钥,该密钥提供对要用于跨集群搜索或跨集群复制的索引的访问权限。您可以使用创建跨集群 API 密钥 API 或Kibana
  6. 将编码的密钥(响应中的 encoded)复制到安全位置。稍后您需要它来连接到远程集群。

在本地集群上

编辑
  1. 在本地集群的每个节点上

    1. 将之前在远程集群上生成的 ca.crt 文件复制到 config 目录,并将该文件重命名为 remote-cluster-ca.crt
    2. 将以下配置添加到 elasticsearch.yml

      xpack.security.remote_cluster_client.ssl.enabled: true
      xpack.security.remote_cluster_client.ssl.certificate_authorities: [ "remote-cluster-ca.crt" ]
    3. 将之前在远程集群上创建的跨集群 API 密钥添加到密钥库

      ./bin/elasticsearch-keystore add cluster.remote.ALIAS.credentials

      ALIAS 替换为您稍后将用于创建远程集群条目的名称。出现提示时,输入之前在远程集群上创建的编码跨集群 API 密钥。

  2. 重新启动本地集群以加载对密钥库和设置的更改。

注意:如果仅配置跨集群 API 密钥,则可以调用节点重新加载安全设置 API,而不是重新启动集群。在 elasticsearch.yml 中配置 remote_cluster_client 设置仍然需要重新启动。

连接到远程集群

编辑

您必须具有 manage 集群权限才能连接远程集群。

本地集群使用远程集群接口来建立与远程集群的通信。本地集群中的协调节点与远程集群中的特定节点建立长期 TCP 连接。Elasticsearch 要求这些连接保持打开状态,即使连接空闲时间很长也是如此。

要从 Kibana 中的 Stack Management 添加远程集群

  1. 从侧面导航中选择远程集群
  2. 输入远程集群的名称(集群别名)。
  3. 指定 Elasticsearch 端点 URL,或远程集群的 IP 地址或主机名,后跟远程集群端口(默认为 9443)。例如,cluster.es.eastus2.staging.azure.foundit.no:9443192.168.1.1:9443

或者,使用集群更新设置 API 添加远程集群。您还可以使用此 API 为本地集群中的每个节点动态配置远程集群。若要在本地集群中的各个节点上配置远程集群,请在每个节点的 elasticsearch.yml 中定义静态设置。

以下请求添加一个别名为 cluster_one 的远程集群。此集群别名是一个唯一标识符,表示与远程集群的连接,用于区分本地索引和远程索引。

resp = client.cluster.put_settings(
    persistent={
        "cluster": {
            "remote": {
                "cluster_one": {
                    "seeds": [
                        "127.0.0.1:{remote-interface-default-port}"
                    ]
                }
            }
        }
    },
)
print(resp)
const response = await client.cluster.putSettings({
  persistent: {
    cluster: {
      remote: {
        cluster_one: {
          seeds: ["127.0.0.1:{remote-interface-default-port}"],
        },
      },
    },
  },
});
console.log(response);
PUT /_cluster/settings
{
  "persistent" : {
    "cluster" : {
      "remote" : {
        "cluster_one" : {    
          "seeds" : [
            "127.0.0.1:9443" 
          ]
        }
      }
    }
  }
}

此远程集群的集群别名为 cluster_one

指定远程集群中种子节点的主机名和远程集群端口。

您可以使用远程集群信息 API 来验证本地集群是否已成功连接到远程集群。

resp = client.cluster.remote_info()
print(resp)
response = client.cluster.remote_info
puts response
const response = await client.cluster.remoteInfo();
console.log(response);
GET /_remote/info

API 响应表明本地集群已通过集群别名 cluster_one 连接到远程集群。

{
  "cluster_one" : {
    "seeds" : [
      "127.0.0.1:9443"
    ],
    "connected" : true,
    "num_nodes_connected" : 1,  
    "max_connections_per_cluster" : 3,
    "initial_connect_timeout" : "30s",
    "skip_unavailable" : true, 
    "cluster_credentials": "::es_redacted::", 
    "mode" : "sniff"
  }
}

本地集群连接到的远程集群中的节点数。

指示在通过跨集群搜索但没有可用节点时是否跳过远程集群。

如果存在,则指示远程集群已使用 API 密钥身份验证连接。

动态配置远程集群

编辑

使用集群更新设置 API 在集群中的每个节点上动态配置远程设置。以下请求添加了三个远程集群:cluster_onecluster_twocluster_three

seeds 参数指定远程集群中种子节点的主机名和远程集群端口(默认值为 9443)。

mode 参数确定配置的连接模式,默认为sniff。由于 cluster_one 没有指定 mode,因此它使用默认值。 cluster_twocluster_three 都显式使用了不同的模式。

resp = client.cluster.put_settings(
    persistent={
        "cluster": {
            "remote": {
                "cluster_one": {
                    "seeds": [
                        "127.0.0.1:{remote-interface-default-port}"
                    ]
                },
                "cluster_two": {
                    "mode": "sniff",
                    "seeds": [
                        "127.0.0.1:{remote-interface-default-port-plus1}"
                    ],
                    "transport.compress": True,
                    "skip_unavailable": True
                },
                "cluster_three": {
                    "mode": "proxy",
                    "proxy_address": "127.0.0.1:{remote-interface-default-port-plus2}"
                }
            }
        }
    },
)
print(resp)
const response = await client.cluster.putSettings({
  persistent: {
    cluster: {
      remote: {
        cluster_one: {
          seeds: ["127.0.0.1:{remote-interface-default-port}"],
        },
        cluster_two: {
          mode: "sniff",
          seeds: ["127.0.0.1:{remote-interface-default-port-plus1}"],
          "transport.compress": true,
          skip_unavailable: true,
        },
        cluster_three: {
          mode: "proxy",
          proxy_address: "127.0.0.1:{remote-interface-default-port-plus2}",
        },
      },
    },
  },
});
console.log(response);
PUT _cluster/settings
{
  "persistent": {
    "cluster": {
      "remote": {
        "cluster_one": {
          "seeds": [
            "127.0.0.1:9443"
          ]
        },
        "cluster_two": {
          "mode": "sniff",
          "seeds": [
            "127.0.0.1:9444"
          ],
          "transport.compress": true,
          "skip_unavailable": true
        },
        "cluster_three": {
          "mode": "proxy",
          "proxy_address": "127.0.0.1:9445"
        }
      }
    }
  }
}

您可以在初始配置后动态更新远程集群的设置。以下请求更新了 cluster_two 的压缩设置,以及 cluster_three 的压缩和 ping 计划设置。

当压缩或 ping 计划设置更改时,所有现有的节点连接都必须关闭并重新打开,这可能会导致正在进行的请求失败。

resp = client.cluster.put_settings(
    persistent={
        "cluster": {
            "remote": {
                "cluster_two": {
                    "transport.compress": False
                },
                "cluster_three": {
                    "transport.compress": True,
                    "transport.ping_schedule": "60s"
                }
            }
        }
    },
)
print(resp)
response = client.cluster.put_settings(
  body: {
    persistent: {
      cluster: {
        remote: {
          cluster_two: {
            'transport.compress' => false
          },
          cluster_three: {
            'transport.compress' => true,
            'transport.ping_schedule' => '60s'
          }
        }
      }
    }
  }
)
puts response
const response = await client.cluster.putSettings({
  persistent: {
    cluster: {
      remote: {
        cluster_two: {
          "transport.compress": false,
        },
        cluster_three: {
          "transport.compress": true,
          "transport.ping_schedule": "60s",
        },
      },
    },
  },
});
console.log(response);
PUT _cluster/settings
{
  "persistent": {
    "cluster": {
      "remote": {
        "cluster_two": {
          "transport.compress": false
        },
        "cluster_three": {
          "transport.compress": true,
          "transport.ping_schedule": "60s"
        }
      }
    }
  }
}

您可以通过为每个远程集群设置传递 null 值,从集群设置中删除远程集群。以下请求从集群设置中删除 cluster_two,保留 cluster_onecluster_three 不变。

resp = client.cluster.put_settings(
    persistent={
        "cluster": {
            "remote": {
                "cluster_two": {
                    "mode": None,
                    "seeds": None,
                    "skip_unavailable": None,
                    "transport.compress": None
                }
            }
        }
    },
)
print(resp)
response = client.cluster.put_settings(
  body: {
    persistent: {
      cluster: {
        remote: {
          cluster_two: {
            mode: nil,
            seeds: nil,
            skip_unavailable: nil,
            'transport.compress' => nil
          }
        }
      }
    }
  }
)
puts response
const response = await client.cluster.putSettings({
  persistent: {
    cluster: {
      remote: {
        cluster_two: {
          mode: null,
          seeds: null,
          skip_unavailable: null,
          "transport.compress": null,
        },
      },
    },
  },
});
console.log(response);
PUT _cluster/settings
{
  "persistent": {
    "cluster": {
      "remote": {
        "cluster_two": {
          "mode": null,
          "seeds": null,
          "skip_unavailable": null,
          "transport.compress": null
        }
      }
    }
  }
}

静态配置远程集群

编辑

如果您在 elasticsearch.yml 中指定设置,则只有具有这些设置的节点才能连接到远程集群并处理远程集群请求。

使用集群更新设置 API 指定的远程集群设置优先于您在单个节点的 elasticsearch.yml 中指定的设置。

在以下示例中,cluster_onecluster_twocluster_three 是表示与每个集群连接的任意集群别名。这些名称随后用于区分本地和远程索引。

cluster:
    remote:
        cluster_one:
            seeds: 127.0.0.1:9443
        cluster_two:
            mode: sniff
            seeds: 127.0.0.1:9444
            transport.compress: true      
            skip_unavailable: true        
        cluster_three:
            mode: proxy
            proxy_address: 127.0.0.1:9445 

已显式为对 cluster_two 的请求启用了压缩。

断开连接的远程集群对于 cluster_two 是可选的。

用于连接到 cluster_three 的代理端点的地址。

配置角色和用户

编辑

要将远程集群用于跨集群复制或跨集群搜索,您需要在本地集群上创建具有远程索引权限远程集群权限的用户角色。

您可以通过从侧边导航中选择 Security > Roles,在 Kibana 的 Stack Management 中管理用户和角色。您还可以使用角色管理 API 来动态添加、更新、删除和检索角色。

以下示例使用创建或更新角色 API。您必须至少拥有 manage_security 集群权限才能使用此 API。

本地集群用于连接远程集群的跨集群 API 密钥必须具有足够的权限,以涵盖单个用户所需的所有远程索引权限。

配置跨集群复制的权限

编辑

假设远程集群以 my_remote_cluster 的名称连接,以下请求在本地集群上创建一个名为 remote-replication 的角色,该角色允许复制远程 leader-index 索引

resp = client.security.put_role(
    name="remote-replication",
    cluster=[
        "manage_ccr"
    ],
    remote_indices=[
        {
            "clusters": [
                "my_remote_cluster"
            ],
            "names": [
                "leader-index"
            ],
            "privileges": [
                "cross_cluster_replication"
            ]
        }
    ],
)
print(resp)
const response = await client.security.putRole({
  name: "remote-replication",
  cluster: ["manage_ccr"],
  remote_indices: [
    {
      clusters: ["my_remote_cluster"],
      names: ["leader-index"],
      privileges: ["cross_cluster_replication"],
    },
  ],
});
console.log(response);
POST /_security/role/remote-replication
{
  "cluster": [
    "manage_ccr"
  ],
  "remote_indices": [
    {
      "clusters": [ "my_remote_cluster" ],
      "names": [
        "leader-index"
      ],
      "privileges": [
        "cross_cluster_replication"
      ]
    }
  ]
}

创建本地 remote-replication 角色后,使用创建或更新用户 API 在本地集群上创建一个用户,并分配 remote-replication 角色。例如,以下请求将 remote-replication 角色分配给名为 cross-cluster-user 的用户。

resp = client.security.put_user(
    username="cross-cluster-user",
    password="l0ng-r4nd0m-p@ssw0rd",
    roles=[
        "remote-replication"
    ],
)
print(resp)
const response = await client.security.putUser({
  username: "cross-cluster-user",
  password: "l0ng-r4nd0m-p@ssw0rd",
  roles: ["remote-replication"],
});
console.log(response);
POST /_security/user/cross-cluster-user
{
  "password" : "l0ng-r4nd0m-p@ssw0rd",
  "roles" : [ "remote-replication" ]
}

请注意,您只需在本地集群上创建此用户。

配置跨集群搜索的权限

编辑

假设远程集群以 my_remote_cluster 的名称连接,以下请求在本地集群上创建一个 remote-search 角色,该角色允许搜索远程 target-index 索引

resp = client.security.put_role(
    name="remote-search",
    remote_indices=[
        {
            "clusters": [
                "my_remote_cluster"
            ],
            "names": [
                "target-index"
            ],
            "privileges": [
                "read",
                "read_cross_cluster",
                "view_index_metadata"
            ]
        }
    ],
)
print(resp)
const response = await client.security.putRole({
  name: "remote-search",
  remote_indices: [
    {
      clusters: ["my_remote_cluster"],
      names: ["target-index"],
      privileges: ["read", "read_cross_cluster", "view_index_metadata"],
    },
  ],
});
console.log(response);
POST /_security/role/remote-search
{
  "remote_indices": [
    {
      "clusters": [ "my_remote_cluster" ],
      "names": [
        "target-index"
      ],
      "privileges": [
        "read",
        "read_cross_cluster",
        "view_index_metadata"
      ]
    }
  ]
}

创建 remote-search 角色后,使用创建或更新用户 API 在本地集群上创建一个用户,并分配 remote-search 角色。例如,以下请求将 remote-search 角色分配给名为 cross-search-user 的用户。

resp = client.security.put_user(
    username="cross-search-user",
    password="l0ng-r4nd0m-p@ssw0rd",
    roles=[
        "remote-search"
    ],
)
print(resp)
const response = await client.security.putUser({
  username: "cross-search-user",
  password: "l0ng-r4nd0m-p@ssw0rd",
  roles: ["remote-search"],
});
console.log(response);
POST /_security/user/cross-search-user
{
  "password" : "l0ng-r4nd0m-p@ssw0rd",
  "roles" : [ "remote-search" ]
}

请注意,您只需在本地集群上创建此用户。