From 6b156de2dfc049b8adcaf37b548bbcfe4dd30e43 Mon Sep 17 00:00:00 2001 From: Joseph Cosentino Date: Wed, 11 Sep 2024 11:01:55 -0700 Subject: [PATCH] feat: support mqtt wildcard resource matching --- .../integrationtests/policy/PolicyTest.java | 23 +++++++++++- .../policy/mqtt-wildcards-in-resource.yaml | 35 +++++++++++++++++++ .../auth/ClientDevicesAuthService.java | 2 ++ .../auth/PermissionEvaluationUtils.java | 28 +++++++++++++-- .../auth/configuration/CDAConfiguration.java | 32 +++++++++-------- 5 files changed, 102 insertions(+), 18 deletions(-) create mode 100644 src/integrationtests/resources/com/aws/greengrass/integrationtests/policy/mqtt-wildcards-in-resource.yaml diff --git a/src/integrationtests/java/com/aws/greengrass/integrationtests/policy/PolicyTest.java b/src/integrationtests/java/com/aws/greengrass/integrationtests/policy/PolicyTest.java index 613732405..3cd45e61a 100644 --- a/src/integrationtests/java/com/aws/greengrass/integrationtests/policy/PolicyTest.java +++ b/src/integrationtests/java/com/aws/greengrass/integrationtests/policy/PolicyTest.java @@ -274,7 +274,7 @@ public static Stream authzRequests() { .resource("mqtt:topic:myThing/world") .expectedResult(false) .build(), - // mqtt wildcards eval not supported + // mqtt wildcards eval not supported by default AuthZRequest.builder() .thingName("myThing") .operation("mqtt:subscribe") @@ -289,6 +289,27 @@ public static Stream authzRequests() { .build() )), + Arguments.of("mqtt-wildcards-in-resource.yaml", Arrays.asList( + AuthZRequest.builder() + .thingName("myThing") + .operation("mqtt:publish") + .resource("mqtt:topic:*/myThing/*") + .expectedResult(true) + .build(), + AuthZRequest.builder() + .thingName("myThing") + .operation("mqtt:publish") + .resource("mqtt:topic:hello/myThing/world") + .expectedResult(true) + .build(), + AuthZRequest.builder() + .thingName("myThing") + .operation("mqtt:subscribe") + .resource("mqtt:topic:myThing/test/test/test/test") + .expectedResult(true) + .build() + )), + Arguments.of("variables-and-wildcards.yaml", Arrays.asList( AuthZRequest.builder() .thingName("myThing") diff --git a/src/integrationtests/resources/com/aws/greengrass/integrationtests/policy/mqtt-wildcards-in-resource.yaml b/src/integrationtests/resources/com/aws/greengrass/integrationtests/policy/mqtt-wildcards-in-resource.yaml new file mode 100644 index 000000000..4339eeb3a --- /dev/null +++ b/src/integrationtests/resources/com/aws/greengrass/integrationtests/policy/mqtt-wildcards-in-resource.yaml @@ -0,0 +1,35 @@ +--- +services: + aws.greengrass.Nucleus: + configuration: + runWithDefault: + posixUser: nobody + windowsUser: integ-tester + logging: + level: "DEBUG" + aws.greengrass.clientdevices.Auth: + configuration: + enableMqttWildcardEvaluation: true + deviceGroups: + formatVersion: "2021-03-05" + definitions: + myThing: + selectionRule: "thingName: myThing" + policyName: "thingAccessPolicy" + policies: + thingAccessPolicy: + publish: + statementDescription: "mqtt publish" + operations: + - "mqtt:publish" + resources: + - "mqtt:topic:*/myThing/*" + subscribe: + statementDescription: "mqtt subscribe" + operations: + - "mqtt:subscribe" + resources: + - "mqtt:topic:${iot:Connection.Thing.ThingName}/+/test/#" + main: + dependencies: + - aws.greengrass.clientdevices.Auth diff --git a/src/main/java/com/aws/greengrass/clientdevices/auth/ClientDevicesAuthService.java b/src/main/java/com/aws/greengrass/clientdevices/auth/ClientDevicesAuthService.java index 69b079002..b80e35558 100644 --- a/src/main/java/com/aws/greengrass/clientdevices/auth/ClientDevicesAuthService.java +++ b/src/main/java/com/aws/greengrass/clientdevices/auth/ClientDevicesAuthService.java @@ -170,6 +170,8 @@ private void subscribeToConfigChanges() { private void onConfigurationChanged() { try { cdaConfiguration = CDAConfiguration.from(cdaConfiguration, getConfig()); + // TODO decouple + context.get(PermissionEvaluationUtils.class).setCdaConfiguration(cdaConfiguration); } catch (URISyntaxException e) { serviceErrored(e); } diff --git a/src/main/java/com/aws/greengrass/clientdevices/auth/PermissionEvaluationUtils.java b/src/main/java/com/aws/greengrass/clientdevices/auth/PermissionEvaluationUtils.java index 624b10c13..f35db0a84 100644 --- a/src/main/java/com/aws/greengrass/clientdevices/auth/PermissionEvaluationUtils.java +++ b/src/main/java/com/aws/greengrass/clientdevices/auth/PermissionEvaluationUtils.java @@ -6,6 +6,7 @@ package com.aws.greengrass.clientdevices.auth; import com.aws.greengrass.authorization.WildcardTrie; +import com.aws.greengrass.clientdevices.auth.configuration.CDAConfiguration; import com.aws.greengrass.clientdevices.auth.configuration.GroupManager; import com.aws.greengrass.clientdevices.auth.configuration.Permission; import com.aws.greengrass.clientdevices.auth.exception.PolicyException; @@ -14,6 +15,7 @@ import com.aws.greengrass.logging.impl.LogManager; import com.aws.greengrass.util.Utils; import lombok.Builder; +import lombok.Setter; import lombok.Value; import org.apache.commons.lang3.StringUtils; @@ -36,6 +38,8 @@ public final class PermissionEvaluationUtils { "Resource is malformed, must be of the form: " + "([a-zA-Z]+):([a-zA-Z]+):" + RESOURCE_NAME_PATTERN.pattern(); private final GroupManager groupManager; + @Setter + private volatile CDAConfiguration cdaConfiguration; /** * Constructor for PermissionEvaluationUtils. @@ -134,9 +138,27 @@ private boolean compareResource(Resource requestResource, String policyResource) return true; } - WildcardTrie wildcardTrie = new WildcardTrie(); - wildcardTrie.add(policyResource); - return wildcardTrie.matchesStandard(requestResource.getResourceStr()); + if (matchMqttWildcards()) { + String name = extractResourceName(policyResource); + WildcardTrie trie = new WildcardTrie(); + trie.add(name); + return trie.matchesMQTT(requestResource.getResourceName()); + } else { + WildcardTrie trie = new WildcardTrie(); + trie.add(policyResource); + return trie.matchesStandard(requestResource.getResourceStr()); + } + } + + private String extractResourceName(String resource) { + // resource is considered valid at this point, so don't need to duplicate validation from parseResource + String typeAndName = resource.substring(resource.indexOf(':') + 1); + return typeAndName.substring(typeAndName.indexOf(':') + 1); + } + + private boolean matchMqttWildcards() { + CDAConfiguration config = cdaConfiguration; + return config != null && config.isEnableMqttWildcardEvaluation(); } private Operation parseOperation(String operationStr) throws PolicyException { diff --git a/src/main/java/com/aws/greengrass/clientdevices/auth/configuration/CDAConfiguration.java b/src/main/java/com/aws/greengrass/clientdevices/auth/configuration/CDAConfiguration.java index 3f3a27fd3..84c0af1a3 100644 --- a/src/main/java/com/aws/greengrass/clientdevices/auth/configuration/CDAConfiguration.java +++ b/src/main/java/com/aws/greengrass/clientdevices/auth/configuration/CDAConfiguration.java @@ -10,6 +10,8 @@ import com.aws.greengrass.clientdevices.auth.configuration.events.MetricsConfigurationChanged; import com.aws.greengrass.clientdevices.auth.configuration.events.SecurityConfigurationChanged; import com.aws.greengrass.config.Topics; +import com.aws.greengrass.util.Coerce; +import lombok.Builder; import lombok.Getter; import java.net.URISyntaxException; @@ -48,23 +50,19 @@ * | *

*/ +@Builder public final class CDAConfiguration { + public static final String ENABLE_MQTT_WILDCARD_EVALUATION = "enableMqttWildcardEvaluation"; + + private final DomainEvents domainEvents; private final RuntimeConfiguration runtime; - private final SecurityConfiguration security; @Getter private final CAConfiguration certificateAuthorityConfiguration; + private final SecurityConfiguration security; private final MetricsConfiguration metricsConfiguration; - private final DomainEvents domainEvents; - - private CDAConfiguration(DomainEvents domainEvents, RuntimeConfiguration runtime, CAConfiguration ca, - SecurityConfiguration security, MetricsConfiguration metricsConfiguration) { - this.domainEvents = domainEvents; - this.runtime = runtime; - this.security = security; - this.certificateAuthorityConfiguration = ca; - this.metricsConfiguration = metricsConfiguration; - } + @Getter + private final boolean enableMqttWildcardEvaluation; /** * Creates the CDA (Client Device Auth) Service configuration. And allows it to be available in the context with the @@ -80,9 +78,15 @@ public static CDAConfiguration from(CDAConfiguration existingConfig, Topics topi DomainEvents domainEvents = topics.getContext().get(DomainEvents.class); - CDAConfiguration newConfig = new CDAConfiguration(domainEvents, RuntimeConfiguration.from(runtimeTopics), - CAConfiguration.from(serviceConfiguration), SecurityConfiguration.from(serviceConfiguration), - MetricsConfiguration.from(serviceConfiguration)); + CDAConfiguration newConfig = CDAConfiguration.builder() + .domainEvents(domainEvents) + .runtime(RuntimeConfiguration.from(runtimeTopics)) + .certificateAuthorityConfiguration(CAConfiguration.from(serviceConfiguration)) + .security(SecurityConfiguration.from(serviceConfiguration)) + .metricsConfiguration(MetricsConfiguration.from(serviceConfiguration)) + .enableMqttWildcardEvaluation( + Coerce.toBoolean(serviceConfiguration.find(ENABLE_MQTT_WILDCARD_EVALUATION))) + .build(); newConfig.triggerChanges(newConfig, existingConfig);