Merge branch 'main' of ssh://code.freedombone.net:2222/bashrc/epicyon

main
Bob Mottram 2021-06-09 18:17:30 +01:00
commit 55cd454907
7 changed files with 105 additions and 8 deletions

View File

@ -82,7 +82,7 @@ Type=simple
User=epicyon
Group=epicyon
WorkingDirectory=/opt/epicyon
ExecStart=/usr/bin/python3 /opt/epicyon/epicyon.py --port 443 --proxy 7156 --domain YOUR_DOMAIN --registration open
ExecStart=/usr/bin/python3 /opt/epicyon/epicyon.py --port 443 --proxy 7156 --domain YOUR_DOMAIN --registration open --logLoginFailures
Environment=USER=epicyon
Environment=PYTHONUNBUFFERED=true
Restart=always
@ -208,6 +208,8 @@ And restart the web server:
systemctl restart nginx
```
If you need to use **fail2ban** then failed login attempts can be found in *accounts/loginfailures.log*.
If you are using the [Caddy web server](https://caddyserver.com) then see *caddy.example.conf*
## Running Static Analysis

45
auth.py
View File

@ -11,6 +11,7 @@ import hashlib
import binascii
import os
import secrets
import datetime
from utils import isSystemAccount
from utils import hasUsersPath
@ -204,3 +205,47 @@ def createPassword(length=10):
validChars = 'abcdefghijklmnopqrstuvwxyz' + \
'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
return ''.join((secrets.choice(validChars) for i in range(length)))
def recordLoginFailure(baseDir: str, ipAddress: str,
countDict: {}, failTime: int,
logToFile: bool) -> None:
"""Keeps ip addresses and the number of times login failures
occured for them in a dict
"""
if not countDict.get(ipAddress):
while len(countDict.items()) > 100:
oldestTime = 0
oldestIP = None
for ipAddr, ipItem in countDict.items():
if oldestTime == 0 or ipItem['time'] < oldestTime:
oldestTime = ipItem['time']
oldestIP = ipAddr
if oldestIP:
del countDict[oldestIP]
countDict[ipAddress] = {
"count": 1,
"time": failTime
}
else:
countDict[ipAddress]['count'] += 1
countDict[ipAddress]['time'] = failTime
failCount = countDict[ipAddress]['count']
if failCount > 4:
print('WARN: ' + str(ipAddress) + ' failed to log in ' +
str(failCount) + ' times')
if not logToFile:
return
failureLog = baseDir + '/accounts/loginfailures.log'
writeType = 'a+'
if not os.path.isfile(failureLog):
writeType = 'w+'
currTime = datetime.datetime.utcnow()
try:
with open(failureLog, writeType) as fp:
fp.write(currTime.strftime("%Y-%m-%d %H:%M:%SZ") +
' ' + ipAddress + '\n')
except BaseException:
pass

View File

@ -101,6 +101,7 @@ from skills import noOfActorSkills
from skills import actorHasSkill
from skills import actorSkillValue
from skills import setActorSkillLevel
from auth import recordLoginFailure
from auth import authorize
from auth import createPassword
from auth import createBasicAuthHeader
@ -206,6 +207,7 @@ from shares import addShare
from shares import removeShare
from shares import expireShares
from categories import setHashtagCategory
from utils import isLocalNetworkAddress
from utils import permittedDir
from utils import isAccountDir
from utils import getOccupationSkills
@ -1437,15 +1439,33 @@ class PubServer(BaseHTTPRequestHandler):
return
authHeader = \
createBasicAuthHeader(loginNickname, loginPassword)
if self.headers.get('X-Forward-For'):
ipAddress = self.headers['X-Forward-For']
elif self.headers.get('X-Forwarded-For'):
ipAddress = self.headers['X-Forwarded-For']
else:
ipAddress = self.client_address[0]
if not domain.endswith('.onion'):
if not isLocalNetworkAddress(ipAddress):
print('Login attempt from IP: ' + str(ipAddress))
if not authorizeBasic(baseDir, '/users/' +
loginNickname + '/outbox',
authHeader, False):
print('Login failed: ' + loginNickname)
self._clearLoginDetails(loginNickname, callingDomain)
self.server.lastLoginFailure = int(time.time())
failTime = int(time.time())
self.server.lastLoginFailure = failTime
if not domain.endswith('.onion'):
if not isLocalNetworkAddress(ipAddress):
recordLoginFailure(baseDir, ipAddress,
self.server.loginFailureCount,
failTime,
self.server.logLoginFailures)
self.server.POSTbusy = False
return
else:
if self.server.loginFailureCount.get(ipAddress):
del self.server.loginFailureCount[ipAddress]
if isSuspended(baseDir, loginNickname):
msg = \
htmlSuspended(self.server.cssCache,
@ -14829,7 +14849,8 @@ def loadTokens(baseDir: str, tokensDict: {}, tokensLookup: {}) -> None:
break
def runDaemon(city: str,
def runDaemon(logLoginFailures: bool,
city: str,
showNodeInfoAccounts: bool,
showNodeInfoVersion: bool,
brochMode: bool,
@ -15097,6 +15118,8 @@ def runDaemon(city: str,
httpd.allowDeletion = allowDeletion
httpd.lastLoginTime = 0
httpd.lastLoginFailure = 0
httpd.loginFailureCount = {}
httpd.logLoginFailures = logLoginFailures
httpd.maxReplies = maxReplies
httpd.tokens = {}
httpd.tokensLookup = {}

View File

@ -291,6 +291,11 @@ parser.add_argument("--iconsAsButtons",
type=str2bool, nargs='?',
const=True, default=False,
help="Show header icons as buttons")
parser.add_argument("--logLoginFailures",
dest='logLoginFailures',
type=str2bool, nargs='?',
const=True, default=False,
help="Whether to log longin failures")
parser.add_argument("--rssIconAtTop",
dest='rssIconAtTop',
type=str2bool, nargs='?',
@ -2510,6 +2515,11 @@ brochMode = \
if brochMode is not None:
args.brochMode = bool(brochMode)
logLoginFailures = \
getConfigParam(baseDir, 'logLoginFailures')
if logLoginFailures is not None:
args.logLoginFailures = bool(logLoginFailures)
showNodeInfoAccounts = \
getConfigParam(baseDir, 'showNodeInfoAccounts')
if showNodeInfoAccounts is not None:
@ -2539,7 +2549,8 @@ if setTheme(baseDir, themeName, domain,
print('Theme set to ' + themeName)
if __name__ == "__main__":
runDaemon(args.city,
runDaemon(args.logLoginFailures,
args.city,
args.showNodeInfoAccounts,
args.showNodeInfoVersion,
args.brochMode,

View File

@ -518,8 +518,9 @@ def createServerAlice(path: str, domain: str, port: int,
showNodeInfoAccounts = True
showNodeInfoVersion = True
city = 'London, England'
logLoginFailures = False
print('Server running: Alice')
runDaemon(city,
runDaemon(logLoginFailures, city,
showNodeInfoAccounts,
showNodeInfoVersion,
brochMode,
@ -620,8 +621,9 @@ def createServerBob(path: str, domain: str, port: int,
showNodeInfoAccounts = True
showNodeInfoVersion = True
city = 'London, England'
logLoginFailures = False
print('Server running: Bob')
runDaemon(city,
runDaemon(logLoginFailures, city,
showNodeInfoAccounts,
showNodeInfoVersion,
brochMode,
@ -677,8 +679,9 @@ def createServerEve(path: str, domain: str, port: int, federationList: [],
showNodeInfoAccounts = True
showNodeInfoVersion = True
city = 'London, England'
logLoginFailures = False
print('Server running: Eve')
runDaemon(city,
runDaemon(logLoginFailures, city,
showNodeInfoAccounts,
showNodeInfoVersion,
brochMode,

View File

@ -669,6 +669,16 @@ def getLocalNetworkAddresses() -> []:
return ('localhost', '127.0.', '192.168', '10.0.')
def isLocalNetworkAddress(ipAddress: str) -> bool:
"""
"""
localIPs = getLocalNetworkAddresses()
for ipAddr in localIPs:
if ipAddress.startswith(ipAddr):
return True
return False
def dangerousMarkup(content: str, allowLocalNetworkAccess: bool) -> bool:
"""Returns true if the given content contains dangerous html markup
"""

View File

@ -1343,7 +1343,7 @@
User=epicyon<br>
Group=epicyon<br>
WorkingDirectory=/opt/epicyon<br>
ExecStart=/usr/bin/python3 /opt/epicyon/epicyon.py --port 443 --proxy 7156 --domain YOUR_DOMAIN --registration open --debug<br>
ExecStart=/usr/bin/python3 /opt/epicyon/epicyon.py --port 443 --proxy 7156 --domain YOUR_DOMAIN --registration open --debug --logLoginFailures<br>
Environment=USER=epicyon<br>
Environment=PYTHONUNBUFFERED=true<br>
Restart=always<br>
@ -1492,6 +1492,9 @@
<div class="shell">
systemctl restart nginx
</div>
<p class="intro">
If you need to use <a href="https://www.fail2ban.org">fail2ban</a> then failed login attempts can be found in <b>accounts/loginfailures.log</b>.
</p>
<p class="intro">
If you are using the <a href="https://caddyserver.com">Caddy web server</a> then see <a href="https://code.freedombone.net/bashrc/epicyon/raw/main/caddy.example.conf">caddy.example.conf</a>
</p>