Test reindex-from-remote with security
Original commit: elastic/x-pack-elasticsearch@7e3530a958
This commit is contained in:
parent
51f91bf9eb
commit
b9e1bdfce6
|
@ -8,6 +8,8 @@ integTest {
|
|||
cluster {
|
||||
setting 'script.inline', 'true'
|
||||
plugin ':x-plugins:elasticsearch:x-pack'
|
||||
// Whitelist reindexing from the local node so we can test it.
|
||||
setting 'reindex.remote.whitelist', 'myself'
|
||||
extraConfigFile 'x-pack/roles.yml', 'roles.yml'
|
||||
[
|
||||
test_admin: 'superuser',
|
||||
|
|
|
@ -10,6 +10,8 @@ admin:
|
|||
# Search and write on both source and destination indices. It should work if you could just search on the source and
|
||||
# write to the destination but that isn't how security works.
|
||||
minimal:
|
||||
cluster:
|
||||
- cluster:monitor/main
|
||||
indices:
|
||||
- names: source
|
||||
privileges:
|
||||
|
@ -26,18 +28,24 @@ minimal:
|
|||
|
||||
# Read only operations on indices
|
||||
readonly:
|
||||
cluster:
|
||||
- cluster:monitor/main
|
||||
indices:
|
||||
- names: '*'
|
||||
privileges: [ read ]
|
||||
|
||||
# Write operations on destination index, none on source index
|
||||
dest_only:
|
||||
cluster:
|
||||
- cluster:monitor/main
|
||||
indices:
|
||||
- names: dest
|
||||
privileges: [ write ]
|
||||
|
||||
# Search and write on both source and destination indices with document level security filtering out some docs.
|
||||
can_not_see_hidden_docs:
|
||||
cluster:
|
||||
- cluster:monitor/main
|
||||
indices:
|
||||
- names: source
|
||||
privileges:
|
||||
|
@ -59,6 +67,8 @@ can_not_see_hidden_docs:
|
|||
|
||||
# Search and write on both source and destination indices with field level security.
|
||||
can_not_see_hidden_fields:
|
||||
cluster:
|
||||
- cluster:monitor/main
|
||||
indices:
|
||||
- names: source
|
||||
privileges:
|
||||
|
|
|
@ -147,10 +147,9 @@
|
|||
indices.refresh: {}
|
||||
|
||||
- do:
|
||||
headers: {es-security-runas-user: dest_only_user}
|
||||
headers: {es-security-runas-user: minimal_user}
|
||||
catch: forbidden
|
||||
reindex:
|
||||
refresh: true
|
||||
body:
|
||||
source:
|
||||
index: source
|
||||
|
|
|
@ -0,0 +1,418 @@
|
|||
---
|
||||
"Reindex from remote as superuser works":
|
||||
- skip:
|
||||
features: catch_unauthorized
|
||||
|
||||
- do:
|
||||
index:
|
||||
index: source
|
||||
type: foo
|
||||
id: 1
|
||||
body: { "text": "test" }
|
||||
- do:
|
||||
indices.refresh: {}
|
||||
|
||||
# Fetch the http host. We use the host of the master because we know there will always be a master.
|
||||
- do:
|
||||
cluster.state: {}
|
||||
- set: { master_node: master }
|
||||
- do:
|
||||
nodes.info:
|
||||
metric: [ http ]
|
||||
- is_true: nodes.$master.http.publish_address
|
||||
- set: {nodes.$master.http.publish_address: host}
|
||||
- do:
|
||||
reindex:
|
||||
body:
|
||||
source:
|
||||
remote:
|
||||
host: http://${host}
|
||||
username: test_admin
|
||||
password: changeme
|
||||
index: source
|
||||
dest:
|
||||
index: dest
|
||||
- match: {created: 1}
|
||||
|
||||
---
|
||||
"Reindex from remote searching as user with minimal privileges works":
|
||||
- skip:
|
||||
features: catch_unauthorized
|
||||
|
||||
- do:
|
||||
index:
|
||||
index: source
|
||||
type: foo
|
||||
id: 1
|
||||
body: { "text": "test" }
|
||||
- do:
|
||||
indices.refresh: {}
|
||||
|
||||
# Fetch the http host. We use the host of the master because we know there will always be a master.
|
||||
- do:
|
||||
cluster.state: {}
|
||||
- set: { master_node: master }
|
||||
- do:
|
||||
nodes.info:
|
||||
metric: [ http ]
|
||||
- is_true: nodes.$master.http.publish_address
|
||||
- set: {nodes.$master.http.publish_address: host}
|
||||
- do:
|
||||
reindex:
|
||||
refresh: true
|
||||
body:
|
||||
source:
|
||||
remote:
|
||||
host: http://${host}
|
||||
username: minimal_user
|
||||
password: changeme
|
||||
index: source
|
||||
dest:
|
||||
index: dest
|
||||
- match: {created: 1}
|
||||
|
||||
- do:
|
||||
search:
|
||||
index: dest
|
||||
body:
|
||||
query:
|
||||
match:
|
||||
text: test
|
||||
- match: { hits.total: 1 }
|
||||
|
||||
---
|
||||
"Reindex from remote reading as readonly user works when the indexing user is allowed to index":
|
||||
- skip:
|
||||
features: catch_unauthorized
|
||||
|
||||
- do:
|
||||
index:
|
||||
index: source
|
||||
type: foo
|
||||
id: 1
|
||||
body: { "text": "test" }
|
||||
- do:
|
||||
indices.refresh: {}
|
||||
|
||||
# Fetch the http host. We use the host of the master because we know there will always be a master.
|
||||
- do:
|
||||
cluster.state: {}
|
||||
- set: { master_node: master }
|
||||
- do:
|
||||
nodes.info:
|
||||
metric: [ http ]
|
||||
- is_true: nodes.$master.http.publish_address
|
||||
- set: {nodes.$master.http.publish_address: host}
|
||||
- do:
|
||||
reindex:
|
||||
refresh: true
|
||||
body:
|
||||
source:
|
||||
remote:
|
||||
host: http://${host}
|
||||
username: readonly_user
|
||||
password: changeme
|
||||
index: source
|
||||
dest:
|
||||
index: dest
|
||||
|
||||
- do:
|
||||
search:
|
||||
index: dest
|
||||
body:
|
||||
query:
|
||||
match:
|
||||
text: test
|
||||
- match: { hits.total: 1 }
|
||||
|
||||
---
|
||||
"Reindex from remote as user that can't read from the source is forbidden":
|
||||
- skip:
|
||||
features: catch_unauthorized
|
||||
|
||||
- do:
|
||||
index:
|
||||
index: source
|
||||
type: foo
|
||||
id: 1
|
||||
body: { "text": "test" }
|
||||
- do:
|
||||
indices.refresh: {}
|
||||
|
||||
# Fetch the http host. We use the host of the master because we know there will always be a master.
|
||||
- do:
|
||||
cluster.state: {}
|
||||
- set: { master_node: master }
|
||||
- do:
|
||||
nodes.info:
|
||||
metric: [ http ]
|
||||
- is_true: nodes.$master.http.publish_address
|
||||
- set: {nodes.$master.http.publish_address: host}
|
||||
- do:
|
||||
catch: forbidden
|
||||
reindex:
|
||||
body:
|
||||
source:
|
||||
remote:
|
||||
host: http://${host}
|
||||
username: dest_only_user
|
||||
password: changeme
|
||||
index: source
|
||||
dest:
|
||||
index: dest
|
||||
|
||||
---
|
||||
"Using a script to write to an index to which you don't have access is forbidden even if you read as a superuser":
|
||||
- do:
|
||||
index:
|
||||
index: source
|
||||
type: tweet
|
||||
id: 1
|
||||
body: { "user": "kimchy" }
|
||||
- do:
|
||||
index:
|
||||
index: source
|
||||
type: tweet
|
||||
id: 2
|
||||
body: { "user": "another" }
|
||||
- do:
|
||||
indices.refresh: {}
|
||||
|
||||
# Fetch the http host. We use the host of the master because we know there will always be a master.
|
||||
- do:
|
||||
cluster.state: {}
|
||||
- set: { master_node: master }
|
||||
- do:
|
||||
nodes.info:
|
||||
metric: [ http ]
|
||||
- is_true: nodes.$master.http.publish_address
|
||||
- set: {nodes.$master.http.publish_address: host}
|
||||
- do:
|
||||
headers: {es-security-runas-user: minimal_user}
|
||||
catch: forbidden
|
||||
reindex:
|
||||
body:
|
||||
source:
|
||||
remote:
|
||||
host: http://${host}
|
||||
username: test_admin
|
||||
password: changeme
|
||||
index: source
|
||||
dest:
|
||||
index: dest
|
||||
script:
|
||||
inline: if (ctx._source.user == "kimchy") {ctx._index = 'other_dest'}
|
||||
|
||||
- do:
|
||||
indices.refresh: {}
|
||||
|
||||
# The index to which the user tried the unauthorized write didn't even get created
|
||||
- do:
|
||||
catch: missing
|
||||
search:
|
||||
index: other_dest
|
||||
|
||||
# Even the authorized index won't have made it because it was in the same batch as the unauthorized one.
|
||||
# If there had been lots of documents being copied then some might have made it into the authorized index.
|
||||
- do:
|
||||
catch: missing
|
||||
search:
|
||||
index: dest
|
||||
|
||||
---
|
||||
"Reindex from remote misses hidden docs":
|
||||
- skip:
|
||||
features: catch_unauthorized
|
||||
|
||||
- do:
|
||||
index:
|
||||
index: source
|
||||
type: foo
|
||||
id: 1
|
||||
body: { "text": "test" }
|
||||
- do:
|
||||
index:
|
||||
index: source
|
||||
type: foo
|
||||
id: 2
|
||||
body: { "text": "test", "hidden": true }
|
||||
- do:
|
||||
indices.refresh: {}
|
||||
|
||||
# Fetch the http host. We use the host of the master because we know there will always be a master.
|
||||
- do:
|
||||
cluster.state: {}
|
||||
- set: { master_node: master }
|
||||
- do:
|
||||
nodes.info:
|
||||
metric: [ http ]
|
||||
- is_true: nodes.$master.http.publish_address
|
||||
- set: {nodes.$master.http.publish_address: host}
|
||||
- do:
|
||||
reindex:
|
||||
refresh: true
|
||||
body:
|
||||
source:
|
||||
remote:
|
||||
host: http://${host}
|
||||
username: can_not_see_hidden_docs_user
|
||||
password: changeme
|
||||
index: source
|
||||
dest:
|
||||
index: dest
|
||||
- match: {created: 1}
|
||||
|
||||
# We copied just one doc, presumably the one without the hidden field
|
||||
- do:
|
||||
search:
|
||||
index: dest
|
||||
body:
|
||||
query:
|
||||
match:
|
||||
text: test
|
||||
- match: { hits.total: 1 }
|
||||
|
||||
# We didn't copy the doc with the hidden field
|
||||
- do:
|
||||
search:
|
||||
index: dest
|
||||
body:
|
||||
query:
|
||||
match:
|
||||
hidden: true
|
||||
- match: { hits.total: 0 }
|
||||
|
||||
---
|
||||
"Reindex misses hidden fields":
|
||||
- skip:
|
||||
features: catch_unauthorized
|
||||
|
||||
- do:
|
||||
index:
|
||||
index: source
|
||||
type: foo
|
||||
id: 1
|
||||
body: { "text": "test", "foo": "z", "bar": "z" }
|
||||
- do:
|
||||
indices.refresh: {}
|
||||
|
||||
# Fetch the http host. We use the host of the master because we know there will always be a master.
|
||||
- do:
|
||||
cluster.state: {}
|
||||
- set: { master_node: master }
|
||||
- do:
|
||||
nodes.info:
|
||||
metric: [ http ]
|
||||
- is_true: nodes.$master.http.publish_address
|
||||
- set: {nodes.$master.http.publish_address: host}
|
||||
- do:
|
||||
reindex:
|
||||
refresh: true
|
||||
body:
|
||||
source:
|
||||
remote:
|
||||
host: http://${host}
|
||||
username: can_not_see_hidden_fields_user
|
||||
password: changeme
|
||||
index: source
|
||||
dest:
|
||||
index: dest
|
||||
- match: {created: 1}
|
||||
|
||||
- do:
|
||||
search:
|
||||
index: dest
|
||||
body:
|
||||
query:
|
||||
match:
|
||||
foo: z
|
||||
- match: { hits.total: 1 }
|
||||
|
||||
- do:
|
||||
search:
|
||||
index: dest
|
||||
body:
|
||||
query:
|
||||
match:
|
||||
bar: z
|
||||
- match: { hits.total: 1 }
|
||||
|
||||
- do:
|
||||
search:
|
||||
index: dest
|
||||
body:
|
||||
query:
|
||||
match:
|
||||
text: test
|
||||
- match: { hits.total: 0 }
|
||||
|
||||
|
||||
---
|
||||
"Reindex from remote with bad password is unauthorized":
|
||||
- skip:
|
||||
features: catch_unauthorized
|
||||
|
||||
- do:
|
||||
index:
|
||||
index: source
|
||||
type: foo
|
||||
id: 1
|
||||
body: { "text": "test" }
|
||||
- do:
|
||||
indices.refresh: {}
|
||||
|
||||
# Fetch the http host. We use the host of the master because we know there will always be a master.
|
||||
- do:
|
||||
cluster.state: {}
|
||||
- set: { master_node: master }
|
||||
- do:
|
||||
nodes.info:
|
||||
metric: [ http ]
|
||||
- is_true: nodes.$master.http.publish_address
|
||||
- set: {nodes.$master.http.publish_address: host}
|
||||
- do:
|
||||
catch: unauthorized
|
||||
reindex:
|
||||
body:
|
||||
source:
|
||||
remote:
|
||||
host: http://${host}
|
||||
username: test_admin
|
||||
password: badpass
|
||||
index: source
|
||||
dest:
|
||||
index: dest
|
||||
|
||||
---
|
||||
"Reindex from remote with no username or password is unauthorized":
|
||||
- skip:
|
||||
features: catch_unauthorized
|
||||
|
||||
- do:
|
||||
index:
|
||||
index: source
|
||||
type: foo
|
||||
id: 1
|
||||
body: { "text": "test" }
|
||||
- do:
|
||||
indices.refresh: {}
|
||||
|
||||
# Fetch the http host. We use the host of the master because we know there will always be a master.
|
||||
- do:
|
||||
cluster.state: {}
|
||||
- set: { master_node: master }
|
||||
- do:
|
||||
nodes.info:
|
||||
metric: [ http ]
|
||||
- is_true: nodes.$master.http.publish_address
|
||||
- set: {nodes.$master.http.publish_address: host}
|
||||
- do:
|
||||
catch: unauthorized
|
||||
reindex:
|
||||
body:
|
||||
source:
|
||||
remote:
|
||||
host: http://${host}
|
||||
index: source
|
||||
dest:
|
||||
index: dest
|
|
@ -52,6 +52,8 @@ teardown:
|
|||
|
||||
---
|
||||
"Test changing users password":
|
||||
- skip:
|
||||
features: catch_unauthorized
|
||||
# validate that the user actually works
|
||||
- do:
|
||||
headers:
|
||||
|
@ -70,7 +72,7 @@ teardown:
|
|||
|
||||
# attempt to login with invalid credentials
|
||||
- do:
|
||||
catch: request
|
||||
catch: unauthorized
|
||||
headers:
|
||||
Authorization: "Basic am9lOnMza3JpdA=="
|
||||
cluster.health: {}
|
||||
|
@ -84,6 +86,8 @@ teardown:
|
|||
|
||||
---
|
||||
"Test user changing their own password":
|
||||
- skip:
|
||||
features: catch_unauthorized
|
||||
# test that the role actually works
|
||||
- do:
|
||||
headers:
|
||||
|
@ -103,7 +107,7 @@ teardown:
|
|||
|
||||
# attempt to login with invalid credentials
|
||||
- do:
|
||||
catch: request
|
||||
catch: unauthorized
|
||||
headers:
|
||||
Authorization: "Basic dW5wcml2aWxlZ2VkX3VzZXI6czNrcml0"
|
||||
cluster.health: {}
|
||||
|
|
|
@ -35,7 +35,7 @@ teardown:
|
|||
---
|
||||
"Test create user and update without and with password":
|
||||
- skip:
|
||||
features: headers
|
||||
features: [headers, catch_unauthorized]
|
||||
|
||||
# test that the role actually works
|
||||
- do:
|
||||
|
@ -108,7 +108,7 @@ teardown:
|
|||
|
||||
# validate old password doesn't work
|
||||
- do:
|
||||
catch: request
|
||||
catch: unauthorized
|
||||
headers:
|
||||
Authorization: "Basic am9lOnMza3JpdA=="
|
||||
cluster.health: {}
|
||||
|
|
Loading…
Reference in New Issue