M config.ini.example => config.ini.example +10 -1
@@ 41,8 41,17 @@ builds=http://builds.sr.ht.local
remote=http://cgit.local
repos=/var/lib/git/
+#
+# The authorized keys hook uses this to dispatch to various handlers
+# The format is a program to exec into as the key, and the user to match as the
+# value. When someone tries to log in as this user, this program is executed
+# and is expected to omit an AuthorizedKeys file.
+[dispatch]
+/usr/bin/git-srht-keys=git:git
+# Uncomment to enable the man.sr.ht dispatcher:
+#/usr/bin/man-srht-keys=man:man
+
[git.sr.ht]
-git-user=git:git
post-update-script=/usr/bin/git-srht-update-hook
[meta.sr.ht]
A git-srht-dispatch => git-srht-dispatch +67 -0
@@ 0,0 1,67 @@
+#!/usr/bin/env python3
+# AuthorizedKeysCommand=/usr/bin/git-srht-dispatch auth "%u" "%h" "%t" "%k"
+# AuthorizedKeysUser=root
+import sys
+import os
+try:
+ f = open("/var/log/git-srht-dispatch", "a")
+ os.close(sys.stderr.fileno())
+ os.dup2(f.fileno(), sys.stderr.fileno())
+except Exception as ex:
+ sys.stderr.write("Unable to open log for writing\n")
+ sys.stderr.write(str(ex) + "\n")
+from collections import namedtuple
+from datetime import datetime
+from pwd import getpwnam
+from grp import getgrnam
+from srht.config import cfg, cfgkeys, load_config
+
+def log(s, *args):
+ sys.stderr.write("{} {}\n".format(datetime.now().isoformat(),
+ s.format(*args) if isinstance(s, str) else str(s)))
+log("Running git-srht-dispatch")
+
+load_config("git")
+
+def auth_keys_error():
+ log("This command should be run by sshd's AuthorizedKeysCommand")
+ log('AuthorizedKeysCommand={} auth "%u" "%h" "%t" "%k"\nAuthorizedKeysUser=root',
+ os.path.abspath(sys.argv[0]))
+ sys.exit(1)
+
+Dispatcher = namedtuple("Dispatcher", ["cmd", "uid", "gid"])
+dispatchers = list()
+
+for cmd in cfgkeys("dispatch"):
+ user = cfg("dispatch", cmd).split(":")
+ uid, gid = getpwnam(user[0]).pw_uid, getgrnam(user[-1]).gr_gid
+ dispatchers.append(Dispatcher(cmd=cmd, uid=uid, gid=gid))
+
+if len(sys.argv) != 5:
+ auth_keys_error()
+
+user = sys.argv[1]
+uid = getpwnam(user).pw_uid
+homedir = sys.argv[2]
+key_type = sys.argv[3]
+b64key = sys.argv[4]
+authorized_keys_file = "{}/.ssh/authorized_keys".format(homedir)
+
+log("authorizing user={} home={} b64key={} key_type={}",
+ user, homedir, b64key, key_type)
+
+for dispatch in dispatchers:
+ if dispatch.uid == uid:
+ log("dispatching to {} with uid={}, gid={}",
+ dispatch.cmd, dispatch.uid, dispatch.gid)
+ os.setgid(dispatch.gid)
+ os.setuid(dispatch.uid)
+ os.execl(cmd, *([cmd] + sys.argv[1:]))
+
+log("Falling back to existing authorized keys file")
+if not os.path.exists(authorized_keys_file):
+ sys.exit(0)
+with open(authorized_keys_file, "r") as f:
+ authorized_keys = f.read()
+print(authorized_keys)
+sys.exit(0)
M git-srht-keys => git-srht-keys +28 -177
@@ 1,182 1,33 @@
#!/usr/bin/env python3
-# AuthorizedKeysCommand=/usr/bin/git-srht-keys auth "%u" "%h" "%t" "%k"
-# AuthorizedKeysUser=root
-import sys
import os
-import shlex
+import sys
import requests
-import time
-from datetime import datetime
-from pwd import getpwnam
-from grp import getgrnam
-from srht.validation import Validation
-from srht.config import cfg, cfgi, load_config
+from srht.config import cfg, load_config
load_config("git")
-
-root = (
- cfg("server", "protocol") +
- "://" +
- cfg("server", "domain")
-)
-
-_log = None
-try:
- _log = open("/var/log/git-srht-push", "a")
-except:
- pass
-
-def log(s, *args):
- if isinstance(s, str):
- s = s.format(*args)
- else:
- s = str(s)
- s = "{} {}".format(datetime.now().isoformat(), s)
- if _log:
- _log.write(s + "\n")
-
-def auth_keys_error():
- log("This command should be run by sshd's AuthorizedKeysCommand")
- log('AuthorizedKeysCommand={} auth "%u" "%h" "%t" "%k"\nAuthorizedKeysUser=root',
- os.path.abspath(sys.argv[0]))
- sys.exit(1)
-
-def drop_root():
- if os.getuid() != git_uid or os.getgid() != git_gid:
- log("setuid to {}", git_user)
- os.setgid(git_gid)
- os.setuid(git_uid)
-
-git_user = cfg("git.sr.ht", "git-user").split(':')
-git_uid, git_gid = getpwnam(git_user[0]).pw_uid, getgrnam(git_user[-1]).gr_gid
-repos = cfg("cgit", "repos")
-
-def auth_keys():
- if len(sys.argv) != 6:
- auth_keys_error()
-
- user = sys.argv[2]
- uid = getpwnam(user).pw_uid
- homedir = sys.argv[3]
- key_type = sys.argv[4]
- b64key = sys.argv[5]
- authorized_keys_file = "{}/.ssh/authorized_keys".format(homedir)
-
- log("user={} home={} b64key={} key_type={}", user, homedir, b64key, key_type)
-
- if user != git_user[0]:
- log("Falling back to existing authorized keys file")
- if not os.path.exists(authorized_keys_file):
- sys.exit(0)
- with open(authorized_keys_file, "r") as f:
- authorized_keys = f.read()
- print(authorized_keys)
- sys.exit(0)
-
- drop_root()
-
- from srht.database import DbSession
- db = DbSession(cfg("sr.ht", "connection-string"))
- from gitsrht.types import User
- db.init()
-
- r = requests.get("{}/api/ssh-key/{}".format(
- cfg("network", "meta"), b64key))
- if r.status_code != 200:
- log("meta.sr.ht returned 404 for this key")
- sys.exit(0)
- j = r.json()
- username = j["user"]["username"]
- u = User.query.filter(User.username == username).first()
- if not u:
- log("Unknown user {}", username)
- log("Authorized user for login")
- keys = "command=\"{} shell '{}' '{}'\",".format(sys.argv[0], u.id, b64key) + \
- "no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty " + \
- "{} {} {}".format(key_type, b64key, username)
- print(keys)
- log(keys)
+from srht.database import DbSession
+db = DbSession(cfg("sr.ht", "connection-string"))
+from gitsrht.types import User
+db.init()
+
+sys.stderr.write(str(sys.argv) + "\n")
+key_type = sys.argv[3]
+b64key = sys.argv[4]
+
+r = requests.get("{}/api/ssh-key/{}".format(
+ cfg("network", "meta"), b64key))
+if r.status_code != 200:
+ sys.stderr.write("meta.sr.ht returned 404 for this key\n")
sys.exit(0)
-
-def shell():
- log("Starting up git.sr.ht shell")
- drop_root()
-
- _cmd = os.environ.get("SSH_ORIGINAL_COMMAND")
- if not _cmd:
- _cmd = ""
- if len(sys.argv) < 3:
- log("Error: expected 2 arguments from SSH")
- user_id = sys.argv[2]
- ssh_key = sys.argv[3]
-
- from srht.database import DbSession
- db = DbSession(cfg("sr.ht", "connection-string"))
- from gitsrht.types import User, Repository, RepoVisibility, Redirect
- from gitsrht.access import has_access, UserAccess
- from gitsrht.repos import create_repo
- db.init()
-
- user = User.query.filter(User.id == user_id).first()
- if not user:
- log("Unknown user ID {}", user_id)
- log("User: {}", user.username)
-
- cmd = shlex.split(_cmd)
- valid_commands = ["git-receive-pack", "git-upload-pack", "git-upload-archive"]
- if len(cmd) < 1 or not cmd[0] in valid_commands:
- log("Not permitting unacceptable command")
- print("Hi {}! You've successfully authenticated, ".format(user.username) +
- "but I do not provide an interactive shell. Bye!")
- sys.exit(128)
- os.chdir(repos)
- path = os.path.abspath(cmd[-1])
- if not path.startswith(repos):
- sys.stderr.write("Access denied")
- sys.exit(128)
- cmd[-1] = path
- _cmd = " ".join(shlex.quote(arg) for arg in cmd)
-
- repo = Repository.query.filter(Repository.path == path).first()
- if not repo:
- repo = Redirect.query.filter(Redirect.path == path).first()
- if repo:
- repo = repo.new_repo
- sys.stderr.write("\n\t\033[93mNOTICE\033[0m\n")
- sys.stderr.write("\tThis repository has moved:\n")
- # TODO: orgs
- sys.stderr.write("\t{}/~{}/{}\n".format(
- root, repo.owner.username, repo.name))
- sys.stderr.write("\tPlease update your remote.\n\n")
- sys.exit(128)
-
- _path, repo_name = os.path.split(path)
- owner = os.path.basename(_path)
- if "~" + user.username != owner:
- sys.exit(128)
-
- valid = Validation({ "name": repo_name })
- repo = create_repo(valid, user)
- if not valid.ok:
- sys.exit(128)
- repo.visibility = RepoVisibility.autocreated
- db.session.commit()
-
- if cmd[0] == "git-receive-pack":
- if not has_access(repo, UserAccess.write, user):
- sys.exit(128)
- else:
- if not has_access(repo, UserAccess.read, user):
- sys.exit(128)
-
- log("Executing {}", _cmd)
- if _log:
- _log.close()
- os.execv("/usr/bin/git-shell", ["/usr/bin/git-shell", "-c", _cmd])
-
-with _log or sys.stdout:
- if len(sys.argv) < 2:
- auth_keys_error()
- if sys.argv[1] == "auth":
- auth_keys()
- if sys.argv[1] == "shell":
- shell()
+j = r.json()
+username = j["user"]["username"]
+u = User.query.filter(User.username == username).first()
+if not u:
+ sys.stderr.write("Unknown user {}\n", username)
+ sys.exit(1)
+shell = os.path.join(os.path.dirname(sys.argv[0]), "git-srht-shell")
+keys = "command=\"{} '{}' '{}'\",".format(shell, u.id, b64key) + \
+ "no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty " + \
+ "{} {} {}".format(key_type, b64key, username) + "\n"
+print(keys)
+sys.stderr.write(keys)
+sys.exit(0)
A git-srht-shell => git-srht-shell +94 -0
@@ 0,0 1,94 @@
+#!/usr/bin/env python3
+import sys
+import os
+try:
+ f = open("/var/log/git-srht-shell", "a")
+ os.close(sys.stderr.fileno())
+ os.dup2(f.fileno(), sys.stderr.fileno())
+except Exception as ex:
+ sys.stderr.write("Unable to open log for writing\n")
+ sys.stderr.write(str(ex) + "\n")
+import requests
+import shlex
+from datetime import datetime
+from srht.config import cfg, load_config
+load_config("git")
+from srht.database import DbSession
+db = DbSession(cfg("sr.ht", "connection-string"))
+from gitsrht.types import User, Repository, RepoVisibility, Redirect
+from gitsrht.access import has_access, UserAccess
+from gitsrht.repos import create_repo
+db.init()
+
+def log(s, *args):
+ sys.stderr.write("{} {}\n".format(datetime.now().isoformat(),
+ s.format(*args) if isinstance(s, str) else str(s)))
+
+root = "{}://{}".format(cfg("server", "protocol"), cfg("server", "domain"))
+repos = cfg("cgit", "repos")
+
+_cmd = os.environ.get("SSH_ORIGINAL_COMMAND")
+if not _cmd:
+ _cmd = ""
+if len(sys.argv) < 2:
+ log("Error: expected 2 arguments from SSH")
+ sys.exit(1)
+user_id = sys.argv[1]
+ssh_key = sys.argv[2]
+
+user = User.query.filter(User.id == user_id).first()
+if not user:
+ log("Unknown user ID {}", user_id)
+ sys.exit(1)
+log("User: {}", user.username)
+
+cmd = shlex.split(_cmd)
+valid_commands = ["git-receive-pack", "git-upload-pack", "git-upload-archive"]
+if len(cmd) < 1 or not cmd[0] in valid_commands:
+ log("Not permitting unacceptable command")
+ print("Hi {}! You've successfully authenticated, ".format(user.username) +
+ "but I do not provide an interactive shell. Bye!")
+ sys.exit(128)
+os.chdir(repos)
+path = os.path.abspath(cmd[-1])
+if not path.startswith(repos):
+ sys.stderr.write("Access denied")
+ sys.exit(128)
+cmd[-1] = path
+_cmd = " ".join(shlex.quote(arg) for arg in cmd)
+
+repo = Repository.query.filter(Repository.path == path).first()
+if not repo:
+ repo = Redirect.query.filter(Redirect.path == path).first()
+ if repo:
+ repo = repo.new_repo
+ sys.stderr.write("\n\t\033[93mNOTICE\033[0m\n")
+ sys.stderr.write("\tThis repository has moved:\n")
+ # TODO: orgs
+ sys.stderr.write("\t{}/~{}/{}\n".format(
+ root, repo.owner.username, repo.name))
+ sys.stderr.write("\tPlease update your remote.\n\n")
+ sys.exit(128)
+
+ _path, repo_name = os.path.split(path)
+ owner = os.path.basename(_path)
+ if "~" + user.username != owner:
+ sys.exit(128)
+
+ valid = Validation({ "name": repo_name })
+ repo = create_repo(valid, user)
+ if not valid.ok:
+ sys.exit(128)
+ repo.visibility = RepoVisibility.autocreated
+ db.session.commit()
+
+if cmd[0] == "git-receive-pack":
+ if not has_access(repo, UserAccess.write, user):
+ sys.exit(128)
+else:
+ if not has_access(repo, UserAccess.read, user):
+ sys.exit(128)
+
+log("Executing {}", _cmd)
+sys.stderr.close()
+os.execv("/usr/bin/git-shell", ["/usr/bin/git-shell", "-c", _cmd])
M setup.py => setup.py +2 -0
@@ 33,7 33,9 @@ setup(
]
},
scripts = [
+ 'git-srht-dispatch',
'git-srht-keys',
+ 'git-srht-shell',
'git-srht-update-hook',
'git-srht-periodic'
]