Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package obs-service-go_modules for openSUSE:Factory checked in at 2025-05-22 16:55:58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/obs-service-go_modules (Old) and /work/SRC/openSUSE:Factory/.obs-service-go_modules.new.2732 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "obs-service-go_modules" Thu May 22 16:55:58 2025 rev:16 rq:1279150 version:0.6.8 Changes: -------- --- /work/SRC/openSUSE:Factory/obs-service-go_modules/obs-service-go_modules.changes 2025-04-20 20:04:33.828569934 +0200 +++ /work/SRC/openSUSE:Factory/.obs-service-go_modules.new.2732/obs-service-go_modules.changes 2025-05-22 16:56:09.460105955 +0200 @@ -1,0 +2,17 @@ +Thu May 22 04:46:05 UTC 2025 - Jeff Kowalczyk <jkowalc...@suse.com> + +- Update to version 0.6.8: + * Implement go mod edit -replace using _service params refs #57 + * cmd_go_mod() use list for arg cmd and new arg subcmd refs #57 + +------------------------------------------------------------------- +Wed May 21 04:07:39 UTC 2025 - Jeff Kowalczyk <jkowalc...@suse.com> + +- Update to version 0.6.7: + * archive_autodetect() match archive files before dir refs #66 #69 #73 + * Ensure some level of compatibility with older libarchive + * archive_autodetect docstring add .obscpio to format list + * Format with python black + * Fix using go.mod of toplevel + +------------------------------------------------------------------- Old: ---- obs-service-go_modules-0.6.6.tar.gz New: ---- obs-service-go_modules-0.6.8.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ obs-service-go_modules.spec ++++++ --- /var/tmp/diff_new_pack.uidhJG/_old 2025-05-22 16:56:09.864123082 +0200 +++ /var/tmp/diff_new_pack.uidhJG/_new 2025-05-22 16:56:09.864123082 +0200 @@ -37,7 +37,7 @@ %define use_test test %endif Name: obs-service-%{service} -Version: 0.6.6 +Version: 0.6.8 Release: 0 Summary: An OBS source service: Download, verify and vendor Go module dependencies License: GPL-2.0-or-later ++++++ _service ++++++ --- /var/tmp/diff_new_pack.uidhJG/_old 2025-05-22 16:56:09.892124269 +0200 +++ /var/tmp/diff_new_pack.uidhJG/_new 2025-05-22 16:56:09.896124438 +0200 @@ -3,7 +3,7 @@ <param name="url">https://github.com/openSUSE/obs-service-go_modules.git</param> <param name="scm">git</param> <param name="exclude">.git</param> - <param name="revision">v0.6.6</param> + <param name="revision">v0.6.8</param> <param name="versionformat">@PARENT_TAG@</param> <param name="changesgenerate">enable</param> <param name="versionrewrite-pattern">v(.*)</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.uidhJG/_old 2025-05-22 16:56:09.916125286 +0200 +++ /var/tmp/diff_new_pack.uidhJG/_new 2025-05-22 16:56:09.920125456 +0200 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/openSUSE/obs-service-go_modules.git</param> - <param name="changesrevision">a9bf055557cf024478744fbd7e8621fd03cb2e87</param></service></servicedata> + <param name="changesrevision">27262b8fa611ffa96f764933b9088660de6a6cae</param></service></servicedata> (No newline at EOF) ++++++ obs-service-go_modules-0.6.6.tar.gz -> obs-service-go_modules-0.6.8.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/obs-service-go_modules-0.6.6/README.md new/obs-service-go_modules-0.6.8/README.md --- old/obs-service-go_modules-0.6.6/README.md 2025-04-13 19:12:30.000000000 +0200 +++ new/obs-service-go_modules-0.6.8/README.md 2025-05-22 06:37:03.000000000 +0200 @@ -245,6 +245,116 @@ Persistent state for changelog generation is stored in `_servicedata`. +## Updating dependencies during vendoring + +When a CVE or bug is reported for a dependency of a Go application, the best +course of action is a pull request to the upstream project to use a newer fixed +version of that dependency, and an accompanying tagged release. govulncheck +output indicates the fixed version of the vulnerable dependency to use. + +On occasion, the upstream project may be slow to accept the update pull request +or tag a release. If the vulnerability is severe and applicable to the packaged +Go application, package maintainers can at their option temporarily vendor a +newer fixed version of the dependency. + +In Long Term Support (LTS) scenarios, an dependency of an upstream project may +be inactive, archived, removed, or explicitly marked EOL. In these cases it is +appropriate to vendor a newer fixed version of dependency from a different +repository URL or local file path. + +## Update a Go module with `go mod edit -replace` + +To replace a specific module with a newer fixed version, +`obs-service-go_modules` supports the command +`go mod edit -replace module=replacement`. +This method is recommended, as the replace statement is explicitly written in +`go.mod` and recorded in the `debug/buildinfo` metadata. Alternative methods of +updating such as `go get` do not highlight the changed versions or the +divergence from upstream pinned versions. Upstream projects do occasionally use +replace statements in their pristine `go.mod` files. + +### Example replace to fix CVEs in dependencies + +Scenario: A packaged Go application has gone a long time since a tagged +release. GitHub dependabot makes regular dependency update PRs to the upstream +`main` branch, but no releases have been tagged. Two CVEs are detected against +the most recent tagged release, as per `govulncheck` run in the local git clone: + +``` +govulncheck . +=== Symbol Results === + +Vulnerability #1: GO-2025-3485 + DoS in go-jose Parsing in github.com/go-jose/go-jose + More info: https://pkg.go.dev/vuln/GO-2025-3485 + Module: github.com/go-jose/go-jose/v3 + Found in: github.com/go-jose/go-jose/v3@v3.0.3 + Fixed in: github.com/go-jose/go-jose/v3@v3.0.4 + Example traces found: + #1: pkg/attestation/attestation.go:145:35: attestation.signAttestation calls sign.SignerFromKeyOpts, which eventually calls jose.ParseSigned + + Module: github.com/go-jose/go-jose/v4 + Found in: github.com/go-jose/go-jose/v4@v4.0.2 + Fixed in: github.com/go-jose/go-jose/v4@v4.0.5 + Example traces found: + #1: pkg/attestation/attestation.go:145:35: attestation.signAttestation calls sign.SignerFromKeyOpts, which eventually calls jose.ParseSignedCompact + +Your code is affected by 1 vulnerability from 1 module. +This scan also found 6 vulnerabilities in packages you import and 2 +vulnerabilities in modules you require, but your code doesn't appear to call +these vulnerabilities. +Use '-show verbose' for more details. +``` + +Mitigation: Add two replace entries to `_service` as indicated by the CVE report +`fixed in` field: + +``` +<service name="go_modules" mode="manual"> + <param name="replace">github.com/go-jose/go-jose/v3=github.com/go-jose/go-jose/v3@v3.0.4</param> + <param name="replace">github.com/go-jose/go-jose/v4=github.com/go-jose/go-jose/v4@v4.0.5</param> +</service> +``` + +Next, run the source service to vendor dependencies as usual. + +`vendor.tar.gz` now contains the original dependencies pinned by upstream +`go.mod`, with the addition of updates to two dependencies as shown above. To +ensure the vendoring remains consistent, `vendor.tar.gz` also contains modified +go.mod and go.sum lock files. These two files are the only diff against pristine +upstream sources that will be neeeded in most uses of replace. + +Running govulncheck on the updated vendored dependencies shows the two CVEs have +been cleared: + +``` +govulncheck . +=== Symbol Results === + +No vulnerabilities found. + +Your code is affected by 0 vulnerabilities. +This scan also found 5 vulnerabilities in packages you import and 2 +vulnerabilities in modules you require, but your code doesn't appear to call +these vulnerabilities. +Use '-show verbose' for more details. +``` + +The use of `replace` can be observed in the modified `go.mod`, or the built +binary: + +``` +go version -m <BINARYNAME> |grep jose + dep github.com/go-jose/go-jose/v3 v3.0.3 + => github.com/go-jose/go-jose/v3 v3.0.4 + dep github.com/go-jose/go-jose/v4 v4.0.2 + => github.com/go-jose/go-jose/v4 v4.0.5 +``` + +As soon as the Go application upstream tags a newer release, remove the replace +parameters from `_service` to return to pristine upstream sources and receive +further updates to the dependency. + ## Transition note Until such time as `obs-service-go_modules` is available on diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/obs-service-go_modules-0.6.6/go_modules new/obs-service-go_modules-0.6.8/go_modules --- old/obs-service-go_modules-0.6.6/go_modules 2025-04-13 19:12:30.000000000 +0200 +++ new/obs-service-go_modules-0.6.8/go_modules 2025-05-22 06:37:03.000000000 +0200 @@ -122,6 +122,7 @@ - .tar.lz - .tar.xz - .tar.zst + - .obscpio Returns str with filename of the archive or subdirectory """ log.info("Autodetecting archive since no archive param provided in _service") @@ -134,11 +135,11 @@ spec = specs[0] c_exts = ("gz", "xz", "zst", "lz", "bz2") for pattern in ( - [spec.stem] - + [f"{spec.stem}*.tar.{c_ext}" for c_ext in c_exts] + [f"{spec.stem}*.tar.{c_ext}" for c_ext in c_exts] + [f"{spec.stem}*.obscpio"] + [f"_service:*:{spec.stem}*tar.{c_ext}" for c_ext in c_exts] + [f"_service:*:{spec.stem}*obscpio"] + + [spec.stem] ): log.debug(f"Trying to find archive name with pattern {pattern}") matches = sorted(spec.parent.glob(pattern), reverse=True) @@ -193,28 +194,49 @@ os.chdir(cwd) -def cmd_go_mod(cmd, moddir): +def cmd_go_mod(subcmd, moddir): """Execute go mod subcommand using subprocess.run(). Capture both stderr and stdout as text. Log as info or error in this function body. Return CompletedProcess object to caller for control flow. """ - log.info(f"go mod {cmd}") + cmd = ["go", "mod"] + cmd.extend(subcmd) + log.info(" ".join(cmd)) # subprocess.run() returns CompletedProcess cp if sys.version_info >= (3, 7): - cp = run(["go", "mod", cmd], cwd=moddir, capture_output=True, text=True) + cp = run(cmd, cwd=moddir, capture_output=True, text=True) else: - cp = run(["go", "mod", cmd], cwd=moddir) + cp = run(cmd, cwd=moddir) if cp.returncode: log.error(cp.stderr.strip()) return cp +def replace_modules(replace, go_mod_dir): + """Replace one or more modules + Parameter replace is a list of strings: 'module=replacement' + Returns boolean indicating go.mod and go.sum are modified + """ + log.info(f"Replacing {len(replace)} modules") + for r in replace: + cp = cmd_go_mod(["edit", "-replace", r], go_mod_dir) + if cp.returncode: + log.error(f"go mod edit -replace={r} failed") + exit(1) + # run go mod tidy to update go.mod and go.sum + cp = cmd_go_mod(["tidy"], go_mod_dir) + if cp.returncode: + log.error("go mod tidy failed") + exit(1) + return True + + def sanitize_subdir(basedir, subdir): ret = os.path.normpath(subdir) if basedir == os.path.commonpath([basedir, ret]): return ret - log.error("Invalid path: {ret} not subdir of {basedir}") + log.error(f"Invalid path: {ret} not subdir of {basedir}") exit(1) @@ -231,11 +253,18 @@ parser.add_argument("--basename") parser.add_argument("--vendorname", default=DEFAULT_VENDOR_STEM) parser.add_argument("--subdir") + parser.add_argument( + "--replace", + action="append", + help="go mod edit replace argument: 'module=replacement'. Can be used multiple times.", + ) args = parser.parse_args() outdir = args.outdir subdir = args.subdir + replace = args.replace + archive_args = get_archive_parameters(args) vendor_tarname = f"{archive_args['vendorname']}.{archive_args['ext']}" if args.archive: @@ -262,6 +291,7 @@ or basename_from_archive(archive) or basename_from_archive_name(archive) ) + basename = basename.split("/")[-1] if subdir: go_mod_path = sanitize_subdir( tempdir, os.path.join(tempdir, basename, subdir, "go.mod") @@ -277,6 +307,12 @@ log.error(f"File go.mod not found under {os.path.join(tempdir, basename)}") exit(1) + modified = False # sentinel value indicating go.mod and go.sum modification + if args.replace: + # replace one or more modules + # go.mod and go.sum will be modified and should be included in vendor archive + modified = replace_modules(replace, go_mod_dir) + if args.strategy == "vendor": # go subcommand sequence: # - go mod download @@ -287,7 +323,7 @@ # (validates checksums) # return value cp is type subprocess.CompletedProcess - cp = cmd_go_mod("download", go_mod_dir) + cp = cmd_go_mod(["download"], go_mod_dir) if cp.returncode: if "invalid version" in cp.stderr: log.warning( @@ -300,12 +336,12 @@ log.error("go mod download failed") exit(1) - cp = cmd_go_mod("vendor", go_mod_dir) + cp = cmd_go_mod(["vendor"], go_mod_dir) if cp.returncode: log.error("go mod vendor failed") exit(1) - cp = cmd_go_mod("verify", go_mod_dir) + cp = cmd_go_mod(["verify"], go_mod_dir) if cp.returncode: log.error("go mod verify failed") exit(1) @@ -330,7 +366,24 @@ archive_args["compression"], options=",".join(options), ) as new_archive: - new_archive.add_files(vendor_dir, mtime=mtime, ctime=mtime, atime=mtime) + try: + new_archive.add_files( + vendor_dir, mtime=mtime, ctime=mtime, atime=mtime + ) + if modified: + new_archive.add_files( + "go.mod", mtime=mtime, ctime=mtime, atime=mtime + ) + new_archive.add_files( + "go.sum", mtime=mtime, ctime=mtime, atime=mtime + ) + except ( + TypeError + ): # If using old libarchive fallback to old non reproducible behavior + log.warning( + "python libarchive is too old, unable to produce reproducible output" + ) + new_archive.add_files(vendor_dir) os.chdir(cwd) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/obs-service-go_modules-0.6.6/go_modules.service new/obs-service-go_modules-0.6.8/go_modules.service --- old/obs-service-go_modules-0.6.6/go_modules.service 2025-04-13 19:12:30.000000000 +0200 +++ new/obs-service-go_modules-0.6.8/go_modules.service 2025-05-22 06:37:03.000000000 +0200 @@ -19,4 +19,7 @@ <parameter name="vendorname"> <description>Specify the name for the generated vendor tarball. Default: vendor</description> </parameter> + <parameter name="replace"> + <description>Specify a module name and its replacement to be updated at vendoring time. Syntax must be a valid expression input to go mod edit -replace, e.g. github.com/go-jose/go-jose/v4=github.com/go-jose/go-jose/v4@v4.0.5. Can be used multiple times. Default: None.</description> + </parameter> </service>