Commit d077ee6a07a29c2184ff1bbae3a3a5932bb6aa6a
1 parent
e83064ec
Improved Security Store to support race conditions during registration
Showing
6 changed files
with
80 additions
and
22 deletions
@@ -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 | +} |