From f617c366103f8a98c117fe2d0ea625b8cc6cb78a Mon Sep 17 00:00:00 2001 From: kannar Date: Tue, 4 Jun 2024 11:36:06 +0200 Subject: [PATCH] consume authZ check has now subscription($subscription) support So the following biscui'ts datalog check is supported: ``` check if topic("tenantTest", "namespaceTest", "topicTest"), topic_operation("consume"), subscription("subnametest") ``` --- .../AuthorizationProviderBiscuit.java | 20 +++++- .../formatter/BiscuitFormatter.java | 16 +++++ .../AuthorizationProviderBiscuitTest.java | 67 +++++++++++++++++++ 3 files changed, 100 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/clevercloud/biscuitpulsar/AuthorizationProviderBiscuit.java b/src/main/java/com/clevercloud/biscuitpulsar/AuthorizationProviderBiscuit.java index 579cacb..3fa5b36 100644 --- a/src/main/java/com/clevercloud/biscuitpulsar/AuthorizationProviderBiscuit.java +++ b/src/main/java/com/clevercloud/biscuitpulsar/AuthorizationProviderBiscuit.java @@ -188,9 +188,23 @@ public CompletableFuture allowTopicOperationAsync(TopicName topicName, facts.add(topicOperationFact(TopicOperation.PRODUCE)); facts.add(topicOperationFact(TopicOperation.CONSUME)); } - Set rules = Set.of("right($tenant, $namespace, $topic, $operation) <- topic($tenant, $namespace, $topic), topic_operation($operation)," + topicOperations + ".contains($operation)"); - Set checks = Set.of("check if right(" + topicFragment(topicName) + "," + topicOperationFragment(operation) + ")"); - return authorize(() -> defaultProvider.allowTopicOperationAsync(topicName, role, operation, authData), true, "allowTopicOperationAsync(" + operation + " -> " + topicName + ")", role, authData, facts, rules, checks); + + Set rules = new HashSet<>(); + Set checks = new HashSet<>(); + String actionLog; + + if (authData == null || !authData.hasSubscription() || authData.getSubscription().isEmpty()) { + rules.add("right($tenant, $namespace, $topic, $operation) <- topic($tenant, $namespace, $topic), topic_operation($operation)," + topicOperations + ".contains($operation)"); + checks.add("check if right(" + topicFragment(topicName) + "," + topicOperationFragment(operation) + ")"); + actionLog = "allowTopicOperationAsync(" + operation + " -> " + topicName + ")"; + } else { + String subscription = authData.getSubscription(); + facts.add(topicSubscriptionFact(subscription)); + rules.add("right($tenant, $namespace, $topic, $operation, $subscription) <- topic($tenant, $namespace, $topic), topic_operation($operation)," + topicOperations + ".contains($operation), subscription($subscription)"); + checks.add("check if right(" + topicFragment(topicName) + "," + topicOperationFragment(operation) + "," + topicSubscriptionFragment(subscription) + ")"); + actionLog = "allowTopicOperationAsync(" + operation + "-> " + topicName + " using subscription " + subscription + ")"; + } + return authorize(() -> defaultProvider.allowTopicOperationAsync(topicName, role, operation, authData), true, actionLog, role, authData, facts, rules, checks); } @Override diff --git a/src/main/java/com/clevercloud/biscuitpulsar/formatter/BiscuitFormatter.java b/src/main/java/com/clevercloud/biscuitpulsar/formatter/BiscuitFormatter.java index 2202de3..5816c79 100644 --- a/src/main/java/com/clevercloud/biscuitpulsar/formatter/BiscuitFormatter.java +++ b/src/main/java/com/clevercloud/biscuitpulsar/formatter/BiscuitFormatter.java @@ -70,6 +70,14 @@ public static String topicOperationFact(TopicOperation operation) { return "topic_operation(" + topicOperationFragment(operation) + ")"; } + public static String topicSubscriptionFragment(String subscription) { + return "\"" + subscription + "\""; + } + + public static String topicSubscriptionFact(String subscription) { + return "subscription(" + topicSubscriptionFragment(subscription) + ")"; + } + public static String topicPolicyOperationFact(PolicyName policy, PolicyOperation operation) { return "topic_operation(\"" + policy.toString().toLowerCase() + "_" + operation.toString().toLowerCase() + "\")"; } @@ -124,10 +132,18 @@ public static String topicOperation(TopicName topic, TopicOperation operation) { return topicFact(topic) + "," + topicOperationFact(operation); } + public static String topicOperation(TopicName topic, TopicOperation operation, String subscription) { + return topicFact(topic) + "," + topicOperationFact(operation) + "," + topicSubscriptionFact(subscription); + } + public static String topicOperationCheck(TopicName topic, TopicOperation operation) { return "check if " + topicOperation(topic, operation); } + public static String topicOperationCheck(TopicName topic, TopicOperation operation, String subscription) { + return "check if " + topicOperation(topic, operation, subscription); + } + public static String adminFact = "right(\"admin\")"; public static String adminCheck = "check if " + adminFact; diff --git a/src/test/java/com/clevercloud/biscuitpulsar/AuthorizationProviderBiscuitTest.java b/src/test/java/com/clevercloud/biscuitpulsar/AuthorizationProviderBiscuitTest.java index 7a1c530..8af209b 100644 --- a/src/test/java/com/clevercloud/biscuitpulsar/AuthorizationProviderBiscuitTest.java +++ b/src/test/java/com/clevercloud/biscuitpulsar/AuthorizationProviderBiscuitTest.java @@ -618,4 +618,71 @@ public void testAuthorizeConsumptionOnSpecifiedTopicPartioned() throws IOExcepti assertFalse(authorizationProvider.allowTopicOperationAsync(TopicName.get("tenantForbidden/" + namespace + "/" +topicWithPartitionTail), authedBiscuit, TopicOperation.PRODUCE, null).get()); } + @Test + public void testConsumeOnTopicWithAuthorizedSubscription() throws Exception { + SecureRandom rng = new SecureRandom(); + KeyPair root = new KeyPair(rng); + SymbolTable symbols = Biscuit.default_symbol_table(); + + String tenant = "tenantTest"; + String namespace = "namespaceTest"; + String topic = "topicTest"; + String subscription = "subNameTest"; + + Block block0 = new Block(0, symbols); + block0.add_fact(adminFact); + block0.add_check(topicOperationCheck(TopicName.get(tenant + "/" + namespace + "/" + topic), TopicOperation.CONSUME, subscription)); + Biscuit biscuit = Biscuit.make(rng, root, symbols, block0.build()); + + String authedBiscuit = authedBiscuit(root, biscuit); + AuthorizationProviderBiscuit authorizationProvider = new AuthorizationProviderBiscuit(); + log.debug(biscuit.print()); + AuthenticationDataSource authenticationDataSource = new AuthenticationDataSource() { + @Override + public boolean hasSubscription() { + return true; + } + + @Override + public String getSubscription() { + return subscription; + } + }; + assertFalse(authorizationProvider.allowTopicOperationAsync(TopicName.get(tenant + "/" + namespace + "/" + topic), authedBiscuit, TopicOperation.PRODUCE, authenticationDataSource).get()); + assertTrue(authorizationProvider.allowTopicOperationAsync(TopicName.get(tenant + "/" + namespace + "/" + topic), authedBiscuit, TopicOperation.CONSUME, authenticationDataSource).get()); + } + + @Test + public void testConsumeOnTopicWithUnauthorizedSubscription() throws Exception { + SecureRandom rng = new SecureRandom(); + KeyPair root = new KeyPair(rng); + SymbolTable symbols = Biscuit.default_symbol_table(); + + String tenant = "tenantTest"; + String namespace = "namespaceTest"; + String topic = "topicTest"; + String subscription = "subNameTest"; + + Block block0 = new Block(0, symbols); + block0.add_fact(adminFact); + block0.add_check(topicOperationCheck(TopicName.get(tenant + "/" + namespace + "/" + topic), TopicOperation.CONSUME, subscription)); + Biscuit biscuit = Biscuit.make(rng, root, symbols, block0.build()); + + String authedBiscuit = authedBiscuit(root, biscuit); + AuthorizationProviderBiscuit authorizationProvider = new AuthorizationProviderBiscuit(); + log.debug(biscuit.print()); + AuthenticationDataSource authenticationDataSource = new AuthenticationDataSource() { + @Override + public boolean hasSubscription() { + return true; + } + + @Override + public String getSubscription() { + return "wrongSubscriptionName"; + } + }; + assertFalse(authorizationProvider.allowTopicOperationAsync(TopicName.get(tenant + "/" + namespace + "/" + topic), authedBiscuit, TopicOperation.PRODUCE, authenticationDataSource).get()); + assertFalse(authorizationProvider.allowTopicOperationAsync(TopicName.get(tenant + "/" + namespace + "/" + topic), authedBiscuit, TopicOperation.CONSUME, authenticationDataSource).get()); + } }