basic security extension ignore permissions that use unknown ResourceType or Action (#10896)

* suppress unknown ResourceType and Action for basic-security authorizer stuff

* fix pom

* print failed role, test logs
This commit is contained in:
Clint Wylie 2021-02-23 14:49:09 -08:00 committed by GitHub
parent 553f5c8570
commit 0ecc90142e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 218 additions and 1 deletions

View File

@ -147,5 +147,12 @@
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.druid</groupId>
<artifactId>druid-processing</artifactId>
<version>${project.parent.version}</version>
<scope>test</scope>
<type>test-jar</type>
</dependency>
</dependencies>
</project>

View File

@ -119,4 +119,13 @@ public class BasicAuthorizerPermission
: 0);
return result;
}
@Override
public String toString()
{
return "BasicAuthorizerPermission{" +
"resourceAction=" + resourceAction +
", resourceNamePattern=" + resourceNamePattern +
'}';
}
}

View File

@ -21,19 +21,31 @@ package org.apache.druid.security.basic.authorization.entity;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import org.apache.druid.java.util.common.RE;
import org.apache.druid.java.util.common.logger.Logger;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class BasicAuthorizerRole
{
private static final Logger log = new Logger(BasicAuthorizerRole.class);
private final String name;
private final List<BasicAuthorizerPermission> permissions;
@JsonCreator
public BasicAuthorizerRole(
@JsonProperty("name") String name,
@JsonProperty("permissions") List<BasicAuthorizerPermission> permissions
@JsonProperty("permissions") @JsonDeserialize(using = PermissionsDeserializer.class) List<BasicAuthorizerPermission> permissions
)
{
this.name = name;
@ -78,4 +90,35 @@ public class BasicAuthorizerRole
result = 31 * result + (getPermissions() != null ? getPermissions().hashCode() : 0);
return result;
}
static class PermissionsDeserializer extends JsonDeserializer<List<BasicAuthorizerPermission>>
{
@Override
public List<BasicAuthorizerPermission> deserialize(
JsonParser jsonParser,
DeserializationContext deserializationContext
) throws IOException
{
List<BasicAuthorizerPermission> permissions = new ArrayList<>();
// sanity check
ObjectCodec codec = jsonParser.getCodec();
JsonNode hopefullyAnArray = codec.readTree(jsonParser);
if (!hopefullyAnArray.isArray()) {
throw new RE("Failed to deserialize authorizer role list");
}
for (JsonNode node : hopefullyAnArray) {
try {
permissions.add(codec.treeToValue(node, BasicAuthorizerPermission.class));
}
catch (JsonProcessingException e) {
// ignore unparseable, it might be resource types we don't know about
log.warn(e, "Failed to deserialize authorizer role, ignoring: %s", node.toPrettyString());
}
}
return permissions;
}
}
}

View File

