@@ 1,73 0,0 @@
-#!/usr/bin/env python3
-import json
-import os
-import sys
-import requests
-from scmsrht.redis import redis
-from srht.api import get_results
-from srht.config import cfg, get_origin
-from uuid import uuid4
-
-from srht.crypto import sign_payload
-
-sys.stderr.write(str(sys.argv) + "\n")
-key_type = sys.argv[3]
-b64key = sys.argv[4]
-
-user_id = username = None
-
-meta_origin = get_origin("meta.sr.ht")
-cache = redis.get(f"git.sr.ht.ssh-keys.{b64key}")
-if cache:
- cache = json.loads(cache.decode())
- user_id = cache["user_id"]
- username = cache["username"]
-else:
- from srht.database import DbSession
- db = DbSession(cfg("git.sr.ht", "connection-string"))
- from gitsrht.types import User, SSHKey
- db.init()
-
- from gitsrht.service import oauth_service
- # Fall back to meta.sr.ht first
- r = requests.get(f"{meta_origin}/api/ssh-key/{b64key}")
- if r.status_code == 200:
- username = r.json()["owner"]["name"]
- user = User.query.filter(User.username == username).one_or_none()
- try:
- # Attempt to pull down keys for next time
- keys_url = f"{meta_origin}/api/user/ssh-keys"
- for key in get_results(keys_url, user.oauth_token):
- oauth_service.ensure_user_sshkey(user, key)
- db.session.commit()
- except:
- pass
- if user:
- user_id = user.id
- username = user.username
-
-if not user_id:
- sys.stderr.write("Unknown public key")
- sys.exit(0)
-
-try:
- headers = {
- "Content-Type": "application/json",
- }
- headers.update(sign_payload(""))
- requests.put(f"{meta_origin}/api/ssh-key/{b64key}",
- data="", headers=headers)
-except:
- pass
-
-default_shell = os.path.join(os.path.dirname(sys.argv[0]), "gitsrht-shell")
-shell = cfg("git.sr.ht", "shell", default=default_shell)
-keys = ("command=\"{} '{}' '{}' '{}'\",".format(
- shell, user_id, username, b64key) +
- "no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty," +
- "environment=\"SRHT_UID={}\",environment=\"SRHT_PUSH={}\"".format(
- user_id, str(uuid4())) +
- " {} {} {}".format(key_type, b64key, username) + "\n")
-print(keys)
-sys.stderr.write(keys)
-sys.exit(0)
@@ 0,0 1,206 @@
+package main
+
+import (
+ "database/sql"
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "net/http"
+ "os"
+ "path"
+
+ goredis "github.com/go-redis/redis"
+ "github.com/google/uuid"
+ _ "github.com/lib/pq"
+ "github.com/vaughan0/go-ini"
+)
+
+type KeyCache struct {
+ UserId int `json:"user_id"`
+ Username string `json:"username"`
+}
+
+// We don't need everything, so we don't include everything.
+type MetaUser struct {
+ Username string `json:"name"`
+}
+
+// We don't need everything, so we don't include everything.
+type MetaSSHKey struct {
+ Id int `json:"id"`
+ Fingerprint string `json:"fingerprint"`
+ Key string `json:"key"`
+ Owner MetaUser `json:"owner"`
+}
+
+// Stores the SSH key in the database and returns the user's ID.
+func storeKey(logger *log.Logger, db *sql.DB, key *MetaSSHKey) int {
+ // Getting the user ID is really a separate concern, but this saves us a
+ // SQL roundtrip and this is a performance-critical section
+ query, err := db.Prepare(`
+ WITH key_owner AS (
+ SELECT id user_id
+ FROM "user"
+ WHERE "user".username = $1
+ )
+ INSERT INTO sshkey (
+ user_id,
+ meta_id,
+ key,
+ fingerprint
+ )
+ SELECT user_id, $2, $3, $4
+ FROM key_owner
+ RETURNING id, user_id;
+ `)
+ if err != nil {
+ logger.Printf("Failed to prepare key insertion statement: %v", err)
+ return 0
+ }
+ defer query.Close()
+
+ var (
+ userId int
+ keyId int
+ )
+ if err = query.QueryRow(key.Owner.Username,
+ key.Id, key.Key, key.Fingerprint).Scan(&keyId, &userId); err != nil {
+
+ logger.Fatalf("Error inserting key & looking up user: %v", err)
+ }
+
+ logger.Printf("Stored key %d for user %d", keyId, userId)
+ return userId
+}
+
+func fetchKeysFromMeta(logger *log.Logger, config ini.File,
+ redis *goredis.Client, b64key string) (string, int) {
+
+ meta, ok := config.Get("meta.sr.ht", "internal-origin")
+ if !ok {
+ meta, ok = config.Get("meta.sr.ht", "origin")
+ }
+ if !ok && meta == "" {
+ logger.Fatalf("No origin configured for meta.sr.ht")
+ }
+
+ resp, err := http.Get(fmt.Sprintf("%s/api/ssh-key/%s", meta, b64key))
+ if err != nil {
+ logger.Printf("meta.sr.ht http.Get: %v", err)
+ return "", 0
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode != 200 {
+ return "", 0
+ }
+
+ body, err := ioutil.ReadAll(resp.Body)
+ var key MetaSSHKey
+ if err = json.Unmarshal(body, &key); err != nil {
+ return "", 0
+ }
+
+ // We wait to connect to postgres until we know we must
+ pgcs, ok := config.Get("git.sr.ht", "connection-string")
+ if !ok {
+ logger.Fatalf("No connection string configured for git.sr.ht: %v", err)
+ }
+ db, err := sql.Open("postgres", pgcs)
+ if err != nil {
+ logger.Fatalf("Failed to open a database connection: %v", err)
+ }
+ userId := storeKey(logger, db, &key)
+ logger.Println("Fetched key from meta.sr.ht")
+
+ // Cache in Redis too
+ cacheKey := fmt.Sprintf("git.sr.ht.ssh-keys.%s", b64key)
+ cache := KeyCache{
+ UserId: userId,
+ Username: key.Owner.Username,
+ }
+ cacheBytes, err := json.Marshal(&cache)
+ if err != nil {
+ logger.Printf("Caching SSH key in redis failed: %v", err)
+ } else {
+ redis.Set(cacheKey, cacheBytes, 0)
+ }
+
+ return key.Owner.Username, userId
+}
+
+func main() {
+ // gitsrht-keys is run by sshd to generate an authorized_key file on stdout.
+
+ var (
+ config ini.File
+ err error
+ logger *log.Logger
+ )
+ // TODO: update key last used timestamp on meta.sr.ht
+
+ redis := goredis.NewClient(&goredis.Options{Addr: "localhost:6379"})
+
+ logf, err := os.OpenFile("/var/log/gitsrht-keys",
+ os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
+ if err != nil {
+ log.Printf("Warning: unable to open log file: %v "+
+ "(using stderr instead)", err)
+ logger = log.New(os.Stderr, "", log.LstdFlags)
+ } else {
+ logger = log.New(logf, "", log.LstdFlags)
+ }
+
+ for _, path := range []string{"../config.ini", "/etc/sr.ht/config.ini"} {
+ config, err = ini.LoadFile(path)
+ if err == nil {
+ break
+ }
+ }
+ if err != nil {
+ logger.Fatalf("Failed to load config file: %v", err)
+ }
+
+ if len(os.Args) < 5 {
+ logger.Fatalf("Expected four arguments from SSH")
+ }
+ logger.Printf("os.Args: %v", os.Args)
+ keyType := os.Args[3]
+ b64key := os.Args[4]
+
+ var (
+ username string
+ userId int
+ )
+ cacheKey := fmt.Sprintf("git.sr.ht.ssh-keys.%s", b64key)
+ logger.Printf("Cache key for SSH key lookup: %s", cacheKey)
+ cacheBytes, err := redis.Get(cacheKey).Bytes()
+ if err != nil {
+ username, userId = fetchKeysFromMeta(logger, config, redis, b64key)
+ } else {
+ var cache KeyCache
+ if err = json.Unmarshal(cacheBytes, &cache); err != nil {
+ logger.Fatalf("Unmarshal cache JSON: %v", err)
+ }
+ userId = cache.UserId
+ username = cache.Username
+ }
+
+ if username == "" {
+ logger.Println("Unknown public key")
+ os.Exit(0)
+ }
+
+ defaultShell := path.Join(path.Dir(os.Args[0]), "gitsrht-shell")
+ shell, ok := config.Get("git.sr.ht", "shell")
+ if !ok {
+ shell = defaultShell
+ }
+
+ push := uuid.New()
+ shellCommand := fmt.Sprintf("%s '%d' '%s' '%s'",
+ shell, userId, username, b64key)
+ fmt.Printf(`restrict,command="%s",environment="SRHT_UID=%d",`+
+ `environment="SRHT_PUSH=%s" %s %s %s`+"\n",
+ shellCommand, userId, push.String(), keyType, b64key, username)
+}