Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to set custom REDIS keys for security endpoint and PSK ID #1398

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 !
Original file line number Diff line number Diff line change
Expand Up @@ -35,27 +35,34 @@

/**
* 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.
* <p>
* Security info are stored using the endpoint as primary key and a secondary index is created for endpoint lookup by
* PSK identity.
*/
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<Jedis> pool;

private final List<SecurityStoreListener> listeners = new CopyOnWriteArrayList<>();

public RedisSecurityStore(Pool<Jedis> pool) {
this.pool = pool;
this(new Builder(pool));
}

protected RedisSecurityStore(Builder builder) {
this.pool = builder.pool;
this.securityInfoByEndpointPrefix = builder.securityInfoByEndpointPrefix;
this.endpointByPskIdKey = builder.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 {
Expand All @@ -67,11 +74,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 {
Expand All @@ -90,7 +97,7 @@ public SecurityInfo getByOscoreIdentity(OscoreIdentity pskIdentity) {
@Override
public Collection<SecurityInfo> getAll() {
try (Jedis j = pool.getResource()) {
ScanParams params = new ScanParams().match(SEC_EP + "*").count(100);
ScanParams params = new ScanParams().match(securityInfoByEndpointPrefix + "*").count(100);
Collection<SecurityInfo> list = new LinkedList<>();
String cursor = "0";
do {
Expand All @@ -111,19 +118,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;
Expand All @@ -133,14 +140,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);
}
Expand All @@ -167,4 +174,86 @@ public void addListener(SecurityStoreListener listener) {
public void removeListener(SecurityStoreListener listener) {
listeners.remove(listener);
}

/**
* Class helping to build and configure a {@link RedisSecurityStore}.
* <p>
* 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<Jedis> pool;
private String securityInfoByEndpointPrefix;

private String endpointByPskIdKey;

private String prefix;

/**
* Set the key prefix for security info lookup by endpoint.
* <p>
* Default value is {@literal SEC#EP#}. Should not be {@code null} or empty.
*/
public Builder setSecurityInfoByEndpointPrefix(String securityInfoByEndpointPrefix) {
this.securityInfoByEndpointPrefix = securityInfoByEndpointPrefix;
return this;
}

/**
* Set the key for endpoint lookup by PSK identity.
* <p>
* Default value is {@literal EP#PSKID}. Should not be {@code null} or empty.
*/
public Builder setEndpointByPskIdKey(String endpointByPskIdKey) {
this.endpointByPskIdKey = endpointByPskIdKey;
sbernard31 marked this conversation as resolved.
Show resolved Hide resolved
return this;
}

/**
* Set the prefix for all keys and prefixes including {@link #securityInfoByEndpointPrefix} and
* {@link #endpointByPskIdKey}.
* <p>
* Default value is {@literal SECSTORE#}.
*/
public Builder setPrefix(String prefix) {
this.prefix = prefix;
return this;
}

public Builder(Pool<Jedis> pool) {
this.pool = pool;
this.prefix = "SECSTORE#";
this.securityInfoByEndpointPrefix = "SEC#EP#";
this.endpointByPskIdKey = "EP#PSKID";
}

/**
* Create the {@link RedisSecurityStore}.
* <p>
* Throws {@link IllegalArgumentException} when {@link #securityInfoByEndpointPrefix} or
* {@link #endpointByPskIdKey} are not set or are equal to each other.
*/
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);
}
}
}