Bläddra i källkod

Added experimental login connection log and security tab in system settings

TC pushbot 5 4 år sedan
förälder
incheckning
82036c14fc

+ 11 - 1
auth.go

@@ -71,7 +71,17 @@ func AuthSettingsInit() {
 	adminRouter.HandleFunc("/system/auth/csvimport", authAgent.HandleCreateUserAccountsFromCSV)
 	adminRouter.HandleFunc("/system/auth/groupdel", authAgent.HandleUserDeleteByGroup)
 
-	//API for checking with the logger
+	//System for logging and displaying login user information
+	//Register FTP Server Setting page
+	registerSetting(settingModule{
+		Name:         "Connection Log",
+		Desc:         "Logs for login attempts",
+		IconPath:     "SystemAO/security/img/small_icon.png",
+		Group:        "Security",
+		StartDir:     "SystemAO/security/connlog.html",
+		RequireAdmin: true,
+	})
+
 	adminRouter.HandleFunc("/system/auth/logger/index", authAgent.Logger.HandleIndexListing)
 	adminRouter.HandleFunc("/system/auth/logger/list", authAgent.Logger.HandleTableListing)
 }

+ 21 - 2
mod/auth/authlogger/authlogger.go

@@ -54,10 +54,28 @@ func (l *Logger) LogAuth(r *http.Request, loginStatus bool) error {
 	tableName := current.Format("Jan-2006")
 
 	//Create table if not exists
-	l.database.NewTable(tableName)
+	if !l.database.TableExists(tableName) {
+		l.database.NewTable(tableName)
+	}
 
 	//Split the remote address into ipaddr and port
-	remoteAddrInfo := strings.Split(r.RemoteAddr, ":")
+	remoteAddrInfo := []string{"unknown", "N/A"}
+	if strings.Contains(r.RemoteAddr, ":") {
+		//For general IPv4  address
+		remoteAddrInfo = strings.Split(r.RemoteAddr, ":")
+	}
+
+	//Check for IPV6
+	if strings.Contains(r.RemoteAddr, "[") && strings.Contains(r.RemoteAddr, "]") {
+		//This is an IPV6 address. Rewrite the split
+		//IPv6 should have the format of something like this [::1]:80
+		ipv6info := strings.Split(r.RemoteAddr, ":")
+		port := ipv6info[len(ipv6info)-1:]
+		ipAddr := ipv6info[:len(ipv6info)-1]
+		remoteAddrInfo = []string{strings.Join(ipAddr, ":"), strings.Join(port, ":")}
+
+	}
+
 	port := -1
 	if len(remoteAddrInfo) > 1 {
 		port, _ = strconv.Atoi(remoteAddrInfo[1])
@@ -71,6 +89,7 @@ func (l *Logger) LogAuth(r *http.Request, loginStatus bool) error {
 		IpAddr:         remoteAddrInfo[0],
 		Port:           port,
 	}
+
 	//Write the log to it
 	entryKey := strconv.Itoa(int(time.Now().UnixNano()))
 	err := l.database.Write(tableName, entryKey, thisRecord)

+ 11 - 1
mod/database/database.go

@@ -26,9 +26,19 @@ type Database struct {
 func NewDatabase(dbfile string, readOnlyMode bool) (*Database, error) {
 	db, err := bolt.Open(dbfile, 0600, nil)
 	log.Println("Key-value Database Service Started: " + dbfile)
+
+	tableMap := sync.Map{}
+	//Build the table list from database
+	err = db.View(func(tx *bolt.Tx) error {
+		return tx.ForEach(func(name []byte, _ *bolt.Bucket) error {
+			tableMap.Store(string(name), "")
+			return nil
+		})
+	})
+
 	return &Database{
 		Db:       db,
-		Tables:   sync.Map{},
+		Tables:   tableMap,
 		ReadOnly: readOnlyMode,
 	}, err
 }

+ 15 - 9
setting.go

@@ -50,55 +50,61 @@ func SystemSettingInit() {
 //Setting group defination. Your setting module defination must match the group in-order to be shown
 func system_setting_getSettingGroups() []settingGroup {
 	return []settingGroup{
-		settingGroup{
+		{
 			Name:     "Host Information",
 			Group:    "Info",
 			IconPath: "SystemAO/system_setting/img/server.svg",
 			Desc:     "Config and info about the Server Host",
 		},
-		settingGroup{
+		{
 			Name:     "Devices & IoT",
 			Group:    "Device",
 			IconPath: "SystemAO/system_setting/img/device.svg",
 			Desc:     "Connected clients and IoT devices",
 		},
-		settingGroup{
+		{
 			Name:     "Module Management",
 			Group:    "Module",
 			IconPath: "SystemAO/system_setting/img/module.svg",
 			Desc:     "List of modules loaded in the system",
 		},
-		settingGroup{
+		{
 			Name:     "Disk & Storage",
 			Group:    "Disk",
 			IconPath: "SystemAO/system_setting/img/drive.svg",
 			Desc:     "Manage Storage Devices and Disks",
 		},
-		settingGroup{
+		{
 			Name:     "Network & Connection",
 			Group:    "Network",
 			IconPath: "SystemAO/system_setting/img/network.svg",
 			Desc:     "Manage Host Network and Connections",
 		},
-		settingGroup{
+		{
 			Name:     "Users & Groups",
 			Group:    "Users",
 			IconPath: "SystemAO/system_setting/img/users.svg",
 			Desc:     "Add, removed or edit users and groups",
 		},
-		settingGroup{
+		{
 			Name:     "Clusters & Scheduling",
 			Group:    "Cluster",
 			IconPath: "SystemAO/system_setting/img/cluster.svg",
 			Desc:     "System Functions related to Time and Dates",
 		},
-		settingGroup{
+		{
+			Name:     "Security & Keys",
+			Group:    "Security",
+			IconPath: "SystemAO/system_setting/img/security.svg",
+			Desc:     "System Security and Keypairs",
+		},
+		{
 			Name:     "Advance Options",
 			Group:    "Advance",
 			IconPath: "SystemAO/system_setting/img/code.svg",
 			Desc:     "Advance configs for developers",
 		},
-		settingGroup{
+		{
 			Name:     "About ArOZ",
 			Group:    "About",
 			IconPath: "SystemAO/system_setting/img/info.svg",

BIN
web/SystemAO/disk/smart/img/f_f_object_10_s512_f_object_10_1nbg.png


+ 183 - 0
web/SystemAO/security/connlog.html

@@ -0,0 +1,183 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Connection Log</title>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+    <link rel="stylesheet" href="../../script/semantic/semantic.min.css">
+    <script type="text/javascript" src="../../script/jquery.min.js"></script>
+    <script type="text/javascript" src="../../script/semantic/semantic.min.js"></script>
+</head>
+<body>
+    <div class="ui container">
+        <p>Connection Attempts</p>
+        <h1 class="ui header">
+            <span id="normalStatus">Analysising</span>
+            <div class="sub header"><span id="loginAptCount"></span> login request logged this month with <span id="incorrectRatio"></span> incorrect password attempts.
+        </h1>
+        <div class="ui divider"></div>
+        <div class="ui fluid selection dropdown">
+            <input type="hidden" name="tablekey" onchange="loadRecords(this);">
+            <i class="dropdown icon"></i>
+            <div class="default text">Date</div>
+            <div id="recordTables" class="menu">
+                
+            </div>
+        </div>
+        <div class="ui divider">
+            <table class="ui celled table">
+                <thead>
+                    <tr>
+                        <th>Accepted</th>
+                        <th>Timestamp</th>
+                        <th>Requested Account</th>
+                        <th>IP Address</th>
+                        <th>Access Port</th>
+                    </tr>
+                </thead>
+                <tbody id="records">
+                
+                </tbody>
+              </table>
+        </div>
+    </div>
+    <br><br><br>
+    <script>
+        var monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun","Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
+
+        $(".ui.dropdown").dropdown();
+
+        initMonthList();
+
+        //Get the table that belongs to today. Put in offset if the current month not found in list
+        function getCurrentMonthTable(monOffset = 0){
+            const d = new Date();
+            return monthNames[d.getMonth() - monOffset] + "-" + d.getFullYear()
+        }
+
+        //Get the month list
+        function initMonthList(){
+            $.ajax({
+                url: "../../system/auth/logger/index",
+                success: function(data){
+                    var currentMonthTable = getCurrentMonthTable();
+                    var currentMontHTableExists = false;
+                    if (data.error){
+                        alert(data.error);
+                    }else{
+                        $("#recordTables").html("");
+                        data.forEach(tableName => {
+                            if (tableName == currentMonthTable){
+                                currentMontHTableExists = true;
+                            }
+                            $("#recordTables").append(`<div class="item" data-value="${tableName}">${tableName}</div>`);
+                        });
+
+                        if (data.length == 0){
+                            //No record
+                            $("#normalStatus").parent().attr("class","ui header");
+                            $("#normalStatus").text("No Record");
+                            $("#incorrectRatio").text("0%");
+                            $("#loginAptCount").text("0");
+                        }
+                    }
+
+                    //Select the current month if it exists
+                    setTimeout(function(){
+                        if (currentMontHTableExists){
+                            $("#recordTables").parent().dropdown("set selected", getCurrentMonthTable());
+                        }
+                    },300);
+                    
+                }
+            });
+        }
+
+        function loadRecords(object){
+            var tableName = object.value;
+            $.ajax({
+                url: "../../system/auth/logger/list",
+                data: {record: tableName},
+                success: function(data){
+                    $("#records").html("");
+                    if (data.error !== undefined){
+                        $("#records").html(`<tr>
+                            <td data-label="status" colspan="5"><i class="remove icon"></i> ${data.error}</td>
+                        </tr>`);
+                    }else{
+                        if (data.length == 0){
+                            $("#records").html(`<tr>
+                                <td data-label="status" colspan="5"><i class="checkmark icon"></i> No login record in this time period.</td>
+                            </tr>`);
+                        }else{
+                            //console.log(data);
+                            //Reverse the array so the latest login on top
+                            data.reverse();
+                            data.forEach(entry => {
+                                var timestamp = getDatetimeFromTimestamp(entry.Timestamp);
+                                var statusIcon = `<i class="green checkmark icon"></i>`;
+                                if (entry.LoginSucceed == false){
+                                    statusIcon = `<i class="red remove icon"></i>`;
+                                }
+                                $("#records").append(`<tr>
+                                    <td data-label="status">${statusIcon}</td>
+                                    <td data-label="timestamp">${timestamp}</td>
+                                    <td data-label="acc">${entry.TargetUsername}</td>
+                                    <td data-label="ip">${entry.IpAddr}</td>
+                                    <td data-label="port">${entry.Port}</td>
+                                </tr>`);
+                            });
+
+                            updateSummaryText(data);
+                            
+                        }
+                    }
+                }
+            })
+        }
+
+        function updateSummaryText(records){
+            if (records.length == 0){
+                $("#normalStatus").parent().attr("class","ui header");
+                $("#normalStatus").text("No Record");
+                $("#loginAptCount").text("No ");
+                return;
+            }
+            var succ = 0;
+            var failed = 0;
+
+            //Calculate the risk ratio
+            records.forEach(entry => {
+                if (entry.LoginSucceed){
+                    succ++;
+                }else{
+                    failed++;
+                }
+            });
+
+            //Do a quick summary
+            var failRatio = (failed) / (succ + failed) * 100;
+            if (failRatio > 30){
+                $("#normalStatus").parent().attr("class","ui yellow header");
+                $("#normalStatus").text("LOW RISK");
+            }else if (failRatio > 50){
+                $("#normalStatus").parent().attr("class","ui red header");
+                $("#normalStatus").text("HIGH RISK");
+            }else{
+                // No problem
+                $("#normalStatus").parent().attr("class","ui green header");
+                $("#normalStatus").text("Normal");
+                
+            }
+            $("#incorrectRatio").text(failRatio.toFixed(1) + "%");
+            $("#loginAptCount").text(succ + failed);
+        }
+
+       function getDatetimeFromTimestamp(timestamp){
+            var s = new Date(timestamp * 1000);
+            var options = { timeZone: 'UTC' };
+            return s.toLocaleDateString(undefined,options) + " " +  s.toLocaleTimeString(undefined,options);
+       }
+    </script>
+</body>
+</html>

BIN
web/SystemAO/security/img/small_icon.png


BIN
web/SystemAO/security/img/small_icon.psd


+ 1 - 0
web/SystemAO/system_setting/img/security.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 0 24 24" width="48px" fill="#000000"><g fill="none"><path d="M0 0h24v24H0V0z"/><path d="M0 0h24v24H0V0z" opacity=".87"/></g><path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zM9 6c0-1.66 1.34-3 3-3s3 1.34 3 3v2H9V6zm9 14H6V10h12v10zm-6-3c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2z"/></svg>