summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore9
-rw-r--r--Cargo.toml (renamed from rust/Cargo.toml)0
-rw-r--r--README.md74
-rw-r--r--build.rs (renamed from rust/build.rs)0
-rw-r--r--clib/sendxmpp.c (renamed from rust/clib/sendxmpp.c)0
-rwxr-xr-xdemo.sh22
-rw-r--r--functions.py80
-rwxr-xr-xlogin-freebsd.sh3
-rwxr-xr-xlogin.py38
-rwxr-xr-xlogin_test.py20
-rw-r--r--rust/.gitignore4
-rw-r--r--rust/README.md40
-rwxr-xr-xserver.py82
-rw-r--r--src/apachelog.rs (renamed from rust/src/apachelog.rs)0
-rw-r--r--src/handler.rs (renamed from rust/src/handler.rs)0
-rw-r--r--src/main.rs (renamed from rust/src/main.rs)0
-rw-r--r--src/message.rs (renamed from rust/src/message.rs)0
-rw-r--r--src/sendxmpp.rs (renamed from rust/src/sendxmpp.rs)0
-rw-r--r--src/token.rs (renamed from rust/src/token.rs)0
19 files changed, 33 insertions, 339 deletions
diff --git a/.gitignore b/.gitignore
index 02e0d9f..4e79e5d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,6 @@
-__pycache__
-*swp
-*~
+/target/
+/.idea/
+/rust.iml
+
+# don't care about it now
+/Cargo.lock
diff --git a/rust/Cargo.toml b/Cargo.toml
index 5b183c4..5b183c4 100644
--- a/rust/Cargo.toml
+++ b/Cargo.toml
diff --git a/README.md b/README.md
index 08ce4e4..826c929 100644
--- a/README.md
+++ b/README.md
@@ -1,60 +1,40 @@
-# apache-auth-xmppmessage
+# auth-xmppessage
-Authenticate users using tokens sent via xmpp.
+### Compile
-This script is almost stateless, there is no database required.
-To protect against DoS it uses a lockfile, this way allowing only on
-instance at a time.
+It's written in rust, compile it with `cargo build`.
-## Install requirements
+### Run
- # pip
- pip3 install --user -r sleekxmpp==1.3.1
+```
+Usage: ./target/debug/auth_xmppmessage [options]
- # FreeBSD:
- pkg install py34-sleekxmpp
- pkg install ap24-mod_authnz_external24
+Options:
+ -j, --jid JID bot jid
+ -p, --password PASSWORD
+ bot password
+ -s, --secret SECRET server secret for token generation
+ -t, --time HOURS Validity of the token in hours (default 48)
+ -o, --port PORT TCP Port to listen on
+ -d, --debug Use loglevel Debug instead of Warn
+ -n, --nosend Don't send XMPP message, just print debug infos
+ -h, --help print this help menu
+```
+### Nginx configuration
-## Configuration
-
- DefineExternalAuth xmpp-login pipe /usr/local/etc/apache24/login.py
- <Location /foo>
- AuthType Basic
- AuthName "Login with Jabber ID and empty password to request a token"
- AuthBasicProvider external
- AuthExternalContext "validsec=7200;secret=adsasd;users=user1@jabber.org,user2@jabber.org;jid=bot@jabber.org;jid_pw=secret-xmpp-pw"
- AuthExternal xmpp-login
- Require valid-user
- </Location>
-
-### Options
-
-- validsec: timespan in which a token is valid.
- There are always 2 valid tokens, the current and the previous.
- The current is `token(now % validsec)`. The previous is `token(now % validsec - validsec)`.
- A token valid-range is determined by `% validsec` and NOT by the time the token was requested.
-- secret: random secret data. Used as a salt for the token.
-- users: comma separated list of JIDs that are allowed to receive tokens.
- Tokens are user-specific. User `A` cannot use the token from user `B`.
-- jid: JID of the bot who sends the tokens to the users.
-- jid\_pw: password of the bot.
-
-
-
-# nginx
-
-## configuration
-
- location /grafana {
- auth_request /_auth;
- # ...
- }
-
+```
location = /_auth {
- proxy_pass http://localhost:8081/;
+ proxy_pass http://127.0.0.1:8081/; # --port PORT
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI "$scheme://$host$request_uri";
+ proxy_set_header X-Allowed-Jid "JID1,JID2";
}
+ location /app {
+ satisfy any;
+ auth_request /_auth;
+ deny all;
+ }
+```
diff --git a/rust/build.rs b/build.rs
index 10c0d21..10c0d21 100644
--- a/rust/build.rs
+++ b/build.rs
diff --git a/rust/clib/sendxmpp.c b/clib/sendxmpp.c
index 3f06202..3f06202 100644
--- a/rust/clib/sendxmpp.c
+++ b/clib/sendxmpp.c
diff --git a/demo.sh b/demo.sh
deleted file mode 100755
index 67cb4dc..0000000
--- a/demo.sh
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/sh
-# Usage:
-# - No arguments: Request a token
-# - With arguments: Verify a token
-export IP=1.2.3.4
-export URI=/test
-export HTTP_HOST=www.example.com;
-export CONTEXT="validsec=60;secret=asdsad;users=yvesf@xapek.org,marc@xapek.org;jid=___;jid_pw=___"
-export SKIP_XMPP=1
-
-if [ -z "$1" ]; then # request token
- (
- echo "yvesf@xapek.org"
- echo ""
- ) | ./login.py
-else # verify token
- (
- echo "yvesf@xapek.org"
- echo "$1"
- ) | ./login.py
- echo "Result $?"
-fi
diff --git a/functions.py b/functions.py
deleted file mode 100644
index e26e43f..0000000
--- a/functions.py
+++ /dev/null
@@ -1,80 +0,0 @@
-import os
-import re
-import time
-import struct
-import hashlib
-from urllib.parse import quote as urlencode
-
-
-def _normalize_token(token):
- return re.sub(r"[^A-F0-9]", "", token.upper())
-
-
-def _generate_token(username, secret, time):
- input = "{}{}{}".format(secret, username, time).encode('utf-8')
- output = struct.unpack(b"<L", hashlib.md5(input).digest()[:4])[0]
- token = "{:02X}-{:02X}-{:02X}".format(
- (output >> 16) & 0xff, (output >> 8) & 0xff, output & 0xff)
- return token
-
-
-def file_lock(lock_file):
- from contextlib import contextmanager
-
- @contextmanager
- def file_lock():
- try:
- with open(lock_file, "x") as fh:
- try:
- yield
- except:
- raise
- finally:
- fh.close()
- os.remove(lock_file)
- except FileExistsError:
- raise Exception("Locking failed on {}".format(lock_file))
-
- return file_lock()
-
-
-def token_message(username, secret, validsec, url):
- time_now = int(time.time())
- time_now_start = int(time_now - time_now % validsec)
- time_next_end = time_now_start + 2 * validsec
- token = _generate_token(username, secret, time_now_start)
- message = "Username: {} Token: {}".format(username, token)
- message += "\nValid from: {} to: {}".format(
- time.strftime("%c %Z(%z)", time.gmtime(time_now_start)),
- time.strftime("%c %Z(%z)", time.gmtime(time_next_end)))
- if url is not None:
- message += re.sub('(https?://)(.*)',
- ' \\1' + urlencode(username) + ':' + urlencode(token) + '@\\2',
- url)
- return message
-
-
-def send_message(jid, password, recipient, message):
- import sleekxmpp
-
- def start(event):
- cl.send_message(mto=recipient, mtype='chat', mbody=message)
- cl.disconnect(wait=True)
-
- cl = sleekxmpp.ClientXMPP(jid, password)
- cl.add_event_handler("session_start", start, threaded=True)
- if cl.connect():
- cl.process(block=True)
- else:
- raise Exception("Unable to connect to xmpp server")
-
-
-def verify_token(username, password, conf_secret, conf_validsec):
- time_now = int(time.time())
- time_now_start = int(time_now - time_now % conf_validsec)
- time_prev_start = time_now_start - conf_validsec
- valid_tokens = list(map(_normalize_token, (
- _generate_token(username, conf_secret, time_now_start),
- _generate_token(username, conf_secret, time_prev_start)
- )))
- return _normalize_token(password) in valid_tokens
diff --git a/login-freebsd.sh b/login-freebsd.sh
deleted file mode 100755
index f943eec..0000000
--- a/login-freebsd.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/sh
-dir="`dirname \"$0\"`"
-/usr/local/bin/python3.4 "$dir/"login.py $*
diff --git a/login.py b/login.py
deleted file mode 100755
index 24f62a1..0000000
--- a/login.py
+++ /dev/null
@@ -1,38 +0,0 @@
-#!/usr/bin/env python3.4
-import os
-import sys
-import functions
-
-
-def run(config):
- conf_users = config['users'].split(',')
- conf_secret = config['secret']
- conf_validsec = int(config['validsec'])
- conf_jid = config['jid']
- conf_jid_pw = config['jid_pw']
-
- # reading the credential supplied in a pipe from apache
- username = sys.stdin.readline().strip()
- password = sys.stdin.readline().strip()
-
- if password == "" and username in conf_users:
- # avoid spamming by allowing only one message sent at a time
- lockfile = os.path.basename(__file__)
- with functions.file_lock("/tmp/lock." + lockfile):
- message = functions.token_message(username, conf_secret, conf_validsec,
- os.getenv("URI"), os.getenv("HTTP_HOST"))
- if os.getenv("SKIP_XMPP"): # used for testing
- print(message)
- else:
- functions.send_message(conf_jid, conf_jid_pw, username, message)
- elif username in conf_users:
- if functions.verify_token(username, password, conf_secret, conf_validsec):
- return os.EX_OK
-
- return os.EX_NOPERM # fail by default
-
-
-if __name__ == "__main__":
- config = dict(map(lambda kv: kv.split("="),
- os.getenv("CONTEXT").split(";")))
- sys.exit(run(config))
diff --git a/login_test.py b/login_test.py
deleted file mode 100755
index 2bdbef3..0000000
--- a/login_test.py
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/usr/bin/env python3
-import unittest
-import functions
-
-
-class TestStringMethods(unittest.TestCase):
- def test_normalize(self):
- self.assertEqual(functions._normalize_token("A4-B4-C5"),
- "A4B4C5")
- self.assertEqual(functions._normalize_token("a4-b4-c5"),
- "A4B4C5")
- self.assertEqual(functions._normalize_token("a4b4c5"),
- "A4B4C5")
- self.assertEqual(functions._normalize_token("A4B4C5"),
- "A4B4C5")
-
-
-if __name__ == '__main__':
- unittest.main()
-
diff --git a/rust/.gitignore b/rust/.gitignore
deleted file mode 100644
index 3876e6f..0000000
--- a/rust/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
-target/
-
-# don't care about it now
-Cargo.lock
diff --git a/rust/README.md b/rust/README.md
deleted file mode 100644
index 7c07af9..0000000
--- a/rust/README.md
+++ /dev/null
@@ -1,40 +0,0 @@
-# auth-xmppessage
-
-### Compile
-
-It's written in rust, compile it with `cargo build`
-
-### Run
-
-```
-Usage: ./target/debug/auth_xmppmessage [options]
-
-Options:
- -j, --jid JID bot jid
- -p, --password PASSWORD
- bot password
- -s, --secret SECRET server secret for token generation
- -t, --time HOURS Validity of the token in hours (default 48)
- -o, --port PORT TCP Port to listen on
- -d, --debug Use loglevel Debug instead of Warn
- -n, --nosend Don't send XMPP message, just print debug infos
- -h, --help print this help menu
-```
-
-### Nginx configuration
-
-```
- location = /_auth {
- proxy_pass http://127.0.0.1:8081/; # --port PORT
- proxy_pass_request_body off;
- proxy_set_header Content-Length "";
- proxy_set_header X-Original-URI "$scheme://$host$request_uri";
- proxy_set_header X-Allowed-Jid "JID1,JID2";
- }
-
- location /app {
- satisfy any;
- auth_request /_auth;
- deny all;
- }
-``` \ No newline at end of file
diff --git a/server.py b/server.py
deleted file mode 100755
index 32f022a..0000000
--- a/server.py
+++ /dev/null
@@ -1,82 +0,0 @@
-#!/usr/bin/env python3
-import time
-import binascii
-import random
-import argparse
-import functions
-import logging
-from http.server import BaseHTTPRequestHandler, HTTPServer
-from socketserver import ThreadingMixIn
-
-logging.basicConfig(level=logging.INFO)
-
-LAST_REQUEST_TIME = 0
-CACHE = {}
-
-
-def send_token(conf, username, orig_uri):
- message = functions.token_message(username, conf.secret, conf.validsec, orig_uri)
- if conf.skip_xmpp: # used for testing
- print(message)
- else:
- functions.send_message(conf.jid, conf.password, username, message)
-
-
-class ThreadingSimpleServer(ThreadingMixIn, HTTPServer):
- pass
-
-
-class RequestHandler(BaseHTTPRequestHandler):
- def do_GET(self):
- global LAST_REQUEST_TIME, CACHE
- if 'Authorization' in self.headers:
- method, value = self.headers['Authorization'].split(' ')
- if method != 'Basic':
- self.send_response(400, 'Unsupported authentication method')
- elif value in CACHE and CACHE[value] > time.time() - 60: # cache cred for 60s for performance
- logging.info("Authorized (cached) %s", value)
- self.send_response(200, "OK go forward")
- else:
- username, password = binascii.a2b_base64(value.encode('utf-8')).decode('utf-8').split(':')
- if password == "" and username in conf.users:
- if LAST_REQUEST_TIME == 0 or time.time() - LAST_REQUEST_TIME > 15: # max 1 msg per 15 sec
- LAST_REQUEST_TIME = time.time()
- send_token(conf, username, self.headers['X-Original-URI'])
- self.send_response(401, "Token sent, retry")
- self.send_header("WWW-Authenticate", "Basic realm=\"xmppmessage auth\"")
- else:
- self.send_response(429, 'Too Many Requests')
- else:
- if functions.verify_token(username, password, conf.secret, conf.validsec):
- logging.info("Authorized %s", username)
- CACHE[value] = time.time()
- self.send_response(200, "OK go forward")
- else:
- logging.info("Denied %s", username)
- self.send_response(403, "Authentication failed, username or password wrong")
- else:
- self.send_response(401)
- self.send_header("WWW-Authenticate", "Basic realm=\"xmppmessage auth\"")
-
- self.end_headers()
-
-
-def run(conf):
- httpd = HTTPServer((conf.server_host, conf.server_port), RequestHandler)
- httpd.conf = conf
- httpd.serve_forever()
-
-
-if __name__ == "__main__":
- parser = argparse.ArgumentParser()
- parser.add_argument('--secret', default="".join([chr(random.randint(ord('0'), ord('Z'))) for x in range(20)]))
- parser.add_argument('--validsec', type=int, default=60 * 60 * 48)
- parser.add_argument('--user', '-u', nargs='+', default=['yvesf@xapek.org', 'marc@xapek.org'], dest='users')
- parser.add_argument('--jid', help="Bot jid", default="bot@xapek.org")
- parser.add_argument('--password', help="Bot jid password")
- parser.add_argument('--server-host', default="127.0.0.1")
- parser.add_argument('--server-port', default=8081, type=int)
- parser.add_argument('--skip-xmpp', default=False, type=bool)
-
- conf = parser.parse_args()
- run(conf)
diff --git a/rust/src/apachelog.rs b/src/apachelog.rs
index fb5d1a6..fb5d1a6 100644
--- a/rust/src/apachelog.rs
+++ b/src/apachelog.rs
diff --git a/rust/src/handler.rs b/src/handler.rs
index 97f5d5c..97f5d5c 100644
--- a/rust/src/handler.rs
+++ b/src/handler.rs
diff --git a/rust/src/main.rs b/src/main.rs
index 30d4f8b..30d4f8b 100644
--- a/rust/src/main.rs
+++ b/src/main.rs
diff --git a/rust/src/message.rs b/src/message.rs
index 0af1a13..0af1a13 100644
--- a/rust/src/message.rs
+++ b/src/message.rs
diff --git a/rust/src/sendxmpp.rs b/src/sendxmpp.rs
index 2db471e..2db471e 100644
--- a/rust/src/sendxmpp.rs
+++ b/src/sendxmpp.rs
diff --git a/rust/src/token.rs b/src/token.rs
index 2a2e446..2a2e446 100644
--- a/rust/src/token.rs
+++ b/src/token.rs