From 95352e6bd55428bed947dbe2e6427ee93240bbe8 Mon Sep 17 00:00:00 2001 From: zeripath Date: Tue, 15 Jun 2021 02:15:12 +0100 Subject: [PATCH] Make command in authorized keys a template (#16003) Fix #15595 Replaces #15978 Signed-off-by: Andrew Thornton Co-authored-by: techknowlogick --- .../doc/advanced/config-cheat-sheet.en-us.md | 1 + models/ssh_key.go | 12 ++- modules/setting/setting.go | 90 ++++++++++--------- 3 files changed, 60 insertions(+), 43 deletions(-) diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index 54ef780bca..1fb05a4253 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -270,6 +270,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a - `SSH_AUTHORIZED_PRINCIPALS_ALLOW`: **off** or **username, email**: \[off, username, email, anything\]: Specify the principals values that users are allowed to use as principal. When set to `anything` no checks are done on the principal string. When set to `off` authorized principal are not allowed to be set. - `SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE`: **false/true**: Gitea will create a authorized_principals file by default when it is not using the internal ssh server and `SSH_AUTHORIZED_PRINCIPALS_ALLOW` is not `off`. - `SSH_AUTHORIZED_PRINCIPALS_BACKUP`: **false/true**: Enable SSH Authorized Principals Backup when rewriting all keys, default is true if `SSH_AUTHORIZED_PRINCIPALS_ALLOW` is not `off`. +- `SSH_AUTHORIZED_KEYS_COMMAND_TEMPLATE`: **{{.AppPath}} --config={{.CustomConf}} serv key-{{.Key.ID}}**: Set the template for the command to passed on authorized keys. Possible keys are: AppPath, AppWorkPath, CustomConf, CustomPath, Key - where Key is a `models.PublicKey` and the others are strings which are shellquoted. - `SSH_SERVER_CIPHERS`: **aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, arcfour256, arcfour128**: For the built-in SSH server, choose the ciphers to support for SSH connections, for system SSH this setting has no effect. - `SSH_SERVER_KEY_EXCHANGES`: **diffie-hellman-group1-sha1, diffie-hellman-group14-sha1, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, curve25519-sha256@libssh.org**: For the built-in SSH server, choose the key exchange algorithms to support for SSH connections, for system SSH this setting has no effect. - `SSH_SERVER_MACS`: **hmac-sha2-256-etm@openssh.com, hmac-sha2-256, hmac-sha1, hmac-sha1-96**: For the built-in SSH server, choose the MACs to support for SSH connections, for system SSH this setting has no effect diff --git a/models/ssh_key.go b/models/ssh_key.go index 9f9c33e848..e35fc12e08 100644 --- a/models/ssh_key.go +++ b/models/ssh_key.go @@ -38,7 +38,6 @@ import ( const ( tplCommentPrefix = `# gitea public key` - tplCommand = "%s --config=%s serv key-%d" tplPublicKey = tplCommentPrefix + "\n" + `command=%s,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty %s` + "\n" authorizedPrincipalsFile = "authorized_principals" @@ -88,7 +87,16 @@ func (key *PublicKey) OmitEmail() string { // AuthorizedString returns formatted public key string for authorized_keys file. func (key *PublicKey) AuthorizedString() string { - return fmt.Sprintf(tplPublicKey, util.ShellEscape(fmt.Sprintf(tplCommand, util.ShellEscape(setting.AppPath), util.ShellEscape(setting.CustomConf), key.ID)), key.Content) + sb := &strings.Builder{} + _ = setting.SSH.AuthorizedKeysCommandTemplateTemplate.Execute(sb, map[string]interface{}{ + "AppPath": util.ShellEscape(setting.AppPath), + "AppWorkPath": util.ShellEscape(setting.AppWorkPath), + "CustomConf": util.ShellEscape(setting.CustomConf), + "CustomPath": util.ShellEscape(setting.CustomPath), + "Key": key, + }) + + return fmt.Sprintf(tplPublicKey, util.ShellEscape(sb.String()), key.Content) } func extractTypeFromBase64Key(key string) (string, error) { diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 355d1d36d1..6e05df1ad9 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -20,6 +20,7 @@ import ( "runtime" "strconv" "strings" + "text/template" "time" "code.gitea.io/gitea/modules/generate" @@ -123,48 +124,51 @@ var ( AbsoluteAssetURL 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"` - ServerHostKeys []string `ini:"SSH_SERVER_HOST_KEYS"` - KeyTestPath string `ini:"SSH_KEY_TEST_PATH"` - KeygenPath string `ini:"SSH_KEYGEN_PATH"` - AuthorizedKeysBackup bool `ini:"SSH_AUTHORIZED_KEYS_BACKUP"` - AuthorizedPrincipalsBackup bool `ini:"SSH_AUTHORIZED_PRINCIPALS_BACKUP"` - MinimumKeySizeCheck bool `ini:"-"` - MinimumKeySizes map[string]int `ini:"-"` - CreateAuthorizedKeysFile bool `ini:"SSH_CREATE_AUTHORIZED_KEYS_FILE"` - CreateAuthorizedPrincipalsFile bool `ini:"SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE"` - ExposeAnonymous bool `ini:"SSH_EXPOSE_ANONYMOUS"` - AuthorizedPrincipalsAllow []string `ini:"SSH_AUTHORIZED_PRINCIPALS_ALLOW"` - AuthorizedPrincipalsEnabled bool `ini:"-"` - TrustedUserCAKeys []string `ini:"SSH_TRUSTED_USER_CA_KEYS"` - TrustedUserCAKeysFile string `ini:"SSH_TRUSTED_USER_CA_KEYS_FILENAME"` - TrustedUserCAKeysParsed []gossh.PublicKey `ini:"-"` - PerWriteTimeout time.Duration `ini:"SSH_PER_WRITE_TIMEOUT"` - PerWritePerKbTimeout time.Duration `ini:"SSH_PER_WRITE_PER_KB_TIMEOUT"` + 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"` + ServerHostKeys []string `ini:"SSH_SERVER_HOST_KEYS"` + KeyTestPath string `ini:"SSH_KEY_TEST_PATH"` + KeygenPath string `ini:"SSH_KEYGEN_PATH"` + AuthorizedKeysBackup bool `ini:"SSH_AUTHORIZED_KEYS_BACKUP"` + AuthorizedPrincipalsBackup bool `ini:"SSH_AUTHORIZED_PRINCIPALS_BACKUP"` + AuthorizedKeysCommandTemplate string `ini:"SSH_AUTHORIZED_KEYS_COMMAND_TEMPLATE"` + AuthorizedKeysCommandTemplateTemplate *template.Template `ini:"-"` + MinimumKeySizeCheck bool `ini:"-"` + MinimumKeySizes map[string]int `ini:"-"` + CreateAuthorizedKeysFile bool `ini:"SSH_CREATE_AUTHORIZED_KEYS_FILE"` + CreateAuthorizedPrincipalsFile bool `ini:"SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE"` + ExposeAnonymous bool `ini:"SSH_EXPOSE_ANONYMOUS"` + AuthorizedPrincipalsAllow []string `ini:"SSH_AUTHORIZED_PRINCIPALS_ALLOW"` + AuthorizedPrincipalsEnabled bool `ini:"-"` + TrustedUserCAKeys []string `ini:"SSH_TRUSTED_USER_CA_KEYS"` + TrustedUserCAKeysFile string `ini:"SSH_TRUSTED_USER_CA_KEYS_FILENAME"` + TrustedUserCAKeysParsed []gossh.PublicKey `ini:"-"` + PerWriteTimeout time.Duration `ini:"SSH_PER_WRITE_TIMEOUT"` + PerWritePerKbTimeout time.Duration `ini:"SSH_PER_WRITE_PER_KB_TIMEOUT"` }{ - Disabled: false, - StartBuiltinServer: false, - Domain: "", - Port: 22, - ServerCiphers: []string{"aes128-ctr", "aes192-ctr", "aes256-ctr", "aes128-gcm@openssh.com", "arcfour256", "arcfour128"}, - ServerKeyExchanges: []string{"diffie-hellman-group1-sha1", "diffie-hellman-group14-sha1", "ecdh-sha2-nistp256", "ecdh-sha2-nistp384", "ecdh-sha2-nistp521", "curve25519-sha256@libssh.org"}, - ServerMACs: []string{"hmac-sha2-256-etm@openssh.com", "hmac-sha2-256", "hmac-sha1", "hmac-sha1-96"}, - KeygenPath: "ssh-keygen", - MinimumKeySizeCheck: true, - MinimumKeySizes: map[string]int{"ed25519": 256, "ed25519-sk": 256, "ecdsa": 256, "ecdsa-sk": 256, "rsa": 2048}, - ServerHostKeys: []string{"ssh/gitea.rsa", "ssh/gogs.rsa"}, - PerWriteTimeout: PerWriteTimeout, - PerWritePerKbTimeout: PerWritePerKbTimeout, + Disabled: false, + StartBuiltinServer: false, + Domain: "", + Port: 22, + ServerCiphers: []string{"aes128-ctr", "aes192-ctr", "aes256-ctr", "aes128-gcm@openssh.com", "arcfour256", "arcfour128"}, + ServerKeyExchanges: []string{"diffie-hellman-group1-sha1", "diffie-hellman-group14-sha1", "ecdh-sha2-nistp256", "ecdh-sha2-nistp384", "ecdh-sha2-nistp521", "curve25519-sha256@libssh.org"}, + ServerMACs: []string{"hmac-sha2-256-etm@openssh.com", "hmac-sha2-256", "hmac-sha1", "hmac-sha1-96"}, + KeygenPath: "ssh-keygen", + MinimumKeySizeCheck: true, + MinimumKeySizes: map[string]int{"ed25519": 256, "ed25519-sk": 256, "ecdsa": 256, "ecdsa-sk": 256, "rsa": 2048}, + ServerHostKeys: []string{"ssh/gitea.rsa", "ssh/gogs.rsa"}, + AuthorizedKeysCommandTemplate: "{{.AppPath}} --config={{.CustomConf}} serv key-{{.Key.ID}}", + PerWriteTimeout: PerWriteTimeout, + PerWritePerKbTimeout: PerWritePerKbTimeout, } // Security settings @@ -785,6 +789,10 @@ func NewContext() { } SSH.ExposeAnonymous = sec.Key("SSH_EXPOSE_ANONYMOUS").MustBool(false) + SSH.AuthorizedKeysCommandTemplate = sec.Key("SSH_AUTHORIZED_KEYS_COMMAND_TEMPLATE").MustString(SSH.AuthorizedKeysCommandTemplate) + + SSH.AuthorizedKeysCommandTemplateTemplate = template.Must(template.New("").Parse(SSH.AuthorizedKeysCommandTemplate)) + SSH.PerWriteTimeout = sec.Key("SSH_PER_WRITE_TIMEOUT").MustDuration(PerWriteTimeout) SSH.PerWritePerKbTimeout = sec.Key("SSH_PER_WRITE_PER_KB_TIMEOUT").MustDuration(PerWritePerKbTimeout)