diff --git a/bin/mosquctl_client b/bin/mosquctl_client index b0eac2d74d..6256091452 100755 --- a/bin/mosquctl_client +++ b/bin/mosquctl_client @@ -17,9 +17,23 @@ shift 2 source $UDMI_ROOT/etc/mosquitto_ctrl.sh client_user=$client_id +role_name="role_${client_id//\//_}" $MOSQUITTO_CTRL deleteClient $client_user || true +$MOSQUITTO_CTRL deleteRole $role_name || true + if [[ $client_pass != "--" ]]; then $MOSQUITTO_CTRL createClient $client_user -p $client_pass -c $client_id - $MOSQUITTO_CTRL addClientRole $client_user device + + $MOSQUITTO_CTRL createRole $role_name + $MOSQUITTO_CTRL addClientRole $client_user $role_name + + $MOSQUITTO_CTRL addRoleACL $role_name subscribePattern "$client_id/config" allow + $MOSQUITTO_CTRL addRoleACL $role_name subscribePattern "$client_id/commands" allow + $MOSQUITTO_CTRL addRoleACL $role_name subscribePattern "$client_id/errors" allow + $MOSQUITTO_CTRL addRoleACL $role_name publishClientSend "$client_id/events/#" allow + $MOSQUITTO_CTRL addRoleACL $role_name publishClientSend "$client_id/state" allow + echo "Device $client_id registered correctly." +else + echo "Device $client_id deleted correctly." fi diff --git a/bin/mosquctl_gateway b/bin/mosquctl_gateway new file mode 100755 index 0000000000..463d3bd7f2 --- /dev/null +++ b/bin/mosquctl_gateway @@ -0,0 +1,47 @@ +#!/bin/bash -e + +UDMI_ROOT=$(dirname $0)/.. +source $UDMI_ROOT/etc/shell_common.sh +source $UDMI_ROOT/etc/mosquitto_ctrl.sh + +if [[ $# != 3 ]]; then + echo Usage: $0 action gateway_id device_id + echo Actions: bind, unbind + false +fi + +action=$1 +gateway_id=$2 +device_id=$3 + +role_name="role_${gateway_id//\//_}" + +if [[ $action == "bind" ]]; then + echo Binding $device_id to gateway $gateway_id + # Create role if not exists (ignore error if exists) + $MOSQUITTO_CTRL createRole $role_name || true + # Add role to gateway client (ignore error if already added) + $MOSQUITTO_CTRL addClientRole $gateway_id $role_name || true + + # Add ACLs + $MOSQUITTO_CTRL addRoleACL $role_name subscribePattern "$device_id/config" allow + $MOSQUITTO_CTRL addRoleACL $role_name subscribePattern "$device_id/commands" allow + $MOSQUITTO_CTRL addRoleACL $role_name subscribePattern "$device_id/errors" allow + $MOSQUITTO_CTRL addRoleACL $role_name publishClientSend "$device_id/events/#" allow + $MOSQUITTO_CTRL addRoleACL $role_name publishClientSend "$device_id/state" allow + $MOSQUITTO_CTRL addRoleACL $role_name publishClientSend "$device_id/attach" allow + echo "Binding successful for $device_id to gateway $gateway_id" +elif [[ $action == "unbind" ]]; then + echo Unbinding $device_id from gateway $gateway_id + # Remove ACLs + $MOSQUITTO_CTRL removeRoleACL $role_name subscribePattern "$device_id/config" || true + $MOSQUITTO_CTRL removeRoleACL $role_name subscribePattern "$device_id/commands" || true + $MOSQUITTO_CTRL removeRoleACL $role_name subscribePattern "$device_id/errors" || true + $MOSQUITTO_CTRL removeRoleACL $role_name publishClientSend "$device_id/events/#" || true + $MOSQUITTO_CTRL removeRoleACL $role_name publishClientSend "$device_id/state" || true + $MOSQUITTO_CTRL removeRoleACL $role_name publishClientSend "$device_id/attach" || true + echo "Unbinding successful for $device_id from gateway $gateway_id" +else + echo Unknown action: $action + false +fi diff --git a/bin/start_mosquitto b/bin/start_mosquitto index 16a309dc0a..d7c60b9677 100755 --- a/bin/start_mosquitto +++ b/bin/start_mosquitto @@ -34,22 +34,30 @@ if [[ ! -f $UDMI_FILE ]]; then [[ $(whoami) == root ]] && echo user root >> $UDMI_FILE fi +# Ensure the mosquitto group can write to the config directory (required for atomic configuration saves) +sudo chgrp $GROUP $ETC_DIR || true +sudo chmod 775 $ETC_DIR || true + if [[ ! -f $DYN_FILE ]]; then echo Creating new $DYN_FILE echo Configuring MQTT user: $AUTH_USER sudo mosquitto_ctrl dynsec init $DYN_FILE $AUTH_USER $AUTH_PASS - [[ $(whoami) != root ]] && sudo chgrp $GROUP $DYN_FILE - sudo chmod 0660 $DYN_FILE fi +# Ensure the dynamic security file is owned by the mosquitto group and is group-writable (restricted to owner and group only) +sudo chgrp $GROUP $DYN_FILE || true +sudo chmod 0660 $DYN_FILE || true + if [[ ! -f $PASS_FILE ]]; then echo Creating $PASS_FILE sudo touch $PASS_FILE - sudo chmod 0640 $PASS_FILE - [[ $(whoami) != root ]] && sudo chgrp $GROUP $PASS_FILE sudo mosquitto_passwd -b ${PASS_FILE} ${AUTH_USER} ${AUTH_PASS} fi +# Ensure the passwd file is owned by the mosquitto user and is strictly private (0600) +sudo chown mosquitto:mosquitto $PASS_FILE || true +sudo chmod 0600 $PASS_FILE || true + if [[ -n $(which systemctl) ]]; then sudo systemctl restart mosquitto else @@ -62,9 +70,6 @@ else echo Completed mosquitto startup. fi -$MOSQUITTO_CTRL createRole device -$MOSQUITTO_CTRL addRoleACL device subscribePattern '/r/+/d/+/#' allow -$MOSQUITTO_CTRL addRoleACL device publishClientSend '/r/+/d/+/#' allow $MOSQUITTO_CTRL createRole service $MOSQUITTO_CTRL addRoleACL service subscribePattern '/r/+/d/+/#' allow $MOSQUITTO_CTRL addRoleACL service publishClientSend '/r/+/d/+/#' allow diff --git a/bin/test_mosquitto b/bin/test_mosquitto index 2ec4df34fb..78c43fae11 100755 --- a/bin/test_mosquitto +++ b/bin/test_mosquitto @@ -33,7 +33,7 @@ CLIENT_OPTS="-i $CLNT_ID -u $CLNT_USER -P $CLNT_PASS --cafile $CA_CERT --cert $C $MOSQUITTO_CTRL deleteClient $CLNT_USER $MOSQUITTO_CTRL createClient $CLNT_USER -p $CLNT_PASS -c $CLNT_ID -$MOSQUITTO_CTRL addClientRole $CLNT_USER device +$MOSQUITTO_CTRL addClientRole $CLNT_USER service killall mosquitto_sub || true diff --git a/udmis/src/main/java/com/google/bos/udmi/service/access/ImplicitIotAccessProvider.java b/udmis/src/main/java/com/google/bos/udmi/service/access/ImplicitIotAccessProvider.java index d43ef01f3d..b59244ed5d 100644 --- a/udmis/src/main/java/com/google/bos/udmi/service/access/ImplicitIotAccessProvider.java +++ b/udmis/src/main/java/com/google/bos/udmi/service/access/ImplicitIotAccessProvider.java @@ -109,8 +109,19 @@ public static String hashedDeviceId(String registryId, String deviceId) { private void bindDevicesToGateway(String registryId, String gatewayId, CloudModel cloudModel) { Set deviceIds = ImmutableSet.copyOf(cloudModel.gateway.proxy_ids); - deviceIds.forEach(deviceId -> - registryDeviceRef(registryId, deviceId).put(BOUND_TO_KEY, gatewayId)); + deviceIds.forEach(deviceId -> { + registryDeviceRef(registryId, deviceId).put(BOUND_TO_KEY, gatewayId); + broker.bindGateway(clientId(registryId, gatewayId), clientId(registryId, deviceId)); + }); + } + + private void unbindDevicesFromGateway(String registryId, String gatewayId, + CloudModel cloudModel) { + Set deviceIds = ImmutableSet.copyOf(cloudModel.gateway.proxy_ids); + deviceIds.forEach(deviceId -> { + registryDeviceRef(registryId, deviceId).delete(BOUND_TO_KEY); + broker.unbindGateway(clientId(registryId, gatewayId), clientId(registryId, deviceId)); + }); } private void blockDevice(String registryId, String deviceId, CloudModel cloudModel) { @@ -161,6 +172,11 @@ private void deleteDevice(String registryId, String deviceId, CloudModel cloudMo properties.entries().keySet().forEach(properties::delete); registryDevicesRef(registryId).delete(deviceId); broker.authorize(clientId(registryId, deviceId), null); + String gatewayId = properties.get(BOUND_TO_KEY); + + if (gatewayId != null) { + broker.unbindGateway(clientId(registryId, gatewayId), clientId(registryId, deviceId)); + } } private CloudModel getReply(String registryId, String deviceId, CloudModel request, @@ -339,6 +355,7 @@ public CloudModel modelDevice(String registryId, String deviceId, CloudModel clo case DELETE -> deleteDevice(registryId, deviceId, cloudModel); case MODIFY -> modifyDevice(registryId, deviceId, cloudModel); case BIND -> bindDevicesToGateway(registryId, deviceId, cloudModel); + case UNBIND -> unbindDevicesFromGateway(registryId, deviceId, cloudModel); case BLOCK -> blockDevice(registryId, deviceId, cloudModel); default -> throw new RuntimeException("Unknown device operation " + operation); } diff --git a/udmis/src/main/java/com/google/bos/udmi/service/support/ConnectionBroker.java b/udmis/src/main/java/com/google/bos/udmi/service/support/ConnectionBroker.java index 430174dcff..fb75ea41e4 100644 --- a/udmis/src/main/java/com/google/bos/udmi/service/support/ConnectionBroker.java +++ b/udmis/src/main/java/com/google/bos/udmi/service/support/ConnectionBroker.java @@ -13,6 +13,10 @@ public interface ConnectionBroker { Future addEventListener(String clientPrefix, Consumer eventConsumer); + void bindGateway(String gatewayId, String deviceId); + + void unbindGateway(String gatewayId, String deviceId); + /** * Simple event for connection broker happenings. */ diff --git a/udmis/src/main/java/com/google/bos/udmi/service/support/MosquittoBroker.java b/udmis/src/main/java/com/google/bos/udmi/service/support/MosquittoBroker.java index 3aa4750328..8264461074 100644 --- a/udmis/src/main/java/com/google/bos/udmi/service/support/MosquittoBroker.java +++ b/udmis/src/main/java/com/google/bos/udmi/service/support/MosquittoBroker.java @@ -26,6 +26,7 @@ public class MosquittoBroker extends ContainerBase implements ConnectionBroker { private static final String UDMI_ROOT = System.getenv("UDMI_ROOT"); private static final String MOSQUCTL_CLIENT_FMT = UDMI_ROOT + "/bin/mosquctl_client %s %s"; private static final String MOSQUCTL_LOG_FMT = UDMI_ROOT + "/bin/mosquctl_log %s"; + private static final String MOSQUCTL_GATEWAY_FMT = UDMI_ROOT + "/bin/mosquctl_gateway %s %s %s"; private static final long EXEC_TIMEOUT_SEC = 10; private static final String REVOKE_PASSWORD = "--"; private static final Pattern LOG_MATCHER = @@ -60,8 +61,7 @@ private void consumeStream(BufferedReader reader, Consumer consumer) { thread.start(); } - private void mosquctlClient(String clientId, String clientPass) { - String cmd = format(MOSQUCTL_CLIENT_FMT, clientId, clientPass); + private void executeCommand(String cmd) { synchronized (MosquittoBroker.class) { try { info("Executing command %s", cmd); @@ -77,6 +77,10 @@ private void mosquctlClient(String clientId, String clientPass) { } } + private void mosquctlClient(String clientId, String clientPass) { + executeCommand(format(MOSQUCTL_CLIENT_FMT, clientId, clientPass)); + } + private void mosquctlLog(String clientPrefix, Consumer eventConsumer) { String cmd = format(MOSQUCTL_LOG_FMT, clientPrefix); synchronized (MosquittoBroker.class) { @@ -148,4 +152,15 @@ public Future addEventListener(String clientPrefix, public void authorize(String clientId, String password) { mosquctlClient(clientId, ofNullable(password).orElse(REVOKE_PASSWORD)); } + + @Override + public void bindGateway(String gatewayId, String deviceId) { + executeCommand(format(MOSQUCTL_GATEWAY_FMT, "bind", gatewayId, deviceId)); + } + + @Override + public void unbindGateway(String gatewayId, String deviceId) { + info("Unbind device Id: %s from gateway Id: %s :%s", deviceId, gatewayId); + executeCommand(format(MOSQUCTL_GATEWAY_FMT, "unbind", gatewayId, deviceId)); + } } diff --git a/validator/src/main/java/com/google/daq/mqtt/registrar/Registrar.java b/validator/src/main/java/com/google/daq/mqtt/registrar/Registrar.java index 4a3718aa4e..4bfd5813be 100644 --- a/validator/src/main/java/com/google/daq/mqtt/registrar/Registrar.java +++ b/validator/src/main/java/com/google/daq/mqtt/registrar/Registrar.java @@ -121,6 +121,7 @@ import udmi.schema.Envelope.SubFolder; import udmi.schema.ExecutionConfiguration; import udmi.schema.GatewayModel; +import udmi.schema.IotAccess.IotProvider; import udmi.schema.LinkExternalsModel; import udmi.schema.Metadata; import udmi.schema.SetupUdmiConfig; @@ -1278,7 +1279,10 @@ private void bindGatewayDevices(Map localDevices) { if (dryRun) { System.err.printf("Dry run: would bind %s to %s%n", setOrSize(toBind), gatewayId); } else { - cloudIotManager.bindDevices(toBind, gatewayId, true); + boolean isLocal = + cloudIotManager.executionConfiguration.iot_provider == IotProvider.MQTT + || cloudIotManager.executionConfiguration.iot_provider == IotProvider.IMPLICIT; + cloudIotManager.bindDevices(isLocal ? proxyIds : toBind, gatewayId, true); } recordOperation(ModelOperation.BIND, toBind.size()); } catch (Exception e) {