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