mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 21:28:11 +09:00 
			
		
		
		
	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.
This commit is contained in:
		| @@ -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) | ||||||
|  | 			}) | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user