diff --git a/docs/_data/navigation.yml b/docs/_data/navigation.yml
index 0bdfae9a..ca0048ec 100644
--- a/docs/_data/navigation.yml
+++ b/docs/_data/navigation.yml
@@ -55,8 +55,13 @@
url: "/housekeeping-repo-location"
- title: "Forks"
url: "/housekeeping-forks"
- - title: "Failed Webhooks"
- url: "/housekeeping-failed-webhooks"
+- title: "Failures"
+ url: "/failures-failed-authentication"
+ subnavigation:
+ - title: "Authentication"
+ url: "/failures-failed-authentication"
+ - title: "Webhooks"
+ url: "/failures-failed-webhooks"
- title: "Recommendations"
url: "/recommendations-tokenless-auth"
subnavigation:
diff --git a/docs/demo-data/failed-auth-detailed.tsv b/docs/demo-data/failed-auth-detailed.tsv
new file mode 100644
index 00000000..0cac0931
--- /dev/null
+++ b/docs/demo-data/failed-auth-detailed.tsv
@@ -0,0 +1,13 @@
+user count
+tom 105
+mary 96
+jane 48
+mike 48
+bob 40
+bill 33
+sue 16
+cindy 9
+joe 3
+buildbot 3
+deploybot 3
+testbot 3
diff --git a/docs/demo-data/failed-auth.tsv b/docs/demo-data/failed-auth.tsv
new file mode 100644
index 00000000..37624b36
--- /dev/null
+++ b/docs/demo-data/failed-auth.tsv
@@ -0,0 +1,7 @@
+date failed authentication
+2020-12-01 1005
+2020-12-02 352
+2020-12-03 564
+2020-12-04 410
+2020-12-05 455
+2020-12-06 407
diff --git a/docs/failures-failed-authentication.html b/docs/failures-failed-authentication.html
new file mode 100644
index 00000000..40308020
--- /dev/null
+++ b/docs/failures-failed-authentication.html
@@ -0,0 +1,59 @@
+---
+layout: default
+title: Failed Authentication
+permalink: /failures-failed-authentication
+---
+
+
+
Failed Authentication
+
+
+
+ Looking at failed authentication attempts across the system is helpful in spotting misconfigured systems that may be tying up authentication workers.
+
+
+ For example, users may have changed credentials used to access GitHub, however they failed to update CI/CD systems using those credentials. Another example is that a CI/CD system
+ may be configured with a user account which is no longer active in GitHub and/or your company.
+
+
+ Therefore, you should try to reduce them.
+
+
+
+
+
diff --git a/docs/housekeeping-failed-webhooks.html b/docs/failures-failed-webhooks.html
similarity index 98%
rename from docs/housekeeping-failed-webhooks.html
rename to docs/failures-failed-webhooks.html
index 30231d5d..bc06e0a1 100644
--- a/docs/housekeeping-failed-webhooks.html
+++ b/docs/failures-failed-webhooks.html
@@ -1,7 +1,7 @@
---
layout: default
title: Failed Webhooks
-permalink: /housekeeping-failed-webhooks
+permalink: /failures-failed-webhooks
---
diff --git a/updater/reports/ReportFailedAuth.py b/updater/reports/ReportFailedAuth.py
new file mode 100644
index 00000000..4916785f
--- /dev/null
+++ b/updater/reports/ReportFailedAuth.py
@@ -0,0 +1,19 @@
+from .ReportDaily import *
+
+# Report how many failed authentication attempts
+class ReportFailedAuth(ReportDaily):
+ def name(self):
+ return "failed-auth"
+
+ def updateDailyData(self):
+ self.detailedHeader, newData = self.parseData(
+ self.executeScript(self.scriptPath("failed-auth.sh")))
+ self.header = ["date", "failed authentication"]
+ self.data.append(
+ [
+ str(self.yesterday()),
+ sum(map(lambda x: int(x[4] if len(x) > 3 else 0), newData)),
+ ])
+ self.detailedData = newData[:25]
+ self.truncateData(self.timeRangeTotal())
+ self.sortDataByDate()
diff --git a/updater/scripts/failed-auth.sh b/updater/scripts/failed-auth.sh
new file mode 100755
index 00000000..0162d225
--- /dev/null
+++ b/updater/scripts/failed-auth.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+#
+# List failed authentication attempts
+#
+
+echo -e "user\tcount"
+
+zcat -f /var/log/github/auth.log.1* |
+ grep -hF 'at=failure' |
+ grep -vF 'raw_login=nil' |
+ grep -oP ' login=.+?(?=raw_login)' |
+ grep -v 'https' |
+ grep -vF 'login=nil' |
+ grep -vF 'login=api' |
+ grep -vF 'login=git' |
+ perl -lape 's/login=//' |
+ sort |
+ uniq -ic |
+ sort -rn |
+ awk '{printf("%s\t%s\n",$2,$1)}'
diff --git a/updater/update-stats.py b/updater/update-stats.py
index 3998e7aa..e75cb668 100755
--- a/updater/update-stats.py
+++ b/updater/update-stats.py
@@ -12,6 +12,7 @@
from reports.ReportAPIRequestsByUser import *
from reports.ReportContributorsByOrg import *
from reports.ReportContributorsByRepo import *
+from reports.ReportFailedAuth import *
from reports.ReportFailedWebhooks import *
from reports.ReportForksToOrgs import *
from reports.ReportGitDownload import *
@@ -81,6 +82,7 @@ def main():
ReportAPIRequestsByUser(configuration, dataDirectory, metaStats).update()
ReportContributorsByOrg(configuration, dataDirectory, metaStats).update()
ReportContributorsByRepo(configuration, dataDirectory, metaStats).update()
+ ReportFailedAuth(configuration, dataDirectory, metaStats).update()
ReportFailedWebhooks(configuration, dataDirectory, metaStats).update()
ReportForksToOrgs(configuration, dataDirectory, metaStats).update()
ReportGitDownload(configuration, dataDirectory, metaStats).update()