Teach Yourself Glacier2 in 10 Minutes
This article provides a short and simple introduction to Glacier2. The claim that you will be able to learn Glacier2 in ten minutes is something of an exaggeration: to learn Glacier2 thoroughly, you will have to invest more than ten minutes. However, you can learn the basics of Glacier2 in the time it takes to read this article, which is about ten minutes.
On this page:
What is Glacier2?
Glacier2, in a nutshell, is a network proxy service for Ice: it allows Ice servers to sit behind a corporate firewall, such that clients in the outside world can use these servers.
Glacier2 is a simple program; at its core, Glacier2 is an Ice server that receives incoming requests from clients and passes them on as blobs of bits to servers. This is quite similar to the functioning of an IP router that receives packets on one interface and forwards them via another interface. This simplicity not only makes Glacier2 easy to configure, but it also makes it much more likely that Glacier2 is secure. (Lower complexity means fewer bugs, not to mention better performance.) In particular, Glacier2 does not depend for its security on the integrity and correct configuration of other components, such as a web server. Glacier2 can also require clients to connect via SSL and reject insecure TCP connections. (Glacier2 does not support UDP.)
Glacier2 Feature Highlights
Here are some of the main distinguishing features of Glacier2:
- You need to open only a single port in the corporate firewall for any number of Ice servers behind the firewall.
- Glacier2 can be configured to only accept SSL connections.
- Glacier2 does not require any configuration that would need to change as applications change. In particular, Glacier2 does not require knowledge of the Slice definitions used by the back-end servers.
- Clients require only minimal source code changes in order to work with Glacier2.
- Servers do not require any source code changes in order to work with Glacier2.
- Callbacks from servers to clients do not require the client's firewall to permit incoming connections.
In addition to the above highlights, Glacier2 also offers a number of advanced features:
- Access control, which allows you to add additional security controls beyond those provided by SSL, such as authentication with passwords or SSL certificates.
- Integration hooks for custom authentication mechanisms.
- Filters for restricting the addresses and ports a client can access on the internal network. Filters can also be used to limit client requests to specific object adapters or objects.
- Session management for recovering resources associated with clients that do not disconnect in an orderly fashion. This includes hooks that you can use to integrate Glacier2's session management with application-specific functionality, for example, to establish and clean up per-client contextual information.
As befits an introductory article, we will focus on getting started with Glacier2 and leave you to check the Ice Manual for details on the advanced features.
Getting Started with Glacier2
Before your clients and servers can communicate via Glacier2, you must configure your firewall and configure and run Glacier2.
Configuring Your Firewall for Glacier2
Chances are that you will already have a firewall that is configured to disallow incoming connections (except for a number of selected services, such as web and e-mail traffic). To allow Glacier2 to work with your firewall, you must configure the firewall to open a single TCP port and forward all traffic for that port to the machine on which Glacier2 runs. Exactly how you achieve this depends on your firewall. However, most firewalls have an administrative interface that allows you to easily add a rule that essentially says "forward all incoming TCP traffic on port 4064 to port 4064 on machine glacier2.zeroc.com
." The figure below illustrates this situation:
Glacier2 Behind a Firewall
We suggest that you use port 4064 as the incoming SSL port for Glacier2 and, if you want to allow client access via TCP, that you use port 4063 as the incoming TCP port for Glacier2. These two ports are reserved for Glacier2 by IANA (Internet Assigned Numbers Authority), so you can be reasonably sure that they are not used by some other service. For this article, we will assume that the firewall (firewall.zeroc.com
) forwards incoming connections on ports 4063 and 4064 to the same ports on the internal machine glacier2.zeroc.com
, which runs Glacier2. (The internal machine need not run Glacier2 on these ports but, seeing that they are reserved for Glacier2, you might as well use them.)
Running Glacier2
Glacier2 is provided as the command glacier2router
in the Ice distribution. The simplest way to run Glacier2 is as follows:
glacier2router --Glacier2.Client.Endpoints="tcp -h glacier2.zeroc.com -p 4063" \ --Glacier2.PermissionsVerifier=Glacier2/NullPermissionsVerifier
The property Glacier2.Client.Endpoints
configures the port at which Glacier2 listens for client TCP requests. In this case, it listens only on the interface bound to glacier2.zeroc.com
's IP address on port 4063. As mentioned earlier, Glacier2 can also be configured to require authentication from clients. The property Glacier2.PermissionsVerifier
determines the authentication mechanism. Glacier2 ships with a built-in null permissions verifier that allows anyone to connect — the object identity Glacier2/NullPermissionsVerifier
selects this "allow anyone" verifier. (We will discuss other authentication options shortly.)
Running a Server with Glacier2
On the server side, no configuration is required at all: to use a server with Glacier2, you simply start the server with the same configuration as you would without Glacier2.
Running a Client with Glacier2
For a client, we need to make minor source code changes to allow the client to use Glacier2. Specifically, clients must establish a session with Glacier2 to have their requests forwarded to servers. On start-up, the client needs to execute the following code:
shared_ptr<Ice::RouterPrx> r = communicator->getDefaultRouter(); shared_ptr<Glacier2::RouterPrx> router = Ice::checkedCast<Glacier2::RouterPrx>(r); shared_ptr<Glacier2::SessionPrx> session; try { session = router->createSession("", ""); } catch(const Ice::Exception& ex) { cerr << "Cannot create session:" << ex << endl; }
The call to createSession
expects a user name and password. Because we are using the null permissions verifier (for the moment), any user name and password will do, so the code passes empty strings. This code establishes the session that allows the client to communicate with the server via Glacier2.
The client should destroy the session before terminating:
try { router->destroySession(); } catch(const Ice::Exception& ex) { cerr << "Cannot destroy session: " << ex << endl; }
You can also configure Glacier2 to automatically destroy idle sessions by setting the property Glacier2.SessionTimeout
to the idle time in seconds. We strongly recommend setting this property to ensure correct cleanup in the event of a client crash. Even with this feature enabled, it is still a good idea for the client to explicitly destroy its session to ensure timely clean-up of resources inside Glacier2. (And, if you do not configure a session timeout, sessions last indefinitely.)
The above code is all that is necessary to make a client cooperate with Glacier2. You can bundle this code into utility functions and then reuse it in your clients so that they automatically use Glacier2. In fact, several Ice language mappings provide helper classes to simplify the task of creating reliable Glacier2 applications.
At a minimum, a Glacier2 client requires the following configuration settings:
# Client configuration Ice.Default.Router=Glacier2/router:tcp -h firewall.zeroc.com -p 4063 Ice.RetryIntervals=-1
The property Ice.Default.Router
configures a default router for the client. Setting this property causes all client requests to be sent to the object specified by that property, instead of being sent to the endpoint that is inside the proxy that a client uses to make an invocation. In effect, the property says "send all invocations to the specified object, instead of sending them as you normally would." The host and port for this property must point at the firewall, which port forwards all traffic to Glacier2 on the host and port set by Glacier2's Glacier2.Client.Endpoints
property.
Finally, retries do not make sense if a client communicates with a server via Glacier2 because Glacier2 will retry failed requests automatically on behalf of the client. To disable retries, we set Ice.RetryIntervals
to a negative value.
This is all that is needed to get off the ground, at least for this simple scenario: run Glacier2, add the preceding few lines of code to the client, run the server, and run the client with these additional configuration settings.
If you have problems getting things to work, it will almost certainly be due to incorrect endpoint configuration. In particular, the client's setting for Ice.Default.Router
must point at the firewall and the firewall must forward to the host and port defined by Glacier2.Client.Endpoints
. You can set Ice.Trace.Network=1
for Glacier2 and the client to check whether connections are being made to the correct address and port.
Better Authentication with Passwords
You can force clients to authenticate themselves with a user name and password when they create a Glacier2 session by leaving Glacier2.PermissionsVerifier
undefined, and instead setting the property Glacier2.CryptPasswords
to the path name of a password file. Doing this activates a built-in permissions verifier to authenticate clients. The password file must contain pairs of user names and encrypted passwords, one pair per line. The client passes the user name and (plain-text) password to createSession
, and Glacier2 allows access only if the supplied password encrypts to the same string that is stored in the password file. Note that if you use this mechanism, you should restrict client access to SSL, otherwise the password will be sent in clear text over the wire.
Using SSL with Glacier2
For security-sensitive applications, you will probably want to use a SSL connection instead of TCP to ensure that no-one can eavesdrop on the traffic between clients and Glacier2. To run Glacier2 with SSL enabled and TCP disabled, you need to set a few additional properties:
# Glacier2 config for SSL Glacier2.Client.Endpoints=ssl -h glacier2.zeroc.com -p 4064 Glacier2.PermissionsVerifier=Glacier2/NullPermissionsVerifier Ice.Plugin.IceSSL=IceSSL:createIceSSL IceSSL.DefaultDir=certs IceSSL.CAs=cacert.pem IceSSL.CertFile=server.p12 IceSSL.VerifyPeer=0
Note that Glacier2 now uses a SSL endpoint. The remaining properties specify that the Ice run time should load the SSL plug-in (Ice.Plugin.IceSSL
) and configure the directory and files that provide the plug-in with the relevant certificate and key information. We are still using the null permissions verifier, so any client can connect, but only via SSL. Because the client is still authenticated via user name and password, it need not provide its own SSL credentials: Glacier2 sets IceSSL.VerifyPeer
to zero to accept such anonymous connections. (This example uses the certificates that accompany the Ice distribution. For a real-world deployment, you would generate your own CA certificate and a unique certificate for the Glacier2 router. See the Ice Manual for more details on configuring the SSL plug-in and generating certificates.)
As before, no changes are required for the server — the server can provide either TCP or SSL endpoints, and Glacier2 will forward client requests to the server as appropriate.
The client must be configured as follows:
# Client configuration Ice.Default.Router=Glacier2/router:ssl -h firewall.zeroc.com -p 4064 Ice.RetryIntervals=-1 Ice.Plugin.IceSSL=IceSSL:createIceSSL IceSSL.DefaultDir=certs IceSSL.CAs=cacert.pem IceSSL.TrustOnly=CN="Server"
The IceSSL.TrustOnly
rule tells the client to connect only to a server whose SSL certificate has the common name Server
. In a real-world deployment, you would use a unique common name for your Glacier2 router, and use that common name instead.
With this configuration, clients communicate with Glacier2 only via SSL, and Glacier2 forwards requests to back-end servers using whatever endpoints (TCP or SSL) are provided by these servers.
Using SSL Connection Credentials with Glacier2
You can use SSL in combination with user name and password authentication exactly as with TCP: leave Glacier2.PermissionsVerifier
undefined, and instead set Glacier2.CryptPasswords
to the path name of the password file. SSL's encryption means the client's password is no longer sent in plain text over the wire when the client calls createSession
.
An alternative way to authenticate clients is to use the credentials that are established for the client's SSL connection. In that case, the client does not need to supply a password — instead, the client calls createSessionFromSecureConnection
, which requires no arguments:
try { session = router->createSessionFromSecureConnection(); } catch(const Ice::Exception& ex) { cerr << "Cannot create session: " << ex << endl; }
For this to work, Glacier2 must be configured slightly differently: instead of setting Glacier2.PermissionsVerifier
or Glacier2.CryptPasswords
, leave these properties undefined and set Glacier2.SSLPermissionsVerifier
instead:
# Glacier2 config for SSL Glacier2.SSLPermissionsVerifier=Glacier2/NullSSLPermissionsVerifier # Other settings as before...
The value Glacier2/NullSSLPermissionsVerifier
allows any client to connect, provided that the SSL connection could be established. If you want to restrict access to specific clients, you need to install a custom permissions verifier.
Custom Permissions Verifiers
You can set Glacier2.PermissionsVerifier
to the proxy of an arbitrary Ice object that you implement in any server that is reachable by Glacier2. The target object must support the Glacier2.PermissionsVerifier
interface, which defines a checkPermissions
operation. For each call to createSession
, Glacier2 invokes your checkPermissions
operation to decide whether the client should be authorized.
Similarly, you can set Glacier2.SSLPermissionsVerifier
to the proxy of an Ice object that implements the Glacier2::SSLPermissionsVerifier
interface, which contains an authorize
operation that Glacier2 invokes when the client calls createSessionFromSecureConnection
.
This facility allows you to implement arbitrary authorization policies, typically by delegating the decision to an authorization mechanism that you already have in place.
Using Callbacks with Glacier2
With the setup we have seen so far, clients can reach servers through the firewall, but servers cannot necessarily reach clients. Doing this is necessary if a client passes a proxy for a callback object to a server. In that case, the client is both client and server and, when the server calls back into the client, they momentarily reverse roles: the server acts as the client, and the client acts as the server.
There is nothing wrong with this as such: if the client is not behind a firewall of its own, the server will simply open a new connection to the client and invoke the callback via that connection. However, chances are that the client will be behind its own firewall, with that firewall disallowing incoming connections.
The solution to this problem is for the server to send the callback request to Glacier2, which forwards it to the correct client via the already-existing connection that was established by the client. That way, the server can reach the client even if the client is behind a firewall that disallows incoming connections, as shown below:
Bidirectional Communication with Glacier2 for Callbacks
To make this setup work, no code or configuration changes are necessary in the server. However, we need to add one additional property setting to Glacier2's configuration:
# Glacier2 config Glacier2.Server.Endpoints=tcp -h glacier2.zeroc.com # Other settings as before...
Setting Glacier2.Server.Endpoints
enables an endpoint in Glacier2 that servers use when they invoke a callback on a client. (Note that you need not specify a port number for this property.) The endpoint you specify here must be accessible on the internal network so that back-end servers can connect to it. This endpoint should not be accessible from the external network to prevent malicious clients from flooding Glacier2's server endpoint with requests.
The million-dollar question is: how does it happen that servers connect to Glacier2's server endpoint when they invoke a callback, instead of attempting to open a separate connection directly to the client? The answer involves two separate components on the client side. The first is that the client must have an additional property setting:
# Client config CallbackAdapter.Router=Glacier2/router:ssl -h firewall.zeroc.com -p 4064 # Other settings as before...
Note the setting of CallbackAdapter.Router
. (We assume that the client's object adapter that hosts the callback object has the name CallbackAdapter
.)
The property configures the client's object adapter with a router, and the specified proxy must point at the firewall. By setting this property, the client's object adapter now publishes the router's server endpoint (instead of the adapter's own endpoint) in the proxies that it creates. This explains how, when the server invokes a callback, it ends up connecting to Glacier2: the client-side run time notices the property setting, asks Glacier2 for the endpoint that Glacier2 provides to servers for callbacks, and puts that server endpoint (which is on the internal network) into the callback proxy. Therefore, the back-end server connects to Glacier2's server endpoint when it invokes the callback.
The second part of the answer deals with how Glacier2 can ensure that callbacks for different clients actually get delivered to the correct client: because each server has only a single connection to Glacier2, but may need to send callbacks to different clients, the identity of the target client is no longer implicit in the server's connection to Glacier2. Instead, the client must provide an identifier that Glacier2 can use to de-multiplex callbacks from back-end servers and forward them to the correct clients.
Glacier2 does this by assigning a unique identifier to each client. In turn, the client is expected to provide that identifier in the category
part of the object identity for its callback objects. For example, suppose the client provides callback objects of interface Callback
to a number of back-end servers. The client must contact Glacier2 once, to obtain the unique category Glacier2 has assigned to the client, and then use that category in the object identity of its callback objects:
// Get category from Glacier2. string myCategory = router->getCategoryForClient(); // Use that category for all callback objects. Identity id; id.category = myCategory; // Create two callback objects with name cb1 and cb2. id.name = "cb1"; adapter->add(new CallbackI, id); id.name = "cb2"; adapter->add(new CallbackI, id);
The Glacier2 helper classes included with Ice make this process quite a bit simpler. For example, the C++ class Glacier2::Application
provides the addWithUUID
method:
namespace Glacier2 { class Application : public Ice::Application { shared_ptr<Ice::ObjectPrx> addWithUUID(const shared_ptr<Ice::Object>& servant); // ... }; }
Although similar in purpose to the ObjectAdapter
operation of the same name, this method offers enhanced functionality for Glacier2 clients:
- It creates an object adapter specifically for hosting callback objects (if the adapter hasn't been created yet);
- It generates an object identity that uses the router-assigned category and a UUID for the name;
- It registers the servant with the callback object adapter and returns a proxy to the new callback object.
We encourage you to use these helper classes whenever possible, or modify them to suit your own needs.
Conclusion
Glacier2 makes it very easy to provide secure access to Ice servers that sit behind a firewall. Once you know Glacier2, you can make a new server available in just a few minutes. The coding effort required to make clients cooperate with Glacier2 is truly minimal: you only need to write a few lines of code once and then can re-use that code in all your clients. Moreover, Ice includes robust helper classes that provide the functionality common to most Glacier2 applications.
If you want to experiment with Glacier2, we suggest you start with the demo that is provided in the Glacier2/callback
directory in the Ice sample programs repository. The demo also illustrates how to configure Glacier2 with a custom permissions verifier, and how to use explicit session management. In addition, we suggest that you take a look at the Glacier2 chapter in the Ice Manual, which provides information on advanced features such as fine-grained access control, integration with IceGrid, and other topics.