Skip to content

Move some asymkey functions to service layer #28894

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cmd/admin_regenerate.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
package cmd

import (
asymkey_model "code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/modules/graceful"
asymkey_service "code.gitea.io/gitea/services/asymkey"
repo_service "code.gitea.io/gitea/services/repository"

"github.com/urfave/cli/v2"
Expand Down Expand Up @@ -42,5 +42,5 @@ func runRegenerateKeys(_ *cli.Context) error {
if err := initDB(ctx); err != nil {
return err
}
return asymkey_model.RewriteAllPublicKeys(ctx)
return asymkey_service.RewriteAllPublicKeys(ctx)
}
66 changes: 6 additions & 60 deletions models/asymkey/ssh_key_authorized_keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"path/filepath"
"strings"
"sync"
"time"

"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log"
Expand Down Expand Up @@ -44,6 +43,12 @@ const (

var sshOpLocker sync.Mutex

func WithSSHOpLocker(f func() error) error {
sshOpLocker.Lock()
defer sshOpLocker.Unlock()
return f()
}

// AuthorizedStringForKey creates the authorized keys string appropriate for the provided key
func AuthorizedStringForKey(key *PublicKey) string {
sb := &strings.Builder{}
Expand Down Expand Up @@ -114,65 +119,6 @@ func appendAuthorizedKeysToFile(keys ...*PublicKey) error {
return nil
}

// RewriteAllPublicKeys removes any authorized key and rewrite all keys from database again.
// Note: db.GetEngine(ctx).Iterate does not get latest data after insert/delete, so we have to call this function
// outside any session scope independently.
func RewriteAllPublicKeys(ctx context.Context) error {
// Don't rewrite key if internal server
if setting.SSH.StartBuiltinServer || !setting.SSH.CreateAuthorizedKeysFile {
return nil
}

sshOpLocker.Lock()
defer sshOpLocker.Unlock()

if setting.SSH.RootPath != "" {
// First of ensure that the RootPath is present, and if not make it with 0700 permissions
// This of course doesn't guarantee that this is the right directory for authorized_keys
// but at least if it's supposed to be this directory and it doesn't exist and we're the
// right user it will at least be created properly.
err := os.MkdirAll(setting.SSH.RootPath, 0o700)
if err != nil {
log.Error("Unable to MkdirAll(%s): %v", setting.SSH.RootPath, err)
return err
}
}

fPath := filepath.Join(setting.SSH.RootPath, "authorized_keys")
tmpPath := fPath + ".tmp"
t, err := os.OpenFile(tmpPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600)
if err != nil {
return err
}
defer func() {
t.Close()
if err := util.Remove(tmpPath); err != nil {
log.Warn("Unable to remove temporary authorized keys file: %s: Error: %v", tmpPath, err)
}
}()

if setting.SSH.AuthorizedKeysBackup {
isExist, err := util.IsExist(fPath)
if err != nil {
log.Error("Unable to check if %s exists. Error: %v", fPath, err)
return err
}
if isExist {
bakPath := fmt.Sprintf("%s_%d.gitea_bak", fPath, time.Now().Unix())
if err = util.CopyFile(fPath, bakPath); err != nil {
return err
}
}
}

if err := RegeneratePublicKeys(ctx, t); err != nil {
return err
}

t.Close()
return util.Rename(tmpPath, fPath)
}

// RegeneratePublicKeys regenerates the authorized_keys file
func RegeneratePublicKeys(ctx context.Context, t io.StringWriter) error {
if err := db.GetEngine(ctx).Where("type != ?", KeyTypePrincipal).Iterate(new(PublicKey), func(idx int, bean any) (err error) {
Expand Down
40 changes: 0 additions & 40 deletions models/asymkey/ssh_key_principals.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,51 +9,11 @@ import (
"strings"

"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/perm"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
)

// AddPrincipalKey adds new principal to database and authorized_principals file.
func AddPrincipalKey(ctx context.Context, ownerID int64, content string, authSourceID int64) (*PublicKey, error) {
dbCtx, committer, err := db.TxContext(ctx)
if err != nil {
return nil, err
}
defer committer.Close()

// Principals cannot be duplicated.
has, err := db.GetEngine(dbCtx).
Where("content = ? AND type = ?", content, KeyTypePrincipal).
Get(new(PublicKey))
if err != nil {
return nil, err
} else if has {
return nil, ErrKeyAlreadyExist{0, "", content}
}

key := &PublicKey{
OwnerID: ownerID,
Name: content,
Content: content,
Mode: perm.AccessModeWrite,
Type: KeyTypePrincipal,
LoginSourceID: authSourceID,
}
if err = db.Insert(dbCtx, key); err != nil {
return nil, fmt.Errorf("addKey: %w", err)
}

if err = committer.Commit(); err != nil {
return nil, err
}

committer.Close()

return key, RewriteAllPrincipalKeys(ctx)
}

// CheckPrincipalKeyString strips spaces and returns an error if the given principal contains newlines
func CheckPrincipalKeyString(ctx context.Context, user *user_model.User, content string) (_ string, err error) {
if setting.SSH.Disabled {
Expand Down
4 changes: 2 additions & 2 deletions routers/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"runtime"

"code.gitea.io/gitea/models"
asymkey_model "code.gitea.io/gitea/models/asymkey"
authmodel "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/eventsource"
Expand All @@ -33,6 +32,7 @@ import (
"code.gitea.io/gitea/routers/private"
web_routers "code.gitea.io/gitea/routers/web"
actions_service "code.gitea.io/gitea/services/actions"
asymkey_service "code.gitea.io/gitea/services/asymkey"
"code.gitea.io/gitea/services/auth"
"code.gitea.io/gitea/services/auth/source/oauth2"
"code.gitea.io/gitea/services/automerge"
Expand Down Expand Up @@ -94,7 +94,7 @@ func syncAppConfForGit(ctx context.Context) error {
mustInitCtx(ctx, repo_service.SyncRepositoryHooks)

log.Info("re-write ssh public keys ...")
mustInitCtx(ctx, asymkey_model.RewriteAllPublicKeys)
mustInitCtx(ctx, asymkey_service.RewriteAllPublicKeys)

return system.AppState.Set(ctx, runtimeState)
}
Expand Down
2 changes: 1 addition & 1 deletion routers/web/user/setting/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func KeysPost(ctx *context.Context) {
ctx.Redirect(setting.AppSubURL + "/user/settings/keys")
return
}
if _, err = asymkey_model.AddPrincipalKey(ctx, ctx.Doer.ID, content, 0); err != nil {
if _, err = asymkey_service.AddPrincipalKey(ctx, ctx.Doer.ID, content, 0); err != nil {
ctx.Data["HasPrincipalError"] = true
switch {
case asymkey_model.IsErrKeyAlreadyExist(err), asymkey_model.IsErrKeyNameAlreadyUsed(err):
Expand Down
3 changes: 1 addition & 2 deletions services/asymkey/deploy_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"context"

"code.gitea.io/gitea/models"
asymkey_model "code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
)
Expand All @@ -27,5 +26,5 @@ func DeleteDeployKey(ctx context.Context, doer *user_model.User, id int64) error
return err
}

return asymkey_model.RewriteAllPublicKeys(ctx)
return RewriteAllPublicKeys(ctx)
}
4 changes: 2 additions & 2 deletions services/asymkey/ssh_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ func DeletePublicKey(ctx context.Context, doer *user_model.User, id int64) (err
committer.Close()

if key.Type == asymkey_model.KeyTypePrincipal {
return asymkey_model.RewriteAllPrincipalKeys(ctx)
return RewriteAllPrincipalKeys(ctx)
}

return asymkey_model.RewriteAllPublicKeys(ctx)
return RewriteAllPublicKeys(ctx)
}
79 changes: 79 additions & 0 deletions services/asymkey/ssh_key_authorized_keys.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package asymkey

import (
"context"
"fmt"
"os"
"path/filepath"
"time"

asymkey_model "code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
)

// RewriteAllPublicKeys removes any authorized key and rewrite all keys from database again.
// Note: db.GetEngine(ctx).Iterate does not get latest data after insert/delete, so we have to call this function
// outside any session scope independently.
func RewriteAllPublicKeys(ctx context.Context) error {
// Don't rewrite key if internal server
if setting.SSH.StartBuiltinServer || !setting.SSH.CreateAuthorizedKeysFile {
return nil
}

return asymkey_model.WithSSHOpLocker(func() error {
return rewriteAllPublicKeys(ctx)
})
}

func rewriteAllPublicKeys(ctx context.Context) error {
if setting.SSH.RootPath != "" {
// First of ensure that the RootPath is present, and if not make it with 0700 permissions
// This of course doesn't guarantee that this is the right directory for authorized_keys
// but at least if it's supposed to be this directory and it doesn't exist and we're the
// right user it will at least be created properly.
err := os.MkdirAll(setting.SSH.RootPath, 0o700)
if err != nil {
log.Error("Unable to MkdirAll(%s): %v", setting.SSH.RootPath, err)
return err
}
}

fPath := filepath.Join(setting.SSH.RootPath, "authorized_keys")
tmpPath := fPath + ".tmp"
t, err := os.OpenFile(tmpPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600)
if err != nil {
return err
}
defer func() {
t.Close()
if err := util.Remove(tmpPath); err != nil {
log.Warn("Unable to remove temporary authorized keys file: %s: Error: %v", tmpPath, err)
}
}()

if setting.SSH.AuthorizedKeysBackup {
isExist, err := util.IsExist(fPath)
if err != nil {
log.Error("Unable to check if %s exists. Error: %v", fPath, err)
return err
}
if isExist {
bakPath := fmt.Sprintf("%s_%d.gitea_bak", fPath, time.Now().Unix())
if err = util.CopyFile(fPath, bakPath); err != nil {
return err
}
}
}

if err := asymkey_model.RegeneratePublicKeys(ctx, t); err != nil {
return err
}

t.Close()
return util.Rename(tmpPath, fPath)
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,22 @@ import (
"strings"
"time"

asymkey_model "code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
)

// _____ __ .__ .__ .___
// / _ \ __ ___/ |_| |__ ___________|__|_______ ____ __| _/
// / /_\ \| | \ __\ | \ / _ \_ __ \ \___ // __ \ / __ |
// / | \ | /| | | Y ( <_> ) | \/ |/ /\ ___// /_/ |
// \____|__ /____/ |__| |___| /\____/|__| |__/_____ \\___ >____ |
// \/ \/ \/ \/ \/
// __________ .__ .__ .__
// \______ _______|__| ____ ____ |_____________ | | ______
// | ___\_ __ | |/ \_/ ___\| \____ \__ \ | | / ___/
// | | | | \| | | \ \___| | |_> / __ \| |__\___ \
// |____| |__| |__|___| /\___ |__| __(____ |____/____ >
// \/ \/ |__| \/ \/
//
// This file contains functions for creating authorized_principals files
//
// There is a dependence on the database within RewriteAllPrincipalKeys & RegeneratePrincipalKeys
// The sshOpLocker is used from ssh_key_authorized_keys.go

const authorizedPrincipalsFile = "authorized_principals"
const (
authorizedPrincipalsFile = "authorized_principals"
tplCommentPrefix = `# gitea public key`
)

// RewriteAllPrincipalKeys removes any authorized principal and rewrite all keys from database again.
// Note: db.GetEngine(ctx).Iterate does not get latest data after insert/delete, so we have to call this function
Expand All @@ -48,9 +39,12 @@ func RewriteAllPrincipalKeys(ctx context.Context) error {
return nil
}

sshOpLocker.Lock()
defer sshOpLocker.Unlock()
return asymkey_model.WithSSHOpLocker(func() error {
return rewriteAllPrincipalKeys(ctx)
})
}

func rewriteAllPrincipalKeys(ctx context.Context) error {
if setting.SSH.RootPath != "" {
// First of ensure that the RootPath is present, and if not make it with 0700 permissions
// This of course doesn't guarantee that this is the right directory for authorized_keys
Expand Down Expand Up @@ -97,8 +91,8 @@ func RewriteAllPrincipalKeys(ctx context.Context) error {
}

func regeneratePrincipalKeys(ctx context.Context, t io.StringWriter) error {
if err := db.GetEngine(ctx).Where("type = ?", KeyTypePrincipal).Iterate(new(PublicKey), func(idx int, bean any) (err error) {
_, err = t.WriteString((bean.(*PublicKey)).AuthorizedString())
if err := db.GetEngine(ctx).Where("type = ?", asymkey_model.KeyTypePrincipal).Iterate(new(asymkey_model.PublicKey), func(idx int, bean any) (err error) {
_, err = t.WriteString((bean.(*asymkey_model.PublicKey)).AuthorizedString())
return err
}); err != nil {
return err
Expand Down
Loading