Watch
1
0
Fork
You've already forked pkg-proxy
1
mirror of https://github.com/git-pkgs/proxy.git synced 2026-11-09 07:39:19 -05:00
pkg-proxy/internal/denylist/denylist.go

157 lines
4.7 KiB
Go
Raw Permalink Normal View History

// Package denylist implements an immutable, exact-version package policy.
package denylist
import (
"fmt"
"strings"
"github.com/git-pkgs/proxy/internal/packageurl"
"github.com/git-pkgs/purl"
)
// Policy is safe for concurrent reads. A nil policy allows every version.
type Policy struct {
packages map[string]map[string]bool
}
// New validates and canonicalizes versioned PURLs. Qualifiers and subpaths are
// rejected because the proxy cannot reliably distinguish them at every endpoint,
// except a conda channel, which download URLs always name.
func New(packages []string) (*Policy, error) {
if len(packages) == 0 {
return nil, nil
}
p := &Policy{packages: make(map[string]map[string]bool)}
for _, value := range packages {
pkg, err := purl.Parse(value)
if err != nil {
return nil, fmt.Errorf("invalid denylist package %q: %w", value, err)
}
if pkg.Version == "" || !allowedQualifiers(pkg) || pkg.Subpath != "" ||
strings.ContainsAny(pkg.Namespace+pkg.Name+pkg.Version, "*?[]<>=| \t\r\n") {
return nil, fmt.Errorf("invalid denylist package %q: expected an exact versioned PURL without qualifiers or subpath", value)
}
id := identify(pkg)
key := id.key
if id.channel != "" {
key = id.channelKey()
}
if p.packages[key] == nil {
p.packages[key] = make(map[string]bool)
}
p.packages[key][id.version] = true
// Debian and RPM filenames leave the epoch out, so an entry with
// one also matches the version without it.
if pkg.Type == "deb" || pkg.Type == "rpm" {
if _, version, ok := strings.Cut(id.version, ":"); ok && version != "" {
p.packages[key][version] = true
}
}
}
return p, nil
}
func allowedQualifiers(pkg *purl.PURL) bool {
for _, q := range pkg.Qualifiers {
if pkg.Type != "conda" || q.Key != "channel" {
return false
}
}
return true
}
// Denied reports whether a versioned PURL is denied.
func (p *Policy) Denied(value string) bool {
if p == nil || len(p.packages) == 0 {
return false
}
pkg, err := purl.Parse(value)
if err != nil {
return false
}
id := identify(pkg)
versions := []string{id.version}
// A platform gem's version carries the platform ("1.16.0-x86_64-linux"),
// so also match the plain version.
if pkg.Type == "gem" {
if base, _, found := strings.Cut(id.version, "-"); found && base != "" {
versions = append(versions, base)
}
}
for _, version := range versions {
if p.packages[id.key][version] || (id.channel != "" && p.packages[id.channelKey()][version]) {
return true
}
}
return false
}
// Versions returns a caller-owned set of denied versions for a package PURL.
func (p *Policy) Versions(packagePURL string) map[string]bool {
if p == nil || len(p.packages) == 0 {
return nil
}
pkg, err := purl.Parse(packagePURL)
if err != nil {
return nil
}
id := identify(pkg)
result := make(map[string]bool)
for version := range p.packages[id.key] {
result[version] = true
}
if id.channel != "" {
for version := range p.packages[id.channelKey()] {
result[version] = true
}
}
if len(result) == 0 {
return nil
}
return result
}
// identity is the form a PURL is compared in: the same for denylist entries
// and for the PURLs handlers build, however each spells the package.
type identity struct {
key string // versionless PURL without channel
channel string // conda channel, if any
version string
}
func (id identity) channelKey() string {
return id.key + "?channel=" + id.channel
}
// genericSwiftRegistry returns the scope and name of a Swift registry archive,
// which the proxy stores as pkg:generic/swift-registry/{scope}.{name}.
func genericSwiftRegistry(pkg *purl.PURL) (scope, name string, ok bool) {
if pkg.Type != "generic" || !strings.EqualFold(pkg.Namespace, "swift-registry") {
return "", "", false
}
scope, name, found := strings.Cut(pkg.Name, ".")
return scope, name, found && scope != "" && name != ""
}
// identify compares PURLs in purl's comparison form. The generic
// swift-registry spelling is the proxy's own, so it is mapped here first.
// The namespace of a distro type names the distribution, which handlers
// include for some types and not others, so it is dropped as retention and
// the inventory do.
func identify(pkg *purl.PURL) identity {
if scope, name, ok := genericSwiftRegistry(pkg); ok {
pkg = purl.New("swift", scope, name, pkg.Version, nil)
}
c := pkg.ComparisonForm()
var channel string
if c.Type == "conda" {
// An entry without a channel matches every channel.
channel = c.Qualifier("channel")
}
namespace := c.Namespace
if packageurl.DistroType(c.Type) {
namespace = ""
}
key := purl.New(c.Type, namespace, c.Name, "", nil).String()
return identity{key: packageurl.PolicyPackageKey(key), channel: channel, version: c.Version}
}