Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Windows purl type #255

Open
rhdesmond opened this issue Oct 24, 2023 · 12 comments
Open

Support for Windows purl type #255

rhdesmond opened this issue Oct 24, 2023 · 12 comments
Labels
Proposed new type PURL type definition Non-core definitions that describe and standardize PURL types type: winget Proposed new type

Comments

@rhdesmond
Copy link

I noticed that most common operating systems and language types are supported, but not Windows. Would it be possible to add Windows as a supported Purl type, perhaps with the signifier win

@stevespringett
Copy link
Member

I think "windows" is too generic, however, support for the Microsoft Store would be a welcome addition. There may be other types as well. Sorry, not a Windows person.

Did you want to propose the purl type definition for the Microsoft Store?

@stevespringett stevespringett added the PURL type definition Non-core definitions that describe and standardize PURL types label Oct 24, 2023
@alowayed
Copy link

alowayed commented Oct 25, 2023

Not the store, but for Windows exe files/packages.

For example, how would one represent the MySQL community server package for Windows found at: https://dev.mysql.com/downloads/mysql/ . msi might be a better type than windows since the package is installed / managed by the windows installer:

Screenshot 2023-10-25 at 3 07 34 PM

Contrast this to the RHEL version of MySQL that is managed through RPM and does have a specific PURL (e.g. pkg:rpm/ol/[email protected]):
Screenshot 2023-10-25 at 3 10 08 PM

Also not a Windows person so correct me if I'm wrong.

@prabhu
Copy link

prabhu commented Oct 30, 2023

Perhaps use swid with purl instead of reinventing another id? Microsoft themselves use a numeric id in the msrc feeds.

