Skip to content

Commit 6ef8a93

Browse files
authored
Fix an authorization error when LogsPatternUsageService attempts to update logsdb.prior_logs_usage cluster setting (elastic#128050)
The following failure occurs when the LogsPatternUsageService updates the `logsb.prior_logs_usage` cluster setting: ``` [2025-05-13T17:11:21,685][DEBUG ][o.e.x.l.LogsPatternUsageService] [test-cluster-0] Failed to update [logsdb.prior_logs_usage] org.elasticsearch.ElasticsearchSecurityException: action [cluster:admin/settings/update] is unauthorized for user [_system] with effective roles [_system], this action is granted by the cluster privileges [manage,all] at [email protected]/org.elasticsearch.xpack.core.security.support.Exceptions.authorizationError(Exceptions.java:36) at [email protected]/org.elasticsearch.xpack.security.authz.AuthorizationService.denialException(AuthorizationService.java:1014) at [email protected]/org.elasticsearch.xpack.security.authz.AuthorizationService.actionDenied(AuthorizationService.java:991) at [email protected]/org.elasticsearch.xpack.security.authz.AuthorizationService.actionDenied(AuthorizationService.java:980) at [email protected]/org.elasticsearch.xpack.security.authz.AuthorizationService.actionDenied(AuthorizationService.java:970) at [email protected]/org.elasticsearch.xpack.security.authz.AuthorizationService.authorizeSystemUser(AuthorizationService.java:706) at [email protected]/org.elasticsearch.xpack.security.authz.AuthorizationService.authorize(AuthorizationService.java:320) at [email protected]/org.elasticsearch.xpack.security.action.filter.SecurityActionFilter.lambda$applyInternal$5(SecurityActionFilter.java:178) ``` This results in the `logsdb.prior_logs_usage` cluster setting not being set and causes the LogsPatternUsageService to continuously attempt to update the mentioned setting every 1 minute. The result of this is that the mentioned setting is never set, which if a cluster upgrades to 9.x, causes logsdb to be enabled by default. Which shouldn't happen for clusters upgrading from 8.x with data streams in the `logs-*-*` namespace. Unfortunately, this bug wasn't noticed given that the error wasn't visible (only if DEBUG logging was enabled) and the tests that test this functionality specifically don't run with security enabled. The fix is to use OriginSettingClient client instead of node client directly, so that xpack internal user is used, which does have to privileges to update cluster settings.
1 parent e3bac3a commit 6ef8a93

File tree

6 files changed

+107
-5
lines changed

6 files changed

+107
-5
lines changed

docs/changelog/128050.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 128050
2+
summary: Fix an authorization error when LogsPatternUsageService attempts to update `logsdb.prior_logs_usage` cluster setting.
3+
area: Logs
4+
type: bug
5+
issues: []

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ClientHelper.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ private static String maybeRewriteSingleAuthenticationHeaderForVersion(
196196
public static final String APM_ORIGIN = "apm";
197197
public static final String OTEL_ORIGIN = "otel";
198198
public static final String REINDEX_DATA_STREAM_ORIGIN = "reindex_data_stream";
199+
public static final String LOGS_PATTERN_USAGE_ORIGIN = "logs_pattern_usage";
199200

200201
private ClientHelper() {}
201202

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.logsdb;
9+
10+
import org.elasticsearch.client.Request;
11+
import org.elasticsearch.cluster.metadata.DataStream;
12+
import org.elasticsearch.common.settings.SecureString;
13+
import org.elasticsearch.common.settings.Settings;
14+
import org.elasticsearch.common.util.concurrent.ThreadContext;
15+
import org.elasticsearch.test.cluster.ElasticsearchCluster;
16+
import org.elasticsearch.test.cluster.local.distribution.DistributionType;
17+
import org.elasticsearch.test.rest.ESRestTestCase;
18+
import org.junit.ClassRule;
19+
20+
import java.util.Map;
21+
22+
import static org.hamcrest.Matchers.equalTo;
23+
import static org.hamcrest.Matchers.nullValue;
24+
25+
public class LogsdbWithSecurityRestIT extends ESRestTestCase {
26+
27+
private static final String PASSWORD = "secret-test-password";
28+
29+
@ClassRule
30+
public static ElasticsearchCluster cluster = ElasticsearchCluster.local()
31+
.distribution(DistributionType.DEFAULT)
32+
.setting("logsdb.usage_check.max_period", "1s")
33+
.setting("xpack.license.self_generated.type", "trial")
34+
.setting("xpack.security.enabled", "true")
35+
.setting("xpack.security.transport.ssl.enabled", "false")
36+
.setting("xpack.security.http.ssl.enabled", "false")
37+
.user("test_admin", PASSWORD, "superuser", true)
38+
.build();
39+
40+
@Override
41+
protected String getTestRestCluster() {
42+
return cluster.getHttpAddresses();
43+
}
44+
45+
@Override
46+
protected Settings restClientSettings() {
47+
String token = basicAuthHeaderValue("test_admin", new SecureString(PASSWORD.toCharArray()));
48+
return Settings.builder().put(ThreadContext.PREFIX + ".Authorization", token).build();
49+
}
50+
51+
public void testPriorLogsUsage() throws Exception {
52+
{
53+
var getClusterSettingsRequest = new Request("GET", "/_cluster/settings");
54+
getClusterSettingsRequest.addParameter("flat_settings", "true");
55+
var getClusterSettingResponse = (Map<?, ?>) entityAsMap(client().performRequest(getClusterSettingsRequest));
56+
var persistentSettings = (Map<?, ?>) getClusterSettingResponse.get("persistent");
57+
assertThat(persistentSettings.get("logsdb.prior_logs_usage"), nullValue());
58+
}
59+
60+
var request = new Request("POST", "/logs-test-foo/_doc");
61+
request.setJsonEntity("""
62+
{
63+
"@timestamp": "2020-01-01T00:00:00.000Z",
64+
"host.name": "foo",
65+
"message": "bar"
66+
}
67+
""");
68+
assertOK(client().performRequest(request));
69+
70+
String index = DataStream.getDefaultBackingIndexName("logs-test-foo", 1);
71+
var settings = (Map<?, ?>) ((Map<?, ?>) getIndexSettings(index).get(index)).get("settings");
72+
assertNull(settings.get("index.mode"));
73+
assertNull(settings.get("index.mapping.source.mode"));
74+
75+
assertBusy(() -> {
76+
var getClusterSettingsRequest = new Request("GET", "/_cluster/settings");
77+
getClusterSettingsRequest.addParameter("flat_settings", "true");
78+
var getClusterSettingResponse = (Map<?, ?>) entityAsMap(client().performRequest(getClusterSettingsRequest));
79+
var persistentSettings = (Map<?, ?>) getClusterSettingResponse.get("persistent");
80+
assertThat(persistentSettings.get("logsdb.prior_logs_usage"), equalTo("true"));
81+
});
82+
}
83+
84+
}

x-pack/plugin/logsdb/src/main/java/org/elasticsearch/xpack/logsdb/LogsPatternUsageService.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsAction;
1212
import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest;
1313
import org.elasticsearch.client.internal.Client;
14+
import org.elasticsearch.client.internal.OriginSettingClient;
1415
import org.elasticsearch.cluster.LocalNodeMasterListener;
1516
import org.elasticsearch.cluster.metadata.Metadata;
1617
import org.elasticsearch.common.regex.Regex;
@@ -26,6 +27,7 @@
2627
import java.util.concurrent.TimeUnit;
2728
import java.util.function.Supplier;
2829

30+
import static org.elasticsearch.xpack.core.ClientHelper.LOGS_PATTERN_USAGE_ORIGIN;
2931
import static org.elasticsearch.xpack.logsdb.LogsdbIndexModeSettingsProvider.LOGS_PATTERN;
3032

3133
/**
@@ -70,7 +72,7 @@ final class LogsPatternUsageService implements LocalNodeMasterListener {
7072
volatile Scheduler.Cancellable cancellable;
7173

7274
LogsPatternUsageService(Client client, Settings nodeSettings, ThreadPool threadPool, Supplier<Metadata> metadataSupplier) {
73-
this.client = client;
75+
this.client = new OriginSettingClient(client, LOGS_PATTERN_USAGE_ORIGIN);
7476
this.nodeSettings = nodeSettings;
7577
this.threadPool = threadPool;
7678
this.metadataSupplier = metadataSupplier;
@@ -155,11 +157,11 @@ void updateSetting() {
155157
hasPriorLogsUsage = true;
156158
cancellable = null;
157159
} else {
158-
LOGGER.debug(() -> "unexpected response [" + LOGSDB_PRIOR_LOGS_USAGE.getKey() + "]");
160+
LOGGER.debug(() -> "unexpected response [" + LOGSDB_PRIOR_LOGS_USAGE.getKey() + "], retrying...");
159161
scheduleNext(TimeValue.ONE_MINUTE);
160162
}
161163
}, e -> {
162-
LOGGER.debug(() -> "Failed to update [" + LOGSDB_PRIOR_LOGS_USAGE.getKey() + "]", e);
164+
LOGGER.warn(() -> "Failed to update [" + LOGSDB_PRIOR_LOGS_USAGE.getKey() + "], retrying...", e);
163165
scheduleNext(TimeValue.ONE_MINUTE);
164166
}));
165167
}

x-pack/plugin/logsdb/src/test/java/org/elasticsearch/xpack/logsdb/LogsPatternUsageServiceTests.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import org.elasticsearch.cluster.metadata.DataStreamTestHelper;
1616
import org.elasticsearch.cluster.metadata.Metadata;
1717
import org.elasticsearch.common.settings.Settings;
18+
import org.elasticsearch.common.util.concurrent.ThreadContext;
1819
import org.elasticsearch.core.TimeValue;
1920
import org.elasticsearch.core.Tuple;
2021
import org.elasticsearch.test.ESTestCase;
@@ -49,6 +50,7 @@ public void testOnMaster() throws Exception {
4950
}).when(client).execute(same(ClusterUpdateSettingsAction.INSTANCE), any(), any());
5051

5152
try (var threadPool = new TestThreadPool(getTestName())) {
53+
when(client.threadPool()).thenReturn(threadPool);
5254
var clusterState = DataStreamTestHelper.getClusterStateWithDataStreams(List.of(new Tuple<>("logs-app1-prod", 1)), List.of());
5355
Supplier<Metadata> metadataSupplier = clusterState::metadata;
5456

@@ -80,6 +82,8 @@ public void testCheckHasUsage() {
8082
}).when(client).execute(same(ClusterUpdateSettingsAction.INSTANCE), any(), any());
8183

8284
var threadPool = mock(ThreadPool.class);
85+
when(client.threadPool()).thenReturn(threadPool);
86+
when(threadPool.getThreadContext()).thenReturn(new ThreadContext(Settings.EMPTY));
8387
var scheduledCancellable = mock(Scheduler.ScheduledCancellable.class);
8488
when(threadPool.schedule(any(), any(), any())).thenReturn(scheduledCancellable);
8589
var clusterState = DataStreamTestHelper.getClusterStateWithDataStreams(List.of(new Tuple<>("logs-app1-prod", 1)), List.of());
@@ -104,6 +108,7 @@ public void testCheckHasUsageNoMatch() {
104108
var client = mock(Client.class);
105109

106110
var threadPool = mock(ThreadPool.class);
111+
when(client.threadPool()).thenReturn(threadPool);
107112
var scheduledCancellable = mock(Scheduler.ScheduledCancellable.class);
108113
when(threadPool.schedule(any(), any(), any())).thenReturn(scheduledCancellable);
109114
var clusterState = DataStreamTestHelper.getClusterStateWithDataStreams(List.of(new Tuple<>("log-app1-prod", 1)), List.of());
@@ -120,7 +125,7 @@ public void testCheckHasUsageNoMatch() {
120125
assertEquals(service.nextWaitTime, TimeValue.timeValueMinutes(2));
121126

122127
verify(threadPool, times(2)).schedule(any(), any(), any());
123-
verifyNoInteractions(client);
128+
verify(client, times(1)).threadPool();
124129
}
125130

126131
public void testCheckPriorLogsUsageAlreadySet() {
@@ -148,7 +153,8 @@ public void testCheckPriorLogsUsageAlreadySet() {
148153
assertTrue(service.hasPriorLogsUsage);
149154
assertNull(service.cancellable);
150155

151-
verifyNoInteractions(client, threadPool);
156+
verify(client, times(1)).threadPool();
157+
verifyNoInteractions(threadPool);
152158
}
153159

154160
public void testCheckHasUsageUnexpectedResponse() {
@@ -170,6 +176,8 @@ public void testCheckHasUsageUnexpectedResponse() {
170176
}).when(client).execute(same(ClusterUpdateSettingsAction.INSTANCE), any(), any());
171177

172178
var threadPool = mock(ThreadPool.class);
179+
when(threadPool.getThreadContext()).thenReturn(new ThreadContext(Settings.EMPTY));
180+
when(client.threadPool()).thenReturn(threadPool);
173181
var scheduledCancellable = mock(Scheduler.ScheduledCancellable.class);
174182
when(threadPool.schedule(any(), any(), any())).thenReturn(scheduledCancellable);
175183
var clusterState = DataStreamTestHelper.getClusterStateWithDataStreams(List.of(new Tuple<>("logs-app1-prod", 1)), List.of());

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationUtils.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import static org.elasticsearch.xpack.core.ClientHelper.INDEX_LIFECYCLE_ORIGIN;
3838
import static org.elasticsearch.xpack.core.ClientHelper.INFERENCE_ORIGIN;
3939
import static org.elasticsearch.xpack.core.ClientHelper.LOGSTASH_MANAGEMENT_ORIGIN;
40+
import static org.elasticsearch.xpack.core.ClientHelper.LOGS_PATTERN_USAGE_ORIGIN;
4041
import static org.elasticsearch.xpack.core.ClientHelper.ML_ORIGIN;
4142
import static org.elasticsearch.xpack.core.ClientHelper.MONITORING_ORIGIN;
4243
import static org.elasticsearch.xpack.core.ClientHelper.OTEL_ORIGIN;
@@ -164,6 +165,7 @@ public static void switchUserBasedOnActionOriginAndExecute(
164165
case ENT_SEARCH_ORIGIN:
165166
case CONNECTORS_ORIGIN:
166167
case INFERENCE_ORIGIN:
168+
case LOGS_PATTERN_USAGE_ORIGIN:
167169
case TASKS_ORIGIN: // TODO use a more limited user for tasks
168170
securityContext.executeAsInternalUser(InternalUsers.XPACK_USER, version, consumer);
169171
break;

0 commit comments

Comments
 (0)