mirror of
https://github.com/go-gitea/gitea.git
synced 2024-09-01 14:56:30 +00:00
replaced custom auth with existing methods
This commit is contained in:
parent
245a54532f
commit
597a948e33
@ -14,16 +14,12 @@ menu:
|
||||
|
||||
# Arch package registry
|
||||
|
||||
Gitea has arch package registry, which can act as a fully working [arch linux mirror](https://wiki.archlinux.org/title/mirrors) and connected directly in `/etc/pacman.conf`. Gitea automatically creates pacman database for packages in user space when new arch package is uploaded.
|
||||
Gitea has arch package registry, which can act as a fully working [arch linux mirror](https://wiki.archlinux.org/title/mirrors) and connected directly in `/etc/pacman.conf`. Gitea automatically creates pacman database for packages in user/organization space when new arch package is uploaded.
|
||||
|
||||
**Table of Contents**
|
||||
|
||||
{{< toc >}}
|
||||
|
||||
## Requirements
|
||||
|
||||
You can install packages in any environment with [pacman](https://wiki.archlinux.org/title/Pacman). Alternatively you can use [pack](https://fmnx.su/core/pack) which connects specified registries automatically and provides simple interface for package uploads and deletions.
|
||||
|
||||
## Install packages
|
||||
|
||||
First, you need to update your pacman configuration, adding following lines:
|
||||
@ -39,136 +35,37 @@ Then, you can run pacman sync command (with -y flag to load connected database f
|
||||
pacman -Sy package
|
||||
```
|
||||
|
||||
## GPG Verification
|
||||
|
||||
Upload and remove operation are validated with [GnuPG](https://gnupg.org/). First, you need to export and upload your public gpg key to `SSH/GPG Keys` in account settings. This works similarly to SSH keys. You can export gpg key with command:
|
||||
|
||||
```sh
|
||||
gpg --armor --export
|
||||
```
|
||||
|
||||
```
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mQENBGSYoJUBCADSJ6v8Egst/gNJVC2206o8JqTzRBxTULKm/DH5J7AzrhJBxC2/
|
||||
...
|
||||
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
```
|
||||
|
||||
## Upload packages
|
||||
|
||||
1. Ensure, that your package have been signed with your gpg key (more about arch [package signing](https://wiki.archlinux.org/title/DeveloperWiki:Package_signing)). You can do that by running following command:
|
||||
|
||||
```sh
|
||||
gpg --verify package-ver-1-x86_64.pkg.tar.zst.sig
|
||||
```
|
||||
|
||||
1. Sign message metadata, which consists of package owner (namespace in gitea), package file name and send time. You can do that by running following command:
|
||||
|
||||
```sh
|
||||
echo -n {owner}{package}$(date --rfc-3339=seconds | tr " " T) >> md
|
||||
gpg --detach-sign md
|
||||
```
|
||||
|
||||
1. Decode message and metadata signatures to hex, by running following commands, save output somewhere.
|
||||
|
||||
```sh
|
||||
xxd -p md.sig >> md.sig.hex
|
||||
xxd -p package-1-1-x86_64.pkg.tar.zst.sig >> pkg.sig.hex
|
||||
xxd -p package-1-1-x86_64.pkg.tar.zst.sig >> package-signature-hex
|
||||
```
|
||||
|
||||
1. Paste your parameters and push package with [curl](https://curl.se/). Important, that time should be the same with metadata (signed md file), since this value is verified with GnuPG.
|
||||
2. Paste your parameters and push package with [curl](https://curl.se/). Important, that time should be the same with metadata (signed md file), since this value is verified with GnuPG.
|
||||
|
||||
```sh
|
||||
curl -X PUT \
|
||||
'https://{domain}/api/packages/{owner}/arch/push' \
|
||||
--header 'filename: {package}-1-1-x86_64.pkg.tar.zst' \
|
||||
--header 'email: dancheg97@fmnx.su' \
|
||||
--header 'filename: package-1-1-x86_64.pkg.tar.zst' \
|
||||
--header 'distro: archlinux' \
|
||||
--header 'time: {metadata-time}' \
|
||||
--header 'pkgsign: {package-signature-hex}' \
|
||||
--header 'metasign: {metadata-signature-hex}' \
|
||||
--header 'Content-Type: application/octet-stream' \
|
||||
--data-binary '@/path/to/package/file/{package}-1-1-x86_64.pkg.tar.zst'
|
||||
```
|
||||
|
||||
Full script for package upload:
|
||||
|
||||
```sh
|
||||
owner=user
|
||||
package=package-0.1.0-1-x86_64.pkg.tar.zst
|
||||
email=user@example.com
|
||||
|
||||
time=`date --rfc-3339=seconds | tr " " T`
|
||||
pkgsignhex=`xxd -p $package.sig | tr -d "\n"`
|
||||
|
||||
echo -n $owner$package$time >> mddata
|
||||
gpg --detach-sign mddata
|
||||
mdsignhex=`xxd -p mddata.sig | tr -d "\n"`
|
||||
|
||||
curl -X PUT \
|
||||
http://{domain}/api/packages/$owner/arch/push \
|
||||
--header "filename: $package" \
|
||||
--header "email: $email" \
|
||||
--header "time: $time" \
|
||||
--header "distro: archlinux" \
|
||||
--header "metasign: $mdsignhex" \
|
||||
--header "pkgsign: $pkgsignhex" \
|
||||
--header 'Content-Type: application/octet-stream' \
|
||||
--data-binary @$package
|
||||
--data-binary '@/path/to/package/file/package-1-1-x86_64.pkg.tar.zst'
|
||||
```
|
||||
|
||||
## Delete packages
|
||||
|
||||
1. Prepare signature for delete message.
|
||||
|
||||
```sh
|
||||
echo -n {owner}{package}$(date --rfc-3339=seconds | tr " " T) >> md
|
||||
gpg --detach-sign md
|
||||
```
|
||||
|
||||
1. Send delete message with [curl](https://curl.se/). Time should be the same with saved in `md` file.
|
||||
|
||||
```sh
|
||||
curl -X DELETE \
|
||||
http://localhost:3000/api/packages/{user}/arch/remove \
|
||||
--header "username: {user}" \
|
||||
--header "email: user@email.com" \
|
||||
--header "target: package" \
|
||||
--header "time: {rmtime}" \
|
||||
--header "version: {version-release}" \
|
||||
--header 'Content-Type: application/octet-stream' \
|
||||
--data-binary @md.sig
|
||||
```
|
||||
|
||||
Full script for package deletion:
|
||||
|
||||
```sh
|
||||
owner=user
|
||||
package=package
|
||||
version=0.1.0-1
|
||||
email=user@example.com
|
||||
arch=x86_64
|
||||
time=`date --rfc-3339=seconds | tr " " T`
|
||||
|
||||
sudo rm -rf md md.sig
|
||||
echo -n $owner$package$time >> md
|
||||
gpg --detach-sign md
|
||||
|
||||
curl -X DELETE \
|
||||
http://{domain}/api/packages/$owner/arch/remove \
|
||||
--header "username: $owner" \
|
||||
--header "email: $email" \
|
||||
--header "target: $package" \
|
||||
--header "time: $time" \
|
||||
--header "version: $version" \
|
||||
--header 'Content-Type: application/octet-stream' \
|
||||
--data-binary @md.sig
|
||||
--header "version: {version-release}"
|
||||
```
|
||||
|
||||
## Clients
|
||||
|
||||
You can generate client code with tools like [thunder client](https://marketplace.visualstudio.com/items?itemName=rangav.vscode-thunder-client), [postman](https://blog.postman.com/curl-and-postman-work-wonderfully-together/) or other code generators to write your own client.
|
||||
|
||||
Also you can take a look at [pack](https://fmnx.su/core/pack) which provides `pacman` functionality with additional commands to build, sign and push your arch packages to gitea.
|
||||
You can use gitea CLI tool to - [tea](https://gitea.com/gitea/tea) to push/remove arch packages from gitea. Alternatively, you can try [pack](https://fmnx.su/core/pack).
|
||||
|
@ -124,8 +124,8 @@ func CommonRoutes() *web.Route {
|
||||
})
|
||||
}, reqPackageAccess(perm.AccessModeRead))
|
||||
r.Group("/arch", func() {
|
||||
r.Put("/push", arch.Push)
|
||||
r.Delete("/remove", arch.Remove)
|
||||
r.Put("/push", arch.Push, reqPackageAccess(perm.AccessModeWrite))
|
||||
r.Delete("/remove", arch.Remove, reqPackageAccess(perm.AccessModeWrite))
|
||||
r.Get("/{distro}/{arch}/{file}", arch.Get)
|
||||
})
|
||||
r.Group("/cargo", func() {
|
||||
|
@ -23,20 +23,10 @@ func Push(ctx *context.Context) {
|
||||
var (
|
||||
owner = ctx.Params("username")
|
||||
filename = ctx.Req.Header.Get("filename")
|
||||
email = ctx.Req.Header.Get("email")
|
||||
distro = ctx.Req.Header.Get("distro")
|
||||
sendtime = ctx.Req.Header.Get("time")
|
||||
pkgsign = ctx.Req.Header.Get("pkgsign")
|
||||
metasign = ctx.Req.Header.Get("metasign")
|
||||
sign = ctx.Req.Header.Get("sign")
|
||||
)
|
||||
|
||||
// Decoding package signature.
|
||||
sigdata, err := hex.DecodeString(pkgsign)
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Read package to memory for signature validation.
|
||||
pkgdata, err := io.ReadAll(ctx.Req.Body)
|
||||
if err != nil {
|
||||
@ -45,33 +35,6 @@ func Push(ctx *context.Context) {
|
||||
}
|
||||
defer ctx.Req.Body.Close()
|
||||
|
||||
// Get user and organization owning arch package.
|
||||
user, org, err := arch_service.IdentifyOwner(ctx, owner, email)
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusUnauthorized, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Decoding time when message was created.
|
||||
t, err := time.Parse(time.RFC3339, sendtime)
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if message is outdated.
|
||||
if time.Since(t) > time.Hour {
|
||||
apiError(ctx, http.StatusUnauthorized, "outdated message")
|
||||
return
|
||||
}
|
||||
|
||||
// Decoding signature related to metadata.
|
||||
msigdata, err := hex.DecodeString(metasign)
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Parse metadata contained in arch package archive.
|
||||
md, err := arch_module.EjectMetadata(filename, distro, setting.Domain, pkgdata)
|
||||
if err != nil {
|
||||
@ -79,53 +42,40 @@ func Push(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Validating metadata signature, to ensure that operation push operation
|
||||
// is initiated by original package owner.
|
||||
sendmetadata := []byte(owner + md.Name + sendtime)
|
||||
err = arch_service.ValidateSignature(ctx, sendmetadata, msigdata, user)
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusUnauthorized, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Validate package signature with any of user's GnuPG keys.
|
||||
err = arch_service.ValidateSignature(ctx, pkgdata, sigdata, user)
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusUnauthorized, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Save file related to arch package.
|
||||
pkgid, err := arch_service.SaveFile(ctx, &arch_service.SaveFileParams{
|
||||
Organization: org,
|
||||
User: user,
|
||||
Metadata: md,
|
||||
Filename: filename,
|
||||
Data: pkgdata,
|
||||
Distro: distro,
|
||||
Creator: ctx.Doer,
|
||||
Owner: ctx.Package.Owner,
|
||||
Metadata: md,
|
||||
Filename: filename,
|
||||
Data: pkgdata,
|
||||
Distro: distro,
|
||||
})
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Save file related to arch package signature.
|
||||
_, err = arch_service.SaveFile(ctx, &arch_service.SaveFileParams{
|
||||
Organization: org,
|
||||
User: user,
|
||||
Metadata: md,
|
||||
Data: sigdata,
|
||||
Filename: filename + ".sig",
|
||||
Distro: distro,
|
||||
})
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
// Decoding package signature, if present saving with package as file.
|
||||
sigdata, err := hex.DecodeString(sign)
|
||||
if err == nil {
|
||||
_, err = arch_service.SaveFile(ctx, &arch_service.SaveFileParams{
|
||||
Creator: ctx.Doer,
|
||||
Owner: ctx.Package.Owner,
|
||||
Metadata: md,
|
||||
Data: sigdata,
|
||||
Filename: filename + ".sig",
|
||||
Distro: distro,
|
||||
})
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Add new architectures and distribution info to package version metadata.
|
||||
err = arch_service.UpdateMetadata(ctx, &arch_service.UpdateMetadataParameters{
|
||||
User: org.AsUser(),
|
||||
User: ctx.Package.Owner,
|
||||
Md: md,
|
||||
})
|
||||
if err != nil {
|
||||
@ -190,50 +140,12 @@ func Get(ctx *context.Context) {
|
||||
// Remove specific package version, related files and pacman database entry.
|
||||
func Remove(ctx *context.Context) {
|
||||
var (
|
||||
owner = ctx.Params("username")
|
||||
email = ctx.Req.Header.Get("email")
|
||||
target = ctx.Req.Header.Get("target")
|
||||
stime = ctx.Req.Header.Get("time")
|
||||
version = ctx.Req.Header.Get("version")
|
||||
pkg = ctx.Req.Header.Get("package")
|
||||
ver = ctx.Req.Header.Get("version")
|
||||
)
|
||||
|
||||
// Parse sent time and check if it is within last minute.
|
||||
t, err := time.Parse(time.RFC3339, stime)
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
if time.Since(t) > time.Minute {
|
||||
apiError(ctx, http.StatusUnauthorized, "outdated message")
|
||||
return
|
||||
}
|
||||
|
||||
// Get user owning the package.
|
||||
user, org, err := arch_service.IdentifyOwner(ctx, owner, email)
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusUnauthorized, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Read signature data from request body.
|
||||
sigdata, err := io.ReadAll(ctx.Req.Body)
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
defer ctx.Req.Body.Close()
|
||||
|
||||
// Validate package signature with any of user's GnuPG keys.
|
||||
mesdata := []byte(owner + target + stime)
|
||||
err = arch_service.ValidateSignature(ctx, mesdata, sigdata, user)
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusUnauthorized, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Remove package files and pacman database entry.
|
||||
err = arch_service.RemovePackage(ctx, org.AsUser(), target, version)
|
||||
err := arch_service.RemovePackage(ctx, ctx.Package.Owner, pkg, ver)
|
||||
if err != nil {
|
||||
apiError(ctx, http.StatusInternalServerError, err)
|
||||
return
|
||||
|
@ -10,7 +10,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
org_model "code.gitea.io/gitea/models/organization"
|
||||
pkg_model "code.gitea.io/gitea/models/packages"
|
||||
"code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/user"
|
||||
@ -58,9 +57,9 @@ func UpdateMetadata(ctx *context.Context, p *UpdateMetadataParameters) error {
|
||||
|
||||
// Parameters required to save new arch package.
|
||||
type SaveFileParams struct {
|
||||
*org_model.Organization
|
||||
*user.User
|
||||
*arch.Metadata
|
||||
Creator *user.User
|
||||
Owner *user.User
|
||||
Metadata *arch.Metadata
|
||||
Data []byte
|
||||
Filename string
|
||||
Distro string
|
||||
@ -80,12 +79,12 @@ func SaveFile(ctx *context.Context, p *SaveFileParams) (int64, error) {
|
||||
pv, _, err := pkg_service.CreatePackageOrAddFileToExisting(
|
||||
&pkg_service.PackageCreationInfo{
|
||||
PackageInfo: pkg_service.PackageInfo{
|
||||
Owner: p.Organization.AsUser(),
|
||||
Owner: p.Owner,
|
||||
PackageType: pkg_model.TypeArch,
|
||||
Name: p.Metadata.Name,
|
||||
Version: p.Metadata.Version,
|
||||
},
|
||||
Creator: p.User,
|
||||
Creator: p.Creator,
|
||||
Metadata: p.Metadata,
|
||||
},
|
||||
&pkg_service.PackageFileCreationInfo{
|
||||
@ -93,7 +92,7 @@ func SaveFile(ctx *context.Context, p *SaveFileParams) (int64, error) {
|
||||
Filename: p.Filename,
|
||||
CompositeKey: p.Distro + "-" + p.Filename,
|
||||
},
|
||||
Creator: p.User,
|
||||
Creator: p.Creator,
|
||||
Data: buf,
|
||||
OverwriteExisting: true,
|
||||
IsLead: p.IsLead,
|
||||
|
@ -1,99 +0,0 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package arch
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/asymkey"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
org "code.gitea.io/gitea/models/organization"
|
||||
"code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
|
||||
"github.com/keybase/go-crypto/openpgp"
|
||||
)
|
||||
|
||||
type IdentidyOwnerParameters struct {
|
||||
*context.Context
|
||||
Owner string
|
||||
Email string
|
||||
}
|
||||
|
||||
// This function will find user related to provided email address and check if
|
||||
// he is able to push packages to provided namespace (user/organization/or
|
||||
// empty namespace allowed for admin users). Function will return user making
|
||||
// operation, organization or user owning the package.
|
||||
func IdentifyOwner(ctx *context.Context, owner, email string) (*user.User, *org.Organization, error) {
|
||||
u, err := user.GetUserByEmail(ctx, email)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to find user with email %s, %v", email, err)
|
||||
}
|
||||
|
||||
if owner == "" && u.IsAdmin {
|
||||
return u, (*org.Organization)(u), nil
|
||||
}
|
||||
|
||||
if owner == u.Name {
|
||||
return u, (*org.Organization)(u), nil
|
||||
}
|
||||
|
||||
if u.Name != owner {
|
||||
org, err := org.GetOrgByName(ctx, owner)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to get organization: %s, %v", owner, err)
|
||||
}
|
||||
ismember, err := org.IsOrgMember(u.ID)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to check if user %s belongs to organization %s: %v", u.Name, org.Name, err)
|
||||
}
|
||||
if !ismember {
|
||||
return nil, nil, fmt.Errorf("user %s is not member of organization %s", u.Name, org.Name)
|
||||
}
|
||||
return u, org, nil
|
||||
}
|
||||
return nil, nil, fmt.Errorf("unknown package owner")
|
||||
}
|
||||
|
||||
// Validate package signature with owner's GnuPG keys stored in gitea's database.
|
||||
func ValidateSignature(ctx *context.Context, pkg, sign []byte, u *user.User) error {
|
||||
keys, err := asymkey.ListGPGKeys(ctx, u.ID, db.ListOptions{
|
||||
ListAll: true,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.New("unable to get public keys")
|
||||
}
|
||||
if len(keys) == 0 {
|
||||
return errors.New("no keys for user with email: " + u.Email)
|
||||
}
|
||||
|
||||
var keyarmors []string
|
||||
for _, key := range keys {
|
||||
k, err := asymkey.GetGPGImportByKeyID(key.KeyID)
|
||||
if err != nil {
|
||||
return errors.New("unable to import GPG key armor")
|
||||
}
|
||||
keyarmors = append(keyarmors, k.Content)
|
||||
}
|
||||
|
||||
var trace []error
|
||||
for _, armor := range keyarmors {
|
||||
kr, err := openpgp.ReadArmoredKeyRing(strings.NewReader(armor))
|
||||
if err != nil {
|
||||
trace = append(trace, fmt.Errorf("unable to get keys for %s: %v", u.Name, err))
|
||||
continue
|
||||
}
|
||||
_, err = openpgp.CheckDetachedSignature(kr, bytes.NewReader(pkg), bytes.NewReader(sign))
|
||||
if err != nil {
|
||||
trace = append(trace, err)
|
||||
continue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.Join(trace...)
|
||||
}
|
Loading…
Reference in New Issue
Block a user