diff --git a/src/lib/ovsdb.py b/src/lib/ovsdb.py
index 625c752..901a9ed 100644
--- a/src/lib/ovsdb.py
+++ b/src/lib/ovsdb.py
@@ -46,8 +46,7 @@ class OVSDB(reactive.Endpoint):
             fmt = '{}'
         return fmt.format(ipaddr)
 
-    @property
-    def cluster_local_addr(self):
+    def _endpoint_local_bound_addr(self):
         """Retrieve local address bound to endpoint.
 
         :returns: IPv4 or IPv6 address bound to endpoint
@@ -62,21 +61,40 @@ class OVSDB(reactive.Endpoint):
                     return self._format_addr(addr['address'])
 
     @property
-    def cluster_remote_addrs(self):
-        """Retrieve remote addresses bound to remote endpoint.
+    def cluster_local_addr(self):
+        """Retrieve local address bound to endpoint.
 
-        :returns: IPv4 or IPv6 addresses bound to remote endpoints.
+        :returns: IPv4 or IPv6 address bound to endpoint
+        :rtype: str
+        """
+        return self._endpoint_local_bound_addr()
+
+    def _remote_addrs(self, key):
+        """Retrieve addresses published by remote units.
+
+        :param key: Relation data key to retrieve value from.
+        :type key: str
+        :returns: IPv4 or IPv6 addresses published by remote units.
         :rtype: Iterator[str]
         """
         for relation in self.relations:
             for unit in relation.units:
                 try:
                     addr = self._format_addr(
-                        unit.received.get('bound-address', ''))
+                        unit.received.get(key, ''))
                     yield addr
                 except ValueError:
                     continue
 
