add functionality to add public key
also add authentication
This commit is contained in:
parent
ba88459551
commit
e55e8609bd
14 changed files with 716 additions and 0 deletions
49
servecerts/__init__.py
Normal file
49
servecerts/__init__.py
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
from flask import Flask
|
||||||
|
|
||||||
|
from flask_debugtoolbar import DebugToolbarExtension
|
||||||
|
from logging.handlers import RotatingFileHandler
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
def create_app(test_config=None):
|
||||||
|
# create and configure the app
|
||||||
|
app = Flask(__name__, instance_relative_config=True)
|
||||||
|
app.config.from_mapping(
|
||||||
|
SECRET_KEY='dev',
|
||||||
|
DATABASE=os.path.join(app.instance_path, 'servecerts.sqlite'),
|
||||||
|
)
|
||||||
|
|
||||||
|
app.debug = True
|
||||||
|
|
||||||
|
if test_config is None:
|
||||||
|
# load the instance config, if it exists, when not testing
|
||||||
|
app.config.from_pyfile('config.py', silent=True)
|
||||||
|
else:
|
||||||
|
# load the test config if passed in
|
||||||
|
app.config.from_mapping(test_config)
|
||||||
|
|
||||||
|
# ensure the instance folder exists
|
||||||
|
try:
|
||||||
|
os.makedirs(app.instance_path)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# a simple page that says hello
|
||||||
|
@app.route('/hello')
|
||||||
|
def hello():
|
||||||
|
return 'Hello World!'
|
||||||
|
|
||||||
|
from . import db
|
||||||
|
db.init_app(app)
|
||||||
|
|
||||||
|
from . import auth
|
||||||
|
app.register_blueprint(auth.bp)
|
||||||
|
|
||||||
|
from . import pubkeys
|
||||||
|
app.register_blueprint(pubkeys.bp)
|
||||||
|
app.add_url_rule('/', endpoint='pubkeys.index')
|
||||||
|
app.add_url_rule('/pubkeys/', endpoint='index')
|
||||||
|
|
||||||
|
return app
|
146
servecerts/auth.py
Normal file
146
servecerts/auth.py
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
import functools
|
||||||
|
|
||||||
|
from flask import (
|
||||||
|
Blueprint, flash, g, redirect, render_template, request, session, url_for
|
||||||
|
)
|
||||||
|
from werkzeug.security import check_password_hash, generate_password_hash
|
||||||
|
|
||||||
|
from servecerts.db import get_db
|
||||||
|
|
||||||
|
from flask_debugtoolbar import DebugToolbarExtension
|
||||||
|
import logging
|
||||||
|
|
||||||
|
bp = Blueprint('auth', __name__, url_prefix='/auth')
|
||||||
|
|
||||||
|
@bp.route('/register', methods=('GET', 'POST'))
|
||||||
|
def register():
|
||||||
|
if request.method == 'POST':
|
||||||
|
username = request.form['username']
|
||||||
|
password = request.form['password']
|
||||||
|
fullname = request.form['fullname']
|
||||||
|
email = request.form['email']
|
||||||
|
db = get_db()
|
||||||
|
error = None
|
||||||
|
|
||||||
|
if not username:
|
||||||
|
error = 'Username is required.'
|
||||||
|
elif not password:
|
||||||
|
error = 'Password is required.'
|
||||||
|
elif not fullname:
|
||||||
|
error = 'Fullname is required.'
|
||||||
|
elif not email:
|
||||||
|
error = 'Email is required.'
|
||||||
|
elif db.execute(
|
||||||
|
'SELECT id FROM user WHERE username = ?', (username,)
|
||||||
|
).fetchone() is not None:
|
||||||
|
error = 'User {} is already registered.'.format(username)
|
||||||
|
|
||||||
|
if error is None:
|
||||||
|
db.execute(
|
||||||
|
'INSERT INTO user (username, password, fullname, email) VALUES (?, ?, ?, ?)',
|
||||||
|
(username, generate_password_hash(password), fullname, email)
|
||||||
|
)
|
||||||
|
db.commit()
|
||||||
|
return redirect(url_for('auth.login'))
|
||||||
|
|
||||||
|
flash(error)
|
||||||
|
|
||||||
|
return render_template('auth/register.html')
|
||||||
|
|
||||||
|
@bp.route('/<int:id>/update', methods=('GET', 'POST'))
|
||||||
|
def update(id):
|
||||||
|
user = get_user(id)
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
username = request.form['username']
|
||||||
|
fullname = request.form['fullname']
|
||||||
|
password = request.form['password']
|
||||||
|
email = request.form['email']
|
||||||
|
error = None
|
||||||
|
|
||||||
|
if not username:
|
||||||
|
error = 'Username is required.'
|
||||||
|
elif not password:
|
||||||
|
error = 'Password is required.'
|
||||||
|
elif not fullname:
|
||||||
|
error = 'Fullname is required.'
|
||||||
|
elif not email:
|
||||||
|
error = 'Email is required.'
|
||||||
|
|
||||||
|
if error is not None:
|
||||||
|
flash(error)
|
||||||
|
else:
|
||||||
|
db = get_db()
|
||||||
|
db.execute(
|
||||||
|
'UPDATE user SET username = ?, password = ?, fullname = ?, email = ?'
|
||||||
|
' WHERE id = ?',
|
||||||
|
(username, generate_password_hash(password), fullname, email, id)
|
||||||
|
)
|
||||||
|
db.commit()
|
||||||
|
return redirect(url_for('pubkeys.index'))
|
||||||
|
|
||||||
|
|
||||||
|
return render_template('auth/update.html', user=user)
|
||||||
|
|
||||||
|
@bp.route('/login', methods=('GET', 'POST'))
|
||||||
|
def login():
|
||||||
|
if request.method == 'POST':
|
||||||
|
username = request.form['username']
|
||||||
|
password = request.form['password']
|
||||||
|
db = get_db()
|
||||||
|
error = None
|
||||||
|
user = db.execute(
|
||||||
|
'SELECT * FROM user WHERE username = ?', (username,)
|
||||||
|
).fetchone()
|
||||||
|
|
||||||
|
if user is None:
|
||||||
|
error = 'Incorrect username.'
|
||||||
|
elif not check_password_hash(user['password'], password):
|
||||||
|
error = 'Incorrect password.'
|
||||||
|
|
||||||
|
if error is None:
|
||||||
|
session.clear()
|
||||||
|
session['user_id'] = user['id']
|
||||||
|
return redirect(url_for('pubkeys.index'))
|
||||||
|
|
||||||
|
flash(error)
|
||||||
|
|
||||||
|
return render_template('auth/login.html')
|
||||||
|
|
||||||
|
@bp.before_app_request
|
||||||
|
def load_logged_in_user():
|
||||||
|
user_id = session.get('user_id')
|
||||||
|
|
||||||
|
if user_id is None:
|
||||||
|
g.user = None
|
||||||
|
else:
|
||||||
|
g.user = get_db().execute(
|
||||||
|
'SELECT * FROM user WHERE id = ?', (user_id,)
|
||||||
|
).fetchone()
|
||||||
|
|
||||||
|
@bp.route('/logout')
|
||||||
|
def logout():
|
||||||
|
session.clear()
|
||||||
|
return redirect(url_for('pubkeys.index'))
|
||||||
|
|
||||||
|
def login_required(view):
|
||||||
|
@functools.wraps(view)
|
||||||
|
def wrapped_view(**kwargs):
|
||||||
|
if g.user is None:
|
||||||
|
return redirect(url_for('auth.login'))
|
||||||
|
|
||||||
|
return view(**kwargs)
|
||||||
|
|
||||||
|
return wrapped_view
|
||||||
|
|
||||||
|
|
||||||
|
def get_user(id):
|
||||||
|
user = get_db().execute(
|
||||||
|
'SELECT * FROM user WHERE id = ?', (id,)
|
||||||
|
).fetchone()
|
||||||
|
|
||||||
|
if user is None:
|
||||||
|
abort(404, "User id {0} does not exist.".format(id))
|
||||||
|
|
||||||
|
return user
|
||||||
|
|
41
servecerts/db.py
Normal file
41
servecerts/db.py
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
|
import click
|
||||||
|
from flask import current_app, g
|
||||||
|
from flask.cli import with_appcontext
|
||||||
|
|
||||||
|
|
||||||
|
def get_db():
|
||||||
|
if 'db' not in g:
|
||||||
|
g.db = sqlite3.connect(
|
||||||
|
current_app.config['DATABASE'],
|
||||||
|
detect_types=sqlite3.PARSE_DECLTYPES
|
||||||
|
)
|
||||||
|
g.db.row_factory = sqlite3.Row
|
||||||
|
|
||||||
|
return g.db
|
||||||
|
|
||||||
|
|
||||||
|
def close_db(e=None):
|
||||||
|
db = g.pop('db', None)
|
||||||
|
|
||||||
|
if db is not None:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
def init_db():
|
||||||
|
db = get_db()
|
||||||
|
|
||||||
|
with current_app.open_resource('schema.sql') as f:
|
||||||
|
db.executescript(f.read().decode('utf8'))
|
||||||
|
|
||||||
|
|
||||||
|
@click.command('init-db')
|
||||||
|
@with_appcontext
|
||||||
|
def init_db_command():
|
||||||
|
"""Clear the existing data and create new tables."""
|
||||||
|
init_db()
|
||||||
|
click.echo('Initialized the database.')
|
||||||
|
|
||||||
|
def init_app(app):
|
||||||
|
app.teardown_appcontext(close_db)
|
||||||
|
app.cli.add_command(init_db_command)
|
156
servecerts/pubkeys.py
Normal file
156
servecerts/pubkeys.py
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
from flask import (
|
||||||
|
Blueprint, flash, g, redirect, render_template, request, url_for
|
||||||
|
)
|
||||||
|
from werkzeug.exceptions import abort
|
||||||
|
|
||||||
|
from servecerts.auth import login_required
|
||||||
|
from servecerts.db import get_db
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import base64
|
||||||
|
|
||||||
|
#bp = Blueprint('pubkeys', __name__, url_prefix='/pubkeys')
|
||||||
|
bp = Blueprint('pubkeys', __name__, url_prefix='/pubkeys')
|
||||||
|
|
||||||
|
@bp.route('/')
|
||||||
|
@login_required
|
||||||
|
def index():
|
||||||
|
db = get_db()
|
||||||
|
pubkeys = db.execute(
|
||||||
|
'SELECT'
|
||||||
|
' p.id, key_name, fullname, ssh_pubkey, p.created, user_id, revoked, deleted'
|
||||||
|
' FROM pubkeys p'
|
||||||
|
' JOIN user u ON p.user_id = u.id'
|
||||||
|
' ORDER BY deleted ASC, revoked ASC, p.created DESC'
|
||||||
|
).fetchall()
|
||||||
|
users = db.execute(
|
||||||
|
'SELECT * FROM user WHERE id = ?', (g.user['id'],)
|
||||||
|
).fetchone()
|
||||||
|
return render_template('pubkeys/index.html', pubkeys=pubkeys, users=users)
|
||||||
|
|
||||||
|
def get_pubkey(id, check_user=True):
|
||||||
|
pubkey = get_db().execute(
|
||||||
|
'SELECT p.id, ssh_pubkey, key_name, user_id, fingerprint, deleted'
|
||||||
|
' FROM pubkeys p JOIN user u ON p.user_id = u.id'
|
||||||
|
' WHERE p.id = ?', (id,)
|
||||||
|
).fetchone()
|
||||||
|
|
||||||
|
if pubkey is None:
|
||||||
|
abort(404, "Pubkey id {0} does not exist.".format(id))
|
||||||
|
|
||||||
|
if check_user and pubkey['user_id'] != g.user['id']:
|
||||||
|
abort(403)
|
||||||
|
|
||||||
|
return pubkey
|
||||||
|
|
||||||
|
@bp.route('/create', methods=('GET', 'POST'))
|
||||||
|
@login_required
|
||||||
|
def create():
|
||||||
|
if request.method == 'POST':
|
||||||
|
key_name = request.form['key_name']
|
||||||
|
ssh_pubkey = request.form['ssh_pubkey']
|
||||||
|
splitkey = ssh_pubkey.split(' ')
|
||||||
|
enc = splitkey.pop(0)
|
||||||
|
key = splitkey.pop(0)
|
||||||
|
comment = ' '.join(splitkey)
|
||||||
|
fingerprint = base64.b64encode(hashlib.sha256(base64.b64decode(key)).digest()).rstrip("=")
|
||||||
|
print fingerprint
|
||||||
|
|
||||||
|
db = get_db()
|
||||||
|
ckfp = db.execute(
|
||||||
|
'SELECT id, fingerprint, deleted FROM pubkeys WHERE fingerprint = ?', (fingerprint, )
|
||||||
|
).fetchone()
|
||||||
|
|
||||||
|
if ckfp != None:
|
||||||
|
if ckfp['fingerprint'] == fingerprint:
|
||||||
|
if ckfp['deleted'] == 0:
|
||||||
|
error = "Key exists already"
|
||||||
|
else:
|
||||||
|
error = "Key was deleted before -> reactivate it again!"
|
||||||
|
flash(error)
|
||||||
|
return redirect(url_for('pubkeys.update', id=ckfp['id']))
|
||||||
|
|
||||||
|
if not key_name:
|
||||||
|
key_name = comment
|
||||||
|
error = None
|
||||||
|
|
||||||
|
if not ssh_pubkey:
|
||||||
|
error = 'SSH-Pubkey is required.'
|
||||||
|
|
||||||
|
if error is not None:
|
||||||
|
flash(error)
|
||||||
|
else:
|
||||||
|
#db = get_db()
|
||||||
|
db.execute(
|
||||||
|
'INSERT INTO pubkeys (ssh_pubkey, key_name, user_id, fingerprint)'
|
||||||
|
' VALUES (?, ?, ?, ?)',
|
||||||
|
(ssh_pubkey, key_name, g.user['id'], fingerprint)
|
||||||
|
)
|
||||||
|
db.commit()
|
||||||
|
return redirect(url_for('pubkeys.index'))
|
||||||
|
|
||||||
|
return render_template('pubkeys/create.html')
|
||||||
|
|
||||||
|
@bp.route('/<int:id>/update', methods=('GET', 'POST'))
|
||||||
|
@login_required
|
||||||
|
def update(id):
|
||||||
|
pubkey = get_pubkey(id)
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
key_name = request.form['key_name']
|
||||||
|
deleted = request.form.get('deleted')
|
||||||
|
error = None
|
||||||
|
|
||||||
|
if not key_name:
|
||||||
|
error = 'Key-ID is required.'
|
||||||
|
|
||||||
|
deleted = 0 if not deleted else 1
|
||||||
|
# if not ssh_pubkey:
|
||||||
|
# error = "SSH-Pubkey is requred."
|
||||||
|
|
||||||
|
if error is not None:
|
||||||
|
flash(error)
|
||||||
|
else:
|
||||||
|
db = get_db()
|
||||||
|
db.execute(
|
||||||
|
'UPDATE pubkeys SET key_name = ?, deleted = ?'
|
||||||
|
' WHERE id = ?',
|
||||||
|
(key_name, deleted, id)
|
||||||
|
)
|
||||||
|
db.commit()
|
||||||
|
return redirect(url_for('pubkeys.index'))
|
||||||
|
|
||||||
|
return render_template('pubkeys/update.html', pubkey=pubkey)
|
||||||
|
|
||||||
|
@bp.route('/<int:id>/delete', methods=('GET',))
|
||||||
|
@login_required
|
||||||
|
def delete(id):
|
||||||
|
get_pubkey(id)
|
||||||
|
db = get_db()
|
||||||
|
#db.execute('DELETE FROM pubkeys WHERE id = ?', (id,))
|
||||||
|
db.execute('UPDATE pubkeys SET deleted = 1 WHERE id = ?', (id,))
|
||||||
|
db.execute('UPDATE certificates SET deleted = 1 WHERE pubkey_id = ?', (id,))
|
||||||
|
db.commit()
|
||||||
|
return redirect(url_for('pubkeys.index'))
|
||||||
|
|
||||||
|
@bp.route('/<int:id>/deletefinal', methods=('GET',))
|
||||||
|
@login_required
|
||||||
|
def deletefinal(id):
|
||||||
|
get_pubkey(id)
|
||||||
|
db = get_db()
|
||||||
|
db.execute('DELETE FROM pubkeys WHERE id = ?', (id,))
|
||||||
|
#db.execute('UPDATE certificates SET deleted = 1 WHERE pubkey_id = ?', (id,))
|
||||||
|
db.commit()
|
||||||
|
return redirect(url_for('pubkeys.index'))
|
||||||
|
|
||||||
|
@bp.route('/<int:id>/revoke', methods=('POST', 'GET'))
|
||||||
|
@login_required
|
||||||
|
def revoke(id):
|
||||||
|
get_pubkey(id)
|
||||||
|
db = get_db()
|
||||||
|
db.execute(
|
||||||
|
'UPDATE pubkeys SET revoked = 1'
|
||||||
|
' WHERE id = ?', (id,)
|
||||||
|
)
|
||||||
|
db.commit()
|
||||||
|
return redirect(url_for('pubkeys.index'))
|
60
servecerts/schema.sql
Normal file
60
servecerts/schema.sql
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
DROP TABLE IF EXISTS user;
|
||||||
|
DROP TABLE IF EXISTS pubkeys;
|
||||||
|
DROP TABLE IF EXISTS certificates;
|
||||||
|
DROP TABLE IF EXISTS settings;
|
||||||
|
|
||||||
|
CREATE TABLE settings (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
current_serialnumber INTEGER NOT NULL,
|
||||||
|
default_principals TEXT,
|
||||||
|
default_commands TEXT,
|
||||||
|
default_capabilities TEXT,
|
||||||
|
default_client_from TEXT,
|
||||||
|
current_timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
username TEXT UNIQUE NOT NULL,
|
||||||
|
password TEXT NOT NULL,
|
||||||
|
fullname TEXT NOT NULL,
|
||||||
|
email TEXT NOT NULL,
|
||||||
|
principals TEXT,
|
||||||
|
commands TEXT DEFAULT username NOT NULL,
|
||||||
|
capabilities TEXT,
|
||||||
|
client_from TEXT,
|
||||||
|
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE pubkeys (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
key_name TEXT NOT NULL,
|
||||||
|
ssh_pubkey TEXT NOT NULL,
|
||||||
|
fingerprint TEXT,
|
||||||
|
revoked INTEGER DEFAULT 0 NOT NULL,
|
||||||
|
deleted INTEGER DEFAULT 0 NOT NULL,
|
||||||
|
userca INTEGER DEFAULT 0 NOT NULL,
|
||||||
|
hostca INTEGER DEFAULT 0 NOT NULL,
|
||||||
|
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES user (id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE certificates (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
pubkey_id INTEGER NOT NULL,
|
||||||
|
key_id TEXT NOT NULL,
|
||||||
|
serial INTEGER NOT NULL,
|
||||||
|
principals TEXT,
|
||||||
|
commands TEXT,
|
||||||
|
capabilities TEXT,
|
||||||
|
client_from TEXT,
|
||||||
|
revoked INTEGER DEFAULT 0 NOT NULL,
|
||||||
|
deleted INTEGER DEFAULT 0 NOT NULL,
|
||||||
|
valid_from TIMESTAMP,
|
||||||
|
valid_unitl TIMESTAMP,
|
||||||
|
expired INTEGER DEFAULT 0 NOT NULL,
|
||||||
|
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (pubkey_id) REFERENCES pubkeys (id)
|
||||||
|
);
|
||||||
|
|
28
servecerts/static/style.css
Normal file
28
servecerts/static/style.css
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
html { font-family: sans-serif; background: #eee; padding: 1rem; }
|
||||||
|
body { max-width: 960px; margin: 0 auto; background: white; }
|
||||||
|
h1 { font-family: serif; color: #377ba8; margin: 1rem 0; }
|
||||||
|
a { color: #377ba8; }
|
||||||
|
hr { border: none; border-top: 1px solid lightgray; }
|
||||||
|
nav { background: lightgray; display: flex; align-items: center; padding: 0 0.5rem; }
|
||||||
|
nav h1 { flex: auto; margin: 0; }
|
||||||
|
nav h1 a { text-decoration: none; padding: 0.25rem 0.5rem; }
|
||||||
|
nav ul { display: flex; list-style: none; margin: 0; padding: 0; }
|
||||||
|
nav ul li a, nav ul li span, header .action { display: block; padding: 0.5rem; }
|
||||||
|
.content { padding: 0 1rem 1rem; }
|
||||||
|
.content > header { border-bottom: 1px solid lightgray; display: flex; align-items: flex-end; }
|
||||||
|
.content > header h1 { flex: auto; margin: 1rem 0 0.25rem 0; }
|
||||||
|
.flash { margin: 1em 0; padding: 1em; background: #cae6f6; border: 1px solid #377ba8; }
|
||||||
|
.post > header { display: flex; align-items: flex-end; font-size: 0.85em; }
|
||||||
|
.post > header > div:first-of-type { flex: auto; }
|
||||||
|
.post > header h1 { font-size: 1.5em; margin-bottom: 0; }
|
||||||
|
.post .about { color: slategray; font-style: italic; }
|
||||||
|
.post .body { white-space: pre-line; }
|
||||||
|
.content:last-child { margin-bottom: 0; }
|
||||||
|
.content form { margin: 1em 0; display: flex; flex-direction: column; }
|
||||||
|
.content label { font-weight: bold; margin-bottom: 0.5em; }
|
||||||
|
.content input, .content textarea { margin-bottom: 1em; }
|
||||||
|
.content textarea { min-height: 12em; resize: vertical; }
|
||||||
|
.content div { overflow: hidden; word-wrap: break-word; }
|
||||||
|
.revoked, .deleted {text-decoration: line-through; color: slategray; font-style: italic;}
|
||||||
|
input.danger { color: #cc2f2e; }
|
||||||
|
input[type=submit] { align-self: start; min-width: 10em; }
|
15
servecerts/templates/auth/login.html
Normal file
15
servecerts/templates/auth/login.html
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
<h1>{% block title %}Log In{% endblock %}</h1>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form method="post">
|
||||||
|
<label for="username">Username</label>
|
||||||
|
<input name="username" id="username" required>
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<input type="password" name="password" id="password" required>
|
||||||
|
<input type="submit" value="Log In">
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
19
servecerts/templates/auth/register.html
Normal file
19
servecerts/templates/auth/register.html
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
<h1>{% block title %}Register new User{% endblock %}</h1>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form method="post">
|
||||||
|
<label for="username">Username</label>
|
||||||
|
<input name="username" id="username" required>
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<input type="password" name="password" id="password" required>
|
||||||
|
<label for="fullname">Anzeigename</label>
|
||||||
|
<input name="fullname" id="fullname" required>
|
||||||
|
<label for="email">Emailaddress</label>
|
||||||
|
<input name="email" id="email" required>
|
||||||
|
<input type="submit" value="Register">
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
37
servecerts/templates/auth/update.html
Normal file
37
servecerts/templates/auth/update.html
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
<h1>{% block title %}Edit User{% endblock %}</h1>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form method="post">
|
||||||
|
<label for="fullname">Fullname ({{ user['fullname'] }})</label>
|
||||||
|
<input name="fullname" id="fullname"
|
||||||
|
value="{{ request.form['fullname'] or user['fullname'] }}" required>
|
||||||
|
<label for="username">Username ({{ user['username'] }})</label>
|
||||||
|
<input name="username" id="username"
|
||||||
|
value="{{ request.form['username'] or user['username'] }}" required>
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<input type="password" name="password" id="password"
|
||||||
|
value="" required>
|
||||||
|
<label for="email">Email: ({{ user['email'] }})</label>
|
||||||
|
<input name="email" id="email"
|
||||||
|
value="{{ request.form['email'] or user['email'] }}" required>
|
||||||
|
<label for="principals">Principals: ({{ user['principals'] }})</label>
|
||||||
|
<input name="principals" id="principals"
|
||||||
|
value="{{ request.form['principals'] or user['principals'] }}">
|
||||||
|
<label for="allowed_commands">Allowed commands: ({{ user['allowed_commands'] }})</label>
|
||||||
|
<input name="allowed_commands" id="allowed_commands"
|
||||||
|
value="{{ request.form['allowed_commands'] or user['allowed_commands'] }}">
|
||||||
|
<label for="client_from">Client from: ({{ user['client_from'] }})</label>
|
||||||
|
<input name="client_from" id="client_from"
|
||||||
|
value="{{ request.form['client_from'] or user['client_from'] }}">
|
||||||
|
<label for="capabilities">Capabilities: ({{ user['capabilities'] }})</label>
|
||||||
|
<input name="capabilities" id="capabilities"
|
||||||
|
value="{{ request.form['capabilities'] or user['capabilities'] }}">
|
||||||
|
<input type="submit" value="Save">
|
||||||
|
</form>
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
{% endblock %}
|
25
servecerts/templates/base.html
Normal file
25
servecerts/templates/base.html
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<!doctype html>
|
||||||
|
<title>{% block title %}{% endblock %} - Serve Certs</title>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||||
|
<nav>
|
||||||
|
<h1><a href="{{ url_for('pubkeys.index') }}">SSH-Certificates</a></h1>
|
||||||
|
"{{ request.environ.get('HTTP_X_REAL_IP', request.remote_addr) }}"
|
||||||
|
<ul>
|
||||||
|
{% if g.user %}
|
||||||
|
<li><a class="action" href="{{ url_for('auth.update', id=g.user['id']) }}">{{ g.user['username'] }} (Settings)</a>
|
||||||
|
<li><a href="{{ url_for('auth.logout') }}">Log Out</a>
|
||||||
|
{% else %}
|
||||||
|
<li><a href="{{ url_for('auth.register') }}">Register</a>
|
||||||
|
<li><a href="{{ url_for('auth.login') }}">Log In</a>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<section class="content">
|
||||||
|
<header>
|
||||||
|
{% block header %}{% endblock %}
|
||||||
|
</header>
|
||||||
|
{% for message in get_flashed_messages() %}
|
||||||
|
<div class="flash">{{ message }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</section>
|
50
servecerts/templates/certificates/index.html
Normal file
50
servecerts/templates/certificates/index.html
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
<h1>{% block title %}
|
||||||
|
{% if g.user %}
|
||||||
|
Certificates for Pubkey {{ g.pubkeys['fullname'] }}
|
||||||
|
{% else %}
|
||||||
|
Certificates
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}</h1>
|
||||||
|
{% if g.user %}
|
||||||
|
<a class="action" href="{{ url_for('certificates.create') }}">New</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% if g.user %}
|
||||||
|
{% for certificate in certificates %}
|
||||||
|
<article class="post">
|
||||||
|
<header>
|
||||||
|
<div>
|
||||||
|
<h1>Certificate ({{ certificate['id'] }}): {{ certificate['key_id'] }} </h1>
|
||||||
|
</div>
|
||||||
|
{% if g.user['id'] == pubkey['user_id'] %}
|
||||||
|
<a class="action" href="{{ url_for('certificates.revoke', id=certificate['id']) }}">Revoke</a>
|
||||||
|
{% endif %}
|
||||||
|
</header>
|
||||||
|
<p class="body{% if pubkey['revoked'] != 0 %} revoked {% endif %}">
|
||||||
|
Serialnumber: {{ certificate['serial'] }}{% if pubkey['revoked'] != 0 %} - revoked {% endif %}"
|
||||||
|
Validity duration: {{ certificate['valid_from'].strftime('%Y-%m-%d %H:%M') }} - {{ certificate['valid_until'].strftime('%Y-%m-%d %H:%M') }}
|
||||||
|
Principals: {{ certificate['principals'] }}
|
||||||
|
Valid Client IP: {{ certificate['from_ip'] }}
|
||||||
|
Allowed Command: {{ certificate['commands'] }}
|
||||||
|
Capabilities: {{ certificate['capabilities'] }}
|
||||||
|
</p>
|
||||||
|
<p class="about">created on {{ certificate['created'].strftime('%Y-%m-%d') }}</p>
|
||||||
|
</form>
|
||||||
|
<form action="{{ url_for('certificates.revoke', id=certificate['id']) }}" method="POST">
|
||||||
|
<input class="danger" type="submit" value="Revoke" onclick="return confirm('Are you sure?');">
|
||||||
|
</form>
|
||||||
|
</article>
|
||||||
|
{% if not loop.last %}
|
||||||
|
<hr>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<div class="danger">To view pubkeys and certificates, please log in</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
15
servecerts/templates/pubkeys/create.html
Normal file
15
servecerts/templates/pubkeys/create.html
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
<h1>{% block title %}New Public Key{% endblock %}</h1>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form method="post">
|
||||||
|
<label for="key_name">Key Identifier</label>
|
||||||
|
<input name="key_name" id="key_name" value="{{ request.form['key_name'] }}">
|
||||||
|
<label for="ssh_pubkey">SSH-Pubkey</label>
|
||||||
|
<textarea name="ssh_pubkey" id="ssh_pubkey" required>{{ request.form['ssh_pubkey'] }}</textarea>
|
||||||
|
<input type="submit" value="Save">
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
50
servecerts/templates/pubkeys/index.html
Normal file
50
servecerts/templates/pubkeys/index.html
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
<h1>{% block title %}
|
||||||
|
{% if g.user %}
|
||||||
|
Pubkeys for {{ g.user['fullname'] }}
|
||||||
|
{% else %}
|
||||||
|
Pubkeys
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}</h1>
|
||||||
|
{% if g.user %}
|
||||||
|
<a class="action" href="{{ url_for('pubkeys.create') }}">New</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% if g.user %}
|
||||||
|
{% for pubkey in pubkeys %}
|
||||||
|
{% if pubkey['deleted'] == 0 %}
|
||||||
|
<article class="post{% if pubkey['revoked'] != 0 %} revoked{% endif %}{% if pubkey['deleted'] != 0 %} deleted{% endif %}">
|
||||||
|
<header>
|
||||||
|
<div>
|
||||||
|
<h1>{% if pubkey['revoked'] != 0 %}<div class="danger">revoked key<div> - {% endif %}({{ pubkey['id'] }}): {{ pubkey['key_name'] }} </h1>
|
||||||
|
</div>
|
||||||
|
{% if g.user['id'] == pubkey['user_id'] %}
|
||||||
|
<a class="action" href="{{ url_for('pubkeys.update', id=pubkey['id']) }}">Edit</a>
|
||||||
|
{% if pubkey['deleted'] == 0 %}
|
||||||
|
<a class="action" href="{{ url_for('pubkeys.delete', id=pubkey['id']) }}" onclick="return confirm('Are you sure?');">Delete</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if pubkey['revoked'] == 0 %}
|
||||||
|
<a class="action" href="{{ url_for('pubkeys.revoke', id=pubkey['id']) }}" onclick="return confirm('Are you sure?');">Revoke</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</header>
|
||||||
|
<div name="ssh_pubkey" id="ssh_pubkey">{{ request.form['ssh_pubkey'] or pubkey['ssh_pubkey'] }}</div>
|
||||||
|
<p class="about">registered on {{ pubkey['created'].strftime('%Y-%m-%d') }}</p>
|
||||||
|
</form>
|
||||||
|
<form action="{{ url_for('pubkeys.revoke', id=pubkey['id']) }}" method="POST">
|
||||||
|
<input class="danger" type="submit" value="Revoke" onclick="return confirm('Are you sure?');">
|
||||||
|
</form>
|
||||||
|
</article>
|
||||||
|
{% if not loop.last %}
|
||||||
|
<hr>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<div class="danger">To view pubkeys and certificates, please log in</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
25
servecerts/templates/pubkeys/update.html
Normal file
25
servecerts/templates/pubkeys/update.html
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
<h1>{% block title %}Edit "{{ pubkey['fingerprint'] }}"{% endblock %}</h1>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form method="post">
|
||||||
|
<label for="key_name">SSH-Pubkey ({{ pubkey['id'] }}) {{ pubkey['deleted'] }}</label>
|
||||||
|
<input name="key_name" id="key_name"
|
||||||
|
value="{{ request.form['key_name'] or pubkey['key_name'] }}" required>
|
||||||
|
<label for="ssh_pubkey">SSH-Pubkey</label>
|
||||||
|
<textarea name="ssh_pubkey" id="ssh_pubkey" readonly>{{ request.form['ssh_pubkey'] or pubkey['ssh_pubkey'] }}</textarea>
|
||||||
|
<!--div class="about">{{ request.form['ssh_pubkey'] or pubkey['ssh_pubkey'] }}<div-->
|
||||||
|
<input type="checkbox" name="deleted" id="deleted" {% if pubkey['deleted'] != 0 %}checked="checked" {% endif %}>
|
||||||
|
<label for="deleted">Key marked as deleted</label>
|
||||||
|
<input type="submit" value="Save">
|
||||||
|
</form>
|
||||||
|
<hr>
|
||||||
|
<ul>
|
||||||
|
<li><form action="{{ url_for('pubkeys.delete', id=pubkey['id']) }}" method="post">
|
||||||
|
<input class="danger" type="submit" value="Delete" onclick="return confirm('Are you sure?');">
|
||||||
|
</form>
|
||||||
|
</ul>
|
||||||
|
{% endblock %}
|
Loading…
Reference in a new issue