From ffb14c0d798e8460ef5684d89470556931797936 Mon Sep 17 00:00:00 2001 From: Aliaksei Zhuk Date: Wed, 15 Feb 2023 00:13:12 +0300 Subject: [PATCH 1/4] Allow to set custom REDIS keys for security endpoint and PSK ID --- .../server/redis/RedisSecurityStore.java | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/RedisSecurityStore.java b/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/RedisSecurityStore.java index a9d0878166..f6182475cc 100644 --- a/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/RedisSecurityStore.java +++ b/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/RedisSecurityStore.java @@ -40,22 +40,28 @@ */ public class RedisSecurityStore implements EditableSecurityStore { - private static final String SEC_EP = "SEC#EP#"; + private final String securityInfoByEndpointPrefix; - private static final String PSKID_SEC = "PSKID#SEC"; + private final String endpointByPskIdKey; private final Pool pool; private final List listeners = new CopyOnWriteArrayList<>(); public RedisSecurityStore(Pool pool) { + this(pool, "SEC#EP#", "PSKID#SEC"); + } + + public RedisSecurityStore(Pool pool, String securityInfoByEndpointPrefix, String endpointByPskIdKey) { this.pool = pool; + this.securityInfoByEndpointPrefix = securityInfoByEndpointPrefix; + this.endpointByPskIdKey = endpointByPskIdKey; } @Override public SecurityInfo getByEndpoint(String endpoint) { try (Jedis j = pool.getResource()) { - byte[] data = j.get((SEC_EP + endpoint).getBytes()); + byte[] data = j.get((securityInfoByEndpointPrefix + endpoint).getBytes()); if (data == null) { return null; } else { @@ -67,11 +73,11 @@ public SecurityInfo getByEndpoint(String endpoint) { @Override public SecurityInfo getByIdentity(String identity) { try (Jedis j = pool.getResource()) { - String ep = j.hget(PSKID_SEC, identity); + String ep = j.hget(endpointByPskIdKey, identity); if (ep == null) { return null; } else { - byte[] data = j.get((SEC_EP + ep).getBytes()); + byte[] data = j.get((securityInfoByEndpointPrefix + ep).getBytes()); if (data == null) { return null; } else { @@ -90,7 +96,7 @@ public SecurityInfo getByOscoreIdentity(OscoreIdentity pskIdentity) { @Override public Collection getAll() { try (Jedis j = pool.getResource()) { - ScanParams params = new ScanParams().match(SEC_EP + "*").count(100); + ScanParams params = new ScanParams().match(securityInfoByEndpointPrefix + "*").count(100); Collection list = new LinkedList<>(); String cursor = "0"; do { @@ -111,19 +117,19 @@ public SecurityInfo add(SecurityInfo info) throws NonUniqueSecurityInfoException try (Jedis j = pool.getResource()) { if (info.getPskIdentity() != null) { // populate the secondary index (security info by PSK id) - String oldEndpoint = j.hget(PSKID_SEC, info.getPskIdentity()); + String oldEndpoint = j.hget(endpointByPskIdKey, info.getPskIdentity()); if (oldEndpoint != null && !oldEndpoint.equals(info.getEndpoint())) { throw new NonUniqueSecurityInfoException( "PSK Identity " + info.getPskIdentity() + " is already used"); } - j.hset(PSKID_SEC.getBytes(), info.getPskIdentity().getBytes(), info.getEndpoint().getBytes()); + j.hset(endpointByPskIdKey.getBytes(), info.getPskIdentity().getBytes(), info.getEndpoint().getBytes()); } - byte[] previousData = j.getSet((SEC_EP + info.getEndpoint()).getBytes(), data); + byte[] previousData = j.getSet((securityInfoByEndpointPrefix + info.getEndpoint()).getBytes(), data); SecurityInfo previous = previousData == null ? null : deserialize(previousData); String previousIdentity = previous == null ? null : previous.getPskIdentity(); if (previousIdentity != null && !previousIdentity.equals(info.getPskIdentity())) { - j.hdel(PSKID_SEC, previousIdentity); + j.hdel(endpointByPskIdKey, previousIdentity); } return previous; @@ -133,14 +139,14 @@ public SecurityInfo add(SecurityInfo info) throws NonUniqueSecurityInfoException @Override public SecurityInfo remove(String endpoint, boolean infosAreCompromised) { try (Jedis j = pool.getResource()) { - byte[] data = j.get((SEC_EP + endpoint).getBytes()); + byte[] data = j.get((securityInfoByEndpointPrefix + endpoint).getBytes()); if (data != null) { SecurityInfo info = deserialize(data); if (info.getPskIdentity() != null) { - j.hdel(PSKID_SEC.getBytes(), info.getPskIdentity().getBytes()); + j.hdel(endpointByPskIdKey.getBytes(), info.getPskIdentity().getBytes()); } - j.del((SEC_EP + endpoint).getBytes()); + j.del((securityInfoByEndpointPrefix + endpoint).getBytes()); for (SecurityStoreListener listener : listeners) { listener.securityInfoRemoved(infosAreCompromised, info); } From 070aa871102578f760da84a1b9f16b2f861b0237 Mon Sep 17 00:00:00 2001 From: Aliaksei Zhuk Date: Tue, 21 Feb 2023 13:14:27 +0300 Subject: [PATCH 2/4] Change default Redis keys for SecurityInfo storage --- CONTRIBUTING.md | 2 +- .../leshan/server/redis/RedisSecurityStore.java | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e3ef5c20a2..37d3c3deb5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,6 +2,6 @@ If you plan to contribute to Leshan please take few minutes to read our [Contribution Guide](https://github.com/eclipse/leshan/wiki/How-to-contribute). -If you are confortable with **java**, **maven**, **git** and **github PR flow**, you can just read the [legal stuff](https://github.com/eclipse/leshan/wiki/How-to-contribute#legal-stuff-) and our [code&design guidelines](https://github.com/eclipse/leshan/wiki/Code-&-design-guidelines). +If you are comfortable with **java**, **maven**, **git** and **github PR flow**, you can just read the [legal stuff](https://github.com/eclipse/leshan/wiki/How-to-contribute#legal-stuff-) and our [code&design guidelines](https://github.com/eclipse/leshan/wiki/Code-&-design-guidelines). Thanks a lot ! \ No newline at end of file diff --git a/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/RedisSecurityStore.java b/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/RedisSecurityStore.java index f6182475cc..29f40dfb3d 100644 --- a/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/RedisSecurityStore.java +++ b/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/RedisSecurityStore.java @@ -35,8 +35,12 @@ /** * A {@link SecurityStore} implementation based on Redis. - * - * Security info are stored using the endpoint as primary key and a secondary index is created for psk-identity lookup. + *

+ * Security info are stored using the endpoint as primary key and a secondary index is created for endpoint lookup by + * PSK identity. + *

+ * By default, uses {@code SEC#EP#} key prefix to find security info by endpoint and {@code EP#PSKID} key to get the + * endpoint by PSK ID. Leshan v1.x used {@code SEC#EP#} and {@code PSKID#SEC} keys for that accordingly. */ public class RedisSecurityStore implements EditableSecurityStore { @@ -49,7 +53,7 @@ public class RedisSecurityStore implements EditableSecurityStore { private final List listeners = new CopyOnWriteArrayList<>(); public RedisSecurityStore(Pool pool) { - this(pool, "SEC#EP#", "PSKID#SEC"); + this(pool, "SEC#EP#", "EP#PSKID"); } public RedisSecurityStore(Pool pool, String securityInfoByEndpointPrefix, String endpointByPskIdKey) { From 58270cc372ecd62e48c842ab15b63ccd9cba1a2a Mon Sep 17 00:00:00 2001 From: Aliaksei Zhuk Date: Thu, 2 Mar 2023 08:31:34 +0300 Subject: [PATCH 3/4] Replace RedisSecurityStore constructors with Builder --- .../RedisSecureIntegrationTestHelper.java | 2 +- .../leshan/server/demo/LeshanServerDemo.java | 10 ++- .../server/redis/RedisSecurityStore.java | 85 ++++++++++++++++--- 3 files changed, 81 insertions(+), 16 deletions(-) diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/RedisSecureIntegrationTestHelper.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/RedisSecureIntegrationTestHelper.java index eb472e64d0..e907752519 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/RedisSecureIntegrationTestHelper.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/RedisSecureIntegrationTestHelper.java @@ -34,7 +34,7 @@ protected SecurityStore createSecurityStore() { } else { jedis = new JedisPool(); } - securityStore = new RedisSecurityStore(jedis); + securityStore = new RedisSecurityStore.Builder(jedis).build(); return securityStore; } } diff --git a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/LeshanServerDemo.java b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/LeshanServerDemo.java index 94f4234c32..e521dbc74c 100644 --- a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/LeshanServerDemo.java +++ b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/LeshanServerDemo.java @@ -34,7 +34,7 @@ import org.eclipse.californium.elements.util.CertPathUtil; import org.eclipse.californium.scandium.config.DtlsConfig; import org.eclipse.californium.scandium.config.DtlsConfig.DtlsRole; -import org.eclipse.californium.scandium.config.DtlsConnectorConfig.Builder; +import org.eclipse.californium.scandium.config.DtlsConnectorConfig; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.webapp.WebAppContext; @@ -162,7 +162,7 @@ public static LeshanServer createLeshanServer(LeshanServerDemoCLI cli) throws Ex securityStore = new FileSecurityStore(); } else { // use Redis Store - securityStore = new RedisSecurityStore(cli.main.redis); + securityStore = new RedisSecurityStore.Builder(cli.main.redis).build(); builder.setRegistrationStore(new RedisRegistrationStore(cli.main.redis)); } builder.setSecurityStore(securityStore); @@ -190,8 +190,10 @@ public CaliforniumServerEndpointFactory createDefaultEndpointFactory(URI uri) { return new CoapsServerEndpointFactory(uri) { @Override - protected Builder createDtlsConnectorConfigBuilder(Configuration endpointConfiguration) { - Builder dtlsConfigBuilder = super.createDtlsConnectorConfigBuilder(endpointConfiguration); + protected DtlsConnectorConfig.Builder createDtlsConnectorConfigBuilder( + Configuration endpointConfiguration) { + DtlsConnectorConfig.Builder dtlsConfigBuilder = super.createDtlsConnectorConfigBuilder( + endpointConfiguration); // Add MDC for connection logs if (cli.helpsOptions.getVerboseLevel() > 0) diff --git a/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/RedisSecurityStore.java b/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/RedisSecurityStore.java index 29f40dfb3d..3d90686723 100644 --- a/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/RedisSecurityStore.java +++ b/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/RedisSecurityStore.java @@ -38,9 +38,6 @@ *

* Security info are stored using the endpoint as primary key and a secondary index is created for endpoint lookup by * PSK identity. - *

- * By default, uses {@code SEC#EP#} key prefix to find security info by endpoint and {@code EP#PSKID} key to get the - * endpoint by PSK ID. Leshan v1.x used {@code SEC#EP#} and {@code PSKID#SEC} keys for that accordingly. */ public class RedisSecurityStore implements EditableSecurityStore { @@ -52,14 +49,10 @@ public class RedisSecurityStore implements EditableSecurityStore { private final List listeners = new CopyOnWriteArrayList<>(); - public RedisSecurityStore(Pool pool) { - this(pool, "SEC#EP#", "EP#PSKID"); - } - - public RedisSecurityStore(Pool pool, String securityInfoByEndpointPrefix, String endpointByPskIdKey) { - this.pool = pool; - this.securityInfoByEndpointPrefix = securityInfoByEndpointPrefix; - this.endpointByPskIdKey = endpointByPskIdKey; + private RedisSecurityStore(Builder builder) { + this.pool = builder.pool; + this.securityInfoByEndpointPrefix = builder.securityInfoByEndpointPrefix; + this.endpointByPskIdKey = builder.endpointByPskIdKey; } @Override @@ -177,4 +170,74 @@ public void addListener(SecurityStoreListener listener) { public void removeListener(SecurityStoreListener listener) { listeners.remove(listener); } + + /** + * Class helping to build and configure a {@link RedisSecurityStore}. + *

+ * By default, uses {@code SECSTORE#} prefix for all keys, {@code SEC#EP#} key prefix to find security info by + * endpoint and {@code EP#PSKID} key to get the endpoint by PSK ID. Leshan v1.x used {@code SEC#EP#} and + * {@code PSKID#SEC} keys for that accordingly. + */ + public static class Builder { + private Pool pool; + private String securityInfoByEndpointPrefix; + + private String endpointByPskIdKey; + + private String prefix; + + /** + * Set the Redis connection pool for the {@link RedisSecurityStore}. + */ + public void setPool(Pool pool) { + this.pool = pool; + } + + /** + * Set the key prefix for security info lookup by endpoint. + *

+ * Default value is {@literal SEC#EP#}. + */ + public void setSecurityInfoByEndpointPrefix(String securityInfoByEndpointPrefix) { + this.securityInfoByEndpointPrefix = securityInfoByEndpointPrefix; + } + + /** + * Set the key for endpoint lookup by PSK identity. + *

+ * Default value is {@literal EP#PSKID}. + */ + public void setEndpointByPskIdKey(String endpointByPskIdKey) { + this.endpointByPskIdKey = endpointByPskIdKey; + } + + /** + * Set the prefix for all keys and prefixes including {@link #securityInfoByEndpointPrefix} and + * {@link #endpointByPskIdKey}. + *

+ * Default value is {@literal SECSTORE#}. + */ + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + public Builder(Pool pool) { + this.pool = pool; + this.prefix = "SECSTORE#"; + this.securityInfoByEndpointPrefix = "SEC#EP#"; + this.endpointByPskIdKey = "EP#PSKID"; + } + + /** + * Create the {@link RedisSecurityStore}. + *

+ * @return the Redis security store. + */ + public RedisSecurityStore build() { + this.securityInfoByEndpointPrefix = this.prefix + this.securityInfoByEndpointPrefix; + this.endpointByPskIdKey = this.prefix + this.endpointByPskIdKey; + + return new RedisSecurityStore(this); + } + } } From bd8b3891bd03be90b1418b073b474a8e2685c228 Mon Sep 17 00:00:00 2001 From: Aliaksei Zhuk Date: Thu, 2 Mar 2023 18:53:36 +0300 Subject: [PATCH 4/4] Forbid setting empty prefixes for RedisSecurityStore --- .../RedisSecureIntegrationTestHelper.java | 2 +- .../leshan/server/demo/LeshanServerDemo.java | 10 ++-- .../server/redis/RedisSecurityStore.java | 50 ++++++++++++------- 3 files changed, 38 insertions(+), 24 deletions(-) diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/RedisSecureIntegrationTestHelper.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/RedisSecureIntegrationTestHelper.java index e907752519..eb472e64d0 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/RedisSecureIntegrationTestHelper.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/util/RedisSecureIntegrationTestHelper.java @@ -34,7 +34,7 @@ protected SecurityStore createSecurityStore() { } else { jedis = new JedisPool(); } - securityStore = new RedisSecurityStore.Builder(jedis).build(); + securityStore = new RedisSecurityStore(jedis); return securityStore; } } diff --git a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/LeshanServerDemo.java b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/LeshanServerDemo.java index e521dbc74c..94f4234c32 100644 --- a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/LeshanServerDemo.java +++ b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/LeshanServerDemo.java @@ -34,7 +34,7 @@ import org.eclipse.californium.elements.util.CertPathUtil; import org.eclipse.californium.scandium.config.DtlsConfig; import org.eclipse.californium.scandium.config.DtlsConfig.DtlsRole; -import org.eclipse.californium.scandium.config.DtlsConnectorConfig; +import org.eclipse.californium.scandium.config.DtlsConnectorConfig.Builder; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.webapp.WebAppContext; @@ -162,7 +162,7 @@ public static LeshanServer createLeshanServer(LeshanServerDemoCLI cli) throws Ex securityStore = new FileSecurityStore(); } else { // use Redis Store - securityStore = new RedisSecurityStore.Builder(cli.main.redis).build(); + securityStore = new RedisSecurityStore(cli.main.redis); builder.setRegistrationStore(new RedisRegistrationStore(cli.main.redis)); } builder.setSecurityStore(securityStore); @@ -190,10 +190,8 @@ public CaliforniumServerEndpointFactory createDefaultEndpointFactory(URI uri) { return new CoapsServerEndpointFactory(uri) { @Override - protected DtlsConnectorConfig.Builder createDtlsConnectorConfigBuilder( - Configuration endpointConfiguration) { - DtlsConnectorConfig.Builder dtlsConfigBuilder = super.createDtlsConnectorConfigBuilder( - endpointConfiguration); + protected Builder createDtlsConnectorConfigBuilder(Configuration endpointConfiguration) { + Builder dtlsConfigBuilder = super.createDtlsConnectorConfigBuilder(endpointConfiguration); // Add MDC for connection logs if (cli.helpsOptions.getVerboseLevel() > 0) diff --git a/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/RedisSecurityStore.java b/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/RedisSecurityStore.java index 3d90686723..0dfa20ec5c 100644 --- a/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/RedisSecurityStore.java +++ b/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/RedisSecurityStore.java @@ -49,7 +49,11 @@ public class RedisSecurityStore implements EditableSecurityStore { private final List listeners = new CopyOnWriteArrayList<>(); - private RedisSecurityStore(Builder builder) { + public RedisSecurityStore(Pool pool) { + this(new Builder(pool)); + } + + protected RedisSecurityStore(Builder builder) { this.pool = builder.pool; this.securityInfoByEndpointPrefix = builder.securityInfoByEndpointPrefix; this.endpointByPskIdKey = builder.endpointByPskIdKey; @@ -186,29 +190,24 @@ public static class Builder { private String prefix; - /** - * Set the Redis connection pool for the {@link RedisSecurityStore}. - */ - public void setPool(Pool pool) { - this.pool = pool; - } - /** * Set the key prefix for security info lookup by endpoint. *

- * Default value is {@literal SEC#EP#}. + * Default value is {@literal SEC#EP#}. Should not be {@code null} or empty. */ - public void setSecurityInfoByEndpointPrefix(String securityInfoByEndpointPrefix) { + public Builder setSecurityInfoByEndpointPrefix(String securityInfoByEndpointPrefix) { this.securityInfoByEndpointPrefix = securityInfoByEndpointPrefix; + return this; } /** * Set the key for endpoint lookup by PSK identity. *

- * Default value is {@literal EP#PSKID}. + * Default value is {@literal EP#PSKID}. Should not be {@code null} or empty. */ - public void setEndpointByPskIdKey(String endpointByPskIdKey) { + public Builder setEndpointByPskIdKey(String endpointByPskIdKey) { this.endpointByPskIdKey = endpointByPskIdKey; + return this; } /** @@ -217,8 +216,9 @@ public void setEndpointByPskIdKey(String endpointByPskIdKey) { *

* Default value is {@literal SECSTORE#}. */ - public void setPrefix(String prefix) { + public Builder setPrefix(String prefix) { this.prefix = prefix; + return this; } public Builder(Pool pool) { @@ -231,11 +231,27 @@ public Builder(Pool pool) { /** * Create the {@link RedisSecurityStore}. *

- * @return the Redis security store. + * Throws {@link IllegalArgumentException} when {@link #securityInfoByEndpointPrefix} or + * {@link #endpointByPskIdKey} are not set or are equal to each other. */ - public RedisSecurityStore build() { - this.securityInfoByEndpointPrefix = this.prefix + this.securityInfoByEndpointPrefix; - this.endpointByPskIdKey = this.prefix + this.endpointByPskIdKey; + public RedisSecurityStore build() throws IllegalArgumentException { + if (this.securityInfoByEndpointPrefix == null || this.securityInfoByEndpointPrefix.isEmpty()) { + throw new IllegalArgumentException("securityInfoByEndpointPrefix should not be empty"); + } + + if (this.endpointByPskIdKey == null || this.endpointByPskIdKey.isEmpty()) { + throw new IllegalArgumentException("endpointByPskIdKey should not be empty"); + } + + if (this.securityInfoByEndpointPrefix.equals(this.endpointByPskIdKey)) { + throw new IllegalArgumentException( + "securityInfoByEndpointPrefix should not be equal to endpointByPskIdKey"); + } + + if (this.prefix != null) { + this.securityInfoByEndpointPrefix = this.prefix + this.securityInfoByEndpointPrefix; + this.endpointByPskIdKey = this.prefix + this.endpointByPskIdKey; + } return new RedisSecurityStore(this); }