Source file src/pkg/cmd/go/internal/modfetch/fetch.go
1
2
3
4
5 package modfetch
6
7 import (
8 "archive/zip"
9 "bytes"
10 "fmt"
11 "io"
12 "io/ioutil"
13 "os"
14 "path/filepath"
15 "sort"
16 "strings"
17 "sync"
18
19 "cmd/go/internal/base"
20 "cmd/go/internal/cfg"
21 "cmd/go/internal/dirhash"
22 "cmd/go/internal/module"
23 "cmd/go/internal/par"
24 "cmd/go/internal/renameio"
25 )
26
27 var downloadCache par.Cache
28
29
30
31
32 func Download(mod module.Version) (dir string, err error) {
33 if PkgMod == "" {
34
35 return "", fmt.Errorf("missing modfetch.PkgMod")
36 }
37
38
39 type cached struct {
40 dir string
41 err error
42 }
43 c := downloadCache.Do(mod, func() interface{} {
44 dir, err := DownloadDir(mod)
45 if err != nil {
46 return cached{"", err}
47 }
48 if err := download(mod, dir); err != nil {
49 return cached{"", err}
50 }
51 checkMod(mod)
52 return cached{dir, nil}
53 }).(cached)
54 return c.dir, c.err
55 }
56
57 func download(mod module.Version, dir string) (err error) {
58
59 fi, err := os.Stat(dir)
60 if err == nil && fi.IsDir() {
61 return nil
62 }
63
64
65
66
67 zipfile, err := DownloadZip(mod)
68 if err != nil {
69 return err
70 }
71
72 if cfg.CmdName != "mod download" {
73 fmt.Fprintf(os.Stderr, "go: extracting %s %s\n", mod.Path, mod.Version)
74 }
75
76 unlock, err := lockVersion(mod)
77 if err != nil {
78 return err
79 }
80 defer unlock()
81
82
83 fi, err = os.Stat(dir)
84 if err == nil && fi.IsDir() {
85 return nil
86 }
87
88
89
90
91 parentDir := filepath.Dir(dir)
92 tmpPrefix := filepath.Base(dir) + ".tmp-"
93 if old, err := filepath.Glob(filepath.Join(parentDir, tmpPrefix+"*")); err == nil {
94 for _, path := range old {
95 RemoveAll(path)
96 }
97 }
98
99
100
101
102
103
104 if err := os.MkdirAll(parentDir, 0777); err != nil {
105 return err
106 }
107 tmpDir, err := ioutil.TempDir(parentDir, tmpPrefix)
108 if err != nil {
109 return err
110 }
111 defer func() {
112 if err != nil {
113 RemoveAll(tmpDir)
114 }
115 }()
116
117 modpath := mod.Path + "@" + mod.Version
118 if err := Unzip(tmpDir, zipfile, modpath, 0); err != nil {
119 fmt.Fprintf(os.Stderr, "-> %s\n", err)
120 return err
121 }
122
123 if err := os.Rename(tmpDir, dir); err != nil {
124 return err
125 }
126
127
128
129 makeDirsReadOnly(dir)
130 return nil
131 }
132
133 var downloadZipCache par.Cache
134
135
136
137 func DownloadZip(mod module.Version) (zipfile string, err error) {
138
139 type cached struct {
140 zipfile string
141 err error
142 }
143 c := downloadZipCache.Do(mod, func() interface{} {
144 zipfile, err := CachePath(mod, "zip")
145 if err != nil {
146 return cached{"", err}
147 }
148
149
150 if _, err := os.Stat(zipfile); err == nil {
151 return cached{zipfile, nil}
152 }
153
154
155 if cfg.CmdName != "mod download" {
156 fmt.Fprintf(os.Stderr, "go: downloading %s %s\n", mod.Path, mod.Version)
157 }
158 unlock, err := lockVersion(mod)
159 if err != nil {
160 return cached{"", err}
161 }
162 defer unlock()
163
164
165
166 if _, err := os.Stat(zipfile); err == nil {
167 return cached{zipfile, nil}
168 }
169 if err := os.MkdirAll(filepath.Dir(zipfile), 0777); err != nil {
170 return cached{"", err}
171 }
172 if err := downloadZip(mod, zipfile); err != nil {
173 return cached{"", err}
174 }
175 return cached{zipfile, nil}
176 }).(cached)
177 return c.zipfile, c.err
178 }
179
180 func downloadZip(mod module.Version, zipfile string) (err error) {
181
182
183
184 for _, base := range []string{zipfile, zipfile + "hash"} {
185 if old, err := filepath.Glob(renameio.Pattern(base)); err == nil {
186 for _, path := range old {
187 os.Remove(path)
188 }
189 }
190 }
191
192
193
194
195
196
197 f, err := ioutil.TempFile(filepath.Dir(zipfile), filepath.Base(renameio.Pattern(zipfile)))
198 if err != nil {
199 return err
200 }
201 defer func() {
202 if err != nil {
203 f.Close()
204 os.Remove(f.Name())
205 }
206 }()
207
208 err = TryProxies(func(proxy string) error {
209 repo, err := Lookup(proxy, mod.Path)
210 if err != nil {
211 return err
212 }
213 return repo.Zip(f, mod.Version)
214 })
215 if err != nil {
216 return err
217 }
218
219
220
221
222 fi, err := f.Stat()
223 if err != nil {
224 return err
225 }
226 z, err := zip.NewReader(f, fi.Size())
227 if err != nil {
228 return err
229 }
230 prefix := mod.Path + "@" + mod.Version + "/"
231 for _, f := range z.File {
232 if !strings.HasPrefix(f.Name, prefix) {
233 return fmt.Errorf("zip for %s has unexpected file %s", prefix[:len(prefix)-1], f.Name)
234 }
235 }
236
237
238
239
240 if err := f.Sync(); err != nil {
241 return err
242 }
243 if err := f.Close(); err != nil {
244 return err
245 }
246
247
248 hash, err := dirhash.HashZip(f.Name(), dirhash.DefaultHash)
249 if err != nil {
250 return err
251 }
252 checkModSum(mod, hash)
253
254 if err := renameio.WriteFile(zipfile+"hash", []byte(hash), 0666); err != nil {
255 return err
256 }
257 if err := os.Rename(f.Name(), zipfile); err != nil {
258 return err
259 }
260
261
262
263 return nil
264 }
265
266 var GoSumFile string
267
268 type modSum struct {
269 mod module.Version
270 sum string
271 }
272
273 var goSum struct {
274 mu sync.Mutex
275 m map[module.Version][]string
276 checked map[modSum]bool
277 dirty bool
278 overwrite bool
279 enabled bool
280 modverify string
281 }
282
283
284
285
286 func initGoSum() bool {
287 if GoSumFile == "" {
288 return false
289 }
290 if goSum.m != nil {
291 return true
292 }
293
294 goSum.m = make(map[module.Version][]string)
295 goSum.checked = make(map[modSum]bool)
296 data, err := renameio.ReadFile(GoSumFile)
297 if err != nil && !os.IsNotExist(err) {
298 base.Fatalf("go: %v", err)
299 }
300 goSum.enabled = true
301 readGoSum(goSum.m, GoSumFile, data)
302
303
304
305 alt := strings.TrimSuffix(GoSumFile, ".sum") + ".modverify"
306 if data, err := renameio.ReadFile(alt); err == nil {
307 migrate := make(map[module.Version][]string)
308 readGoSum(migrate, alt, data)
309 for mod, sums := range migrate {
310 for _, sum := range sums {
311 addModSumLocked(mod, sum)
312 }
313 }
314 goSum.modverify = alt
315 }
316 return true
317 }
318
319
320
321
322 const emptyGoModHash = "h1:G7mAYYxgmS0lVkHyy2hEOLQCFB0DlQFTMLWggykrydY="
323
324
325
326 func readGoSum(dst map[module.Version][]string, file string, data []byte) {
327 lineno := 0
328 for len(data) > 0 {
329 var line []byte
330 lineno++
331 i := bytes.IndexByte(data, '\n')
332 if i < 0 {
333 line, data = data, nil
334 } else {
335 line, data = data[:i], data[i+1:]
336 }
337 f := strings.Fields(string(line))
338 if len(f) == 0 {
339
340 continue
341 }
342 if len(f) != 3 {
343 base.Fatalf("go: malformed go.sum:\n%s:%d: wrong number of fields %v", file, lineno, len(f))
344 }
345 if f[2] == emptyGoModHash {
346
347 continue
348 }
349 mod := module.Version{Path: f[0], Version: f[1]}
350 dst[mod] = append(dst[mod], f[2])
351 }
352 }
353
354
355 func checkMod(mod module.Version) {
356 if PkgMod == "" {
357
358 return
359 }
360
361
362 ziphash, err := CachePath(mod, "ziphash")
363 if err != nil {
364 base.Fatalf("verifying %s@%s: %v", mod.Path, mod.Version, err)
365 }
366 data, err := renameio.ReadFile(ziphash)
367 if err != nil {
368 if os.IsNotExist(err) {
369
370 return
371 }
372 base.Fatalf("verifying %s@%s: %v", mod.Path, mod.Version, err)
373 }
374 h := strings.TrimSpace(string(data))
375 if !strings.HasPrefix(h, "h1:") {
376 base.Fatalf("verifying %s@%s: unexpected ziphash: %q", mod.Path, mod.Version, h)
377 }
378
379 checkModSum(mod, h)
380 }
381
382
383 func goModSum(data []byte) (string, error) {
384 return dirhash.Hash1([]string{"go.mod"}, func(string) (io.ReadCloser, error) {
385 return ioutil.NopCloser(bytes.NewReader(data)), nil
386 })
387 }
388
389
390
391 func checkGoMod(path, version string, data []byte) {
392 h, err := goModSum(data)
393 if err != nil {
394 base.Fatalf("verifying %s %s go.mod: %v", path, version, err)
395 }
396
397 checkModSum(module.Version{Path: path, Version: version + "/go.mod"}, h)
398 }
399
400
401 func checkModSum(mod module.Version, h string) {
402
403
404
405
406
407
408 goSum.mu.Lock()
409 inited := initGoSum()
410 done := inited && haveModSumLocked(mod, h)
411 goSum.mu.Unlock()
412
413 if done {
414 return
415 }
416
417
418
419 if useSumDB(mod) {
420
421 checkSumDB(mod, h)
422 }
423
424
425 if inited {
426 goSum.mu.Lock()
427 addModSumLocked(mod, h)
428 goSum.mu.Unlock()
429 }
430 }
431
432
433
434
435 func haveModSumLocked(mod module.Version, h string) bool {
436 goSum.checked[modSum{mod, h}] = true
437 for _, vh := range goSum.m[mod] {
438 if h == vh {
439 return true
440 }
441 if strings.HasPrefix(vh, "h1:") {
442 base.Fatalf("verifying %s@%s: checksum mismatch\n\tdownloaded: %v\n\tgo.sum: %v"+goSumMismatch, mod.Path, mod.Version, h, vh)
443 }
444 }
445 return false
446 }
447
448
449
450 func addModSumLocked(mod module.Version, h string) {
451 if haveModSumLocked(mod, h) {
452 return
453 }
454 if len(goSum.m[mod]) > 0 {
455 fmt.Fprintf(os.Stderr, "warning: verifying %s@%s: unknown hashes in go.sum: %v; adding %v"+hashVersionMismatch, mod.Path, mod.Version, strings.Join(goSum.m[mod], ", "), h)
456 }
457 goSum.m[mod] = append(goSum.m[mod], h)
458 goSum.dirty = true
459 }
460
461
462
463 func checkSumDB(mod module.Version, h string) {
464 db, lines, err := lookupSumDB(mod)
465 if err != nil {
466 base.Fatalf("verifying %s@%s: %v", mod.Path, mod.Version, err)
467 }
468
469 have := mod.Path + " " + mod.Version + " " + h
470 prefix := mod.Path + " " + mod.Version + " h1:"
471 for _, line := range lines {
472 if line == have {
473 return
474 }
475 if strings.HasPrefix(line, prefix) {
476 base.Fatalf("verifying %s@%s: checksum mismatch\n\tdownloaded: %v\n\t%s: %v"+sumdbMismatch, mod.Path, mod.Version, h, db, line[len(prefix)-len("h1:"):])
477 }
478 }
479 }
480
481
482
483 func Sum(mod module.Version) string {
484 if PkgMod == "" {
485
486 return ""
487 }
488
489 ziphash, err := CachePath(mod, "ziphash")
490 if err != nil {
491 return ""
492 }
493 data, err := renameio.ReadFile(ziphash)
494 if err != nil {
495 return ""
496 }
497 return strings.TrimSpace(string(data))
498 }
499
500
501 func WriteGoSum() {
502 goSum.mu.Lock()
503 defer goSum.mu.Unlock()
504
505 if !goSum.enabled {
506
507
508
509 return
510 }
511 if !goSum.dirty {
512
513 return
514 }
515 if cfg.BuildMod == "readonly" {
516 base.Fatalf("go: updates to go.sum needed, disabled by -mod=readonly")
517 }
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535 unlock := SideLock()
536 defer unlock()
537
538 if !goSum.overwrite {
539
540
541 data, err := renameio.ReadFile(GoSumFile)
542 if err != nil && !os.IsNotExist(err) {
543 base.Fatalf("go: re-reading go.sum: %v", err)
544 }
545
546
547
548
549 goSum.m = make(map[module.Version][]string, len(goSum.m))
550 readGoSum(goSum.m, GoSumFile, data)
551 for ms := range goSum.checked {
552 addModSumLocked(ms.mod, ms.sum)
553 goSum.dirty = true
554 }
555 }
556
557 var mods []module.Version
558 for m := range goSum.m {
559 mods = append(mods, m)
560 }
561 module.Sort(mods)
562 var buf bytes.Buffer
563 for _, m := range mods {
564 list := goSum.m[m]
565 sort.Strings(list)
566 for _, h := range list {
567 fmt.Fprintf(&buf, "%s %s %s\n", m.Path, m.Version, h)
568 }
569 }
570
571 if err := renameio.WriteFile(GoSumFile, buf.Bytes(), 0666); err != nil {
572 base.Fatalf("go: writing go.sum: %v", err)
573 }
574
575 goSum.checked = make(map[modSum]bool)
576 goSum.dirty = false
577 goSum.overwrite = false
578
579 if goSum.modverify != "" {
580 os.Remove(goSum.modverify)
581 }
582 }
583
584
585 func TrimGoSum(keep map[module.Version]bool) {
586 goSum.mu.Lock()
587 defer goSum.mu.Unlock()
588 if !initGoSum() {
589 return
590 }
591
592 for m := range goSum.m {
593
594
595 noGoMod := module.Version{Path: m.Path, Version: strings.TrimSuffix(m.Version, "/go.mod")}
596 if !keep[m] && !keep[noGoMod] {
597 delete(goSum.m, m)
598 goSum.dirty = true
599 goSum.overwrite = true
600 }
601 }
602 }
603
604 const goSumMismatch = `
605
606 SECURITY ERROR
607 This download does NOT match an earlier download recorded in go.sum.
608 The bits may have been replaced on the origin server, or an attacker may
609 have intercepted the download attempt.
610
611 For more information, see 'go help module-auth'.
612 `
613
614 const sumdbMismatch = `
615
616 SECURITY ERROR
617 This download does NOT match the one reported by the checksum server.
618 The bits may have been replaced on the origin server, or an attacker may
619 have intercepted the download attempt.
620
621 For more information, see 'go help module-auth'.
622 `
623
624 const hashVersionMismatch = `
625
626 SECURITY WARNING
627 This download is listed in go.sum, but using an unknown hash algorithm.
628 The download cannot be verified.
629
630 For more information, see 'go help module-auth'.
631
632 `
633
634 var HelpModuleAuth = &base.Command{
635 UsageLine: "module-auth",
636 Short: "module authentication using go.sum",
637 Long: `
638 The go command tries to authenticate every downloaded module,
639 checking that the bits downloaded for a specific module version today
640 match bits downloaded yesterday. This ensures repeatable builds
641 and detects introduction of unexpected changes, malicious or not.
642
643 In each module's root, alongside go.mod, the go command maintains
644 a file named go.sum containing the cryptographic checksums of the
645 module's dependencies.
646
647 The form of each line in go.sum is three fields:
648
649 <module> <version>[/go.mod] <hash>
650
651 Each known module version results in two lines in the go.sum file.
652 The first line gives the hash of the module version's file tree.
653 The second line appends "/go.mod" to the version and gives the hash
654 of only the module version's (possibly synthesized) go.mod file.
655 The go.mod-only hash allows downloading and authenticating a
656 module version's go.mod file, which is needed to compute the
657 dependency graph, without also downloading all the module's source code.
658
659 The hash begins with an algorithm prefix of the form "h<N>:".
660 The only defined algorithm prefix is "h1:", which uses SHA-256.
661
662 Module authentication failures
663
664 The go command maintains a cache of downloaded packages and computes
665 and records the cryptographic checksum of each package at download time.
666 In normal operation, the go command checks the main module's go.sum file
667 against these precomputed checksums instead of recomputing them on
668 each command invocation. The 'go mod verify' command checks that
669 the cached copies of module downloads still match both their recorded
670 checksums and the entries in go.sum.
671
672 In day-to-day development, the checksum of a given module version
673 should never change. Each time a dependency is used by a given main
674 module, the go command checks its local cached copy, freshly
675 downloaded or not, against the main module's go.sum. If the checksums
676 don't match, the go command reports the mismatch as a security error
677 and refuses to run the build. When this happens, proceed with caution:
678 code changing unexpectedly means today's build will not match
679 yesterday's, and the unexpected change may not be beneficial.
680
681 If the go command reports a mismatch in go.sum, the downloaded code
682 for the reported module version does not match the one used in a
683 previous build of the main module. It is important at that point
684 to find out what the right checksum should be, to decide whether
685 go.sum is wrong or the downloaded code is wrong. Usually go.sum is right:
686 you want to use the same code you used yesterday.
687
688 If a downloaded module is not yet included in go.sum and it is a publicly
689 available module, the go command consults the Go checksum database to fetch
690 the expected go.sum lines. If the downloaded code does not match those
691 lines, the go command reports the mismatch and exits. Note that the
692 database is not consulted for module versions already listed in go.sum.
693
694 If a go.sum mismatch is reported, it is always worth investigating why
695 the code downloaded today differs from what was downloaded yesterday.
696
697 The GOSUMDB environment variable identifies the name of checksum database
698 to use and optionally its public key and URL, as in:
699
700 GOSUMDB="sum.golang.org"
701 GOSUMDB="sum.golang.org+<publickey>"
702 GOSUMDB="sum.golang.org+<publickey> https://sum.golang.org"
703
704 The go command knows the public key of sum.golang.org, and also that the name
705 sum.golang.google.cn (available inside mainland China) connects to the
706 sum.golang.org checksum database; use of any other database requires giving
707 the public key explicitly.
708 The URL defaults to "https://" followed by the database name.
709
710 GOSUMDB defaults to "sum.golang.org", the Go checksum database run by Google.
711 See https://sum.golang.org/privacy for the service's privacy policy.
712
713 If GOSUMDB is set to "off", or if "go get" is invoked with the -insecure flag,
714 the checksum database is not consulted, and all unrecognized modules are
715 accepted, at the cost of giving up the security guarantee of verified repeatable
716 downloads for all modules. A better way to bypass the checksum database
717 for specific modules is to use the GOPRIVATE or GONOSUMDB environment
718 variables. See 'go help module-private' for details.
719
720 The 'go env -w' command (see 'go help env') can be used to set these variables
721 for future go command invocations.
722 `,
723 }
724
725 var HelpModulePrivate = &base.Command{
726 UsageLine: "module-private",
727 Short: "module configuration for non-public modules",
728 Long: `
729 The go command defaults to downloading modules from the public Go module
730 mirror at proxy.golang.org. It also defaults to validating downloaded modules,
731 regardless of source, against the public Go checksum database at sum.golang.org.
732 These defaults work well for publicly available source code.
733
734 The GOPRIVATE environment variable controls which modules the go command
735 considers to be private (not available publicly) and should therefore not use the
736 proxy or checksum database. The variable is a comma-separated list of
737 glob patterns (in the syntax of Go's path.Match) of module path prefixes.
738 For example,
739
740 GOPRIVATE=*.corp.example.com,rsc.io/private
741
742 causes the go command to treat as private any module with a path prefix
743 matching either pattern, including git.corp.example.com/xyzzy, rsc.io/private,
744 and rsc.io/private/quux.
745
746 The GOPRIVATE environment variable may be used by other tools as well to
747 identify non-public modules. For example, an editor could use GOPRIVATE
748 to decide whether to hyperlink a package import to a godoc.org page.
749
750 For fine-grained control over module download and validation, the GONOPROXY
751 and GONOSUMDB environment variables accept the same kind of glob list
752 and override GOPRIVATE for the specific decision of whether to use the proxy
753 and checksum database, respectively.
754
755 For example, if a company ran a module proxy serving private modules,
756 users would configure go using:
757
758 GOPRIVATE=*.corp.example.com
759 GOPROXY=proxy.example.com
760 GONOPROXY=none
761
762 This would tell the go command and other tools that modules beginning with
763 a corp.example.com subdomain are private but that the company proxy should
764 be used for downloading both public and private modules, because
765 GONOPROXY has been set to a pattern that won't match any modules,
766 overriding GOPRIVATE.
767
768 The 'go env -w' command (see 'go help env') can be used to set these variables
769 for future go command invocations.
770 `,
771 }
772
View as plain text