17 Commits
0.0.7 ... 0.2.0

Author SHA1 Message Date
e0bc8371ac adjust for postgres 2021-05-11 16:48:02 +02:00
7346da8419 move to psycopg2 2021-05-11 15:16:13 +02:00
fd9b673df9 change to postgres 2021-05-11 14:31:22 +02:00
c7dbaeabbb add application as aud in token 2021-05-07 14:40:40 +02:00
0911a73085 fix 2021-05-07 14:06:18 +02:00
1de73e99e3 message 2021-05-07 14:05:57 +02:00
b44af0658a jwe 2021-05-07 13:28:12 +02:00
309b4c6ba8 authe 2021-05-07 12:24:59 +02:00
a921fb6a0f changes 2021-05-07 12:15:30 +02:00
f56db65012 pubkey stuff, remove debug 2021-05-06 16:55:39 +02:00
ef0793be4e pubkey stuff 2021-05-06 16:52:16 +02:00
3f2442e259 pubkey stuff 2021-05-06 16:50:17 +02:00
78439a7ed8 pubkey stuff 2021-05-06 16:46:19 +02:00
0377278ea0 pubkey stuff 2021-05-06 16:37:32 +02:00
49e8aa43b4 use rs256 2021-05-06 15:42:46 +02:00
35a997774f fix in claims handling 2021-05-06 15:22:43 +02:00
08734cb82c remove x from private claims 2021-01-27 13:31:34 +01:00
8 changed files with 201 additions and 172 deletions

View File

@ -16,8 +16,9 @@ ENV JWT_SECRET='streng_geheim'
RUN \
apt update && \
apt install -y libmariadbclient-dev && \
pip3 install mariadb && \
apt install -y postgresql-client-common && \
pip3 install psycopg2 && \
pip3 install loguru && \
pip3 install dateparser && \
pip3 install connexion && \
pip3 install connexion[swagger-ui] && \

128
auth.py
View File

