Hi Peter, Nigel...
we encountered also the same problem than Michael in our TAO/Pairformance implementation for Intel. The fact is that the key rotation did not run properly in our case on 1.9.3 codebase. There is moreover another case where the "just when necessary" key upgrading does not comply the need : when using, as we do, massive use of XML-RPC data transfers between platform, for data exchange servicing. When adding such services, you barely not use the auth hook that could renew those keys. Keys should be renewed essentially throiugh an explicit remote auth action of a remote user. We cannot wait for that event in our case. So we made such an automated renewal solution :
We implemented it in a local/mnetcron.php hook to not denature standard code (although we rely on an unevitable patch in mnet/lib.php) :
<?php
/// check keys and reniew with peers.
/// requires : patching /mnet/xmlrpc/server.php for mnet_keyswap()
/// requires : patching /mnet/lib.php for mnet_keyswap()
global $MNET;
mtrace("Cron automatic rotation for MNET keys...\n");
if (!isset($CFG->mnet_key_autorenew_gap)) set_config('mnet_key_autorenew_gap', 24 * 3); // three days
// if autorenewal is enabled and we are mnetworking
if (!empty($CFG->mnet_key_autorenew) && $CFG->mnet_dispatcher_mode != 'none'){
include_once $CFG->dirroot."/mnet/peer.php";
include_once $CFG->dirroot."/mnet/lib.php";
// check if key is getting obsolete
$havetorenew = 0;
// key is getting old : check if it is time to operate
if ($MNET->public_key_expires - time() < $CFG->mnet_key_autorenew_gap * HOURSECS){
// this one is needed as temporary global toggle between distinct cron invocations,
// but should not be changed through the GUI
if (empty($CFG->mnet_autorenew_haveto)){
set_config('mnet_autorenew_haveto', 1);
mtrace('Local key is expiring. Need renewing MNET keys...');
} else {
if (!empty($CFG->mnet_key_autorenew_time)){
$now = getdate(time());
if ($now['hours'] > $CFG->mnet_key_autorenew_time){
$havetorenew = 1;
}
} else {
$havetorenew = 1;
}
}
}
// renew if needed
$force = optional_param('forcerenew', 0, PARAM_INT);
if ($havetorenew || $force){
mtrace("Local key will expire very soon. Renew MNET keys now !!...\n");
// make a key and exchange it with all known and active peers
$mnet_peers = get_records('mnet_host', 'deleted', 0);
// reniew local key
$MNET->replace_keys();
// send new key using key exchange transportation
if ($mnet_peers){
foreach($mnet_peers as $peer){
if (($peer->id == $CFG->mnet_all_hosts_id) || ($peer->id == $CFG->mnet_localhost_id)) continue;
$mnet_peer = new mnet_peer();
$mnet_peer->set_wwwroot($peer->wwwroot);
// get the sessions for each vmoodle that have same ID Number
// we use a force parameter to force fetching the key remotely anyway
$currentkey = mnet_get_public_key($mnet_peer->wwwroot, $mnet_peer->application, 1);
if ($currentkey){
$mnet_peer->public_key = clean_param($currentkey, PARAM_PEM);
$mnet_peer->updateparams->public_key = clean_param($currentkey, PARAM_PEM);
$mnet_peer->public_key_expires = $mnet_peer->check_common_name($currentkey);
$mnet_peer->updateparams->public_key_expires = $mnet_peer->check_common_name($currentkey);
$mnet_peer->commit();
mtrace('Key renewed for '.$peer->wwwroot.' till '.userdate($mnet_peer->public_key_expires));
} else {
mtrace('Failed renewing key with '.$peer->wwwroot);
}
}
}
set_config('mnet_autorenew_haveto', 0);
}
}
?>
This relies on a little change in get_public_key() and on the remote system/keyswap call that it implies :
we added a force mode, that allows ofrcing renewal on certain conditions :
/**
* Accepts a public key from a new remote host and returns the public key for
* this host. If 'register all hosts' is turned on, it will bootstrap a record
* for the remote host in the mnet_host table (if it's not already there)
*
* @param string $function XML-RPC requires this but we don't... discard!
* @param array $params Array of parameters
* $params[0] is the remote wwwroot
* $params[1] is the remote public key
* @return string The XML-RPC response
*/
function mnet_keyswap($function, $params) {
global $CFG, $MNET;
$return = array();
$wwwroot = $params[0];
$pubkey = $params[1];
$application = $params[2];
$forcerenew = $params[3];
if ($forcerenew == 0){
// standard keyswap for first key recording
if (!empty($CFG->mnet_register_allhosts)) {
$mnet_peer = new mnet_peer();
$keyok = $mnet_peer->bootstrap($wwwroot, $pubkey, $application);
if ($keyok) {
$mnet_peer->commit();
}
}
} else {
$mnet_peer = new mnet_peer();
// we can only renew hosts that we know something about.
if ($mnet_peer->set_wwwroot($wwwroot)){
$mnet_peer->public_key = clean_param($pubkey, PARAM_PEM);
$mnet_peer->public_key_expires = $mnet_peer->check_common_name($pubkey);
$mnet_peer->updateparams->public_key = clean_param($pubkey, PARAM_PEM);
$mnet_peer->updateparams->public_key_expires = $mnet_peer->check_common_name($pubkey);
$mnet_peer->commit();
}
return false; // avoid giving our key to unkown hosts.
}
return $MNET->public_key;
}
The precaution was not to let anyone ask for a key renewal, spoofing a registered host. I don't know if this precaution is sufficiant as it is. But it works in an internally secured subnetwork anyway.
The above change in renew process needs just passing an extra param in get_public_key (mnet/lib.php) :
/**
* Get the remote machine's SSL Cert
*
* @param string $uri The URI of a file on the remote computer, including
* its http:// or https:// prefix
* @return string A PEM formatted SSL Certificate.
*/
// PATCH Mnet automated key renewal : adding force
function mnet_get_public_key($uri, $application=null, $force=0) {
// \PATCH Mnet automated key renewal
global $CFG, $MNET;
// The key may be cached in the mnet_set_public_key function...
// check this first
// PATCH Mnet automated key renewal
// cache location of key must be bypassed when we need an automated renew.
if (!$force){
$key = mnet_set_public_key($uri);
if ($key != false) {
return $key;
}
}
// \PATCH Mnet automated key renewal
if (empty($application)) {
$application = get_record('mnet_application', 'name', 'moodle');
}
// PATCH Mnet automated key renewal
$rq = xmlrpc_encode_request('system/keyswap', array($CFG->wwwroot, $MNET->public_key, $application->name, $force), array("encoding" => "utf-8"));
// \PATCH Mnet automated key renewal
$ch = curl_init($uri . $application->xmlrpc_server_url);
...
This resolved all our issues of failure in standard key renewal, including those we would have expected to be working before the change.
Cheers.