gianm commented on code in PR #17774:
URL: https://github.com/apache/druid/pull/17774#discussion_r2027868639
##########
processing/src/test/java/org/apache/druid/query/DataSourceTest.java:
##########
@@ -221,4 +225,53 @@ public void
testMapWithRestriction_onRestrictedDataSource_alwaysThrows()
ISE e3 = Assert.assertThrows(ISE.class, () ->
restrictedDataSource.withPolicies(policyWasNotChecked));
Assert.assertEquals("Missing policy check result for table [table1]",
e3.getMessage());
}
+
+ @Test
+ public void testValidate_onInlineDataSource()
+ {
+ InlineDataSource inlineDataSource =
InlineDataSource.fromIterable(ImmutableList.of(), RowSignature.empty());
+
Assert.assertTrue(inlineDataSource.validate(NoopPolicyEnforcer.instance()));
+ Assert.assertTrue(inlineDataSource.validate(new
RestrictAllTablesPolicyEnforcer(null)));
+ }
+
+ @Test
+ public void testValidate_onRestrictedDataSource()
+ {
+ RestrictedDataSource restrictedDataSource = RestrictedDataSource.create(
+ TableDataSource.create("table1"),
+ RowFilterPolicy.from(new NullFilter("random-column", null))
+ );
+
Assert.assertTrue(restrictedDataSource.validate(NoopPolicyEnforcer.instance()));
+ Assert.assertTrue(restrictedDataSource.validate(new
RestrictAllTablesPolicyEnforcer(null)));
+ // Fail, only NoRestrictionPolicy is allowed.
+ Assert.assertFalse(restrictedDataSource.validate(new
RestrictAllTablesPolicyEnforcer(ImmutableList.of(
+ "NoRestrictionPolicy"))));
+ }
+
+ @Test
+ public void testValidate_onTableDataSource()
+ {
+ TableDataSource tableDataSource = TableDataSource.create("table1");
+ Assert.assertTrue(tableDataSource.validate(NoopPolicyEnforcer.instance()));
+ // Fail, policy must exist on table
+ Assert.assertFalse(tableDataSource.validate(new
RestrictAllTablesPolicyEnforcer(null)));
+ }
+
+ @Test
+ public void testValidate_onUnionDataSource()
Review Comment:
Please include a version of this where there is a `union` and both children
are `restricted`. In this case would `RestrictAllTablesPolicyEnforcer` be able
to pass?
Please also include one with a `join`.
##########
integration-tests/docker/environment-configs/common:
##########
@@ -39,6 +39,7 @@ druid_auth_authenticatorChain=["basic"]
druid_auth_authorizer_basic_type=basic
druid_auth_authorizers=["basic"]
druid_auth_authorizeQueryContextParams=true
+druid_policy_enforcer_type=none
Review Comment:
Let's not include this here, same reason as we shouldn't include in the
default configs.
##########
processing/src/main/java/org/apache/druid/query/policy/PolicyEnforcer.java:
##########
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+package org.apache.druid.query.policy;
+
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import org.apache.druid.guice.annotations.UnstableApi;
+import org.apache.druid.query.DataSource;
+import org.apache.druid.segment.Segment;
+
+/**
+ * A mechanism for enforcing policies on data sources in Druid queries.
+ * <p>
+ * Note: The {@code PolicyEnforcer} is intended to serve as a sanity checker
and not as a primary authorization mechanism.
+ * It should not be used to implement security rules. Instead, it acts as a
last line of defense to verify that
+ * security policies have been implemented correctly and to prevent incorrect
policy usage.
+ * </p>
+ */
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
+@JsonSubTypes({
+ @JsonSubTypes.Type(value = NoopPolicyEnforcer.class, name = "none"),
+ @JsonSubTypes.Type(value = RestrictAllTablesPolicyEnforcer.class, name =
"restrictAllTables"),
+})
+@UnstableApi
+public interface PolicyEnforcer
+{
+
+ /**
+ * Validate a {@link DataSource} node against the policy enforcer. This
method is called during query planning from
+ * {@link DataSource#validate(PolicyEnforcer)}.
+ * <p>
+ * Don't call this method directly, use {@link
DataSource#validate(PolicyEnforcer)}.
+ *
+ * @param ds data source to validate
+ * @return true if the data source is valid according to the policy
enforcer, false otherwise
+ */
+ boolean validate(DataSource ds, Policy policy);
+
+
+ /**
+ * Validate a {@link Segment} node against the policy enforcer. This method
is called during query planning from
+ * {@link Segment#validate(PolicyEnforcer)}.
+ * <p>
+ * Don't call this method directly, use {@link
Segment#validate(PolicyEnforcer)}.
+ *
+ * @param segment segment to validate
+ * @return true if the segment is valid according to the policy enforcer,
false otherwise
+ */
+ boolean validate(Segment segment, Policy policy);
Review Comment:
I believe a call site for this is missing on realtime tasks. There should be
something in `SinkQuerySegmentWalker`. Please add a test that covers that too.
##########
processing/src/main/java/org/apache/druid/query/policy/PolicyEnforcer.java:
##########
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+package org.apache.druid.query.policy;
+
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import org.apache.druid.guice.annotations.UnstableApi;
+import org.apache.druid.query.DataSource;
+import org.apache.druid.segment.Segment;
+
+/**
+ * A mechanism for enforcing policies on data sources in Druid queries.
+ * <p>
+ * Note: The {@code PolicyEnforcer} is intended to serve as a sanity checker
and not as a primary authorization mechanism.
+ * It should not be used to implement security rules. Instead, it acts as a
last line of defense to verify that
+ * security policies have been implemented correctly and to prevent incorrect
policy usage.
+ * </p>
+ */
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
+@JsonSubTypes({
+ @JsonSubTypes.Type(value = NoopPolicyEnforcer.class, name = "none"),
+ @JsonSubTypes.Type(value = RestrictAllTablesPolicyEnforcer.class, name =
"restrictAllTables"),
+})
+@UnstableApi
+public interface PolicyEnforcer
+{
+
+ /**
+ * Validate a {@link DataSource} node against the policy enforcer. This
method is called during query planning from
+ * {@link DataSource#validate(PolicyEnforcer)}.
+ * <p>
+ * Don't call this method directly, use {@link
DataSource#validate(PolicyEnforcer)}.
+ *
+ * @param ds data source to validate
+ * @return true if the data source is valid according to the policy
enforcer, false otherwise
+ */
+ boolean validate(DataSource ds, Policy policy);
Review Comment:
I see a few call sites missing for this:
- it isn't being called on realtime tasks (equivalent of `ServerManager`
there is `SinkQuerySegmentWalker`)
- it isn't being called for any MSQ paths-- for this I think it would work
to call it in `BaseLeafFrameProcessorFactory#makeProcessors` right before
creating the `segmentMapFnProcessor`. Actually, you could also move
`validate(Segment)` into the map fn itself, which would simplify enforcement.
All the enforcement logic could be in the `BaseLeafFrameProcessorFactory` class.
Please add tests that cover these.
##########
processing/src/test/java/org/apache/druid/query/policy/RestrictAllTablesPolicyEnforcerTest.java:
##########
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+package org.apache.druid.query.policy;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.ImmutableList;
+import org.apache.druid.query.RestrictedDataSource;
+import org.apache.druid.query.TableDataSource;
+import org.apache.druid.query.filter.NullFilter;
+import org.apache.druid.segment.TestHelper;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class RestrictAllTablesPolicyEnforcerTest
+{
+ @Test
+ public void test_serialize() throws Exception
+ {
+ RestrictAllTablesPolicyEnforcer policyEnforcer = new
RestrictAllTablesPolicyEnforcer(null);
+ ObjectMapper jsonMapper = TestHelper.makeJsonMapper();
+
+ String expected =
"{\"type\":\"restrictAllTables\",\"allowedPolicies\":[]}";
+ Assert.assertEquals(expected,
jsonMapper.writeValueAsString(policyEnforcer));
+ }
+
+ @Test
+ public void test_serde_roundTrip() throws Exception
+ {
+ RestrictAllTablesPolicyEnforcer policyEnforcer = new
RestrictAllTablesPolicyEnforcer(null);
+ ObjectMapper jsonMapper = TestHelper.makeJsonMapper();
+ PolicyEnforcer deserialized = jsonMapper.readValue(
+ jsonMapper.writeValueAsString(policyEnforcer),
+ PolicyEnforcer.class
+ );
+ Assert.assertEquals(policyEnforcer, deserialized);
+ }
+
+ @Test
+ public void test_validate() throws Exception
+ {
+ RestrictAllTablesPolicyEnforcer policyEnforcer = new
RestrictAllTablesPolicyEnforcer(null);
+ RowFilterPolicy policy = RowFilterPolicy.from(new NullFilter("some-col",
null));
+ TableDataSource table = TableDataSource.create("table");
+ RestrictedDataSource restricted = RestrictedDataSource.create(table,
policy);
+
+ Assert.assertFalse(policyEnforcer.validate(table));
+ Assert.assertTrue(policyEnforcer.validate(restricted));
Review Comment:
ok, TY
##########
server/src/main/java/org/apache/druid/guice/security/PolicyModule.java:
##########
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+package org.apache.druid.guice.security;
+
+import com.google.inject.Binder;
+import org.apache.druid.guice.JsonConfigProvider;
+import org.apache.druid.initialization.DruidModule;
+import org.apache.druid.query.policy.PolicyEnforcer;
+
+/**
+ * Module for configuring the policy enforcer.
+ */
+public class PolicyModule implements DruidModule
+{
+ @Override
+ public void configure(Binder binder)
+ {
+ JsonConfigProvider.bind(binder, "druid.policy.enforcer",
PolicyEnforcer.class);
Review Comment:
Ah, maybe you had to include the config in the default files because there
is no default here. This is the right place to put a default, you can do it
with `JsonConfigProvider.bindWithDefault`. It doesn't have the same problem as
Jackson's `defaultImpl`: with the default here, if the type is misspelled then
the user will get an error rather than silently using the default impl.
##########
processing/src/main/java/org/apache/druid/query/policy/RestrictAllTablesPolicyEnforcer.java:
##########
@@ -0,0 +1,109 @@
+/*
+ * 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.
+ */
+
+package org.apache.druid.query.policy;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import org.apache.druid.query.DataSource;
+import org.apache.druid.query.RestrictedDataSource;
+import org.reflections.Reflections;
+
+import javax.annotation.Nullable;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * All tables must be restricted by a policy.
+ */
+public class RestrictAllTablesPolicyEnforcer implements PolicyEnforcer
+{
+ private final ImmutableList<String> allowedPolicies;
+
+ @JsonCreator
+ public RestrictAllTablesPolicyEnforcer(
+ @Nullable @JsonProperty("allowedPolicies") List<String> allowedPolicies
+ )
+ {
+ if (allowedPolicies == null) {
+ this.allowedPolicies = ImmutableList.of();
+ } else {
+ Set<String> policyClazz = new
Reflections("org.apache.druid.query.policy").getSubTypesOf(
Review Comment:
Extensions can't make code changes here, though, because the code here is
core. Extensions could fork it and create a new enforcer, but then it wouldn't
be the same enforcer. So as it is, this code isn't really useful, because it
only works for core policies, and there's only one kind of policy in core
anyway. IMO, it's better to remove this so any class can be provided here.
##########
processing/src/main/java/org/apache/druid/query/policy/PolicyEnforcer.java:
##########
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+package org.apache.druid.query.policy;
+
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import org.apache.druid.guice.annotations.UnstableApi;
+import org.apache.druid.query.DataSource;
+import org.apache.druid.segment.Segment;
+
+/**
+ * A mechanism for enforcing policies on data sources in Druid queries.
+ * <p>
+ * Note: The {@code PolicyEnforcer} is intended to serve as a sanity checker
and not as a primary authorization mechanism.
+ * It should not be used to implement security rules. Instead, it acts as a
last line of defense to verify that
+ * security policies have been implemented correctly and to prevent incorrect
policy usage.
+ * </p>
+ */
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
+@JsonSubTypes({
+ @JsonSubTypes.Type(value = NoopPolicyEnforcer.class, name = "none"),
+ @JsonSubTypes.Type(value = RestrictAllTablesPolicyEnforcer.class, name =
"restrictAllTables"),
+})
+@UnstableApi
+public interface PolicyEnforcer
+{
+
+ /**
+ * Validate a {@link DataSource} node against the policy enforcer. This
method is called during query planning from
+ * {@link DataSource#validate(PolicyEnforcer)}.
+ * <p>
+ * Don't call this method directly, use {@link
DataSource#validate(PolicyEnforcer)}.
+ *
+ * @param ds data source to validate
+ * @return true if the data source is valid according to the policy
enforcer, false otherwise
+ */
+ boolean validate(DataSource ds, Policy policy);
+
+
+ /**
+ * Validate a {@link Segment} node against the policy enforcer. This method
is called during query planning from
Review Comment:
Similar thought here, the javadoc should specify how it's called and what
the `segment` argument will be.
##########
processing/src/main/java/org/apache/druid/query/policy/PolicyEnforcer.java:
##########
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+package org.apache.druid.query.policy;
+
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import org.apache.druid.guice.annotations.UnstableApi;
+import org.apache.druid.query.DataSource;
+import org.apache.druid.segment.Segment;
+
+/**
+ * A mechanism for enforcing policies on data sources in Druid queries.
+ * <p>
+ * Note: The {@code PolicyEnforcer} is intended to serve as a sanity checker
and not as a primary authorization mechanism.
+ * It should not be used to implement security rules. Instead, it acts as a
last line of defense to verify that
+ * security policies have been implemented correctly and to prevent incorrect
policy usage.
+ * </p>
+ */
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
+@JsonSubTypes({
+ @JsonSubTypes.Type(value = NoopPolicyEnforcer.class, name = "none"),
+ @JsonSubTypes.Type(value = RestrictAllTablesPolicyEnforcer.class, name =
"restrictAllTables"),
+})
+@UnstableApi
+public interface PolicyEnforcer
+{
+
+ /**
+ * Validate a {@link DataSource} node against the policy enforcer. This
method is called during query planning from
+ * {@link DataSource#validate(PolicyEnforcer)}.
Review Comment:
The javadoc here should specify how it's called and what the `ds` argument
will be. I think with the new structure, where it is called indirectly through
`DataSource` itself, that means this `validate` method will get called once for
each `restricted` and once for each `table` that is not wrapped under a
`restricted`.
##########
examples/conf/druid/auto/_common/common.runtime.properties:
##########
@@ -151,3 +151,8 @@ druid.lookup.enableLookupSyncOnStartup=false
# Expression processing config
#
druid.expressions.useStrictBooleans=true
+
+#
+# Row security policies
+#
+druid.policy.enforcer.type=none
Review Comment:
Let's not include this in the default configs. It won't be a commonly-set
setting, and we generally just want to put the common ones here.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]