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 {
|
cluster {
|
||||||
setting 'script.inline', 'true'
|
setting 'script.inline', 'true'
|
||||||
plugin ':x-plugins:elasticsearch:x-pack'
|
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'
|
extraConfigFile 'x-pack/roles.yml', 'roles.yml'
|
||||||
[
|
[
|
||||||
test_admin: 'superuser',
|
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
|
# 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.
|
# write to the destination but that isn't how security works.
|
||||||
minimal:
|
minimal:
|
||||||
|
cluster:
|
||||||
|
- cluster:monitor/main
|
||||||
indices:
|
indices:
|
||||||
- names: source
|
- names: source
|
||||||
privileges:
|
privileges:
|
||||||
|
@ -26,18 +28,24 @@ minimal:
|
||||||
|
|
||||||
# Read only operations on indices
|
# Read only operations on indices
|
||||||
readonly:
|
readonly:
|
||||||
|
cluster:
|
||||||
|
- cluster:monitor/main
|
||||||
indices:
|
indices:
|
||||||
- names: '*'
|
- names: '*'
|
||||||
privileges: [ read ]
|
privileges: [ read ]
|
||||||
|
|
||||||
# Write operations on destination index, none on source index
|
# Write operations on destination index, none on source index
|
||||||
dest_only:
|
dest_only:
|
||||||
|
cluster:
|
||||||
|
- cluster:monitor/main
|
||||||
indices:
|
indices:
|
||||||
- names: dest
|
- names: dest
|
||||||
privileges: [ write ]
|
privileges: [ write ]
|
||||||
|
|
||||||
# Search and write on both source and destination indices with document level security filtering out some docs.
|
# Search and write on both source and destination indices with document level security filtering out some docs.
|
||||||
can_not_see_hidden_docs:
|
can_not_see_hidden_docs:
|
||||||
|
cluster:
|
||||||
|
- cluster:monitor/main
|
||||||
indices:
|
indices:
|
||||||
- names: source
|
- names: source
|
||||||
privileges:
|
privileges:
|
||||||
|
@ -59,6 +67,8 @@ can_not_see_hidden_docs:
|
||||||
|
|
||||||
# Search and write on both source and destination indices with field level security.
|
# Search and write on both source and destination indices with field level security.
|
||||||
can_not_see_hidden_fields:
|
can_not_see_hidden_fields:
|
||||||
|
cluster:
|
||||||
|
- cluster:monitor/main
|
||||||
indices:
|
indices:
|
||||||
- names: source
|
- names: source
|
||||||
privileges:
|
privileges:
|
||||||
|
|
|
@ -147,10 +147,9 @@
|
||||||
indices.refresh: {}
|
indices.refresh: {}
|
||||||
|
|
||||||
- do:
|
- do:
|
||||||
headers: {es-security-runas-user: dest_only_user}
|
headers: {es-security-runas-user: minimal_user}
|
||||||
catch: forbidden
|
catch: forbidden
|
||||||
reindex:
|
reindex:
|
||||||
refresh: true
|
|
||||||
body:
|
body:
|
||||||
source:
|
source:
|
||||||
index: 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":
|
"Test changing users password":
|
||||||
|
- skip:
|
||||||
|
features: catch_unauthorized
|
||||||
# validate that the user actually works
|
# validate that the user actually works
|
||||||
- do:
|
- do:
|
||||||
headers:
|
headers:
|
||||||
|
@ -70,7 +72,7 @@ teardown:
|
||||||
|
|
||||||
# attempt to login with invalid credentials
|
# attempt to login with invalid credentials
|
||||||
- do:
|
- do:
|
||||||
catch: request
|
catch: unauthorized
|
||||||
headers:
|
headers:
|
||||||
Authorization: "Basic am9lOnMza3JpdA=="
|
Authorization: "Basic am9lOnMza3JpdA=="
|
||||||
cluster.health: {}
|
cluster.health: {}
|
||||||
|
@ -84,6 +86,8 @@ teardown:
|
||||||
|
|
||||||
---
|
---
|
||||||
"Test user changing their own password":
|
"Test user changing their own password":
|
||||||
|
- skip:
|
||||||
|
features: catch_unauthorized
|
||||||
# test that the role actually works
|
# test that the role actually works
|
||||||
- do:
|
- do:
|
||||||
headers:
|
headers:
|
||||||
|
@ -103,7 +107,7 @@ teardown:
|
||||||
|
|
||||||
# attempt to login with invalid credentials
|
# attempt to login with invalid credentials
|
||||||
- do:
|
- do:
|
||||||
catch: request
|
catch: unauthorized
|
||||||
headers:
|
headers:
|
||||||
Authorization: "Basic dW5wcml2aWxlZ2VkX3VzZXI6czNrcml0"
|
Authorization: "Basic dW5wcml2aWxlZ2VkX3VzZXI6czNrcml0"
|
||||||
cluster.health: {}
|
cluster.health: {}
|
||||||
|
|
|
@ -35,7 +35,7 @@ teardown:
|
||||||
---
|
---
|
||||||
"Test create user and update without and with password":
|
"Test create user and update without and with password":
|
||||||
- skip:
|
- skip:
|
||||||
features: headers
|
features: [headers, catch_unauthorized]
|
||||||
|
|
||||||
# test that the role actually works
|
# test that the role actually works
|
||||||
- do:
|
- do:
|
||||||
|
@ -108,7 +108,7 @@ teardown:
|
||||||
|
|
||||||
# validate old password doesn't work
|
# validate old password doesn't work
|
||||||
- do:
|
- do:
|
||||||
catch: request
|
catch: unauthorized
|
||||||
headers:
|
headers:
|
||||||
Authorization: "Basic am9lOnMza3JpdA=="
|
Authorization: "Basic am9lOnMza3JpdA=="
|
||||||
cluster.health: {}
|
cluster.health: {}
|
||||||
|
|
Loading…
Reference in New Issue