diff --git a/docs/reference/settings/notification-settings.asciidoc b/docs/reference/settings/notification-settings.asciidoc index 736e3cae8d68d..400b55271f975 100644 --- a/docs/reference/settings/notification-settings.asciidoc +++ b/docs/reference/settings/notification-settings.asciidoc @@ -37,8 +37,14 @@ required. For more information, see {xpack-ref}/encrypting-data.html[Encrypting sensitive data in {watcher}]. `xpack.watcher.history.cleaner_service.enabled`:: +ifdef::asciidoctor[] +added:[6.3.0,Default changed to `true`.] +deprecated:[7.0.0,Watcher history indices are now managed by the `watch-history-ilm-policy` ILM policy] +endif::[] +ifndef::asciidoctor[] added[6.3.0,Default changed to `true`.] deprecated[7.0.0,Watcher history indices are now managed by the `watch-history-ilm-policy` ILM policy] +endif::[] + Set to `true` (default) to enable the cleaner service. If this setting is `true`, the `xpack.monitoring.enabled` setting must also be set to `true` with diff --git a/modules/analysis-common/src/test/resources/rest-api-spec/test/search.query/70_intervals.yml b/modules/analysis-common/src/test/resources/rest-api-spec/test/search.query/70_intervals.yml index dd8125f9fc27b..35a611d13f359 100644 --- a/modules/analysis-common/src/test/resources/rest-api-spec/test/search.query/70_intervals.yml +++ b/modules/analysis-common/src/test/resources/rest-api-spec/test/search.query/70_intervals.yml @@ -23,8 +23,8 @@ setup: --- "Test use_field": - skip: - version: " - 7.0.99" - reason: "Implemented in 7.1" + version: " - 7.1.99" + reason: "Implemented in 7.2" - do: search: index: test diff --git a/modules/mapper-extras/src/test/resources/rest-api-spec/test/dense-vector/10_basic.yml b/modules/mapper-extras/src/test/resources/rest-api-spec/test/dense-vector/10_basic.yml index e5db535b69b80..320d9ce1fa331 100644 --- a/modules/mapper-extras/src/test/resources/rest-api-spec/test/dense-vector/10_basic.yml +++ b/modules/mapper-extras/src/test/resources/rest-api-spec/test/dense-vector/10_basic.yml @@ -1,8 +1,8 @@ setup: - skip: features: headers - version: " - 7.0.99" - reason: "dense_vector functions were introduced in 7.1.0" + version: " - 7.1.99" + reason: "dense_vector functions were introduced in 7.2.0" - do: indices.create: diff --git a/modules/mapper-extras/src/test/resources/rest-api-spec/test/dense-vector/20_special_cases.yml b/modules/mapper-extras/src/test/resources/rest-api-spec/test/dense-vector/20_special_cases.yml index 0520fe792e175..0df3a8fcaadc8 100644 --- a/modules/mapper-extras/src/test/resources/rest-api-spec/test/dense-vector/20_special_cases.yml +++ b/modules/mapper-extras/src/test/resources/rest-api-spec/test/dense-vector/20_special_cases.yml @@ -1,8 +1,8 @@ setup: - skip: features: headers - version: " - 7.0.99" - reason: "dense_vector functions were introduced in 7.1.0" + version: " - 7.1.99" + reason: "dense_vector functions were introduced in 7.2.0" - do: indices.create: diff --git a/modules/mapper-extras/src/test/resources/rest-api-spec/test/search-as-you-type/10_basic.yml b/modules/mapper-extras/src/test/resources/rest-api-spec/test/search-as-you-type/10_basic.yml index 3ddcd89347fcb..ffe05097748a6 100644 --- a/modules/mapper-extras/src/test/resources/rest-api-spec/test/search-as-you-type/10_basic.yml +++ b/modules/mapper-extras/src/test/resources/rest-api-spec/test/search-as-you-type/10_basic.yml @@ -1,7 +1,7 @@ setup: - skip: - version: " - 7.0.99" - reason: "added in 7.1.0" + version: " - 7.1.99" + reason: "added in 7.2.0" - do: indices.create: diff --git a/modules/mapper-extras/src/test/resources/rest-api-spec/test/search-as-you-type/20_highlighting.yml b/modules/mapper-extras/src/test/resources/rest-api-spec/test/search-as-you-type/20_highlighting.yml index 82a599ce686c2..b09bc8418c98a 100644 --- a/modules/mapper-extras/src/test/resources/rest-api-spec/test/search-as-you-type/20_highlighting.yml +++ b/modules/mapper-extras/src/test/resources/rest-api-spec/test/search-as-you-type/20_highlighting.yml @@ -1,7 +1,7 @@ setup: - skip: - version: " - 7.0.99" - reason: "added in 7.1.0" + version: " - 7.1.99" + reason: "added in 7.2.0" - do: indices.create: diff --git a/modules/mapper-extras/src/test/resources/rest-api-spec/test/sparse-vector/10_basic.yml b/modules/mapper-extras/src/test/resources/rest-api-spec/test/sparse-vector/10_basic.yml index 142a80291aebf..b1330bbe852d3 100644 --- a/modules/mapper-extras/src/test/resources/rest-api-spec/test/sparse-vector/10_basic.yml +++ b/modules/mapper-extras/src/test/resources/rest-api-spec/test/sparse-vector/10_basic.yml @@ -1,8 +1,8 @@ setup: - skip: features: headers - version: " - 7.0.99" - reason: "sparse_vector functions were introduced in 7.1.0" + version: " - 7.1.99" + reason: "sparse_vector functions were introduced in 7.2.0" - do: indices.create: diff --git a/modules/mapper-extras/src/test/resources/rest-api-spec/test/sparse-vector/20_special_cases.yml b/modules/mapper-extras/src/test/resources/rest-api-spec/test/sparse-vector/20_special_cases.yml index 3b128ccaf802e..d2ae0bb1a7549 100644 --- a/modules/mapper-extras/src/test/resources/rest-api-spec/test/sparse-vector/20_special_cases.yml +++ b/modules/mapper-extras/src/test/resources/rest-api-spec/test/sparse-vector/20_special_cases.yml @@ -1,8 +1,8 @@ setup: - skip: features: headers - version: " - 7.0.99" - reason: "sparse_vector functions were introduced in 7.1.0" + version: " - 7.1.99" + reason: "sparse_vector functions were introduced in 7.2.0" - do: indices.create: diff --git a/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java b/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java index 44f8d4e145bf0..50eee32810adc 100644 --- a/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java +++ b/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java @@ -988,7 +988,7 @@ public void testClosedIndices() throws Exception { closeIndex(index); } - if (getOldClusterVersion().onOrAfter(Version.V_7_1_0)) { + if (getOldClusterVersion().onOrAfter(Version.V_7_2_0)) { ensureGreenLongWait(index); assertClosedIndex(index, true); } else { diff --git a/qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/RecoveryIT.java b/qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/RecoveryIT.java index 85102209e60f4..bbc6d27472467 100644 --- a/qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/RecoveryIT.java +++ b/qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/RecoveryIT.java @@ -364,7 +364,7 @@ public void testRecoveryClosedIndex() throws Exception { } final Version indexVersionCreated = indexVersionCreated(indexName); - if (indexVersionCreated.onOrAfter(Version.V_7_1_0)) { + if (indexVersionCreated.onOrAfter(Version.V_7_2_0)) { // index was created on a version that supports the replication of closed indices, // so we expect the index to be closed and replicated ensureGreen(indexName); @@ -393,7 +393,7 @@ public void testCloseIndexDuringRollingUpgrade() throws Exception { closeIndex(indexName); } - if (minimumNodeVersion.onOrAfter(Version.V_7_1_0)) { + if (minimumNodeVersion.onOrAfter(Version.V_7_2_0)) { // index is created on a version that supports the replication of closed indices, // so we expect the index to be closed and replicated ensureGreen(indexName); diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/cat.indices/10_basic.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/cat.indices/10_basic.yml index 4b2ca68102fb9..4da58c2ed4d0d 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/cat.indices/10_basic.yml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/cat.indices/10_basic.yml @@ -50,10 +50,10 @@ ) $/ --- -"Test cat indices output for closed index (pre 7.1.0)": +"Test cat indices output for closed index (pre 7.2.0)": - skip: - version: "7.1.0 - " - reason: "closed indices are replicated starting version 7.1.0" + version: "7.2.0 - " + reason: "closed indices are replicated starting version 7.2.0" - do: indices.create: @@ -93,8 +93,8 @@ --- "Test cat indices output for closed index": - skip: - version: " - 7.0.99" - reason: "closed indices are replicated starting version 7.1.0" + version: " - 7.1.99" + reason: "closed indices are replicated starting version 7.2.0" - do: indices.create: diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/cat.recovery/10_basic.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/cat.recovery/10_basic.yml index c6a752c2b7e1e..ef1272322e9af 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/cat.recovery/10_basic.yml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/cat.recovery/10_basic.yml @@ -79,8 +79,8 @@ --- "Test cat recovery output for closed index": - skip: - version: " - 7.0.99" - reason: closed indices are replicated starting version 7.1.0 + version: " - 7.1.99" + reason: closed indices are replicated starting version 7.2.0 - do: indices.create: diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/cat.shards/10_basic.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/cat.shards/10_basic.yml index 884cf45c84ceb..6632d912cd57e 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/cat.shards/10_basic.yml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/cat.shards/10_basic.yml @@ -1,8 +1,8 @@ --- "Help": - skip: - version: " - 7.0.99" - reason: external refresh stats were added in 7.1.0 + version: " - 7.1.99" + reason: external refresh stats were added in 7.2.0 - do: cat.shards: help: true diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/cluster.allocation_explain/10_basic.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/cluster.allocation_explain/10_basic.yml index 732a53aeea4f8..f0fdca695b829 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/cluster.allocation_explain/10_basic.yml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/cluster.allocation_explain/10_basic.yml @@ -50,8 +50,8 @@ --- "Cluster shard allocation explanation test with a closed index": - skip: - version: " - 7.0.99" - reason: closed indices are replicated starting version 7.1.0 + version: " - 7.1.99" + reason: closed indices are replicated starting version 7.2.0 - do: indices.create: diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/cluster.health/10_basic.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/cluster.health/10_basic.yml index b06005b7ea765..9d589c1dc532e 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/cluster.health/10_basic.yml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/cluster.health/10_basic.yml @@ -130,10 +130,10 @@ - is_true: indices.test_index.shards --- -"cluster health with closed index (pre 7.1.0)": +"cluster health with closed index (pre 7.2.0)": - skip: - version: "7.1.0 - " - reason: "closed indices are replicated starting version 7.1.0" + version: "7.2.0 - " + reason: "closed indices are replicated starting version 7.2.0" - do: indices.create: @@ -206,8 +206,8 @@ --- "cluster health with closed index": - skip: - version: " - 7.0.99" - reason: "closed indices are replicated starting version 7.1.0" + version: " - 7.1.99" + reason: "closed indices are replicated starting version 7.2.0" - do: indices.create: diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/cluster.health/30_indices_options.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/cluster.health/30_indices_options.yml index 646d6ff6feaa4..93ca5da19d2ff 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/cluster.health/30_indices_options.yml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/cluster.health/30_indices_options.yml @@ -31,8 +31,8 @@ setup: --- "cluster health with expand_wildcards": - skip: - version: " - 7.0.99" - reason: "indices options has been introduced in cluster health request starting version 7.1.0" + version: " - 7.1.99" + reason: "indices options has been introduced in cluster health request starting version 7.2.0" - do: cluster.health: diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/cluster.stats/10_basic.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/cluster.stats/10_basic.yml index bdf651a6ec46b..c58d959c934ca 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/cluster.stats/10_basic.yml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/cluster.stats/10_basic.yml @@ -76,8 +76,8 @@ "get cluster stats returns packaging types": - skip: - version: " - 7.0.99" - reason: "packaging types are added for v7.1.0" + version: " - 7.1.99" + reason: "packaging types are added for v7.2.0" - do: cluster.stats: {} diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/field_caps/10_basic.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/field_caps/10_basic.yml index 3c59f5a929a8b..d6978fd62258e 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/field_caps/10_basic.yml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/field_caps/10_basic.yml @@ -298,8 +298,8 @@ setup: --- "Field caps with include_unmapped": - skip: - version: " - 7.0.99" - reason: include_unmapped has been added in 7.1.0 + version: " - 7.1.99" + reason: include_unmapped has been added in 7.2.0 - do: field_caps: diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/indices.flush/10_basic.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/indices.flush/10_basic.yml index 6bea9f0bf2319..213b3c587363c 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/indices.flush/10_basic.yml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/indices.flush/10_basic.yml @@ -54,8 +54,8 @@ --- "Flush parameters validation": - skip: - version: " - 7.0.99" - reason: flush parameters validation is introduced in 7.1.0 + version: " - 7.1.99" + reason: flush parameters validation is introduced in 7.2.0 - do: indices.create: index: test diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/indices.open/10_basic.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/indices.open/10_basic.yml index 6aaad4301d61b..a8ab29d9feb97 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/indices.open/10_basic.yml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/indices.open/10_basic.yml @@ -62,8 +62,8 @@ --- "Close index with wait_for_active_shards set to all": - skip: - version: " - 7.0.99" - reason: "closed indices are replicated starting version 7.1.0" + version: " - 7.1.99" + reason: "closed indices are replicated starting version 7.2.0" - do: indices.create: diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/indices.recovery/10_basic.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/indices.recovery/10_basic.yml index 08273ffcacef8..f227e076aa9c6 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/indices.recovery/10_basic.yml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/indices.recovery/10_basic.yml @@ -42,8 +42,8 @@ --- "Indices recovery test for closed index": - skip: - version: " - 7.0.99" - reason: closed indices are replicated starting version 7.1.0 + version: " - 7.1.99" + reason: closed indices are replicated starting version 7.2.0 - do: indices.create: diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/indices.stats/30_segments.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/indices.stats/30_segments.yml index 933bfc5c61c2c..2d4d804063220 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/indices.stats/30_segments.yml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/indices.stats/30_segments.yml @@ -20,7 +20,7 @@ setup: "Segment Stats": - skip: - version: " - 7.0.99" + version: " - 7.1.99" reason: forbid_closed_indices is not supported in ealier version - do: diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/search.aggregation/230_composite.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/search.aggregation/230_composite.yml index 32593896fa43a..39492d282b758 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/search.aggregation/230_composite.yml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/search.aggregation/230_composite.yml @@ -487,8 +487,8 @@ setup: --- "Missing source": - skip: - version: " - 7.0.99" - reason: null/empty sources disallowed in 7.1 + version: " - 7.1.99" + reason: null/empty sources disallowed in 7.2 - do: catch: /Composite \[sources\] cannot be null or empty/ @@ -516,8 +516,8 @@ setup: --- "Duplicate sources": - skip: - version: " - 7.0.99" - reason: duplicate names disallowed in 7.1 + version: " - 7.1.99" + reason: duplicate names disallowed in 7.2 - do: catch: /Composite source names must be unique, found duplicates[:] \[keyword\]/ diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/search/230_interval_query.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/search/230_interval_query.yml index 130e6d42504b4..46bf2cada8e4d 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/search/230_interval_query.yml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/search/230_interval_query.yml @@ -323,8 +323,8 @@ setup: --- "Test overlapping": - skip: - version: " - 7.0.99" - reason: "Implemented in 7.1" + version: " - 7.1.99" + reason: "Implemented in 7.2" - do: search: index: test @@ -346,8 +346,8 @@ setup: --- "Test before": - skip: - version: " - 7.0.99" - reason: "Implemented in 7.1" + version: " - 7.1.99" + reason: "Implemented in 7.2" - do: search: index: test @@ -366,8 +366,8 @@ setup: --- "Test after": - skip: - version: " - 7.0.99" - reason: "Implemented in 7.1" + version: " - 7.1.99" + reason: "Implemented in 7.2" - do: search: index: test diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/search/250_distance_feature.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/search/250_distance_feature.yml index b1bea82918446..bafb7d52c7186 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/search/250_distance_feature.yml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/search/250_distance_feature.yml @@ -1,7 +1,7 @@ setup: - skip: - version: " - 7.0.99" - reason: "Implemented in 7.1" + version: " - 7.1.99" + reason: "Implemented in 7.2" - do: indices.create: diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/search/310_match_bool_prefix.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/search/310_match_bool_prefix.yml index bcc28c7853425..aa6a5158b4795 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/search/310_match_bool_prefix.yml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/search/310_match_bool_prefix.yml @@ -1,7 +1,7 @@ setup: - skip: - version: " - 7.0.99" - reason: "added in 7.1.0" + version: " - 7.1.99" + reason: "added in 7.2.0" - do: indices.create: diff --git a/server/src/main/java/org/elasticsearch/Version.java b/server/src/main/java/org/elasticsearch/Version.java index e2a4d7a901579..e9234efca456a 100644 --- a/server/src/main/java/org/elasticsearch/Version.java +++ b/server/src/main/java/org/elasticsearch/Version.java @@ -128,12 +128,18 @@ public class Version implements Comparable, ToXContentFragment { public static final Version V_6_7_1 = new Version(V_6_7_1_ID, org.apache.lucene.util.Version.LUCENE_7_7_0); public static final int V_6_7_2_ID = 6070299; public static final Version V_6_7_2 = new Version(V_6_7_2_ID, org.apache.lucene.util.Version.LUCENE_7_7_0); + public static final int V_6_8_0_ID = 6080099; + public static final Version V_6_8_0 = new Version(V_6_8_0_ID, org.apache.lucene.util.Version.LUCENE_7_7_0); public static final int V_7_0_0_ID = 7000099; public static final Version V_7_0_0 = new Version(V_7_0_0_ID, org.apache.lucene.util.Version.LUCENE_8_0_0); public static final int V_7_0_1_ID = 7000199; public static final Version V_7_0_1 = new Version(V_7_0_1_ID, org.apache.lucene.util.Version.LUCENE_8_0_0); + public static final int V_7_0_2_ID = 7000299; + public static final Version V_7_0_2 = new Version(V_7_0_2_ID, org.apache.lucene.util.Version.LUCENE_8_0_0); public static final int V_7_1_0_ID = 7010099; public static final Version V_7_1_0 = new Version(V_7_1_0_ID, org.apache.lucene.util.Version.LUCENE_8_0_0); + public static final int V_7_2_0_ID = 7020099; + public static final Version V_7_2_0 = new Version(V_7_2_0_ID, org.apache.lucene.util.Version.LUCENE_8_0_0); public static final int V_8_0_0_ID = 8000099; public static final Version V_8_0_0 = new Version(V_8_0_0_ID, org.apache.lucene.util.Version.LUCENE_8_1_0); public static final Version CURRENT = V_8_0_0; @@ -152,12 +158,18 @@ public static Version fromId(int id) { switch (id) { case V_8_0_0_ID: return V_8_0_0; + case V_7_2_0_ID: + return V_7_2_0; case V_7_1_0_ID: return V_7_1_0; + case V_7_0_2_ID: + return V_7_0_2; case V_7_0_1_ID: return V_7_0_1; case V_7_0_0_ID: return V_7_0_0; + case V_6_8_0_ID: + return V_6_8_0; case V_6_7_1_ID: return V_6_7_1; case V_6_7_2_ID: diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthRequest.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthRequest.java index 3663fa1b4aa08..375d9e271398f 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthRequest.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthRequest.java @@ -82,7 +82,7 @@ public ClusterHealthRequest(StreamInput in) throws IOException { waitForEvents = Priority.readFrom(in); } waitForNoInitializingShards = in.readBoolean(); - if (in.getVersion().onOrAfter(Version.V_7_1_0)) { + if (in.getVersion().onOrAfter(Version.V_7_2_0)) { indicesOptions = IndicesOptions.readIndicesOptions(in); } else { indicesOptions = IndicesOptions.lenientExpandOpen(); @@ -117,7 +117,7 @@ public void writeTo(StreamOutput out) throws IOException { Priority.writeTo(waitForEvents, out); } out.writeBoolean(waitForNoInitializingShards); - if (out.getVersion().onOrAfter(Version.V_7_1_0)) { + if (out.getVersion().onOrAfter(Version.V_7_2_0)) { indicesOptions.writeIndicesOptions(out); } } diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/close/CloseIndexRequest.java b/server/src/main/java/org/elasticsearch/action/admin/indices/close/CloseIndexRequest.java index d42a348ba1e01..351194f9a789c 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/indices/close/CloseIndexRequest.java +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/close/CloseIndexRequest.java @@ -118,7 +118,7 @@ public void readFrom(StreamInput in) throws IOException { super.readFrom(in); indices = in.readStringArray(); indicesOptions = IndicesOptions.readIndicesOptions(in); - if (in.getVersion().onOrAfter(Version.V_7_1_0)) { + if (in.getVersion().onOrAfter(Version.V_7_2_0)) { waitForActiveShards = ActiveShardCount.readFrom(in); } else { waitForActiveShards = ActiveShardCount.NONE; @@ -130,7 +130,7 @@ public void writeTo(StreamOutput out) throws IOException { super.writeTo(out); out.writeStringArray(indices); indicesOptions.writeIndicesOptions(out); - if (out.getVersion().onOrAfter(Version.V_7_1_0)) { + if (out.getVersion().onOrAfter(Version.V_7_2_0)) { waitForActiveShards.writeTo(out); } } diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/close/CloseIndexResponse.java b/server/src/main/java/org/elasticsearch/action/admin/indices/close/CloseIndexResponse.java index 9f93034479475..ea44ba7a8e46b 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/indices/close/CloseIndexResponse.java +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/close/CloseIndexResponse.java @@ -37,7 +37,7 @@ public CloseIndexResponse(final boolean acknowledged, final boolean shardsAcknow @Override public void readFrom(StreamInput in) throws IOException { super.readFrom(in); - if (in.getVersion().onOrAfter(Version.V_7_1_0)) { + if (in.getVersion().onOrAfter(Version.V_7_2_0)) { readShardsAcknowledged(in); } } @@ -45,7 +45,7 @@ public void readFrom(StreamInput in) throws IOException { @Override public void writeTo(StreamOutput out) throws IOException { super.writeTo(out); - if (out.getVersion().onOrAfter(Version.V_7_1_0)) { + if (out.getVersion().onOrAfter(Version.V_7_2_0)) { writeShardsAcknowledged(out); } } diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/stats/CommonStatsFlags.java b/server/src/main/java/org/elasticsearch/action/admin/indices/stats/CommonStatsFlags.java index e80a7cc8a1730..2cbadd0806cca 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/indices/stats/CommonStatsFlags.java +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/stats/CommonStatsFlags.java @@ -64,7 +64,7 @@ public CommonStatsFlags(StreamInput in) throws IOException { fieldDataFields = in.readStringArray(); completionDataFields = in.readStringArray(); includeSegmentFileSizes = in.readBoolean(); - if (in.getVersion().onOrAfter(Version.V_7_1_0)) { + if (in.getVersion().onOrAfter(Version.V_7_2_0)) { includeUnloadedSegments = in.readBoolean(); } } @@ -82,7 +82,7 @@ public void writeTo(StreamOutput out) throws IOException { out.writeStringArrayNullable(fieldDataFields); out.writeStringArrayNullable(completionDataFields); out.writeBoolean(includeSegmentFileSizes); - if (out.getVersion().onOrAfter(Version.V_7_1_0)) { + if (out.getVersion().onOrAfter(Version.V_7_2_0)) { out.writeBoolean(includeUnloadedSegments); } } diff --git a/server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesRequest.java b/server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesRequest.java index e34c55eb99fbc..75686cc6b0eae 100644 --- a/server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesRequest.java +++ b/server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesRequest.java @@ -84,7 +84,7 @@ public void readFrom(StreamInput in) throws IOException { indices = in.readStringArray(); indicesOptions = IndicesOptions.readIndicesOptions(in); mergeResults = in.readBoolean(); - if (in.getVersion().onOrAfter(Version.V_7_1_0)) { + if (in.getVersion().onOrAfter(Version.V_7_2_0)) { includeUnmapped = in.readBoolean(); } else { includeUnmapped = false; @@ -98,7 +98,7 @@ public void writeTo(StreamOutput out) throws IOException { out.writeStringArray(indices); indicesOptions.writeIndicesOptions(out); out.writeBoolean(mergeResults); - if (out.getVersion().onOrAfter(Version.V_7_1_0)) { + if (out.getVersion().onOrAfter(Version.V_7_2_0)) { out.writeBoolean(includeUnmapped); } } diff --git a/server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesResponse.java b/server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesResponse.java index 7999e1e70adf6..e480d5dc69a58 100644 --- a/server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesResponse.java +++ b/server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesResponse.java @@ -108,7 +108,7 @@ public Map getField(String field) { @Override public void readFrom(StreamInput in) throws IOException { super.readFrom(in); - if (in.getVersion().onOrAfter(Version.V_7_1_0)) { + if (in.getVersion().onOrAfter(Version.V_7_2_0)) { indices = in.readStringArray(); } else { indices = Strings.EMPTY_ARRAY; @@ -124,7 +124,7 @@ private static Map readField(StreamInput in) throws I @Override public void writeTo(StreamOutput out) throws IOException { super.writeTo(out); - if (out.getVersion().onOrAfter(Version.V_7_1_0)) { + if (out.getVersion().onOrAfter(Version.V_7_2_0)) { out.writeStringArray(indices); } out.writeMap(responseMap, StreamOutput::writeString, FieldCapabilitiesResponse::writeField); diff --git a/server/src/main/java/org/elasticsearch/cluster/metadata/IndexMetaData.java b/server/src/main/java/org/elasticsearch/cluster/metadata/IndexMetaData.java index 5af1dc9b508ac..412e16e14a3a5 100644 --- a/server/src/main/java/org/elasticsearch/cluster/metadata/IndexMetaData.java +++ b/server/src/main/java/org/elasticsearch/cluster/metadata/IndexMetaData.java @@ -711,7 +711,7 @@ private static class IndexMetaDataDiff implements Diff { version = in.readLong(); mappingVersion = in.readVLong(); settingsVersion = in.readVLong(); - if (in.getVersion().onOrAfter(Version.V_7_1_0)) { + if (in.getVersion().onOrAfter(Version.V_7_2_0)) { aliasesVersion = in.readVLong(); } else { aliasesVersion = 1; @@ -738,7 +738,7 @@ public void writeTo(StreamOutput out) throws IOException { out.writeLong(version); out.writeVLong(mappingVersion); out.writeVLong(settingsVersion); - if (out.getVersion().onOrAfter(Version.V_7_1_0)) { + if (out.getVersion().onOrAfter(Version.V_7_2_0)) { out.writeVLong(aliasesVersion); } out.writeByte(state.id); @@ -776,7 +776,7 @@ public static IndexMetaData readFrom(StreamInput in) throws IOException { builder.version(in.readLong()); builder.mappingVersion(in.readVLong()); builder.settingsVersion(in.readVLong()); - if (in.getVersion().onOrAfter(Version.V_7_1_0)) { + if (in.getVersion().onOrAfter(Version.V_7_2_0)) { builder.aliasesVersion(in.readVLong()); } builder.setRoutingNumShards(in.readInt()); @@ -818,7 +818,7 @@ public void writeTo(StreamOutput out) throws IOException { out.writeLong(version); out.writeVLong(mappingVersion); out.writeVLong(settingsVersion); - if (out.getVersion().onOrAfter(Version.V_7_1_0)) { + if (out.getVersion().onOrAfter(Version.V_7_2_0)) { out.writeVLong(aliasesVersion); } out.writeInt(routingNumShards); @@ -1414,8 +1414,8 @@ public static IndexMetaData fromXContent(XContentParser parser) throws IOExcepti if (Assertions.ENABLED && Version.indexCreated(builder.settings).onOrAfter(Version.V_6_5_0)) { assert settingsVersion : "settings version should be present for indices created on or after 6.5.0"; } - if (Assertions.ENABLED && Version.indexCreated(builder.settings).onOrAfter(Version.V_7_1_0)) { - assert aliasesVersion : "aliases version should be present for indices created on or after 7.1.0"; + if (Assertions.ENABLED && Version.indexCreated(builder.settings).onOrAfter(Version.V_7_2_0)) { + assert aliasesVersion : "aliases version should be present for indices created on or after 7.2.0"; } return builder.build(); } diff --git a/server/src/main/java/org/elasticsearch/cluster/metadata/MetaDataIndexStateService.java b/server/src/main/java/org/elasticsearch/cluster/metadata/MetaDataIndexStateService.java index a004d0a5a2324..80be71dadd3d6 100644 --- a/server/src/main/java/org/elasticsearch/cluster/metadata/MetaDataIndexStateService.java +++ b/server/src/main/java/org/elasticsearch/cluster/metadata/MetaDataIndexStateService.java @@ -402,7 +402,7 @@ static ClusterState closeRoutingTable(final ClusterState currentState, // Remove the index routing table of closed indices if the cluster is in a mixed version // that does not support the replication of closed indices - final boolean removeRoutingTable = currentState.nodes().getMinNodeVersion().before(Version.V_7_1_0); + final boolean removeRoutingTable = currentState.nodes().getMinNodeVersion().before(Version.V_7_2_0); final MetaData.Builder metadata = MetaData.builder(currentState.metaData()); final ClusterBlocks.Builder blocks = ClusterBlocks.builder().blocks(currentState.blocks()); diff --git a/server/src/main/java/org/elasticsearch/index/engine/ReadOnlyEngine.java b/server/src/main/java/org/elasticsearch/index/engine/ReadOnlyEngine.java index 7b47d60437fe1..5cf62dbbf7c74 100644 --- a/server/src/main/java/org/elasticsearch/index/engine/ReadOnlyEngine.java +++ b/server/src/main/java/org/elasticsearch/index/engine/ReadOnlyEngine.java @@ -130,7 +130,7 @@ protected void ensureMaxSeqNoEqualsToGlobalCheckpoint(final SeqNoStats seqNoStat // created after the refactoring of the Close Index API and its TransportVerifyShardBeforeCloseAction // that guarantee that all operations have been flushed to Lucene. final Version indexVersionCreated = engineConfig.getIndexSettings().getIndexVersionCreated(); - if (indexVersionCreated.onOrAfter(Version.V_7_1_0) || + if (indexVersionCreated.onOrAfter(Version.V_7_2_0) || (seqNoStats.getGlobalCheckpoint() != SequenceNumbers.UNASSIGNED_SEQ_NO && indexVersionCreated.onOrAfter(Version.V_6_7_0))) { if (seqNoStats.getMaxSeqNo() != seqNoStats.getGlobalCheckpoint()) { throw new IllegalStateException("Maximum sequence number [" + seqNoStats.getMaxSeqNo() diff --git a/server/src/main/java/org/elasticsearch/index/query/IntervalsSourceProvider.java b/server/src/main/java/org/elasticsearch/index/query/IntervalsSourceProvider.java index 6aa8f2d700e7e..e551654af9a76 100644 --- a/server/src/main/java/org/elasticsearch/index/query/IntervalsSourceProvider.java +++ b/server/src/main/java/org/elasticsearch/index/query/IntervalsSourceProvider.java @@ -120,7 +120,7 @@ public Match(StreamInput in) throws IOException { this.ordered = in.readBoolean(); this.analyzer = in.readOptionalString(); this.filter = in.readOptionalWriteable(IntervalFilter::new); - if (in.getVersion().onOrAfter(Version.V_7_1_0)) { + if (in.getVersion().onOrAfter(Version.V_7_2_0)) { this.useField = in.readOptionalString(); } else { @@ -186,7 +186,7 @@ public void writeTo(StreamOutput out) throws IOException { out.writeBoolean(ordered); out.writeOptionalString(analyzer); out.writeOptionalWriteable(filter); - if (out.getVersion().onOrAfter(Version.V_7_1_0)) { + if (out.getVersion().onOrAfter(Version.V_7_2_0)) { out.writeOptionalString(useField); } } diff --git a/server/src/main/java/org/elasticsearch/index/refresh/RefreshStats.java b/server/src/main/java/org/elasticsearch/index/refresh/RefreshStats.java index c77e903373873..6488de5df0aca 100644 --- a/server/src/main/java/org/elasticsearch/index/refresh/RefreshStats.java +++ b/server/src/main/java/org/elasticsearch/index/refresh/RefreshStats.java @@ -52,7 +52,7 @@ public RefreshStats() { public RefreshStats(StreamInput in) throws IOException { total = in.readVLong(); totalTimeInMillis = in.readVLong(); - if (in.getVersion().onOrAfter(Version.V_7_1_0)) { + if (in.getVersion().onOrAfter(Version.V_7_2_0)) { externalTotal = in.readVLong(); externalTotalTimeInMillis = in.readVLong(); } @@ -63,7 +63,7 @@ public RefreshStats(StreamInput in) throws IOException { public void writeTo(StreamOutput out) throws IOException { out.writeVLong(total); out.writeVLong(totalTimeInMillis); - if (out.getVersion().onOrAfter(Version.V_7_1_0)) { + if (out.getVersion().onOrAfter(Version.V_7_2_0)) { out.writeVLong(externalTotal); out.writeVLong(externalTotalTimeInMillis); } diff --git a/server/src/main/java/org/elasticsearch/indices/recovery/RecoveryCleanFilesRequest.java b/server/src/main/java/org/elasticsearch/indices/recovery/RecoveryCleanFilesRequest.java index d23f89a769c79..8d847dcef91c7 100644 --- a/server/src/main/java/org/elasticsearch/indices/recovery/RecoveryCleanFilesRequest.java +++ b/server/src/main/java/org/elasticsearch/indices/recovery/RecoveryCleanFilesRequest.java @@ -52,7 +52,7 @@ public class RecoveryCleanFilesRequest extends TransportRequest { shardId = ShardId.readShardId(in); snapshotFiles = new Store.MetadataSnapshot(in); totalTranslogOps = in.readVInt(); - if (in.getVersion().onOrAfter(Version.V_7_1_0)) { + if (in.getVersion().onOrAfter(Version.V_7_2_0)) { globalCheckpoint = in.readZLong(); } else { globalCheckpoint = SequenceNumbers.UNASSIGNED_SEQ_NO; @@ -66,7 +66,7 @@ public void writeTo(StreamOutput out) throws IOException { shardId.writeTo(out); snapshotFiles.writeTo(out); out.writeVInt(totalTranslogOps); - if (out.getVersion().onOrAfter(Version.V_7_1_0)) { + if (out.getVersion().onOrAfter(Version.V_7_2_0)) { out.writeZLong(globalCheckpoint); } } diff --git a/server/src/main/java/org/elasticsearch/indices/recovery/RecoveryTarget.java b/server/src/main/java/org/elasticsearch/indices/recovery/RecoveryTarget.java index a27ac8a352a32..ce1eb3ac85589 100644 --- a/server/src/main/java/org/elasticsearch/indices/recovery/RecoveryTarget.java +++ b/server/src/main/java/org/elasticsearch/indices/recovery/RecoveryTarget.java @@ -289,7 +289,7 @@ public void prepareForTranslogOperations(boolean fileBasedRecovery, int totalTra state().getTranslog().totalOperations(totalTranslogOps); indexShard().openEngineAndSkipTranslogRecovery(); assert indexShard.getGlobalCheckpoint() >= indexShard.seqNoStats().getMaxSeqNo() || - indexShard.indexSettings().getIndexVersionCreated().before(Version.V_7_1_0) + indexShard.indexSettings().getIndexVersionCreated().before(Version.V_7_2_0) : "global checkpoint is not initialized [" + indexShard.seqNoStats() + "]"; return null; }); @@ -397,7 +397,7 @@ public void cleanFiles(int totalTranslogOps, long globalCheckpoint, Store.Metada try { store.cleanupAndVerify("recovery CleanFilesRequestHandler", sourceMetaData); assert globalCheckpoint >= Long.parseLong(sourceMetaData.getCommitUserData().get(SequenceNumbers.MAX_SEQ_NO)) - || indexShard.indexSettings().getIndexVersionCreated().before(Version.V_7_1_0) : + || indexShard.indexSettings().getIndexVersionCreated().before(Version.V_7_2_0) : "invalid global checkpoint[" + globalCheckpoint + "] source_meta_data [" + sourceMetaData.getCommitUserData() + "]"; final String translogUUID = Translog.createEmptyTranslog( indexShard.shardPath().resolveTranslog(), globalCheckpoint, shardId, indexShard.getPendingPrimaryTerm()); diff --git a/server/src/main/java/org/elasticsearch/indices/recovery/RecoveryTranslogOperationsRequest.java b/server/src/main/java/org/elasticsearch/indices/recovery/RecoveryTranslogOperationsRequest.java index 7f0fa23f2431c..9608455216746 100644 --- a/server/src/main/java/org/elasticsearch/indices/recovery/RecoveryTranslogOperationsRequest.java +++ b/server/src/main/java/org/elasticsearch/indices/recovery/RecoveryTranslogOperationsRequest.java @@ -106,7 +106,7 @@ long mappingVersionOnPrimary() { maxSeenAutoIdTimestampOnPrimary = in.readZLong(); maxSeqNoOfUpdatesOrDeletesOnPrimary = in.readZLong(); retentionLeases = new RetentionLeases(in); - if (in.getVersion().onOrAfter(Version.V_7_1_0)) { + if (in.getVersion().onOrAfter(Version.V_7_2_0)) { mappingVersionOnPrimary = in.readVLong(); } else { mappingVersionOnPrimary = Long.MAX_VALUE; @@ -123,7 +123,7 @@ public void writeTo(StreamOutput out) throws IOException { out.writeZLong(maxSeenAutoIdTimestampOnPrimary); out.writeZLong(maxSeqNoOfUpdatesOrDeletesOnPrimary); retentionLeases.writeTo(out); - if (out.getVersion().onOrAfter(Version.V_7_1_0)) { + if (out.getVersion().onOrAfter(Version.V_7_2_0)) { out.writeVLong(mappingVersionOnPrimary); } } diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/metrics/InternalGeoCentroid.java b/server/src/main/java/org/elasticsearch/search/aggregations/metrics/InternalGeoCentroid.java index b1ca571e7134b..63dd8b50f71ef 100644 --- a/server/src/main/java/org/elasticsearch/search/aggregations/metrics/InternalGeoCentroid.java +++ b/server/src/main/java/org/elasticsearch/search/aggregations/metrics/InternalGeoCentroid.java @@ -70,7 +70,7 @@ public InternalGeoCentroid(StreamInput in) throws IOException { super(in); count = in.readVLong(); if (in.readBoolean()) { - if (in.getVersion().onOrAfter(Version.V_7_1_0)) { + if (in.getVersion().onOrAfter(Version.V_7_2_0)) { centroid = new GeoPoint(in.readDouble(), in.readDouble()); } else { final long hash = in.readLong(); @@ -87,7 +87,7 @@ protected void doWriteTo(StreamOutput out) throws IOException { out.writeVLong(count); if (centroid != null) { out.writeBoolean(true); - if (out.getVersion().onOrAfter(Version.V_7_1_0)) { + if (out.getVersion().onOrAfter(Version.V_7_2_0)) { out.writeDouble(centroid.lat()); out.writeDouble(centroid.lon()); } else { diff --git a/server/src/main/java/org/elasticsearch/search/query/QuerySearchResult.java b/server/src/main/java/org/elasticsearch/search/query/QuerySearchResult.java index 9f9a2c2680a1f..4ccfdd64b0d3c 100644 --- a/server/src/main/java/org/elasticsearch/search/query/QuerySearchResult.java +++ b/server/src/main/java/org/elasticsearch/search/query/QuerySearchResult.java @@ -286,7 +286,7 @@ public void readFromWithId(long id, StreamInput in) throws IOException { if (hasAggs = in.readBoolean()) { aggregations = InternalAggregations.readAggregations(in); } - if (in.getVersion().before(Version.V_7_1_0)) { + if (in.getVersion().before(Version.V_7_2_0)) { List pipelineAggregators = in.readNamedWriteableList(PipelineAggregator.class).stream() .map(a -> (SiblingPipelineAggregator) a).collect(Collectors.toList()); if (hasAggs && pipelineAggregators.isEmpty() == false) { @@ -334,7 +334,7 @@ public void writeToNoId(StreamOutput out) throws IOException { out.writeBoolean(true); aggregations.writeTo(out); } - if (out.getVersion().before(Version.V_7_1_0)) { + if (out.getVersion().before(Version.V_7_2_0)) { //Earlier versions expect sibling pipeline aggs separately as they used to be set to QuerySearchResult directly, //while later versions expect them in InternalAggregations. Note that despite serializing sibling pipeline aggs as part of //InternalAggregations is supported since 6.7.0, the shards set sibling pipeline aggs to InternalAggregations only from 7.1 on. diff --git a/server/src/main/java/org/elasticsearch/search/sort/FieldSortBuilder.java b/server/src/main/java/org/elasticsearch/search/sort/FieldSortBuilder.java index 5e5cddc81fc5f..2be73a0da9cb6 100644 --- a/server/src/main/java/org/elasticsearch/search/sort/FieldSortBuilder.java +++ b/server/src/main/java/org/elasticsearch/search/sort/FieldSortBuilder.java @@ -128,7 +128,7 @@ public FieldSortBuilder(StreamInput in) throws IOException { sortMode = in.readOptionalWriteable(SortMode::readFromStream); unmappedType = in.readOptionalString(); nestedSort = in.readOptionalWriteable(NestedSortBuilder::new); - if (in.getVersion().onOrAfter(Version.V_7_1_0)) { + if (in.getVersion().onOrAfter(Version.V_7_2_0)) { numericType = in.readOptionalString(); } } @@ -143,7 +143,7 @@ public void writeTo(StreamOutput out) throws IOException { out.writeOptionalWriteable(sortMode); out.writeOptionalString(unmappedType); out.writeOptionalWriteable(nestedSort); - if (out.getVersion().onOrAfter(Version.V_7_1_0)) { + if (out.getVersion().onOrAfter(Version.V_7_2_0)) { out.writeOptionalString(numericType); } } diff --git a/server/src/test/java/org/elasticsearch/VersionTests.java b/server/src/test/java/org/elasticsearch/VersionTests.java index 7a4b859bb2bc2..c04f131eab063 100644 --- a/server/src/test/java/org/elasticsearch/VersionTests.java +++ b/server/src/test/java/org/elasticsearch/VersionTests.java @@ -185,7 +185,7 @@ public void testMinCompatVersion() { // from 7.0 on we are supporting the latest minor of the previous major... this might fail once we add a new version ie. 5.x is // released since we need to bump the supported minor in Version#minimumCompatibilityVersion() - Version lastVersion = Version.V_6_7_0; // TODO: remove this once min compat version is a constant instead of method + Version lastVersion = Version.V_6_8_0; // TODO: remove this once min compat version is a constant instead of method assertEquals(lastVersion.major, Version.V_7_0_0.minimumCompatibilityVersion().major); assertEquals("did you miss to bump the minor in Version#minimumCompatibilityVersion()", lastVersion.minor, Version.V_7_0_0.minimumCompatibilityVersion().minor); @@ -345,7 +345,8 @@ public static void assertUnknownVersion(Version version) { public void testIsCompatible() { assertTrue(isCompatible(Version.CURRENT, Version.CURRENT.minimumCompatibilityVersion())); assertFalse(isCompatible(Version.V_6_6_0, Version.V_7_0_0)); - assertTrue(isCompatible(Version.V_6_7_0, Version.V_7_0_0)); + assertFalse(isCompatible(Version.V_6_7_0, Version.V_7_0_0)); + assertTrue(isCompatible(Version.V_6_8_0, Version.V_7_0_0)); assertFalse(isCompatible(Version.fromId(2000099), Version.V_7_0_0)); assertFalse(isCompatible(Version.fromId(2000099), Version.V_6_5_0)); diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthRequestTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthRequestTests.java index d91f162c1bc19..e532d245ec8e3 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthRequestTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/health/ClusterHealthRequestTests.java @@ -57,9 +57,9 @@ public void testSerialize() throws Exception { public void testBwcSerialization() throws Exception { for (int runs = 0; runs < randomIntBetween(5, 20); runs++) { - // Generate a random cluster health request in version < 7.1.0 and serializes it + // Generate a random cluster health request in version < 7.2.0 and serializes it final BytesStreamOutput out = new BytesStreamOutput(); - out.setVersion(randomVersionBetween(random(), Version.V_6_3_0, getPreviousVersion(Version.V_7_1_0))); + out.setVersion(randomVersionBetween(random(), Version.V_6_3_0, getPreviousVersion(Version.V_7_2_0))); final ClusterHealthRequest expected = randomRequest(); { @@ -112,9 +112,9 @@ public void testBwcSerialization() throws Exception { // Generate a random cluster health request in current version final ClusterHealthRequest expected = randomRequest(); - // Serialize to node in version < 7.1.0 + // Serialize to node in version < 7.2.0 final BytesStreamOutput out = new BytesStreamOutput(); - out.setVersion(randomVersionBetween(random(), Version.V_6_3_0, getPreviousVersion(Version.V_7_1_0))); + out.setVersion(randomVersionBetween(random(), Version.V_6_3_0, getPreviousVersion(Version.V_7_2_0))); expected.writeTo(out); // Deserialize and check the cluster health request diff --git a/server/src/test/java/org/elasticsearch/action/admin/indices/close/CloseIndexRequestTests.java b/server/src/test/java/org/elasticsearch/action/admin/indices/close/CloseIndexRequestTests.java index 91183d852afe7..985b4304a32f4 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/indices/close/CloseIndexRequestTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/indices/close/CloseIndexRequestTests.java @@ -54,7 +54,7 @@ public void testBwcSerialization() throws Exception { { final CloseIndexRequest request = randomRequest(); try (BytesStreamOutput out = new BytesStreamOutput()) { - out.setVersion(randomVersionBetween(random(), Version.V_6_4_0, VersionUtils.getPreviousVersion(Version.V_7_1_0))); + out.setVersion(randomVersionBetween(random(), Version.V_6_4_0, VersionUtils.getPreviousVersion(Version.V_7_2_0))); request.writeTo(out); try (StreamInput in = out.bytes().streamInput()) { @@ -77,7 +77,7 @@ public void testBwcSerialization() throws Exception { final CloseIndexRequest deserializedRequest = new CloseIndexRequest(); try (StreamInput in = out.bytes().streamInput()) { - in.setVersion(randomVersionBetween(random(), Version.V_6_4_0, VersionUtils.getPreviousVersion(Version.V_7_1_0))); + in.setVersion(randomVersionBetween(random(), Version.V_6_4_0, VersionUtils.getPreviousVersion(Version.V_7_2_0))); deserializedRequest.readFrom(in); } assertEquals(sample.getParentTask(), deserializedRequest.getParentTask()); diff --git a/server/src/test/java/org/elasticsearch/action/admin/indices/close/CloseIndexResponseTests.java b/server/src/test/java/org/elasticsearch/action/admin/indices/close/CloseIndexResponseTests.java index 6e855e492620c..cca95e09151ef 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/indices/close/CloseIndexResponseTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/indices/close/CloseIndexResponseTests.java @@ -48,7 +48,7 @@ public void testBwcSerialization() throws Exception { { final CloseIndexResponse response = randomResponse(); try (BytesStreamOutput out = new BytesStreamOutput()) { - out.setVersion(randomVersionBetween(random(), Version.V_6_0_0, VersionUtils.getPreviousVersion(Version.V_7_1_0))); + out.setVersion(randomVersionBetween(random(), Version.V_6_0_0, VersionUtils.getPreviousVersion(Version.V_7_2_0))); response.writeTo(out); final AcknowledgedResponse deserializedResponse = new AcknowledgedResponse(); @@ -65,7 +65,7 @@ public void testBwcSerialization() throws Exception { final CloseIndexResponse deserializedResponse = new CloseIndexResponse(); try (StreamInput in = out.bytes().streamInput()) { - in.setVersion(randomVersionBetween(random(), Version.V_6_0_0, VersionUtils.getPreviousVersion(Version.V_7_1_0))); + in.setVersion(randomVersionBetween(random(), Version.V_6_0_0, VersionUtils.getPreviousVersion(Version.V_7_2_0))); deserializedResponse.readFrom(in); } assertThat(deserializedResponse.isAcknowledged(), equalTo(response.isAcknowledged())); diff --git a/server/src/test/java/org/elasticsearch/cluster/metadata/MetaDataIndexStateServiceTests.java b/server/src/test/java/org/elasticsearch/cluster/metadata/MetaDataIndexStateServiceTests.java index e4dd37ede4255..36bca0be1c2d5 100644 --- a/server/src/test/java/org/elasticsearch/cluster/metadata/MetaDataIndexStateServiceTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/metadata/MetaDataIndexStateServiceTests.java @@ -139,7 +139,7 @@ public void testCloseRoutingTableRemovesRoutingTable() { .add(new DiscoveryNode("old_node", buildNewFakeTransportAddress(), emptyMap(), new HashSet<>(Arrays.asList(DiscoveryNode.Role.values())), Version.V_7_0_0)) .add(new DiscoveryNode("new_node", buildNewFakeTransportAddress(), emptyMap(), - new HashSet<>(Arrays.asList(DiscoveryNode.Role.values())), Version.V_7_1_0))) + new HashSet<>(Arrays.asList(DiscoveryNode.Role.values())), Version.V_7_2_0))) .build(); state = MetaDataIndexStateService.closeRoutingTable(state, blockedIndices, results); diff --git a/server/src/test/java/org/elasticsearch/node/NodeTests.java b/server/src/test/java/org/elasticsearch/node/NodeTests.java index 6f0419421b868..c083928436446 100644 --- a/server/src/test/java/org/elasticsearch/node/NodeTests.java +++ b/server/src/test/java/org/elasticsearch/node/NodeTests.java @@ -174,6 +174,7 @@ public void testAwaitCloseTimeoutsOnNonInterruptibleTask() throws Exception { shouldRun.set(false); } + @AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/41448") public void testCloseOnInterruptibleTask() throws Exception { Node node = new MockNode(baseSettings().build(), basePlugins()); node.start(); diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/License.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/License.java index 158c0eb7b2e63..62ffd76e8ea05 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/License.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/License.java @@ -126,6 +126,10 @@ public static OperationMode resolve(String type) { throw new IllegalArgumentException("unknown type [" + type + "]"); } } + + public String description() { + return name().toLowerCase(Locale.ROOT); + } } private License(int version, String uid, String issuer, String issuedTo, long issueDate, String type, diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackLicenseState.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackLicenseState.java index a5da656b886ce..51b8894a54e2a 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackLicenseState.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackLicenseState.java @@ -331,8 +331,17 @@ public synchronized boolean isAuthAllowed() { OperationMode mode = status.mode; final boolean isSecurityCurrentlyEnabled = isSecurityEnabled(mode, isSecurityExplicitlyEnabled, isSecurityEnabled); - return isSecurityCurrentlyEnabled && (mode == OperationMode.STANDARD || mode == OperationMode.GOLD - || mode == OperationMode.PLATINUM || mode == OperationMode.TRIAL); + if (isSecurityCurrentlyEnabled) { + switch (mode) { + case BASIC: + case STANDARD: + case GOLD: + case PLATINUM: + case TRIAL: + return true; + } + } + return false; } /** @@ -405,6 +414,7 @@ public synchronized AllowedRealmType allowedRealmType() { return AllowedRealmType.ALL; case GOLD: return AllowedRealmType.DEFAULT; + case BASIC: case STANDARD: return AllowedRealmType.NATIVE; default: @@ -425,6 +435,24 @@ public synchronized boolean isCustomRoleProvidersAllowed() { && status.active; } + /** + * @return whether the Elasticsearch {@code TokenService} is allowed based on the license {@link OperationMode} + */ + public synchronized boolean isTokenServiceAllowed() { + final OperationMode mode = status.mode; + final boolean isSecurityCurrentlyEnabled = isSecurityEnabled(mode, isSecurityExplicitlyEnabled, isSecurityEnabled); + return isSecurityCurrentlyEnabled && (mode == OperationMode.GOLD || mode == OperationMode.PLATINUM || mode == OperationMode.TRIAL); + } + + /** + * @return whether the Elasticsearch {@code ApiKeyService} is allowed based on the license {@link OperationMode} + */ + public synchronized boolean isApiKeyServiceAllowed() { + final OperationMode mode = status.mode; + final boolean isSecurityCurrentlyEnabled = isSecurityEnabled(mode, isSecurityExplicitlyEnabled, isSecurityEnabled); + return isSecurityCurrentlyEnabled && (mode == OperationMode.GOLD || mode == OperationMode.PLATINUM || mode == OperationMode.TRIAL); + } + /** * @return whether "authorization_realms" are allowed based on the license {@link OperationMode} * @see org.elasticsearch.xpack.core.security.authc.support.DelegatedAuthorizationSettings @@ -674,24 +702,35 @@ public synchronized boolean isTrialLicense() { public synchronized boolean isSecurityAvailable() { OperationMode mode = status.mode; return mode == OperationMode.GOLD || mode == OperationMode.PLATINUM || mode == OperationMode.STANDARD || - mode == OperationMode.TRIAL; + mode == OperationMode.TRIAL || mode == OperationMode.BASIC; } /** - * @return true if security has been disabled by a trial license which is the case of the - * default distribution post 6.3.0. The conditions necessary for this are: + * @return true if security has been disabled due it being the default setting for this license type. + * The conditions necessary for this are: *
    - *
  • A trial license
  • + *
  • A trial or basic license
  • *
  • xpack.security.enabled not specified as a setting
  • *
*/ - public synchronized boolean isSecurityDisabledByTrialLicense() { - return status.mode == OperationMode.TRIAL && isSecurityEnabled && isSecurityExplicitlyEnabled == false; + public synchronized boolean isSecurityDisabledByLicenseDefaults() { + switch (status.mode) { + case TRIAL: + case BASIC: + return isSecurityEnabled && isSecurityExplicitlyEnabled == false; + } + return false; } private static boolean isSecurityEnabled(final OperationMode mode, final boolean isSecurityExplicitlyEnabled, final boolean isSecurityEnabled) { - return mode == OperationMode.TRIAL ? isSecurityExplicitlyEnabled : isSecurityEnabled; + switch (mode) { + case TRIAL: + case BASIC: + return isSecurityExplicitlyEnabled; + default: + return isSecurityEnabled; + } } /** diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/action/TransportXPackInfoAction.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/action/TransportXPackInfoAction.java index 541bd4e2fce92..16f4541e0a7d9 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/action/TransportXPackInfoAction.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/action/TransportXPackInfoAction.java @@ -20,7 +20,6 @@ import org.elasticsearch.xpack.core.XPackBuild; import org.elasticsearch.xpack.core.XPackFeatureSet; -import java.util.Locale; import java.util.Set; import java.util.stream.Collectors; @@ -51,7 +50,7 @@ protected void doExecute(Task task, XPackInfoRequest request, ActionListener + WaitForHttpResource http = new WaitForHttpResource("http", node.httpUri(), numNodes) + http.setUsername("admin_user") + http.setPassword("admin-password") + return http.wait(5000) + } +} diff --git a/x-pack/plugin/security/qa/security-basic/src/test/java/org/elasticsearch/xpack/security/SecurityWithBasicLicenseIT.java b/x-pack/plugin/security/qa/security-basic/src/test/java/org/elasticsearch/xpack/security/SecurityWithBasicLicenseIT.java new file mode 100644 index 0000000000000..837c9ac4d8ded --- /dev/null +++ b/x-pack/plugin/security/qa/security-basic/src/test/java/org/elasticsearch/xpack/security/SecurityWithBasicLicenseIT.java @@ -0,0 +1,296 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.security; + +import org.apache.http.HttpHeaders; +import org.elasticsearch.client.Request; +import org.elasticsearch.client.RequestOptions; +import org.elasticsearch.client.Response; +import org.elasticsearch.client.ResponseException; +import org.elasticsearch.common.collect.Tuple; +import org.elasticsearch.common.settings.SecureString; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.test.rest.ESRestTestCase; +import org.elasticsearch.test.rest.yaml.ObjectPath; +import org.elasticsearch.xpack.security.authc.InternalRealms; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Base64; +import java.util.Map; + +import static org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.notNullValue; + +public class SecurityWithBasicLicenseIT extends ESRestTestCase { + + @Override + protected Settings restAdminSettings() { + String token = basicAuthHeaderValue("admin_user", new SecureString("admin-password".toCharArray())); + return Settings.builder() + .put(ThreadContext.PREFIX + ".Authorization", token) + .build(); + } + + @Override + protected Settings restClientSettings() { + String token = basicAuthHeaderValue("security_test_user", new SecureString("security-test-password".toCharArray())); + return Settings.builder() + .put(ThreadContext.PREFIX + ".Authorization", token) + .build(); + } + + public void testWithBasicLicense() throws Exception { + checkLicenseType("basic"); + checkSecurityEnabled(false); + checkAuthentication(); + checkHasPrivileges(); + checkIndexWrite(); + assertFailToGetToken(); + assertFailToGetApiKey(); + assertAddRoleWithDLS(false); + assertAddRoleWithFLS(false); + } + + public void testWithTrialLicense() throws Exception { + startTrial(); + String accessToken = null; + Tuple keyAndId = null; + try { + checkLicenseType("trial"); + checkSecurityEnabled(true); + checkAuthentication(); + checkHasPrivileges(); + checkIndexWrite(); + accessToken = getAccessToken(); + keyAndId = getApiKeyAndId(); + assertAuthenticateWithToken(accessToken, true); + assertAuthenticateWithApiKey(keyAndId, true); + assertAddRoleWithDLS(true); + assertAddRoleWithFLS(true); + } finally { + revertTrial(); + assertAuthenticateWithToken(accessToken, false); + assertAuthenticateWithApiKey(keyAndId, false); + assertFailToGetToken(); + assertFailToGetApiKey(); + assertAddRoleWithDLS(false); + assertAddRoleWithFLS(false); + } + } + + private void startTrial() throws IOException { + Response response = client().performRequest(new Request("POST", "/_license/start_trial?acknowledge=true")); + assertOK(response); + } + + private void revertTrial() throws IOException { + client().performRequest(new Request("POST", "/_license/start_basic?acknowledge=true")); + } + + private void checkLicenseType(String type) throws IOException { + Map license = getAsMap("/_license"); + assertThat(license, notNullValue()); + assertThat(ObjectPath.evaluate(license, "license.type"), equalTo(type)); + } + + private void checkSecurityEnabled(boolean allowAllRealms) throws IOException { + Map usage = getAsMap("/_xpack/usage"); + assertThat(usage, notNullValue()); + assertThat(ObjectPath.evaluate(usage, "security.available"), equalTo(true)); + assertThat(ObjectPath.evaluate(usage, "security.enabled"), equalTo(true)); + for (String realm : Arrays.asList("file", "native")) { + assertThat(ObjectPath.evaluate(usage, "security.realms." + realm + ".available"), equalTo(true)); + assertThat(ObjectPath.evaluate(usage, "security.realms." + realm + ".enabled"), equalTo(true)); + } + for (String realm : InternalRealms.getConfigurableRealmsTypes()) { + if (realm.equals("file") == false && realm.equals("native") == false) { + assertThat(ObjectPath.evaluate(usage, "security.realms." + realm + ".available"), equalTo(allowAllRealms)); + assertThat(ObjectPath.evaluate(usage, "security.realms." + realm + ".enabled"), equalTo(false)); + } + } + } + + private void checkAuthentication() throws IOException { + final Map auth = getAsMap("/_security/_authenticate"); + // From file realm, configured in build.gradle + assertThat(ObjectPath.evaluate(auth, "username"), equalTo("security_test_user")); + assertThat(ObjectPath.evaluate(auth, "roles"), contains("security_test_role")); + } + + private void checkHasPrivileges() throws IOException { + final Request request = new Request("GET", "/_security/user/_has_privileges"); + request.setJsonEntity("{" + + "\"cluster\": [ \"manage\", \"monitor\" ]," + + "\"index\": [{ \"names\": [ \"index_allowed\", \"index_denied\" ], \"privileges\": [ \"read\", \"all\" ] }]" + + "}"); + Response response = client().performRequest(request); + final Map auth = entityAsMap(response); + assertThat(ObjectPath.evaluate(auth, "username"), equalTo("security_test_user")); + assertThat(ObjectPath.evaluate(auth, "has_all_requested"), equalTo(false)); + assertThat(ObjectPath.evaluate(auth, "cluster.manage"), equalTo(false)); + assertThat(ObjectPath.evaluate(auth, "cluster.monitor"), equalTo(true)); + assertThat(ObjectPath.evaluate(auth, "index.index_allowed.read"), equalTo(true)); + assertThat(ObjectPath.evaluate(auth, "index.index_allowed.all"), equalTo(false)); + assertThat(ObjectPath.evaluate(auth, "index.index_denied.read"), equalTo(false)); + assertThat(ObjectPath.evaluate(auth, "index.index_denied.all"), equalTo(false)); + } + + private void checkIndexWrite() throws IOException { + final Request request1 = new Request("POST", "/index_allowed/_doc"); + request1.setJsonEntity("{ \"key\" : \"value\" }"); + Response response1 = client().performRequest(request1); + final Map result1 = entityAsMap(response1); + assertThat(ObjectPath.evaluate(result1, "_index"), equalTo("index_allowed")); + assertThat(ObjectPath.evaluate(result1, "result"), equalTo("created")); + + final Request request2 = new Request("POST", "/index_denied/_doc"); + request2.setJsonEntity("{ \"key\" : \"value\" }"); + ResponseException e = expectThrows(ResponseException.class, () -> client().performRequest(request2)); + assertThat(e.getResponse().getStatusLine().getStatusCode(), equalTo(403)); + assertThat(e.getMessage(), containsString("unauthorized for user [security_test_user]")); + } + + private Request buildGetTokenRequest() { + final Request getToken = new Request("POST", "/_security/oauth2/token"); + getToken.setJsonEntity("{\"grant_type\" : \"password\",\n" + + " \"username\" : \"security_test_user\",\n" + + " \"password\" : \"security-test-password\"\n" + + "}"); + return getToken; + } + + private Request buildGetApiKeyRequest() { + final Request getApiKey = new Request("POST", "/_security/api_key"); + getApiKey.setJsonEntity("{\"name\" : \"my-api-key\",\n" + + " \"expiration\" : \"2d\",\n" + + " \"role_descriptors\" : {} \n" + + "}"); + return getApiKey; + } + + private String getAccessToken() throws IOException { + Response getTokenResponse = adminClient().performRequest(buildGetTokenRequest()); + assertThat(getTokenResponse.getStatusLine().getStatusCode(), equalTo(200)); + final Map tokens = entityAsMap(getTokenResponse); + return ObjectPath.evaluate(tokens, "access_token").toString(); + } + + private Tuple getApiKeyAndId() throws IOException { + Response getApiKeyResponse = adminClient().performRequest(buildGetApiKeyRequest()); + assertThat(getApiKeyResponse.getStatusLine().getStatusCode(), equalTo(200)); + final Map apiKeyResponseMap = entityAsMap(getApiKeyResponse); + assertOK(getApiKeyResponse); + return new Tuple<>(ObjectPath.evaluate(apiKeyResponseMap, "api_key").toString(), + ObjectPath.evaluate(apiKeyResponseMap, "id").toString()); + } + + private void assertFailToGetToken() { + ResponseException e = expectThrows(ResponseException.class, () -> adminClient().performRequest(buildGetTokenRequest())); + assertThat(e.getResponse().getStatusLine().getStatusCode(), equalTo(403)); + assertThat(e.getMessage(), containsString("current license is non-compliant for [security tokens]")); + } + + private void assertFailToGetApiKey() { + ResponseException e = expectThrows(ResponseException.class, () -> adminClient().performRequest(buildGetApiKeyRequest())); + assertThat(e.getResponse().getStatusLine().getStatusCode(), equalTo(403)); + assertThat(e.getMessage(), containsString("current license is non-compliant for [api keys]")); + } + + private void assertAuthenticateWithToken(String accessToken, boolean shouldSucceed) throws IOException { + assertNotNull("access token cannot be null", accessToken); + Request request = new Request("GET", "/_security/_authenticate"); + RequestOptions.Builder options = request.getOptions().toBuilder(); + options.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken); + request.setOptions(options); + if (shouldSucceed) { + Response authenticateResponse = client().performRequest(request); + assertOK(authenticateResponse); + assertEquals("security_test_user", entityAsMap(authenticateResponse).get("username")); + } else { + ResponseException e = expectThrows(ResponseException.class, () -> client().performRequest(request)); + assertThat(e.getResponse().getStatusLine().getStatusCode(), equalTo(401)); + assertThat(e.getMessage(), containsString("missing authentication credentials for REST request")); + } + } + + private void assertAuthenticateWithApiKey(Tuple keyAndId, boolean shouldSucceed) throws IOException { + assertNotNull("API Key and Id cannot be null", keyAndId); + Request request = new Request("GET", "/_security/_authenticate"); + RequestOptions.Builder options = request.getOptions().toBuilder(); + String headerValue = Base64.getEncoder().encodeToString((keyAndId.v2() + ":" + keyAndId.v1()).getBytes(StandardCharsets.UTF_8)); + options.addHeader(HttpHeaders.AUTHORIZATION, "ApiKey " + headerValue); + request.setOptions(options); + if (shouldSucceed) { + Response authenticateResponse = client().performRequest(request); + assertOK(authenticateResponse); + assertEquals("admin_user", entityAsMap(authenticateResponse).get("username")); + } else { + ResponseException e = expectThrows(ResponseException.class, () -> client().performRequest(request)); + assertThat(e.getResponse().getStatusLine().getStatusCode(), equalTo(401)); + assertThat(e.getMessage(), containsString("missing authentication credentials for REST request")); + } + } + + private void assertAddRoleWithDLS(boolean shouldSucceed) throws IOException { + final Request addRole = new Request("POST", "/_security/role/dlsrole"); + addRole.setJsonEntity("{\n" + + " \"cluster\": [\"all\"],\n" + + " \"indices\": [\n" + + " {\n" + + " \"names\": [ \"index1\", \"index2\" ],\n" + + " \"privileges\": [\"all\"],\n" + + " \"query\": \"{\\\"match\\\": {\\\"title\\\": \\\"foo\\\"}}\" \n" + + " }\n" + + " ],\n" + + " \"run_as\": [ \"other_user\" ],\n" + + " \"metadata\" : { // optional\n" + + " \"version\" : 1\n" + + " }\n" + + "}"); + if (shouldSucceed) { + Response addRoleResponse = adminClient().performRequest(addRole); + assertThat(addRoleResponse.getStatusLine().getStatusCode(), equalTo(200)); + } else { + ResponseException e = expectThrows(ResponseException.class, () -> adminClient().performRequest(addRole)); + assertThat(e.getResponse().getStatusLine().getStatusCode(), equalTo(403)); + assertThat(e.getMessage(), containsString("current license is non-compliant for [field and document level security]")); + } + } + + private void assertAddRoleWithFLS(boolean shouldSucceed) throws IOException { + final Request addRole = new Request("POST", "/_security/role/dlsrole"); + addRole.setJsonEntity("{\n" + + " \"cluster\": [\"all\"],\n" + + " \"indices\": [\n" + + " {\n" + + " \"names\": [ \"index1\", \"index2\" ],\n" + + " \"privileges\": [\"all\"],\n" + + " \"field_security\" : { // optional\n" + + " \"grant\" : [ \"title\", \"body\" ]\n" + + " }\n" + + " }\n" + + " ],\n" + + " \"run_as\": [ \"other_user\" ],\n" + + " \"metadata\" : { // optional\n" + + " \"version\" : 1\n" + + " }\n" + + "}"); + if (shouldSucceed) { + Response addRoleResponse = adminClient().performRequest(addRole); + assertThat(addRoleResponse.getStatusLine().getStatusCode(), equalTo(200)); + } else { + ResponseException e = expectThrows(ResponseException.class, () -> adminClient().performRequest(addRole)); + assertThat(e.getResponse().getStatusLine().getStatusCode(), equalTo(403)); + assertThat(e.getMessage(), containsString("current license is non-compliant for [field and document level security]")); + } + } +} diff --git a/x-pack/plugin/security/qa/security-basic/src/test/resources/roles.yml b/x-pack/plugin/security/qa/security-basic/src/test/resources/roles.yml new file mode 100644 index 0000000000000..9b2171257fc61 --- /dev/null +++ b/x-pack/plugin/security/qa/security-basic/src/test/resources/roles.yml @@ -0,0 +1,8 @@ +# A basic role that is used to test security +security_test_role: + cluster: + - monitor + - "cluster:admin/xpack/license/*" + indices: + - names: [ "index_allowed" ] + privileges: [ "read", "write", "create_index" ] diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java index 815993e343cbc..a36a004c7f413 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java @@ -195,9 +195,9 @@ import org.elasticsearch.xpack.security.ingest.SetSecurityUserProcessor; import org.elasticsearch.xpack.security.rest.SecurityRestFilter; import org.elasticsearch.xpack.security.rest.action.RestAuthenticateAction; -import org.elasticsearch.xpack.security.rest.action.RestCreateApiKeyAction; -import org.elasticsearch.xpack.security.rest.action.RestGetApiKeyAction; -import org.elasticsearch.xpack.security.rest.action.RestInvalidateApiKeyAction; +import org.elasticsearch.xpack.security.rest.action.apikey.RestCreateApiKeyAction; +import org.elasticsearch.xpack.security.rest.action.apikey.RestGetApiKeyAction; +import org.elasticsearch.xpack.security.rest.action.apikey.RestInvalidateApiKeyAction; import org.elasticsearch.xpack.security.rest.action.oauth2.RestGetTokenAction; import org.elasticsearch.xpack.security.rest.action.oauth2.RestInvalidateTokenAction; import org.elasticsearch.xpack.security.rest.action.oidc.RestOpenIdConnectAuthenticateAction; @@ -408,8 +408,8 @@ Collection createComponents(Client client, ThreadPool threadPool, Cluste securityIndex.set(SecurityIndexManager.buildSecurityMainIndexManager(client, clusterService)); - final TokenService tokenService = new TokenService(settings, Clock.systemUTC(), client, securityIndex.get(), - SecurityIndexManager.buildSecurityTokensIndexManager(client, clusterService), clusterService); + final TokenService tokenService = new TokenService(settings, Clock.systemUTC(), client, getLicenseState(), + securityIndex.get(), SecurityIndexManager.buildSecurityTokensIndexManager(client, clusterService), clusterService); this.tokenService.set(tokenService); components.add(tokenService); @@ -450,8 +450,8 @@ Collection createComponents(Client client, ThreadPool threadPool, Cluste rolesProviders.addAll(extension.getRolesProviders(settings, resourceWatcherService)); } - final ApiKeyService apiKeyService = new ApiKeyService(settings, Clock.systemUTC(), client, securityIndex.get(), clusterService, - threadPool); + final ApiKeyService apiKeyService = new ApiKeyService(settings, Clock.systemUTC(), client, getLicenseState(), securityIndex.get(), + clusterService, threadPool); components.add(apiKeyService); final CompositeRolesStore allRolesStore = new CompositeRolesStore(settings, fileRolesStore, nativeRolesStore, reservedRolesStore, privilegeStore, rolesProviders, threadPool.getThreadContext(), getLicenseState(), fieldPermissionsCache, apiKeyService, diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/SecurityFeatureSet.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/SecurityFeatureSet.java index ce7e54a3cb29d..b24bf4375440f 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/SecurityFeatureSet.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/SecurityFeatureSet.java @@ -82,7 +82,7 @@ public boolean available() { public boolean enabled() { if (licenseState != null) { return XPackSettings.SECURITY_ENABLED.get(settings) && - licenseState.isSecurityDisabledByTrialLicense() == false; + licenseState.isSecurityDisabledByLicenseDefaults() == false; } return false; } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java index d993807bfc4ba..b9c298023e04b 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java @@ -111,8 +111,9 @@ public void app listener.onFailure(e); } } else if (SECURITY_ACTION_MATCHER.test(action)) { - if (licenseState.isSecurityDisabledByTrialLicense()) { - listener.onFailure(new ElasticsearchException("Security must be explicitly enabled when using a trial license. " + + if (licenseState.isSecurityDisabledByLicenseDefaults()) { + listener.onFailure(new ElasticsearchException("Security must be explicitly enabled when using a [" + + licenseState.getOperationMode().description() + "] license. " + "Enable security by setting [xpack.security.enabled] to [true] in the elasticsearch.yml file " + "and restart the node.")); } else { diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java index 54c9b4fc4dfbe..74d4cce8d02de 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java @@ -53,6 +53,8 @@ import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.license.LicenseUtils; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.search.SearchHit; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.xpack.core.XPackSettings; @@ -69,6 +71,7 @@ import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.security.support.SecurityIndexManager; +import javax.crypto.SecretKeyFactory; import java.io.Closeable; import java.io.IOException; import java.io.UncheckedIOException; @@ -92,8 +95,6 @@ import java.util.function.Function; import java.util.stream.Collectors; -import javax.crypto.SecretKeyFactory; - import static org.elasticsearch.index.mapper.MapperService.SINGLE_MAPPING_NAME; import static org.elasticsearch.search.SearchService.DEFAULT_KEEPALIVE_SETTING; import static org.elasticsearch.xpack.core.ClientHelper.SECURITY_ORIGIN; @@ -136,6 +137,7 @@ public class ApiKeyService { private final Clock clock; private final Client client; + private final XPackLicenseState licenseState; private final SecurityIndexManager securityIndex; private final ClusterService clusterService; private final Hasher hasher; @@ -149,10 +151,11 @@ public class ApiKeyService { private volatile long lastExpirationRunMs; - public ApiKeyService(Settings settings, Clock clock, Client client, SecurityIndexManager securityIndex, ClusterService clusterService, - ThreadPool threadPool) { + public ApiKeyService(Settings settings, Clock clock, Client client, XPackLicenseState licenseState, SecurityIndexManager securityIndex, + ClusterService clusterService, ThreadPool threadPool) { this.clock = clock; this.client = client; + this.licenseState = licenseState; this.securityIndex = securityIndex; this.clusterService = clusterService; this.enabled = XPackSettings.API_KEY_SERVICE_ENABLED_SETTING.get(settings); @@ -177,10 +180,10 @@ public ApiKeyService(Settings settings, Clock clock, Client client, SecurityInde * Asynchronously creates a new API key based off of the request and authentication * @param authentication the authentication that this api key should be based off of * @param request the request to create the api key included any permission restrictions - * @param roleDescriptorSet the user's actual roles that we always enforce + * @param userRoles the user's actual roles that we always enforce * @param listener the listener that will be used to notify of completion */ - public void createApiKey(Authentication authentication, CreateApiKeyRequest request, Set roleDescriptorSet, + public void createApiKey(Authentication authentication, CreateApiKeyRequest request, Set userRoles, ActionListener listener) { ensureEnabled(); if (authentication == null) { @@ -191,12 +194,12 @@ public void createApiKey(Authentication authentication, CreateApiKeyRequest requ * this check is best effort as there could be two nodes executing search and * then index concurrently allowing a duplicate name. */ - checkDuplicateApiKeyNameAndCreateApiKey(authentication, request, roleDescriptorSet, listener); + checkDuplicateApiKeyNameAndCreateApiKey(authentication, request, userRoles, listener); } } private void checkDuplicateApiKeyNameAndCreateApiKey(Authentication authentication, CreateApiKeyRequest request, - Set roleDescriptorSet, + Set userRoles, ActionListener listener) { final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery() .filter(QueryBuilders.termQuery("doc_type", "api_key")) @@ -221,7 +224,7 @@ private void checkDuplicateApiKeyNameAndCreateApiKey(Authentication authenticati listener.onFailure(traceLog("create api key", new ElasticsearchSecurityException( "Error creating api key as api key with name [{}] already exists", request.getName()))); } else { - createApiKeyAndIndexIt(authentication, request, roleDescriptorSet, listener); + createApiKeyAndIndexIt(authentication, request, userRoles, listener); } }, listener::onFailure))); @@ -234,51 +237,8 @@ private void createApiKeyAndIndexIt(Authentication authentication, CreateApiKeyR final SecureString apiKey = UUIDs.randomBase64UUIDSecureString(); final Version version = clusterService.state().nodes().getMinNodeVersion(); - final char[] keyHash = hasher.hash(apiKey); - try (XContentBuilder builder = XContentFactory.jsonBuilder()) { - builder.startObject() - .field("doc_type", "api_key") - .field("creation_time", created.toEpochMilli()) - .field("expiration_time", expiration == null ? null : expiration.toEpochMilli()) - .field("api_key_invalidated", false); - - byte[] utf8Bytes = null; - try { - utf8Bytes = CharArrays.toUtf8Bytes(keyHash); - builder.field("api_key_hash").utf8Value(utf8Bytes, 0, utf8Bytes.length); - } finally { - if (utf8Bytes != null) { - Arrays.fill(utf8Bytes, (byte) 0); - } - } - - // Save role_descriptors - builder.startObject("role_descriptors"); - if (request.getRoleDescriptors() != null && request.getRoleDescriptors().isEmpty() == false) { - for (RoleDescriptor descriptor : request.getRoleDescriptors()) { - builder.field(descriptor.getName(), - (contentBuilder, params) -> descriptor.toXContent(contentBuilder, params, true)); - } - } - builder.endObject(); - - // Save limited_by_role_descriptors - builder.startObject("limited_by_role_descriptors"); - for (RoleDescriptor descriptor : roleDescriptorSet) { - builder.field(descriptor.getName(), - (contentBuilder, params) -> descriptor.toXContent(contentBuilder, params, true)); - } - builder.endObject(); - - builder.field("name", request.getName()) - .field("version", version.id) - .startObject("creator") - .field("principal", authentication.getUser().principal()) - .field("metadata", authentication.getUser().metadata()) - .field("realm", authentication.getLookedUpBy() == null ? - authentication.getAuthenticatedBy().getName() : authentication.getLookedUpBy().getName()) - .endObject() - .endObject(); + try (XContentBuilder builder = newDocument(apiKey, request.getName(), authentication, roleDescriptorSet, created, expiration, + request.getRoleDescriptors(), version)) { final IndexRequest indexRequest = client.prepareIndex(SECURITY_MAIN_ALIAS, SINGLE_MAPPING_NAME) .setSource(builder) @@ -292,9 +252,64 @@ private void createApiKeyAndIndexIt(Authentication authentication, CreateApiKeyR listener::onFailure))); } catch (IOException e) { listener.onFailure(e); + } + } + + /** + * package protected for testing + */ + XContentBuilder newDocument(SecureString apiKey, String name, Authentication authentication, Set userRoles, + Instant created, Instant expiration, List keyRoles, + Version version) throws IOException { + XContentBuilder builder = XContentFactory.jsonBuilder(); + builder.startObject() + .field("doc_type", "api_key") + .field("creation_time", created.toEpochMilli()) + .field("expiration_time", expiration == null ? null : expiration.toEpochMilli()) + .field("api_key_invalidated", false); + + byte[] utf8Bytes = null; + final char[] keyHash = hasher.hash(apiKey); + try { + utf8Bytes = CharArrays.toUtf8Bytes(keyHash); + builder.field("api_key_hash").utf8Value(utf8Bytes, 0, utf8Bytes.length); } finally { + if (utf8Bytes != null) { + Arrays.fill(utf8Bytes, (byte) 0); + } Arrays.fill(keyHash, (char) 0); } + + + // Save role_descriptors + builder.startObject("role_descriptors"); + if (keyRoles != null && keyRoles.isEmpty() == false) { + for (RoleDescriptor descriptor : keyRoles) { + builder.field(descriptor.getName(), + (contentBuilder, params) -> descriptor.toXContent(contentBuilder, params, true)); + } + } + builder.endObject(); + + // Save limited_by_role_descriptors + builder.startObject("limited_by_role_descriptors"); + for (RoleDescriptor descriptor : userRoles) { + builder.field(descriptor.getName(), + (contentBuilder, params) -> descriptor.toXContent(contentBuilder, params, true)); + } + builder.endObject(); + + builder.field("name", name) + .field("version", version.id) + .startObject("creator") + .field("principal", authentication.getUser().principal()) + .field("metadata", authentication.getUser().metadata()) + .field("realm", authentication.getLookedUpBy() == null ? + authentication.getAuthenticatedBy().getName() : authentication.getLookedUpBy().getName()) + .endObject() + .endObject(); + + return builder; } /** @@ -302,7 +317,7 @@ private void createApiKeyAndIndexIt(Authentication authentication, CreateApiKeyR * {@code ApiKey }. If found this will attempt to authenticate the key. */ void authenticateWithApiKeyIfPresent(ThreadContext ctx, ActionListener listener) { - if (enabled) { + if (isEnabled()) { final ApiKeyCredentials credentials; try { credentials = getCredentialsFromHeader(ctx); @@ -564,7 +579,14 @@ private Instant getApiKeyExpiration(Instant now, CreateApiKeyRequest request) { } } + private boolean isEnabled() { + return enabled && licenseState.isApiKeyServiceAllowed(); + } + private void ensureEnabled() { + if (licenseState.isApiKeyServiceAllowed() == false) { + throw LicenseUtils.newComplianceException("api keys"); + } if (enabled == false) { throw new IllegalStateException("api keys are not enabled"); } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/InternalRealms.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/InternalRealms.java index 3a27b6670aeb1..4e2a2362ded4d 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/InternalRealms.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/InternalRealms.java @@ -77,7 +77,7 @@ static boolean isXPackRealm(String type) { return ReservedRealm.TYPE.equals(type); } - static Collection getConfigurableRealmsTypes() { + public static Collection getConfigurableRealmsTypes() { return Collections.unmodifiableSet(XPACK_TYPES); } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/TokenService.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/TokenService.java index 0481867b594f2..6f96c9bf7dd88 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/TokenService.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/TokenService.java @@ -74,6 +74,8 @@ import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.index.seqno.SequenceNumbers; +import org.elasticsearch.license.LicenseUtils; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.rest.RestStatus; import org.elasticsearch.search.SearchHit; import org.elasticsearch.xpack.core.XPackField; @@ -180,9 +182,9 @@ public final class TokenService { private static final String TOKEN_DOC_ID_PREFIX = TOKEN_DOC_TYPE + "_"; static final int MINIMUM_BYTES = VERSION_BYTES + SALT_BYTES + IV_BYTES + 1; static final int MINIMUM_BASE64_BYTES = Double.valueOf(Math.ceil((4 * MINIMUM_BYTES) / 3)).intValue(); - static final Version VERSION_TOKENS_INDEX_INTRODUCED = Version.V_7_1_0; - static final Version VERSION_ACCESS_TOKENS_AS_UUIDS = Version.V_7_1_0; - static final Version VERSION_MULTIPLE_CONCURRENT_REFRESHES = Version.V_7_1_0; + static final Version VERSION_TOKENS_INDEX_INTRODUCED = Version.V_7_2_0; + static final Version VERSION_ACCESS_TOKENS_AS_UUIDS = Version.V_7_2_0; + static final Version VERSION_MULTIPLE_CONCURRENT_REFRESHES = Version.V_7_2_0; // UUIDs are 16 bytes encoded base64 without padding, therefore the length is (16 / 3) * 4 + ((16 % 3) * 8 + 5) / 6 chars private static final int TOKEN_ID_LENGTH = 22; private static final Logger logger = LogManager.getLogger(TokenService.class); @@ -198,6 +200,7 @@ public final class TokenService { private final SecurityIndexManager securityTokensIndex; private final ExpiredTokenRemover expiredTokenRemover; private final boolean enabled; + private final XPackLicenseState licenseState; private volatile TokenKeys keyCache; private volatile long lastExpirationRunMs; private final AtomicLong createdTimeStamps = new AtomicLong(-1); @@ -205,8 +208,9 @@ public final class TokenService { /** * Creates a new token service */ - public TokenService(Settings settings, Clock clock, Client client, SecurityIndexManager securityMainIndex, - SecurityIndexManager securityTokensIndex, ClusterService clusterService) throws GeneralSecurityException { + public TokenService(Settings settings, Clock clock, Client client, XPackLicenseState licenseState, + SecurityIndexManager securityMainIndex, SecurityIndexManager securityTokensIndex, + ClusterService clusterService) throws GeneralSecurityException { byte[] saltArr = new byte[SALT_BYTES]; secureRandom.nextBytes(saltArr); final SecureString tokenPassphrase = generateTokenKey(); @@ -214,12 +218,13 @@ public TokenService(Settings settings, Clock clock, Client client, SecurityIndex this.clock = clock.withZone(ZoneOffset.UTC); this.expirationDelay = TOKEN_EXPIRATION.get(settings); this.client = client; + this.licenseState = licenseState; this.securityMainIndex = securityMainIndex; this.securityTokensIndex = securityTokensIndex; this.lastExpirationRunMs = client.threadPool().relativeTimeInMillis(); this.deleteInterval = DELETE_INTERVAL.get(settings); this.enabled = isTokenServiceEnabled(settings); - this.expiredTokenRemover = new ExpiredTokenRemover(settings, client, securityMainIndex, securityTokensIndex); + this.expiredTokenRemover = new ExpiredTokenRemover(settings, client, this.securityMainIndex, securityTokensIndex); ensureEncryptionCiphersSupported(); KeyAndCache keyAndCache = new KeyAndCache(new KeyAndTimestamp(tokenPassphrase, createdTimeStamps.incrementAndGet()), new BytesKey(saltArr)); @@ -302,7 +307,7 @@ private void createOAuth2Tokens(String userTokenId, Version tokenVersion, Securi * has not been revoked or is expired. */ void getAndValidateToken(ThreadContext ctx, ActionListener listener) { - if (enabled) { + if (isEnabled()) { final String token = getFromHeader(ctx); if (token == null) { listener.onResponse(null); @@ -1360,9 +1365,16 @@ private static String getTokenIdFromDocumentId(String docId) { } } + private boolean isEnabled() { + return enabled && licenseState.isTokenServiceAllowed(); + } + private void ensureEnabled() { + if (licenseState.isTokenServiceAllowed() == false) { + throw LicenseUtils.newComplianceException("security tokens"); + } if (enabled == false) { - throw new IllegalStateException("tokens are not enabled"); + throw new IllegalStateException("security tokens are not enabled"); } } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/SecurityBaseRestHandler.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/SecurityBaseRestHandler.java index c2066996f9ce4..801902d5b9623 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/SecurityBaseRestHandler.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/SecurityBaseRestHandler.java @@ -71,8 +71,9 @@ protected Exception checkFeatureAvailable(RestRequest request) { return new IllegalStateException("Security is not enabled but a security rest handler is registered"); } else if (licenseState.isSecurityAvailable() == false) { return LicenseUtils.newComplianceException(XPackField.SECURITY); - } else if (licenseState.isSecurityDisabledByTrialLicense()) { - return new ElasticsearchException("Security must be explicitly enabled when using a trial license. " + + } else if (licenseState.isSecurityDisabledByLicenseDefaults()) { + return new ElasticsearchException("Security must be explicitly enabled when using a [" + + licenseState.getOperationMode().description() + "] license. " + "Enable security by setting [xpack.security.enabled] to [true] in the elasticsearch.yml file " + "and restart the node."); } else { diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/apikey/ApiKeyBaseRestHandler.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/apikey/ApiKeyBaseRestHandler.java new file mode 100644 index 0000000000000..4d797bd543dc3 --- /dev/null +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/apikey/ApiKeyBaseRestHandler.java @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +package org.elasticsearch.xpack.security.rest.action.apikey; + +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.license.LicenseUtils; +import org.elasticsearch.license.XPackLicenseState; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.xpack.security.rest.action.SecurityBaseRestHandler; + +/** + * A base rest handler that handles licensing for ApiKey actions + */ +abstract class ApiKeyBaseRestHandler extends SecurityBaseRestHandler { + ApiKeyBaseRestHandler(Settings settings, XPackLicenseState licenseState) { + super(settings, licenseState); + } + + @Override + protected Exception checkFeatureAvailable(RestRequest request) { + Exception failedFeature = super.checkFeatureAvailable(request); + if (failedFeature != null) { + return failedFeature; + } else if (licenseState.isApiKeyServiceAllowed()) { + return null; + } else { + logger.info("API Keys are not available under the current [{}] license", licenseState.getOperationMode().description()); + return LicenseUtils.newComplianceException("api keys"); + } + } +} diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/RestCreateApiKeyAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/apikey/RestCreateApiKeyAction.java similarity index 94% rename from x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/RestCreateApiKeyAction.java rename to x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/apikey/RestCreateApiKeyAction.java index 2e3ced0d8933f..14d4726553dff 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/RestCreateApiKeyAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/apikey/RestCreateApiKeyAction.java @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.xpack.security.rest.action; +package org.elasticsearch.xpack.security.rest.action.apikey; import org.elasticsearch.action.support.WriteRequest; import org.elasticsearch.client.node.NodeClient; @@ -24,7 +24,7 @@ /** * Rest action to create an API key */ -public final class RestCreateApiKeyAction extends SecurityBaseRestHandler { +public final class RestCreateApiKeyAction extends ApiKeyBaseRestHandler { /** * @param settings the node's settings diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/RestGetApiKeyAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/apikey/RestGetApiKeyAction.java similarity index 95% rename from x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/RestGetApiKeyAction.java rename to x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/apikey/RestGetApiKeyAction.java index ec0bd7bd9fd31..71ed5a06efb65 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/RestGetApiKeyAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/apikey/RestGetApiKeyAction.java @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.xpack.security.rest.action; +package org.elasticsearch.xpack.security.rest.action.apikey; import org.elasticsearch.client.node.NodeClient; import org.elasticsearch.common.Strings; @@ -26,7 +26,7 @@ /** * Rest action to get one or more API keys information. */ -public final class RestGetApiKeyAction extends SecurityBaseRestHandler { +public final class RestGetApiKeyAction extends ApiKeyBaseRestHandler { public RestGetApiKeyAction(Settings settings, RestController controller, XPackLicenseState licenseState) { super(settings, licenseState); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/RestInvalidateApiKeyAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/apikey/RestInvalidateApiKeyAction.java similarity index 95% rename from x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/RestInvalidateApiKeyAction.java rename to x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/apikey/RestInvalidateApiKeyAction.java index eb10ec6669e32..b11a0edde42f8 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/RestInvalidateApiKeyAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/apikey/RestInvalidateApiKeyAction.java @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.xpack.security.rest.action; +package org.elasticsearch.xpack.security.rest.action.apikey; import org.elasticsearch.client.node.NodeClient; import org.elasticsearch.common.ParseField; @@ -28,7 +28,7 @@ /** * Rest action to invalidate one or more API keys */ -public final class RestInvalidateApiKeyAction extends SecurityBaseRestHandler { +public final class RestInvalidateApiKeyAction extends ApiKeyBaseRestHandler { static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>("invalidate_api_key", a -> { return new InvalidateApiKeyRequest((String) a[0], (String) a[1], (String) a[2], (String) a[3]); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenAction.java index 94317145b02d8..4734c39bc5af5 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestGetTokenAction.java @@ -30,7 +30,6 @@ import org.elasticsearch.xpack.core.security.action.token.CreateTokenRequest; import org.elasticsearch.xpack.core.security.action.token.CreateTokenResponse; import org.elasticsearch.xpack.core.security.action.token.RefreshTokenAction; -import org.elasticsearch.xpack.security.rest.action.SecurityBaseRestHandler; import java.io.IOException; import java.util.Arrays; @@ -45,7 +44,7 @@ * specification as this aspect does not make the most sense since the response body is * expected to be JSON */ -public final class RestGetTokenAction extends SecurityBaseRestHandler { +public final class RestGetTokenAction extends TokenBaseRestHandler { private static final DeprecationLogger deprecationLogger = new DeprecationLogger(LogManager.getLogger(RestGetTokenAction.class)); static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>("token_request", diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestInvalidateTokenAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestInvalidateTokenAction.java index 9801f3c93c839..cb72e53bcc8d3 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestInvalidateTokenAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/oauth2/RestInvalidateTokenAction.java @@ -24,7 +24,6 @@ import org.elasticsearch.xpack.core.security.action.token.InvalidateTokenAction; import org.elasticsearch.xpack.core.security.action.token.InvalidateTokenRequest; import org.elasticsearch.xpack.core.security.action.token.InvalidateTokenResponse; -import org.elasticsearch.xpack.security.rest.action.SecurityBaseRestHandler; import java.io.IOException; @@ -33,7 +32,7 @@ /** * Rest handler for handling access token invalidation requests */ -public final class RestInvalidateTokenAction extends SecurityBaseRestHandler { +public final class RestInvalidateTokenAction extends TokenBaseRestHandler { private static final DeprecationLogger deprecationLogger = new DeprecationLogger(LogManager.getLogger(RestInvalidateTokenAction.class)); static final ConstructingObjectParser PARSER = diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/oauth2/TokenBaseRestHandler.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/oauth2/TokenBaseRestHandler.java new file mode 100644 index 0000000000000..7111a5387fe5b --- /dev/null +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/oauth2/TokenBaseRestHandler.java @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +package org.elasticsearch.xpack.security.rest.action.oauth2; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.license.LicenseUtils; +import org.elasticsearch.license.XPackLicenseState; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.xpack.security.rest.action.SecurityBaseRestHandler; + +/** + * A base rest handler that handles licensing for Token actions + */ +abstract class TokenBaseRestHandler extends SecurityBaseRestHandler { + + protected Logger logger = LogManager.getLogger(getClass()); + + TokenBaseRestHandler(Settings settings, XPackLicenseState licenseState) { + super(settings, licenseState); + } + + @Override + protected Exception checkFeatureAvailable(RestRequest request) { + Exception failedFeature = super.checkFeatureAvailable(request); + if (failedFeature != null) { + return failedFeature; + } else if (licenseState.isTokenServiceAllowed()) { + return null; + } else { + logger.info("Security tokens are not available under the current [{}] license", licenseState.getOperationMode().description()); + return LicenseUtils.newComplianceException("security tokens"); + } + } +} diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/license/LicensingTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/license/LicensingTests.java index 9fb044d6202ed..02b9cf61e4a39 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/license/LicensingTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/license/LicensingTests.java @@ -133,7 +133,7 @@ protected int maxNumberOfNodes() { @Before public void resetLicensing() throws InterruptedException { - enableLicensing(OperationMode.BASIC); + enableLicensing(OperationMode.MISSING); } @After @@ -230,7 +230,7 @@ public void testSecurityActionsByLicenseType() throws Exception { // enable a license that enables security License.OperationMode mode = randomFrom(License.OperationMode.GOLD, License.OperationMode.TRIAL, - License.OperationMode.PLATINUM, License.OperationMode.STANDARD); + License.OperationMode.PLATINUM, License.OperationMode.STANDARD, OperationMode.BASIC); enableLicensing(mode); // security actions should work! try (TransportClient client = new TestXPackTransportClient(settings, LocalStateSecurity.class)) { diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityFeatureSetTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityFeatureSetTests.java index 2fc2ea8865d91..9bfa0b9a886f3 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityFeatureSetTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityFeatureSetTests.java @@ -78,7 +78,7 @@ public void testEnabled() { rolesStore, roleMappingStore, ipFilter); assertThat(featureSet.enabled(), is(true)); - when(licenseState.isSecurityDisabledByTrialLicense()).thenReturn(true); + when(licenseState.isSecurityDisabledByLicenseDefaults()).thenReturn(true); featureSet = new SecurityFeatureSet(settings, licenseState, realms, rolesStore, roleMappingStore, ipFilter); assertThat(featureSet.enabled(), is(false)); @@ -228,7 +228,7 @@ public void testUsage() throws Exception { public void testUsageOnTrialLicenseWithSecurityDisabledByDefault() throws Exception { when(licenseState.isSecurityAvailable()).thenReturn(true); - when(licenseState.isSecurityDisabledByTrialLicense()).thenReturn(true); + when(licenseState.isSecurityDisabledByLicenseDefaults()).thenReturn(true); Settings.Builder settings = Settings.builder().put(this.settings); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/oidc/TransportOpenIdConnectLogoutActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/oidc/TransportOpenIdConnectLogoutActionTests.java index e31ccc6733290..69cedf6389f7f 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/oidc/TransportOpenIdConnectLogoutActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/oidc/TransportOpenIdConnectLogoutActionTests.java @@ -30,6 +30,7 @@ import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.env.Environment; import org.elasticsearch.env.TestEnvironment; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.tasks.Task; import org.elasticsearch.test.ClusterServiceUtils; import org.elasticsearch.threadpool.ThreadPool; @@ -167,7 +168,12 @@ public void setup() throws Exception { when(securityIndex.isAvailable()).thenReturn(true); final ClusterService clusterService = ClusterServiceUtils.createClusterService(threadPool); - tokenService = new TokenService(settings, Clock.systemUTC(), client, securityIndex, securityIndex, clusterService); + + final XPackLicenseState licenseState = mock(XPackLicenseState.class); + when(licenseState.isTokenServiceAllowed()).thenReturn(true); + + tokenService = new TokenService(settings, Clock.systemUTC(), client, licenseState, + securityIndex, securityIndex, clusterService); final TransportService transportService = new TransportService(Settings.EMPTY, mock(Transport.class), null, TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet()); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlInvalidateSessionActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlInvalidateSessionActionTests.java index 63c58c5ce10e8..3f4ac8942089c 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlInvalidateSessionActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlInvalidateSessionActionTests.java @@ -45,6 +45,7 @@ import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.TermQueryBuilder; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.SearchHits; import org.elasticsearch.tasks.Task; @@ -200,8 +201,11 @@ void doExecute(Action action, Request request, ActionListener null, null, Collections.emptySet()); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlLogoutActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlLogoutActionTests.java index bd1a20db2f196..1652122bf6e80 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlLogoutActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlLogoutActionTests.java @@ -37,6 +37,7 @@ import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.env.Environment; import org.elasticsearch.env.TestEnvironment; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.tasks.Task; import org.elasticsearch.test.ClusterServiceUtils; import org.elasticsearch.threadpool.ThreadPool; @@ -202,8 +203,10 @@ public void setup() throws Exception { }).when(securityIndex).checkIndexVersionThenExecute(any(Consumer.class), any(Runnable.class)); when(securityIndex.isAvailable()).thenReturn(true); + final XPackLicenseState licenseState = mock(XPackLicenseState.class); + when(licenseState.isTokenServiceAllowed()).thenReturn(true); final ClusterService clusterService = ClusterServiceUtils.createClusterService(threadPool); - tokenService = new TokenService(settings, Clock.systemUTC(), client, securityIndex, securityIndex, clusterService); + tokenService = new TokenService(settings, Clock.systemUTC(), client, licenseState, securityIndex, securityIndex, clusterService); final TransportService transportService = new TransportService(Settings.EMPTY, mock(Transport.class), null, TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet()); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenActionTests.java index cea1a532e13c6..c2050c95fc354 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenActionTests.java @@ -29,6 +29,7 @@ import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.index.shard.ShardId; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.node.Node; import org.elasticsearch.test.ClusterServiceUtils; import org.elasticsearch.test.ESTestCase; @@ -72,6 +73,7 @@ public class TransportCreateTokenActionTests extends ESTestCase { private ClusterService clusterService; private AtomicReference idxReqReference; private AuthenticationService authenticationService; + private XPackLicenseState license; @Before public void setupClient() { @@ -137,6 +139,9 @@ public void setupClient() { any(UsernamePasswordToken.class), any(ActionListener.class)); this.clusterService = ClusterServiceUtils.createClusterService(threadPool); + + this.license = mock(XPackLicenseState.class); + when(license.isTokenServiceAllowed()).thenReturn(true); } @After @@ -147,8 +152,8 @@ public void stopThreadPool() throws Exception { } public void testClientCredentialsCreatesWithoutRefreshToken() throws Exception { - final TokenService tokenService = new TokenService(SETTINGS, Clock.systemUTC(), client, securityIndex, securityIndex, - clusterService); + final TokenService tokenService = new TokenService(SETTINGS, Clock.systemUTC(), client, license, + securityIndex, securityIndex, clusterService); Authentication authentication = new Authentication(new User("joe"), new Authentication.RealmRef("realm", "type", "node"), null); authentication.writeToContext(threadPool.getThreadContext()); @@ -172,8 +177,8 @@ public void testClientCredentialsCreatesWithoutRefreshToken() throws Exception { } public void testPasswordGrantTypeCreatesWithRefreshToken() throws Exception { - final TokenService tokenService = new TokenService(SETTINGS, Clock.systemUTC(), client, securityIndex, securityIndex, - clusterService); + final TokenService tokenService = new TokenService(SETTINGS, Clock.systemUTC(), client, license, + securityIndex, securityIndex, clusterService); Authentication authentication = new Authentication(new User("joe"), new Authentication.RealmRef("realm", "type", "node"), null); authentication.writeToContext(threadPool.getThreadContext()); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyServiceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyServiceTests.java index 507cb8349aa46..0491d20d74c8a 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyServiceTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyServiceTests.java @@ -9,6 +9,7 @@ import org.elasticsearch.Version; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.support.PlainActionFuture; +import org.elasticsearch.client.Client; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.common.settings.Settings; @@ -18,10 +19,12 @@ import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.common.xcontent.json.JsonXContent; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.test.ClusterServiceUtils; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.threadpool.TestThreadPool; import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.xpack.core.XPackSettings; import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.authc.Authentication.AuthenticationType; import org.elasticsearch.xpack.core.security.authc.Authentication.RealmRef; @@ -29,17 +32,20 @@ import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilege; -import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore; import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.security.authc.ApiKeyService.ApiKeyCredentials; import org.elasticsearch.xpack.security.authc.ApiKeyService.ApiKeyRoleDescriptors; import org.elasticsearch.xpack.security.authc.ApiKeyService.CachedApiKeyHashResult; import org.elasticsearch.xpack.security.authz.store.NativePrivilegeStore; +import org.elasticsearch.xpack.security.support.SecurityIndexManager; +import org.elasticsearch.xpack.security.test.SecurityMocks; import org.junit.After; import org.junit.Before; +import java.io.IOException; import java.nio.charset.StandardCharsets; import java.time.Clock; +import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Arrays; import java.util.Base64; @@ -48,19 +54,26 @@ import java.util.HashMap; import java.util.Map; +import static org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR; import static org.hamcrest.Matchers.arrayContaining; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.sameInstance; import static org.mockito.Matchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class ApiKeyServiceTests extends ESTestCase { private ThreadPool threadPool; + private XPackLicenseState licenseState; + private Client client; + private SecurityIndexManager securityIndex; @Before public void createThreadPool() { @@ -72,6 +85,15 @@ public void stopThreadPool() { terminate(threadPool); } + @Before + public void setupMocks() { + this.licenseState = mock(XPackLicenseState.class); + when(licenseState.isApiKeyServiceAllowed()).thenReturn(true); + + this.client = mock(Client.class); + this.securityIndex = SecurityMocks.mockSecurityIndexManager(); + } + public void testGetCredentialsFromThreadContext() { ThreadContext threadContext = threadPool.getThreadContext(); assertNull(ApiKeyService.getCredentialsFromHeader(threadContext)); @@ -107,6 +129,57 @@ public void testGetCredentialsFromThreadContext() { } } + public void testAuthenticateWithApiKey() throws Exception { + final Settings settings = Settings.builder().put(XPackSettings.API_KEY_SERVICE_ENABLED_SETTING.getKey(), true).build(); + final ApiKeyService service = createApiKeyService(settings); + + final String id = randomAlphaOfLength(12); + final String key = randomAlphaOfLength(16); + + mockKeyDocument(service, id, key, new User("hulk", "superuser")); + + final AuthenticationResult auth = tryAuthenticate(service, id, key); + assertThat(auth.getStatus(), is(AuthenticationResult.Status.SUCCESS)); + assertThat(auth.getUser(), notNullValue()); + assertThat(auth.getUser().principal(), is("hulk")); + } + + public void testAuthenticationIsSkippedIfLicenseDoesNotAllowIt() throws Exception { + final Settings settings = Settings.builder().put(XPackSettings.API_KEY_SERVICE_ENABLED_SETTING.getKey(), true).build(); + final ApiKeyService service = createApiKeyService(settings); + + final String id = randomAlphaOfLength(12); + final String key = randomAlphaOfLength(16); + + mockKeyDocument(service, id, key, new User(randomAlphaOfLength(6), randomAlphaOfLength(12))); + + when(licenseState.isApiKeyServiceAllowed()).thenReturn(false); + final AuthenticationResult auth = tryAuthenticate(service, id, key); + assertThat(auth.getStatus(), is(AuthenticationResult.Status.CONTINUE)); + assertThat(auth.getUser(), nullValue()); + } + + public void mockKeyDocument(ApiKeyService service, String id, String key, User user) throws IOException { + final Authentication authentication = new Authentication(user, new RealmRef("realm1", "native", "node01"), null, Version.CURRENT); + final XContentBuilder docSource = service.newDocument(new SecureString(key.toCharArray()), "test", authentication, + Collections.singleton(SUPERUSER_ROLE_DESCRIPTOR), Instant.now(), Instant.now().plusSeconds(3600), null, Version.CURRENT); + + SecurityMocks.mockGetRequest(client, id, BytesReference.bytes(docSource)); + } + + private AuthenticationResult tryAuthenticate(ApiKeyService service, String id, String key) throws Exception { + final ThreadContext threadContext = threadPool.getThreadContext(); + final String header = "ApiKey " + Base64.getEncoder().encodeToString((id + ":" + key).getBytes(StandardCharsets.UTF_8)); + threadContext.putHeader("Authorization", header); + + final PlainActionFuture future = new PlainActionFuture<>(); + service.authenticateWithApiKeyIfPresent(threadContext, future); + + final AuthenticationResult auth = future.get(); + assertThat(auth, notNullValue()); + return auth; + } + public void testValidateApiKey() throws Exception { final String apiKey = randomAlphaOfLength(16); Hasher hasher = randomFrom(Hasher.PBKDF2, Hasher.BCRYPT4, Hasher.BCRYPT); @@ -122,8 +195,7 @@ public void testValidateApiKey() throws Exception { sourceMap.put("creator", creatorMap); sourceMap.put("api_key_invalidated", false); - ApiKeyService service = new ApiKeyService(Settings.EMPTY, Clock.systemUTC(), null, null, - ClusterServiceUtils.createClusterService(threadPool), threadPool); + ApiKeyService service = createApiKeyService(Settings.EMPTY); ApiKeyService.ApiKeyCredentials creds = new ApiKeyService.ApiKeyCredentials(randomAlphaOfLength(12), new SecureString(apiKey.toCharArray())); PlainActionFuture future = new PlainActionFuture<>(); @@ -136,7 +208,7 @@ public void testValidateApiKey() throws Exception { assertThat(result.getUser().metadata(), is(Collections.emptyMap())); assertThat(result.getMetadata().get(ApiKeyService.API_KEY_ROLE_DESCRIPTORS_KEY), equalTo(sourceMap.get("role_descriptors"))); assertThat(result.getMetadata().get(ApiKeyService.API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY), - equalTo(sourceMap.get("limited_by_role_descriptors"))); + equalTo(sourceMap.get("limited_by_role_descriptors"))); sourceMap.put("expiration_time", Clock.systemUTC().instant().plus(1L, ChronoUnit.HOURS).toEpochMilli()); future = new PlainActionFuture<>(); @@ -149,7 +221,7 @@ public void testValidateApiKey() throws Exception { assertThat(result.getUser().metadata(), is(Collections.emptyMap())); assertThat(result.getMetadata().get(ApiKeyService.API_KEY_ROLE_DESCRIPTORS_KEY), equalTo(sourceMap.get("role_descriptors"))); assertThat(result.getMetadata().get(ApiKeyService.API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY), - equalTo(sourceMap.get("limited_by_role_descriptors"))); + equalTo(sourceMap.get("limited_by_role_descriptors"))); sourceMap.put("expiration_time", Clock.systemUTC().instant().minus(1L, ChronoUnit.HOURS).toEpochMilli()); future = new PlainActionFuture<>(); @@ -165,7 +237,7 @@ public void testValidateApiKey() throws Exception { result = future.get(); assertNotNull(result); assertFalse(result.isAuthenticated()); - + sourceMap.put("api_key_invalidated", true); creds = new ApiKeyService.ApiKeyCredentials(randomAlphaOfLength(12), new SecureString(randomAlphaOfLength(15).toCharArray())); future = new PlainActionFuture<>(); @@ -179,7 +251,7 @@ public void testGetRolesForApiKeyNotInContext() throws Exception { Map superUserRdMap; try (XContentBuilder builder = JsonXContent.contentBuilder()) { superUserRdMap = XContentHelper.convertToMap(XContentType.JSON.xContent(), - BytesReference.bytes(ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR + BytesReference.bytes(SUPERUSER_ROLE_DESCRIPTOR .toXContent(builder, ToXContent.EMPTY_PARAMS, true)) .streamInput(), false); @@ -187,14 +259,13 @@ public void testGetRolesForApiKeyNotInContext() throws Exception { Map authMetadata = new HashMap<>(); authMetadata.put(ApiKeyService.API_KEY_ID_KEY, randomAlphaOfLength(12)); authMetadata.put(ApiKeyService.API_KEY_ROLE_DESCRIPTORS_KEY, - Collections.singletonMap(ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR.getName(), superUserRdMap)); + Collections.singletonMap(SUPERUSER_ROLE_DESCRIPTOR.getName(), superUserRdMap)); authMetadata.put(ApiKeyService.API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY, - Collections.singletonMap(ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR.getName(), superUserRdMap)); + Collections.singletonMap(SUPERUSER_ROLE_DESCRIPTOR.getName(), superUserRdMap)); final Authentication authentication = new Authentication(new User("joe"), new RealmRef("apikey", "apikey", "node"), null, Version.CURRENT, AuthenticationType.API_KEY, authMetadata); - ApiKeyService service = new ApiKeyService(Settings.EMPTY, Clock.systemUTC(), null, null, - ClusterServiceUtils.createClusterService(threadPool), threadPool); + ApiKeyService service = createApiKeyService(Settings.EMPTY); PlainActionFuture roleFuture = new PlainActionFuture<>(); service.getRoleForApiKey(authentication, roleFuture); @@ -208,22 +279,22 @@ public void testGetRolesForApiKey() throws Exception { authMetadata.put(ApiKeyService.API_KEY_ID_KEY, randomAlphaOfLength(12)); boolean emptyApiKeyRoleDescriptor = randomBoolean(); final RoleDescriptor roleARoleDescriptor = new RoleDescriptor("a role", new String[] { "monitor" }, - new RoleDescriptor.IndicesPrivileges[] { - RoleDescriptor.IndicesPrivileges.builder().indices("*").privileges("monitor").build() }, - null); + new RoleDescriptor.IndicesPrivileges[] { + RoleDescriptor.IndicesPrivileges.builder().indices("*").privileges("monitor").build() }, + null); Map roleARDMap; try (XContentBuilder builder = JsonXContent.contentBuilder()) { roleARDMap = XContentHelper.convertToMap(XContentType.JSON.xContent(), - BytesReference.bytes(roleARoleDescriptor.toXContent(builder, ToXContent.EMPTY_PARAMS, true)).streamInput(), false); + BytesReference.bytes(roleARoleDescriptor.toXContent(builder, ToXContent.EMPTY_PARAMS, true)).streamInput(), false); } authMetadata.put(ApiKeyService.API_KEY_ROLE_DESCRIPTORS_KEY, - (emptyApiKeyRoleDescriptor) ? randomFrom(Arrays.asList(null, Collections.emptyMap())) - : Collections.singletonMap("a role", roleARDMap)); + (emptyApiKeyRoleDescriptor) ? randomFrom(Arrays.asList(null, Collections.emptyMap())) + : Collections.singletonMap("a role", roleARDMap)); final RoleDescriptor limitedRoleDescriptor = new RoleDescriptor("limited role", new String[] { "all" }, - new RoleDescriptor.IndicesPrivileges[] { - RoleDescriptor.IndicesPrivileges.builder().indices("*").privileges("all").build() }, - null); + new RoleDescriptor.IndicesPrivileges[] { + RoleDescriptor.IndicesPrivileges.builder().indices("*").privileges("all").build() }, + null); Map limitedRdMap; try (XContentBuilder builder = JsonXContent.contentBuilder()) { limitedRdMap = XContentHelper.convertToMap(XContentType.JSON.xContent(), @@ -235,7 +306,7 @@ public void testGetRolesForApiKey() throws Exception { authMetadata.put(ApiKeyService.API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY, Collections.singletonMap("limited role", limitedRdMap)); final Authentication authentication = new Authentication(new User("joe"), new RealmRef("apikey", "apikey", "node"), null, - Version.CURRENT, AuthenticationType.API_KEY, authMetadata); + Version.CURRENT, AuthenticationType.API_KEY, authMetadata); final NativePrivilegeStore privilegesStore = mock(NativePrivilegeStore.class); doAnswer(i -> { @@ -247,8 +318,7 @@ public void testGetRolesForApiKey() throws Exception { return null; } ).when(privilegesStore).getPrivileges(any(Collection.class), any(Collection.class), any(ActionListener.class)); - ApiKeyService service = new ApiKeyService(Settings.EMPTY, Clock.systemUTC(), null, null, - ClusterServiceUtils.createClusterService(threadPool), threadPool); + ApiKeyService service = createApiKeyService(Settings.EMPTY); PlainActionFuture roleFuture = new PlainActionFuture<>(); service.getRoleForApiKey(authentication, roleFuture); @@ -280,8 +350,7 @@ public void testApiKeyCache() { sourceMap.put("creator", creatorMap); sourceMap.put("api_key_invalidated", false); - ApiKeyService service = new ApiKeyService(Settings.EMPTY, Clock.systemUTC(), null, null, - ClusterServiceUtils.createClusterService(threadPool), threadPool); + ApiKeyService service = createApiKeyService(Settings.EMPTY); ApiKeyCredentials creds = new ApiKeyCredentials(randomAlphaOfLength(12), new SecureString(apiKey.toCharArray())); PlainActionFuture future = new PlainActionFuture<>(); service.validateApiKeyCredentials(sourceMap, creds, Clock.systemUTC(), future); @@ -345,8 +414,7 @@ public void testApiKeyCacheDisabled() { sourceMap.put("creator", creatorMap); sourceMap.put("api_key_invalidated", false); - ApiKeyService service = new ApiKeyService(settings, Clock.systemUTC(), null, null, - ClusterServiceUtils.createClusterService(threadPool), threadPool); + ApiKeyService service = createApiKeyService(settings); ApiKeyCredentials creds = new ApiKeyCredentials(randomAlphaOfLength(12), new SecureString(apiKey.toCharArray())); PlainActionFuture future = new PlainActionFuture<>(); service.validateApiKeyCredentials(sourceMap, creds, Clock.systemUTC(), future); @@ -355,4 +423,11 @@ public void testApiKeyCacheDisabled() { CachedApiKeyHashResult cachedApiKeyHashResult = service.getFromCache(creds.getId()); assertNull(cachedApiKeyHashResult); } + + private ApiKeyService createApiKeyService(Settings settings) { + return new ApiKeyService(settings, Clock.systemUTC(), client, licenseState, securityIndex, + ClusterServiceUtils.createClusterService(threadPool), threadPool); + } + + } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java index a86edb98251ff..c7994888a2631 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java @@ -179,6 +179,8 @@ public void init() throws Exception { XPackLicenseState licenseState = mock(XPackLicenseState.class); when(licenseState.allowedRealmType()).thenReturn(XPackLicenseState.AllowedRealmType.ALL); when(licenseState.isAuthAllowed()).thenReturn(true); + when(licenseState.isApiKeyServiceAllowed()).thenReturn(true); + when(licenseState.isTokenServiceAllowed()).thenReturn(true); realms = spy(new TestRealms(Settings.EMPTY, TestEnvironment.newEnvironment(settings), Collections.emptyMap(), licenseState, threadContext, mock(ReservedRealm.class), Arrays.asList(firstRealm, secondRealm), Collections.singletonList(firstRealm))); @@ -219,8 +221,8 @@ licenseState, threadContext, mock(ReservedRealm.class), Arrays.asList(firstRealm return null; }).when(securityIndex).checkIndexVersionThenExecute(any(Consumer.class), any(Runnable.class)); ClusterService clusterService = ClusterServiceUtils.createClusterService(threadPool); - apiKeyService = new ApiKeyService(settings, Clock.systemUTC(), client, securityIndex, clusterService, threadPool); - tokenService = new TokenService(settings, Clock.systemUTC(), client, securityIndex, securityIndex, clusterService); + apiKeyService = new ApiKeyService(settings, Clock.systemUTC(), client, licenseState, securityIndex, clusterService, threadPool); + tokenService = new TokenService(settings, Clock.systemUTC(), client, licenseState, securityIndex, securityIndex, clusterService); service = new AuthenticationService(settings, realms, auditTrail, new DefaultAuthenticationFailureHandler(Collections.emptyMap()), threadPool, new AnonymousUser(settings), tokenService, apiKeyService); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/TokenServiceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/TokenServiceTests.java index 494b8070c57d9..7f09444784c6d 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/TokenServiceTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/TokenServiceTests.java @@ -39,6 +39,7 @@ import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.Index; import org.elasticsearch.index.shard.ShardId; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.node.Node; import org.elasticsearch.test.ClusterServiceUtils; import org.elasticsearch.test.ESTestCase; @@ -55,6 +56,8 @@ import org.elasticsearch.xpack.core.watcher.watch.ClockMock; import org.elasticsearch.xpack.security.support.SecurityIndexManager; import org.junit.After; +import org.elasticsearch.xpack.security.test.SecurityMocks; +import org.hamcrest.Matchers; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; @@ -71,7 +74,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.function.Consumer; import javax.crypto.CipherOutputStream; import javax.crypto.SecretKey; @@ -103,6 +105,7 @@ public class TokenServiceTests extends ESTestCase { private DiscoveryNode oldNode; private Settings tokenServiceEnabledSettings = Settings.builder() .put(XPackSettings.TOKEN_SERVICE_ENABLED_SETTING.getKey(), true).build(); + private XPackLicenseState licenseState; @Before public void setupClient() { @@ -128,10 +131,15 @@ public void setupClient() { }).when(client).execute(eq(IndexAction.INSTANCE), any(IndexRequest.class), any(ActionListener.class)); // setup lifecycle service - this.securityMainIndex = mockSecurityManager(); - this.securityTokensIndex = mockSecurityManager(); + this.securityMainIndex = SecurityMocks.mockSecurityIndexManager(); + this.securityTokensIndex = SecurityMocks.mockSecurityIndexManager(); this.clusterService = ClusterServiceUtils.createClusterService(threadPool); - // version 7.1 was an "inflection" point in the Token Service development (access_tokens as UUIDS, multiple concurrent refreshes, + + // License state (enabled by default) + licenseState = mock(XPackLicenseState.class); + when(licenseState.isTokenServiceAllowed()).thenReturn(true); + + // version 7.2 was an "inflection" point in the Token Service development (access_tokens as UUIDS, multiple concurrent refreshes, // tokens docs on a separate index), let's test the TokenService works in a mixed cluster with nodes with versions prior to these // developments if (randomBoolean()) { @@ -159,8 +167,7 @@ public static void shutdownThreadpool() throws InterruptedException { } public void testAttachAndGetToken() throws Exception { - TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex, - securityTokensIndex, clusterService); + TokenService tokenService = createTokenService(tokenServiceEnabledSettings, systemUTC()); Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null); PlainActionFuture> tokenFuture = new PlainActionFuture<>(); tokenService.createOAuth2Tokens(authentication, authentication, Collections.emptyMap(), true, tokenFuture); @@ -181,8 +188,7 @@ public void testAttachAndGetToken() throws Exception { try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) { // verify a second separate token service with its own salt can also verify - TokenService anotherService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex, - securityTokensIndex, clusterService); + TokenService anotherService = createTokenService(tokenServiceEnabledSettings, systemUTC()); anotherService.refreshMetaData(tokenService.getTokenMetaData()); PlainActionFuture future = new PlainActionFuture<>(); anotherService.getAndValidateToken(requestContext, future); @@ -192,8 +198,7 @@ public void testAttachAndGetToken() throws Exception { } public void testInvalidAuthorizationHeader() throws Exception { - TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex, - securityTokensIndex, clusterService); + TokenService tokenService = createTokenService(tokenServiceEnabledSettings, systemUTC()); ThreadContext requestContext = new ThreadContext(Settings.EMPTY); String token = randomFrom("", " "); String authScheme = randomFrom("Bearer ", "BEARER ", "bearer ", "Basic "); @@ -208,8 +213,7 @@ public void testInvalidAuthorizationHeader() throws Exception { } public void testRotateKey() throws Exception { - TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex, - securityTokensIndex, clusterService); + TokenService tokenService = createTokenService(tokenServiceEnabledSettings, systemUTC()); Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null); PlainActionFuture> tokenFuture = new PlainActionFuture<>(); tokenService.createOAuth2Tokens(authentication, authentication, Collections.emptyMap(), true, tokenFuture); @@ -219,7 +223,7 @@ public void testRotateKey() throws Exception { authentication = token.getAuthentication(); ThreadContext requestContext = new ThreadContext(Settings.EMPTY); - requestContext.putHeader("Authorization", "Bearer " + getDeprecatedAccessTokenString(tokenService, token)); + storeTokenHeader(requestContext, getDeprecatedAccessTokenString(tokenService, token)); try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) { PlainActionFuture future = new PlainActionFuture<>(); @@ -243,7 +247,7 @@ public void testRotateKey() throws Exception { assertNotEquals(getDeprecatedAccessTokenString(tokenService, newToken), getDeprecatedAccessTokenString(tokenService, token)); requestContext = new ThreadContext(Settings.EMPTY); - requestContext.putHeader("Authorization", "Bearer " + getDeprecatedAccessTokenString(tokenService, newToken)); + storeTokenHeader(requestContext, getDeprecatedAccessTokenString(tokenService, newToken)); mockGetTokenFromId(newToken, false); try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) { @@ -262,14 +266,12 @@ private void rotateKeys(TokenService tokenService) { } public void testKeyExchange() throws Exception { - TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex, - securityTokensIndex, clusterService); + TokenService tokenService = createTokenService(tokenServiceEnabledSettings, systemUTC()); int numRotations = randomIntBetween(1, 5); for (int i = 0; i < numRotations; i++) { rotateKeys(tokenService); } - TokenService otherTokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex, - securityTokensIndex, clusterService); + TokenService otherTokenService = createTokenService(tokenServiceEnabledSettings, systemUTC()); otherTokenService.refreshMetaData(tokenService.getTokenMetaData()); Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null); PlainActionFuture> tokenFuture = new PlainActionFuture<>(); @@ -280,7 +282,7 @@ public void testKeyExchange() throws Exception { authentication = token.getAuthentication(); ThreadContext requestContext = new ThreadContext(Settings.EMPTY); - requestContext.putHeader("Authorization", "Bearer " + getDeprecatedAccessTokenString(tokenService, token)); + storeTokenHeader(requestContext, getDeprecatedAccessTokenString(tokenService, token)); try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) { PlainActionFuture future = new PlainActionFuture<>(); otherTokenService.getAndValidateToken(requestContext, future); @@ -301,8 +303,7 @@ public void testKeyExchange() throws Exception { } public void testPruneKeys() throws Exception { - TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex, - securityTokensIndex, clusterService); + TokenService tokenService = createTokenService(tokenServiceEnabledSettings, systemUTC()); Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null); PlainActionFuture> tokenFuture = new PlainActionFuture<>(); tokenService.createOAuth2Tokens(authentication, authentication, Collections.emptyMap(), true, tokenFuture); @@ -312,7 +313,7 @@ public void testPruneKeys() throws Exception { authentication = token.getAuthentication(); ThreadContext requestContext = new ThreadContext(Settings.EMPTY); - requestContext.putHeader("Authorization", "Bearer " + getDeprecatedAccessTokenString(tokenService, token)); + storeTokenHeader(requestContext, getDeprecatedAccessTokenString(tokenService, token)); try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) { PlainActionFuture future = new PlainActionFuture<>(); @@ -352,7 +353,7 @@ public void testPruneKeys() throws Exception { } requestContext = new ThreadContext(Settings.EMPTY); - requestContext.putHeader("Authorization", "Bearer " + getDeprecatedAccessTokenString(tokenService, newToken)); + storeTokenHeader(requestContext, getDeprecatedAccessTokenString(tokenService, newToken)); mockGetTokenFromId(newToken, false); try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) { PlainActionFuture future = new PlainActionFuture<>(); @@ -364,8 +365,7 @@ public void testPruneKeys() throws Exception { } public void testPassphraseWorks() throws Exception { - TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex, - securityTokensIndex, clusterService); + TokenService tokenService = createTokenService(tokenServiceEnabledSettings, systemUTC()); Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null); PlainActionFuture> tokenFuture = new PlainActionFuture<>(); tokenService.createOAuth2Tokens(authentication, authentication, Collections.emptyMap(), true, tokenFuture); @@ -375,7 +375,7 @@ public void testPassphraseWorks() throws Exception { authentication = token.getAuthentication(); ThreadContext requestContext = new ThreadContext(Settings.EMPTY); - requestContext.putHeader("Authorization", "Bearer " + getDeprecatedAccessTokenString(tokenService, token)); + storeTokenHeader(requestContext, getDeprecatedAccessTokenString(tokenService, token)); try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) { PlainActionFuture future = new PlainActionFuture<>(); @@ -386,8 +386,7 @@ public void testPassphraseWorks() throws Exception { try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) { // verify a second separate token service with its own passphrase cannot verify - TokenService anotherService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex, - securityTokensIndex, clusterService); + TokenService anotherService = createTokenService(tokenServiceEnabledSettings, systemUTC()); PlainActionFuture future = new PlainActionFuture<>(); anotherService.getAndValidateToken(requestContext, future); assertNull(future.get()); @@ -395,8 +394,7 @@ public void testPassphraseWorks() throws Exception { } public void testGetTokenWhenKeyCacheHasExpired() throws Exception { - TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex, - securityTokensIndex, clusterService); + TokenService tokenService = createTokenService(tokenServiceEnabledSettings, systemUTC()); Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null); PlainActionFuture> tokenFuture = new PlainActionFuture<>(); @@ -410,8 +408,7 @@ public void testGetTokenWhenKeyCacheHasExpired() throws Exception { public void testInvalidatedToken() throws Exception { when(securityMainIndex.indexExists()).thenReturn(true); - TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex, - securityTokensIndex, clusterService); + TokenService tokenService = createTokenService(tokenServiceEnabledSettings, systemUTC()); Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null); PlainActionFuture> tokenFuture = new PlainActionFuture<>(); tokenService.createOAuth2Tokens(authentication, authentication, Collections.emptyMap(), true, tokenFuture); @@ -420,7 +417,7 @@ public void testInvalidatedToken() throws Exception { mockGetTokenFromId(token, true); ThreadContext requestContext = new ThreadContext(Settings.EMPTY); - requestContext.putHeader("Authorization", "Bearer " + tokenService.getAccessTokenAsString(token)); + storeTokenHeader(requestContext, tokenService.getAccessTokenAsString(token)); try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) { PlainActionFuture future = new PlainActionFuture<>(); @@ -432,6 +429,10 @@ public void testInvalidatedToken() throws Exception { } } + private void storeTokenHeader(ThreadContext requestContext, String tokenString) throws IOException, GeneralSecurityException { + requestContext.putHeader("Authorization", "Bearer " + tokenString); + } + public void testComputeSecretKeyIsConsistent() throws Exception { byte[] saltArr = new byte[32]; random().nextBytes(saltArr); @@ -465,8 +466,7 @@ public void testTokenExpiryConfig() { public void testTokenExpiry() throws Exception { ClockMock clock = ClockMock.frozen(); - TokenService tokenService = new TokenService(tokenServiceEnabledSettings, clock, client, securityMainIndex, securityTokensIndex, - clusterService); + TokenService tokenService = createTokenService(tokenServiceEnabledSettings, clock); Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null); PlainActionFuture> tokenFuture = new PlainActionFuture<>(); tokenService.createOAuth2Tokens(authentication, authentication, Collections.emptyMap(), true, tokenFuture); @@ -475,7 +475,7 @@ public void testTokenExpiry() throws Exception { authentication = token.getAuthentication(); ThreadContext requestContext = new ThreadContext(Settings.EMPTY); - requestContext.putHeader("Authorization", "Bearer " + tokenService.getAccessTokenAsString(token)); + storeTokenHeader(requestContext, tokenService.getAccessTokenAsString(token)); try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) { // the clock is still frozen, so the cookie should be valid @@ -519,10 +519,10 @@ public void testTokenServiceDisabled() throws Exception { TokenService tokenService = new TokenService(Settings.builder() .put(XPackSettings.TOKEN_SERVICE_ENABLED_SETTING.getKey(), false) .build(), - Clock.systemUTC(), client, securityMainIndex, securityTokensIndex, clusterService); + Clock.systemUTC(), client, licenseState, securityMainIndex, securityTokensIndex, clusterService); IllegalStateException e = expectThrows(IllegalStateException.class, () -> tokenService.createOAuth2Tokens(null, null, null, true, null)); - assertEquals("tokens are not enabled", e.getMessage()); + assertEquals("security tokens are not enabled", e.getMessage()); PlainActionFuture future = new PlainActionFuture<>(); tokenService.getAndValidateToken(null, future); @@ -533,7 +533,7 @@ public void testTokenServiceDisabled() throws Exception { tokenService.invalidateAccessToken((String) null, invalidateFuture); invalidateFuture.actionGet(); }); - assertEquals("tokens are not enabled", e.getMessage()); + assertEquals("security tokens are not enabled", e.getMessage()); } public void testBytesKeyEqualsHashCode() { @@ -562,11 +562,10 @@ public void testMalformedToken() throws Exception { final int numBytes = randomIntBetween(1, TokenService.MINIMUM_BYTES + 32); final byte[] randomBytes = new byte[numBytes]; random().nextBytes(randomBytes); - TokenService tokenService = new TokenService(Settings.EMPTY, systemUTC(), client, securityMainIndex, securityTokensIndex, - clusterService); + TokenService tokenService = createTokenService(Settings.EMPTY, systemUTC()); ThreadContext requestContext = new ThreadContext(Settings.EMPTY); - requestContext.putHeader("Authorization", "Bearer " + Base64.getEncoder().encodeToString(randomBytes)); + storeTokenHeader(requestContext, Base64.getEncoder().encodeToString(randomBytes)); try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) { PlainActionFuture future = new PlainActionFuture<>(); @@ -576,8 +575,7 @@ public void testMalformedToken() throws Exception { } public void testIndexNotAvailable() throws Exception { - TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex, - securityTokensIndex, clusterService); + TokenService tokenService = createTokenService(tokenServiceEnabledSettings, systemUTC()); Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null); PlainActionFuture> tokenFuture = new PlainActionFuture<>(); tokenService.createOAuth2Tokens(authentication, authentication, Collections.emptyMap(), true, tokenFuture); @@ -586,7 +584,7 @@ public void testIndexNotAvailable() throws Exception { //mockGetTokenFromId(token, false); ThreadContext requestContext = new ThreadContext(Settings.EMPTY); - requestContext.putHeader("Authorization", "Bearer " + tokenService.getAccessTokenAsString(token)); + storeTokenHeader(requestContext, tokenService.getAccessTokenAsString(token)); doAnswer(invocationOnMock -> { ActionListener listener = (ActionListener) invocationOnMock.getArguments()[1]; @@ -630,8 +628,7 @@ public void testIndexNotAvailable() throws Exception { } public void testGetAuthenticationWorksWithExpiredUserToken() throws Exception { - TokenService tokenService = new TokenService(tokenServiceEnabledSettings, Clock.systemUTC(), client, securityMainIndex, - securityTokensIndex, clusterService); + TokenService tokenService = createTokenService(tokenServiceEnabledSettings, Clock.systemUTC()); Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null); UserToken expired = new UserToken(authentication, Instant.now().minus(3L, ChronoUnit.DAYS)); mockGetTokenFromId(expired, false); @@ -642,6 +639,27 @@ public void testGetAuthenticationWorksWithExpiredUserToken() throws Exception { assertEquals(authentication, retrievedAuth); } + public void testCannotValidateTokenIfLicenseDoesNotAllowTokens() throws Exception { + when(licenseState.isTokenServiceAllowed()).thenReturn(true); + TokenService tokenService = createTokenService(tokenServiceEnabledSettings, Clock.systemUTC()); + Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null); + UserToken token = new UserToken(authentication, Instant.now().plusSeconds(180)); + mockGetTokenFromId(token, false); + + final ThreadContext threadContext = new ThreadContext(Settings.EMPTY); + storeTokenHeader(threadContext, tokenService.getAccessTokenAsString(token)); + + PlainActionFuture authFuture = new PlainActionFuture<>(); + when(licenseState.isTokenServiceAllowed()).thenReturn(false); + tokenService.getAndValidateToken(threadContext, authFuture); + UserToken authToken = authFuture.actionGet(); + assertThat(authToken, Matchers.nullValue()); + } + + private TokenService createTokenService(Settings settings, Clock clock) throws GeneralSecurityException { + return new TokenService(settings, clock, client, licenseState, securityMainIndex, securityTokensIndex, clusterService); + } + private void mockGetTokenFromId(UserToken userToken, boolean isExpired) { mockGetTokenFromId(userToken, isExpired, client); } @@ -700,23 +718,6 @@ protected String getDeprecatedAccessTokenString(TokenService tokenService, UserT } } - private SecurityIndexManager mockSecurityManager() { - SecurityIndexManager mockSecurityIndex = mock(SecurityIndexManager.class); - doAnswer(invocationOnMock -> { - Runnable runnable = (Runnable) invocationOnMock.getArguments()[1]; - runnable.run(); - return null; - }).when(mockSecurityIndex).prepareIndexIfNeededThenExecute(any(Consumer.class), any(Runnable.class)); - doAnswer(invocationOnMock -> { - Runnable runnable = (Runnable) invocationOnMock.getArguments()[1]; - runnable.run(); - return null; - }).when(mockSecurityIndex).checkIndexVersionThenExecute(any(Consumer.class), any(Runnable.class)); - when(mockSecurityIndex.indexExists()).thenReturn(true); - when(mockSecurityIndex.isAvailable()).thenReturn(true); - return mockSecurityIndex; - } - private DiscoveryNode addAnotherDataNodeWithVersion(ClusterService clusterService, Version version) { final ClusterState currentState = clusterService.state(); final DiscoveryNodes.Builder discoBuilder = DiscoveryNodes.builder(currentState.getNodes()); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/mapper/ExpressionRoleMappingTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/mapper/ExpressionRoleMappingTests.java index 42652676d39f1..3905bb6d3b4c1 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/mapper/ExpressionRoleMappingTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/mapper/ExpressionRoleMappingTests.java @@ -288,7 +288,7 @@ public void testToXContentWithTemplates() throws Exception { public void testSerialization() throws Exception { final ExpressionRoleMapping original = randomRoleMapping(true); - final Version version = VersionUtils.randomVersionBetween(random(), Version.V_7_1_0, null); + final Version version = VersionUtils.randomVersionBetween(random(), Version.V_7_2_0, null); BytesStreamOutput output = new BytesStreamOutput(); output.setVersion(version); original.writeTo(output); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/SecurityBaseRestHandlerTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/SecurityBaseRestHandlerTests.java index 4ff582f01bd88..4b40d165b5e49 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/SecurityBaseRestHandlerTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/SecurityBaseRestHandlerTests.java @@ -7,6 +7,7 @@ import org.elasticsearch.client.node.NodeClient; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.license.License; import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.rest.RestRequest; import org.elasticsearch.test.ESTestCase; @@ -24,11 +25,13 @@ public class SecurityBaseRestHandlerTests extends ESTestCase { public void testSecurityBaseRestHandlerChecksLicenseState() throws Exception { - final boolean securityDisabledByTrial = randomBoolean(); + final boolean securityDisabledByLicenseDefaults = randomBoolean(); final AtomicBoolean consumerCalled = new AtomicBoolean(false); final XPackLicenseState licenseState = mock(XPackLicenseState.class); when(licenseState.isSecurityAvailable()).thenReturn(true); - when(licenseState.isSecurityDisabledByTrialLicense()).thenReturn(securityDisabledByTrial); + when(licenseState.isSecurityDisabledByLicenseDefaults()).thenReturn(securityDisabledByLicenseDefaults); + when(licenseState.getOperationMode()).thenReturn( + randomFrom(License.OperationMode.BASIC, License.OperationMode.STANDARD, License.OperationMode.GOLD)); SecurityBaseRestHandler handler = new SecurityBaseRestHandler(Settings.EMPTY, licenseState) { @Override @@ -46,7 +49,7 @@ protected RestChannelConsumer innerPrepareRequest(RestRequest request, NodeClien } }; FakeRestRequest fakeRestRequest = new FakeRestRequest(); - FakeRestChannel fakeRestChannel = new FakeRestChannel(fakeRestRequest, randomBoolean(), securityDisabledByTrial ? 1 : 0); + FakeRestChannel fakeRestChannel = new FakeRestChannel(fakeRestRequest, randomBoolean(), securityDisabledByLicenseDefaults ? 1 : 0); NodeClient client = mock(NodeClient.class); assertFalse(consumerCalled.get()); @@ -54,7 +57,7 @@ protected RestChannelConsumer innerPrepareRequest(RestRequest request, NodeClien handler.handleRequest(fakeRestRequest, fakeRestChannel, client); verify(licenseState).isSecurityAvailable(); - if (securityDisabledByTrial == false) { + if (securityDisabledByLicenseDefaults == false) { assertTrue(consumerCalled.get()); assertEquals(0, fakeRestChannel.responses().get()); assertEquals(0, fakeRestChannel.errors().get()); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/RestCreateApiKeyActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/apikey/RestCreateApiKeyActionTests.java similarity index 95% rename from x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/RestCreateApiKeyActionTests.java rename to x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/apikey/RestCreateApiKeyActionTests.java index 1b1b0fe8f0f1a..394c9747b6daf 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/RestCreateApiKeyActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/apikey/RestCreateApiKeyActionTests.java @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.xpack.security.rest.action; +package org.elasticsearch.xpack.security.rest.action.apikey; import org.apache.lucene.util.SetOnce; import org.elasticsearch.ElasticsearchSecurityException; @@ -30,6 +30,7 @@ import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.xpack.core.security.action.CreateApiKeyRequest; import org.elasticsearch.xpack.core.security.action.CreateApiKeyResponse; +import org.elasticsearch.xpack.security.rest.action.apikey.RestCreateApiKeyAction; import java.time.Duration; import java.time.Instant; @@ -56,6 +57,7 @@ public void setUp() throws Exception { .build(); threadPool = new ThreadPool(settings); when(mockLicenseState.isSecurityAvailable()).thenReturn(true); + when(mockLicenseState.isApiKeyServiceAllowed()).thenReturn(true); } @Override diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/RestGetApiKeyActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/apikey/RestGetApiKeyActionTests.java similarity index 97% rename from x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/RestGetApiKeyActionTests.java rename to x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/apikey/RestGetApiKeyActionTests.java index 533fa6195edc2..9788bc1a5b22f 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/RestGetApiKeyActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/apikey/RestGetApiKeyActionTests.java @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.xpack.security.rest.action; +package org.elasticsearch.xpack.security.rest.action.apikey; import org.apache.lucene.util.SetOnce; import org.elasticsearch.ElasticsearchSecurityException; @@ -31,6 +31,7 @@ import org.elasticsearch.xpack.core.security.action.ApiKey; import org.elasticsearch.xpack.core.security.action.GetApiKeyRequest; import org.elasticsearch.xpack.core.security.action.GetApiKeyResponse; +import org.elasticsearch.xpack.security.rest.action.apikey.RestGetApiKeyAction; import java.time.Instant; import java.time.temporal.ChronoUnit; @@ -56,6 +57,7 @@ public void setUp() throws Exception { .put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toString()).build(); threadPool = new ThreadPool(settings); when(mockLicenseState.isSecurityAvailable()).thenReturn(true); + when(mockLicenseState.isApiKeyServiceAllowed()).thenReturn(true); } @Override diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/RestInvalidateApiKeyActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/apikey/RestInvalidateApiKeyActionTests.java similarity index 96% rename from x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/RestInvalidateApiKeyActionTests.java rename to x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/apikey/RestInvalidateApiKeyActionTests.java index 6a8a60ae2a999..e73f4e3c210d4 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/RestInvalidateApiKeyActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/apikey/RestInvalidateApiKeyActionTests.java @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.xpack.security.rest.action; +package org.elasticsearch.xpack.security.rest.action.apikey; import org.apache.lucene.util.SetOnce; import org.elasticsearch.ElasticsearchSecurityException; @@ -29,6 +29,7 @@ import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyRequest; import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyResponse; +import org.elasticsearch.xpack.security.rest.action.apikey.RestInvalidateApiKeyAction; import java.util.Collections; @@ -52,6 +53,7 @@ public void setUp() throws Exception { .build(); threadPool = new ThreadPool(settings); when(mockLicenseState.isSecurityAvailable()).thenReturn(true); + when(mockLicenseState.isApiKeyServiceAllowed()).thenReturn(true); } @Override diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/saml/SamlBaseRestHandlerTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/saml/SamlBaseRestHandlerTests.java index 5b442deacf6e7..66993c2269dfd 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/saml/SamlBaseRestHandlerTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/saml/SamlBaseRestHandlerTests.java @@ -27,16 +27,9 @@ public void testSamlAvailableOnTrialAndPlatinum() { assertThat(handler.checkFeatureAvailable(new FakeRestRequest()), Matchers.nullValue()); } - public void testSecurityNotAvailableOnBasic() { - final SamlBaseRestHandler handler = buildHandler(License.OperationMode.BASIC); - Exception e = handler.checkFeatureAvailable(new FakeRestRequest()); - assertThat(e, instanceOf(ElasticsearchException.class)); - ElasticsearchException elasticsearchException = (ElasticsearchException) e; - assertThat(elasticsearchException.getMetadata(LicenseUtils.EXPIRED_FEATURE_METADATA), contains("security")); - } - - public void testSamlNotAvailableOnStandardOrGold() { - final SamlBaseRestHandler handler = buildHandler(randomFrom(License.OperationMode.STANDARD, License.OperationMode.GOLD)); + public void testSamlNotAvailableOnBasicStandardOrGold() { + final SamlBaseRestHandler handler = buildHandler(randomFrom(License.OperationMode.BASIC, License.OperationMode.STANDARD, + License.OperationMode.GOLD)); Exception e = handler.checkFeatureAvailable(new FakeRestRequest()); assertThat(e, instanceOf(ElasticsearchException.class)); ElasticsearchException elasticsearchException = (ElasticsearchException) e; diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/test/SecurityMocks.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/test/SecurityMocks.java new file mode 100644 index 0000000000000..3476a3d7c00a3 --- /dev/null +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/test/SecurityMocks.java @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +package org.elasticsearch.xpack.security.test; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.get.GetAction; +import org.elasticsearch.action.get.GetRequest; +import org.elasticsearch.action.get.GetRequestBuilder; +import org.elasticsearch.action.get.GetResponse; +import org.elasticsearch.client.Client; +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.index.get.GetResult; +import org.elasticsearch.xpack.security.support.SecurityIndexManager; +import org.junit.Assert; + +import java.util.function.Consumer; + +import static java.util.Collections.emptyMap; +import static org.elasticsearch.index.mapper.MapperService.SINGLE_MAPPING_NAME; +import static org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames.SECURITY_MAIN_ALIAS; +import static org.hamcrest.Matchers.arrayWithSize; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Utility class for constructing commonly used mock objects. + * Note to maintainers: + * It is not intended that this class cover _all_ mocking scenarios. Consider very carefully before adding methods to this class that are + * only used in one or 2 places. This class is intended for the situations where a common piece of complex mock code is used in multiple + * test suites. + */ +public final class SecurityMocks { + + private SecurityMocks() { + throw new IllegalStateException("Cannot instantiate utility class"); + } + + public static SecurityIndexManager mockSecurityIndexManager() { + return mockSecurityIndexManager(true, true); + } + + public static SecurityIndexManager mockSecurityIndexManager(boolean exists, boolean available) { + final SecurityIndexManager securityIndexManager = mock(SecurityIndexManager.class); + doAnswer(invocationOnMock -> { + Runnable runnable = (Runnable) invocationOnMock.getArguments()[1]; + runnable.run(); + return null; + }).when(securityIndexManager).prepareIndexIfNeededThenExecute(any(Consumer.class), any(Runnable.class)); + doAnswer(invocationOnMock -> { + Runnable runnable = (Runnable) invocationOnMock.getArguments()[1]; + runnable.run(); + return null; + }).when(securityIndexManager).checkIndexVersionThenExecute(any(Consumer.class), any(Runnable.class)); + when(securityIndexManager.indexExists()).thenReturn(exists); + when(securityIndexManager.isAvailable()).thenReturn(available); + return securityIndexManager; + } + + public static void mockGetRequest(Client client, String documentId, BytesReference source) { + GetResult result = new GetResult(SECURITY_MAIN_ALIAS, SINGLE_MAPPING_NAME, documentId, 0, 1, 1, true, source, emptyMap()); + mockGetRequest(client, documentId, result); + } + + public static void mockGetRequest(Client client, String documentId, GetResult result) { + final GetRequestBuilder requestBuilder = new GetRequestBuilder(client, GetAction.INSTANCE); + requestBuilder.setIndex(SECURITY_MAIN_ALIAS); + requestBuilder.setType(SINGLE_MAPPING_NAME); + requestBuilder.setId(documentId); + when(client.prepareGet(SECURITY_MAIN_ALIAS, SINGLE_MAPPING_NAME, documentId)).thenReturn(requestBuilder); + + doAnswer(inv -> { + Assert.assertThat(inv.getArguments(), arrayWithSize(2)); + Assert.assertThat(inv.getArguments()[0], instanceOf(GetRequest.class)); + final GetRequest request = (GetRequest) inv.getArguments()[0]; + Assert.assertThat(request.id(), equalTo(documentId)); + Assert.assertThat(request.index(), equalTo(SECURITY_MAIN_ALIAS)); + Assert.assertThat(request.type(), equalTo(SINGLE_MAPPING_NAME)); + + Assert.assertThat(inv.getArguments()[1], instanceOf(ActionListener.class)); + ActionListener listener = (ActionListener) inv.getArguments()[1]; + listener.onResponse(new GetResponse(result)); + + return null; + }).when(client).get(any(GetRequest.class), any(ActionListener.class)); + } +} diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/test/rollup/rollup_search.yml b/x-pack/plugin/src/test/resources/rest-api-spec/test/rollup/rollup_search.yml index be9c9f4a41e1c..0f3488d146a00 100644 --- a/x-pack/plugin/src/test/resources/rest-api-spec/test/rollup/rollup_search.yml +++ b/x-pack/plugin/src/test/resources/rest-api-spec/test/rollup/rollup_search.yml @@ -884,8 +884,8 @@ setup: --- "Obsolete Timezone": - skip: - version: " - 7.0.99" - reason: "IANA TZ deprecations in 7.1" + version: " - 7.1.99" + reason: "IANA TZ deprecations in 7.2" features: "warnings" - do: indices.create: @@ -1032,8 +1032,8 @@ setup: --- "Obsolete BWC Timezone": - skip: - version: " - 7.0.99" - reason: "IANA TZ deprecations in 7.1" + version: " - 7.1.99" + reason: "IANA TZ deprecations in 7.2" - do: indices.create: index: tz_rollup