~edwargix/git.sr.ht

caad1db81c40e7b0cf1c2d9b12ad9d3b088f18d8 — Denis Laxalde 6 years ago 2bdb70d
Make gitsrht-keys use srht-keys module from scm.sr.ht

Only the main() is kept and it uses srhtkeys's functions.
3 files changed, 23 insertions(+), 165 deletions(-)

M gitsrht-keys/go.mod
M gitsrht-keys/go.sum
M gitsrht-keys/main.go
M gitsrht-keys/go.mod => gitsrht-keys/go.mod +1 -2
@@ 3,8 3,7 @@ module git.sr.ht/~sircmpwn/git.sr.ht/gitsrht-keys
go 1.13

require (
	git.sr.ht/~sircmpwn/scm.sr.ht/srht-keys v0.0.0
	github.com/go-redis/redis v6.15.6+incompatible
	github.com/google/uuid v1.1.1
	github.com/lib/pq v1.2.0
	github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec
)

M gitsrht-keys/go.sum => gitsrht-keys/go.sum +1 -0
@@ 1,3 1,4 @@
git.sr.ht/~sircmpwn/scm.sr.ht v0.0.0-20191204153510-f740494d6b60 h1:24gw/gL9vvY2v7HDGwLOK9dsq2QOCbDTDzsVoyS0jeo=
github.com/go-redis/redis v6.15.6+incompatible h1:H9evprGPLI8+ci7fxQx6WNZHJSb7be8FqJQRhdQZ5Sg=
github.com/go-redis/redis v6.15.6+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=

M gitsrht-keys/main.go => gitsrht-keys/main.go +21 -163
@@ 1,150 1,33 @@
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"
	"git.sr.ht/~sircmpwn/scm.sr.ht/srht-keys"
)

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 {
	logger.Println("Storing meta.sr.ht key in git.sr.ht database")

	// 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
		-- This no-ops on conflict, but we still need this query to complete so
		-- that we can extract the user ID. DO NOTHING returns zero rows.
		ON CONFLICT (meta_id) DO UPDATE SET meta_id = $2
		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.Printf("Error inserting key: %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 {
		logger.Printf("non-200 response from meta.sr.ht: %d", resp.StatusCode)
		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.
	// In order to facilitate this, we do one of two things:
	// - Attempt to fetch the cached key info from Redis (preferred)
	// - Fetch the key from meta.sr.ht and store it in SQL and Redis (slower)
	service := "git.sr.ht"
	shellName := "gitsrht-shell"
	logFile := "/var/log/gitsrht-keys"

	var (
		config ini.File
		err    error
		logger *log.Logger
		config   ini.File
		err      error
		logger   *log.Logger
		username string
		userId   int
		b64key   string
		keyType  string
		prefix   string
	)
	// TODO: update key last used timestamp on meta.sr.ht



@@ 154,7 37,7 @@ func main() {
	}
	redis := goredis.NewClient(&goredis.Options{Addr: redisHost})

	logf, err := os.OpenFile("/var/log/gitsrht-keys",
	logf, err := os.OpenFile(logFile,
		os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
	if err != nil {
		log.Printf("Warning: unable to open log file: %v "+


@@ 174,49 57,24 @@ func main() {
		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()
	keyType, b64key, prefix, err = srhtkeys.ParseArgs(logger)
	if err != nil {
		logger.Println("Cache miss, going to meta.sr.ht")
		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
		logger.Printf("Cache hit: %d %s", userId, username)
		os.Exit(0)
	}

	username, userId = srhtkeys.UserFromKey(logger, config, redis, service, b64key)

	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")
	defaultShell := path.Join(prefix, shellName)
	shell, ok := config.Get(service, "shell")
	if !ok {
		shell = defaultShell
	}

	push := uuid.New()
	logger.Printf("Assigned uuid %s to this push", push.String())
	shellCommand := fmt.Sprintf("%s '%d' '%s' '%s'",
		shell, userId, username, b64key)
	fmt.Printf(`restrict,command="%s",`+
		`environment="SRHT_PUSH=%s" %s %s %s`+"\n",
		shellCommand, push.String(), keyType, b64key, username)
	srhtkeys.RenderAuthorizedKeysEntry(logger, shell, userId, username,
		b64key, keyType)
}