-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathclient.go
More file actions
163 lines (141 loc) · 4.76 KB
/
Copy pathclient.go
File metadata and controls
163 lines (141 loc) · 4.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
package pin
import (
"fmt"
"net/http"
"os"
"path/filepath"
rclient "github.com/git-pkgs/registries/client"
"github.com/git-pkgs/sigstore"
"github.com/sigstore/sigstore-go/pkg/root"
"github.com/sigstore/sigstore-go/pkg/tuf"
"github.com/git-pkgs/pin/source"
"github.com/git-pkgs/pin/source/forge"
"github.com/git-pkgs/pin/source/npm"
"github.com/git-pkgs/pin/source/rawurl"
)
// ClientOptions configures a Client. Zero values give the CLI defaults.
type ClientOptions struct {
// HTTPClient overrides the default, which is the safehttp transport
// (SSRF-safe dial gate, redirect cap, scheme allowlist).
HTTPClient *http.Client
// RegistryURL overrides the default npm registry. Honoured by the
// built-in npm resolver only.
RegistryURL string
Forge forge.Options
// SignatureMode controls npm dist.signatures verification. Zero is
// SignatureModeWarn.
SignatureMode npm.SignatureMode
// Verifier validates each attestation bundle the built-in npm and
// forge resolvers record. Nil means record-only — attestations
// land in the lockfile but the certificate chain and inclusion
// proof are not checked. The pin CLI sets this to sigstore.New(<TUF
// root>) when --verify-provenance is passed.
Verifier source.ProvenanceVerifier
}
// Client holds shared state across pin operations: HTTP client,
// source resolvers keyed by purl type, and typed accessors for the
// built-in sources. Safe for concurrent use across operations.
type Client struct {
httpClient *http.Client
// NPM, Forge, URL stay pointing at the resolvers registered by
// New even when a consumer overrides the same purl type via
// RegisterResolver. Operations that need source-specific APIs
// (npm.IsSticky, npm.Status, npm tarball re-derive for
// verify --strict) use these.
NPM *npm.Source
Forge *forge.Source
URL *rawurl.Source
resolvers map[string]source.Resolver
}
// New returns a Client with the built-in npm, github, and generic
// resolvers registered. Consumers can add or replace resolvers via
// RegisterResolver before calling any operation method.
func New(opts ClientOptions) *Client {
var clientOpts []rclient.Option
if opts.HTTPClient != nil {
clientOpts = append(clientOpts, rclient.WithHTTPClient(opts.HTTPClient))
} else {
clientOpts = append(clientOpts, rclient.WithSafeHTTP())
}
rc := rclient.NewClient(clientOpts...).WithUserAgent(userAgent())
npmS := npm.New(npm.Options{
HTTPClient: rc,
RegistryURL: opts.RegistryURL,
SignatureMode: opts.SignatureMode,
Verifier: opts.Verifier,
})
fopts := opts.Forge
fopts.HTTPClient = rc
if fopts.Verifier == nil {
fopts.Verifier = opts.Verifier
}
forgeS := forge.New(fopts)
rawurlS := rawurl.New(rawurl.Options{HTTPClient: rc})
return &Client{
httpClient: rc.HTTPClient,
NPM: npmS,
Forge: forgeS,
URL: rawurlS,
resolvers: map[string]source.Resolver{
"npm": npmS,
"github": forgeS,
"generic": rawurlS,
},
}
}
// RegisterResolver attaches a resolver for the given purl type.
// Overwrites any previously-registered resolver. Resolvers are
// read-only after registration; operations dispatch on resolved purl
// type at sync time.
func (c *Client) RegisterResolver(purlType string, r source.Resolver) {
c.resolvers[purlType] = r
}
// Resolver returns the resolver registered for the given purl type,
// or nil. Useful for custom resolvers that delegate to a built-in for
// non-matching purls.
//
//nolint:ireturn // the plug-in surface is interface-typed by design
func (c *Client) Resolver(purlType string) source.Resolver {
return c.resolvers[purlType]
}
func userAgent() string {
return "pin/" + ToolVersion + " (+https://github.com/git-pkgs/pin)"
}
// loadTrustedRoot caches the Sigstore TUF trust root locally so a
// second sync within the metadata's validity window skips the network
// round-trip.
func loadTrustedRoot() (*root.TrustedRoot, error) {
cachePath, err := pinTUFCachePath()
if err != nil {
return nil, err
}
if err := os.MkdirAll(cachePath, dirPerm); err != nil {
return nil, fmt.Errorf("create TUF cache dir %s: %w", cachePath, err)
}
tufOpts := tuf.DefaultOptions()
tufOpts.CachePath = cachePath
tufOpts.ForceCache = true
return root.FetchTrustedRootWithOptions(tufOpts)
}
func pinTUFCachePath() (string, error) {
dir, err := os.UserCacheDir()
if err != nil {
return "", err
}
return filepath.Join(dir, "pin", "sigstore-tuf"), nil
}
func clientFromSyncOptions(opts SyncOptions) (*Client, error) {
co := ClientOptions{
RegistryURL: opts.RegistryURL,
Forge: opts.Forge,
SignatureMode: opts.SignatureMode,
}
if opts.VerifyProvenance {
tr, err := loadTrustedRoot()
if err != nil {
return nil, fmt.Errorf("--verify-provenance: load Sigstore trust root: %w", err)
}
co.Verifier = sigstore.New(tr)
}
return New(co), nil
}