{
            "Items": [
              {
                "ProductID": "11568",
                "Value": "Windows 10 Version 1809 for 32-bit Systems"
              },
              {
                "ProductID": "11569",
                "Value": "Windows 10 Version 1809 for x64-based Systems"
              },
              {
                "ProductID": "11570",
                "Value": "Windows 10 Version 1809 for ARM64-based Systems"
              },
              {
                "ProductID": "11571",
                "Value": "Windows Server 2019"
              },
              {
                "ProductID": "11572",
                "Value": "Windows Server 2019 (Server Core installation)"
              },
              {
                "ProductID": "11923",
                "Value": "Windows Server 2022"
              },
              {
                "ProductID": "11924",
                "Value": "Windows Server 2022 (Server Core installation)"
              },
              {
                "ProductID": "11926",
                "Value": "Windows 11 version 21H2 for x64-based Systems"
              },
              {
                "ProductID": "11927",
                "Value": "Windows 11 version 21H2 for ARM64-based Systems"
              },
              {
                "ProductID": "11929",
                "Value": "Windows 10 Version 21H2 for 32-bit Systems"
              },
              {
                "ProductID": "11930",
                "Value": "Windows 10 Version 21H2 for ARM64-based Systems"
              },
              {
                "ProductID": "11931",
                "Value": "Windows 10 Version 21H2 for x64-based Systems"
              },
              {
                "ProductID": "12085",
                "Value": "Windows 11 Version 22H2 for ARM64-based Systems"
              },
              {
                "ProductID": "12086",
                "Value": "Windows 11 Version 22H2 for x64-based Systems"
              },
              {
                "ProductID": "12097",
                "Value": "Windows 10 Version 22H2 for x64-based Systems"
              },
              {
                "ProductID": "12098",
                "Value": "Windows 10 Version 22H2 for ARM64-based Systems"
              },
              {
                "ProductID": "12099",
                "Value": "Windows 10 Version 22H2 for 32-bit Systems"
              },

@bureado
Copy link

bureado commented Oct 31, 2023

For example, how would one represent the MySQL community server package for Windows found at: https://dev.mysql.com/downloads/mysql/ . msi might be a better type than windows since the package is installed / managed by the windows installer:

I think generic would be appropriate in this case.

For OS, see: #161

For WinGet, Chocolatey, Store, etc., I'm not an expert either but I do think those could be distinct types.

@prabhu
Copy link

prabhu commented Oct 31, 2023

https://dev.mysql.com/get/Downloads/MySQL-8.2/mysql-8.2.0-winx64.msi

pkg:generic/[email protected]?download_url=https%3A%2F%2Fdev.mysql.com%2Fget%2FDownloads%2FMySQL-8.2%2Fmysql-8.2.0-winx64.msi

chocolatey has its own type. Winget appears to be merely distributing/invoking msi or msix.

@bureado
Copy link

bureado commented Nov 2, 2023

@prabhu re. Winget, interesting. I don't think purl is too opinionated on what qualifies as a type, but for the sake of argument: I don't think a Winget manifest can always be reduced 1:1 to a generic type, because:

  • Installers is an array although I'm not clear if you can have two Installers with the same Scope and Architecture
  • Manifests allow InstallerSwitches and other behaviors of potential security interest (such as Protocols and FileExtensions) which aren't necessarily inherited from the defaults of their generic counterpart; it's less consequential than mutating hooks and triggers like many other package types have, but worth considering

I honestly don't know if this is enough to justify its own type (it's up to that community to weigh in!) but I can see why a given Winget manifest can't be fully represented with the generic for one of their backing installers.

@prabhu
Copy link

prabhu commented Nov 2, 2023

The mysql manifest is here. To uninstall or update a package installed by winget, the real msi based installer or uninstaller is used. Users can also uninstall from Add/Remove programs. So Winget is only a package manager that is invoking other commands for installation/uninstallation/updates.

To me, this resembles Ubuntu PPA, which supports easy distribution of .deb packages. We use deb as the type for even PPA-distributed packages. To remove these packages, we use apt remove command.

vsix (Visual studio extension) is a package even though it only wraps a bunch of npm packages. Why? Because the vsix is distributed, installed, updated, and removed independently without involving the os or npm package manager.

@bureado
Copy link

bureado commented Nov 3, 2023

In keeping with the intent of @rhdesmond, let me recap. It sounds like this issue is not about OS identifiers (@prabhu suggested to use the swid type) but more about type coverage for Windows systems:

  1. Chocolatey is one of those, and it needs to be defined.
  2. Generic installers for Windows could likely use generic.
  3. Store is an open question.
  4. And then we had the discussion on Winget.

@prabhu, I think I now agree Winget is not a type. But maybe for other reasons than what you specifically argued. I think it's great that we can leave some perspectives here for whatever proposals come after.

First, I agree that a purl type is not defined by the package manager (tool) This has less to do with the merits of the tool (you can say that apt-get is also "only" a package manager that is invoking other commands, like dpkg-deb and install) and more with the fact that the nature of the package is not mutated by the client, even if the state of the package installation is: in the case of Winget, with switches and locales; in the case of apt-get, with debconf, maintainer scripts and other local execution hooks and triggers. To give a pedantic example, the EOL status and license of a given .deb does not change whether you install it with apt-get or with aptitude or with dpkg-deb.

I also think that pkg:deb/ubuntu/[email protected]?arch=amd64 obtained from archive.ubuntu.com is different than obtained from a random PPA. But the deb type accommodates for that with repository_url.

_Note: if I had a Windows bulk software inventory, I wouldn't just like to see the applications installed, but the installation method - and if it was Winget, to know which manifest it came from. It looks like winget can already do this with winget list in the Source attribute. At any rate, this isn't a problem that purl can solve by enforcing an identifier on Winget. Instead, maybe purl can help represent an array of pkg:generic that make up a Winget manifest, or a Chef windows_package recipe.

The fact that Winget calls something else to install the software is not a definitive argument (against it being a type) for me either. Why entertain Helm charts or Stacksmith then? They would all be purl identifiers that point to other purl identifiers, and not because of composability but because of how distributions work. This semantic transitivity applies to many other types (and in my opinion should absolutely be considered as a factor by implementers, see ossf/wg-securing-critical-projects#74) such as apk or deb. But I think what differentiates those from Winget is that in Winget, the manifest author does not have room to vendor the installer.

In addition to the ability to vendoring an upstream, purl-representable package, there are likely more considerations for creating standalone types including the existence of a publishing marketplace, for example. Thanks for the additional perspectives that are helpful for me to expand and complement how I'm thinking about this. Fascinating discussion, thanks for entertaining it.

@prabhu
Copy link

prabhu commented Nov 6, 2023

Like sex, we all understand purl differently but eventually get there :)

I am happy with using "Is it possible to vendor the installer" as a criterion.

@matt-phylum
Copy link
Contributor

I think a WinGet type would make sense. If you have MSI packages installed on a machine, you have distinguishing information in the form of the UpgradeCode and ProductCode, but a PURL like pkg:win/{18B94B70-06F1-4AC0-B308-37280DB868C2}@{114D28E8-A096-4841-8873-5ADD831B63E4}¹ is completely opaque. It can be gathered from an installed image, but it doesn't tell you what the product is, what version it is, or where to get it. Conversely, if you have a PURL like pkg:generic/[email protected]?download_url=https%3A%2F%2Fdev.mysql.com%2Fget%2FDownloads%2FMySQL-8.2%2Fmysql-8.2.0-winx64.msi, you know what the product is, what version it is, and where to get it, but two of those fields are just metadata that a user has entered by hand, possibly unrelated to the important download_url qualifier, you can't generate such a PURL through automated inspection of an installed image, and you can't tell whether this package is present on a system without downloading it and comparing it to what is installed.

WinGet basically wraps appx/exe/msi/msix/zip installers to make apk/deb/rpm-like package sources with detached payloads. However, the payloads are still part of the package because they must have the expected hash. It's not just a pointer "if you want to install MySQL, download it from Oracle." The WinGet package is connected to the MSI file by the hash of the MSI and by the UpgradeCode+ProductCode such that there is exactly one MSI file and installed product that corresponds to that WinGet package.

In Chocolatey, packages typically have a detached payload and work generally the same way as WinGet packages, downloading, verifying, and installing the payload from an external source. However, that does not mean that the packages cannot be vendored. With Chocolatey, if you wanted to make a mirror that didn't depend on third party servers, that's called "Package Internalization," and involves modifying the Chocolatey package file and creating a parallel source. For WinGet, the payload is always detached, but the same thing should be possible by saving the payload to your own location and updating the WinGet package manifest to point to it.

The value of having a pkg:winget is that WinGet works in both directions. If you install a package, through WinGet or Chocolatey or otherwise, and then you look up in the Windows registry what is installed, you don't get enough information that you can easily install it again. However, by combining the WinGet package source, there is enough information to map an installed package, even one not installed by WinGet, to a WinGet package with a (possibly²) meaningful name that can be used for locating the package, eg pkg:winget/[email protected]?repository_url=https://winget.azureedge.net/cache.

For an example use case, there is a Microsoft.WinGet.DSC module which is used to create configurations that install packages. This example configuration currently installs pkg:winget/[email protected], pkg:npm/yarn, pkg:winget/Python.Python.3.10 (latest version of the 3.10 series), pkg:winget/Microsoft.VisualStudio.2022.BuildTools, and a Visual Studio workload. If you use pkg:generic, you can't tell whether the current system satisfies the requirements.

¹ This cannot be pkg:msi, nor can the GUIDs be converted into another format. The Windows Add Remove Programs system supports installers that are not MSI-based, and those installers do not always use GUIDs. Packages have user-facing names and versions, but at least the name part maybe localized, may change from one version to the next, and may not be globally unique. There's also another issue where sometimes multiple versions of the same product use different upgrade codes, so even if using a user-friendly version number, a theoretical pkg:win/{18B94B70-06F1-4AC0-B308-37280DB868C2}@8.2 can be confusing because pkg:win/{98882450-D440-45A9-A23E-295EDEB3425B}@9.0 could be the next version.

² WinGet has an msstore package source, and the package IDs for the msstore source are just random characters. So, unfortunately, if the app was downloaded from the app store, WinGet may give a name that doesn't mean anything to humans but can still be used to uniquely identify the software and resolve it to an installer. Some packages are available in both the msstore source and the winget-pkgs source using the same installers, so those MSI packages may be resolvable to two different WinGet packages. For example, KeepassXC is both XP8K2L36VP0QMB in the msstore source and KeePassXCTeam.KeePassXC in the winget-pkgs source. It's exactly the same installer file in both sources, so once it's installed there is no difference and either name is correct when referring to it as a WinGet package.

@bureado bureado mentioned this issue Oct 7, 2024
@chrisrodrigue
Copy link

I fully agree with specifying a type for the Windows Package Manager (winget).

It is the official CLI package management tool for Windows and I believe that it ships with Windows 10/11 now. The manifests are maintained in the winget-pkgs repo and it supports MSIX/MSI/APPX/EXE package types.

I think that winget's PackageIdentifier YAML key maps most appropriately to the PURL name field. It is case-insensitive.

Example

pkg:winget/<namespace>/<name>@<version>?<qualifiers>#<subpath>

pkg:winget/[email protected]

It might be reasonable for one to expect other PURL components to work:

pkg:winget/microsoft/[email protected]?os=windows&arch=x86

(os might be redundant here but I think it would still be smart to have it)

@matt-phylum
Copy link
Contributor

If the package manager is winget and the OS is always going to be windows, it seems unnecessary to introduce an os qualifier. If later on winget gets support for other operating systems, os could be defined at that time with a default of windows so the meanings of existing purls would remain unchanged.

The publisher name being split out into the namespace field in the second example is optional. It's mandatory for packages in the winget-pkgs repo to have a publisher, but the winget-pkgs repo is not the only source of packages that can be installed by the winget cli. The schema used by the winget CLI only requires that packages have an ID. Winget comes with a second preinstalled package source where none of the package IDs contain ..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Proposed new type PURL type definition Non-core definitions that describe and standardize PURL types type: winget Proposed new type
Projects
None yet
Development

No branches or pull requests

8 participants