aboutsummaryrefslogtreecommitdiffstats
path: root/modules
diff options
context:
space:
mode:
authorKN4CK3R <admin@oldschoolhack.me>2023-05-12 19:27:50 +0200
committerGitHub <noreply@github.com>2023-05-12 17:27:50 +0000
commit9173e079ae9ddf18685216fd846ca1727297393c (patch)
tree3437a68d48c338f5721146e951f553fb40facbab /modules
parent80bde0141bb4a04b65b399b40ab547bf56c0567e (diff)
downloadgitea-9173e079ae9ddf18685216fd846ca1727297393c.tar.gz
gitea-9173e079ae9ddf18685216fd846ca1727297393c.zip
Add Alpine package registry (#23714)
This PR adds an Alpine package registry. You can follow [this tutorial](https://wiki.alpinelinux.org/wiki/Creating_an_Alpine_package) to build a *.apk package for testing. This functionality is similar to the Debian registry (#22854) and therefore shares some methods. I marked this PR as blocked because it should be merged after #22854. ![grafik](https://user-images.githubusercontent.com/1666336/227779595-b76163aa-eea1-4a79-9583-775c24ad74e8.png) --------- Co-authored-by: techknowlogick <techknowlogick@gitea.io> Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: Giteabot <teabot@gitea.io>
Diffstat (limited to 'modules')
-rw-r--r--modules/packages/alpine/metadata.go236
-rw-r--r--modules/packages/alpine/metadata_test.go143
-rw-r--r--modules/setting/packages.go2
-rw-r--r--modules/util/keypair.go16
4 files changed, 397 insertions, 0 deletions
diff --git a/modules/packages/alpine/metadata.go b/modules/packages/alpine/metadata.go
new file mode 100644
index 0000000000..c2d0caffa1
--- /dev/null
+++ b/modules/packages/alpine/metadata.go
@@ -0,0 +1,236 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package alpine
+
+import (
+ "archive/tar"
+ "bufio"
+ "compress/gzip"
+ "crypto/sha1"
+ "encoding/base64"
+ "io"
+ "strconv"
+ "strings"
+
+ "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/validation"
+)
+
+var (
+ ErrMissingPKGINFOFile = util.NewInvalidArgumentErrorf("PKGINFO file is missing")
+ ErrInvalidName = util.NewInvalidArgumentErrorf("package name is invalid")
+ ErrInvalidVersion = util.NewInvalidArgumentErrorf("package version is invalid")
+)
+
+const (
+ PropertyMetadata = "alpine.metadata"
+ PropertyBranch = "alpine.branch"
+ PropertyRepository = "alpine.repository"
+ PropertyArchitecture = "alpine.architecture"
+
+ SettingKeyPrivate = "alpine.key.private"
+ SettingKeyPublic = "alpine.key.public"
+
+ RepositoryPackage = "_alpine"
+ RepositoryVersion = "_repository"
+)
+
+// https://wiki.alpinelinux.org/wiki/Apk_spec
+
+// Package represents an Alpine package
+type Package struct {
+ Name string
+ Version string
+ VersionMetadata VersionMetadata
+ FileMetadata FileMetadata
+}
+
+// Metadata of an Alpine package
+type VersionMetadata struct {
+ Description string `json:"description,omitempty"`
+ License string `json:"license,omitempty"`
+ ProjectURL string `json:"project_url,omitempty"`
+ Maintainer string `json:"maintainer,omitempty"`
+}
+
+type FileMetadata struct {
+ Checksum string `json:"checksum"`
+ Packager string `json:"packager,omitempty"`
+ BuildDate int64 `json:"build_date,omitempty"`
+ Size int64 `json:"size,omitempty"`
+ Architecture string `json:"architecture,omitempty"`
+ Origin string `json:"origin,omitempty"`
+ CommitHash string `json:"commit_hash,omitempty"`
+ InstallIf string `json:"install_if,omitempty"`
+ Provides []string `json:"provides,omitempty"`
+ Dependencies []string `json:"dependencies,omitempty"`
+}
+
+// ParsePackage parses the Alpine package file
+func ParsePackage(r io.Reader) (*Package, error) {
+ // Alpine packages are concated .tar.gz streams. Usually the first stream contains the package metadata.
+
+ br := bufio.NewReader(r) // needed for gzip Multistream
+
+ h := sha1.New()
+
+ gzr, err := gzip.NewReader(&teeByteReader{br, h})
+ if err != nil {
+ return nil, err
+ }
+ defer gzr.Close()
+
+ for {
+ gzr.Multistream(false)
+
+ tr := tar.NewReader(gzr)
+ for {
+ hd, err := tr.Next()
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ if hd.Name == ".PKGINFO" {
+ p, err := ParsePackageInfo(tr)
+ if err != nil {
+ return nil, err
+ }
+
+ // drain the reader
+ for {
+ if _, err := tr.Next(); err != nil {
+ break
+ }
+ }
+
+ p.FileMetadata.Checksum = "Q1" + base64.StdEncoding.EncodeToString(h.Sum(nil))
+
+ return p, nil
+ }
+ }
+
+ h = sha1.New()
+
+ err = gzr.Reset(&teeByteReader{br, h})
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return nil, ErrMissingPKGINFOFile
+}
+
+// ParsePackageInfo parses a PKGINFO file to retrieve the metadata of an Alpine package
+func ParsePackageInfo(r io.Reader) (*Package, error) {
+ p := &Package{}
+
+ scanner := bufio.NewScanner(r)
+ for scanner.Scan() {
+ line := scanner.Text()
+
+ if strings.HasPrefix(line, "#") {
+ continue
+ }
+
+ i := strings.IndexRune(line, '=')
+ if i == -1 {
+ continue
+ }
+
+ key := strings.TrimSpace(line[:i])
+ value := strings.TrimSpace(line[i+1:])
+
+ switch key {
+ case "pkgname":
+ p.Name = value
+ case "pkgver":
+ p.Version = value
+ case "pkgdesc":
+ p.VersionMetadata.Description = value
+ case "url":
+ p.VersionMetadata.ProjectURL = value
+ case "builddate":
+ n, err := strconv.ParseInt(value, 10, 64)
+ if err == nil {
+ p.FileMetadata.BuildDate = n
+ }
+ case "size":
+ n, err := strconv.ParseInt(value, 10, 64)
+ if err == nil {
+ p.FileMetadata.Size = n
+ }
+ case "arch":
+ p.FileMetadata.Architecture = value
+ case "origin":
+ p.FileMetadata.Origin = value
+ case "commit":
+ p.FileMetadata.CommitHash = value
+ case "maintainer":
+ p.VersionMetadata.Maintainer = value
+ case "packager":
+ p.FileMetadata.Packager = value
+ case "license":
+ p.VersionMetadata.License = value
+ case "install_if":
+ p.FileMetadata.InstallIf = value
+ case "provides":
+ if value != "" {
+ p.FileMetadata.Provides = append(p.FileMetadata.Provides, value)
+ }
+ case "depend":
+ if value != "" {
+ p.FileMetadata.Dependencies = append(p.FileMetadata.Dependencies, value)
+ }
+ }
+ }
+ if err := scanner.Err(); err != nil {
+ return nil, err
+ }
+
+ if p.Name == "" {
+ return nil, ErrInvalidName
+ }
+
+ if p.Version == "" {
+ return nil, ErrInvalidVersion
+ }
+
+ if !validation.IsValidURL(p.VersionMetadata.ProjectURL) {
+ p.VersionMetadata.ProjectURL = ""
+ }
+
+ return p, nil
+}
+
+// Same as io.TeeReader but implements io.ByteReader
+type teeByteReader struct {
+ r *bufio.Reader
+ w io.Writer
+}
+
+func (t *teeByteReader) Read(p []byte) (int, error) {
+ n, err := t.r.Read(p)
+ if n > 0 {
+ if n, err := t.w.Write(p[:n]); err != nil {
+ return n, err
+ }
+ }
+ return n, err
+}
+
+func (t *teeByteReader) ReadByte() (byte, error) {
+ b, err := t.r.ReadByte()
+ if err == nil {
+ if _, err := t.w.Write([]byte{b}); err != nil {
+ return 0, err
+ }
+ }
+ return b, err
+}
diff --git a/modules/packages/alpine/metadata_test.go b/modules/packages/alpine/metadata_test.go
new file mode 100644
index 0000000000..2a3c48ffb9
--- /dev/null
+++ b/modules/packages/alpine/metadata_test.go
@@ -0,0 +1,143 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package alpine
+
+import (
+ "archive/tar"
+ "bytes"
+ "compress/gzip"
+ "io"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+const (
+ packageName = "gitea"
+ packageVersion = "1.0.1"
+ packageDescription = "Package Description"
+ packageProjectURL = "https://gitea.io"
+ packageMaintainer = "KN4CK3R <dummy@gitea.io>"
+)
+
+func createPKGINFOContent(name, version string) []byte {
+ return []byte(`pkgname = ` + name + `
+pkgver = ` + version + `
+pkgdesc = ` + packageDescription + `
+url = ` + packageProjectURL + `
+# comment
+builddate = 1678834800
+packager = Gitea <pack@ag.er>
+size = 123456
+arch = aarch64
+origin = origin
+commit = 1111e709613fbc979651b09ac2bc27c6591a9999
+maintainer = ` + packageMaintainer + `
+license = MIT
+depend = common
+install_if = value
+depend = gitea
+provides = common
+provides = gitea`)
+}
+
+func TestParsePackage(t *testing.T) {
+ createPackage := func(name string, content []byte) io.Reader {
+ names := []string{"first.stream", name}
+ contents := [][]byte{{0}, content}
+
+ var buf bytes.Buffer
+ zw := gzip.NewWriter(&buf)
+
+ for i := range names {
+ if i != 0 {
+ zw.Close()
+ zw.Reset(&buf)
+ }
+
+ tw := tar.NewWriter(zw)
+ hdr := &tar.Header{
+ Name: names[i],
+ Mode: 0o600,
+ Size: int64(len(contents[i])),
+ }
+ tw.WriteHeader(hdr)
+ tw.Write(contents[i])
+ tw.Close()
+ }
+
+ zw.Close()
+
+ return &buf
+ }
+
+ t.Run("MissingPKGINFOFile", func(t *testing.T) {
+ data := createPackage("dummy.txt", []byte{})
+
+ pp, err := ParsePackage(data)
+ assert.Nil(t, pp)
+ assert.ErrorIs(t, err, ErrMissingPKGINFOFile)
+ })
+
+ t.Run("InvalidPKGINFOFile", func(t *testing.T) {
+ data := createPackage(".PKGINFO", []byte{})
+
+ pp, err := ParsePackage(data)
+ assert.Nil(t, pp)
+ assert.ErrorIs(t, err, ErrInvalidName)
+ })
+
+ t.Run("Valid", func(t *testing.T) {
+ data := createPackage(".PKGINFO", createPKGINFOContent(packageName, packageVersion))
+
+ p, err := ParsePackage(data)
+ assert.NoError(t, err)
+ assert.NotNil(t, p)
+
+ assert.Equal(t, "Q1SRYURM5+uQDqfHSwTnNIOIuuDVQ=", p.FileMetadata.Checksum)
+ })
+}
+
+func TestParsePackageInfo(t *testing.T) {
+ t.Run("InvalidName", func(t *testing.T) {
+ data := createPKGINFOContent("", packageVersion)
+
+ p, err := ParsePackageInfo(bytes.NewReader(data))
+ assert.Nil(t, p)
+ assert.ErrorIs(t, err, ErrInvalidName)
+ })
+
+ t.Run("InvalidVersion", func(t *testing.T) {
+ data := createPKGINFOContent(packageName, "")
+
+ p, err := ParsePackageInfo(bytes.NewReader(data))
+ assert.Nil(t, p)
+ assert.ErrorIs(t, err, ErrInvalidVersion)
+ })
+
+ t.Run("Valid", func(t *testing.T) {
+ data := createPKGINFOContent(packageName, packageVersion)
+
+ p, err := ParsePackageInfo(bytes.NewReader(data))
+ assert.NoError(t, err)
+ assert.NotNil(t, p)
+
+ assert.Equal(t, packageName, p.Name)
+ assert.Equal(t, packageVersion, p.Version)
+ assert.Equal(t, packageDescription, p.VersionMetadata.Description)
+ assert.Equal(t, packageMaintainer, p.VersionMetadata.Maintainer)
+ assert.Equal(t, packageProjectURL, p.VersionMetadata.ProjectURL)
+ assert.Equal(t, "MIT", p.VersionMetadata.License)
+ assert.Empty(t, p.FileMetadata.Checksum)
+ assert.Equal(t, "Gitea <pack@ag.er>", p.FileMetadata.Packager)
+ assert.EqualValues(t, 1678834800, p.FileMetadata.BuildDate)
+ assert.EqualValues(t, 123456, p.FileMetadata.Size)
+ assert.Equal(t, "aarch64", p.FileMetadata.Architecture)
+ assert.Equal(t, "origin", p.FileMetadata.Origin)
+ assert.Equal(t, "1111e709613fbc979651b09ac2bc27c6591a9999", p.FileMetadata.CommitHash)
+ assert.Equal(t, "value", p.FileMetadata.InstallIf)
+ assert.ElementsMatch(t, []string{"common", "gitea"}, p.FileMetadata.Provides)
+ assert.ElementsMatch(t, []string{"common", "gitea"}, p.FileMetadata.Dependencies)
+ })
+}
diff --git a/modules/setting/packages.go b/modules/setting/packages.go
index 00d8b6122f..3719e2f644 100644
--- a/modules/setting/packages.go
+++ b/modules/setting/packages.go
@@ -24,6 +24,7 @@ var (
LimitTotalOwnerCount int64
LimitTotalOwnerSize int64
+ LimitSizeAlpine int64
LimitSizeCargo int64
LimitSizeChef int64
LimitSizeComposer int64
@@ -69,6 +70,7 @@ func loadPackagesFrom(rootCfg ConfigProvider) {
}
Packages.LimitTotalOwnerSize = mustBytes(sec, "LIMIT_TOTAL_OWNER_SIZE")
+ Packages.LimitSizeAlpine = mustBytes(sec, "LIMIT_SIZE_ALPINE")
Packages.LimitSizeCargo = mustBytes(sec, "LIMIT_SIZE_CARGO")
Packages.LimitSizeChef = mustBytes(sec, "LIMIT_SIZE_CHEF")
Packages.LimitSizeComposer = mustBytes(sec, "LIMIT_SIZE_COMPOSER")
diff --git a/modules/util/keypair.go b/modules/util/keypair.go
index 5a3ce715a4..97f2d9ebca 100644
--- a/modules/util/keypair.go
+++ b/modules/util/keypair.go
@@ -4,10 +4,13 @@
package util
import (
+ "crypto"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
+
+ "github.com/minio/sha256-simd"
)
// GenerateKeyPair generates a public and private keypair
@@ -43,3 +46,16 @@ func pemBlockForPub(pub *rsa.PublicKey) (string, error) {
})
return string(pubBytes), nil
}
+
+// CreatePublicKeyFingerprint creates a fingerprint of the given key.
+// The fingerprint is the sha256 sum of the PKIX structure of the key.
+func CreatePublicKeyFingerprint(key crypto.PublicKey) ([]byte, error) {
+ bytes, err := x509.MarshalPKIXPublicKey(key)
+ if err != nil {
+ return nil, err
+ }
+
+ checksum := sha256.Sum256(bytes)
+
+ return checksum[:], nil
+}