forked from mirrors/pkg-proxy
Compare commits
1 commit
081c30287d
...
e59798cb1f
| Author | SHA1 | Date | |
|---|---|---|---|
| e59798cb1f |
8 changed files with 266 additions and 11 deletions
|
|
@ -49,6 +49,145 @@ log:
|
||||||
# Log format: "text" or "json"
|
# Log format: "text" or "json"
|
||||||
format: "text"
|
format: "text"
|
||||||
|
|
||||||
|
# Ecosystem support - routes and upstream repositories
|
||||||
|
#
|
||||||
|
# This section is optional, since 'include_default' in each section
|
||||||
|
# defaults to 'true' and the route map will be populated with all of
|
||||||
|
# the default routes if no configuration is provided.
|
||||||
|
ecosystem:
|
||||||
|
cargo:
|
||||||
|
include_default: true
|
||||||
|
# the default route for crates.io
|
||||||
|
# route:
|
||||||
|
# - path: /cargo
|
||||||
|
# upstream:
|
||||||
|
# - name: crates.io
|
||||||
|
# index: https://index.crates.io
|
||||||
|
# crates: https://static.crates.io/crates
|
||||||
|
composer:
|
||||||
|
include_default: true
|
||||||
|
# the default route for packagist.org
|
||||||
|
# route:
|
||||||
|
# - name: composer
|
||||||
|
# upstream:
|
||||||
|
# - name: packagist.org
|
||||||
|
# upstream: https://packagist.org
|
||||||
|
# repository: https://repo.packagist.org
|
||||||
|
conan:
|
||||||
|
include_default: true
|
||||||
|
# the default route for conan.io
|
||||||
|
# route:
|
||||||
|
# - name: conan
|
||||||
|
# upstream:
|
||||||
|
# - name: conan.io
|
||||||
|
# upstream: https://center.conan.io
|
||||||
|
conda:
|
||||||
|
include_default: true
|
||||||
|
# the default route for anaconda.org
|
||||||
|
# route:
|
||||||
|
# - name: conda
|
||||||
|
# upstream:
|
||||||
|
# - name: anaconda.org
|
||||||
|
# upstream: https://conda.anaconda.org
|
||||||
|
cran:
|
||||||
|
include_default: true
|
||||||
|
# the default route for r-project.org
|
||||||
|
# route:
|
||||||
|
# - name: cran
|
||||||
|
# upstream:
|
||||||
|
# - name: r-project.org
|
||||||
|
# upstream: https://cloud.r-project.org
|
||||||
|
debian:
|
||||||
|
include_default: true
|
||||||
|
# the default route for debian.org
|
||||||
|
# route:
|
||||||
|
# - name: debian
|
||||||
|
# upstream:
|
||||||
|
# - name: debian.org
|
||||||
|
# upstream: http://deb.debian.org/debian
|
||||||
|
gem:
|
||||||
|
include_default: true
|
||||||
|
# the default route for rubygems.org
|
||||||
|
# route:
|
||||||
|
# - name: gem
|
||||||
|
# upstream:
|
||||||
|
# - name: rubygems.org
|
||||||
|
# upstream: https://rubygems.org
|
||||||
|
go:
|
||||||
|
include_default: true
|
||||||
|
# the default route for golang.org
|
||||||
|
# route:
|
||||||
|
# - name: go
|
||||||
|
# upstream:
|
||||||
|
# - name: golang.org
|
||||||
|
# upstream: https://proxy.golang.org
|
||||||
|
hex:
|
||||||
|
include_default: true
|
||||||
|
# the default route for hex.pm
|
||||||
|
# route:
|
||||||
|
# - name: hex
|
||||||
|
# upstream:
|
||||||
|
# - name: hex.pm
|
||||||
|
# upstream: https://repo.hex.pm
|
||||||
|
maven:
|
||||||
|
include_default: true
|
||||||
|
# the default route for maven.org
|
||||||
|
# route:
|
||||||
|
# - name: maven
|
||||||
|
# upstream:
|
||||||
|
# - name: maven.org
|
||||||
|
# upstream: https://repo1.maven.org/maven2
|
||||||
|
npm:
|
||||||
|
include_default: true
|
||||||
|
# the default route for npmjs.org
|
||||||
|
# route:
|
||||||
|
# - name: npm
|
||||||
|
# upstream:
|
||||||
|
# - name: npmjs.org
|
||||||
|
# upstream: https://registry.npmjs.org
|
||||||
|
nuget:
|
||||||
|
include_default: true
|
||||||
|
# the default route for nuget.org
|
||||||
|
# route:
|
||||||
|
# - name: nuget
|
||||||
|
# upstream:
|
||||||
|
# - name: nuget.org
|
||||||
|
# upstream: https://api.nuget.org
|
||||||
|
oci:
|
||||||
|
include_default: true
|
||||||
|
# the default route for docker.io
|
||||||
|
# route:
|
||||||
|
# - name: v2
|
||||||
|
# upstream:
|
||||||
|
# - name: docker.io
|
||||||
|
# registry: https://registry-1.docker.io
|
||||||
|
# auth: https://auth.docker.io
|
||||||
|
pub:
|
||||||
|
include_default: true
|
||||||
|
# the default route for pub.dev
|
||||||
|
# route:
|
||||||
|
# - name: pub
|
||||||
|
# upstream:
|
||||||
|
# - name: pub.dev
|
||||||
|
# upstream: https://pub.dev
|
||||||
|
pypi:
|
||||||
|
include_default: true
|
||||||
|
# the default route for pypi.org
|
||||||
|
# route:
|
||||||
|
# - name: pypi
|
||||||
|
# upstream:
|
||||||
|
# - name: pypi.org
|
||||||
|
# index: https://pypi.org
|
||||||
|
# files_host: files.pythonhosted.org
|
||||||
|
rpm:
|
||||||
|
include_default: true
|
||||||
|
# the default route for fedoraproject.org
|
||||||
|
# route:
|
||||||
|
# - name: rpm
|
||||||
|
# upstream:
|
||||||
|
# - name: fedoraproject.org
|
||||||
|
# upstream: https://dl.fedoraproject.org/pub/fedora/linux
|
||||||
|
|
||||||
# Upstream registry URLs and authentication
|
# Upstream registry URLs and authentication
|
||||||
upstream:
|
upstream:
|
||||||
# npm registry URL
|
# npm registry URL
|
||||||
|
|
|
||||||
78
internal/config/cargo/cargo.go
Normal file
78
internal/config/cargo/cargo.go
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
package cargo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config configures routes
|
||||||
|
type Config struct {
|
||||||
|
IncludeDefault bool `json:"include_default" yaml:"include_default"`
|
||||||
|
Route []RouteConfig `json:"route" yaml:"route"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RouteConfig configures a route
|
||||||
|
type RouteConfig struct {
|
||||||
|
Path string `json:"path" yaml:"path"`
|
||||||
|
Upstream []UpstreamConfig `json:"upstream" yaml:"upstream"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpstreamConfig configures an upstream (source)
|
||||||
|
type UpstreamConfig struct {
|
||||||
|
Name string `json:"name" yaml:"name"`
|
||||||
|
Index string `json:"index" yaml:"index"`
|
||||||
|
Crates string `json:"crates" yaml:"crates"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RouteDefault is the default route
|
||||||
|
var RouteDefault = RouteConfig{
|
||||||
|
Path: "/cargo",
|
||||||
|
Upstream: []UpstreamConfig{
|
||||||
|
{
|
||||||
|
Name: "crates.io",
|
||||||
|
Index: "https://index.crates.io",
|
||||||
|
Crates: "https://static.crates.io/crates",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) Validate() error {
|
||||||
|
for _, route := range c.Route {
|
||||||
|
if err := route.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RouteConfig) Validate() error {
|
||||||
|
// TODO: validate Path
|
||||||
|
|
||||||
|
if len(r.Upstream) == 0 {
|
||||||
|
return fmt.Errorf("cargo route %q does not have any upstreams", r.Path)
|
||||||
|
}
|
||||||
|
if len(r.Upstream) > 1 {
|
||||||
|
return fmt.Errorf("cargo route %q has multiple upstreams; this is not yet supported", r.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, upstream := range r.Upstream {
|
||||||
|
if err := upstream.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UpstreamConfig) Validate() error {
|
||||||
|
if _, err := url.Parse(u.Index); err != nil {
|
||||||
|
return fmt.Errorf("cargo upstream index %q is not a valid URL", u.Index)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := url.Parse(u.Crates); err != nil {
|
||||||
|
return fmt.Errorf("cargo upstream crates %q is not a valid URL", u.Crates)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -57,6 +57,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/git-pkgs/proxy/internal/config/cargo"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -82,6 +83,9 @@ type Config struct {
|
||||||
// Upstream configures upstream registry URLs (optional overrides).
|
// Upstream configures upstream registry URLs (optional overrides).
|
||||||
Upstream UpstreamConfig `json:"upstream" yaml:"upstream"`
|
Upstream UpstreamConfig `json:"upstream" yaml:"upstream"`
|
||||||
|
|
||||||
|
// Ecosystem configures ecosystem routes and upstreams
|
||||||
|
Ecosystem EcosystemConfig `json:"ecosystem" yaml:"ecosystem"`
|
||||||
|
|
||||||
// Cooldown configures version age filtering to mitigate supply chain attacks.
|
// Cooldown configures version age filtering to mitigate supply chain attacks.
|
||||||
Cooldown CooldownConfig `json:"cooldown" yaml:"cooldown"`
|
Cooldown CooldownConfig `json:"cooldown" yaml:"cooldown"`
|
||||||
|
|
||||||
|
|
@ -239,6 +243,11 @@ func Default() *Config {
|
||||||
Level: "info",
|
Level: "info",
|
||||||
Format: "text",
|
Format: "text",
|
||||||
},
|
},
|
||||||
|
Ecosystem: EcosystemConfig{
|
||||||
|
Cargo: cargo.Config{
|
||||||
|
IncludeDefault: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
Upstream: UpstreamConfig{
|
Upstream: UpstreamConfig{
|
||||||
NPM: "https://registry.npmjs.org",
|
NPM: "https://registry.npmjs.org",
|
||||||
Cargo: "https://index.crates.io",
|
Cargo: "https://index.crates.io",
|
||||||
|
|
@ -334,6 +343,11 @@ func (c *Config) LoadFromEnv() {
|
||||||
|
|
||||||
// Validate checks the configuration for errors.
|
// Validate checks the configuration for errors.
|
||||||
func (c *Config) Validate() error {
|
func (c *Config) Validate() error {
|
||||||
|
// finalize the configuration by injecting default routes if requested
|
||||||
|
if c.Ecosystem.Cargo.IncludeDefault {
|
||||||
|
c.Ecosystem.Cargo.Route = append(c.Ecosystem.Cargo.Route, cargo.RouteDefault)
|
||||||
|
}
|
||||||
|
|
||||||
if c.Listen == "" {
|
if c.Listen == "" {
|
||||||
return fmt.Errorf("listen address is required")
|
return fmt.Errorf("listen address is required")
|
||||||
}
|
}
|
||||||
|
|
@ -386,6 +400,10 @@ func (c *Config) Validate() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := c.Ecosystem.Cargo.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
10
internal/config/ecosystem.go
Normal file
10
internal/config/ecosystem.go
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/git-pkgs/proxy/internal/config/cargo"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Ecosystem configuration (routes and upstreams)
|
||||||
|
type EcosystemConfig struct {
|
||||||
|
Cargo cargo.Config `json:"cargo" yaml:"cargo"`
|
||||||
|
}
|
||||||
|
|
@ -9,13 +9,11 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/git-pkgs/proxy/internal/config/cargo"
|
||||||
"github.com/git-pkgs/purl"
|
"github.com/git-pkgs/purl"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
cargoUpstream = "https://index.crates.io"
|
|
||||||
cargoDownloadBase = "https://static.crates.io/crates"
|
|
||||||
|
|
||||||
cargoIndexLen1 = 1
|
cargoIndexLen1 = 1
|
||||||
cargoIndexLen2 = 2
|
cargoIndexLen2 = 2
|
||||||
cargoIndexLen3 = 3
|
cargoIndexLen3 = 3
|
||||||
|
|
@ -24,21 +22,27 @@ const (
|
||||||
// CargoHandler handles cargo registry protocol requests.
|
// CargoHandler handles cargo registry protocol requests.
|
||||||
type CargoHandler struct {
|
type CargoHandler struct {
|
||||||
proxy *Proxy
|
proxy *Proxy
|
||||||
|
path string
|
||||||
indexURL string
|
indexURL string
|
||||||
downloadURL string
|
downloadURL string
|
||||||
proxyURL string
|
proxyURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCargoHandler creates a new cargo protocol handler.
|
// NewCargoHandler creates a new cargo protocol handler.
|
||||||
func NewCargoHandler(proxy *Proxy, proxyURL string) *CargoHandler {
|
func NewCargoHandler(proxy *Proxy, proxyURL string, cfg cargo.RouteConfig) *CargoHandler {
|
||||||
return &CargoHandler{
|
return &CargoHandler{
|
||||||
proxy: proxy,
|
proxy: proxy,
|
||||||
indexURL: cargoUpstream,
|
path: cfg.Path,
|
||||||
downloadURL: cargoDownloadBase,
|
indexURL: cfg.Upstream[0].Index,
|
||||||
|
downloadURL: cfg.Upstream[0].Crates,
|
||||||
proxyURL: strings.TrimSuffix(proxyURL, "/"),
|
proxyURL: strings.TrimSuffix(proxyURL, "/"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *CargoHandler) Path() string {
|
||||||
|
return h.path
|
||||||
|
}
|
||||||
|
|
||||||
// Routes returns the HTTP handler for cargo requests.
|
// Routes returns the HTTP handler for cargo requests.
|
||||||
// Mount this at /cargo on your router.
|
// Mount this at /cargo on your router.
|
||||||
func (h *CargoHandler) Routes() http.Handler {
|
func (h *CargoHandler) Routes() http.Handler {
|
||||||
|
|
@ -71,7 +75,7 @@ type CargoConfig struct {
|
||||||
// handleConfig returns the registry configuration.
|
// handleConfig returns the registry configuration.
|
||||||
func (h *CargoHandler) handleConfig(w http.ResponseWriter, r *http.Request) {
|
func (h *CargoHandler) handleConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
config := CargoConfig{
|
config := CargoConfig{
|
||||||
DL: h.proxyURL + "/cargo/crates/{crate}/{version}/download",
|
DL: h.proxyURL + h.path + "/crates/{crate}/{version}/download",
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ func TestCargoBuildIndexPath(t *testing.T) {
|
||||||
func TestCargoConfigEndpoint(t *testing.T) {
|
func TestCargoConfigEndpoint(t *testing.T) {
|
||||||
h := &CargoHandler{
|
h := &CargoHandler{
|
||||||
proxyURL: "http://localhost:8080",
|
proxyURL: "http://localhost:8080",
|
||||||
|
path: "/cargo",
|
||||||
}
|
}
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodGet, "/config.json", nil)
|
req := httptest.NewRequest(http.MethodGet, "/config.json", nil)
|
||||||
|
|
|
||||||
|
|
@ -170,7 +170,6 @@ func (s *Server) Start() error {
|
||||||
|
|
||||||
// Mount protocol handlers
|
// Mount protocol handlers
|
||||||
npmHandler := handler.NewNPMHandler(proxy, s.cfg.BaseURL)
|
npmHandler := handler.NewNPMHandler(proxy, s.cfg.BaseURL)
|
||||||
cargoHandler := handler.NewCargoHandler(proxy, s.cfg.BaseURL)
|
|
||||||
gemHandler := handler.NewGemHandler(proxy, s.cfg.BaseURL)
|
gemHandler := handler.NewGemHandler(proxy, s.cfg.BaseURL)
|
||||||
goHandler := handler.NewGoHandler(proxy, s.cfg.BaseURL)
|
goHandler := handler.NewGoHandler(proxy, s.cfg.BaseURL)
|
||||||
hexHandler := handler.NewHexHandler(proxy, s.cfg.BaseURL)
|
hexHandler := handler.NewHexHandler(proxy, s.cfg.BaseURL)
|
||||||
|
|
@ -186,8 +185,13 @@ func (s *Server) Start() error {
|
||||||
debianHandler := handler.NewDebianHandler(proxy, s.cfg.BaseURL)
|
debianHandler := handler.NewDebianHandler(proxy, s.cfg.BaseURL)
|
||||||
rpmHandler := handler.NewRPMHandler(proxy, s.cfg.BaseURL)
|
rpmHandler := handler.NewRPMHandler(proxy, s.cfg.BaseURL)
|
||||||
|
|
||||||
|
for _, route := range s.cfg.Ecosystem.Cargo.Route {
|
||||||
|
routeHandler := handler.NewCargoHandler(proxy, s.cfg.BaseURL, route)
|
||||||
|
r.Mount(routeHandler.Path(), http.StripPrefix(routeHandler.Path(), routeHandler.Routes()))
|
||||||
|
s.logger.Info("mounted handler", "ecosystem", "cargo", "path", routeHandler.Path())
|
||||||
|
}
|
||||||
|
|
||||||
r.Mount("/npm", http.StripPrefix("/npm", npmHandler.Routes()))
|
r.Mount("/npm", http.StripPrefix("/npm", npmHandler.Routes()))
|
||||||
r.Mount("/cargo", http.StripPrefix("/cargo", cargoHandler.Routes()))
|
|
||||||
r.Mount("/gem", http.StripPrefix("/gem", gemHandler.Routes()))
|
r.Mount("/gem", http.StripPrefix("/gem", gemHandler.Routes()))
|
||||||
r.Mount("/go", http.StripPrefix("/go", goHandler.Routes()))
|
r.Mount("/go", http.StripPrefix("/go", goHandler.Routes()))
|
||||||
r.Mount("/hex", http.StripPrefix("/hex", hexHandler.Routes()))
|
r.Mount("/hex", http.StripPrefix("/hex", hexHandler.Routes()))
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/git-pkgs/proxy/internal/config"
|
"github.com/git-pkgs/proxy/internal/config"
|
||||||
|
"github.com/git-pkgs/proxy/internal/config/cargo"
|
||||||
"github.com/git-pkgs/proxy/internal/database"
|
"github.com/git-pkgs/proxy/internal/database"
|
||||||
"github.com/git-pkgs/proxy/internal/handler"
|
"github.com/git-pkgs/proxy/internal/handler"
|
||||||
"github.com/git-pkgs/proxy/internal/storage"
|
"github.com/git-pkgs/proxy/internal/storage"
|
||||||
|
|
@ -68,13 +69,13 @@ func newTestServer(t *testing.T) *testServer {
|
||||||
|
|
||||||
// Mount handlers
|
// Mount handlers
|
||||||
npmHandler := handler.NewNPMHandler(proxy, cfg.BaseURL)
|
npmHandler := handler.NewNPMHandler(proxy, cfg.BaseURL)
|
||||||
cargoHandler := handler.NewCargoHandler(proxy, cfg.BaseURL)
|
cargoHandler := handler.NewCargoHandler(proxy, cfg.BaseURL, cargo.RouteDefault)
|
||||||
gemHandler := handler.NewGemHandler(proxy, cfg.BaseURL)
|
gemHandler := handler.NewGemHandler(proxy, cfg.BaseURL)
|
||||||
goHandler := handler.NewGoHandler(proxy, cfg.BaseURL)
|
goHandler := handler.NewGoHandler(proxy, cfg.BaseURL)
|
||||||
pypiHandler := handler.NewPyPIHandler(proxy, cfg.BaseURL)
|
pypiHandler := handler.NewPyPIHandler(proxy, cfg.BaseURL)
|
||||||
|
|
||||||
r.Mount("/npm", http.StripPrefix("/npm", npmHandler.Routes()))
|
r.Mount("/npm", http.StripPrefix("/npm", npmHandler.Routes()))
|
||||||
r.Mount("/cargo", http.StripPrefix("/cargo", cargoHandler.Routes()))
|
r.Mount(cargoHandler.Path(), http.StripPrefix(cargoHandler.Path(), cargoHandler.Routes()))
|
||||||
r.Mount("/gem", http.StripPrefix("/gem", gemHandler.Routes()))
|
r.Mount("/gem", http.StripPrefix("/gem", gemHandler.Routes()))
|
||||||
r.Mount("/go", http.StripPrefix("/go", goHandler.Routes()))
|
r.Mount("/go", http.StripPrefix("/go", goHandler.Routes()))
|
||||||
r.Mount("/pypi", http.StripPrefix("/pypi", pypiHandler.Routes()))
|
r.Mount("/pypi", http.StripPrefix("/pypi", pypiHandler.Routes()))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue