diff options
Diffstat (limited to 'modules')
-rw-r--r-- | modules/cron/tasks_extended.go | 11 | ||||
-rw-r--r-- | modules/setting/setting.go | 111 | ||||
-rw-r--r-- | modules/ssh/ssh.go | 47 |
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) |