diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java index 7c8e8e64ab..cb276b3fdc 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java @@ -82,6 +82,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest " ],\n" + " \"attributeLwm2m\": {}\n" + " },\n" + + " \"bootstrapServerUpdateEnable\": true,\n" + " \"bootstrap\": [\n" + " {\n" + " \"host\": \"0.0.0.0\",\n" + diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java index ae1ef48235..903750663a 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java @@ -92,6 +92,7 @@ public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest { " ],\n" + " \"attributeLwm2m\": {}\n" + " },\n" + + " \"bootstrapServerUpdateEnable\": true,\n" + " \"bootstrap\": [\n" + " {\n" + " \"host\": \"0.0.0.0\",\n" + diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java index 6f4a520295..95878b74d1 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java @@ -142,6 +142,7 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg " ],\n" + " \"attributeLwm2m\": {}\n" + " },\n" + + " \"bootstrapServerUpdateEnable\": true,\n" + " \"bootstrap\": [\n" + " {\n" + " \"host\": \"0.0.0.0\",\n" + diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapSecurityStore.java index 5c1669fcf3..74e5061166 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapSecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapSecurityStore.java @@ -24,6 +24,7 @@ import org.eclipse.leshan.server.security.SecurityInfo; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; +import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.AbstractLwM2MBootstrapServerCredential; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapConfig; @@ -36,6 +37,7 @@ import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper; import java.util.Collections; import java.util.Iterator; import java.util.Map; +import java.util.Optional; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; @@ -75,6 +77,18 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { BootstrapConfig bsConfigNew = store.getBootstrapConfig(); if (bsConfigNew != null) { try { + boolean bootstrapServerUpdateEnable = ((Lwm2mDeviceProfileTransportConfiguration)store.getDeviceProfile().getProfileData().getTransportConfiguration()).isBootstrapServerUpdateEnable(); + if (!bootstrapServerUpdateEnable) { + Optional> securities = bsConfigNew.security.entrySet().stream().filter(sec -> ((BootstrapConfig.ServerSecurity)sec.getValue()).bootstrapServer==true).findAny(); + if (securities.isPresent()) { + bsConfigNew.security.entrySet().remove(securities.get()); + int serverSortId = securities.get().getValue().serverId; + Optional> serverConfigs = bsConfigNew.servers.entrySet().stream().filter(serv -> ((BootstrapConfig.ServerConfig)serv.getValue()).shortId==serverSortId).findAny(); + if (serverConfigs.isPresent()) { + bsConfigNew.servers.entrySet().remove(serverConfigs.get()); + } + } + } for (String config : bootstrapConfigStore.getAll().keySet()) { if (config.equals(endPoint)) { bootstrapConfigStore.remove(config); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MConfigurationChecker.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MConfigurationChecker.java new file mode 100644 index 0000000000..d27ec707c2 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MConfigurationChecker.java @@ -0,0 +1,83 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed 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.thingsboard.server.transport.lwm2m.bootstrap.store; + +import org.eclipse.leshan.server.bootstrap.BootstrapConfig; +import org.eclipse.leshan.server.bootstrap.ConfigurationChecker; +import org.eclipse.leshan.server.bootstrap.InvalidConfigurationException; +import java.util.Map; + +public class LwM2MConfigurationChecker extends ConfigurationChecker { + + @Override + public void verify(BootstrapConfig config) throws InvalidConfigurationException { + // check security configurations + for (Map.Entry e : config.security.entrySet()) { + BootstrapConfig.ServerSecurity sec = e.getValue(); + + // checks security config + switch (sec.securityMode) { + case NO_SEC: + checkNoSec(sec); + break; + case PSK: + checkPSK(sec); + break; + case RPK: + checkRPK(sec); + break; + case X509: + checkX509(sec); + break; + case EST: + throw new InvalidConfigurationException("EST is not currently supported.", e); + } + + validateMandatoryField(sec); + } + + // does each server have a corresponding security entry? + validateOneSecurityByServer(config); + } + + protected void validateOneSecurityByServer(BootstrapConfig config) throws InvalidConfigurationException { + for (Map.Entry e : config.servers.entrySet()) { + BootstrapConfig.ServerConfig srvCfg = e.getValue(); + + // shortId checks + if (srvCfg.shortId == 0) { + throw new InvalidConfigurationException("short ID must not be 0"); + } + + // look for security entry + BootstrapConfig.ServerSecurity security = getSecurityEntry(config, srvCfg.shortId); + + if (security == null) { + throw new InvalidConfigurationException("no security entry for server instance: " + e.getKey()); + } + } + } + + protected static BootstrapConfig.ServerSecurity getSecurityEntry(BootstrapConfig config, int shortId) { + for (Map.Entry es : config.security.entrySet()) { + if (es.getValue().serverId == shortId) { + return es.getValue(); + } + } + return null; + } + +} \ No newline at end of file diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MInMemoryBootstrapConfigStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MInMemoryBootstrapConfigStore.java index 2fa470a58e..485a8946f7 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MInMemoryBootstrapConfigStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MInMemoryBootstrapConfigStore.java @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.request.Identity; import org.eclipse.leshan.server.bootstrap.BootstrapConfig; import org.eclipse.leshan.server.bootstrap.BootstrapSession; +import org.eclipse.leshan.server.bootstrap.ConfigurationChecker; import org.eclipse.leshan.server.bootstrap.InMemoryBootstrapConfigStore; import org.eclipse.leshan.server.bootstrap.InvalidConfigurationException; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; @@ -36,6 +37,7 @@ public class LwM2MInMemoryBootstrapConfigStore extends InMemoryBootstrapConfigSt private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private final Lock readLock = readWriteLock.readLock(); private final Lock writeLock = readWriteLock.writeLock(); + protected final ConfigurationChecker configChecker = new LwM2MConfigurationChecker(); @Override public BootstrapConfig get(String endpoint, Identity deviceIdentity, BootstrapSession session) { @@ -73,6 +75,26 @@ public class LwM2MInMemoryBootstrapConfigStore extends InMemoryBootstrapConfigSt } public void addToStore(String endpoint, BootstrapConfig config) throws InvalidConfigurationException { - super.add(endpoint, config); + + configChecker.verify(config); + // Check PSK identity uniqueness for bootstrap server: + PskByServer pskToAdd = getBootstrapPskIdentity(config); + if (pskToAdd != null) { + BootstrapConfig existingConfig = bootstrapByPskId.get(pskToAdd); + if (existingConfig != null) { + // check if this config will be replace by the new one. + BootstrapConfig previousConfig = bootstrapByEndpoint.get(endpoint); + if (previousConfig != existingConfig) { + throw new InvalidConfigurationException( + "Psk identity [%s] already used for this bootstrap server [%s]", pskToAdd.identity, + pskToAdd.serverUrl); + } + } + } + + bootstrapByEndpoint.put(endpoint, config); + if (pskToAdd != null) { + bootstrapByPskId.put(pskToAdd, config); + } } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index a9bc2401c4..18a8a1ecde 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -420,7 +420,8 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D } } else if (transportConfiguration instanceof Lwm2mDeviceProfileTransportConfiguration) { List lwM2MBootstrapServersConfigurations = ((Lwm2mDeviceProfileTransportConfiguration) transportConfiguration).getBootstrap(); - validateLwm2mServersConfigOfBootstrapForClient(lwM2MBootstrapServersConfigurations); + validateLwm2mServersConfigOfBootstrapForClient(lwM2MBootstrapServersConfigurations, + ((Lwm2mDeviceProfileTransportConfiguration) transportConfiguration).isBootstrapServerUpdateEnable()); for (LwM2MBootstrapServerCredential bootstrapServerCredential : lwM2MBootstrapServersConfigurations) { validateLwm2mServersCredentialOfBootstrapForClient(bootstrapServerCredential); } @@ -707,11 +708,14 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D } } - private void validateLwm2mServersConfigOfBootstrapForClient(List lwM2MBootstrapServersConfigurations) { + private void validateLwm2mServersConfigOfBootstrapForClient(List lwM2MBootstrapServersConfigurations, boolean isBootstrapServerUpdateEnable) { Set uris = new HashSet<>(); Set shortServerIds = new HashSet<>(); for (LwM2MBootstrapServerCredential bootstrapServerCredential : lwM2MBootstrapServersConfigurations) { AbstractLwM2MBootstrapServerCredential serverConfig = (AbstractLwM2MBootstrapServerCredential) bootstrapServerCredential; + if (!isBootstrapServerUpdateEnable && serverConfig.isBootstrapServerIs()) { + throw new DeviceCredentialsValidationException("Bootstrap config must not include \"Bootstrap Server\". \"Include Bootstrap Server updates\" is " + isBootstrapServerUpdateEnable + "." ); + } String server = serverConfig.isBootstrapServerIs() ? "Bootstrap Server" : "LwM2M Server" + " shortServerId: " + serverConfig.getShortServerId() + ":"; if (serverConfig.getShortServerId() < 1 || serverConfig.getShortServerId() > 65534) { throw new DeviceCredentialsValidationException(server + " ShortServerId must not be less than 1 and more than 65534!");