Passive Peer
How to set up a listener to accept a replicator connection and sync using peer-to-peer
Android enablers
Allow Unencrypted Network Traffic
To use cleartext, un-encrypted, network traffic (http://
and-or ws://
), include
android:usesCleartextTraffic="true"
in the application
element of the manifest as shown on
developer.android.com.
This is not recommended in production.
iOS Restrictions
iOS 14 Applications
When your application attempts to access the user’s local network, iOS will prompt them to allow (or deny) access.
You can customize the message presented to the user by editing the description for the
NSLocalNetworkUsageDescription
key in the Info.plist
.
Use Background Threads
As with any network or file I/O activity, Couchbase Lite activities should not be performed on the UI thread. Always use a background thread.
Code Snippets
All code examples are indicative only. They demonstrate the basic concepts and approaches to using a feature. Use them as inspiration and adapt these examples to best practice when developing applications for your platform.
Introduction
This is an Enterprise Edition feature.
This content provides code and configuration examples covering the implementation of Peer-to-Peer Sync over WebSockets. Specifically, it covers the implementation of a Passive Peer.
Couchbase’s Passive Peer (also referred to as the server, or listener) will accept a connection from an Active Peer (also referred to as the client or replicator) and replicate database changes to synchronize both databases.
Subsequent sections provide additional details and examples for the main configuration options.
Secure Storage
The use of TLS, its associated keys and certificates requires using secure storage to minimize the chances of a security breach. The implementation of this storage differs from platform to platform — see Using Secure Storage.
Configuration Summary
You should configure and initialize a listener for each Couchbase Lite database instance you want to sync. There is no limit on the number of listeners you may configure — Example 1 shows a simple initialization and configuration process.
Example 1. Listener configuration and initialization
val listener = URLEndpointListener(
URLEndpointListenerConfigurationFactory.newConfig(
collections = collections,
port = 55990,
networkInterface = "wlan0",
enableDeltaSync = false,
// Configure server security
disableTls = false,
// Use an Anonymous Self-Signed Cert
identity = null,
// Configure Client Security using an Authenticator
// For example, Basic Authentication
authenticator = ListenerPasswordAuthenticator { usr, pwd ->
(usr === validUser) && (pwd.concatToString() == validPass)
}
)
)
// Start the listener
listener.start()
- Identify the collections from the local database to be used — see Initialize the Listener Configuration
- Optionally, choose a port to use. By default, the system will automatically assign a port — to override this, see Set Port and Network Interface
- Optionally, choose a network interface to use. By default, the system will listen on all network interfaces — to override this see Set Port and Network Interface
- Optionally, choose to sync only changes. The default is not to enable delta-sync — see Delta Sync
- Set server security. TLS is always enabled instantly, so you can usually omit this line. But you can, optionally, disable TLS (not advisable in production) — see TLS Security
- Set the credentials this server will present to the client for authentication. Here we show the default TLS authentication, which is an anonymous self-signed certificate. The server must always authenticate itself to the client.
- Set client security — define the credentials the server expects the client to present for authentication. Here we
show how basic authentication is configured to authenticate the client-supplied credentials from the http
authentication header against valid credentials — see Authenticating the Client for
more options.
Note that client authentication is optional. - Initialize the listener using the configuration settings.
- Start Listener
Device Discovery
This phase is optional: If the listener is initialized on a well-known URL endpoint (for example, a static IP address or well-known DNS address) then you can configure Active Peers to connect to those.
Before initiating the listener, you may execute a peer discovery phase. For the Passive Peer, this involves advertising the service using, for example, Network Service Discovery on Android or Bonjour on iOS and waiting for an invite from the Active Peer. The connection is established once the Passive Peer has authenticated and accepted an Active Peer’s invitation.
Initialize the Listener Configuration
Initialize the listener configuration with the collections to sync from the local database — see Example 2. All other configuration values take their default setting.
Each listener instance serves one Couchbase Lite database. Couchbase sets no hard limit on the number of listeners you can initialize.
Set the local database using the URLEndpointListenerConfiguration
's constructor
URLEndpointListenerConfiguration(Database)
.
The database must be opened before the listener is started.
Set Port and Network Interface
Port number
The Listener will automatically select an available port if you do not specify one — see Example 3 for how to specify a port.
To use a canonical port — one known to other applications — specify it explicitly using the port
property shown here.
Ensure that firewall rules do not block any port you do specify.
Network Interface
The listener will listen on all network interfaces by default.
To specify an interface — one known to other applications — identify it explicitly, using the networkInterface
property shown here. This
must be either an IP address or network interface name such as en0
.
Delta Sync
Delta Sync allows clients to sync only those parts of a document that have changed. This can result in significant bandwidth consumption savings and throughput improvements. Both are valuable benefits, especially when network bandwidth is constrained.
Delta sync replication is not enabled by default. Use URLEndpointListenerConfiguration
's isDeltaSyncEnabled
property to activate
or deactivate it.
TLS Security
Enable or Disable TLS
Define whether the connection is to use TLS or clear text.
TLS-based encryption is enabled by default, and this setting ought to be used in any production environment. However, it can be disabled. For example, for development or test environments.
When TLS is enabled, Couchbase Lite provides several options on how the listener may be configured with an appropriate TLS Identity — see Configure TLS Identity for Listener.
Note
On the Android platform, to use cleartext, un-encrypted, network traffic (http://
and-or ws://
), include
android:usesCleartextTraffic="true"
in the application
element of the manifest as shown on
developer.android.com.
This is not recommended in production.
You can use URLEndpointListenerConfiguration
's isTlsDisabled
method to disable TLS
communication if necessary.
The isTlsDisabled
setting must be false
when Client Cert Authentication is required.
Basic Authentication can be used with, or without, TLS.
isTlsDisabled
works in conjunction with TLSIdentity
, to enable developers to define the key and certificate to be
used.
- If
isTlsDisabled
istrue
— TLS communication is disabled and TLS identity is ignored.
Active peers will use thews://
URL scheme used to connect to the listener. - If
isTlsDisabled
isfalse
or not specified — TLS communication is enabled.
Active peers will use the wss:// URL scheme to connect to the listener.
Configure TLS Identity for Listener
Define the credentials the server will present to the client for authentication. Note that the server must always authenticate itself with the client — see Authenticating the Listener on Active Peer for how the client deals with this.
Use URLEndpointListenerConfiguration
's
tlsIdentity
property to
configure the TLS Identity used in TLS communication.
If TLSIdentity
is not set, then the listener uses an auto-generated
anonymous self-signed identity (unless isTlsDisabled = true
). Whilst the client cannot use this to authenticate the
server, it will use it to encrypt communication, giving a more secure option than non-TLS communication.
The auto-generated anonymous self-signed identity is saved in secure storage for future use to obviate the need to re-generate it.
Note
Typically, you will configure the listener’s TLS Identity once during the initial launch and re-use it (from secure storage on any subsequent starts.
Here are some example code snippets showing:
- Importing a TLS identity — see Example 6
- Setting TLS identity to expect self-signed certificate — see Example 7
- Setting TLS identity to expect anonymous certificate — see Example 8
Example 6. Import Listener’s TLS identity
TLS identity certificate import APIs are platform-specific.
config.isTlsDisabled = false
val path = NSBundle.mainBundle.pathForResource("cert", ofType = "p12") ?: return
val certData = NSData.dataWithContentsOfFile(path) ?: return
val tlsIdentity = TLSIdentity.importIdentity(
data = certData.toByteArray(),
password = "123".toCharArray(),
alias = "alias"
)
config.tlsIdentity = tlsIdentity
- Ensure TLS is used
- Get key and certificate data
- Use the retrieved data to create and store the TLS identity
- Set this identity as the one presented in response to the client’s prompt
Example 7. Create Self-Signed Cert
config.isTlsDisabled = false
val attrs = mapOf(
TLSIdentity.CERT_ATTRIBUTE_COMMON_NAME to "Couchbase Demo",
TLSIdentity.CERT_ATTRIBUTE_ORGANIZATION to "Couchbase",
TLSIdentity.CERT_ATTRIBUTE_ORGANIZATION_UNIT to "Mobile",
TLSIdentity.CERT_ATTRIBUTE_EMAIL_ADDRESS to "noreply@couchbase.com"
)
val tlsIdentity = TLSIdentity.createIdentity(
true,
attrs,
Clock.System.now() + 1.days,
"cert-alias"
)
config.tlsIdentity = tlsIdentity
- Ensure TLS is used.
- Map the required certificate attributes.
- Create the required TLS identity using the attributes. Add to secure storage as 'cert-alias'.
- Configure the server to present the defined identity credentials when prompted.
Example 8. Use Anonymous Self-Signed Certificate
This example uses an anonymous self-signed certificate. Generated certificates are held in secure storage.
- Ensure TLS is used.
This is the default setting. - Authenticate using an anonymous self-signed certificate.
This is the default setting.
Authenticating the Client
In this section
Use Basic Authentication | Using Client Certificate Authentication
| Delete Entry | The Impact of TLS Settings
Define how the server (listener) will authenticate the client as one it is prepared to interact with.
Whilst client authentication is optional, Couchbase Lite provides the necessary tools to implement it. Use the
URLEndpointListenerConfiguration
class’s
authenticator
property to
specify how the client-supplied credentials are to be authenticated.
Valid options are:
- No authentication — If you do not define a
ListenerAuthenticator
then all clients are accepted. - Basic Authentication — uses the
ListenerPasswordAuthenticator
to authenticate the client using the client-supplied username and password (from the http authentication header). ListenerCertificateAuthenticator
— which authenticates the client using a client supplied chain of one or more certificates. You should initialize the authenticator using one of the following constructors:- A list of one or more root certificates — the client supplied certificate must end at a certificate in this list if it is to be authenticated
- A block of code that assumes total responsibility for authentication — it must return a boolean response (
true
for an authenticated client, orfalse
for a failed authentication).
Use Basic Authentication
Define how to authenticate client-supplied username and password credentials. To use client-supplied certificates instead — see Using Client Certificate Authentication
Example 9. Password authentication
Where username
/password
are the client-supplied values (from the http-authentication header) and
validUser
/validPassword
are the values acceptable to the server.
Using Client Certificate Authentication
Define how the server will authenticate client-supplied certificates.
There are two ways to authenticate a client:
- A chain of one or more certificates that ends at a certificate in the list of certificates supplied to the constructor
for
ListenerCertificateAuthenticator
— see Example 10 - Application logic: This method assumes complete responsibility for verifying and authenticating the client — see
Example 11
If the parameter supplied to the constructor forListenerCertificateAuthenticator
is of typeListenerCertificateAuthenticatorDelegate
, all other forms of authentication are bypassed.
The client response to the certificate request is passed to the method supplied as the constructor parameter. The logic should take the form of a function or lambda.
Example 10. Set Certificate Authorization
Configure the server (listener) to authenticate the client against a list of one or more certificates provided by
the server to the ListenerCertificateAuthenticator
.
// Configure the client authenticator
// to validate using ROOT CA
// validId.certs is a list containing a client cert to accept
// and any other certs needed to complete a chain between
// the client cert and a CA
val validId = TLSIdentity.getIdentity("Our Corporate Id")
?: throw IllegalStateException("Cannot find corporate id")
// accept only clients signed by the corp cert
val listener = URLEndpointListener(
URLEndpointListenerConfigurationFactory.newConfig(
// get the identity
collections = collections,
identity = validId,
authenticator = ListenerCertificateAuthenticator(validId.certs)
)
)
- Get the identity data to authenticate against. This can be, for example, from a resource file provided with the app, or an identity previously saved in secure storage.
- Configure the authenticator to authenticate the client supplied certificate(s) using these root certs. A valid client will provide one or more certificates that match a certificate in this list.
- Add the authenticator to the listener configuration.
Example 11. Application Logic
Configure the server (listener) to authenticate the client using user-supplied logic.
// Configure authentication using application logic
val corpId = TLSIdentity.getIdentity("OurCorp")
?: throw IllegalStateException("Cannot find corporate id")
config.tlsIdentity = corpId
config.authenticator = ListenerCertificateAuthenticator { certs ->
// supply logic that returns boolean
// true for authenticate, false if not
// For instance:
certs[0].contentEquals(corpId.certs[0])
}
- Get the identity data to authenticate against. This can be, for example, from a resource file provided with the app, or an identity previously saved in secure storage.
- Configure the authenticator to pass the root certificates to a user supplied code block. This code assumes complete
responsibility for authenticating the client supplied certificate(s). It must return a boolean value; with
true
denoting the client supplied certificate authentic. - Add the authenticator to the listener configuration.
Delete Entry
You can remove unwanted TLS identities from secure storage using the convenience API.
The Impact of TLS Settings
The table in this section shows the expected system behavior (in regards to security) depending on the TLS configuration settings deployed.
Table 1. Expected system behavior
isTlsDisabled | tlsIdentity (corresponding to server) | Expected system behavior |
---|---|---|
true |
Ignored | TLS is disabled; all communication is plain text. |
false |
Set to null |
|
false |
Set to server identity generated from a self- or CA-signed certificate
|
|
Start Listener
Once you have completed the listener’s configuration settings you can initialize the listener instance and start it running — see Example 13.
Example 13. Initialize and start listener
// Initialize the listener
val listener = URLEndpointListener(
URLEndpointListenerConfigurationFactory.newConfig(
collections = collections,
port = 55990,
networkInterface = "wlan0",
enableDeltaSync = false,
// Configure server security
disableTls = false,
// Use an Anonymous Self-Signed Cert
identity = null,
// Configure Client Security using an Authenticator
// For example, Basic Authentication
authenticator = ListenerPasswordAuthenticator { usr, pwd ->
(usr === validUser) && (pwd.concatToString() == validPass)
}
)
)
// Start the listener
listener.start()
Monitor Listener
Use the listener’s status
property to get
counts of total and active connections — see Example 14.
You should note that these counts can be extremely volatile. So, the actual number of active connections may have
changed, by the time the ConnectionStatus
class returns a
result.
Example 14. Get connection counts
Stop Listener
It is best practice to check the status of the listener’s connections and stop only when you have confirmed that there are no active connections — see Example 15.
Note
Closing the database will also close the listener.