banshee is under active development: collectors will keep changing, and a version you publish will likely need adjustments. For stable use, wait for version 1.0.0.

Active Directory

banshee/active-directory-collection/active-directory/ssh/

time

The clock of a domain controller against a time server, and the time source its role calls for, by SSH and PowerShell

  • active-directory
  • domain-controller
  • time
  • ntp
  • w32time
  • ssh
  • powershell

by banshee1 installation2 versions

Keen 1.0.0 · 76 lines
// The clock of a domain controller, by SSH: its offset from a time server, and the time source the Windows
// Time service is set to follow. Microsoft sets the domain hierarchy (NT5DS) on every domain controller but
// one: the PDC emulator of the root domain of the forest, the source of the whole forest, which follows an
// external time server (NTP). Watch that one first.
// Runs one PowerShell command that only reads: [DateTime]::UtcNow.Ticks, read first; the PDC emulator of the
// root domain, through the classes of .NET that Microsoft documents (Forest.RootDomain.PdcRoleOwner); and the
// values Type and NtpServer under HKLM\SYSTEM\CurrentControlSet\Services\W32Time\Parameters, or under
// HKLM\SOFTWARE\Policies\Microsoft\W32Time\Parameters when a group policy sets them, as it then prevails.
// The offset is read as the clock collector of windows-collection reads it: to about a second.
need('protocol:ssh');
need('protocol:ntp');

output('offset', 'number', array('label' => 'Offset from the time server', 'unit' => 's'));
output('drift', 'number', array('label' => 'Offset, without its sign', 'unit' => 's'));
output('role', 'text', array('label' => 'Role in the time hierarchy'));
output('type', 'text', array('label' => 'Time source type'));
output('peers', 'text', array('label' => 'Time servers set'));

watch.above('clock-drift', 'Clock off', 'drift', array('warning' => 5, 'critical' => 60));
watch.custom('source', 'Time source unexpected', array(
    'help' => 'Warning when the PDC emulator of the root domain does not follow an external time server (NTP), or another domain controller does not follow the domain hierarchy (NT5DS).',
));

$asked = time.now();
$read = ssh.run("powershell -NoProfile -NonInteractive -Command \""
    . "$ticks = [string][DateTime]::UtcNow.Ticks; "
    . "$ErrorActionPreference = 'Stop'; "
    . "[Console]::OutputEncoding = New-Object System.Text.UTF8Encoding; "
    . "$computer = Get-CimInstance Win32_ComputerSystem; "
    . "$server = $computer.DNSHostName + '.' + $computer.Domain; "
    . "$context = New-Object DirectoryServices.ActiveDirectory.DirectoryContext('DirectoryServer', $server); "
    . "$dc = [DirectoryServices.ActiveDirectory.DomainController]::GetDomainController($context); "
    . "$role = 'other'; "
    . "if ($dc.Name -eq $dc.Forest.RootDomain.PdcRoleOwner.Name) { $role = 'root-pdc' }; "
    . "$config = Get-ItemProperty HKLM:\\SYSTEM\\CurrentControlSet\\Services\\W32Time\\Parameters; "
    . "$policy = Get-ItemProperty HKLM:\\SOFTWARE\\Policies\\Microsoft\\W32Time\\Parameters -ErrorAction SilentlyContinue; "
    . "$type = $config.Type; $peers = $config.NtpServer; "
    . "if ($policy.Type) { $type = $policy.Type; $peers = $policy.NtpServer }; "
    . "ConvertTo-Json -Compress -InputObject @{ticks = $ticks; role = $role; type = $type; peers = $peers}\"");
$answered = time.now();
$server = ntp.time(errors=$err);
$now = time.now();
if ($err['code'] != '') {
    fail('unreachable', 'Time server unreachable', $err['text']);
}
$answer = text.trim($read['output']);
if (list.count(text.match($answer, '^\{')) == 0) {
    fail('misconfigured', 'PowerShell did not return the time of the domain controller', $answer . $read['errors']);
}
$clock = json.read($answer);
if (!text.is_number($clock['ticks'])) {
    fail('misconfigured', 'PowerShell did not return the time of the domain controller', $answer);
}

// The server's time at the middle of the SSH call: its time, less the time elapsed since, measured by the
// poller's clock.
$reference = $server - ($now - ($asked + $answered) / 2);
// 621355968000000000 ticks separate 1 January of the year 1 from 1 January 1970; ten million make a second.
$offset = ($clock['ticks'] - 621355968000000000) / 10000000 - $reference;
$drift = $offset;
if ($drift < 0) {
    $drift = -$drift;
}
value('offset', number.round($offset, 1));
value('drift', number.round($drift, 1));
value('role', $clock['role']);
value('type', $clock['type']);
value('peers', $clock['peers']);

$type = text.lower($clock['type']);
if ($clock['role'] == 'root-pdc' && $type != 'ntp') {
    watch.raise('source', 'warning', 'PDC emulator of the root domain set to ' . $clock['type'] . ', not to an external time server (NTP)');
}
if ($clock['role'] == 'other' && $type != 'nt5ds') {
    watch.raise('source', 'warning', 'Domain controller set to ' . $clock['type'] . ', not to the domain hierarchy (NT5DS)');
}