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.

Cloud

banshee/cloud-collection/cloud/aws/https/

health

The open AWS service issues, scheduled changes and account notifications of an AWS account, by the AWS Health API.

  • cloud
  • aws
  • amazon
  • health
  • service-health
  • events
  • https
  • api

by banshee0 installations1 version

v1Latest

Published on October 27, 2026. First version.

Keen 1.0.0 · 96 lines
// The AWS Health events of an AWS account, by the AWS Health API with an access key: how many AWS service issues
// are open, how many scheduled changes and account notifications are open or upcoming, and which services the
// issues touch. The AWS Health API serves only an account with a Business, Enterprise On-Ramp or Enterprise
// support plan; any other account is told so.
// Reads POST https://health.us-east-1.amazonaws.com/, the action DescribeEvents, page after page, signed by
// Signature Version 4.
need('protocol:http');
need('protocol:aws');
$regions = variable('aws:health-regions', 'text', array(
    'label'    => 'Regions',
    'help'     => 'Only the events of these regions, separated by commas, global for the services without a region: eu-west-3, global. Empty, the events of every region.',
    'required' => false,
));

output('issues', 'number', array('label' => 'Open service issues'));
output('scheduled', 'number', array('label' => 'Open or upcoming scheduled changes'));
output('notifications', 'number', array('label' => 'Open or upcoming account notifications'));
output('issue-names', 'text', array('label' => 'Services touched by the open issues'));

watch.above('issue', 'AWS service issue', 'issues', array('warning' => 0));
watch.above('scheduled', 'AWS scheduled change', 'scheduled', array('warning' => 0, 'enabled' => false));
watch.above('notification', 'AWS account notification', 'notifications', array('warning' => 0, 'enabled' => false));

// The regions set on the probe, as the list the filter takes.
$filter = array('eventStatusCodes' => array('open', 'upcoming'));
if ($regions != '') {
    $names = array();
    foreach (text.split($regions, ',') as $name) {
        if (text.trim($name) != '') {
            $names = list.add($names, text.trim($name));
        }
    }
    $filter['regions'] = $names;
}

// {"events": [{"arn": "…", "service": "EC2", "eventTypeCode": "AWS_EC2_OPERATIONAL_ISSUE", "eventTypeCategory":
// "issue", "region": "eu-west-3", "statusCode": "open", "startTime": 1793107815.0, …}], "nextToken": "…"}.
// eventTypeCategory is issue, investigation, scheduledChange or accountNotification; statusCode open, closed or
// upcoming. The API speaks the JSON protocol of AWS: one POST, its action named by X-Amz-Target, the body
// signed with the headers. The body carries no secret, and neither does the answer.
$url = 'https://health.us-east-1.amazonaws.com/';
$issues = 0;
$scheduled = 0;
$notifications = 0;
$touched = array();
$token = '';
while (true) {
    $request = array('filter' => $filter, 'maxResults' => 100);
    if ($token != '') {
        $request['nextToken'] = $token;
    }
    $body = json.write($request);
    $headers = array('Content-Type' => 'application/x-amz-json-1.1', 'X-Amz-Target' => 'AWSHealth_20160804.DescribeEvents');
    $answer = http.post($url, $body, headers=aws.sign('POST', $url, $headers, $body, 'us-east-1', 'health'));
    if ($answer['status'] != 200) {
        if (text.contains($answer['body'], 'SubscriptionRequiredException')) {
            fail('misconfigured', 'The account has no support plan that opens the AWS Health API',
                'AWS answers SubscriptionRequiredException: the AWS Health API serves only a Business, Enterprise On-Ramp or Enterprise support plan.');
        }
        if (text.contains($answer['body'], 'ThrottlingException') || $answer['status'] >= 500) {
            fail('unreachable', 'AWS Health answers the reading of the events with code ' . $answer['status']);
        }
        if ($answer['status'] == 400 || $answer['status'] == 401 || $answer['status'] == 403) {
            $type = text.match($answer['body'], '"__type"\s*:\s*"([^"]+)"');
            $code = '';
            if (list.count($type) > 0) {
                $code = $type[1];
            }
            fail('misconfigured', 'AWS Health refuses to read the events, code ' . $answer['status'] . ' ' . $code,
                'Check the access key and the permissions of its user or role.');
        }
        fail('unreachable', 'AWS Health answers the reading of the events with code ' . $answer['status']);
    }
    $page = json.read($answer['body']);
    foreach (list.get($page, 'events', array()) as $event) {
        $category = list.get($event, 'eventTypeCategory', '');
        $status = list.get($event, 'statusCode', '');
        if (($category == 'issue' || $category == 'investigation') && $status == 'open') {
            $issues = $issues + 1;
            $touched = list.add($touched, list.get($event, 'service', '') . ' ' . list.get($event, 'eventTypeCode', '')
                . ' (' . list.get($event, 'region', '') . ')');
        } elseif ($category == 'scheduledChange') {
            $scheduled = $scheduled + 1;
        } elseif ($category == 'accountNotification') {
            $notifications = $notifications + 1;
        }
    }
    $token = list.get($page, 'nextToken', '');
    if ($token == '') {
        break;
    }
}
value('issues', $issues);
value('scheduled', $scheduled);
value('notifications', $notifications);
value('issue-names', text.join($touched, ' | '));