From 7d9a191a3c78f7135e7ce3e314290e844fb7edfe Mon Sep 17 00:00:00 2001 From: zeripath Date: Thu, 1 Nov 2018 13:41:07 +0000 Subject: [PATCH] Create AuthorizedKeysCommand (#5236) --- cmd/cmd.go | 6 +- cmd/keys.go | 85 ++++++++++++++++++++++++++ custom/conf/app.ini.sample | 3 + docs/content/doc/usage/command-line.md | 21 +++++++ main.go | 2 + models/ssh_key.go | 2 +- modules/setting/setting.go | 36 +++++------ 7 files changed, 136 insertions(+), 19 deletions(-) create mode 100644 cmd/keys.go diff --git a/cmd/cmd.go b/cmd/cmd.go index 15dd08524..1ca885a42 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -27,10 +27,14 @@ func argsSet(c *cli.Context, args ...string) error { } func initDB() error { + return initDBDisableConsole(false) +} + +func initDBDisableConsole(disableConsole bool) error { setting.NewContext() models.LoadConfigs() - setting.NewXORMLogService(false) + setting.NewXORMLogService(disableConsole) if err := models.SetEngine(); err != nil { return fmt.Errorf("models.SetEngine: %v", err) } diff --git a/cmd/keys.go b/cmd/keys.go new file mode 100644 index 000000000..66565cc56 --- /dev/null +++ b/cmd/keys.go @@ -0,0 +1,85 @@ +// Copyright 2018 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package cmd + +import ( + "errors" + "fmt" + "strings" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/setting" + + "github.com/urfave/cli" +) + +// CmdKeys represents the available keys sub-command +var CmdKeys = cli.Command{ + Name: "keys", + Usage: "This command queries the Gitea database to get the authorized command for a given ssh key fingerprint", + Action: runKeys, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "expected, e", + Value: "git", + Usage: "Expected user for whom provide key commands", + }, + cli.StringFlag{ + Name: "username, u", + Value: "", + Usage: "Username trying to log in by SSH", + }, + cli.StringFlag{ + Name: "type, t", + Value: "", + Usage: "Type of the SSH key provided to the SSH Server (requires content to be provided too)", + }, + cli.StringFlag{ + Name: "content, k", + Value: "", + Usage: "Base64 encoded content of the SSH key provided to the SSH Server (requires type to be provided too)", + }, + cli.StringFlag{ + Name: "config, c", + Value: "custom/conf/app.ini", + Usage: "Custom configuration file path", + }, + }, +} + +func runKeys(c *cli.Context) error { + if c.IsSet("config") { + setting.CustomConf = c.String("config") + } + + if !c.IsSet("username") { + return errors.New("No username provided") + } + // Check username matches the expected username + if strings.TrimSpace(c.String("username")) != strings.TrimSpace(c.String("expected")) { + return nil + } + + content := "" + + if c.IsSet("type") && c.IsSet("content") { + content = fmt.Sprintf("%s %s", strings.TrimSpace(c.String("type")), strings.TrimSpace(c.String("content"))) + } + + if content == "" { + return errors.New("No key type and content provided") + } + + if err := initDBDisableConsole(true); err != nil { + return err + } + + publicKey, err := models.SearchPublicKeyByContent(content) + if err != nil { + return err + } + fmt.Println(publicKey.AuthorizedString()) + return nil +} diff --git a/custom/conf/app.ini.sample b/custom/conf/app.ini.sample index 1a85f9364..a5925b48d 100644 --- a/custom/conf/app.ini.sample +++ b/custom/conf/app.ini.sample @@ -154,6 +154,9 @@ SSH_PORT = 22 SSH_LISTEN_PORT = %(SSH_PORT)s ; Root path of SSH directory, default is '~/.ssh', but you have to use '/home/git/.ssh'. SSH_ROOT_PATH = +; Gitea will create a authorized_keys file by default when it is not using the internal ssh server +; If you intend to use the AuthorizedKeysCommand functionality then you should turn this off. +SSH_CREATE_AUTHORIZED_KEYS_FILE = true ; For the built-in SSH server, choose the ciphers to support for SSH connections, ; for system SSH this setting has no effect SSH_SERVER_CIPHERS = aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, arcfour256, arcfour128 diff --git a/docs/content/doc/usage/command-line.md b/docs/content/doc/usage/command-line.md index 90e1ae251..904a395e8 100644 --- a/docs/content/doc/usage/command-line.md +++ b/docs/content/doc/usage/command-line.md @@ -163,3 +163,24 @@ for automatic deployments. - `gitea generate secret INTERNAL_TOKEN` - `gitea generate secret LFS_JWT_SECRET` - `gitea generate secret SECRET_KEY` + +#### keys + +Provides an SSHD AuthorizedKeysCommand. Needs to be configured in the sshd config file: + +```ini +... +# The value of -e and the AuthorizedKeysCommandUser should match the +# username running gitea +AuthorizedKeysCommandUser git +AuthorizedKeysCommand /path/to/gitea keys -e git -u %u -t %t -k %k +``` + +The command will return the appropriate authorized_keys line for the +provided key. You should also set the value +`SSH_CREATE_AUTHORIZED_KEYS_FILE=false` in the `[server]` section of +`app.ini`. + +NB: opensshd requires the gitea program to be owned by root and not +writable by group or others. The program must be specified by an absolute +path. diff --git a/main.go b/main.go index a0a52c3ae..ccfc884b1 100644 --- a/main.go +++ b/main.go @@ -13,6 +13,7 @@ import ( "code.gitea.io/gitea/cmd" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + // register supported doc types _ "code.gitea.io/gitea/modules/markup/csv" _ "code.gitea.io/gitea/modules/markup/markdown" @@ -48,6 +49,7 @@ arguments - which can alternatively be run by running the subcommand web.` cmd.CmdAdmin, cmd.CmdGenerate, cmd.CmdMigrate, + cmd.CmdKeys, } app.Flags = append(app.Flags, cmd.CmdWeb.Flags...) app.Action = cmd.CmdWeb.Action diff --git a/models/ssh_key.go b/models/ssh_key.go index 0368ffad3..5d046c020 100644 --- a/models/ssh_key.go +++ b/models/ssh_key.go @@ -558,7 +558,7 @@ func DeletePublicKey(doer *User, id int64) (err error) { // outside any session scope independently. func RewriteAllPublicKeys() error { //Don't rewrite key if internal server - if setting.SSH.StartBuiltinServer { + if setting.SSH.StartBuiltinServer || !setting.SSH.CreateAuthorizedKeysFile { return nil } diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 0eaf58988..a15dc116a 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -118,23 +118,24 @@ var ( LetsEncryptEmail string SSH = struct { - Disabled bool `ini:"DISABLE_SSH"` - StartBuiltinServer bool `ini:"START_SSH_SERVER"` - BuiltinServerUser string `ini:"BUILTIN_SSH_SERVER_USER"` - Domain string `ini:"SSH_DOMAIN"` - Port int `ini:"SSH_PORT"` - ListenHost string `ini:"SSH_LISTEN_HOST"` - ListenPort int `ini:"SSH_LISTEN_PORT"` - RootPath string `ini:"SSH_ROOT_PATH"` - ServerCiphers []string `ini:"SSH_SERVER_CIPHERS"` - ServerKeyExchanges []string `ini:"SSH_SERVER_KEY_EXCHANGES"` - ServerMACs []string `ini:"SSH_SERVER_MACS"` - KeyTestPath string `ini:"SSH_KEY_TEST_PATH"` - KeygenPath string `ini:"SSH_KEYGEN_PATH"` - AuthorizedKeysBackup bool `ini:"SSH_AUTHORIZED_KEYS_BACKUP"` - MinimumKeySizeCheck bool `ini:"-"` - MinimumKeySizes map[string]int `ini:"-"` - ExposeAnonymous bool `ini:"SSH_EXPOSE_ANONYMOUS"` + Disabled bool `ini:"DISABLE_SSH"` + StartBuiltinServer bool `ini:"START_SSH_SERVER"` + BuiltinServerUser string `ini:"BUILTIN_SSH_SERVER_USER"` + Domain string `ini:"SSH_DOMAIN"` + Port int `ini:"SSH_PORT"` + ListenHost string `ini:"SSH_LISTEN_HOST"` + ListenPort int `ini:"SSH_LISTEN_PORT"` + RootPath string `ini:"SSH_ROOT_PATH"` + ServerCiphers []string `ini:"SSH_SERVER_CIPHERS"` + ServerKeyExchanges []string `ini:"SSH_SERVER_KEY_EXCHANGES"` + ServerMACs []string `ini:"SSH_SERVER_MACS"` + KeyTestPath string `ini:"SSH_KEY_TEST_PATH"` + KeygenPath string `ini:"SSH_KEYGEN_PATH"` + AuthorizedKeysBackup bool `ini:"SSH_AUTHORIZED_KEYS_BACKUP"` + MinimumKeySizeCheck bool `ini:"-"` + MinimumKeySizes map[string]int `ini:"-"` + CreateAuthorizedKeysFile bool `ini:"SSH_CREATE_AUTHORIZED_KEYS_FILE"` + ExposeAnonymous bool `ini:"SSH_EXPOSE_ANONYMOUS"` }{ Disabled: false, StartBuiltinServer: false, @@ -863,6 +864,7 @@ func NewContext() { } } SSH.AuthorizedKeysBackup = sec.Key("SSH_AUTHORIZED_KEYS_BACKUP").MustBool(true) + SSH.CreateAuthorizedKeysFile = sec.Key("SSH_CREATE_AUTHORIZED_KEYS_FILE").MustBool(true) SSH.ExposeAnonymous = sec.Key("SSH_EXPOSE_ANONYMOUS").MustBool(false) sec = Cfg.Section("server")