@ -19,10 +19,25 @@
package org.apache.druid.security;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.apache.druid.security.basic.BasicAuthUtils;
import org.apache.druid.security.basic.BasicSecurityDruidModule;
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerPermission;
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRole;
import org.apache.druid.segment.TestHelper;
import org.apache.druid.server.security.Action;
import org.apache.druid.server.security.Resource;
import org.apache.druid.server.security.ResourceAction;
import org.apache.druid.server.security.ResourceType;
import org.junit.Assert;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
public class BasicAuthUtilsTest
{
@Test
@ -36,4 +51,112 @@ public class BasicAuthUtilsTest
Assert.assertEquals(BasicAuthUtils.SALT_LENGTH, salt.length);
Assert.assertEquals(BasicAuthUtils.KEY_LENGTH / 8, hash.length);
}
@Test
public void testPermissionSerdeIsChillAboutUnknownEnumStuffs() throws JsonProcessingException
{
final String someRoleName = "some-role";
final String otherRoleName = "other-role";
final String thirdRoleName = "third-role";
final ResourceAction fooRead = new ResourceAction(new Resource("foo", ResourceType.DATASOURCE), Action.READ);
final ResourceAction barRead = new ResourceAction(new Resource("bar", ResourceType.DATASOURCE), Action.READ);
final ObjectMapper mapper = TestHelper.makeJsonMapper();
mapper.registerModules(new BasicSecurityDruidModule().getJacksonModules());
Map<String, Object> rawMap = new HashMap<>();
rawMap.put(
someRoleName,
new BasicAuthorizerRole(
someRoleName,
BasicAuthorizerPermission.makePermissionList(
ImmutableList.of(
fooRead,
barRead
)
)
)
);
// bad ResourceType
rawMap.put(
otherRoleName,
ImmutableMap.of(
"name",
otherRoleName,
"permissions",
ImmutableList.of(
ImmutableMap.of(
"resourceAction", fooRead,
"resourceNamePattern", "foo"
),
ImmutableMap.of(
"resourceAction",
ImmutableMap.of(
"resource",
ImmutableMap.of("name", "bar", "type", "UNKNOWN"),
"action", "READ"
),
"resourceNamePattern", "bar"
)
)
)
);
// bad Action
rawMap.put(
thirdRoleName,
ImmutableMap.of(
"name",
thirdRoleName,
"permissions",
ImmutableList.of(
ImmutableMap.of(
"resourceAction",
ImmutableMap.of(
"resource",
ImmutableMap.of("name", "some-view", "type", "VIEW"),
"action", "READ"
),
"resourceNamePattern", "some-view"
),
ImmutableMap.of(
"resourceAction",
ImmutableMap.of(
"resource",
ImmutableMap.of("name", "foo", "type", "DATASOURCE"),
"action", "UNKNOWN"
),
"resourceNamePattern", "some-view"
)
)
)
);
byte[] mapBytes = mapper.writeValueAsBytes(rawMap);
Map<String, BasicAuthorizerRole> roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(mapper, mapBytes);
Assert.assertNotNull(roleMap);
Assert.assertEquals(3, roleMap.size());
Assert.assertTrue(roleMap.containsKey(someRoleName));
Assert.assertEquals(2, roleMap.get(someRoleName).getPermissions().size());
Assert.assertEquals(
BasicAuthorizerPermission.makePermissionList(ImmutableList.of(fooRead, barRead)),
roleMap.get(someRoleName).getPermissions()
);
// this one has an unknown ResourceType, expect only 1 permission to deserialize correctly and failure ignored
Assert.assertTrue(roleMap.containsKey(otherRoleName));
Assert.assertEquals(1, roleMap.get(otherRoleName).getPermissions().size());
Assert.assertEquals(
BasicAuthorizerPermission.makePermissionList(ImmutableList.of(fooRead)),
roleMap.get(otherRoleName).getPermissions()
);
// this one has an unknown Action, expect only 1 permission to deserialize correctly and failure ignored
Assert.assertTrue(roleMap.containsKey(thirdRoleName));
Assert.assertEquals(1, roleMap.get(thirdRoleName).getPermissions().size());
Assert.assertEquals(
BasicAuthorizerPermission.makePermissionList(
ImmutableList.of(new ResourceAction(new Resource("some-view", ResourceType.VIEW), Action.READ))
),
roleMap.get(thirdRoleName).getPermissions()
);
}
}

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
~ Licensed to the Apache Software Foundation (ASF) under one
~ or more contributor license agreements. See the NOTICE file
~ distributed with this work for additional information
~ regarding copyright ownership. The ASF licenses this file
~ to you under the Apache License, Version 2.0 (the
~ "License"); you may not use this file except in compliance
~ with the License. You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing,
~ software distributed under the License is distributed on an
~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
~ KIND, either express or implied. See the License for the
~ specific language governing permissions and limitations
~ under the License.
-->
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{ISO8601} %p [%t] %c - %m%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
<Logger level="debug" name="org.apache.druid" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
</Loggers>
</Configuration>