Add htpasswd password support
This commit is contained in:
parent
0c86a78d51
commit
1dbf7ddb60
|
@ -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"
|
||||||
'';
|
'';
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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(
|
||||||
|
|
43
module.nix
43
module.nix
|
@ -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}") {
|
||||||
|
|
Loading…
Reference in New Issue