Secure LLDP-based Topology Detection
Current LLDP/BDDP-based Topology Detection is vulnerable to the
creation of fake links via forged, modified, or replayed LLDP packets.
This patch fixes this vulnerability by authenticating LLDP/BDDP packets
using a Message Authentication Code and adding a timestamp to prevent
replay. We use HMAC with SHA-256 has our Messge Authentication Code and
derive the key from the config/cluster.json file via the
ClusterMetadata class.
Change-Id: I01dd6edc5cffd6dfe274bcdb97189f2661a6c4f1
diff --git a/core/api/src/main/java/org/onosproject/cluster/ClusterMetadata.java b/core/api/src/main/java/org/onosproject/cluster/ClusterMetadata.java
index e1f664e..bf3e4a0 100644
--- a/core/api/src/main/java/org/onosproject/cluster/ClusterMetadata.java
+++ b/core/api/src/main/java/org/onosproject/cluster/ClusterMetadata.java
@@ -46,6 +46,8 @@
private final ControllerNode localNode;
private final Set<ControllerNode> controllerNodes;
private final Set<Node> storageNodes;
+ private final String clusterSecret;
+
public static final Funnel<ClusterMetadata> HASH_FUNNEL = new Funnel<ClusterMetadata>() {
@Override
@@ -61,6 +63,30 @@
localNode = null;
controllerNodes = null;
storageNodes = null;
+ clusterSecret = null;
+ }
+
+ /**
+ * @deprecated since 1.15.
+ * @param providerId the provider Id
+ * @param name The cluster Name
+ * @param localNode The local node
+ * @param controllerNodes Set of nodes in cluster
+ * @param storageNodes Set of storage nodes
+ */
+ @Deprecated
+ public ClusterMetadata(
+ ProviderId providerId,
+ String name,
+ ControllerNode localNode,
+ Set<ControllerNode> controllerNodes,
+ Set<Node> storageNodes) {
+ this.providerId = checkNotNull(providerId);
+ this.name = checkNotNull(name);
+ this.localNode = localNode;
+ this.controllerNodes = ImmutableSet.copyOf(checkNotNull(controllerNodes));
+ this.storageNodes = ImmutableSet.copyOf(checkNotNull(storageNodes));
+ this.clusterSecret = "INSECURE!";
}
public ClusterMetadata(
@@ -68,17 +94,33 @@
String name,
ControllerNode localNode,
Set<ControllerNode> controllerNodes,
- Set<Node> storageNodes) {
+ Set<Node> storageNodes,
+ String clusterSecret) {
this.providerId = checkNotNull(providerId);
this.name = checkNotNull(name);
this.localNode = localNode;
this.controllerNodes = ImmutableSet.copyOf(checkNotNull(controllerNodes));
this.storageNodes = ImmutableSet.copyOf(checkNotNull(storageNodes));
+ this.clusterSecret = clusterSecret;
+ }
+
+ /**
+ * @deprecated since 1.15.
+ * @param name The cluster Name
+ * @param localNode The local node
+ * @param controllerNodes Set of nodes in cluster
+ * @param storageNodes Set of storage nodes
+ */
+ @Deprecated
+ public ClusterMetadata(
+ String name, ControllerNode localNode, Set<ControllerNode> controllerNodes, Set<Node> storageNodes) {
+ this(new ProviderId("none", "none"), name, localNode, controllerNodes, storageNodes, "INSECURE!");
}
public ClusterMetadata(
- String name, ControllerNode localNode, Set<ControllerNode> controllerNodes, Set<Node> storageNodes) {
- this(new ProviderId("none", "none"), name, localNode, controllerNodes, storageNodes);
+ String name, ControllerNode localNode, Set<ControllerNode> controllerNodes, Set<Node> storageNodes,
+ String clusterSecret) {
+ this(new ProviderId("none", "none"), name, localNode, controllerNodes, storageNodes, clusterSecret);
}
@Override
@@ -140,6 +182,14 @@
return Collections.emptySet();
}
+ /**
+ * Returns the cluster's shared secret.
+ * @return key.
+ */
+ public String getClusterSecret() {
+ return clusterSecret;
+ }
+
@Override
public String toString() {
return MoreObjects.toStringHelper(ClusterMetadata.class)
diff --git a/core/api/src/main/java/org/onosproject/cluster/ClusterMetadataDiff.java b/core/api/src/main/java/org/onosproject/cluster/ClusterMetadataDiff.java
index acad25f..e9f73b4 100644
--- a/core/api/src/main/java/org/onosproject/cluster/ClusterMetadataDiff.java
+++ b/core/api/src/main/java/org/onosproject/cluster/ClusterMetadataDiff.java
@@ -37,6 +37,7 @@
private final ClusterMetadata newValue;
private final Set<ControllerNode> nodesAdded;
private final Set<NodeId> nodesRemoved;
+ private final boolean secretChanged;
public ClusterMetadataDiff(ClusterMetadata oldValue, ClusterMetadata newValue) {
this.oldValue = oldValue;
@@ -51,6 +52,20 @@
.stream()
.map(ControllerNode::id)
.collect(Collectors.toSet());
+
+ boolean haveOldSecret = (oldValue != null && oldValue.getClusterSecret() != null);
+ boolean haveNewSecret = (newValue != null && newValue.getClusterSecret() != null);
+
+ if (!haveOldSecret && haveNewSecret) {
+ secretChanged = true;
+ } else if (haveOldSecret && haveNewSecret &&
+ !oldValue.getClusterSecret().equals(newValue.getClusterSecret())) {
+ secretChanged = true;
+ } else if (haveOldSecret && !haveNewSecret) {
+ secretChanged = true;
+ } else {
+ secretChanged = false;
+ }
}
/**
@@ -70,6 +85,14 @@
}
/**
+ * Returns whether the cluster-wide shared secret changed.
+ * @return whether the cluster secret changed
+ */
+ public boolean clusterSecretChanged() {
+ return secretChanged;
+ }
+
+ /**
* Returns a mapping of all partition diffs.
* @return partition diffs.
*/