Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Enterprise Search] Add .connector-secrets system index #104766

Merged
merged 26 commits into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
50369c2
Add search secrets index and GET requests
navarone-feekery Dec 20, 2023
2570622
Rename to Connector Secrets
navarone-feekery Dec 22, 2023
a026c0d
Fix inconsistent naming
navarone-feekery Dec 22, 2023
2296b3f
Rename cluster privilege
navarone-feekery Dec 22, 2023
b37c242
Add GET tests
navarone-feekery Jan 5, 2024
17f733f
Add POST request and tests
navarone-feekery Jan 5, 2024
ad7152f
Add missing tests
navarone-feekery Jan 5, 2024
4350f5d
Fix cluster privilege name
navarone-feekery Jan 5, 2024
bf73fb2
Fix more tests
navarone-feekery Jan 5, 2024
5d37d1a
Fix cluster privilege tests and add index permissions
navarone-feekery Jan 9, 2024
2e1b807
Add java rest test for system index
navarone-feekery Jan 9, 2024
2f106ed
Fix cluster privilege builtin count
navarone-feekery Jan 9, 2024
ae5a122
Fix cluster privilege test
navarone-feekery Jan 9, 2024
99171d6
Changes based on code review
navarone-feekery Jan 10, 2024
798e71c
Placate linter
navarone-feekery Jan 10, 2024
3184d8a
Fix typo and add FF check for index
navarone-feekery Jan 10, 2024
d4fb222
Move transport executions to index service class
navarone-feekery Jan 10, 2024
1e36981
Re-introduce cluster privilege
navarone-feekery Jan 11, 2024
28499cc
Add auth tests for endpoints
navarone-feekery Jan 11, 2024
8a89051
Remove outdated index permissions tests
navarone-feekery Jan 23, 2024
6a9e6d5
Add BWC and post/get unit tests
navarone-feekery Jan 23, 2024
b0fb528
Fix styling
navarone-feekery Jan 24, 2024
8359359
Add service index unit tests
navarone-feekery Jan 24, 2024
8cd866f
Remove unnecessary kibana index privilege
navarone-feekery Jan 24, 2024
5f952d3
Re-add mistakenly removed code
navarone-feekery Jan 24, 2024
7a80b8f
Remove unnecessary extra arguments
navarone-feekery Jan 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,14 @@ A successful call returns an object with "cluster" and "index" fields.
"none",
"post_behavioral_analytics_event",
"read_ccr",
"read_connector_secrets",
"read_fleet_secrets",
"read_ilm",
"read_pipeline",
"read_security",
"read_slm",
"transport_client",
"write_connector_secrets",
"write_fleet_secrets"
],
"index" : [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"connector_secret.get": {
"documentation": {
"url": null,
"description": "Retrieves a secret stored by Connectors."
},
"stability": "experimental",
"visibility":"private",
"headers":{
"accept": [ "application/json"]
},
"url":{
"paths":[
{
"path":"/_connector/_secret/{id}",
"methods":[ "GET" ],
"parts":{
"id":{
"type":"string",
"description":"The ID of the secret"
}
}
}
]
},
"params":{}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"connector_secret.post": {
"documentation": {
"url": null,
"description": "Creates a secret for a Connector."
},
"stability": "experimental",
"visibility":"private",
"headers":{
"accept": [ "application/json" ]
},
"url":{
"paths":[
{
"path":"/_connector/_secret",
"methods":[ "POST" ]
}
]
},
"params":{},
"body": {
"description":"The secret value to store",
"required":true
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,16 @@ public class ClusterPrivilegeResolver {
CROSS_CLUSTER_REPLICATION_PATTERN
);

public static final NamedClusterPrivilege READ_CONNECTOR_SECRETS = new ActionClusterPrivilege(
"read_connector_secrets",
Set.of("cluster:admin/xpack/connector/secret/get")
);

public static final NamedClusterPrivilege WRITE_CONNECTOR_SECRETS = new ActionClusterPrivilege(
"write_connector_secrets",
Set.of("cluster:admin/xpack/connector/secret/post")
);

private static final Map<String, NamedClusterPrivilege> VALUES = sortByAccessLevel(
Stream.of(
NONE,
Expand Down Expand Up @@ -380,7 +390,9 @@ public class ClusterPrivilegeResolver {
POST_BEHAVIORAL_ANALYTICS_EVENT,
MANAGE_SEARCH_QUERY_RULES,
CROSS_CLUSTER_SEARCH,
CROSS_CLUSTER_REPLICATION
CROSS_CLUSTER_REPLICATION,
READ_CONNECTOR_SECRETS,
WRITE_CONNECTOR_SECRETS
).filter(Objects::nonNull).toList()
);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"settings": {
"index": {
"auto_expand_replicas": "0-1",
"number_of_shards": 1,
"number_of_replicas": 0,
"priority": 100,
"refresh_interval": "1s"
}
},
"mappings": {
"_doc" : {
"dynamic": false,
"_meta": {
"version": "${connector-secrets.version}",
"managed_index_mappings_version": ${connector-secrets.managed.index.version}
},
"properties": {
"value": {
"type": "keyword",
"index": false
}
}
}
}
}
7 changes: 7 additions & 0 deletions x-pack/plugin/ent-search/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ dependencies {
module ':modules:search-business-rules'
}

testClusters.configureEach {
testDistribution = 'DEFAULT'
setting 'xpack.security.enabled', 'true'
setting 'xpack.security.autoconfiguration.enabled', 'false'
user username: 'x_pack_rest_user', password: 'x-pack-test-password'
}

tasks.named("dependencyLicenses").configure {
mapping from: /jackson.*/, to: 'jackson'
}
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugin/ent-search/qa/rest/roles.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ user:
cluster:
- post_behavioral_analytics_event
- manage_api_key
- read_connector_secrets
- write_connector_secrets
indices:
- names: [
"test-index1",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
setup:
- skip:
version: " - 8.12.99"
reason: Introduced in 8.13.0

---
'Post connector secret - admin':
- do:
connector_secret.post:
body:
value: my-secret
- set: { id: id }
- match: { id: $id }
- do:
connector_secret.get:
id: $id
- match: { value: my-secret }

---
'Post connector secret - authorized user':
- skip:
features: headers

- do:
headers: { Authorization: "Basic ZW50c2VhcmNoLXVzZXI6ZW50c2VhcmNoLXVzZXItcGFzc3dvcmQ=" } # user
connector_secret.post:
body:
value: my-secret
- set: { id: id }
- match: { id: $id }
- do:
headers: { Authorization: "Basic ZW50c2VhcmNoLXVzZXI6ZW50c2VhcmNoLXVzZXItcGFzc3dvcmQ=" } # user
connector_secret.get:
id: $id
- match: { value: my-secret }

---
'Post connector secret - unauthorized user':
- skip:
features: headers

- do:
headers: { Authorization: "Basic ZW50c2VhcmNoLXVucHJpdmlsZWdlZDplbnRzZWFyY2gtdW5wcml2aWxlZ2VkLXVzZXI=" } # unprivileged
connector_secret.post:
body:
value: my-secret
catch: unauthorized

---
'Post connector secret when id is missing should fail':
- do:
connector_secret.post:
body:
value: null
catch: bad_request
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
setup:
- skip:
version: " - 8.12.99"
reason: Introduced in 8.13.0

---
'Get connector secret - admin':
- do:
connector_secret.post:
body:
value: my-secret
- set: { id: id }
- match: { id: $id }
- do:
connector_secret.get:
id: $id
- match: { value: my-secret }

---
'Get connector secret - user with privileges':
- skip:
features: headers

- do:
headers: { Authorization: "Basic ZW50c2VhcmNoLXVzZXI6ZW50c2VhcmNoLXVzZXItcGFzc3dvcmQ=" } # user
connector_secret.post:
body:
value: my-secret
- set: { id: id }
- match: { id: $id }
- do:
headers: { Authorization: "Basic ZW50c2VhcmNoLXVzZXI6ZW50c2VhcmNoLXVzZXItcGFzc3dvcmQ=" } # user
connector_secret.get:
id: $id
- match: { value: my-secret }

---
'Get connector secret - user without privileges':
- skip:
features: headers

- do:
headers: { Authorization: "Basic ZW50c2VhcmNoLXVzZXI6ZW50c2VhcmNoLXVzZXItcGFzc3dvcmQ=" } # user
connector_secret.post:
body:
value: my-secret
- set: { id: id }
- match: { id: $id }
- do:
headers: { Authorization: "Basic ZW50c2VhcmNoLXVucHJpdmlsZWdlZDplbnRzZWFyY2gtdW5wcml2aWxlZ2VkLXVzZXI=" } # unprivileged
connector_secret.get:
id: $id
catch: unauthorized

---
'Get connector secret - Missing secret id':
- do:
connector_secret.get:
id: non-existing-secret-id
catch: missing
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.entsearch;

import org.apache.http.util.EntityUtils;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.Response;
import org.elasticsearch.client.ResponseException;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.test.SecuritySettingsSourceField;
import org.elasticsearch.test.rest.ESRestTestCase;
import org.elasticsearch.xcontent.XContentBuilder;
import org.elasticsearch.xcontent.XContentType;
import org.elasticsearch.xcontent.json.JsonXContent;

import java.io.IOException;
import java.util.Map;

import static org.hamcrest.Matchers.is;

public class ConnectorSecretsSystemIndexIT extends ESRestTestCase {

static final String BASIC_AUTH_VALUE = basicAuthHeaderValue(
"x_pack_rest_user",
SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING
);

@Override
protected Settings restClientSettings() {
return Settings.builder().put(ThreadContext.PREFIX + ".Authorization", BASIC_AUTH_VALUE).build();
}

public void testConnectorSecretsCRUD() throws Exception {
// post secret
final String secretJson = getPostSecretJson();
Request postRequest = new Request("POST", "/_connector/_secret/");
postRequest.setJsonEntity(secretJson);
Response postResponse = client().performRequest(postRequest);
assertThat(postResponse.getStatusLine().getStatusCode(), is(200));
Map<String, Object> responseMap = getResponseMap(postResponse);
assertThat(responseMap.size(), is(1));
assertTrue(responseMap.containsKey("id"));
final String id = responseMap.get("id").toString();

// get secret
Request getRequest = new Request("GET", "/_connector/_secret/" + id);
Response getResponse = client().performRequest(getRequest);
assertThat(getResponse.getStatusLine().getStatusCode(), is(200));
responseMap = getResponseMap(getResponse);
assertThat(responseMap.size(), is(2));
assertTrue(responseMap.containsKey("id"));
assertTrue(responseMap.containsKey("value"));
assertThat(responseMap.get("value"), is("test secret"));
}

public void testPostInvalidSecretBody() throws Exception {
Request postRequest = new Request("POST", "/_connector/_secret/");
postRequest.setJsonEntity("""
{"something":"else"}""");
ResponseException re = expectThrows(ResponseException.class, () -> client().performRequest(postRequest));
Response getResponse = re.getResponse();
assertThat(getResponse.getStatusLine().getStatusCode(), is(400));
}

public void testGetNonExistingSecret() {
Request getRequest = new Request("GET", "/_connector/_secret/123");
ResponseException re = expectThrows(ResponseException.class, () -> client().performRequest(getRequest));
Response getResponse = re.getResponse();
assertThat(getResponse.getStatusLine().getStatusCode(), is(404));
}

private String getPostSecretJson() throws IOException {
try (XContentBuilder builder = JsonXContent.contentBuilder()) {
builder.startObject();
{
builder.field("value", "test secret");
}
builder.endObject();
return BytesReference.bytes(builder).utf8ToString();
}
}

private Map<String, Object> getResponseMap(Response response) throws IOException {
return XContentHelper.convertToMap(XContentType.JSON.xContent(), EntityUtils.toString(response.getEntity()), false);
}
}
2 changes: 2 additions & 0 deletions x-pack/plugin/ent-search/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,6 @@
exports org.elasticsearch.xpack.application.connector.syncjob.action;

provides org.elasticsearch.features.FeatureSpecification with org.elasticsearch.xpack.application.EnterpriseSearchFeatures;

exports org.elasticsearch.xpack.application.connector.secrets.action to org.elasticsearch.server;
}
Loading