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 75 return device;
76 76 }
77 77
78   - //TODO: use different endpoints to isolate tests.
79   - @Ignore()
80 78 @Test
81 79 public void testConnectAndObserveTelemetry() throws Exception {
82 80 createDeviceProfile(TRANSPORT_CONFIGURATION);
83 81 X509ClientCredentials credentials = new X509ClientCredentials();
84   - credentials.setEndpoint(endpoint+1);
  82 + credentials.setEndpoint(endpoint);
85 83 Device device = createDevice(credentials);
86 84
87 85 SingleEntityFilter sef = new SingleEntityFilter();
... ... @@ -99,7 +97,7 @@ public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
99 97 wsClient.waitForReply();
100 98
101 99 wsClient.registerWaitForUpdate();
102   - LwM2MTestClient client = new LwM2MTestClient(executor, endpoint+1);
  100 + LwM2MTestClient client = new LwM2MTestClient(executor, endpoint);
103 101 Security security = x509(serverUri, 123, clientX509Cert.getEncoded(), clientPrivateKeyFromCert.getEncoded(), serverX509Cert.getEncoded());
104 102 client.init(security, coapConfig);
105 103 String msg = wsClient.waitForUpdate();
... ...
... ... @@ -42,8 +42,8 @@ import org.thingsboard.server.common.transport.util.SslUtil;
42 42 import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
43 43 import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
44 44 import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials;
45   -import org.thingsboard.server.transport.lwm2m.server.store.TbEditableSecurityStore;
46 45 import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore;
  46 +import org.thingsboard.server.transport.lwm2m.server.store.TbMainSecurityStore;
47 47
48 48 import javax.annotation.PostConstruct;
49 49 import javax.security.auth.x500.X500Principal;
... ... @@ -67,7 +67,7 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer
67 67 private final TbLwM2MDtlsSessionStore sessionStorage;
68 68 private final LwM2MTransportServerConfig config;
69 69 private final LwM2mCredentialsSecurityInfoValidator securityInfoValidator;
70   - private final TbEditableSecurityStore securityStore;
  70 + private final TbMainSecurityStore securityStore;
71 71
72 72 @SuppressWarnings("deprecation")
73 73 private StaticCertificateVerifier staticCertificateVerifier;
... ... @@ -134,7 +134,7 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer
134 134 if (msg.hasDeviceInfo() && deviceProfile != null) {
135 135 sessionStorage.put(endpoint, new TbX509DtlsSessionInfo(cert.getSubjectX500Principal().getName(), msg));
136 136 try {
137   - securityStore.put(securityInfo);
  137 + securityStore.putX509(securityInfo);
138 138 } catch (NonUniqueSecurityInfoException e) {
139 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 17
18 18 import lombok.RequiredArgsConstructor;
19 19 import lombok.extern.slf4j.Slf4j;
  20 +import org.eclipse.leshan.core.SecurityMode;
20 21 import org.eclipse.leshan.core.model.ResourceModel;
21 22 import org.eclipse.leshan.core.node.LwM2mPath;
22 23 import org.eclipse.leshan.server.registration.Registration;
... ... @@ -30,7 +31,7 @@ import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
30 31 import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo;
31 32 import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext;
32 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 36 import java.util.Arrays;
36 37 import java.util.Collection;
... ... @@ -54,7 +55,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
54 55
55 56 private final LwM2mTransportContext context;
56 57 private final LwM2MTransportServerConfig config;
57   - private final TbEditableSecurityStore securityStore;
  58 + private final TbMainSecurityStore securityStore;
58 59 private final Map<String, LwM2mClient> lwM2mClientsByEndpoint = new ConcurrentHashMap<>();
59 60 private final Map<String, LwM2mClient> lwM2mClientsByRegistrationId = new ConcurrentHashMap<>();
60 61 private final Map<UUID, Lwm2mDeviceProfileTransportConfiguration> profiles = new ConcurrentHashMap<>();
... ... @@ -75,6 +76,9 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
75 76 oldSession = lwM2MClient.getSession();
76 77 TbLwM2MSecurityInfo securityInfo = securityStore.getTbLwM2MSecurityInfoByEndpoint(lwM2MClient.getEndpoint());
77 78 if (securityInfo.getSecurityMode() != null) {
  79 + if (SecurityMode.X509.equals(securityInfo.getSecurityMode())) {
  80 + securityStore.registerX509(registration.getEndpoint(), registration.getId());
  81 + }
78 82 if (securityInfo.getDeviceProfile() != null) {
79 83 profileUpdate(securityInfo.getDeviceProfile());
80 84 if (securityInfo.getSecurityInfo() != null) {
... ... @@ -124,7 +128,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
124 128 if (currentRegistration.getId().equals(registration.getId())) {
125 129 lwM2MClient.setState(LwM2MClientState.UNREGISTERED);
126 130 lwM2mClientsByEndpoint.remove(lwM2MClient.getEndpoint());
127   - this.securityStore.remove(lwM2MClient.getEndpoint());
  131 + this.securityStore.remove(lwM2MClient.getEndpoint(), registration.getId());
128 132 UUID profileId = lwM2MClient.getProfileId();
129 133 if (profileId != null) {
130 134 Optional<LwM2mClient> otherClients = lwM2mClientsByRegistrationId.values().stream().filter(e -> e.getProfileId().equals(profileId)).findFirst();
... ...
... ... @@ -22,13 +22,22 @@ import org.jetbrains.annotations.Nullable;
22 22 import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator;
23 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 33 import static org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer.CLIENT;
26 34
27 35 @Slf4j
28   -public class TbLwM2mSecurityStore implements TbEditableSecurityStore {
  36 +public class TbLwM2mSecurityStore implements TbMainSecurityStore {
29 37
30 38 private final TbEditableSecurityStore securityStore;
31 39 private final LwM2mCredentialsSecurityInfoValidator validator;
  40 + private final ConcurrentMap<String, Set<String>> endpointRegistrations = new ConcurrentHashMap<>();
32 41
33 42 public TbLwM2mSecurityStore(TbEditableSecurityStore securityStore, LwM2mCredentialsSecurityInfoValidator validator) {
34 43 this.securityStore = securityStore;
... ... @@ -61,24 +70,42 @@ public class TbLwM2mSecurityStore implements TbEditableSecurityStore {
61 70 @Nullable
62 71 public SecurityInfo fetchAndPutSecurityInfo(String credentialsId) {
63 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 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 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 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 51 }
52 52
53 53 @Bean
54   - private TbEditableSecurityStore securityStore() {
  54 + private TbMainSecurityStore securityStore() {
55 55 return new TbLwM2mSecurityStore(redisConfiguration.isPresent() && useRedis ?
56 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 +}
... ...