+    @property
+    def cluster_remote_addrs(self):
+        """Retrieve remote addresses bound to remote endpoint.
+
+        :returns: IPv4 or IPv6 addresses bound to remote endpoints.
+        :rtype: Iterator[str]
+        """
+        return self._remote_addrs('bound-address')
+
     @property
     def db_nb_port(self):
         """Provide port number for OVN Northbound OVSDB.
diff --git a/src/ovsdb_cms/provides.py b/src/ovsdb_cms/provides.py
index ab1742c..f13b450 100644
--- a/src/ovsdb_cms/provides.py
+++ b/src/ovsdb_cms/provides.py
@@ -29,6 +29,11 @@ from .lib import ovsdb as ovsdb
 
 class OVSDBCMSProvides(ovsdb.OVSDB):
 
+    @property
+    def client_remote_addrs(self):
+        """Retrieve remote bound addresses as provided by our clients."""
+        return self._remote_addrs('cms-client-bound-address')
+
     @when('endpoint.{endpoint_name}.joined')
     def joined(self):
         ch_core.hookenv.log('{}: {} -> {}'
diff --git a/src/ovsdb_cms/requires.py b/src/ovsdb_cms/requires.py
index 141af4f..67812d3 100644
--- a/src/ovsdb_cms/requires.py
+++ b/src/ovsdb_cms/requires.py
@@ -31,6 +31,15 @@ from .lib import ovsdb as ovsdb
 
 class OVSDBCMSRequires(ovsdb.OVSDB):
 
+    def publish_cms_client_bound_addr(self):
+        """Announce address on relation.
+
+        This will be used by our peers to grant access.
+        """
+        for relation in self.relations:
+            relation.to_publish['cms-client-bound-address'] = (
+                self._endpoint_local_bound_addr())
+
     @when('endpoint.{endpoint_name}.joined')
     def joined(self):
         ch_core.hookenv.log('{}: {} -> {}'
@@ -39,6 +48,7 @@ class OVSDBCMSRequires(ovsdb.OVSDB):
                                     inspect.currentframe().f_code.co_name),
                             level=ch_core.hookenv.INFO)
         super().joined()
+        self.publish_cms_client_bound_addr()
         if self.expected_units_available():
             reactive.set_flag(self.expand_name('{endpoint_name}.available'))
 
diff --git a/unit_tests/test_lib_ovsdb.py b/unit_tests/test_lib_ovsdb.py
index 5d07d94..3be59db 100644
--- a/unit_tests/test_lib_ovsdb.py
+++ b/unit_tests/test_lib_ovsdb.py
@@ -67,7 +67,7 @@ class TestOVSDBLib(test_utils.PatchHelper):
         with self.assertRaises(ValueError):
             self.target._format_addr('2001:db8::g')
 
-    def test_cluster_local_addr(self):
+    def test__endpoint_local_bound_addr(self):
         relation = mock.MagicMock()
         relation.relation_id = 'some-endpoint:42'
         self.patch_target('_relations')
@@ -92,11 +92,17 @@ class TestOVSDBLib(test_utils.PatchHelper):
             'egress-subnets': ['42.42.42.42/32'],
             'ingress-addresses': ['42.42.42.42'],
         }
-        self.assertEquals(self.target.cluster_local_addr, '42.42.42.42')
+        self.assertEquals(self.target._endpoint_local_bound_addr(),
+                          '42.42.42.42')
         self.network_get.assert_called_once_with(
             'some-relation', relation_id='some-endpoint:42')
 
-    def test_cluster_remote_addr(self):
+    def test_cluster_local_addr(self):
+        self.patch_target('_endpoint_local_bound_addr')
+        self.target.cluster_local_addr
+        self._endpoint_local_bound_addr.assert_called_once_with()
+
+    def test__remote_addrs(self):
         relation = mock.MagicMock()
         relation.relation_id = 'some-endpoint:42'
         unit4 = mock.MagicMock()
@@ -107,9 +113,14 @@ class TestOVSDBLib(test_utils.PatchHelper):
         self.patch_target('_relations')
         self._relations.__iter__.return_value = [relation]
         self.assertEquals(
-            sorted(self.target.cluster_remote_addrs),
+            sorted(self.target._remote_addrs('bound-address')),
             ['1.2.3.4', '[2001:db8::42]'])
 
+    def test_cluster_remote_addrs(self):
+        self.patch_target('_remote_addrs')
+        self.target.cluster_remote_addrs
+        self._remote_addrs.assert_called_once_with('bound-address')
+
     def test_db_nb_port(self):
         self.assertEquals(self.target.db_nb_port, self.target.DB_NB_PORT)
 
diff --git a/unit_tests/test_ovsdb_cms_provides.py b/unit_tests/test_ovsdb_cms_provides.py
new file mode 100644
index 0000000..f171669
--- /dev/null
+++ b/unit_tests/test_ovsdb_cms_provides.py
@@ -0,0 +1,52 @@
+# Copyright 2019 Canonical Ltd
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#  http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import mock
+
+from ovsdb_cms import provides
+
+import charms_openstack.test_utils as test_utils
+
+
+_hook_args = {}
+
+
+class TestOVSDBCMSProvides(test_utils.PatchHelper):
+
+    def setUp(self):
+        super().setUp()
+        self.target = provides.OVSDBCMSProvides('some-relation', [])
+        self._patches = {}
+        self._patches_start = {}
+
+    def tearDown(self):
+        self.target = None
+        for k, v in self._patches.items():
+            v.stop()
+            setattr(self, k, None)
+        self._patches = None
+        self._patches_start = None
+
+    def patch_target(self, attr, return_value=None):
+        mocked = mock.patch.object(self.target, attr)
+        self._patches[attr] = mocked
+        started = mocked.start()
+        started.return_value = return_value
+        self._patches_start[attr] = started
+        setattr(self, attr, started)
+
+    def test_client_remote_addrs(self):
+        self.patch_target('_remote_addrs')
+        self.target.client_remote_addrs
+        self._remote_addrs.assert_called_once_with('cms-client-bound-address')
diff --git a/unit_tests/test_ovsdb_cms_requires.py b/unit_tests/test_ovsdb_cms_requires.py
new file mode 100644
index 0000000..45bc5dd
--- /dev/null
+++ b/unit_tests/test_ovsdb_cms_requires.py
@@ -0,0 +1,79 @@
+# Copyright 2019 Canonical Ltd
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#  http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import mock
+
+from ovsdb_cms import requires
+
+import charms_openstack.test_utils as test_utils
+
+
+_hook_args = {}
+
+
+class TestOVSDBCMSProvides(test_utils.PatchHelper):
+
+    def setUp(self):
+        super().setUp()
+        self.target = requires.OVSDBCMSRequires('some-relation', [])
+        self._patches = {}
+        self._patches_start = {}
+
+    def tearDown(self):
+        self.target = None
+        for k, v in self._patches.items():
+            v.stop()
+            setattr(self, k, None)
+        self._patches = None
+        self._patches_start = None
+
+    def patch_target(self, attr, return_value=None):
+        mocked = mock.patch.object(self.target, attr)
+        self._patches[attr] = mocked
+        started = mocked.start()
+        started.return_value = return_value
+        self._patches_start[attr] = started
+        setattr(self, attr, started)
+
+    def patch_topublish(self):
+        self.patch_target('_relations')
+        relation = mock.MagicMock()
+        to_publish = mock.PropertyMock()
+        type(relation).to_publish = to_publish
+        self._relations.__iter__.return_value = [relation]
+        return relation.to_publish
+
+    def test_publish_cms_client_bound_addr(self):
+        self.patch_target('_endpoint_local_bound_addr')
+        self._endpoint_local_bound_addr.return_value = 'a.b.c.d'
+        to_publish = self.patch_topublish()
+        self.target.publish_cms_client_bound_addr()
+        to_publish.__setitem__.assert_called_once_with(
+            'cms-client-bound-address', 'a.b.c.d')
+
+    def test_joined(self):
+        self.patch_target('publish_cms_client_bound_addr')
+        self.patch_target('expected_units_available')
+        self.patch_object(requires.reactive, 'set_flag')
+        self.expected_units_available.return_value = False
+        self.target.joined()
+        self.publish_cms_client_bound_addr.assert_called_once_with()
+        self.set_flag.assert_called_once_with('some-relation.connected')
+        self.set_flag.reset_mock()
+        self.expected_units_available.return_value = True
+        self.target.joined()
+        self.set_flag.assert_has_calls([
+            mock.call('some-relation.connected'),
+            mock.call('some-relation.available'),
+        ])