-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 0fc15dd
Showing
6 changed files
with
221 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
# auth-notify | ||
|
||
A small ssh and sudo notification daemon. It parses /var/log/auth.log, searches for successful ssh authentications | ||
and all sudo authentications, and notifies about them both in stdout, and optionally in Telegram. | ||
|
||
Currently only tested with OpenSSH and systemd on Debian. | ||
|
||
## Installation | ||
|
||
Download binary from the release, copy it to `/usr/local/bin/auth-notify`, then copy `auth-notify.conf` into | ||
`/etc/`, and `auth-notify.service` into `/etc/systemd/system/`. | ||
|
||
You can start the service by running `systemctl start auth-notify`, and enable automatic start on boot with | ||
`systemctl enable auth-notify`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
TELEGRAM_CHAT= | ||
TELEGRAM_TOKEN= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
[Unit] | ||
Description=auth-notify is a login notification service | ||
Wants=multi-user.target | ||
|
||
[Service] | ||
ExecStart=/usr/local/bin/auth-notify | ||
EnvironmentFile=/etc/auth-notify.conf | ||
Restart=on-failure | ||
|
||
[Install] | ||
WantedBy=multi-user.target |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
module auth-notify | ||
|
||
go 1.18 | ||
|
||
require github.com/hpcloud/tail v1.0.0 | ||
|
||
require ( | ||
github.com/fsnotify/fsnotify v1.5.4 // indirect | ||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect | ||
gopkg.in/fsnotify.v1 v1.4.7 // indirect | ||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= | ||
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= | ||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= | ||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= | ||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0= | ||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= | ||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= | ||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= | ||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"github.com/hpcloud/tail" | ||
"io" | ||
"log" | ||
"net/http" | ||
"net/url" | ||
"os" | ||
"strings" | ||
"time" | ||
) | ||
|
||
type TelegramResponse struct { | ||
OK bool `json:"ok"` | ||
Description string `json:"description"` | ||
} | ||
|
||
func SendMessage(chat, token, message string) error { | ||
if chat == "" || token == "" { | ||
// If no configuration provided, don't do anything | ||
return nil | ||
} | ||
|
||
endpoint := "https://api.telegram.org/bot" + token + "/sendMessage" | ||
|
||
params := url.Values{} | ||
params.Set("chat_id", chat) | ||
params.Set("text", message) | ||
|
||
c := http.Client{Timeout: 15 * time.Second} | ||
resp, err := c.Get(endpoint + "?" + params.Encode()) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
defer resp.Body.Close() | ||
|
||
data, err := io.ReadAll(resp.Body) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
tgResp := &TelegramResponse{} | ||
err = json.Unmarshal(data, tgResp) | ||
|
||
if err != nil { | ||
return err | ||
} | ||
|
||
if !tgResp.OK { | ||
return fmt.Errorf("telegram returned an error: %v", tgResp.Description) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func main() { | ||
telegramChat := os.Getenv("TELEGRAM_CHAT") | ||
telegramToken := os.Getenv("TELEGRAM_TOKEN") | ||
|
||
logTail, err := tail.TailFile("/var/log/auth.log", tail.Config{ | ||
ReOpen: true, | ||
Follow: true, | ||
}) | ||
if err != nil { | ||
log.Fatalln(err) | ||
} | ||
|
||
skipping := true | ||
now := time.Now() | ||
|
||
for line := range logTail.Lines { | ||
entryFields := strings.SplitN(line.Text, ": ", 2) | ||
if len(entryFields) != 2 { | ||
continue | ||
} | ||
|
||
meta, text := entryFields[0], entryFields[1] | ||
metaFields := strings.Fields(meta) | ||
if len(metaFields) != 5 { | ||
continue | ||
} | ||
|
||
date := strings.Join(metaFields[:3], " ") | ||
hostname := metaFields[3] | ||
unit := metaFields[4] | ||
|
||
t, err := time.Parse("Jan 2 15:04:05", date) | ||
if err != nil { | ||
log.Println("cannot parse time:", err) | ||
continue | ||
} | ||
|
||
// Skip all records that exist before starting the tail | ||
if skipping && (t.Day() != now.Day() || | ||
t.Month() != now.Month() || | ||
t.Hour() < now.Hour() || | ||
(t.Hour() == now.Hour() && t.Minute() < now.Minute()) || | ||
(t.Hour() == now.Hour() && t.Minute() == now.Minute() && t.Second() <= now.Second())) { | ||
continue | ||
} | ||
|
||
skipping = false | ||
|
||
switch { | ||
case strings.HasPrefix(unit, "sshd"): | ||
if !strings.HasPrefix(text, "Accepted") { | ||
continue | ||
} | ||
|
||
sshFields := strings.Fields(text) | ||
if len(sshFields) < 6 { | ||
continue | ||
} | ||
|
||
method, user, ip := sshFields[1], sshFields[3], sshFields[5] | ||
|
||
fmt.Printf("new ssh session for user %v (from %v; using %v)\n", user, ip, method) | ||
err = SendMessage(telegramChat, telegramToken, | ||
fmt.Sprintf("new ssh session started by user %v\n\nfrom: %v\nmethod: %v\nhostname: %v", | ||
user, ip, method, hostname)) | ||
if err != nil { | ||
fmt.Printf("could not send message: %v\n", err) | ||
} | ||
case strings.HasPrefix(unit, "sudo"): | ||
text = strings.TrimSpace(text) | ||
splitSudo := strings.SplitN(text, " : ", 2) | ||
if len(splitSudo) != 2 { | ||
continue | ||
} | ||
|
||
user, rest := splitSudo[0], splitSudo[1] | ||
|
||
failed := strings.Contains(rest, "incorrect password") | ||
|
||
entries := strings.Split(rest, " ; ") | ||
var asUser, command string | ||
|
||
for _, entry := range entries { | ||
switch { | ||
case strings.HasPrefix(entry, "USER="): | ||
asUser = entry[5:] | ||
case strings.HasPrefix(entry, "COMMAND="): | ||
command = entry[8:] | ||
} | ||
} | ||
|
||
if !failed { | ||
fmt.Printf("sudo executed by %v (became %v; for command %v)\n", user, asUser, command) | ||
|
||
err = SendMessage(telegramChat, telegramToken, | ||
fmt.Sprintf("sudo started by %v\n\ntarget: %v\ncommand: %v\nhostname: %v", | ||
user, asUser, command, hostname)) | ||
if err != nil { | ||
fmt.Printf("could not send message: %v\n", err) | ||
} | ||
} else { | ||
fmt.Printf("failed attempt to execute sudo by %v (to become %v; for command %v)\n", user, asUser, command) | ||
|
||
err = SendMessage(telegramChat, telegramToken, | ||
fmt.Sprintf("failed attempt to start sudo by %v\n\ntarget: %v\ncommand: %v\nhostname: %v", | ||
user, asUser, command, hostname)) | ||
if err != nil { | ||
fmt.Printf("could not send message: %v\n", err) | ||
} | ||
} | ||
} | ||
} | ||
} |