3 Authorization
Once a principal has been authenticated, it may have access to and usage of services corresponding to its level of privilege. This implies
that the node must be able to map some set of protected services to each
principal--this is the security policy of the node--and keep track of
how services may be used by that principal.
For access, we define a way to formulate such as policy based on sets
in [7, 1]. Each level of privilege is defined by the
set of principals which have the same privilege level, and each service is
associated with some privilege level. Privilege levels may be overlapping.
For instance, in the government, those with top secret clearance may look at
all classified documents, those with secret clearance may see a subset of
these, and finally those with confidential clearance may see a subset of
these. In our model, top secret clearance would be defined by some set of
users TS, secret clearance would be TS È S, and confidential
clearance would be TS È S È C. This way, a service which is
associated with top secret clearance may only be accessed by those in the
set TS; however, a service with secret clearance may be accessed by those
in S and TS. In this case, the sets are concentric, but other
approaches are also possible.
For the latter, we leave it to the service to define sets indicating how the
service may be used. This may be formed in a way similar to the access
policy, but rather than storing which services may be access, we store
parameters indicating how that service may be used. The form of these
parameters depends on the implementation of the service.
Using this architecture implies that the node must be able to determine to
which privilege set a user belongs, and the node must be able to determine
the association between services (or usage parameters) and privilege sets.
How these determinations are made is a matter of implementation.
Authorization data for a node could be stored monolithically---that
is, the node maintains all security information internally to itself---or,
hierarchically---it shares the task of storing the information with
other, trusted nodes---or, ephemerally---it doesn't store any
information directly, but instead relies on certificates from trusted
sources which describe privilege on a per-use basis. We have structured the
PLAN implementation to be independent of the approach taken, or the means by
which the approach is implemented.
In addition to access to privileged services, the use of those
services may also be a matter of privilege. For example, a service which
provides persistent storage on the node may be available to privileged
users, and the amount of storage allowed to those users may also vary
according to privilege.
To implement the security policy of the node, one must write some code that
implements the POLICY signature (in the file security/policy.ml):
(* Stuff particular to an implementation *)
module type POLICY =
sig
(* Type for the user's key *)
type key = Dsa.public_key
type privkey = Dsa.private_key
type cert = string
(* For default policies *)
val default_principal : key
val default_principal_privkey : privkey
(* Namespace authorization action *)
val construct_all_service_lists : unit -> string list
val construct_service_lists : key -> (cert list) option ->
(string list * string list)
(* Per-service authorization action *)
val query_service_parameter: key -> string -> string ->
(cert list) option -> string
(* Initialization *)
val init : string -> int -> key -> privkey -> unit
end
The implementation of this signature provides privilege-based service access
and use. The first three type definitions indicate that public and private
keys are implemented using DSA, and that certificates are in string form.
The next two definitions define the principal corresponding to the default
(unauthenticated) user. The next three definitions indicate the
authorization actions. The first two functions deal with service access
management while the third is for per-usage queries. The first function,
construct_all_service_lists, returns all services which are
privileged beyond the default user; this is used by the node to prevent
these services from being added to the default service symbol table at
startup.3 The second function,
construct_service_lists, is called to alter the namespace with the
additional services allowed to the authenticated principal and those
restricted from him (usually this will be one or the other, but not both).
This function may optionally take a list of certificates to provide further
information. The third function, query_service_parameter, is used
to return service parameters: given a user's public key, service name, and
service parameter name, it returns, in string form, the value of the
specified parameter. Finally, the initialization function, init,
initializes the node policy, given the policy file, port number, and public
and private keys of the node.
In the PLAN distribution, we provide two implementations of this signature,
one using a monolithic approach, and another allowing all possible policy
storage strategies, using the Query Certificate Manager (QCM).
3.1 Monolithic Approach
By default, PLAN is compiled to use our monolithic, set-based approach to
policy management. It is not expected that this approach scales well to
real-world situations; it was designed as a testbed for ideas. In fact, we
have also developed a translator to QCM for policy files written for the
monolithic approach. We present a basic example here.
The form of the policy file is as a list of set definitions with key
associations. For example:
p1 = ( print ) + { "public.key.3324" };
creates a set named p1 that specifies that the print service
routine is privileged and may be accessed by the prinicipal whose public key
is stored in file public.key.3324, implying that it is unavailable by
default. By contrast:
p1 = ( print ) - { "public.key.3324" };
indicates that the print service is not available to the given
principal, implying that it is available by default. This is useful for our
active firewall, discussed in Section 3.3.2. Finally, sets can be
specified in relation to each other, as in
p1 = ( print ) + { "public.key" };
privileged = ( thisHost ) union p1 +
{ "public.key.3325", "public.key.3324" };
This indicates that the privilege afforded to prinicipals associated with
set privileged is a superset of the privilege afforded to those
associated with set p1.
This approach does not currently implement usage parameters; thus the
function query_service_parameter is not implemented. This is,
however, implemented with QCM as explained below.
3.2 Query Certificate Manager
Also provided with the distribution is some code which implements the POLICY signature using the Query Certificate Manager (QCM). QCM is a
natural choice for a policy implementation because it is based on sets, and
supports all three implementation approaches described above (monolithic,
hierarchical, and ephemeral). Please see the QCM documentation in the
distribution for more specific information about QCM; it's in plan/qcmlib/docs. By default, plan is built with QCM support; if you
wish to not use QCM, you should comment out the line
USE_QCM = true
in the Makefile before building plan.
The QCM policy checker is implemented in the file security/policy_qcm.ml. The implementation strategy for access is to
define a QCM program with a set named acl. This set consists of a
list of three-tuples consisting of
-
a set of keys indicating the principals this tuple applies to
- a thicken set of privileged services to add to the principals'
environment
- a thin set of services to subtract from the principals' environment
Using this approach, the last example in the previous section could be
encoded in QCM as:
online
{
p1 = {
Principal(<PublicKey="...">)
};
p10 = {"print"};
privileged = {
Principal(<PublicKey="...">)
};
privileged0 = {"thisHost"};
acl = {
( p1, p10, {} ),
( privileged, union ( privileged0, p10 ), {} )
};
}
Note that QCM programs may refer to QCM programs on other nodes to implement
a distributed, hierarchical program. Details are omitted here; see the QCM
documentation for more.
For usage-based security, you simply add set definitions such that the name
of the set is the name of the service to parameterize. The elements of the
set are two-tuples of the form
-
a set of principals (as in the acl)
- a labeled record of length 1 with the label corresponding to
the parameter name
To have multiple parameters per service, you add multiple
records.4
As an example, the resident state package implements the ability for
packets to store soft state on the router. PLAN 3.2 allows you to create a
policy about how much particular users are allowed to store. For example,
the above program might be extended as follows:
default = {
Principal(<PublicKey="...">)
};
resident = { ( default, <amount=100> ),
( privileged, <amount=1000> ) };
This indicates that all of the principals in default and privileged are allowed to store 100 and 1000 words of information,
respectively, at any one time. The value of the default key should match
Policy_impl.default_principal (see security/policy_qcm.ml for
the exact string value). This policy is enforced by the resident state
implementation itself. If we look in the file resident/resident_svc_impl.ml at the implementation of the function put, we see5
exception TooMuch
(* check_put: 'a -> string *)
(* Code to implement restriction of service policy *)
let check_put v =
let whokey =
(match (Auth.get_principal()) with
Some k -> k
| None -> Policy_impl.default_principal) in
let who = Dsa.string_of_public_key whokey in
let max_amount = int_of_string
(Policy_impl.query_service_parameter
whokey "resident" "amount" None) in
let current_amount = space_used who in
let requested_amount = Obj.size (Obj.repr v) in
if (current_amount + requested_amount) > max_amount then
raise TooMuch
else
who
let put key v timeout =
let who = check_put v in
(* Actual service code ... *)
The first thing that happens in put is that the function check_put is called to determine whether the action is permitted. It first
checks who the current principal is from the Auth.get_principal()
call; this value is stored after a packet does an authEval(). If no
authEval has taken place, get_principal returns None and
so the default principal is used. The next thing is that the policy is
queried using Policy_impl.query_service_parameter to determine the
maximum allowable amount for this prinicpal. Then, the code looks up in the
state table how much this principal currently has stored and if that
combined with the requested amount is less than the value dictated by
policy, then the operation is permitted (returning the current principal to
be used by put). Otherwise, an exception is raised.
In the next subsection we provide an example implementation and service for
use with authapp to demonstrate the use of QCM in the monolithic
setting.
3.3 Authorization Tutorial
We present three demonstrations of authorization. First, we present the use
of privilege-expansion so that a privileged packet may have access to a
greater number of services. Next we present the use of
privilege-restriction to limit the default services available to a packet;
this allows us to implement an active firewall. Finally, we present
usage-based security using resident state, as described above.
3.3.1 Application Privilege Expansion
As a demonstration of namespace-expansion authorization, we have provided a
sample QCM program in the file expand.qcm in the plan directory:
online
{
enhancements = {
Principal(<PublicKey="P: iuWJZ7KVoRbhoCDrDqUSo7pd/hsYFvs9E5rk2
JYB2W7M06F2ughbk8zaIzgiVriopSMhZziJXyvs6fla/6QzRIwMwhLtTUzAIQZYDUD
duJjClHZzU8lN7FM4jGNyq3wOn/uH/L9i3tJGm/V9ywfqqUFNF0O2amlxhM1s1JMci
kM=, Q: ledWdPQsmKTqyynTCBn0vWlhFeM=, ALPHA: D7CquhERwI22+bW0JfY9o
D+tLiV7+0p+5y12Mvx/RYsQgwmA1BUD6PqyZ54lXRh3hm6k/AM9zvYXbcG8Dx49uq6
jQXHdjQCK2c8rlztrjplJvPdG5bSMTZzbVJAY1oDM9sugFGmMJLvizqFsZfJsrE13P
pOTNc7LRS8b26NodUM=, Y: Ow++XUNYt5JnxuQ4WgjvESddl+CKV6vEE6SGSu+mbI
ubArSzYUV2OBYdMU6uqZSaPxX0lSIDrHC+Tvq4ckNU+U5LciYDydgfUxFQQXbLInwS
UI2iaus9jGVP5Px8gASIkhy0eKQg45qZHySalBpuA6AyDlHqRMIR7IK8zV2XKcQ=">)
};
enhancements0 = {"authSvc"};
acl = {
( enhancements, enhancements0, {} )
};
}
It defines a single set named enhancements with a single member (which
is the pair of keys in the file key used for authapp in the last
tutorial). Then this user authenticates on the node using this policy, the
service authSvc will be added to his namespace. This service is
defined in security/crypto_svc_impl.ml as the function auth_svc, and always returns the value true.
We can try this service out with a slight modification to authapp. In
the file security/authapp.ml, on line 266 you'll find three blocks of
PLAN code:
(let chunk_code = Printf.sprintf
"svc print : 'a -> unit
svc toString : 'a -> string
svc getHostByName : string -> host
svc defaultRoute : host -> host * dev
svc getRB : void -> int
svc thisHost : void -> host list
fun doit() =
OnRemote(|print|(toString(hd thisHost()) ^
\" authenticated successfully\"),
getHostByName(\"%s\"),getRB(),defaultRoute)"
(* "svc print : 'a -> unit
svc getHostByName : string -> host
svc defaultRoute : host -> host * dev
svc getRB : void -> int
svc authSvc : void -> bool
fun doit() = OnRemote(|print|(authSvc()),
getHostByName(\"%s\"),getRB(),
defaultRoute)"
"svc print : 'a -> unit
svc toString : 'a -> string
svc put : (string,key,'a,int) -> unit
svc generateKey : void -> key
svc thisHost : void -> host list
svc getHostByName : string -> host
svc defaultRoute : host -> host * dev
svc getRB : void -> int
fun store(i:int list) =
let val session = generateKey()
fun mark(a: int, f:unit) =
put(\"mark\"^toString(a),session,true,20)
in
(foldr(mark,i,());
OnRemote(|print|(toString(hd thisHost()) ^
\" stored data successfully\"),
getHostByName(\"%s\"),getRB(),defaultRoute))
end
fun doit() = store([1;2;3;4;5])" *)
(Activehost.activeHostToString my_host_addr) in
Comment out the first block of PLAN code (a string) and uncomment the
second:
(let chunk_code = Printf.sprintf
(* "svc print : 'a -> unit
svc toString : 'a -> string
svc getHostByName : string -> host
svc defaultRoute : host -> host * dev
svc getRB : void -> int
svc thisHost : void -> host list
fun doit() =
OnRemote(|print|(toString(hd thisHost()) ^
\" authenticated successfully\"),
getHostByName(\"%s\"),getRB(),defaultRoute)" *)
"svc print : 'a -> unit
svc getHostByName : string -> host
svc defaultRoute : host -> host * dev
svc getRB : void -> int
svc authSvc : void -> bool
fun doit() = OnRemote(|print|(authSvc()),
getHostByName(\"%s\"),getRB(),
defaultRoute)"
(* "svc print : 'a -> unit
svc toString : 'a -> string
svc put : (string,key,'a,int) -> unit
svc generateKey : void -> key
svc thisHost : void -> host list
svc getHostByName : string -> host
svc defaultRoute : host -> host * dev
svc getRB : void -> int
fun store(i:int list) =
let val session = generateKey()
fun mark(a: int, f:unit) =
put(\"mark\"^toString(a),session,true,20)
in
(foldr(mark,i,());
OnRemote(|print|(toString(hd thisHost()) ^
\" stored data successfully\"),
getHostByName(\"%s\"),getRB(),defaultRoute))
end
fun doit() = store([1;2;3;4;5])" *)
(Activehost.activeHostToString my_host_addr) in
Then recompile authapp.
Now, we must start node m1 slightly differently, so that it will use expand.qcm. To start it, do:
bin/pland -l log3324 -ip 3324 -policy expand.qcm
m1
Now start authapp as before. After it has authenticated with the
remote node, rather than simply authenticating and printing the message
``authenticated successfully,'' the program will access this service which
requires privilege. If the principal authenticated with is in the
privileged set, the message true will be printed as the last of authapp's output; this should happen if you specify key as the app's
keys as in the last tutorial:
bin/authapp -p 3324
m key
To see the authorization
fail, have authapp use the keys in key.3325, also present in the
plan directory:
bin/authapp -p 3324
m key.3325
In this case, ERROR:
will be printed in authapp, and
the log file for m1
will contain:
Call: No such function authSvc
Eval thread 4 exiting with failure:
Uncaught packet exception ServiceNotPresent
3.3.2 An Active Firewall
The active firewall is described in more detail in [7]. The
basic idea is that it wraps packets coming in from a protected interface
with a chunk that evaluates the original chunk associated with a guest principal. The local policy restricts the namespace of this guest
principal, thus limiting the effect of external packets.
Figure 1: Firewall Tutorial: Shows three nodes:
m1 is the source node, inside the trusted network; m2 is
the firewall; and m3 is the unstrusted node. Each connection is
labelled by the device name and that device's address, as specified in the
interface configuration files for the three respective nodes.
To demonstrate the use of the firewall, we need to set up a three-node
network, as depicted in Figure 1. Node m3
in the
untrusted network is started as normal:6
bin/pland -l log3326 -ip 3326
m3
where m3
contains:
1
ip0 ip
m:3326
m:3327
The middle node, m2
, serves as the firewall. It must be started
with one additional parameter:
bin/pland -l log3325 -ip 3325 -firewall ip0
m2
where m2
contains:
2
ip0 ip
m:3325
m:3324
ip1 ip
m:3327
m:3326
The -firewall parameter indicates that pland should function as
a firewall rather than a router. The arguments given to this option specify
which devices are being protected by the firewall. All packets that are
emitted via these devices are first wrapped with a privilege-reducing
chunk. Here we specify ip0 as that is the device that leads to the
trusted network (see the Figure).
Finally, the trusted node m1
is started as:
bin/pland -l log3324 -ip 3324 -policy firewall.qcm -authlist
m:3325
m1
where m1
contains:
1
ip0 ip
m:3324
m:3325
There are two additional parameters. The first parameter -policy
takes a policy file that specifies which services should be removed from the
the guest environment. For this tutorial, we use the file firewall.qcm:
online
{
restrictions = {
Principal(<PublicKey="...">)
};
restrictions0 = {"DevNotLocal", "FlowNoRoute", "getToll", "setTo
ll", "flowSet", "flow", "RIPNoRoute", "RIPGetRoutes", "RIPRecv", "
getNeighbors", "RIPGetNeighbors", "RIP", "DHmessageThree", "DHmess
ageTwo", "DHmessageOne", "authSvc", "uncompress", "compress", "set
Percent", "setUncompDelay", "setCompDelay", "getHostByName", "Load
GenNotStarted", "completeFlow", "startFlow", "stopSender", "demoSh
owHop", "flowUseNewFlow", "flowNewFlowReady", "flowNewScout", "flo
wGo", "flowKeyReady", "flowStartToDemo", "flowStartToPort", "flowS
tart", "startSenderToDemo", "startSenderToPort", "startSender", "s
tartReceiverDemo", "NonLocalHost", "delete", "genKeyHost", "genera
teKey", "setGT", "setLT", "put", "get", "BindFailed", "NoBinding",
"retrieveBinding", "bind", "openPort", "abort", "evalBlob", "eval
", "getMTU", "getDevs", "getSrcDev", "getStaticRoutes", "staticRou
te", "remove", "member", "length", "gettimeofday", "timestamp", "t
hisHost", "getSessionKey", "getSrc", "getSource", "logGC", "queryG
C", "exit"};
acl = {
( restrictions, {}, restrictions0 ),
};
}
This defines s set of principals called restrictions. The sole member
of the restrictions set is the firewall's public key (as stored in the
file key.3325. This set is stored in the ACL with the thin set as
defined by restrictions0. To protect the trusted node using this
policy, the firewall wraps incoming packets with an authEval that
changes the identity to that of the firewall (serving as the guest
identity). Packets running with this principal will have the thinned
environment. In order for the firewall to sign incoming packets, the
trusted host must authenticate with the firewall. This is done via the -authlist parameter, which takes a comma-separated list of nodes to be
authenticated with. The firewall is specified in this list.
Once all of these nodes are started, m1
will authenticate with the
m2
, just as we saw in Section 2.3.2. Once this
authentication is complete, the stage is set to try running some packets.
To show how privilege is restricted, we send the Helloworld program
from m3
to m1
through the firewall. The policy specified
in firewall.qcm indicates that the thisHost service should be
removed. Indeed, when we do:
bin/inject -p 3326 -ed
m:3324 interp_tests/Helloworld.plan 10
doit();
The result is
1.1-1.6: Warning: service doit not declared.
ERROR: <chunk:
doit[]> raised ServiceNotPresent on (127.0.0.1,3324)
and at the end of the log file for m1
we see
Call: No such function thisHost
Eval thread 1 exiting with failure:
Uncaught packet exception ServiceNotPresent
On the other hand, if we run a program, such as ping, that does not
use any thinned services (e.g. thisHost) on m1
, things should
work OK. Indeed, if we run bin/planping:
bin/planping
m:3326
PING 0 483.964920 ms
PING 1 21.622062 ms
PING 2 21.566033 ms
...
3.3.3 Privileged Usage
As described in the previous section, we have implemented privileged usage
for the resident state package. In this section we demonstrate the
how privileged service usage works using this service suite. The policy
file that we use for m1
in this case is usage.qcm:
online
{
privileged = {
Principal(<PublicKey="P: iuWJZ7KVoRbhoCDrDqUSo7pd/hsYFvs9E5rk2
JYB2W7M06F2ughbk8zaIzgiVriopSMhZziJXyvs6fla/6QzRIwMwhLtTUzAIQZYDUD
duJjClHZzU8lN7FM4jGNyq3wOn/uH/L9i3tJGm/V9ywfqqUFNF0O2amlxhM1s1JMci
kM=, Q: ledWdPQsmKTqyynTCBn0vWlhFeM=, ALPHA: D7CquhERwI22+bW0JfY9o
D+tLiV7+0p+5y12Mvx/RYsQgwmA1BUD6PqyZ54lXRh3hm6k/AM9zvYXbcG8Dx49uq6
jQXHdjQCK2c8rlztrjplJvPdG5bSMTZzbVJAY1oDM9sugFGmMJLvizqFsZfJsrE13P
pOTNc7LRS8b26NodUM=, Y: Ow++XUNYt5JnxuQ4WgjvESddl+CKV6vEE6SGSu+mbI
ubArSzYUV2OBYdMU6uqZSaPxX0lSIDrHC+Tvq4ckNU+U5LciYDydgfUxFQQXbLInwS
UI2iaus9jGVP5Px8gASIkhy0eKQg45qZHySalBpuA6AyDlHqRMIR7IK8zV2XKcQ=">)
};
acl = {
( privileged, {}, {} )
};
# usage-based security
# this key matches Policy_qcm.Policy_impl.default_principal
default = {
Principal(<PublicKey="P: 6e+1xVU3D5dHFrZo+Cd2YsR/CxvFVcDyAImn5
nESz17GjBurur3xrEVj7XV+wpS7N2XtZ0QPeNoUId1RRXAY2qDK8Cc7WyofsomqCzW
5Sdz4d4sa45J2ur/+pdiZtQEnfGoYd15mUPhShE5BtfaYOVrBzrfX7pqIPLhq476zo
bs=, Q: 2dfTnTvli0m4HzCh9rlIaKT2alk=, ALPHA: OXmMrRFwHdRY2i/ya7d1m
urBUkhJwc+H4lJTpZ7LJNKcOs3+HolntEwtWiO5B4OwwSO2Pl5B7azZxm23zFXUJVX
QATGEH13XsX+BEaxrIMj6Vh+dOw5B3286wjhAm/lgyglrSsl20BNqNeW42zTMCoYQe
Frs+vI4Z10Zx8yCMmY=, Y: D+4Mymn0id6KMo0gZjappc1Azd/qM3LI2A8aaTERym
YQe0bqULkLxX/SiYkGaLZNJ9LJ2Iy9EQGiG2NPEaC+nENHnYYDfSKOXjaaeCOaulRE
kyOQVK5qvvXql3lYAbinnBMRrJx5nZoL2Te1VAW/KY5JNM9riUSp6KdKKnT+dE4=">)
};
resident = { ( default, <amount=4> ),
( privileged, <amount=1000> ) };
}
In this file, two principal sets are defined: one contains the default
principal (matching that defined in Policy_qcm.Policy_impl.default_principal) and the other contains a
privileged principal. The policy is implemented by the resident set:
it specifies that the default principal may store (at any one time) at most
4 words of soft state, while the privileged user may store 1000
words.7
To demonstrate the use of this policy, we've provided a PLAN program interp_tests/store.plan which stores the value true on
the node 5 times, exceeding the policy for the default user, but well under
it for the privileged one. This code has also been incorporated into authapp; you need to uncomment the last region of code:
(let chunk_code = Printf.sprintf
(* "svc print : 'a -> unit
svc toString : 'a -> string
svc getHostByName : string -> host
svc defaultRoute : host -> host * dev
svc getRB : void -> int
svc thisHost : void -> host list
fun doit() =
OnRemote(|print|(toString(hd thisHost()) ^
\" authenticated successfully\"),
getHostByName(\"%s\"),getRB(),defaultRoute)" *)
"svc print : 'a -> unit
svc getHostByName : string -> host
svc defaultRoute : host -> host * dev
svc getRB : void -> int
svc authSvc : void -> bool
fun doit() = OnRemote(|print|(authSvc()),
getHostByName(\"%s\"),getRB(),
defaultRoute)" *)
"svc print : 'a -> unit
svc toString : 'a -> string
svc put : (string,key,'a,int) -> unit
svc generateKey : void -> key
svc thisHost : void -> host list
svc getHostByName : string -> host
svc defaultRoute : host -> host * dev
svc getRB : void -> int
fun store(i:int list) =
let val session = generateKey()
fun mark(a: int, f:unit) =
put(\"mark\"^toString(a),session,true,20)
in
(foldr(mark,i,());
OnRemote(|print|(toString(hd thisHost()) ^
\" stored data successfully\"),
getHostByName(\"%s\"),getRB(),defaultRoute))
end
fun doit() = store([1;2;3;4;5])"
(Activehost.activeHostToString my_host_addr) in
Let us first run this as the default user. Start up the node, using the
usage.qcm policy:
bin/pland -l log3324 -ip 3324 -policy usage.qcm
m1
Then, inject the store.plan program with initial invocation doit:
bin/inject -p 3326 -ed
m:3324 interp_tests/Helloworld.plan 10
doit();
The result is
1.1-1.6: Warning: service doit not declared.
ERROR: <chunk:
doit[]> raised InsufficientPrivilege on (127.0.0.1,3324)
and at the end of the log file for m1
we see
Principal |P: 6e+1xVU3D5dHFrZo+Cd2YsR/CxvFVcDyAImn5nESz17GjBurur3x
rEVj7XV+wpS7N2XtZ0QPeNoUId1RRXAY2qDK8Cc7WyofsomqCzW5Sdz4d4sa45J2ur
/+pdiZtQEnfGoYd15mUPhShE5BtfaYOVrBzrfX7pqIPLhq476zobs=, Q: 2dfTnTv
li0m4HzCh9rlIaKT2alk=, ALPHA: OXmMrRFwHdRY2i/ya7d1murBUkhJwc+H4lJT
pZ7LJNKcOs3+HolntEwtWiO5B4OwwSO2Pl5B7azZxm23zFXUJVXQATGEH13XsX+BEa
xrIMj6Vh+dOw5B3286wjhAm/lgyglrSsl20BNqNeW42zTMCoYQeFrs+vI4Z10Zx8yC
MmY=, Y: D+4Mymn0id6KMo0gZjappc1Azd/qM3LI2A8aaTERymYQe0bqULkLxX/Si
YkGaLZNJ9LJ2Iy9EQGiG2NPEaC+nENHnYYDfSKOXjaaeCOaulREkyOQVK5qvvXql3l
YAbinnBMRrJx5nZoL2Te1VAW/KY5JNM9riUSp6KdKKnT+dE4=|
Allowed=4
Current=4
Request=1
Eval thread 4 exiting with failure:
Uncaught packet exception InsufficientPrivilege
Now let's try this program with greater privilege. Start authapp as
before, using the keyfile key. Now, after it has authenticated with
the remote node, it will run the store program, but it will complete
successfully, printing on authapp:
127.0.0.1:3324 stored data successfully
3.4 Implementation Notes
To use QCM in a hierarchical fashion, it must be possible for multiple PLAN
evaluations to execute concurrently. Due to overheads in the runtime
system, this causes a significant performance hit, so this functionality is
not enabled by default. To enable it, you need to recompile plan with the
-DMULTI_EVAL flag added to the CAMLP4FLAGS variable in the
Makefile.
QCM has its own notion of the node's identity as specified in the QCM
program at startup. If a public/private key pair is specified in the QCM
program, it will be overridden by any keys given explicitly to pland
at startup.