Browse Source

Fail downsampling if DLS or FLS is defined on the source index (#90593)

We fail downsampling if field level security or document level security
restrict access to fields and/or documents in the source index.
This is done mainly to prevent situations where a user not allowed to
read documents and/or fields on the source index is (by mistake) allowed
access to documents and/or fields in the target index, which would normally
not be allowed access to.

We also add YAML test for the following four scenarios:
1. Donwsample operation executed by a non-admin user
2. Downsample operation executed by an admin with field level security
3. Downsample operation executed by an admin with document level security
4. Downsample operation executed by an admin without field or document level security
Salvatore Campagna 3 years ago
parent
commit
bb73711b4b

+ 5 - 0
docs/changelog/90593.yaml

@@ -0,0 +1,5 @@
+pr: 90593
+summary: Test downsample runtime fields and security
+area: Rollup
+type: enhancement
+issues: []

+ 48 - 0
rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/tsdb/15_timestamp_mapping.yml

@@ -339,3 +339,51 @@ explicitly enable timestamp meta field:
   - match: { "test.mappings.properties.@timestamp.type": date }
   - match: { "test.mappings.properties.@timestamp.meta.field_meta": time_series }
   - match: { 'test.mappings._data_stream_timestamp.enabled': true }
+
+---
+unable to create a time series index with @timestamp runtime field:
+  - skip:
+      version: " - 8.1.99"
+      reason: tsdb indexing changed in 8.2.0
+
+  - do:
+      catch: '/docvalues not found for index sort field:\[\@timestamp\]/'
+      indices.create:
+        index: test_1
+        body:
+          settings:
+            number_of_shards: 1
+            number_of_replicas: 0
+            index:
+              mode: time_series
+              routing_path: [metricset, k8s.pod.uid]
+              time_series:
+                start_time: 2021-04-28T00:00:00Z
+                end_time: 2021-04-29T00:00:00Z
+          mappings:
+            runtime:
+              "@timestamp":
+                type: date
+            properties:
+              "@timestamp":
+                type: date
+              metricset:
+                type: keyword
+                time_series_dimension: true
+              k8s:
+                properties:
+                  pod:
+                    properties:
+                      uid:
+                        type: keyword
+                        time_series_dimension: true
+                      name:
+                        type: keyword
+                      network:
+                        properties:
+                          tx:
+                            type: long
+                            time_series_metric: gauge
+                          rx:
+                            type: long
+                            time_series_metric: gauge

+ 49 - 0
rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/tsdb/20_mapping.yml

@@ -364,3 +364,52 @@ nested fields:
                 properties:
                   foo:
                     type: keyword
+
+---
+"Unable to define a metric type for a runtime field":
+  - skip:
+      version: " - 8.4.99"
+      reason: "downsample introduced in 8.5.0"
+
+  - do:
+      catch: '/unknown parameter \[time_series_metric\] on runtime field \[counter\] of type \[long\]/'
+      indices.create:
+        index: test_3
+        body:
+          settings:
+            number_of_shards: 1
+            number_of_replicas: 0
+            index:
+              mode: time_series
+              routing_path: [metricset, k8s.pod.uid]
+              time_series:
+                start_time: 2021-04-28T00:00:00Z
+                end_time: 2021-04-29T00:00:00Z
+          mappings:
+            runtime:
+              counter:
+                type: long
+                time_series_metric: counter
+            properties:
+              "@timestamp":
+                type: date
+              metricset:
+                type: keyword
+                time_series_dimension: true
+              k8s:
+                properties:
+                  pod:
+                    properties:
+                      uid:
+                        type: keyword
+                        time_series_dimension: true
+                      name:
+                        type: keyword
+                      network:
+                        properties:
+                          tx:
+                            type: long
+                            time_series_metric: gauge
+                          rx:
+                            type: long
+                            time_series_metric: gauge

+ 51 - 0
rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/tsdb/70_dimension_types.yml

@@ -220,3 +220,54 @@ ip dimension:
   - match: {aggregations.tsids.buckets.1.key: { ip: "2001:db8:85a3::8a2e:370:7334", metricset: aa }}
   - match: {aggregations.tsids.buckets.1.doc_count: 4}
   - close_to: {aggregations.tsids.buckets.1.voltage.value: { value: 3.3, error: 0.01 }}
+
+---
+runtime time series dimension:
+  - skip:
+      version: " - 8.4.99"
+      reason: "downsample introduced in 8.5.0"
+
+  - do:
+      catch: '/unknown parameter \[time_series_dimension\] on runtime field \[metricset\] of type \[keyword\]/'
+      indices.create:
+        index: test_2
+        body:
+          settings:
+            number_of_shards: 1
+            number_of_replicas: 0
+            index:
+              mode: time_series
+              routing_path: [metricset, k8s.pod.uid]
+              time_series:
+                start_time: 2021-04-28T00:00:00Z
+                end_time: 2021-04-29T00:00:00Z
+          mappings:
+            runtime:
+              metricset:
+                type: keyword
+                time_series_dimension: true
+                script:
+                  source: "emit('pod');"
+            properties:
+              time:
+                type: date
+              metricset:
+                type: keyword
+                time_series_dimension: true
+              k8s:
+                properties:
+                  pod:
+                    properties:
+                      uid:
+                        type: keyword
+                        time_series_dimension: true
+                      name:
+                        type: keyword
+                      network:
+                        properties:
+                          tx:
+                            type: long
+                            time_series_metric: gauge
+                          rx:
+                            type: long
+                            time_series_metric: gauge

+ 69 - 0
x-pack/plugin/rollup/qa/rest/src/yamlRestTest/resources/rest-api-spec/test/rollup/10_basic.yml

@@ -208,6 +208,75 @@ setup:
           {
             "fixed_interval": "1h"
           }
+---
+"Downsample using multiple indices":
+  - skip:
+      version: " - 8.4.99"
+      reason: "rollup renamed to downsample in 8.5.0"
+
+  - do:
+      indices.create:
+        index: test1
+        body:
+          settings:
+            number_of_shards: 1
+            number_of_replicas: 0
+            index:
+              mode: time_series
+              routing_path: [ metricset, k8s.pod.uid ]
+              time_series:
+                start_time: 2021-04-28T00:00:00Z
+                end_time: 2021-04-29T00:00:00Z
+
+  - do:
+      indices.create:
+        index: test2
+        body:
+          settings:
+            number_of_shards: 1
+            number_of_replicas: 0
+            index:
+              mode: time_series
+              routing_path: [ metricset, k8s.pod.uid ]
+              time_series:
+                start_time: 2021-04-28T00:00:00Z
+                end_time: 2021-04-29T00:00:00Z
+
+  - do:
+      indices.put_settings:
+        index: test1,test2
+        body:
+          index.blocks.write: true
+
+  - do:
+      catch: /index_not_found_exception/
+      indices.downsample:
+        index: test*
+        target_index: rollup-test
+        body:  >
+          {
+            "fixed_interval": "1h"
+          }
+
+  - do:
+      catch: /index_not_found_exception/
+      indices.downsample:
+        index: _all
+        target_index: rollup-test
+        body: >
+          {
+            "fixed_interval": "1h"
+          }
+
+  - do:
+      catch: /invalid_index_name_exception/
+      indices.downsample:
+        index: test1
+        target_index: _all
+        body: >
+          {
+            "fixed_interval": "1h"
+          }
 
 ---
 "Downsample not time_series index":

+ 407 - 0
x-pack/plugin/rollup/qa/rest/src/yamlRestTest/resources/rest-api-spec/test/rollup/40_runtime_fields.yml

@@ -0,0 +1,407 @@
+---
+"Runtime fields accessing metric fields in downsample target index":
+  - skip:
+      version: " - 8.4.99"
+      reason: "downsample introduced in 8.5.0"
+      features: close_to
+
+  - do:
+      indices.create:
+        index: source
+        body:
+          settings:
+            number_of_shards: 1
+            number_of_replicas: 0
+            index:
+              mode: time_series
+              routing_path: [ metricset, k8s.pod.uid ]
+              time_series:
+                start_time: 2021-04-28T00:00:00Z
+                end_time: 2021-04-29T00:00:00Z
+          mappings:
+            runtime:
+              # NOTE: all runtime fields are defined with `type: double` to avoid cast exceptions
+              tx_kb:
+                type: double
+                script: |
+                  emit(doc['k8s.pod.network.tx'].value / 1024.0d);
+              rx_kb:
+                type: double
+                script: |
+                  emit(doc['k8s.pod.network.rx'].value / 1024.0d);
+              sent_kb:
+                type: double
+                script: |
+                  emit(doc['k8s.pod.network.sent'].value / 1024.0d);
+              received_kb:
+                type: double
+                script: |
+                  emit(doc['k8s.pod.network.received'].value / 1024.0d);
+            properties:
+              "@timestamp":
+                type: date
+              metricset:
+                type: keyword
+                time_series_dimension: true
+              k8s:
+                properties:
+                  pod:
+                    properties:
+                      uid:
+                        type: keyword
+                        time_series_dimension: true
+                      network:
+                        properties:
+                          tx:
+                            type: long
+                            time_series_metric: gauge
+                          rx:
+                            type: long
+                            time_series_metric: gauge
+                          sent:
+                            type: long
+                            time_series_metric: counter
+                          received:
+                            type: long
+                            time_series_metric: counter
+
+  - do:
+      bulk:
+        refresh: true
+        index: source
+        body:
+          - '{"index": {}}'
+          - '{"@timestamp": "2021-04-28T18:50:04.467Z", "metricset": "pod", "k8s": {"pod": {"uid":"947e4ced-1786-4e53-9e0c-5c447e959507", "network": {"tx": 2001818691, "rx": 802133794, "sent": 2001818691, "received": 802133794}}}}'
+          - '{"index": {}}'
+          - '{"@timestamp": "2021-04-28T18:50:24.467Z", "metricset": "pod", "k8s": {"pod": {"uid":"947e4ced-1786-4e53-9e0c-5c447e959507", "network": {"tx": 2005177954, "rx": 801479970, "sent": 2001826691, "received": 802143794}}}}'
+          - '{"index": {}}'
+          - '{"@timestamp": "2021-04-28T20:50:44.467Z", "metricset": "pod", "k8s": {"pod": {"uid":"947e4ced-1786-4e53-9e0c-5c447e959507", "network": {"tx": 2006223737, "rx": 802337279, "sent": 2002018680, "received": 802173799}}}}'
+          - '{"index": {}}'
+          - '{"@timestamp": "2021-04-28T20:51:04.467Z", "metricset": "pod", "k8s": {"pod": {"uid":"947e4ced-1786-4e53-9e0c-5c447e959507", "network": {"tx": 2012916202, "rx": 803685721, "sent": 2002267888, "received": 802178800}}}}'
+          - '{"index": {}}'
+          - '{"@timestamp": "2021-04-28T18:50:03.142Z", "metricset": "pod", "k8s": {"pod": {"uid":"df3145b3-0563-4d3b-a0f7-897eb2876ea9", "network": {"tx": 1434521831, "rx": 530575198, "sent": 1434521831, "received": 530575198}}}}'
+          - '{"index": {}}'
+          - '{"@timestamp": "2021-04-28T18:50:23.142Z", "metricset": "pod", "k8s": {"pod": {"uid":"df3145b3-0563-4d3b-a0f7-897eb2876ea9", "network": {"tx": 1434577921, "rx": 530600088, "sent": 1434557898, "received": 530577811}}}}'
+          - '{"index": {}}'
+          - '{"@timestamp": "2021-04-28T19:50:53.142Z", "metricset": "pod", "k8s": {"pod": {"uid":"df3145b3-0563-4d3b-a0f7-897eb2876ea9", "network": {"tx": 1434587694, "rx": 530604797, "sent": 1434589900, "received": 530600110}}}}'
+          - '{"index": {}}'
+          - '{"@timestamp": "2021-04-28T19:51:03.142Z", "metricset": "pod", "k8s": {"pod": {"uid":"df3145b3-0563-4d3b-a0f7-897eb2876ea9", "network": {"tx": 1434595272, "rx": 530605511, "sent": 1434612001, "received": 530622980}}}}'
+
+  # Make the downsample source index read-only
+  - do:
+      indices.put_settings:
+        index: source
+        body:
+          index.blocks.write: true
+
+  # Downsample using `1h` fixed interval
+  - do:
+      indices.downsample:
+        index: source
+        target_index: target
+        body:  >
+          {
+            "fixed_interval": "1h"
+          }
+  - is_true: acknowledged
+
+  # Check downsample target index runtime fields mappings
+  - do:
+      indices.get_mapping:
+        index: target
+
+  - match: { target.mappings.runtime.tx_kb.type: double }
+  - match: { target.mappings.runtime.tx_kb.script.lang: painless }
+  - match:
+      target.mappings.runtime.tx_kb.script.source: |
+        emit(doc['k8s.pod.network.tx'].value / 1024.0d);
+  - match: { target.mappings.runtime.rx_kb.type: double }
+  - match: { target.mappings.runtime.rx_kb.script.lang: painless }
+  - match:
+      target.mappings.runtime.rx_kb.script.source: |
+        emit(doc['k8s.pod.network.rx'].value / 1024.0d);
+  - match: { target.mappings.runtime.sent_kb.type: double }
+  - match: { target.mappings.runtime.sent_kb.script.lang: painless }
+  - match:
+      target.mappings.runtime.sent_kb.script.source: |
+        emit(doc['k8s.pod.network.sent'].value / 1024.0d);
+  - match: { target.mappings.runtime.received_kb.type: double }
+  - match: { target.mappings.runtime.received_kb.script.lang: painless }
+  - match:
+      target.mappings.runtime.received_kb.script.source: |
+        emit(doc['k8s.pod.network.received'].value / 1024.0d);
+
+  # Search the downsample target index including runtime fields
+  - do:
+      search:
+        index: target
+        body:
+          fields: [ tx_kb, rx_kb, sent_kb, received_kb ]
+          sort: [ "_tsid", "@timestamp" ]
+
+  - length: { hits.hits: 4 }
+
+  - close_to: { hits.hits.0.fields.received_kb.0: { value: 783343.5488, error: 0.0001 } }
+  - close_to: { hits.hits.0.fields.sent_kb.0: { value: 1954908.8779, error: 0.0001 } }
+  - close_to: { hits.hits.0.fields.tx_kb.0: { value: 1958181.5957, error: 0.0001 } }
+  - close_to: { hits.hits.0.fields.rx_kb.0: { value: 783333.7832, error: 0.0001 } }
+
+  - close_to: { hits.hits.1.fields.received_kb.0: { value: 783377.7343, error: 0.0001 } }
+  - close_to: { hits.hits.1.fields.sent_kb.0: { value: 1955339.7343, error: 0.0001 } }
+  - close_to: { hits.hits.1.fields.tx_kb.0: { value: 1965738.4785, error: 0.0001 } }
+  - close_to: { hits.hits.1.fields.rx_kb.0: { value: 784849.3369, error: 0.0001 } }
+
+  - close_to: { hits.hits.2.fields.received_kb.0: { value: 518142.3935, error: 0.0001 } }
+  - close_to: { hits.hits.2.fields.sent_kb.0: { value: 1400935.4472, error: 0.0001 } }
+  - close_to: { hits.hits.2.fields.tx_kb.0: { value: 1400955.0009, error: 0.0001 } }
+  - close_to: { hits.hits.2.fields.rx_kb.0: { value: 518164.1484, error: 0.0001 } }
+
+  - close_to: { hits.hits.3.fields.received_kb.0: { value: 518186.5039, error: 0.0001 } }
+  - close_to: { hits.hits.3.fields.sent_kb.0: { value: 1400988.2822, error: 0.0001 } }
+  - close_to: { hits.hits.3.fields.tx_kb.0: { value: 1400971.9453, error: 0.0001 } }
+  - close_to: { hits.hits.3.fields.rx_kb.0: { value: 518169.4443, error: 0.0001 } }
+
+---
+"Runtime field accessing dimension fields in downsample target index":
+  - skip:
+      version: " - 8.4.99"
+      reason: "downsample introduced in 8.5.0"
+
+  - do:
+      indices.create:
+        index: source
+        body:
+          settings:
+            number_of_shards: 1
+            number_of_replicas: 0
+            index:
+              mode: time_series
+              routing_path: [ metricset, k8s.pod.uid ]
+              time_series:
+                start_time: 2021-04-28T00:00:00Z
+                end_time: 2021-04-29T00:00:00Z
+          mappings:
+            runtime:
+              metricset_tag:
+                type: keyword
+                script: |
+                  emit(doc['metricset'].value + '-' + doc['tag'].value);
+            properties:
+              "@timestamp":
+                type: date
+              metricset:
+                type: keyword
+                time_series_dimension: true
+              tag:
+                type: keyword
+                time_series_dimension: true
+              k8s:
+                properties:
+                  pod:
+                    properties:
+                      uid:
+                        type: keyword
+                        time_series_dimension: true
+                      network:
+                        properties:
+                          tx:
+                            type: long
+                            time_series_metric: gauge
+                          rx:
+                            type: long
+                            time_series_metric: gauge
+                          sent:
+                            type: long
+                            time_series_metric: counter
+                          received:
+                            type: long
+                            time_series_metric: counter
+
+  - do:
+      bulk:
+        refresh: true
+        index: source
+        body:
+          - '{"index": {}}'
+          - '{"@timestamp": "2021-04-28T18:50:04.467Z", "metricset": "pod", "tag": "AAA", "k8s": {"pod": {"uid":"947e4ced-1786-4e53-9e0c-5c447e959507", "network": {"tx": 2001818691, "rx": 802133794, "sent": 2001818691, "received": 802133794}}}}'
+          - '{"index": {}}'
+          - '{"@timestamp": "2021-04-28T18:50:24.467Z", "metricset": "pod", "tag": "AAA", "k8s": {"pod": {"uid":"947e4ced-1786-4e53-9e0c-5c447e959507", "network": {"tx": 2005177954, "rx": 801479970, "sent": 2001826691, "received": 802143794}}}}'
+          - '{"index": {}}'
+          - '{"@timestamp": "2021-04-28T20:50:44.467Z", "metricset": "pod", "tag": "AAB", "k8s": {"pod": {"uid":"947e4ced-1786-4e53-9e0c-5c447e959507", "network": {"tx": 2006223737, "rx": 802337279, "sent": 2002018680, "received": 802173799}}}}'
+          - '{"index": {}}'
+          - '{"@timestamp": "2021-04-28T20:51:04.467Z", "metricset": "pod", "tag": "AAB", "k8s": {"pod": {"uid":"947e4ced-1786-4e53-9e0c-5c447e959507", "network": {"tx": 2012916202, "rx": 803685721, "sent": 2002267888, "received": 802178800}}}}'
+          - '{"index": {}}'
+          - '{"@timestamp": "2021-04-28T18:50:03.142Z", "metricset": "pod", "tag": "AAC", "k8s": {"pod": {"uid":"df3145b3-0563-4d3b-a0f7-897eb2876ea9", "network": {"tx": 1434521831, "rx": 530575198, "sent": 1434521831, "received": 530575198}}}}'
+          - '{"index": {}}'
+          - '{"@timestamp": "2021-04-28T18:50:23.142Z", "metricset": "pod", "tag": "AAC", "k8s": {"pod": {"uid":"df3145b3-0563-4d3b-a0f7-897eb2876ea9", "network": {"tx": 1434577921, "rx": 530600088, "sent": 1434557898, "received": 530577811}}}}'
+          - '{"index": {}}'
+          - '{"@timestamp": "2021-04-28T19:50:53.142Z", "metricset": "pod", "tag": "AAD", "k8s": {"pod": {"uid":"df3145b3-0563-4d3b-a0f7-897eb2876ea9", "network": {"tx": 1434587694, "rx": 530604797, "sent": 1434589900, "received": 530600110}}}}'
+          - '{"index": {}}'
+          - '{"@timestamp": "2021-04-28T19:51:03.142Z", "metricset": "pod", "tag": "AAD", "k8s": {"pod": {"uid":"df3145b3-0563-4d3b-a0f7-897eb2876ea9", "network": {"tx": 1434595272, "rx": 530605511, "sent": 1434612001, "received": 530622980}}}}'
+
+  # Make the downsample source index read-only
+  - do:
+      indices.put_settings:
+        index: source
+        body:
+          index.blocks.write: true
+
+  # Downsample using `1h` fixed interval
+  - do:
+      indices.downsample:
+        index: source
+        target_index: target
+        body:  >
+          {
+            "fixed_interval": "1h"
+          }
+  - is_true: acknowledged
+
+  # Check downsample target index runtime fields mappings
+  - do:
+      indices.get_mapping:
+        index: target
+
+  - match: { target.mappings.runtime.metricset_tag.type: keyword }
+  - match: { target.mappings.runtime.metricset_tag.script.lang: painless }
+  - match:
+      target.mappings.runtime.metricset_tag.script.source: |
+        emit(doc['metricset'].value + '-' + doc['tag'].value);
+
+  # Search the downsample target index including runtime fields
+  - do:
+      search:
+        index: target
+        body:
+          fields: [ metricset_tag ]
+          sort: [ "_tsid", "@timestamp" ]
+
+  - length: { hits.hits: 4 }
+
+  - match: { hits.hits.0.fields.metricset_tag.0: "pod-AAA" }
+  - match: { hits.hits.1.fields.metricset_tag.0: "pod-AAB" }
+  - match: { hits.hits.2.fields.metricset_tag.0: "pod-AAC" }
+  - match: { hits.hits.3.fields.metricset_tag.0: "pod-AAD" }
+
+---
+"Runtime field accessing label fields in downsample target index":
+  - skip:
+      version: " - 8.4.99"
+      reason: "downsample introduced in 8.5.0"
+
+  - do:
+      indices.create:
+        index: source
+        body:
+          settings:
+            number_of_shards: 1
+            number_of_replicas: 0
+            index:
+              mode: time_series
+              routing_path: [ metricset, k8s.pod.uid ]
+              time_series:
+                start_time: 2021-04-28T00:00:00Z
+                end_time: 2021-04-29T00:00:00Z
+          mappings:
+            runtime:
+              labels:
+                type: keyword
+                script: |
+                  emit(doc['label_1'].value + '-' + doc['label_2'].value);
+            properties:
+              "@timestamp":
+                type: date
+              metricset:
+                type: keyword
+                time_series_dimension: true
+              label_1:
+                type: keyword
+              label_2:
+                type: long
+              k8s:
+                properties:
+                  pod:
+                    properties:
+                      uid:
+                        type: keyword
+                        time_series_dimension: true
+                      network:
+                        properties:
+                          tx:
+                            type: long
+                            time_series_metric: gauge
+                          rx:
+                            type: long
+                            time_series_metric: gauge
+                          sent:
+                            type: long
+                            time_series_metric: counter
+                          received:
+                            type: long
+                            time_series_metric: counter
+
+  - do:
+      bulk:
+        refresh: true
+        index: source
+        body:
+          - '{"index": {}}'
+          - '{"@timestamp": "2021-04-28T18:50:04.467Z", "metricset": "pod", "label_1": "AAA", "label_2": 9, "k8s": {"pod": {"uid":"947e4ced-1786-4e53-9e0c-5c447e959507", "network": {"tx": 2001818691, "rx": 802133794, "sent": 2001818691, "received": 802133794}}}}'
+          - '{"index": {}}'
+          - '{"@timestamp": "2021-04-28T18:50:24.467Z", "metricset": "pod", "label_1": "AAA", "label_2": 10, "k8s": {"pod": {"uid":"947e4ced-1786-4e53-9e0c-5c447e959507", "network": {"tx": 2005177954, "rx": 801479970, "sent": 2001826691, "received": 802143794}}}}'
+          - '{"index": {}}'
+          - '{"@timestamp": "2021-04-28T20:50:44.467Z", "metricset": "pod", "label_1": "AAB", "label_2": 117, "k8s": {"pod": {"uid":"947e4ced-1786-4e53-9e0c-5c447e959507", "network": {"tx": 2006223737, "rx": 802337279, "sent": 2002018680, "received": 802173799}}}}'
+          - '{"index": {}}'
+          - '{"@timestamp": "2021-04-28T20:51:04.467Z", "metricset": "pod", "label_1": "AAB", "label_2": 110, "k8s": {"pod": {"uid":"947e4ced-1786-4e53-9e0c-5c447e959507", "network": {"tx": 2012916202, "rx": 803685721, "sent": 2002267888, "received": 802178800}}}}'
+          - '{"index": {}}'
+          - '{"@timestamp": "2021-04-28T18:50:03.142Z", "metricset": "pod", "label_1": "AAC", "label_2": 110, "k8s": {"pod": {"uid":"df3145b3-0563-4d3b-a0f7-897eb2876ea9", "network": {"tx": 1434521831, "rx": 530575198, "sent": 1434521831, "received": 530575198}}}}'
+          - '{"index": {}}'
+          - '{"@timestamp": "2021-04-28T18:50:23.142Z", "metricset": "pod", "label_1": "AAC", "label_2": 11, "k8s": {"pod": {"uid":"df3145b3-0563-4d3b-a0f7-897eb2876ea9", "network": {"tx": 1434577921, "rx": 530600088, "sent": 1434557898, "received": 530577811}}}}'
+          - '{"index": {}}'
+          - '{"@timestamp": "2021-04-28T19:50:53.142Z", "metricset": "pod", "label_1": "AAD", "label_2": 114, "k8s": {"pod": {"uid":"df3145b3-0563-4d3b-a0f7-897eb2876ea9", "network": {"tx": 1434587694, "rx": 530604797, "sent": 1434589900, "received": 530600110}}}}'
+          - '{"index": {}}'
+          - '{"@timestamp": "2021-04-28T19:51:03.142Z", "metricset": "pod", "label_1": "AAD", "label_2": 111, "k8s": {"pod": {"uid":"df3145b3-0563-4d3b-a0f7-897eb2876ea9", "network": {"tx": 1434595272, "rx": 530605511, "sent": 1434612001, "received": 530622980}}}}'
+
+  # Make the downsample source index read-only
+  - do:
+      indices.put_settings:
+        index: source
+        body:
+          index.blocks.write: true
+
+  # Downsample using `1h` fixed interval
+  - do:
+      indices.downsample:
+        index: source
+        target_index: target
+        body:  >
+          {
+            "fixed_interval": "1h"
+          }
+  - is_true: acknowledged
+
+  # Check downsample target index runtime fields mappings
+  - do:
+      indices.get_mapping:
+        index: target
+
+  - match: { target.mappings.runtime.labels.type: keyword }
+  - match: { target.mappings.runtime.labels.script.lang: painless }
+  - match:
+      target.mappings.runtime.labels.script.source: |
+        emit(doc['label_1'].value + '-' + doc['label_2'].value);
+
+  # Search the downsample target index including runtime fields
+  - do:
+      search:
+        index: target
+        body:
+          fields: [ labels ]
+          sort: [ "_tsid", "@timestamp" ]
+
+  - length: { hits.hits: 4 }
+
+  - match: { hits.hits.0.fields.labels.0: "AAA-10" }
+  - match: { hits.hits.1.fields.labels.0: "AAB-110" }
+  - match: { hits.hits.2.fields.labels.0: "AAC-11" }
+  - match: { hits.hits.3.fields.labels.0: "AAD-111" }

+ 22 - 1
x-pack/plugin/rollup/src/main/java/org/elasticsearch/xpack/downsample/TransportRollupAction.java

@@ -43,6 +43,7 @@ import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.settings.IndexScopedSettings;
 import org.elasticsearch.common.settings.Setting;
 import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.util.concurrent.ThreadContext;
 import org.elasticsearch.common.xcontent.XContentHelper;
 import org.elasticsearch.core.Tuple;
 import org.elasticsearch.index.Index;
@@ -67,6 +68,8 @@ import org.elasticsearch.xpack.core.ClientHelper;
 import org.elasticsearch.xpack.core.downsample.DownsampleAction;
 import org.elasticsearch.xpack.core.downsample.DownsampleConfig;
 import org.elasticsearch.xpack.core.downsample.RollupIndexerAction;
+import org.elasticsearch.xpack.core.security.authz.AuthorizationServiceField;
+import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -90,6 +93,7 @@ public class TransportRollupAction extends AcknowledgedTransportMasterNodeAction
     private final ClusterService clusterService;
     private final MetadataCreateIndexService metadataCreateIndexService;
     private final IndexScopedSettings indexScopedSettings;
+    private final ThreadContext threadContext;
 
     /**
      * This is the cluster state task executor for cluster state update actions.
@@ -134,6 +138,7 @@ public class TransportRollupAction extends AcknowledgedTransportMasterNodeAction
         this.clusterService = clusterService;
         this.metadataCreateIndexService = metadataCreateIndexService;
         this.indexScopedSettings = indexScopedSettings;
+        this.threadContext = threadPool.getThreadContext();
     }
 
     @Override
@@ -144,8 +149,24 @@ public class TransportRollupAction extends AcknowledgedTransportMasterNodeAction
         ActionListener<AcknowledgedResponse> listener
     ) {
         String sourceIndexName = request.getSourceIndex();
-        IndexMetadata sourceIndexMetadata = state.getMetadata().index(sourceIndexName);
+
+        final IndicesAccessControl indicesAccessControl = threadContext.getTransient(AuthorizationServiceField.INDICES_PERMISSIONS_KEY);
+        if (indicesAccessControl != null) {
+            final IndicesAccessControl.IndexAccessControl indexPermissions = indicesAccessControl.getIndexPermissions(sourceIndexName);
+            if (indexPermissions != null) {
+                boolean hasDocumentLevelPermissions = indexPermissions.getDocumentPermissions().hasDocumentLevelPermissions();
+                boolean hasFieldLevelSecurity = indexPermissions.getFieldPermissions().hasFieldLevelSecurity();
+                if (hasDocumentLevelPermissions || hasFieldLevelSecurity) {
+                    listener.onFailure(
+                        new ElasticsearchException(
+                            "Rollup forbidden for index [" + sourceIndexName + "] with document level or field level security settings."
+                        )
+                    );
+                }
+            }
+        }
         // Assert source index exists
+        IndexMetadata sourceIndexMetadata = state.getMetadata().index(sourceIndexName);
         if (sourceIndexMetadata == null) {
             listener.onFailure(new IndexNotFoundException(sourceIndexName));
             return;

+ 253 - 0
x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/security/authz/80_downsample.yml

@@ -0,0 +1,253 @@
+setup:
+  - skip:
+      version: " - 8.4.99"
+      reason: "downsample introduced in 8.5.0"
+      features: headers
+
+  - do:
+      indices.create:
+        index: source
+        body:
+          settings:
+            number_of_shards: 1
+            number_of_replicas: 0
+            index:
+              mode: time_series
+              routing_path: [ metricset, k8s.pod.uid ]
+              time_series:
+                start_time: 2021-04-28T00:00:00Z
+                end_time: 2021-04-29T00:00:00Z
+          mappings:
+            properties:
+              "@timestamp":
+                type: date
+              metricset:
+                type: keyword
+                time_series_dimension: true
+              k8s:
+                properties:
+                  pod:
+                    properties:
+                      uid:
+                        type: keyword
+                        time_series_dimension: true
+                      network:
+                        properties:
+                          tx:
+                            type: long
+                            time_series_metric: gauge
+                          rx:
+                            type: long
+                            time_series_metric: gauge
+                          sent:
+                            type: long
+                            time_series_metric: counter
+                          received:
+                            type: long
+                            time_series_metric: counter
+
+  - do:
+      bulk:
+        refresh: true
+        index: source
+        body:
+          - '{"index": {}}'
+          - '{"@timestamp": "2021-04-28T18:50:04.467Z", "metricset": "pod", "k8s": {"pod": {"uid":"947e4ced-1786-4e53-9e0c-5c447e959507", "network": {"tx": 2001818691, "rx": 802133794, "sent": 2001818691, "received": 802133794}}}}'
+          - '{"index": {}}'
+          - '{"@timestamp": "2021-04-28T18:50:24.467Z", "metricset": "pod", "k8s": {"pod": {"uid":"947e4ced-1786-4e53-9e0c-5c447e959507", "network": {"tx": 2005177954, "rx": 801479970, "sent": 2001826691, "received": 802143794}}}}'
+          - '{"index": {}}'
+          - '{"@timestamp": "2021-04-28T20:50:44.467Z", "metricset": "pod", "k8s": {"pod": {"uid":"947e4ced-1786-4e53-9e0c-5c447e959507", "network": {"tx": 2006223737, "rx": 802337279, "sent": 2002018680, "received": 802173799}}}}'
+          - '{"index": {}}'
+          - '{"@timestamp": "2021-04-28T20:51:04.467Z", "metricset": "pod", "k8s": {"pod": {"uid":"947e4ced-1786-4e53-9e0c-5c447e959507", "network": {"tx": 2012916202, "rx": 803685721, "sent": 2002267888, "received": 802178800}}}}'
+          - '{"index": {}}'
+          - '{"@timestamp": "2021-04-28T18:50:03.142Z", "metricset": "pod", "k8s": {"pod": {"uid":"df3145b3-0563-4d3b-a0f7-897eb2876ea9", "network": {"tx": 1434521831, "rx": 530575198, "sent": 1434521831, "received": 530575198}}}}'
+          - '{"index": {}}'
+          - '{"@timestamp": "2021-04-28T18:50:23.142Z", "metricset": "pod", "k8s": {"pod": {"uid":"df3145b3-0563-4d3b-a0f7-897eb2876ea9", "network": {"tx": 1434577921, "rx": 530600088, "sent": 1434557898, "received": 530577811}}}}'
+          - '{"index": {}}'
+          - '{"@timestamp": "2021-04-28T19:50:53.142Z", "metricset": "pod", "k8s": {"pod": {"uid":"df3145b3-0563-4d3b-a0f7-897eb2876ea9", "network": {"tx": 1434587694, "rx": 530604797, "sent": 1434589900, "received": 530600110}}}}'
+          - '{"index": {}}'
+          - '{"@timestamp": "2021-04-28T19:51:03.142Z", "metricset": "pod", "k8s": {"pod": {"uid":"df3145b3-0563-4d3b-a0f7-897eb2876ea9", "network": {"tx": 1434595272, "rx": 530605511, "sent": 1434612001, "received": 530622980}}}}'
+
+  # Make the downsample source index read-only
+  - do:
+      indices.put_settings:
+        index: source
+        body:
+          index.blocks.write: true
+
+---
+"Downsample user missing admin permissions to run the downsample operation":
+  - skip:
+      version: " - 8.4.99"
+      reason: "downsample introduced in 8.5.0"
+      features: headers
+
+  - do:
+      security.put_role:
+        name: "downsample-role"
+        body: >
+          {
+            "indices": [
+              {
+                "names": ["source"],
+                "privileges": ["read"]
+              },
+              {
+                "names": ["target"],
+                "privileges": ["write"]
+              }
+            ]
+          }
+  - do:
+      security.put_user:
+        username: "downsample-user"
+        body: >
+          {
+            "password" : "x-pack-test-password",
+            "roles" : [ "downsample-role" ],
+            "full_name" : "user without permissions on the downsample source index"
+          }
+
+  # Downsample using `1h` fixed interval
+  - do:
+      headers: { Authorization: "Basic ZG93bnNhbXBsZS11c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" }
+      catch: '/action \[indices:admin/xpack/downsample\] is unauthorized for user \[downsample-user\] with effective roles \[downsample-role\] on indices \[source\], this action is granted by the index privileges \[manage,all\]/'
+      indices.downsample:
+        index: source
+        target_index: target
+        body: >
+          {
+            "fixed_interval": "1h"
+          }
+
+---
+"Downsample admin user with field level security settings defined on the source index":
+  - skip:
+      version: " - 8.4.99"
+      reason: "downsample introduced in 8.5.0"
+      features: headers
+
+  - do:
+      security.put_role:
+        name: "downsample-role"
+        body: >
+          {
+            "indices": [
+              {
+                "names": ["source"],
+                "privileges": ["all"],
+                "field_security": {
+                  "grant": ["@timestamp", "k8s.pod.network.*"]
+                }
+              }
+            ]
+          }
+  - do:
+      security.put_user:
+        username: "downsample-user"
+        body: >
+          {
+            "password" : "x-pack-test-password",
+            "roles" : [ "downsample-role" ],
+            "full_name" : "user who can read a limited set of fields in the source index"
+          }
+
+  # Downsample using `1h` fixed interval
+  - do:
+      headers: { Authorization: "Basic ZG93bnNhbXBsZS11c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" }
+      catch: '/Rollup forbidden for index \[source\] with document level or field level security settings\./'
+      indices.downsample:
+        index: source
+        target_index: target
+        body: >
+          {
+            "fixed_interval": "1h"
+          }
+
+---
+"Downsample admin user with document level security settings defined on the source index":
+  - skip:
+      version: " - 8.4.99"
+      reason: "downsample introduced in 8.5.0"
+      features: headers
+
+  - do:
+      security.put_role:
+        name: "downsample-role"
+        body: >
+          {
+            "indices": [
+              {
+                "names": ["source", "target"],
+                "privileges": ["all"],
+                "query": { "match_all": {} }
+              }
+            ]
+          }
+
+  # User performing the downsample operation
+  - do:
+      security.put_user:
+        username: "downsample-user"
+        body: >
+          {
+            "password" : "x-pack-test-password",
+            "roles" : [ "downsample-role" ],
+            "full_name" : "user with all privileges on downsample source and target indices"
+          }
+
+  # Downsample using `1h` fixed interval
+  - do:
+      headers: { Authorization: "Basic ZG93bnNhbXBsZS11c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" }
+      catch: '/Rollup forbidden for index \[source\] with document level or field level security settings./'
+      indices.downsample:
+        index: source
+        target_index: target
+        body: >
+          {
+            "fixed_interval": "1h"
+          }
+
+---
+"Downsample admin user without document level or field level security settings defined on the source index":
+  - skip:
+      version: " - 8.4.99"
+      reason: "downsample introduced in 8.5.0"
+      features: headers
+
+  - do:
+      security.put_role:
+        name: "downsample-role"
+        body: >
+          {
+            "indices": [
+              {
+                "names": ["source"],
+                "privileges": ["all"]
+              }
+            ]
+          }
+
+  # User performing the downsample operation
+  - do:
+      security.put_user:
+        username: "downsample-user"
+        body: >
+          {
+            "password" : "x-pack-test-password",
+            "roles" : [ "downsample-role" ],
+            "full_name" : "user with all privileges on downsample source and target indices"
+          }
+
+  # Downsample using `1h` fixed interval
+  - do:
+      headers: { Authorization: "Basic ZG93bnNhbXBsZS11c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" }
+      indices.downsample:
+        index: source
+        target_index: target
+        body: >
+          {
+            "fixed_interval": "1h"
+          }
+
+  - is_true: acknowledged