Commit d077ee6a07a29c2184ff1bbae3a3a5932bb6aa6a

Authored by Andrii Shvaika
1 parent e83064ec

Improved Security Store to support race conditions during registration

@@ -75,13 +75,11 @@ public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { @@ -75,13 +75,11 @@ public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
75 return device; 75 return device;
76 } 76 }
77 77
78 - //TODO: use different endpoints to isolate tests.  
79 - @Ignore()  
80 @Test 78 @Test
81 public void testConnectAndObserveTelemetry() throws Exception { 79 public void testConnectAndObserveTelemetry() throws Exception {
82 createDeviceProfile(TRANSPORT_CONFIGURATION); 80 createDeviceProfile(TRANSPORT_CONFIGURATION);
83 X509ClientCredentials credentials = new X509ClientCredentials(); 81 X509ClientCredentials credentials = new X509ClientCredentials();
84 - credentials.setEndpoint(endpoint+1); 82 + credentials.setEndpoint(endpoint);
85 Device device = createDevice(credentials); 83 Device device = createDevice(credentials);
86 84
87 SingleEntityFilter sef = new SingleEntityFilter(); 85 SingleEntityFilter sef = new SingleEntityFilter();
@@ -99,7 +97,7 @@ public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { @@ -99,7 +97,7 @@ public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
99 wsClient.waitForReply(); 97 wsClient.waitForReply();
100 98
101 wsClient.registerWaitForUpdate(); 99 wsClient.registerWaitForUpdate();
102 - LwM2MTestClient client = new LwM2MTestClient(executor, endpoint+1); 100 + LwM2MTestClient client = new LwM2MTestClient(executor, endpoint);
103 Security security = x509(serverUri, 123, clientX509Cert.getEncoded(), clientPrivateKeyFromCert.getEncoded(), serverX509Cert.getEncoded()); 101 Security security = x509(serverUri, 123, clientX509Cert.getEncoded(), clientPrivateKeyFromCert.getEncoded(), serverX509Cert.getEncoded());
104 client.init(security, coapConfig); 102 client.init(security, coapConfig);
105 String msg = wsClient.waitForUpdate(); 103 String msg = wsClient.waitForUpdate();
@@ -42,8 +42,8 @@ import org.thingsboard.server.common.transport.util.SslUtil; @@ -42,8 +42,8 @@ import org.thingsboard.server.common.transport.util.SslUtil;
42 import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; 42 import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
43 import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; 43 import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
44 import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials; 44 import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials;
45 -import org.thingsboard.server.transport.lwm2m.server.store.TbEditableSecurityStore;  
46 import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore; 45 import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore;
  46 +import org.thingsboard.server.transport.lwm2m.server.store.TbMainSecurityStore;
47 47
48 import javax.annotation.PostConstruct; 48 import javax.annotation.PostConstruct;
49 import javax.security.auth.x500.X500Principal; 49 import javax.security.auth.x500.X500Principal;
@@ -67,7 +67,7 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer @@ -67,7 +67,7 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer
67 private final TbLwM2MDtlsSessionStore sessionStorage; 67 private final TbLwM2MDtlsSessionStore sessionStorage;
68 private final LwM2MTransportServerConfig config; 68 private final LwM2MTransportServerConfig config;
69 private final LwM2mCredentialsSecurityInfoValidator securityInfoValidator; 69 private final LwM2mCredentialsSecurityInfoValidator securityInfoValidator;
70 - private final TbEditableSecurityStore securityStore; 70 + private final TbMainSecurityStore securityStore;
71 71
72 @SuppressWarnings("deprecation") 72 @SuppressWarnings("deprecation")
73 private StaticCertificateVerifier staticCertificateVerifier; 73 private StaticCertificateVerifier staticCertificateVerifier;
@@ -134,7 +134,7 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer @@ -134,7 +134,7 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer
134 if (msg.hasDeviceInfo() && deviceProfile != null) { 134 if (msg.hasDeviceInfo() && deviceProfile != null) {
135 sessionStorage.put(endpoint, new TbX509DtlsSessionInfo(cert.getSubjectX500Principal().getName(), msg)); 135 sessionStorage.put(endpoint, new TbX509DtlsSessionInfo(cert.getSubjectX500Principal().getName(), msg));
136 try { 136 try {
137 - securityStore.put(securityInfo); 137 + securityStore.putX509(securityInfo);
138 } catch (NonUniqueSecurityInfoException e) { 138 } catch (NonUniqueSecurityInfoException e) {
139 log.trace("Failed to add security info: {}", securityInfo, e); 139 log.trace("Failed to add security info: {}", securityInfo, e);
140 } 140 }
@@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.client; @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.client;
17 17
18 import lombok.RequiredArgsConstructor; 18 import lombok.RequiredArgsConstructor;
19 import lombok.extern.slf4j.Slf4j; 19 import lombok.extern.slf4j.Slf4j;
  20 +import org.eclipse.leshan.core.SecurityMode;
20 import org.eclipse.leshan.core.model.ResourceModel; 21 import org.eclipse.leshan.core.model.ResourceModel;
21 import org.eclipse.leshan.core.node.LwM2mPath; 22 import org.eclipse.leshan.core.node.LwM2mPath;
22 import org.eclipse.leshan.server.registration.Registration; 23 import org.eclipse.leshan.server.registration.Registration;
@@ -30,7 +31,7 @@ import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; @@ -30,7 +31,7 @@ import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
30 import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; 31 import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo;
31 import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; 32 import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext;
32 import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; 33 import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil;
33 -import org.thingsboard.server.transport.lwm2m.server.store.TbEditableSecurityStore; 34 +import org.thingsboard.server.transport.lwm2m.server.store.TbMainSecurityStore;
34 35
35 import java.util.Arrays; 36 import java.util.Arrays;
36 import java.util.Collection; 37 import java.util.Collection;
@@ -54,7 +55,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { @@ -54,7 +55,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
54 55
55 private final LwM2mTransportContext context; 56 private final LwM2mTransportContext context;
56 private final LwM2MTransportServerConfig config; 57 private final LwM2MTransportServerConfig config;
57 - private final TbEditableSecurityStore securityStore; 58 + private final TbMainSecurityStore securityStore;
58 private final Map<String, LwM2mClient> lwM2mClientsByEndpoint = new ConcurrentHashMap<>(); 59 private final Map<String, LwM2mClient> lwM2mClientsByEndpoint = new ConcurrentHashMap<>();
59 private final Map<String, LwM2mClient> lwM2mClientsByRegistrationId = new ConcurrentHashMap<>(); 60 private final Map<String, LwM2mClient> lwM2mClientsByRegistrationId = new ConcurrentHashMap<>();
60 private final Map<UUID, Lwm2mDeviceProfileTransportConfiguration> profiles = new ConcurrentHashMap<>(); 61 private final Map<UUID, Lwm2mDeviceProfileTransportConfiguration> profiles = new ConcurrentHashMap<>();
@@ -75,6 +76,9 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { @@ -75,6 +76,9 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
75 oldSession = lwM2MClient.getSession(); 76 oldSession = lwM2MClient.getSession();
76 TbLwM2MSecurityInfo securityInfo = securityStore.getTbLwM2MSecurityInfoByEndpoint(lwM2MClient.getEndpoint()); 77 TbLwM2MSecurityInfo securityInfo = securityStore.getTbLwM2MSecurityInfoByEndpoint(lwM2MClient.getEndpoint());
77 if (securityInfo.getSecurityMode() != null) { 78 if (securityInfo.getSecurityMode() != null) {
  79 + if (SecurityMode.X509.equals(securityInfo.getSecurityMode())) {
  80 + securityStore.registerX509(registration.getEndpoint(), registration.getId());
  81 + }
78 if (securityInfo.getDeviceProfile() != null) { 82 if (securityInfo.getDeviceProfile() != null) {
79 profileUpdate(securityInfo.getDeviceProfile()); 83 profileUpdate(securityInfo.getDeviceProfile());
80 if (securityInfo.getSecurityInfo() != null) { 84 if (securityInfo.getSecurityInfo() != null) {
@@ -124,7 +128,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { @@ -124,7 +128,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
124 if (currentRegistration.getId().equals(registration.getId())) { 128 if (currentRegistration.getId().equals(registration.getId())) {
125 lwM2MClient.setState(LwM2MClientState.UNREGISTERED); 129 lwM2MClient.setState(LwM2MClientState.UNREGISTERED);
126 lwM2mClientsByEndpoint.remove(lwM2MClient.getEndpoint()); 130 lwM2mClientsByEndpoint.remove(lwM2MClient.getEndpoint());
127 - this.securityStore.remove(lwM2MClient.getEndpoint()); 131 + this.securityStore.remove(lwM2MClient.getEndpoint(), registration.getId());
128 UUID profileId = lwM2MClient.getProfileId(); 132 UUID profileId = lwM2MClient.getProfileId();
129 if (profileId != null) { 133 if (profileId != null) {
130 Optional<LwM2mClient> otherClients = lwM2mClientsByRegistrationId.values().stream().filter(e -> e.getProfileId().equals(profileId)).findFirst(); 134 Optional<LwM2mClient> otherClients = lwM2mClientsByRegistrationId.values().stream().filter(e -> e.getProfileId().equals(profileId)).findFirst();
@@ -22,13 +22,22 @@ import org.jetbrains.annotations.Nullable; @@ -22,13 +22,22 @@ import org.jetbrains.annotations.Nullable;
22 import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator; 22 import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator;
23 import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; 23 import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo;
24 24
  25 +import java.util.HashSet;
  26 +import java.util.Map;
  27 +import java.util.Set;
  28 +import java.util.concurrent.ConcurrentHashMap;
  29 +import java.util.concurrent.ConcurrentMap;
  30 +import java.util.concurrent.locks.Lock;
  31 +import java.util.concurrent.locks.ReentrantLock;
  32 +
25 import static org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer.CLIENT; 33 import static org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer.CLIENT;
26 34
27 @Slf4j 35 @Slf4j
28 -public class TbLwM2mSecurityStore implements TbEditableSecurityStore { 36 +public class TbLwM2mSecurityStore implements TbMainSecurityStore {
29 37
30 private final TbEditableSecurityStore securityStore; 38 private final TbEditableSecurityStore securityStore;
31 private final LwM2mCredentialsSecurityInfoValidator validator; 39 private final LwM2mCredentialsSecurityInfoValidator validator;
  40 + private final ConcurrentMap<String, Set<String>> endpointRegistrations = new ConcurrentHashMap<>();
32 41
33 public TbLwM2mSecurityStore(TbEditableSecurityStore securityStore, LwM2mCredentialsSecurityInfoValidator validator) { 42 public TbLwM2mSecurityStore(TbEditableSecurityStore securityStore, LwM2mCredentialsSecurityInfoValidator validator) {
34 this.securityStore = securityStore; 43 this.securityStore = securityStore;
@@ -61,24 +70,42 @@ public class TbLwM2mSecurityStore implements TbEditableSecurityStore { @@ -61,24 +70,42 @@ public class TbLwM2mSecurityStore implements TbEditableSecurityStore {
61 @Nullable 70 @Nullable
62 public SecurityInfo fetchAndPutSecurityInfo(String credentialsId) { 71 public SecurityInfo fetchAndPutSecurityInfo(String credentialsId) {
63 TbLwM2MSecurityInfo securityInfo = validator.getEndpointSecurityInfoByCredentialsId(credentialsId, CLIENT); 72 TbLwM2MSecurityInfo securityInfo = validator.getEndpointSecurityInfoByCredentialsId(credentialsId, CLIENT);
64 - try {  
65 - if (securityInfo != null) { 73 + doPut(securityInfo);
  74 + return securityInfo != null ? securityInfo.getSecurityInfo() : null;
  75 + }
  76 +
  77 + private void doPut(TbLwM2MSecurityInfo securityInfo) {
  78 + if (securityInfo != null) {
  79 + try {
66 securityStore.put(securityInfo); 80 securityStore.put(securityInfo);
  81 + } catch (NonUniqueSecurityInfoException e) {
  82 + log.trace("Failed to add security info: {}", securityInfo, e);
67 } 83 }
68 - } catch (NonUniqueSecurityInfoException e) {  
69 - log.trace("Failed to add security info: {}", securityInfo, e);  
70 } 84 }
71 - return securityInfo != null ? securityInfo.getSecurityInfo() : null;  
72 } 85 }
73 86
74 @Override 87 @Override
75 - public void put(TbLwM2MSecurityInfo tbSecurityInfo) throws NonUniqueSecurityInfoException {  
76 - securityStore.put(tbSecurityInfo); 88 + public void putX509(TbLwM2MSecurityInfo securityInfo) throws NonUniqueSecurityInfoException {
  89 + securityStore.put(securityInfo);
77 } 90 }
78 91
79 @Override 92 @Override
80 - public void remove(String endpoint) {  
81 - //TODO: Make sure we delay removal of security store from endpoint due to reg/unreg race condition.  
82 -// securityStore.remove(endpoint); 93 + public void registerX509(String endpoint, String registrationId) {
  94 + endpointRegistrations.computeIfAbsent(endpoint, ep -> new HashSet<>()).add(registrationId);
  95 + }
  96 +
  97 + @Override
  98 + public void remove(String endpoint, String registrationId) {
  99 + Set<String> epRegistrationIds = endpointRegistrations.get(endpoint);
  100 + boolean shouldRemove;
  101 + if (epRegistrationIds == null) {
  102 + shouldRemove = true;
  103 + } else {
  104 + epRegistrationIds.remove(registrationId);
  105 + shouldRemove = epRegistrationIds.isEmpty();
  106 + }
  107 + if (shouldRemove) {
  108 + securityStore.remove(endpoint);
  109 + }
83 } 110 }
84 } 111 }
@@ -51,7 +51,7 @@ public class TbLwM2mStoreFactory { @@ -51,7 +51,7 @@ public class TbLwM2mStoreFactory {
51 } 51 }
52 52
53 @Bean 53 @Bean
54 - private TbEditableSecurityStore securityStore() { 54 + private TbMainSecurityStore securityStore() {
55 return new TbLwM2mSecurityStore(redisConfiguration.isPresent() && useRedis ? 55 return new TbLwM2mSecurityStore(redisConfiguration.isPresent() && useRedis ?
56 new TbLwM2mRedisSecurityStore(redisConfiguration.get().redisConnectionFactory()) : new TbInMemorySecurityStore(), validator); 56 new TbLwM2mRedisSecurityStore(redisConfiguration.get().redisConnectionFactory()) : new TbInMemorySecurityStore(), validator);
57 } 57 }
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.transport.lwm2m.server.store;
  17 +
  18 +import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException;
  19 +import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo;
  20 +
  21 +public interface TbMainSecurityStore extends TbSecurityStore {
  22 +
  23 + void putX509(TbLwM2MSecurityInfo tbSecurityInfo) throws NonUniqueSecurityInfoException;
  24 +
  25 + void registerX509(String endpoint, String registrationId);
  26 +
  27 + void remove(String endpoint, String registrationId);
  28 +
  29 +}