Skip to content
Closed
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
40 changes: 26 additions & 14 deletions models/asymkey/ssh_key_authorized_keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,10 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"

"golang.org/x/crypto/ssh"
)

// _____ __ .__ .__ .___
// / _ \ __ ___/ |_| |__ ___________|__|_______ ____ __| _/
// / /_\ \| | \ __\ | \ / _ \_ __ \ \___ // __ \ / __ |
// / | \ | /| | | Y ( <_> ) | \/ |/ /\ ___// /_/ |
// \____|__ /____/ |__| |___| /\____/|__| |__/_____ \\___ >____ |
// \/ \/ \/ \/ \/
// ____ __.
// | |/ _|____ ___.__. ______
// | <_/ __ < | |/ ___/
// | | \ ___/\___ |\___ \
// |____|__ \___ > ____/____ >
// \/ \/\/ \/
//
// This file contains functions for creating authorized_keys files
//
// There is a dependence on the database within RegeneratePublicKeys however most of these functions probably belong in a module
Expand All @@ -49,6 +38,23 @@ func WithSSHOpLocker(f func() error) error {
return f()
}

// removeSSHKeyComment removes the trailing comment from an SSH public key line.
func removeSSHKeyComment(pubKeyLine string) (string, error) {
pubKeyLine = strings.TrimSpace(pubKeyLine)
if pubKeyLine == "" || strings.HasPrefix(pubKeyLine, "#") {
return pubKeyLine, nil
}

pubKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(pubKeyLine))
if err != nil {
return "", fmt.Errorf("invalid public key: %w", err)
}

// MarshalAuthorizedKey returns "<type> <base64>\n"
key := strings.TrimSpace(string(ssh.MarshalAuthorizedKey(pubKey)))
return key, nil
}

// AuthorizedStringForKey creates the authorized keys string appropriate for the provided key
func AuthorizedStringForKey(key *PublicKey) string {
sb := &strings.Builder{}
Expand All @@ -60,7 +66,13 @@ func AuthorizedStringForKey(key *PublicKey) string {
"Key": key,
})

return fmt.Sprintf(tplPublicKey, util.ShellEscape(sb.String()), key.Content)
content, err := removeSSHKeyComment(key.Content)
if err != nil {
log.Error("Failed to remove comment from SSH key ID %d: %v", key.ID, err)
content = key.Content
}

return fmt.Sprintf(tplPublicKey, util.ShellEscape(sb.String()), content)
}

// appendAuthorizedKeysToFile appends new SSH keys' content to authorized_keys file.
Expand Down
16 changes: 16 additions & 0 deletions models/asymkey/ssh_key_authorized_keys_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package asymkey

import (
"testing"

"github.com/stretchr/testify/assert"
)

