aboutsummaryrefslogtreecommitdiffstats
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/cron/tasks_extended.go11
-rw-r--r--modules/setting/setting.go111
-rw-r--r--modules/ssh/ssh.go47
3 files changed, 151 insertions, 18 deletions
diff --git a/modules/cron/tasks_extended.go b/modules/cron/tasks_extended.go
index fa2d6e0c38..f0742eb471 100644
--- a/modules/cron/tasks_extended.go
+++ b/modules/cron/tasks_extended.go
@@ -67,6 +67,16 @@ func registerRewriteAllPublicKeys() {
})
}
+func registerRewriteAllPrincipalKeys() {
+ RegisterTaskFatal("resync_all_sshprincipals", &BaseConfig{
+ Enabled: false,
+ RunAtStart: false,
+ Schedule: "@every 72h",
+ }, func(_ context.Context, _ *models.User, _ Config) error {
+ return models.RewriteAllPrincipalKeys()
+ })
+}
+
func registerRepositoryUpdateHook() {
RegisterTaskFatal("resync_all_hooks", &BaseConfig{
Enabled: false,
@@ -112,6 +122,7 @@ func initExtendedTasks() {
registerDeleteRepositoryArchives()
registerGarbageCollectRepositories()
registerRewriteAllPublicKeys()
+ registerRewriteAllPrincipalKeys()
registerRepositoryUpdateHook()
registerReinitMissingRepositories()
registerDeleteMissingRepositories()
diff --git a/modules/setting/setting.go b/modules/setting/setting.go
index 52a14e0d28..8088cffcdf 100644
--- a/modules/setting/setting.go
+++ b/modules/setting/setting.go
@@ -28,6 +28,7 @@ import (
shellquote "github.com/kballard/go-shellquote"
"github.com/unknwon/com"
+ gossh "golang.org/x/crypto/ssh"
ini "gopkg.in/ini.v1"
"strk.kbt.io/projects/go/libravatar"
)
@@ -103,24 +104,31 @@ var (
StaticURLPrefix 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:"-"`
- CreateAuthorizedKeysFile bool `ini:"SSH_CREATE_AUTHORIZED_KEYS_FILE"`
- 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"`
+ 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:"-"`
}{
Disabled: false,
StartBuiltinServer: false,
@@ -672,12 +680,38 @@ func NewContext() {
SSH.StartBuiltinServer = false
}
+ trustedUserCaKeys := sec.Key("SSH_TRUSTED_USER_CA_KEYS").Strings(",")
+ for _, caKey := range trustedUserCaKeys {
+ pubKey, _, _, _, err := gossh.ParseAuthorizedKey([]byte(caKey))
+ if err != nil {
+ log.Fatal("Failed to parse TrustedUserCaKeys: %s %v", caKey, err)
+ }
+
+ SSH.TrustedUserCAKeysParsed = append(SSH.TrustedUserCAKeysParsed, pubKey)
+ }
+ if len(trustedUserCaKeys) > 0 {
+ // Set the default as email,username otherwise we can leave it empty
+ sec.Key("SSH_AUTHORIZED_PRINCIPALS_ALLOW").MustString("username,email")
+ } else {
+ sec.Key("SSH_AUTHORIZED_PRINCIPALS_ALLOW").MustString("off")
+ }
+
+ SSH.AuthorizedPrincipalsAllow, SSH.AuthorizedPrincipalsEnabled = parseAuthorizedPrincipalsAllow(sec.Key("SSH_AUTHORIZED_PRINCIPALS_ALLOW").Strings(","))
+
if !SSH.Disabled && !SSH.StartBuiltinServer {
if err := os.MkdirAll(SSH.RootPath, 0700); err != nil {
log.Fatal("Failed to create '%s': %v", SSH.RootPath, err)
} else if err = os.MkdirAll(SSH.KeyTestPath, 0644); err != nil {
log.Fatal("Failed to create '%s': %v", SSH.KeyTestPath, err)
}
+
+ if len(trustedUserCaKeys) > 0 && SSH.AuthorizedPrincipalsEnabled {
+ fname := sec.Key("SSH_TRUSTED_USER_CA_KEYS_FILENAME").MustString(filepath.Join(SSH.RootPath, "gitea-trusted-user-ca-keys.pem"))
+ if err := ioutil.WriteFile(fname,
+ []byte(strings.Join(trustedUserCaKeys, "\n")), 0600); err != nil {
+ log.Fatal("Failed to create '%s': %v", fname, err)
+ }
+ }
}
SSH.MinimumKeySizeCheck = sec.Key("MINIMUM_KEY_SIZE_CHECK").MustBool(SSH.MinimumKeySizeCheck)
@@ -689,8 +723,17 @@ func NewContext() {
delete(SSH.MinimumKeySizes, strings.ToLower(key.Name()))
}
}
+
SSH.AuthorizedKeysBackup = sec.Key("SSH_AUTHORIZED_KEYS_BACKUP").MustBool(true)
SSH.CreateAuthorizedKeysFile = sec.Key("SSH_CREATE_AUTHORIZED_KEYS_FILE").MustBool(true)
+
+ SSH.AuthorizedPrincipalsBackup = false
+ SSH.CreateAuthorizedPrincipalsFile = false
+ if SSH.AuthorizedPrincipalsEnabled {
+ SSH.AuthorizedPrincipalsBackup = sec.Key("SSH_AUTHORIZED_PRINCIPALS_BACKUP").MustBool(true)
+ SSH.CreateAuthorizedPrincipalsFile = sec.Key("SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE").MustBool(true)
+ }
+
SSH.ExposeAnonymous = sec.Key("SSH_EXPOSE_ANONYMOUS").MustBool(false)
if err = Cfg.Section("oauth2").MapTo(&OAuth2); err != nil {
@@ -944,6 +987,38 @@ func NewContext() {
}
}
+func parseAuthorizedPrincipalsAllow(values []string) ([]string, bool) {
+ anything := false
+ email := false
+ username := false
+ for _, value := range values {
+ v := strings.ToLower(strings.TrimSpace(value))
+ switch v {
+ case "off":
+ return []string{"off"}, false
+ case "email":
+ email = true
+ case "username":
+ username = true
+ case "anything":
+ anything = true
+ }
+ }
+ if anything {
+ return []string{"anything"}, true
+ }
+
+ authorizedPrincipalsAllow := []string{}
+ if username {
+ authorizedPrincipalsAllow = append(authorizedPrincipalsAllow, "username")
+ }
+ if email {
+ authorizedPrincipalsAllow = append(authorizedPrincipalsAllow, "email")
+ }
+
+ return authorizedPrincipalsAllow, true
+}
+
func loadInternalToken(sec *ini.Section) string {
uri := sec.Key("INTERNAL_TOKEN_URI").String()
if len(uri) == 0 {
diff --git a/modules/ssh/ssh.go b/modules/ssh/ssh.go
index e7a694683a..7a449dd41b 100644
--- a/modules/ssh/ssh.go
+++ b/modules/ssh/ssh.go
@@ -5,6 +5,7 @@
package ssh
import (
+ "bytes"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
@@ -136,6 +137,52 @@ func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool {
return false
}
+ // check if we have a certificate
+ if cert, ok := key.(*gossh.Certificate); ok {
+ if len(setting.SSH.TrustedUserCAKeys) == 0 {
+ return false
+ }
+
+ // look for the exact principal
+ for _, principal := range cert.ValidPrincipals {
+ pkey, err := models.SearchPublicKeyByContentExact(principal)
+ if err != nil {
+ log.Error("SearchPublicKeyByContentExact: %v", err)
+ return false
+ }
+
+ if models.IsErrKeyNotExist(err) {
+ continue
+ }
+
+ c := &gossh.CertChecker{
+ IsUserAuthority: func(auth gossh.PublicKey) bool {
+ for _, k := range setting.SSH.TrustedUserCAKeysParsed {
+ if bytes.Equal(auth.Marshal(), k.Marshal()) {
+ return true
+ }
+ }
+
+ return false
+ },
+ }
+
+ // check the CA of the cert
+ if !c.IsUserAuthority(cert.SignatureKey) {
+ return false
+ }
+
+ // validate the cert for this principal
+ if err := c.CheckCert(principal, cert); err != nil {
+ return false
+ }
+
+ ctx.SetValue(giteaKeyID, pkey.ID)
+
+ return true
+ }
+ }
+
pkey, err := models.SearchPublicKeyByContent(strings.TrimSpace(string(gossh.MarshalAuthorizedKey(key))))
if err != nil {
log.Error("SearchPublicKeyByContent: %v", err)