@ -3,9 +3,10 @@ import connexion
from jose import JWTError, jwt
import werkzeug
import os
import mariadb
import psycopg2
from collections import namedtuple
from pbkdf2 import crypt
from loguru import logger
DB_USER = os.environ["DB_USER"]
DB_PASS = os.environ["DB_PASS"]
@ -13,7 +14,9 @@ DB_HOST = os.environ["DB_HOST"]
DB_NAME = os.environ["DB_NAME"]
JWT_ISSUER = os.environ["JWT_ISSUER"]
JWT_SECRET = os.environ["JWT_SECRET"]
class NoUserException(Exception):
@ -26,60 +29,71 @@ class PasswordMismatchException(Exception):
pass
UserEntry = namedtuple('UserEntry', ['id', 'login', 'expiry', 'claims'])
UserEntry = namedtuple('UserEntry', ['id', 'login', 'pwhash', 'expiry', 'claims'])
JWT_PRIV_KEY = ""
try:
JWT_PRIV_KEY = os.environ["JWT_PRIV_KEY"]
except KeyError:
with open('/opt/app/config/authservice.key', 'r') as f:
JWT_PRIV_KEY = f.read()
JWT_PUB_KEY = ""
try:
JWT_PUB_KEY = os.environ["JWT_PUB_KEY"]
except KeyError:
with open('/opt/app/config/authservice.pub', 'r') as f:
JWT_PUB_KEY = f.read()
def getUserEntryFromDB(application: str, login: str):
conn = None
cur = None
try:
conn = mariadb.connect(user = DB_USER, password = DB_PASS,
host = DB_HOST, database = DB_NAME)
conn = psycopg2.connect(user = DB_USER, password = DB_PASS,
host = DB_HOST, database = DB_NAME)
conn.autocommit = False
cur = conn.cursor(dictionary=True)
cur.execute("SELECT id, pwhash, expiry FROM user_application" +
" WHERE application = ? AND login = ?",
[application, login])
resObj = cur.next()
print("DEBUG: getUserEntryFromDB: resObj: {}".format(resObj))
if not resObj:
raise NoUserException()
invObj = cur.next()
if invObj:
raise ManyUsersException()
userObj = None
with conn.cursor() as cur:
cur.execute("SELECT id, pwhash, expiry FROM user_application_v" +
" WHERE application = %s AND login = %s",
(application, login))
userObj = cur.fetchone()
logger.debug("getUserEntryFromDB: userObj: {}".format(userObj))
if not userObj:
raise NoUserException()
invObj = cur.fetchone()
if invObj:
raise ManyUsersException()
userId = resObj["id"]
cur.execute("SELECT user, `key`, `value` FROM claims_for_user where user = ?",
[userId])
claims = {}
for claimObj in cur:
print("DEBUG: getUserEntryFromDB: add claim {} -> {}".format(claimObj["key"], claimObj["value"]))
if claimObj["key"] in claims:
if isinstance(claimObj["key"], list):
claims[claimObj["key"]].append(claimObj["value"])
with conn.cursor() as cur:
cur.execute('SELECT key, value FROM claims_for_user_v where "user" = %s and application = %s',
(userObj[0], application))
for claimObj in cur:
logger.debug("getUserEntryFromDB: add claim {} -> {}".format(claimObj[0], claimObj[1]))
if claimObj[0] in claims:
if isinstance(claims[claimObj[0]], list):
claims[claimObj[0]].append(claimObj[1])
else:
claims[claimObj[0]] = [ claims[claimObj[0]] ]
claims[claimObj[0]].append(claimObj[1])
else:
claims[claimObj["key"]] = [ claims[claimObj["key"]] ]
claims[claimObj["key"]].append(claimObj["value"])
else:
claims[claimObj["key"]] = claimObj["value"]
claims[claimObj[0]] = claimObj[1]
userEntry = UserEntry(id=userId, login=login, expiry=resObj["expiry"], claims=claims)
userEntry = UserEntry(id=userObj[0], login=login, pwhash=userObj[1], expiry=userObj[2], claims=claims)
return userEntry, resObj["pwhash"]
except mariadb.Error as err:
return userEntry
except psycopg2.Error as err:
raise Exception("Error when connecting to database: {}".format(err))
finally:
if cur:
cur.close()
if conn:
conn.rollback()
conn.close()
def getUserEntry(application, login, password):
userEntry, pwhash = getUserEntryFromDB(application, login)
if pwhash != crypt(password, pwhash):
userEntry = getUserEntryFromDB(application, login)
if userEntry.pwhash != crypt(password, userEntry.pwhash):
raise PasswordMismatchException()
return userEntry
@ -97,25 +111,49 @@ def generateToken(**args):
"iss": JWT_ISSUER,
"iat": int(timestamp),
"exp": int(timestamp + userEntry.expiry),
"sub": str(userEntry.id)
"sub": str(userEntry.id),
"aud": application
}
logger.debug("claims: {}".format(userEntry.claims))
for claim in userEntry.claims.items():
# print("DEBUG: generateToken: add claim {} -> {}".format(claim[0], claim[1]))
payload["x-{}".format(claim[0])] = claim[1]
logger.debug("generateToken: add claim {}".format(claim))
payload[claim[0]] = claim[1]
return jwt.encode(payload, JWT_SECRET)
return jwt.encode(payload, JWT_PRIV_KEY, algorithm='RS256')
except NoUserException:
print("ERROR: generateToken: no user found, login or application wrong")
logger.error("generateToken: no user found, login or application wrong")
raise werkzeug.exceptions.Unauthorized()
except ManyUsersException:
print("ERROR: generateToken: too many users found")
logger.error("generateToken: too many users found")
raise werkzeug.exceptions.Unauthorized()
except PasswordMismatchException:
print("ERROR: generateToken: wrong password")
logger.error("generateToken: wrong password")
raise werkzeug.exceptions.Unauthorized()
except KeyError:
print("ERROR: generateToken: application, login or password missing")
logger.error("generateToken: application, login or password missing")
raise werkzeug.exceptions.Unauthorized()
except Exception as e:
print("ERROR: generateToken: unspecific exception: {}".format(str(e)))
logger.error("generateToken: unspecific exception: {}".format(str(e)))
raise werkzeug.exceptions.Unauthorized()
def generateTokenFromEnc(**args):
cryptContent = args["body"]
raise werkzeug.exceptions.NotImplemented("Stay tuned, will be added soon")
return str(cryptContent)
def getPubKey():
return JWT_PUB_KEY
def decodeToken(token):
try:
return jwt.decode(token, JWT_PUB_KEY, audience="test")
except JWTError as e:
logger.error("decodeToken: {}".format(e))
raise werkzeug.exceptions.Unauthorized()
def getSecret(user, token_info):
return '''
You are user_id {user} and the secret is 'wbevuec'.
Decoded token claims: {token_info}.
'''.format(user=user, token_info=token_info)

View File

@ -4,5 +4,5 @@ IMAGE_NAME="registry.hottis.de/wolutator/authservice"
VERSION=0.0.1
docker build -t ${IMAGE_NAME}:${VERSION} .
docker push ${IMAGE_NAME}:${VERSION}
# docker push ${IMAGE_NAME}:${VERSION}