func Test_removeSSHKeyComment(t *testing.T) {
content, err := removeSSHKeyComment("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINna5Jd6FTG4d87pUHnd/uLBr/6zGOVVFEQmdTs6k21L user@hostname")
assert.NoError(t, err)
assert.Equal(t, "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINna5Jd6FTG4d87pUHnd/uLBr/6zGOVVFEQmdTs6k21L", content)
}
56 changes: 33 additions & 23 deletions services/repository/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,33 +153,43 @@ func processGiteaTemplateFile(ctx context.Context, tmpDir string, templateRepo,
if d.IsDir() {
return nil
}
fInfo, err := d.Info()
if err != nil {
return err
}
mode := fInfo.Mode()
if !mode.IsRegular() { // for symlinks and other special files, just skip
return nil
}

base := strings.TrimPrefix(filepath.ToSlash(path), tmpDirSlash)
for _, g := range giteaTemplateFile.Globs() {
if g.Match(base) {
content, err := os.ReadFile(path)
if err != nil {
return err
}

generatedContent := []byte(generateExpansion(ctx, string(content), templateRepo, generateRepo, false))
if err := os.WriteFile(path, generatedContent, 0o644); err != nil {
return err
}

substPath := filepath.FromSlash(filepath.Join(tmpDirSlash, generateExpansion(ctx, base, templateRepo, generateRepo, true)))

// Create parent subdirectories if needed or continue silently if it exists
if err = os.MkdirAll(filepath.Dir(substPath), 0o755); err != nil {
return err
}

// Substitute filename variables
if err = os.Rename(path, substPath); err != nil {
return err
}
break
if !g.Match(base) {
continue
}

content, err := os.ReadFile(path)
if err != nil {
return err
}

generatedContent := []byte(generateExpansion(ctx, string(content), templateRepo, generateRepo, false))
if err := os.WriteFile(path, generatedContent, 0o644); err != nil {
return err
}

substPath := filepath.FromSlash(filepath.Join(tmpDirSlash, generateExpansion(ctx, base, templateRepo, generateRepo, true)))

// Create parent subdirectories if needed or continue silently if it exists
if err = os.MkdirAll(filepath.Dir(substPath), 0o755); err != nil {
return err
}

// Substitute filename variables
if err = os.Rename(path, substPath); err != nil {
return err
}
break
}
return nil
}) // end: WalkDir
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/cmd_keys_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func Test_CmdKeys(t *testing.T) {
"with_key",
[]string{"keys", "-e", "git", "-u", "git", "-t", "ssh-rsa", "-k", "AAAAB3NzaC1yc2EAAAADAQABAAABgQDWVj0fQ5N8wNc0LVNA41wDLYJ89ZIbejrPfg/avyj3u/ZohAKsQclxG4Ju0VirduBFF9EOiuxoiFBRr3xRpqzpsZtnMPkWVWb+akZwBFAx8p+jKdy4QXR/SZqbVobrGwip2UjSrri1CtBxpJikojRIZfCnDaMOyd9Jp6KkujvniFzUWdLmCPxUE9zhTaPu0JsEP7MW0m6yx7ZUhHyfss+NtqmFTaDO+QlMR7L2QkDliN2Jl3Xa3PhuWnKJfWhdAq1Cw4oraKUOmIgXLkuiuxVQ6mD3AiFupkmfqdHq6h+uHHmyQqv3gU+/sD8GbGAhf6ftqhTsXjnv1Aj4R8NoDf9BS6KRkzkeun5UisSzgtfQzjOMEiJtmrep2ZQrMGahrXa+q4VKr0aKJfm+KlLfwm/JztfsBcqQWNcTURiCFqz+fgZw0Ey/de0eyMzldYTdXXNRYCKjs9bvBK+6SSXRM7AhftfQ0ZuoW5+gtinPrnmoOaSCEJbAiEiTO/BzOHgowiM="},
false,
"# gitea public key\ncommand=\"" + setting.AppPath + " --config=" + util.ShellEscape(setting.CustomConf) + " serv key-1\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,no-user-rc,restrict ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDWVj0fQ5N8wNc0LVNA41wDLYJ89ZIbejrPfg/avyj3u/ZohAKsQclxG4Ju0VirduBFF9EOiuxoiFBRr3xRpqzpsZtnMPkWVWb+akZwBFAx8p+jKdy4QXR/SZqbVobrGwip2UjSrri1CtBxpJikojRIZfCnDaMOyd9Jp6KkujvniFzUWdLmCPxUE9zhTaPu0JsEP7MW0m6yx7ZUhHyfss+NtqmFTaDO+QlMR7L2QkDliN2Jl3Xa3PhuWnKJfWhdAq1Cw4oraKUOmIgXLkuiuxVQ6mD3AiFupkmfqdHq6h+uHHmyQqv3gU+/sD8GbGAhf6ftqhTsXjnv1Aj4R8NoDf9BS6KRkzkeun5UisSzgtfQzjOMEiJtmrep2ZQrMGahrXa+q4VKr0aKJfm+KlLfwm/JztfsBcqQWNcTURiCFqz+fgZw0Ey/de0eyMzldYTdXXNRYCKjs9bvBK+6SSXRM7AhftfQ0ZuoW5+gtinPrnmoOaSCEJbAiEiTO/BzOHgowiM= user2@localhost\n",
"# gitea public key\ncommand=\"" + setting.AppPath + " --config=" + util.ShellEscape(setting.CustomConf) + " serv key-1\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,no-user-rc,restrict ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDWVj0fQ5N8wNc0LVNA41wDLYJ89ZIbejrPfg/avyj3u/ZohAKsQclxG4Ju0VirduBFF9EOiuxoiFBRr3xRpqzpsZtnMPkWVWb+akZwBFAx8p+jKdy4QXR/SZqbVobrGwip2UjSrri1CtBxpJikojRIZfCnDaMOyd9Jp6KkujvniFzUWdLmCPxUE9zhTaPu0JsEP7MW0m6yx7ZUhHyfss+NtqmFTaDO+QlMR7L2QkDliN2Jl3Xa3PhuWnKJfWhdAq1Cw4oraKUOmIgXLkuiuxVQ6mD3AiFupkmfqdHq6h+uHHmyQqv3gU+/sD8GbGAhf6ftqhTsXjnv1Aj4R8NoDf9BS6KRkzkeun5UisSzgtfQzjOMEiJtmrep2ZQrMGahrXa+q4VKr0aKJfm+KlLfwm/JztfsBcqQWNcTURiCFqz+fgZw0Ey/de0eyMzldYTdXXNRYCKjs9bvBK+6SSXRM7AhftfQ0ZuoW5+gtinPrnmoOaSCEJbAiEiTO/BzOHgowiM=\n",
},
{"invalid", []string{"keys", "--not-a-flag=git"}, true, "Incorrect Usage: flag provided but not defined: -not-a-flag\n\n"},
}
Expand Down