Skip to content

Commit acb1061

Browse files
authored
Make project and cluster secrets customs available in server (elastic#125406)
* Make project and cluster secrets customs available in core
1 parent 419052f commit acb1061

File tree

7 files changed

+731
-1
lines changed

7 files changed

+731
-1
lines changed

server/src/main/java/org/elasticsearch/cluster/ClusterModule.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,9 @@
6565
import org.elasticsearch.common.io.stream.NamedWriteable;
6666
import org.elasticsearch.common.io.stream.NamedWriteableRegistry.Entry;
6767
import org.elasticsearch.common.io.stream.Writeable.Reader;
68+
import org.elasticsearch.common.settings.ClusterSecrets;
6869
import org.elasticsearch.common.settings.ClusterSettings;
70+
import org.elasticsearch.common.settings.ProjectSecrets;
6971
import org.elasticsearch.common.settings.Setting;
7072
import org.elasticsearch.common.settings.Setting.Property;
7173
import org.elasticsearch.common.settings.Settings;
@@ -264,7 +266,9 @@ public static List<Entry> getNamedWriteables() {
264266
RegisteredPolicySnapshots::new,
265267
RegisteredPolicySnapshots.RegisteredSnapshotsDiff::new
266268
);
267-
269+
// Secrets
270+
registerClusterCustom(entries, ClusterSecrets.TYPE, ClusterSecrets::new, ClusterSecrets::readDiffFrom);
271+
registerProjectCustom(entries, ProjectSecrets.TYPE, ProjectSecrets::new, ProjectSecrets::readDiffFrom);
268272
// Task Status (not Diffable)
269273
entries.add(new Entry(Task.Status.class, PersistentTasksNodeService.Status.NAME, PersistentTasksNodeService.Status::new));
270274

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
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", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.common.settings;
11+
12+
import org.elasticsearch.TransportVersion;
13+
import org.elasticsearch.TransportVersions;
14+
import org.elasticsearch.cluster.AbstractNamedDiffable;
15+
import org.elasticsearch.cluster.ClusterState;
16+
import org.elasticsearch.cluster.NamedDiff;
17+
import org.elasticsearch.common.io.stream.StreamInput;
18+
import org.elasticsearch.common.io.stream.StreamOutput;
19+
import org.elasticsearch.xcontent.ToXContent;
20+
21+
import java.io.IOException;
22+
import java.util.Collections;
23+
import java.util.Iterator;
24+
import java.util.Objects;
25+
26+
/**
27+
* Secrets that are stored in cluster state
28+
*
29+
* <p>Cluster state secrets are initially loaded on each node, from a file on disk,
30+
* in the format defined by {@link org.elasticsearch.common.settings.LocallyMountedSecrets}.
31+
* Once the cluster is running, the master node watches the file for changes. This class
32+
* propagates changes in the file-based secure settings from the master node out to other
33+
* nodes.
34+
*
35+
* <p>Since the master node should always have settings on disk, we don't need to
36+
* persist this class to saved cluster state, either on disk or in the cloud. Therefore,
37+
* we have defined this {@link ClusterState.Custom} as a private custom object. Additionally,
38+
* we don't want to ever write this class's secrets out in a client response, so
39+
* {@link #toXContentChunked(ToXContent.Params)} returns an empty iterator.
40+
*/
41+
public class ClusterSecrets extends AbstractNamedDiffable<ClusterState.Custom> implements ClusterState.Custom {
42+
43+
/**
44+
* The name for this data class
45+
*
46+
* <p>This name will be used to identify this {@link org.elasticsearch.common.io.stream.NamedWriteable} in cluster
47+
* state. See {@link #getWriteableName()}.
48+
*/
49+
public static final String TYPE = "cluster_state_secrets";
50+
51+
private final SecureClusterStateSettings settings;
52+
private final long version;
53+
54+
public ClusterSecrets(long version, SecureClusterStateSettings settings) {
55+
this.version = version;
56+
this.settings = settings;
57+
}
58+
59+
public ClusterSecrets(StreamInput in) throws IOException {
60+
this.version = in.readLong();
61+
this.settings = new SecureClusterStateSettings(in);
62+
}
63+
64+
public SecureSettings getSettings() {
65+
return new SecureClusterStateSettings(settings);
66+
}
67+
68+
public long getVersion() {
69+
return version;
70+
}
71+
72+
@Override
73+
public boolean isPrivate() {
74+
return true;
75+
}
76+
77+
@Override
78+
public Iterator<? extends ToXContent> toXContentChunked(ToXContent.Params params) {
79+
// never render this to the user
80+
return Collections.emptyIterator();
81+
}
82+
83+
@Override
84+
public String getWriteableName() {
85+
return TYPE;
86+
}
87+
88+
@Override
89+
public TransportVersion getMinimalSupportedVersion() {
90+
return TransportVersions.V_8_9_X;
91+
}
92+
93+
@Override
94+
public void writeTo(StreamOutput out) throws IOException {
95+
out.writeLong(version);
96+
settings.writeTo(out);
97+
}
98+
99+
public static NamedDiff<ClusterState.Custom> readDiffFrom(StreamInput in) throws IOException {
100+
return readDiffFrom(ClusterState.Custom.class, TYPE, in);
101+
}
102+
103+
@Override
104+
public String toString() {
105+
return "ClusterStateSecrets{[all secret]}";
106+
}
107+
108+
@Override
109+
public boolean equals(Object o) {
110+
if (this == o) return true;
111+
if (o == null || getClass() != o.getClass()) return false;
112+
ClusterSecrets that = (ClusterSecrets) o;
113+
return version == that.version && Objects.equals(settings, that.settings);
114+
}
115+
116+
@Override
117+
public int hashCode() {
118+
return Objects.hash(settings, version);
119+
}
120+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
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", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.common.settings;
11+
12+
import org.elasticsearch.TransportVersion;
13+
import org.elasticsearch.TransportVersions;
14+
import org.elasticsearch.cluster.AbstractNamedDiffable;
15+
import org.elasticsearch.cluster.NamedDiff;
16+
import org.elasticsearch.cluster.metadata.Metadata;
17+
import org.elasticsearch.common.io.stream.StreamInput;
18+
import org.elasticsearch.common.io.stream.StreamOutput;
19+
import org.elasticsearch.xcontent.ToXContent;
20+
21+
import java.io.IOException;
22+
import java.util.Collections;
23+
import java.util.EnumSet;
24+
import java.util.Iterator;
25+
import java.util.Objects;
26+
27+
/**
28+
* Secrets that are stored in project state as a {@link Metadata.ProjectCustom}
29+
*
30+
* <p>Project state secrets are initially loaded on the master node, from a file on disk.
31+
* Once the cluster is running, the master node watches the file for changes. This class
32+
* propagates changes in the file-based secure settings for each project from the master
33+
* node out to other nodes using the transport protocol.
34+
*
35+
* <p>Since the master node should always have settings on disk, we don't need to
36+
* persist this class to saved cluster state, either on disk or in the cloud. Therefore,
37+
* we have defined this {@link Metadata.ProjectCustom} as a "private custom" object by not
38+
* serializing its content in {@link #toXContentChunked(ToXContent.Params)}.
39+
*/
40+
public class ProjectSecrets extends AbstractNamedDiffable<Metadata.ProjectCustom> implements Metadata.ProjectCustom {
41+
42+
public static final String TYPE = "project_state_secrets";
43+
44+
private final SecureClusterStateSettings settings;
45+
46+
public ProjectSecrets(SecureClusterStateSettings settings) {
47+
this.settings = settings;
48+
}
49+
50+
public ProjectSecrets(StreamInput in) throws IOException {
51+
this.settings = new SecureClusterStateSettings(in);
52+
}
53+
54+
public SecureSettings getSettings() {
55+
return new SecureClusterStateSettings(settings);
56+
}
57+
58+
@Override
59+
public Iterator<? extends ToXContent> toXContentChunked(ToXContent.Params params) {
60+
// No need to persist in index or return to user, so do not serialize the secrets
61+
return Collections.emptyIterator();
62+
}
63+
64+
@Override
65+
public String getWriteableName() {
66+
return TYPE;
67+
}
68+
69+
@Override
70+
public TransportVersion getMinimalSupportedVersion() {
71+
return TransportVersions.MULTI_PROJECT;
72+
}
73+
74+
@Override
75+
public void writeTo(StreamOutput out) throws IOException {
76+
settings.writeTo(out);
77+
}
78+
79+
public static NamedDiff<Metadata.ProjectCustom> readDiffFrom(StreamInput in) throws IOException {
80+
return readDiffFrom(Metadata.ProjectCustom.class, TYPE, in);
81+
}
82+
83+
@Override
84+
public String toString() {
85+
return "ProjectSecrets{[all secret]}";
86+
}
87+
88+
@Override
89+
public boolean equals(Object o) {
90+
if (this == o) return true;
91+
if (o == null || getClass() != o.getClass()) return false;
92+
ProjectSecrets that = (ProjectSecrets) o;
93+
return Objects.equals(settings, that.settings);
94+
}
95+
96+
@Override
97+
public int hashCode() {
98+
return Objects.hash(settings);
99+
}
100+
101+
@Override
102+
public EnumSet<Metadata.XContentContext> context() {
103+
return EnumSet.noneOf(Metadata.XContentContext.class);
104+
}
105+
}

0 commit comments

Comments
 (0)