Initial commit
This commit is contained in:
parent
7a14f952a6
commit
9c433c75ef
23
Dockerfile
Normal file
23
Dockerfile
Normal file
@ -0,0 +1,23 @@
|
||||
# Base on latest (edge) alpine image
|
||||
FROM harbor.ervine.dev/library/x86_64/alpine:v3.12
|
||||
LABEL "Maintainer Jonathan Ervine <docker@ervine.org>"
|
||||
|
||||
# Install updates
|
||||
ENV LANG='en_US.UTF-8' \
|
||||
LANGUAGE='en_US.UTF-8' \
|
||||
|
||||
RUN apk update && \
|
||||
apk -U upgrade --ignore aline-baselayout && \
|
||||
apk -U add python3 gcc py3-pip python3-dev musl-dev libffi-dev git && \
|
||||
adduser -D python && \
|
||||
mkdir /data
|
||||
|
||||
ADD requirements.txt /data/requirements.txt
|
||||
ADD smtp2slack4qnap.py /data/smtp2slack4qnap.py
|
||||
|
||||
RUN pip3 install -r /data/requirements.txt && \
|
||||
rm -rf /tmp/src && rm -rf /var/cache/apk/*
|
||||
|
||||
#USER python
|
||||
|
||||
CMD [ "/usr/bin/python3", "/data/smtp2slack4qnap.py" ]
|
||||
37
Jenkinsfile
vendored
Normal file
37
Jenkinsfile
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
podTemplate(yaml: """
|
||||
kind: Pod
|
||||
spec:
|
||||
containers:
|
||||
- name: kaniko
|
||||
image: gcr.io/kaniko-project/executor:debug-539ddefcae3fd6b411a95982a830d987f4214251
|
||||
imagePullPolicy: Always
|
||||
command:
|
||||
- /busybox/cat
|
||||
tty: true
|
||||
volumeMounts:
|
||||
- name: jenkins-docker-cfg
|
||||
mountPath: /kaniko/.docker
|
||||
volumes:
|
||||
- name: jenkins-docker-cfg
|
||||
projected:
|
||||
sources:
|
||||
- secret:
|
||||
name: regcred
|
||||
items:
|
||||
- key: .dockerconfigjson
|
||||
path: config.json
|
||||
"""
|
||||
) {
|
||||
|
||||
node(POD_LABEL) {
|
||||
stage('Build with Kaniko') {
|
||||
git url: 'ssh://git@git.ervine.org/jonny/x86_64-alpine-qnap2slack.git', credentialsId: 'jenkins-to-git'
|
||||
container('kaniko') {
|
||||
sh '/kaniko/executor -f `pwd`/Dockerfile -c `pwd` --cache=true --destination=harbor.ervine.dev/public/x86_64/alpine/qnap2slack:v3.12.0 --destination=harbor.ervine.dev/public/x86_64/alpine/qnap2slack:v3.12'
|
||||
}
|
||||
}
|
||||
stage('Notify gchat') {
|
||||
hangoutsNotify message: "QNAP to Slack Notifier Application on Alpine Linux 3.12.0 has built",token: "A2ET831pVslqXTqAx6ycu573r",threadByJob: false
|
||||
}
|
||||
}
|
||||
}
|
||||
4
requirements.txt
Normal file
4
requirements.txt
Normal file
@ -0,0 +1,4 @@
|
||||
asyncio
|
||||
aiosmtpd
|
||||
requests
|
||||
html2text
|
||||
92
smtp2qnap4slack.py
Normal file
92
smtp2qnap4slack.py
Normal file
@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Compact SMTP to HTTP Gateway
|
||||
# -> targeting Slack for QNAP-NAS notifications
|
||||
#
|
||||
|
||||
# generate self-signed cert (better than nothing):
|
||||
# openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 3650 -nodes -subj '/CN=localhost'
|
||||
|
||||
import ssl
|
||||
import asyncio
|
||||
from aiosmtpd.controller import Controller
|
||||
from aiosmtpd.smtp import SMTP as Server, syntax
|
||||
from aiosmtpd.handlers import Debugging
|
||||
from hashlib import sha256
|
||||
from base64 import b64encode, b64decode
|
||||
import requests
|
||||
import email
|
||||
import json
|
||||
import html2text
|
||||
import re
|
||||
import os
|
||||
|
||||
### CONFIG DATA
|
||||
|
||||
# for SMTP AUTH LOGIN (SECRET = sha256(password) avoiding storing plaintext)
|
||||
USER = 'username'
|
||||
SECRET = '1c18f3a76a7ad787ee1d5aea573bd51db1e559b85bbc4a3228076442e9a0bc90'
|
||||
|
||||
# SMTP listener (set to localhost if running on QNAP device)
|
||||
LHOST, LPORT = '192.168.0.50', 1025
|
||||
|
||||
# target slack authenticated webhook url (keep confidential!)
|
||||
WEBHOOK_URL = 'https://hooks.slack.com/services/XXXXXXXXX/YYYYYYYYY/aaaaaaaaaaaaaaaaaaaaaaaa'
|
||||
|
||||
### END OF CONFIG DATA
|
||||
|
||||
# implemented LOGIN authentication (non-RFC compliant, works with QNAP-NAS)
|
||||
# overkill for running locally, but mandatory for remote
|
||||
class MyServer(Server):
|
||||
authenticated = False
|
||||
@syntax('AUTH LOGIN')
|
||||
async def smtp_AUTH(self, arg):
|
||||
if arg != 'LOGIN':
|
||||
await self.push('501 Syntax: AUTH LOGIN')
|
||||
return
|
||||
await self.push('334 VXNlcm5hbWU=') # b64('Username')
|
||||
username = await self._reader.readline()
|
||||
username = b64decode(username.rstrip(b'\r\n'))
|
||||
await self.push('334 UGFzc3dvcmQ=') # b64('Password')
|
||||
password = await self._reader.readline()
|
||||
password = b64decode(password.rstrip(b'\r\n'))
|
||||
if username.decode() == USER and sha256(password).hexdigest() == SECRET:
|
||||
self.authenticated = True
|
||||
print("[+] Authenticated")
|
||||
await self.push('235 2.7.0 Authentication successful')
|
||||
else:
|
||||
await self.push('535 Invalid credentials')
|
||||
|
||||
# requires STARTTLS
|
||||
# again, overkill for running locally, but mandatory for remote
|
||||
class MyController(Controller):
|
||||
def factory(self):
|
||||
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
|
||||
context.load_cert_chain('cert.pem', 'key.pem')
|
||||
return MyServer(self.handler, tls_context=context, require_starttls=True)
|
||||
|
||||
def email2text(data):
|
||||
body = email.message_from_bytes(data).get_payload()
|
||||
h = html2text.HTML2Text()
|
||||
h.ignore_tables = True
|
||||
return re.sub(r'\n\s*\n', '\n\n', h.handle(body))
|
||||
|
||||
class CustomHandler:
|
||||
async def handle_DATA(self, server, session, envelope):
|
||||
if not server.authenticated:
|
||||
return '500 Unauthenticated. Could not process your message'
|
||||
mail_from = envelope.mail_from
|
||||
data = envelope.content
|
||||
text = email2text(data)
|
||||
# tuned for slack, but can be anything else
|
||||
requests.post(WEBHOOK_URL, data={'payload': json.dumps({'username': mail_from, 'text': text})})
|
||||
print("[+] Alert sent: {}".format(text.encode()))
|
||||
return '250 OK'
|
||||
|
||||
if __name__ == '__main__':
|
||||
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||
handler = CustomHandler()
|
||||
controller = MyController(handler, hostname=LHOST, port=LPORT)
|
||||
controller.start()
|
||||
input('SMTP server is running. Press Return to stop server and exit.\n')
|
||||
controller.stop()
|
||||
Loading…
Reference in New Issue
Block a user