Add htpasswd password support

This commit is contained in:
Tim Van Baak 2023-06-19 11:34:52 -07:00
parent 0c86a78d51
commit 1dbf7ddb60
4 changed files with 88 additions and 6 deletions

View File

@ -33,7 +33,12 @@
default = let default = let
pythonEnv = pkgs.python38.withPackages (pypkgs: with pypkgs; [ flask black pytest ]); pythonEnv = pkgs.python38.withPackages (pypkgs: with pypkgs; [ flask black pytest ]);
in pkgs.mkShell { in pkgs.mkShell {
packages = [ pythonEnv pkgs.nixos-shell ]; packages = [
pythonEnv
pkgs.nixos-shell
# We only take this dependency for htpasswd, which is a little unfortunate
pkgs.apacheHttpd
];
shellHook = '' shellHook = ''
PS1="(develop) $PS1" PS1="(develop) $PS1"
''; '';

View File

@ -41,19 +41,21 @@ def auth_check(route):
""" """
Checks the HTTP Basic Auth header against the stored credential. Checks the HTTP Basic Auth header against the stored credential.
""" """
@wraps(route) @wraps(route)
def _route(*args, **kwargs): def _route(*args, **kwargs):
data_path = intake_data_dir() data_path = intake_data_dir()
auth_path = data_path / "credentials.json" auth_path = data_path / "credentials.json"
if auth_path.exists(): if auth_path.exists():
if not request.authorization: if not request.authorization:
abort(403) abort(401)
auth = json.load(auth_path.open(encoding="utf8")) auth = json.load(auth_path.open(encoding="utf8"))
if request.authorization.username != auth["username"]: if request.authorization.username != auth["username"]:
abort(403) abort(403)
if request.authorization.password != auth["secret"]: if request.authorization.password != auth["secret"]:
abort(403) abort(403)
return route(*args, **kwargs) return route(*args, **kwargs)
return _route return _route

View File

@ -2,9 +2,11 @@ from datetime import datetime
from pathlib import Path from pathlib import Path
from shutil import get_terminal_size from shutil import get_terminal_size
import argparse import argparse
import getpass
import json import json
import os import os
import os.path import os.path
import pwd
import subprocess import subprocess
import sys import sys
@ -263,6 +265,44 @@ def cmd_feed(cmd_args):
print() print()
def cmd_passwd(cmd_args):
"""Update password for the web interface."""
parser = argparse.ArgumentParser(
prog="intake passwd",
description=cmd_passwd.__doc__,
)
parser.add_argument(
"--data",
"-d",
default=intake_data_dir(),
help="Path to the intake data directory",
)
args = parser.parse_args(cmd_args)
command_exists = subprocess.run(["command", "-v" "htpasswd"], shell=True)
if command_exists.returncode:
print("Could not find htpasswd, cannot update password")
return 1
creds = Path(args.data) / "credentials.json"
if not creds.parent.exists():
creds.parent.mkdir(parents=True)
user = pwd.getpwuid(os.getuid()).pw_name
password = getpass.getpass(f"intake password for {user}: ")
update_pwd = subprocess.run(
["htpasswd", "-b", "/etc/intake/htpasswd", user, password]
)
if update_pwd.returncode:
print("Could not update password file")
return 1
new_creds = {"username": user, "secret": password}
creds.write_text(json.dumps(new_creds, indent=2))
return 0
def cmd_run(cmd_args): def cmd_run(cmd_args):
"""Run the default Flask server.""" """Run the default Flask server."""
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(

View File

@ -9,19 +9,22 @@ in {
listen.addr = mkOption { listen.addr = mkOption {
type = types.str; type = types.str;
default = "0.0.0.0"; default = "0.0.0.0";
description = "The listen address for the entry point to intake services. This endpoint will redirect to a local port based on the request's HTTP Basic Auth credentials."; description = "The listen address for the entry point to intake services. This endpoint will redirect to a "
"local port based on the request's HTTP Basic Auth credentials.";
}; };
listen.port = mkOption { listen.port = mkOption {
type = types.port; type = types.port;
default = 80; default = 80;
description = "The listen port for the entry point to intake services. This endpoint will redirect to a local port based on the request's HTTP Basic Auth credentials."; description = "The listen port for the entry point to intake services. This endpoint will redirect to a local "
"port based on the request's HTTP Basic Auth credentials.";
}; };
internalPortStart = mkOption { internalPortStart = mkOption {
type = types.port; type = types.port;
default = 24130; default = 24130;
description = "The first port to use for internal service endpoints. A number of ports will be continguously allocated equal to the number of users with enabled intake services."; description = "The first port to use for internal service endpoints. A number of ports will be continguously "
"allocated equal to the number of users with enabled intake services.";
}; };
users = mkOption { users = mkOption {
@ -53,7 +56,39 @@ in {
enabledUserNames = mapAttrsToList (userName: userCfg: userName) enabledUsers; enabledUserNames = mapAttrsToList (userName: userCfg: userName) enabledUsers;
userPortList = imap1 (i: userName: { ${userName} = i + intakeCfg.internalPortStart; }) enabledUserNames; userPortList = imap1 (i: userName: { ${userName} = i + intakeCfg.internalPortStart; }) enabledUserNames;
userPort = foldl (acc: val: acc // val) {} userPortList; userPort = foldl (acc: val: acc // val) {} userPortList;
# To avoid polluting PATH with httpd programs, define an htpasswd wrapper
htpasswdWrapper = pkgs.writeShellScriptBin "htpasswd" ''
${pkgs.apacheHttpd}/bin/htpasswd $@
'';
# File locations
intakeDir = "/etc/intake";
intakePwd = "${intakeDir}/htpasswd";
in { in {
# Define a user group for access to the htpasswd file.
users.groups.intake.members = mkIf (enabledUsers != {}) (enabledUserNames ++ [ "nginx" ]);
# Define an activation script that ensures that the htpasswd file exists.
system.activationScripts.etc-intake = ''
if [ ! -e ${intakeDir} ]; then
${pkgs.coreutils}/bin/mkdir -p ${intakeDir};
fi
${pkgs.coreutils}/bin/chown root:root ${intakeDir}
${pkgs.coreutils}/bin/chmod 755 ${intakeDir}
if [ ! -e ${intakePwd} ]; then
${pkgs.coreutils}/bin/touch ${intakePwd}
fi
${pkgs.coreutils}/bin/chown root:intake ${intakePwd}
${pkgs.coreutils}/bin/chmod 660 ${intakePwd}
'';
# Give the htpasswd wrapper to every intake user
users.users =
let
addWrapperToUser = userName: { ${userName}.packages = [ htpasswdWrapper ]; };
in mkMerge (map addWrapperToUser enabledUserNames);
# Define a user service for each configured user # Define a user service for each configured user
systemd.services = systemd.services =
let let
@ -84,7 +119,7 @@ in {
listen = [ intakeCfg.listen ]; listen = [ intakeCfg.listen ];
locations."/" = { locations."/" = {
proxyPass = "http://127.0.0.1:$target_port"; proxyPass = "http://127.0.0.1:$target_port";
basicAuth = { alice = "alpha"; bob = "beta"; }; basicAuthFile = intakePwd;
}; };
extraConfig = foldl (acc: val: acc + val) "" (mapAttrsToList (userName: port: '' extraConfig = foldl (acc: val: acc + val) "" (mapAttrsToList (userName: port: ''
if ($remote_user = "${userName}") { if ($remote_user = "${userName}") {