From 3cce84dee6c2f9a78cbd3b6c3def0e67a4d27863 Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Fri, 21 Feb 2020 22:56:56 +1100 Subject: [PATCH 01/15] Validate role templates before save --- .../support/mapper/NativeRoleMappingStore.java | 9 +++++++++ .../mapper/NativeRoleMappingStoreTests.java | 15 +++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStore.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStore.java index 2bd02e6decb1f..aa54613d066d4 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStore.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStore.java @@ -34,6 +34,7 @@ import org.elasticsearch.xpack.core.security.action.rolemapping.DeleteRoleMappingRequest; import org.elasticsearch.xpack.core.security.action.rolemapping.PutRoleMappingRequest; import org.elasticsearch.xpack.core.security.authc.support.mapper.ExpressionRoleMapping; +import org.elasticsearch.xpack.core.security.authc.support.mapper.TemplateRoleName; import org.elasticsearch.xpack.core.security.authc.support.mapper.expressiondsl.ExpressionModel; import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames; import org.elasticsearch.xpack.core.security.authc.support.CachingRealm; @@ -165,6 +166,14 @@ protected ExpressionRoleMapping buildMapping(String id, BytesReference source) { * Stores (create or update) a single mapping in the index */ public void putRoleMapping(PutRoleMappingRequest request, ActionListener listener) { + // Validate all templates before storing the role mapping + try { + for (TemplateRoleName templateRoleName : request.getRoleTemplates()) { + templateRoleName.getRoleNames(scriptService, new ExpressionModel()); + } + } catch (Exception e) { + throw new IllegalArgumentException(e); + } modifyMapping(request.getName(), this::innerPutMapping, request, listener); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStoreTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStoreTests.java index 9d96f1e115869..a549964511d7b 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStoreTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStoreTests.java @@ -24,6 +24,7 @@ import org.elasticsearch.xpack.core.security.action.realm.ClearRealmCacheAction; import org.elasticsearch.xpack.core.security.action.realm.ClearRealmCacheRequest; import org.elasticsearch.xpack.core.security.action.realm.ClearRealmCacheResponse; +import org.elasticsearch.xpack.core.security.action.rolemapping.PutRoleMappingRequest; import org.elasticsearch.xpack.core.security.authc.AuthenticationResult; import org.elasticsearch.xpack.core.security.authc.RealmConfig; import org.elasticsearch.xpack.core.security.authc.RealmSettings; @@ -210,6 +211,20 @@ public void testCacheIsNotClearedIfNoRealmsAreAttached() { assertEquals(0, numInvalidation.get()); } + public void testPutRoleMappingWillComputeRoleNamesBeforeSave() { + final PutRoleMappingRequest putRoleMappingRequest = mock(PutRoleMappingRequest.class); + final TemplateRoleName templateRoleName = mock(TemplateRoleName.class); + final ScriptService scriptService = mock(ScriptService.class); + when(putRoleMappingRequest.getRoleTemplates()).thenReturn(Collections.singletonList(templateRoleName)); + doAnswer(invocationOnMock -> { + throw new IllegalArgumentException(); + }).when(templateRoleName).getRoleNames(eq(scriptService), any()); + + final NativeRoleMappingStore nativeRoleMappingStore = + new NativeRoleMappingStore(Settings.EMPTY, mock(Client.class), mock(SecurityIndexManager.class), scriptService); + expectThrows(IllegalArgumentException.class, () -> nativeRoleMappingStore.putRoleMapping(putRoleMappingRequest, null)); + } + private NativeRoleMappingStore buildRoleMappingStoreForInvalidationTesting(AtomicInteger invalidationCounter, boolean attachRealm) { final Settings settings = Settings.builder().put("path.home", createTempDir()).build(); From 07c78190056926c367efaba8701cfcc3d8664754 Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Fri, 21 Feb 2020 23:09:07 +1100 Subject: [PATCH 02/15] No double wrap for illegal argument exception --- .../security/authc/support/mapper/NativeRoleMappingStore.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStore.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStore.java index aa54613d066d4..a7344424ca9cf 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStore.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStore.java @@ -171,6 +171,8 @@ public void putRoleMapping(PutRoleMappingRequest request, ActionListener Date: Sat, 22 Feb 2020 00:02:04 +1100 Subject: [PATCH 03/15] Fix doc tests --- .../authc/support/mapper/TemplateRoleName.java | 14 ++++++++++++++ .../support/MustacheTemplateEvaluator.java | 9 +++++++-- .../support/mapper/NativeRoleMappingStore.java | 10 ++-------- .../mapper/NativeRoleMappingStoreTests.java | 4 ++-- 4 files changed, 25 insertions(+), 12 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/mapper/TemplateRoleName.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/mapper/TemplateRoleName.java index 59f9eafec1c00..6f24037ec5d8d 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/mapper/TemplateRoleName.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/mapper/TemplateRoleName.java @@ -97,6 +97,20 @@ public List getRoleNames(ScriptService scriptService, ExpressionModel mo } } + public void validate(ScriptService scriptService) { + try { + final XContentParser parser =XContentHelper.createParser( + NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, template, XContentType.JSON); + MustacheTemplateEvaluator.evaluate(scriptService, parser, Collections.emptyMap()); + } catch (IllegalArgumentException e) { + throw e; + } catch (IOException e) { + throw new UncheckedIOException(e); + } catch (Exception e) { + throw new IllegalArgumentException(e); + } + } + private List convertJsonToList(String evaluation) throws IOException { final XContentParser parser = XContentFactory.xContent(XContentType.JSON).createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, evaluation); diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/support/MustacheTemplateEvaluator.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/support/MustacheTemplateEvaluator.java index 02f730333de3a..9a0ff69e74bec 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/support/MustacheTemplateEvaluator.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/support/MustacheTemplateEvaluator.java @@ -26,6 +26,12 @@ private MustacheTemplateEvaluator() { } public static String evaluate(ScriptService scriptService, XContentParser parser, Map extraParams) throws IOException { + TemplateScript compiledTemplate = compile(scriptService, parser, extraParams); + return compiledTemplate.execute(); + } + + public static TemplateScript compile(ScriptService scriptService, XContentParser parser, Map extraParams) throws + IOException { Script script = Script.parse(parser); // Add the user details to the params Map params = new HashMap<>(); @@ -36,7 +42,6 @@ public static String evaluate(ScriptService scriptService, XContentParser parser // Always enforce mustache script lang: script = new Script(script.getType(), script.getType() == ScriptType.STORED ? null : "mustache", script.getIdOrCode(), script.getOptions(), params); - TemplateScript compiledTemplate = scriptService.compile(script, TemplateScript.CONTEXT).newInstance(script.getParams()); - return compiledTemplate.execute(); + return scriptService.compile(script, TemplateScript.CONTEXT).newInstance(script.getParams()); } } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStore.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStore.java index a7344424ca9cf..9586ddb47ab20 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStore.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStore.java @@ -167,14 +167,8 @@ protected ExpressionRoleMapping buildMapping(String id, BytesReference source) { */ public void putRoleMapping(PutRoleMappingRequest request, ActionListener listener) { // Validate all templates before storing the role mapping - try { - for (TemplateRoleName templateRoleName : request.getRoleTemplates()) { - templateRoleName.getRoleNames(scriptService, new ExpressionModel()); - } - } catch (IllegalArgumentException e) { - throw e; - } catch (Exception e) { - throw new IllegalArgumentException(e); + for (TemplateRoleName templateRoleName : request.getRoleTemplates()) { + templateRoleName.validate(scriptService); } modifyMapping(request.getName(), this::innerPutMapping, request, listener); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStoreTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStoreTests.java index a549964511d7b..37490d0ddec11 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStoreTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStoreTests.java @@ -211,14 +211,14 @@ public void testCacheIsNotClearedIfNoRealmsAreAttached() { assertEquals(0, numInvalidation.get()); } - public void testPutRoleMappingWillComputeRoleNamesBeforeSave() { + public void testPutRoleMappingWillValidateTemplateRoleNamesBeforeSave() { final PutRoleMappingRequest putRoleMappingRequest = mock(PutRoleMappingRequest.class); final TemplateRoleName templateRoleName = mock(TemplateRoleName.class); final ScriptService scriptService = mock(ScriptService.class); when(putRoleMappingRequest.getRoleTemplates()).thenReturn(Collections.singletonList(templateRoleName)); doAnswer(invocationOnMock -> { throw new IllegalArgumentException(); - }).when(templateRoleName).getRoleNames(eq(scriptService), any()); + }).when(templateRoleName).validate(scriptService); final NativeRoleMappingStore nativeRoleMappingStore = new NativeRoleMappingStore(Settings.EMPTY, mock(Client.class), mock(SecurityIndexManager.class), scriptService); From e07b24373fb4622c17468e1539fc39f9a188e853 Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Wed, 26 Feb 2020 16:08:01 +1100 Subject: [PATCH 04/15] Address feedback --- .../support/mapper/TemplateRoleName.java | 4 +--- .../support/mapper/TemplateRoleNameTests.java | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/mapper/TemplateRoleName.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/mapper/TemplateRoleName.java index 6f24037ec5d8d..e5a039b89a1ad 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/mapper/TemplateRoleName.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/mapper/TemplateRoleName.java @@ -99,9 +99,7 @@ public List getRoleNames(ScriptService scriptService, ExpressionModel mo public void validate(ScriptService scriptService) { try { - final XContentParser parser =XContentHelper.createParser( - NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, template, XContentType.JSON); - MustacheTemplateEvaluator.evaluate(scriptService, parser, Collections.emptyMap()); + parseTemplate(scriptService, Collections.emptyMap()); } catch (IllegalArgumentException e) { throw e; } catch (IOException e) { diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authc/support/mapper/TemplateRoleNameTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authc/support/mapper/TemplateRoleNameTests.java index cab10ca728323..3c06fce3a0fe4 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authc/support/mapper/TemplateRoleNameTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authc/support/mapper/TemplateRoleNameTests.java @@ -116,4 +116,28 @@ public void tryEquals(TemplateRoleName original) { }; EqualsHashCodeTestUtils.checkEqualsAndHashCode(original, copy, mutate); } + + public void testValidate() { + final ScriptService scriptService = new ScriptService(Settings.EMPTY, + Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine()), ScriptModule.CORE_CONTEXTS); + final ExpressionModel model = new ExpressionModel(); + model.defineField("username", "hulk"); + model.defineField("groups", Arrays.asList("avengers", "defenders", "panthenon")); + + final TemplateRoleName plainString = new TemplateRoleName(new BytesArray("{ \"source\":\"heroes\" }"), Format.STRING); + plainString.validate(scriptService); + + final TemplateRoleName user = new TemplateRoleName(new BytesArray("{ \"source\":\"_user_{{username}}\" }"), Format.STRING); + user.validate(scriptService); + + final TemplateRoleName groups = new TemplateRoleName(new BytesArray("{ \"source\":\"{{#tojson}}groups{{/tojson}}\" }"), + Format.JSON); + groups.validate(scriptService); + + final TemplateRoleName notObject = new TemplateRoleName(new BytesArray("heroes"), Format.STRING); + expectThrows(IllegalArgumentException.class, () -> notObject.validate(scriptService)); + + final TemplateRoleName invalidField = new TemplateRoleName(new BytesArray("{ \"foo\":\"heroes\" }"), Format.STRING); + expectThrows(IllegalArgumentException.class, () -> invalidField.validate(scriptService)); + } } From 51d9c20619a096594000abad3cf0664baac1210c Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Wed, 26 Feb 2020 16:09:19 +1100 Subject: [PATCH 05/15] Revert change to mustacheTemplateEvaluator --- .../core/security/support/MustacheTemplateEvaluator.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/support/MustacheTemplateEvaluator.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/support/MustacheTemplateEvaluator.java index 9a0ff69e74bec..02f730333de3a 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/support/MustacheTemplateEvaluator.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/support/MustacheTemplateEvaluator.java @@ -26,12 +26,6 @@ private MustacheTemplateEvaluator() { } public static String evaluate(ScriptService scriptService, XContentParser parser, Map extraParams) throws IOException { - TemplateScript compiledTemplate = compile(scriptService, parser, extraParams); - return compiledTemplate.execute(); - } - - public static TemplateScript compile(ScriptService scriptService, XContentParser parser, Map extraParams) throws - IOException { Script script = Script.parse(parser); // Add the user details to the params Map params = new HashMap<>(); @@ -42,6 +36,7 @@ public static TemplateScript compile(ScriptService scriptService, XContentParser // Always enforce mustache script lang: script = new Script(script.getType(), script.getType() == ScriptType.STORED ? null : "mustache", script.getIdOrCode(), script.getOptions(), params); - return scriptService.compile(script, TemplateScript.CONTEXT).newInstance(script.getParams()); + TemplateScript compiledTemplate = scriptService.compile(script, TemplateScript.CONTEXT).newInstance(script.getParams()); + return compiledTemplate.execute(); } } From 18333bb5fdde92b265f942e1e8e21953f400610b Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Fri, 28 Feb 2020 16:08:31 +1100 Subject: [PATCH 06/15] Add more tests for role template validation --- .../support/mapper/TemplateRoleNameTests.java | 80 ++++++++++++++++++- 1 file changed, 77 insertions(+), 3 deletions(-) diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authc/support/mapper/TemplateRoleNameTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authc/support/mapper/TemplateRoleNameTests.java index 3c06fce3a0fe4..f28ee74e71b87 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authc/support/mapper/TemplateRoleNameTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authc/support/mapper/TemplateRoleNameTests.java @@ -6,6 +6,9 @@ package org.elasticsearch.xpack.core.security.authc.support.mapper; +import org.elasticsearch.cluster.ClusterChangedEvent; +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesReference; @@ -17,8 +20,11 @@ import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.script.ScriptException; +import org.elasticsearch.script.ScriptMetaData; import org.elasticsearch.script.ScriptModule; import org.elasticsearch.script.ScriptService; +import org.elasticsearch.script.StoredScriptSource; import org.elasticsearch.script.mustache.MustacheScriptEngine; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.EqualsHashCodeTestUtils; @@ -31,8 +37,11 @@ import java.util.Collections; import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.notNullValue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class TemplateRoleNameTests extends ESTestCase { @@ -120,9 +129,6 @@ public void tryEquals(TemplateRoleName original) { public void testValidate() { final ScriptService scriptService = new ScriptService(Settings.EMPTY, Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine()), ScriptModule.CORE_CONTEXTS); - final ExpressionModel model = new ExpressionModel(); - model.defineField("username", "hulk"); - model.defineField("groups", Arrays.asList("avengers", "defenders", "panthenon")); final TemplateRoleName plainString = new TemplateRoleName(new BytesArray("{ \"source\":\"heroes\" }"), Format.STRING); plainString.validate(scriptService); @@ -140,4 +146,72 @@ public void testValidate() { final TemplateRoleName invalidField = new TemplateRoleName(new BytesArray("{ \"foo\":\"heroes\" }"), Format.STRING); expectThrows(IllegalArgumentException.class, () -> invalidField.validate(scriptService)); } + + public void testValidateWillPassWithEmptyContext() { + final ScriptService scriptService = new ScriptService(Settings.EMPTY, + Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine()), ScriptModule.CORE_CONTEXTS); + + final BytesReference template = new BytesArray("{ \"source\":\"" + + "{{username}}/{{dn}}/{{realm}}/{{metadata}}" + + "{{#realm}}" + + " {{name}}/{{type}}" + + "{{/realm}}" + + "{{#toJson}}groups{{/toJson}}" + + "{{^groups}}{{.}}{{/groups}}" + + "{{#metadata}}" + + " {{#first}}" + + "
  • {{name}}
  • " + + " {{/first}}" + + " {{#link}}" + + "
  • {{name}}
  • " + + " {{/link}}" + + " {{#toJson}}subgroups{{/toJson}}" + + " {{something-else}}" + + "{{/metadata}}\" }"); + final TemplateRoleName templateRoleName = new TemplateRoleName(template, Format.STRING); + templateRoleName.validate(scriptService); + } + + public void testValidateWillFailForSyntaxError() { + final ScriptService scriptService = new ScriptService(Settings.EMPTY, + Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine()), ScriptModule.CORE_CONTEXTS); + + final BytesReference template = new BytesArray("{ \"source\":\" {{#not-closed}} {{other-variable}} \" }"); + + final IllegalArgumentException e = expectThrows(IllegalArgumentException.class, + () -> new TemplateRoleName(template, Format.STRING).validate(scriptService)); + assertTrue(e.getCause() instanceof ScriptException); + } + + public void testValidationWillFailWhenInlineScriptIsNotEnabled() { + final Settings settings = Settings.builder().put("script.allowed_types", ScriptService.ALLOW_NONE).build(); + final ScriptService scriptService = new ScriptService(settings, + Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine()), ScriptModule.CORE_CONTEXTS); + final BytesReference inlineScript = new BytesArray("{ \"source\":\"\" }"); + final IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> new TemplateRoleName(inlineScript, Format.STRING).validate(scriptService)); + assertThat(e.getMessage(), containsString("[inline]")); + } + + public void testValidateWillFailawhenStoredScriptIsNotEnabled() { + final Settings settings = Settings.builder().put("script.allowed_types", ScriptService.ALLOW_NONE).build(); + final ScriptService scriptService = new ScriptService(settings, + Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine()), ScriptModule.CORE_CONTEXTS); + final ClusterChangedEvent clusterChangedEvent = mock(ClusterChangedEvent.class); + final ClusterState clusterState = mock(ClusterState.class); + final MetaData metaData = mock(MetaData.class); + final StoredScriptSource storedScriptSource = mock(StoredScriptSource.class); + final ScriptMetaData scriptMetaData = new ScriptMetaData.Builder(null).storeScript("foo", storedScriptSource).build(); + when(clusterChangedEvent.state()).thenReturn(clusterState); + when(clusterState.metaData()).thenReturn(metaData); + when(metaData.custom(ScriptMetaData.TYPE)).thenReturn(scriptMetaData); + when(storedScriptSource.getLang()).thenReturn("mustache"); + when(storedScriptSource.getSource()).thenReturn(""); + when(storedScriptSource.getOptions()).thenReturn(Collections.emptyMap()); + scriptService.applyClusterState(clusterChangedEvent); + + final BytesReference storedScript = new BytesArray("{ \"id\":\"foo\" }"); + final IllegalArgumentException e = expectThrows(IllegalArgumentException.class, + () -> new TemplateRoleName(storedScript, Format.STRING).validate(scriptService)); + assertThat(e.getMessage(), containsString("[stored]")); + } } From d58a4c8ea60a5d3d33ea708f9d7b0befacbda4e5 Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Fri, 28 Feb 2020 16:38:04 +1100 Subject: [PATCH 07/15] Fix tests and add doc --- .../en/rest-api/security/create-role-mappings.asciidoc | 8 ++++++-- .../authc/support/mapper/TemplateRoleNameTests.java | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc b/x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc index e80ff662c8305..3987e984cbd2c 100644 --- a/x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc +++ b/x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc @@ -23,7 +23,7 @@ Creates and updates role mappings. [[security-api-put-role-mapping-desc]] ==== {api-description-title} -Role mappings define which roles are assigned to each user. Each mapping has +Role mappings define which roles are assigned to each user. Each mapping has _rules_ that identify users and a list of _roles_ that are granted to those users. The role mapping APIs are generally the preferred way to manage role mappings @@ -77,10 +77,14 @@ _Exactly one of `roles` or `role_templates` must be specified_. `rules`:: (Required, object) The rules that determine which users should be matched by the mapping. A rule is a logical condition that is expressed by using a JSON DSL. -See <>. +See <>. ==== Role Templates +NOTE: Role templates require corresponding scripting feature to be enabled. +Otherwise, role mapping with role templates will fail to create. See +<>. + The most common use for role mappings is to create a mapping from a known value on the user to a fixed role name. For example, all users in the `cn=admin,dc=example,dc=com` LDAP group should be diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authc/support/mapper/TemplateRoleNameTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authc/support/mapper/TemplateRoleNameTests.java index f28ee74e71b87..70e158deb94bd 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authc/support/mapper/TemplateRoleNameTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authc/support/mapper/TemplateRoleNameTests.java @@ -188,7 +188,8 @@ public void testValidationWillFailWhenInlineScriptIsNotEnabled() { final ScriptService scriptService = new ScriptService(settings, Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine()), ScriptModule.CORE_CONTEXTS); final BytesReference inlineScript = new BytesArray("{ \"source\":\"\" }"); - final IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> new TemplateRoleName(inlineScript, Format.STRING).validate(scriptService)); + final IllegalArgumentException e = expectThrows(IllegalArgumentException.class, + () -> new TemplateRoleName(inlineScript, Format.STRING).validate(scriptService)); assertThat(e.getMessage(), containsString("[inline]")); } From 4587f14601d6ad09c6cf533384ae7faa6bd13162 Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Mon, 2 Mar 2020 10:25:36 +1100 Subject: [PATCH 08/15] Update x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc Co-Authored-By: Ioannis Kakavas --- .../docs/en/rest-api/security/create-role-mappings.asciidoc | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc b/x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc index 3987e984cbd2c..532b0246033f2 100644 --- a/x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc +++ b/x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc @@ -81,9 +81,7 @@ See <>. ==== Role Templates -NOTE: Role templates require corresponding scripting feature to be enabled. -Otherwise, role mapping with role templates will fail to create. See -<>. +NOTE: Role templates require the relevant scripting feature to be enabled, otherwise the creation of a role mapping with role templates will fail. See <>. The most common use for role mappings is to create a mapping from a known value on the user to a fixed role name. @@ -343,4 +341,3 @@ POST /_security/role_mapping/mapping9 <1> Because it is not possible to specify both `roles` and `role_templates` in the same role mapping, we can apply a "fixed name" role by using a template that has no substitutions. - From 59b840550c0605d54476fca8072384af34031bd2 Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Mon, 2 Mar 2020 11:05:27 +1100 Subject: [PATCH 09/15] Add example of using stored script for role templates --- .../security/create-role-mappings.asciidoc | 36 ++++++++++++++++++- .../support/mapper/TemplateRoleNameTests.java | 20 ++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc b/x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc index 532b0246033f2..78b90ef408b13 100644 --- a/x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc +++ b/x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc @@ -81,7 +81,9 @@ See <>. ==== Role Templates -NOTE: Role templates require the relevant scripting feature to be enabled, otherwise the creation of a role mapping with role templates will fail. See <>. +NOTE: Role templates require the relevant scripting feature to be enabled, +otherwise the creation of a role mapping with role templates will fail. +See <>. The most common use for role mappings is to create a mapping from a known value on the user to a fixed role name. @@ -235,6 +237,38 @@ POST /_security/role_mapping/mapping5 <2> Because the template produces a JSON array, the format must be set to `json`. +In addition to inline script, we can also use stored script for the +`template` field. Above role mapping can also be created with the follows: + +[source,console] +------------------------------------------------------------ +POST /_scripts/derive-roles +{ + "script": { + "lang": "mustache", + "source" : "{{#tojson}}groups{{/tojson}}" + } +} +------------------------------------------------------------ + +[source,console] +------------------------------------------------------------ +POST /_security/role_mapping/mapping5 +{ + "role_templates": [ + { + "template": { "id": "derive-roles" }, + "format" : "json" + } + ], + "rules": { + "field" : { "realm.name" : "saml1" } + }, + "enabled": true +} +------------------------------------------------------------ +// TEST[continued] + The following example matches users within a specific LDAP sub-tree: [source,console] diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authc/support/mapper/TemplateRoleNameTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authc/support/mapper/TemplateRoleNameTests.java index 70e158deb94bd..19433d132f1ed 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authc/support/mapper/TemplateRoleNameTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authc/support/mapper/TemplateRoleNameTests.java @@ -193,7 +193,7 @@ public void testValidationWillFailWhenInlineScriptIsNotEnabled() { assertThat(e.getMessage(), containsString("[inline]")); } - public void testValidateWillFailawhenStoredScriptIsNotEnabled() { + public void testValidateWillFailWhenStoredScriptIsNotEnabled() { final Settings settings = Settings.builder().put("script.allowed_types", ScriptService.ALLOW_NONE).build(); final ScriptService scriptService = new ScriptService(settings, Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine()), ScriptModule.CORE_CONTEXTS); @@ -215,4 +215,22 @@ public void testValidateWillFailawhenStoredScriptIsNotEnabled() { () -> new TemplateRoleName(storedScript, Format.STRING).validate(scriptService)); assertThat(e.getMessage(), containsString("[stored]")); } + + public void testValidateWillFailWhenStoredScriptIsNotFound() { + final ScriptService scriptService = new ScriptService(Settings.EMPTY, + Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine()), ScriptModule.CORE_CONTEXTS); + final ClusterChangedEvent clusterChangedEvent = mock(ClusterChangedEvent.class); + final ClusterState clusterState = mock(ClusterState.class); + final MetaData metaData = mock(MetaData.class); + final ScriptMetaData scriptMetaData = new ScriptMetaData.Builder(null).build(); + when(clusterChangedEvent.state()).thenReturn(clusterState); + when(clusterState.metaData()).thenReturn(metaData); + when(metaData.custom(ScriptMetaData.TYPE)).thenReturn(scriptMetaData); + scriptService.applyClusterState(clusterChangedEvent); + + final BytesReference storedScript = new BytesArray("{ \"id\":\"foo\" }"); + final IllegalArgumentException e = expectThrows(IllegalArgumentException.class, + () -> new TemplateRoleName(storedScript, Format.STRING).validate(scriptService)); + assertThat(e.getMessage(), containsString("unable to find script")); + } } From 2f4494652a7218fea78fdd0f6ac1da0e31388ad8 Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Tue, 3 Mar 2020 10:51:30 +1100 Subject: [PATCH 10/15] Update x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc Co-Authored-By: Lisa Cawley --- x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc b/x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc index 78b90ef408b13..67410b59ecbad 100644 --- a/x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc +++ b/x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc @@ -237,7 +237,7 @@ POST /_security/role_mapping/mapping5 <2> Because the template produces a JSON array, the format must be set to `json`. -In addition to inline script, we can also use stored script for the +In addition to an inline script, you can also use a stored script for the `template` field. Above role mapping can also be created with the follows: [source,console] From 2f84c1e4dadd0b135da828118e211dcbe9635fe2 Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Tue, 3 Mar 2020 10:51:46 +1100 Subject: [PATCH 11/15] Update x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc Co-Authored-By: Lisa Cawley --- x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc b/x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc index 67410b59ecbad..bb333dc7ceb04 100644 --- a/x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc +++ b/x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc @@ -82,7 +82,7 @@ See <>. ==== Role Templates NOTE: Role templates require the relevant scripting feature to be enabled, -otherwise the creation of a role mapping with role templates will fail. +Otherwise, all attempts to create a role mapping with role templates fail. See <>. The most common use for role mappings is to create a mapping from a known value From 3f2512fccb364b055326ff5a97080756e15124e8 Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Tue, 3 Mar 2020 10:55:25 +1100 Subject: [PATCH 12/15] Update x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc Co-Authored-By: Lisa Cawley --- x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc b/x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc index bb333dc7ceb04..c4adf37b9117b 100644 --- a/x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc +++ b/x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc @@ -81,7 +81,7 @@ See <>. ==== Role Templates -NOTE: Role templates require the relevant scripting feature to be enabled, +NOTE: To use role templates successfully, the relevant scripting feature must be enabled. Otherwise, all attempts to create a role mapping with role templates fail. See <>. From b6f68ba2c54575f985f207d79e181a93c012742f Mon Sep 17 00:00:00 2001 From: lcawl Date: Mon, 2 Mar 2020 16:42:34 -0800 Subject: [PATCH 13/15] [DOCS] Moves role templates into API description --- .../security/create-role-mappings.asciidoc | 53 +++++++++---------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc b/x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc index c4adf37b9117b..516d8ff904e54 100644 --- a/x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc +++ b/x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc @@ -37,6 +37,31 @@ roles API>> or <>. For more information, see <>. +[[security-api-put-role-mapping-desc-templates]] +===== Role templates + +The most common use for role mappings is to create a mapping from a known value +on the user to a fixed role name. For example, all users in the +`cn=admin,dc=example,dc=com` LDAP group should be given the `superuser` role in +{es}. The `roles` field is used for this purpose. + +For more complex needs, it is possible to use Mustache templates to dynamically +determine the names of the roles that should be granted to the user. The +`role_templates` field is used for this purpose. + +NOTE: To use role templates successfully, the relevant scripting feature must be +enabled. Otherwise, all attempts to create a role mapping with role templates +fail. See <>. + +All of the <> that are available in the +role mapping `rules` are also available in the role templates. Thus it is possible +to assign a user to a role that reflects their `username`, their `groups`, or the +name of the `realm` to which they authenticated. + +By default a template is evaluated to produce a single string that is the name +of the role which should be assigned to the user. If the `format` of the template +is set to `"json"` then the template is expected to produce a JSON string or an +array of JSON strings for the role names. [[security-api-put-role-mapping-path-params]] ==== {api-path-parms-title} @@ -79,34 +104,6 @@ _Exactly one of `roles` or `role_templates` must be specified_. mapping. A rule is a logical condition that is expressed by using a JSON DSL. See <>. -==== Role Templates - -NOTE: To use role templates successfully, the relevant scripting feature must be enabled. -Otherwise, all attempts to create a role mapping with role templates fail. -See <>. - -The most common use for role mappings is to create a mapping from a known value -on the user to a fixed role name. -For example, all users in the `cn=admin,dc=example,dc=com` LDAP group should be -given the `superuser` role in {es}. -The `roles` field is used for this purpose. - -For more complex needs it is possible to use Mustache templates to dynamically -determine the names of the roles that should be granted to the user. -The `role_templates` field is used for this purpose. - -All of the <> that are available in the -role mapping `rules` are also available in the role templates. Thus it is possible -to assign a user to a role that reflects their `username`, their `groups` or the -name of the `realm` to which they authenticated. - -By default a template is evaluated to produce a single string that is the name -of the role which should be assigned to the user. If the `format` of the template -is set to `"json"` then the template is expected to produce a JSON string, or an -array of JSON strings for the role name(s). - -The Examples section below demonstrates the use of templated role names. - [[security-api-put-role-mapping-example]] ==== {api-examples-title} From c86d09ba4e737d347956154a0412ee5e2addc840 Mon Sep 17 00:00:00 2001 From: Lisa Cawley Date: Tue, 3 Mar 2020 09:45:45 -0800 Subject: [PATCH 14/15] Update x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc --- x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc b/x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc index 516d8ff904e54..51b485ead7914 100644 --- a/x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc +++ b/x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc @@ -37,7 +37,7 @@ roles API>> or <>. For more information, see <>. -[[security-api-put-role-mapping-desc-templates]] +[[_role_templates]] ===== Role templates The most common use for role mappings is to create a mapping from a known value From fe0da6782768acfb82984e5b49bb0af927ff38aa Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Tue, 24 Mar 2020 14:38:57 +1100 Subject: [PATCH 15/15] Remove doc of using stored script for role template since we do not recommend it --- .../security/create-role-mappings.asciidoc | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc b/x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc index 51b485ead7914..51ccb39a69eca 100644 --- a/x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc +++ b/x-pack/docs/en/rest-api/security/create-role-mappings.asciidoc @@ -234,38 +234,6 @@ POST /_security/role_mapping/mapping5 <2> Because the template produces a JSON array, the format must be set to `json`. -In addition to an inline script, you can also use a stored script for the -`template` field. Above role mapping can also be created with the follows: - -[source,console] ------------------------------------------------------------- -POST /_scripts/derive-roles -{ - "script": { - "lang": "mustache", - "source" : "{{#tojson}}groups{{/tojson}}" - } -} ------------------------------------------------------------- - -[source,console] ------------------------------------------------------------- -POST /_security/role_mapping/mapping5 -{ - "role_templates": [ - { - "template": { "id": "derive-roles" }, - "format" : "json" - } - ], - "rules": { - "field" : { "realm.name" : "saml1" } - }, - "enabled": true -} ------------------------------------------------------------- -// TEST[continued] - The following example matches users within a specific LDAP sub-tree: [source,console]