Add signature support for the RPM module (#27069)

close  #27031

If the rpm package does not contain a matching gpg signature, the
installation will fail. See (#27031) , now auto-signing rpm uploads.

This option is turned off by default for compatibility.
pull/31778/head^2
Exploding Dragon 4 months ago committed by GitHub
parent 94cca8846e
commit de175e3b06
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      custom/conf/app.example.ini
  2. 3
      modules/setting/packages.go
  3. 16
      routers/api/packages/rpm/rpm.go
  4. 38
      services/packages/rpm/repository.go
  5. 27
      tests/integration/api_packages_rpm_test.go

@ -2555,7 +2555,8 @@ LEVEL = Info
;LIMIT_SIZE_SWIFT = -1 ;LIMIT_SIZE_SWIFT = -1
;; Maximum size of a Vagrant upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) ;; Maximum size of a Vagrant upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
;LIMIT_SIZE_VAGRANT = -1 ;LIMIT_SIZE_VAGRANT = -1
;; Enable RPM re-signing by default. (It will overwrite the old signature ,using v4 format, not compatible with CentOS 6 or older)
;DEFAULT_RPM_SIGN_ENABLED = false
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; default storage for attachments, lfs and avatars ;; default storage for attachments, lfs and avatars

@ -42,6 +42,8 @@ var (
LimitSizeRubyGems int64 LimitSizeRubyGems int64
LimitSizeSwift int64 LimitSizeSwift int64
LimitSizeVagrant int64 LimitSizeVagrant int64
DefaultRPMSignEnabled bool
}{ }{
Enabled: true, Enabled: true,
LimitTotalOwnerCount: -1, LimitTotalOwnerCount: -1,
@ -97,6 +99,7 @@ func loadPackagesFrom(rootCfg ConfigProvider) (err error) {
Packages.LimitSizeRubyGems = mustBytes(sec, "LIMIT_SIZE_RUBYGEMS") Packages.LimitSizeRubyGems = mustBytes(sec, "LIMIT_SIZE_RUBYGEMS")
Packages.LimitSizeSwift = mustBytes(sec, "LIMIT_SIZE_SWIFT") Packages.LimitSizeSwift = mustBytes(sec, "LIMIT_SIZE_SWIFT")
Packages.LimitSizeVagrant = mustBytes(sec, "LIMIT_SIZE_VAGRANT") Packages.LimitSizeVagrant = mustBytes(sec, "LIMIT_SIZE_VAGRANT")
Packages.DefaultRPMSignEnabled = sec.Key("DEFAULT_RPM_SIGN_ENABLED").MustBool(false)
return nil return nil
} }

@ -133,6 +133,21 @@ func UploadPackageFile(ctx *context.Context) {
} }
defer buf.Close() defer buf.Close()
// if rpm sign enabled
if setting.Packages.DefaultRPMSignEnabled || ctx.FormBool("sign") {
pri, _, err := rpm_service.GetOrCreateKeyPair(ctx, ctx.Package.Owner.ID)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
buf, err = rpm_service.SignPackage(buf, pri)
if err != nil {
// Not in rpm format, parsing failed.
apiError(ctx, http.StatusBadRequest, err)
return
}
}
pck, err := rpm_module.ParsePackage(buf) pck, err := rpm_module.ParsePackage(buf)
if err != nil { if err != nil {
if errors.Is(err, util.ErrInvalidArgument) { if errors.Is(err, util.ErrInvalidArgument) {
@ -142,7 +157,6 @@ func UploadPackageFile(ctx *context.Context) {
} }
return return
} }
if _, err := buf.Seek(0, io.SeekStart); err != nil { if _, err := buf.Seek(0, io.SeekStart); err != nil {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)
return return

@ -21,14 +21,16 @@ import (
rpm_model "code.gitea.io/gitea/models/packages/rpm" rpm_model "code.gitea.io/gitea/models/packages/rpm"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages" packages_module "code.gitea.io/gitea/modules/packages"
rpm_module "code.gitea.io/gitea/modules/packages/rpm" rpm_module "code.gitea.io/gitea/modules/packages/rpm"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
packages_service "code.gitea.io/gitea/services/packages" packages_service "code.gitea.io/gitea/services/packages"
"github.com/keybase/go-crypto/openpgp" "github.com/ProtonMail/go-crypto/openpgp"
"github.com/keybase/go-crypto/openpgp/armor" "github.com/ProtonMail/go-crypto/openpgp/armor"
"github.com/keybase/go-crypto/openpgp/packet" "github.com/ProtonMail/go-crypto/openpgp/packet"
"github.com/sassoftware/go-rpmutils"
) )
// GetOrCreateRepositoryVersion gets or creates the internal repository package // GetOrCreateRepositoryVersion gets or creates the internal repository package
@ -641,3 +643,33 @@ func addDataAsFileToRepo(ctx context.Context, pv *packages_model.PackageVersion,
OpenSize: wc.Written(), OpenSize: wc.Written(),
}, nil }, nil
} }
func SignPackage(rpm *packages_module.HashedBuffer, privateKey string) (*packages_module.HashedBuffer, error) {
keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewReader([]byte(privateKey)))
if err != nil {
// failed to parse key
return nil, err
}
entity := keyring[0]
h, err := rpmutils.SignRpmStream(rpm, entity.PrivateKey, nil)
if err != nil {
// error signing rpm
return nil, err
}
signBlob, err := h.DumpSignatureHeader(false)
if err != nil {
// error writing sig header
return nil, err
}
if len(signBlob)%8 != 0 {
log.Info("incorrect padding: got %d bytes, expected a multiple of 8", len(signBlob))
return nil, err
}
// move fp to sign end
if _, err := rpm.Seek(int64(h.OriginalSignatureHeaderSize()), io.SeekStart); err != nil {
return nil, err
}
// create signed rpm buf
return packages_module.CreateHashedBufferFromReader(io.MultiReader(bytes.NewReader(signBlob), rpm))
}

@ -24,7 +24,10 @@ import (
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/tests" "code.gitea.io/gitea/tests"
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/sassoftware/go-rpmutils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestPackageRpm(t *testing.T) { func TestPackageRpm(t *testing.T) {
@ -431,6 +434,30 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`,
AddBasicAuth(user.Name) AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusNotFound) MakeRequest(t, req, http.StatusNotFound)
}) })
t.Run("UploadSign", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
url := groupURL + "/upload?sign=true"
req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)).
AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusCreated)
gpgReq := NewRequest(t, "GET", rootURL+"/repository.key")
gpgResp := MakeRequest(t, gpgReq, http.StatusOK)
pub, err := openpgp.ReadArmoredKeyRing(gpgResp.Body)
require.NoError(t, err)
req = NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s", groupURL, packageName, packageVersion, packageArchitecture))
resp := MakeRequest(t, req, http.StatusOK)
_, sigs, err := rpmutils.Verify(resp.Body, pub)
require.NoError(t, err)
require.NotEmpty(t, sigs)
req = NewRequest(t, "DELETE", fmt.Sprintf("%s/package/%s/%s/%s", groupURL, packageName, packageVersion, packageArchitecture)).
AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusNoContent)
})
}) })
} }
} }

Loading…
Cancel
Save