View File

@ -1,111 +1,61 @@
CREATE DATABASE `authservice`;
USE `authservice`;
create sequence application_s start with 1 increment by 1;
create table application_t (
id integer primary key not null default nextval('application_s'),
name varchar(128) not null unique
);
CREATE TABLE `applications` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(128) NOT NULL,
CONSTRAINT PRIMARY KEY (`id`),
CONSTRAINT UNIQUE KEY `uk_applications_name` (`name`)
) ENGINE=InnoDB;
create sequence user_s start with 1 increment by 1;
create table user_t (
id integer primary key not null default nextval('user_s'),
login varchar(64) not null unique,
pwhash varchar(64) not null,
expiry integer not null default 600
);
CREATE TABLE `users` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`login` varchar(64) NOT NULL,
`pwhash` varchar(64) NOT NULL,
`expiry` int(10) unsigned NOT NULL DEFAULT 600,
CONSTRAINT PRIMARY KEY (`id`),
CONSTRAINT UNIQUE KEY `uk_users_login` (`login`)
) ENGINE=InnoDB;
create sequence claim_s start with 1 increment by 1;
create table claim_t (
id integer primary key not null default nextval('claim_s'),
key varchar(64) not null,
value varchar(64) not null,
application integer not null references application(id),
unique (key, value)
);
CREATE TABLE `claims` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`key` varchar(64) NOT NULL,
`value` varchar(1024) NOT NULL,
CONSTRAINT PRIMARY KEY (`id`),
CONSTRAINT UNIQUE KEY `uk_claims_key_value` (`key`, `value`)
) ENGINE=InnoDB;
create table user_claim_mapping_t (
"user" integer not null references user_t(id),
claim integer not null references claim_t(id),
unique ("user", claim)
);
CREATE TABLE `user_claims_mapping` (
`user` int(10) unsigned NOT NULL,
`claim` int(10) unsigned NOT NULL,
CONSTRAINT UNIQUE KEY `uk_user_claims_mapping` (`user`, `claim` ),
CONSTRAINT FOREIGN KEY `fk_user_claims_mapping_user` (`user`)
REFERENCES `users`(`id`),
CONSTRAINT FOREIGN KEY `fk_user_claims_mapping_claim` (`claim`)
REFERENCES `claims`(`id`)
) ENGINE=InnoDB;
create table user_application_mapping_t (
"user" integer not null references user_t(id),
application integer not null references application_t(id),
unique ("user", application)
);
CREATE TABLE `user_applications_mapping` (
`user` int(10) unsigned NOT NULL,
`application` int(10) unsigned NOT NULL,
CONSTRAINT UNIQUE KEY `uk_user_applications_mapping` (`user`, `application` ),
CONSTRAINT FOREIGN KEY `fk_user_applications_mapping_user` (`user`)
REFERENCES `users`(`id`),
CONSTRAINT FOREIGN KEY `fk_user_applications_mapping_application` (`application`)
REFERENCES `applications`(`id`)
) ENGINE=InnoDB;
CREATE OR REPLACE VIEW claims_for_user AS
SELECT u.id AS user,
c.`key` AS `key`,
c.`value` AS `value`
FROM users u,
claims c,
user_claims_mapping m
WHERE m.user = u.id AND
m.claim = c.id;
CREATE OR REPLACE VIEW user_application AS
SELECT u.login AS login,
u.pwhash AS pwhash,
u.id AS id,
u.expiry AS expiry,
create or replace view claims_for_user_v as
select u.id as "user",
a.name as application,
c.key as key,
c.value as value
from user_t u,
claim_t c,
user_claim_mapping_t m,
application_t a
where m.user = u.id and
m.claim = c.id and
a.id = c.application;
create or replace view user_application_v as
select u.login as login,
u.pwhash as pwhash,
u.id as id,
u.expiry as expiry,
a.name as application
FROM users u,
applications a,
user_applications_mapping m
WHERE u.id = m.user AND
a.id = m.application;
from user_t u,
application_t a,
user_application_mapping_t m
where u.id = m.user and
a.id = m.application;
CREATE USER 'authservice-ui'@'%' IDENTIFIED BY 'test123';
GRANT SELECT ON `user_application` TO 'authservice-ui'@'%';
GRANT SELECT ON `claims_for_user` TO 'authservice-ui'@'%';
CREATE USER 'authservice-cli'@'%' IDENTIFIED BY 'test123';
GRANT INSERT ON `users` TO 'authservice-cli'@'%';
GRANT INSERT ON `user_applications_mapping` TO 'authservice-cli'@'%';
FLUSH PRIVILEGES;
INSERT INTO `applications` (`name`) VALUES ('hv');
INSERT INTO `claims` (`key`, `value`) VALUES ('accesslevel', 'r');
INSERT INTO `claims` (`key`, `value`) VALUES ('accesslevel', 'rw');
-- password is 'test123'
INSERT INTO `users` (`login`, `pwhash`) VALUES ('wn', '$p5k2$186a0$dJXL0AjF$0HualDF92nyilDXPgSbaUn/UpFzSrpPx');
INSERT INTO `user_applications_mapping` (`user`, `application`)
VALUES(
(SELECT `id` FROM `users` WHERE `login` = 'wn'),
(SELECT `id` FROM `applications` WHERE `name` = 'hv')
);
INSERT INTO `user_claims_mapping` (`user`, `claim`)
VALUES(
(SELECT `id` FROM `users` WHERE `login` = 'wn'),
(SELECT `id` FROM `claims` WHERE `key` = 'accesslevel' AND `value` = 'rw')
);
-- password is 'geheim'
INSERT INTO `users` (`login`, `pwhash`) VALUES ('gregor', '$p5k2$186a0$Tcwps8Ar$TsypGB.y1dCB9pWOPz2X2SsxYqrTn3Fv');
INSERT INTO `user_applications_mapping` (`user`, `application`)
VALUES(
(SELECT `id` FROM `users` WHERE `login` = 'gregor'),
(SELECT `id` FROM `applications` WHERE `name` = 'hv')
);
INSERT INTO `user_claims_mapping` (`user`, `claim`)
VALUES(
(SELECT `id` FROM `users` WHERE `login` = 'gregor'),
(SELECT `id` FROM `claims` WHERE `key` = 'accesslevel' AND `value` = 'rw')
);

