Skip to content

Commit c9d77d2

Browse files
authored
Watcher: Never return credentials after watch creation... (elastic/x-pack-elasticsearch#3581)
... yet support updates. This commit introduces a few changes of how watches are put. The GET Watch API will never return credentials like basic auth passwords, but a placeholder instead now. If the watcher is enabled to encrypt sensitive settings, then the original encrypted value is returned otherwise a "::es_redacted::" place holder. There have been several Put Watch API changes. The API now internally uses the Update API and versioning. This has several implications. First if no version is supplied, we assume an initial creation. This will work as before, however if a credential is marked as redacted we will reject storing the watch, so users do not accidentally store the wrong watch. The watch xcontent parser now has an additional methods to tell the caller if redacted passwords have been found. Based on this information an error can be thrown. If the user now wants to store a watch that contains a password marked as redacted, this password will not be part of the toXContent representation of the watch and in combinatination with update request the existing password will be merged in. If the encrypted password is supplied this one will be stored. The serialization for GetWatchResponse/PutWatchRequest has changed. The version checks for this will be put into the 6.x branch. The Watcher UI now needs specify the version, when it wants to store a watch. This also prevents last-write-wins scenarios and is the reason why the put/get watch response now contains the internal version. relates elastic/x-pack-elasticsearch#3089 Original commit: elastic/x-pack-elasticsearch@bb63be9
1 parent 56c761f commit c9d77d2

File tree

45 files changed

+625
-175
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+625
-175
lines changed

docs/en/rest-api/watcher/ack-watch.asciidoc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ The action state of a newly-created watch is `awaits_successful_execution`:
8686
--------------------------------------------------
8787
{
8888
"found": true,
89+
"_version": 1,
8990
"_id": "my_watch",
9091
"status": {
9192
"version": 1,
@@ -129,6 +130,7 @@ and the action is now in `ackable` state:
129130
{
130131
"found": true,
131132
"_id": "my_watch",
133+
"_version": 2,
132134
"status": {
133135
"version": 2,
134136
"actions": {
@@ -177,6 +179,7 @@ GET _xpack/watcher/watch/my_watch
177179
{
178180
"found": true,
179181
"_id": "my_watch",
182+
"_version": 3,
180183
"status": {
181184
"version": 3,
182185
"actions": {

docs/en/rest-api/watcher/activate-watch.asciidoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ GET _xpack/watcher/watch/my_watch
4141
{
4242
"found": true,
4343
"_id": "my_watch",
44+
"_version": 1,
4445
"status": {
4546
"state" : {
4647
"active" : false,

docs/en/rest-api/watcher/deactivate-watch.asciidoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ GET _xpack/watcher/watch/my_watch
4040
{
4141
"found": true,
4242
"_id": "my_watch",
43+
"_version": 1,
4344
"status": {
4445
"state" : {
4546
"active" : true,

docs/en/rest-api/watcher/get-watch.asciidoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ Response:
4141
{
4242
"found": true,
4343
"_id": "my_watch",
44+
"_version": 1,
4445
"status": { <1>
4546
"version": 1,
4647
"state": {

plugin/core/src/main/java/org/elasticsearch/xpack/core/watcher/client/WatchSourceBuilder.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.elasticsearch.xpack.core.watcher.input.Input;
2222
import org.elasticsearch.xpack.core.watcher.input.none.NoneInput;
2323
import org.elasticsearch.xpack.core.watcher.support.Exceptions;
24+
import org.elasticsearch.xpack.core.watcher.support.xcontent.WatcherParams;
2425
import org.elasticsearch.xpack.core.watcher.support.xcontent.XContentSource;
2526
import org.elasticsearch.xpack.core.watcher.transform.Transform;
2627
import org.elasticsearch.xpack.core.watcher.trigger.Trigger;
@@ -175,7 +176,8 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
175176
*/
176177
public final BytesReference buildAsBytes(XContentType contentType) {
177178
try {
178-
return XContentHelper.toXContent(this, contentType, false);
179+
WatcherParams params = WatcherParams.builder().hideSecrets(false).build();
180+
return XContentHelper.toXContent(this, contentType, params,false);
179181
} catch (Exception e) {
180182
throw new ElasticsearchException("Failed to build ToXContent", e);
181183
}

plugin/core/src/main/java/org/elasticsearch/xpack/core/watcher/crypto/CryptoService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public class CryptoService extends AbstractComponent {
3939
public static final String KEY_ALGO = "HmacSHA512";
4040
public static final int KEY_SIZE = 1024;
4141

42-
static final String ENCRYPTED_TEXT_PREFIX = "::es_encrypted::";
42+
public static final String ENCRYPTED_TEXT_PREFIX = "::es_encrypted::";
4343

4444
// the encryption used in this class was picked when Java 7 was still the min. supported
4545
// version. The use of counter mode was chosen to simplify the need to deal with padding

plugin/core/src/main/java/org/elasticsearch/xpack/core/watcher/support/xcontent/WatcherParams.java

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package org.elasticsearch.xpack.core.watcher.support.xcontent;
77

88
import org.elasticsearch.common.xcontent.ToXContent;
9+
import org.elasticsearch.xpack.core.watcher.watch.Watch;
910

1011
import java.util.HashMap;
1112
import java.util.Map;
@@ -17,9 +18,9 @@ public class WatcherParams extends ToXContent.DelegatingMapParams {
1718

1819
public static final WatcherParams HIDE_SECRETS = WatcherParams.builder().hideSecrets(true).build();
1920

20-
static final String HIDE_SECRETS_KEY = "hide_secrets";
21-
public static final String HIDE_HEADERS = "hide_headers";
22-
static final String DEBUG_KEY = "debug";
21+
private static final String HIDE_SECRETS_KEY = "hide_secrets";
22+
private static final String HIDE_HEADERS = "hide_headers";
23+
private static final String DEBUG_KEY = "debug";
2324

2425
public static boolean hideSecrets(ToXContent.Params params) {
2526
return wrap(params).hideSecrets();
@@ -29,19 +30,23 @@ public static boolean debug(ToXContent.Params params) {
2930
return wrap(params).debug();
3031
}
3132

33+
public static boolean hideHeaders(ToXContent.Params params) {
34+
return wrap(params).hideHeaders();
35+
}
36+
3237
private WatcherParams(Map<String, String> params, ToXContent.Params delegate) {
3338
super(params, delegate);
3439
}
3540

36-
public boolean hideSecrets() {
37-
return paramAsBoolean(HIDE_SECRETS_KEY, false);
41+
private boolean hideSecrets() {
42+
return paramAsBoolean(HIDE_SECRETS_KEY, true);
3843
}
3944

40-
public boolean debug() {
45+
private boolean debug() {
4146
return paramAsBoolean(DEBUG_KEY, false);
4247
}
4348

44-
public boolean hideHeaders() {
49+
private boolean hideHeaders() {
4550
return paramAsBoolean(HIDE_HEADERS, true);
4651
}
4752

@@ -83,6 +88,11 @@ public Builder debug(boolean debug) {
8388
return this;
8489
}
8590

91+
public Builder includeStatus(boolean includeStatus) {
92+
params.put(Watch.INCLUDE_STATUS_KEY, String.valueOf(includeStatus));
93+
return this;
94+
}
95+
8696
public Builder put(String key, Object value) {
8797
params.put(key, String.valueOf(value));
8898
return this;

plugin/core/src/main/java/org/elasticsearch/xpack/core/watcher/support/xcontent/WatcherXContentParser.java

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import org.apache.lucene.util.BytesRef;
99
import org.elasticsearch.ElasticsearchException;
10+
import org.elasticsearch.ElasticsearchParseException;
1011
import org.elasticsearch.common.Nullable;
1112
import org.elasticsearch.common.xcontent.DeprecationHandler;
1213
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
@@ -33,41 +34,47 @@
3334
*/
3435
public class WatcherXContentParser implements XContentParser {
3536

36-
public static Secret secret(XContentParser parser) throws IOException {
37-
char[] chars = parser.text().toCharArray();
38-
if (parser instanceof WatcherXContentParser) {
39-
WatcherXContentParser watcherParser = (WatcherXContentParser) parser;
40-
if (watcherParser.cryptoService != null) {
41-
chars = watcherParser.cryptoService.encrypt(chars);
42-
}
43-
}
44-
return new Secret(chars);
45-
}
37+
public static final String REDACTED_PASSWORD = "::es_redacted::";
4638

4739
public static Secret secretOrNull(XContentParser parser) throws IOException {
4840
String text = parser.textOrNull();
4941
if (text == null) {
5042
return null;
5143
}
52-
char[] chars = parser.text().toCharArray();
44+
45+
char[] chars = text.toCharArray();
46+
boolean isEncryptedAlready = text.startsWith(CryptoService.ENCRYPTED_TEXT_PREFIX);
47+
if (isEncryptedAlready) {
48+
return new Secret(chars);
49+
}
50+
5351
if (parser instanceof WatcherXContentParser) {
5452
WatcherXContentParser watcherParser = (WatcherXContentParser) parser;
55-
if (watcherParser.cryptoService != null) {
56-
chars = watcherParser.cryptoService.encrypt(text.toCharArray());
53+
if (REDACTED_PASSWORD.equals(text)) {
54+
if (watcherParser.allowRedactedPasswords) {
55+
return null;
56+
} else {
57+
throw new ElasticsearchParseException("found redacted password in field [{}]", parser.currentName());
58+
}
59+
} else if (watcherParser.cryptoService != null) {
60+
return new Secret(watcherParser.cryptoService.encrypt(chars));
5761
}
58-
return new Secret(chars);
5962
}
63+
6064
return new Secret(chars);
6165
}
6266

6367
private final DateTime parseTime;
6468
private final XContentParser parser;
6569
@Nullable private final CryptoService cryptoService;
70+
private final boolean allowRedactedPasswords;
6671

67-
public WatcherXContentParser(XContentParser parser, DateTime parseTime, @Nullable CryptoService cryptoService) {
72+
public WatcherXContentParser(XContentParser parser, DateTime parseTime, @Nullable CryptoService cryptoService,
73+
boolean allowRedactedPasswords) {
6874
this.parseTime = parseTime;
6975
this.parser = parser;
7076
this.cryptoService = cryptoService;
77+
this.allowRedactedPasswords = allowRedactedPasswords;
7178
}
7279

7380
public DateTime getParseDateTime() { return parseTime; }

plugin/core/src/main/java/org/elasticsearch/xpack/core/watcher/transport/actions/get/GetWatchResponse.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
*/
66
package org.elasticsearch.xpack.core.watcher.transport.actions.get;
77

8+
import org.elasticsearch.Version;
89
import org.elasticsearch.action.ActionResponse;
910
import org.elasticsearch.common.bytes.BytesReference;
1011
import org.elasticsearch.common.io.stream.StreamInput;
1112
import org.elasticsearch.common.io.stream.StreamOutput;
13+
import org.elasticsearch.common.lucene.uid.Versions;
1214
import org.elasticsearch.common.xcontent.XContentType;
1315
import org.elasticsearch.xpack.core.watcher.support.xcontent.XContentSource;
1416
import org.elasticsearch.xpack.core.watcher.watch.WatchStatus;
@@ -21,6 +23,7 @@ public class GetWatchResponse extends ActionResponse {
2123
private WatchStatus status;
2224
private boolean found = false;
2325
private XContentSource source;
26+
private long version;
2427

2528
public GetWatchResponse() {
2629
}
@@ -32,16 +35,18 @@ public GetWatchResponse(String id) {
3235
this.id = id;
3336
this.found = false;
3437
this.source = null;
38+
version = Versions.NOT_FOUND;
3539
}
3640

3741
/**
3842
* ctor for found watch
3943
*/
40-
public GetWatchResponse(String id, WatchStatus status, BytesReference source, XContentType contentType) {
44+
public GetWatchResponse(String id, long version, WatchStatus status, BytesReference source, XContentType contentType) {
4145
this.id = id;
4246
this.status = status;
4347
this.found = true;
4448
this.source = new XContentSource(source, contentType);
49+
this.version = version;
4550
}
4651

4752
public String getId() {
@@ -60,6 +65,10 @@ public XContentSource getSource() {
6065
return source;
6166
}
6267

68+
public long getVersion() {
69+
return version;
70+
}
71+
6372
@Override
6473
public void readFrom(StreamInput in) throws IOException {
6574
super.readFrom(in);
@@ -68,6 +77,7 @@ public void readFrom(StreamInput in) throws IOException {
6877
if (found) {
6978
status = WatchStatus.read(in);
7079
source = XContentSource.readFrom(in);
80+
version = in.readZLong();
7181
}
7282
}
7383

@@ -79,6 +89,7 @@ public void writeTo(StreamOutput out) throws IOException {
7989
if (found) {
8090
status.writeTo(out);
8191
XContentSource.writeTo(source, out);
92+
out.writeZLong(version);
8293
}
8394
}
8495
}

plugin/core/src/main/java/org/elasticsearch/xpack/core/watcher/transport/actions/put/PutWatchRequest.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.elasticsearch.common.bytes.BytesReference;
1313
import org.elasticsearch.common.io.stream.StreamInput;
1414
import org.elasticsearch.common.io.stream.StreamOutput;
15+
import org.elasticsearch.common.lucene.uid.Versions;
1516
import org.elasticsearch.common.xcontent.XContentType;
1617
import org.elasticsearch.xpack.core.watcher.client.WatchSourceBuilder;
1718
import org.elasticsearch.xpack.core.watcher.support.WatcherUtils;
@@ -28,14 +29,11 @@ public class PutWatchRequest extends ActionRequest {
2829
private BytesReference source;
2930
private boolean active = true;
3031
private XContentType xContentType = XContentType.JSON;
32+
private long version = Versions.MATCH_ANY;
3133

3234
public PutWatchRequest() {
3335
}
3436

35-
public PutWatchRequest(String id, WatchSourceBuilder source) {
36-
this(id, source.buildAsBytes(XContentType.JSON), XContentType.JSON);
37-
}
38-
3937
public PutWatchRequest(String id, BytesReference source, XContentType xContentType) {
4038
this.id = id;
4139
this.source = source;
@@ -48,6 +46,7 @@ public PutWatchRequest(StreamInput in) throws IOException {
4846
source = in.readBytesReference();
4947
active = in.readBoolean();
5048
xContentType = XContentType.readFrom(in);
49+
version = in.readZLong();
5150
}
5251

5352
@Override
@@ -57,6 +56,7 @@ public void writeTo(StreamOutput out) throws IOException {
5756
out.writeBytesReference(source);
5857
out.writeBoolean(active);
5958
xContentType.writeTo(out);
59+
out.writeZLong(version);
6060
}
6161

6262
/**
@@ -116,6 +116,14 @@ public XContentType xContentType() {
116116
return xContentType;
117117
}
118118

119+
public long getVersion() {
120+
return version;
121+
}
122+
123+
public void setVersion(long version) {
124+
this.version = version;
125+
}
126+
119127
@Override
120128
public ActionRequestValidationException validate() {
121129
ActionRequestValidationException validationException = null;

0 commit comments

Comments
 (0)