View File

@ -7,7 +7,7 @@ paths:
/auth:
post:
tags: [ "JWT" ]
summary: Return JWT token
summary: Accept login and password, return JWT token
operationId: auth.generateToken
requestBody:
content:
@ -21,11 +21,28 @@ paths:
'text/plain':
schema:
type: string
/authe:
post:
tags: [ "JWT" ]
summary: Accept encrypted set of credentials, return JWT token
operationId: auth.generateTokenFromEnc
requestBody:
content:
'text/plain':
schema:
type: string
responses:
'200':
description: JWT token
content:
'text/plain':
schema:
type: string
/secret:
get:
tags: [ "JWT" ]
summary: Return secret string
operationId: test.getSecret
operationId: auth.getSecret
responses:
'200':
description: secret response
@ -35,6 +52,19 @@ paths:
type: string
security:
- jwt: ['secret']
/pubkey:
get:
tags: [ "JWT" ]
summary: Get the public key of this issuer
operationId: auth.getPubKey
responses:
'200':
description: public key
content:
'text/plain':
schema:
type: string
components:
securitySchemes:
@ -42,7 +72,7 @@ components:
type: http
scheme: bearer
bearerFormat: JWT
x-bearerInfoFunc: test.decodeToken
x-bearerInfoFunc: auth.decodeToken
schemas:
User:
description: Application/Login/Password tuple

13
readme.md Normal file
View File

@ -0,0 +1,13 @@
Generate the RSA key pair using:
Private key (keep it secret!):
openssl genrsa -out authservice.key 2048
Extract the public key (publish it):
openssl rsa -in authservice.pem -outform PEM -pubout -out authservice.pub

24
test.py
View File

@ -1,20 +1,8 @@
from jose import JWTError, jwt
import os
import werkzeug
import connexion
import logging
logging.basicConfig(level=logging.INFO)
JWT_SECRET = os.environ['JWT_SECRET']
def decodeToken(token):
try:
return jwt.decode(token, JWT_SECRET)
except JWTError as e:
print("ERROR: decodeToken: {}".format(e))
raise werkzeug.exceptions.Unauthorized()
def getSecret(user, token_info):
return '''
You are user_id {user} and the secret is 'wbevuec'.
Decoded token claims: {token_info}.
'''.format(user=user, token_info=token_info)
app = connexion.App('authservice')
app.add_api('./openapi.yaml')
app.run(port=8080)

9
testjwe.py Normal file
View File

@ -0,0 +1,9 @@
from jose import jwe
JWT_PUB_KEY = os.environ["JWT_PUB_KEY"]
plainText = "BlaBlaBla123"
cryptText = jwe.encrypt(plainText, JWT_PUB_KEY, "A256GCM", "RSA-OAEP")
print(cryptText)