diff options
| -rwxr-xr-x | guix/scripts/substitute.scm | 115 | ||||
| -rw-r--r-- | tests/substitute.scm | 20 |
2 files changed, 100 insertions, 35 deletions
diff --git a/guix/scripts/substitute.scm b/guix/scripts/substitute.scm index 455f5a23461..49f5389fe27 100755 --- a/guix/scripts/substitute.scm +++ b/guix/scripts/substitute.scm | |||
| @@ -58,6 +58,7 @@ | |||
| 58 | #:use-module (web response) | 58 | #:use-module (web response) |
| 59 | #:use-module (guix http-client) | 59 | #:use-module (guix http-client) |
| 60 | #:export (%allow-unauthenticated-substitutes? | 60 | #:export (%allow-unauthenticated-substitutes? |
| 61 | %allow-unsafe-substitute-uris? | ||
| 61 | %reply-file-descriptor | 62 | %reply-file-descriptor |
| 62 | 63 | ||
| 63 | substitute-urls | 64 | substitute-urls |
| @@ -90,6 +91,19 @@ disabled!~%")) | |||
| 90 | (and=> (getenv "GUIX_ALLOW_UNAUTHENTICATED_SUBSTITUTES") | 91 | (and=> (getenv "GUIX_ALLOW_UNAUTHENTICATED_SUBSTITUTES") |
| 91 | (cut string-ci=? <> "yes")))) | 92 | (cut string-ci=? <> "yes")))) |
| 92 | 93 | ||
| 94 | (define %allow-unsafe-substitute-uris? | ||
| 95 | ;; Whether to allow substitutes with a "file://" URI. These are useful for | ||
| 96 | ;; testing purposes but should never be allowed otherwise, as the URIs | ||
| 97 | ;; contained in narinfos are not signed and could point to any | ||
| 98 | ;; attacker-controlled location. Use GUIX_ALLOW_UNAUTHENTICATED_SUBSTITUTES | ||
| 99 | ;; as a way to tell when we're testing. | ||
| 100 | (make-parameter (%allow-unauthenticated-substitutes?))) | ||
| 101 | |||
| 102 | (define %allow-unsafe-narinfo-uris? | ||
| 103 | ;; Like %allow-unsafe-substitute-uris?, but affecting the urls used to fetch | ||
| 104 | ;; narinfos. | ||
| 105 | (make-parameter (%allow-unauthenticated-substitutes?))) | ||
| 106 | |||
| 93 | (define (at-most max-length lst) | 107 | (define (at-most max-length lst) |
| 94 | "If LST is shorter than MAX-LENGTH, return it and the empty list; otherwise | 108 | "If LST is shorter than MAX-LENGTH, return it and the empty list; otherwise |
| 95 | return its MAX-LENGTH first elements and its tail." | 109 | return its MAX-LENGTH first elements and its tail." |
| @@ -368,6 +382,21 @@ server certificates." | |||
| 368 | (drain-input socket) | 382 | (drain-input socket) |
| 369 | socket)))))))) | 383 | socket)))))))) |
| 370 | 384 | ||
| 385 | (define (uri-safe? uri) | ||
| 386 | "Return a boolean indicating whether URI, which is either a uri or its | ||
| 387 | string representation, is generally safe to use without requiring any trust." | ||
| 388 | (let ((uri (if (uri? uri) | ||
| 389 | uri | ||
| 390 | (string->uri uri)))) | ||
| 391 | (and uri | ||
| 392 | (case (uri-scheme uri) | ||
| 393 | ((#f file) #f) | ||
| 394 | (else #t))))) | ||
| 395 | |||
| 396 | (define (narinfo-uris-safe? narinfo) | ||
| 397 | (or (%allow-unsafe-substitute-uris?) | ||
| 398 | (every uri-safe? (narinfo-uris narinfo)))) | ||
| 399 | |||
| 371 | (define* (process-substitution/fallback narinfo destination | 400 | (define* (process-substitution/fallback narinfo destination |
| 372 | #:key cache-urls acl | 401 | #:key cache-urls acl |
| 373 | deduplicate? print-build-trace? | 402 | deduplicate? print-build-trace? |
| @@ -391,9 +420,10 @@ way to download the nar." | |||
| 391 | #:open-connection | 420 | #:open-connection |
| 392 | open-connection-for-uri/cached) | 421 | open-connection-for-uri/cached) |
| 393 | ((alternate) | 422 | ((alternate) |
| 394 | (if (or (equivalent-narinfo? narinfo alternate) | 423 | (if (and (narinfo-uris-safe? alternate) |
| 395 | (valid-narinfo? alternate acl) | 424 | (or (equivalent-narinfo? narinfo alternate) |
| 396 | (%allow-unauthenticated-substitutes?)) | 425 | (valid-narinfo? alternate acl) |
| 426 | (%allow-unauthenticated-substitutes?))) | ||
| 397 | (guard (c ((or (http-get-error? c) | 427 | (guard (c ((or (http-get-error? c) |
| 398 | (network-error? c)) | 428 | (network-error? c)) |
| 399 | (when (http-get-error? c) | 429 | (when (http-get-error? c) |
| @@ -429,6 +459,16 @@ PORT." | |||
| 429 | (const #t) | 459 | (const #t) |
| 430 | (cut valid-narinfo? <> acl)))) | 460 | (cut valid-narinfo? <> acl)))) |
| 431 | 461 | ||
| 462 | (define (fallback) | ||
| 463 | (process-substitution/fallback narinfo destination | ||
| 464 | #:cache-urls cache-urls | ||
| 465 | #:acl acl | ||
| 466 | #:deduplicate? deduplicate? | ||
| 467 | #:print-build-trace? | ||
| 468 | print-build-trace? | ||
| 469 | #:fast-decompression? | ||
| 470 | fast-decompression?)) | ||
| 471 | |||
| 432 | (unless narinfo | 472 | (unless narinfo |
| 433 | (raise | 473 | (raise |
| 434 | (formatted-message | 474 | (formatted-message |
| @@ -437,33 +477,30 @@ PORT." | |||
| 437 | 477 | ||
| 438 | (let ((expected-hash | 478 | (let ((expected-hash |
| 439 | actual-hash | 479 | actual-hash |
| 440 | (guard | 480 | (cond |
| 441 | (c ((or (http-get-error? c) | 481 | ((narinfo-uris-safe? narinfo) |
| 442 | (network-error? c)) | 482 | (guard |
| 443 | (when (http-get-error? c) | 483 | (c ((or (http-get-error? c) |
| 444 | (warning (G_ "download from '~a' failed: ~a, ~s~%") | 484 | (network-error? c)) |
| 445 | (uri->string (http-get-error-uri c)) | 485 | (when (http-get-error? c) |
| 446 | (http-get-error-code c) | 486 | (warning (G_ "download from '~a' failed: ~a, ~s~%") |
| 447 | (http-get-error-reason c))) | 487 | (uri->string (http-get-error-uri c)) |
| 448 | (format | 488 | (http-get-error-code c) |
| 449 | (current-error-port) | 489 | (http-get-error-reason c))) |
| 450 | (G_ "retrying download of '~a' with other substitute URLs...~%") | 490 | (format |
| 451 | store-item) | 491 | (current-error-port) |
| 452 | (process-substitution/fallback narinfo destination | 492 | (G_ "retrying download of '~a' with other substitute URLs...~%") |
| 453 | #:cache-urls cache-urls | 493 | store-item) |
| 454 | #:acl acl | 494 | (fallback))) |
| 455 | #:deduplicate? deduplicate? | 495 | (download-nar narinfo destination |
| 456 | #:print-build-trace? | 496 | #:deduplicate? deduplicate? |
| 457 | print-build-trace? | 497 | #:print-build-trace? print-build-trace? |
| 458 | #:fast-decompression? | 498 | #:fast-decompression? fast-decompression? |
| 459 | fast-decompression?))) | 499 | #:open-connection-for-uri |
| 460 | (download-nar narinfo destination | 500 | open-connection-for-uri/cached |
| 461 | #:deduplicate? deduplicate? | 501 | #:keep-alive? #t))) |
| 462 | #:print-build-trace? print-build-trace? | 502 | (else |
| 463 | #:fast-decompression? fast-decompression? | 503 | (fallback))))) |
| 464 | #:open-connection-for-uri | ||
| 465 | open-connection-for-uri/cached | ||
| 466 | #:keep-alive? #t)))) | ||
| 467 | (values narinfo | 504 | (values narinfo |
| 468 | expected-hash | 505 | expected-hash |
| 469 | actual-hash))) | 506 | actual-hash))) |
| @@ -519,10 +556,17 @@ substitutes may be unavailable\n"))))) | |||
| 519 | found." | 556 | found." |
| 520 | (assoc-ref (force options) option)))) | 557 | (assoc-ref (force options) option)))) |
| 521 | 558 | ||
| 559 | (define (assert-safe-uris uris) | ||
| 560 | (unless (or (%allow-unsafe-narinfo-uris?) | ||
| 561 | (every uri-safe? uris)) | ||
| 562 | (leave (G_ "unsafe or invalid URI in ~S~%") uris)) | ||
| 563 | uris) | ||
| 564 | |||
| 522 | (define %default-substitute-urls | 565 | (define %default-substitute-urls |
| 523 | (match (and=> (or (find-daemon-option "untrusted-substitute-urls") ;client | 566 | (match (or (and=> (find-daemon-option "untrusted-substitute-urls") ;client |
| 524 | (find-daemon-option "substitute-urls")) ;admin | 567 | (compose assert-safe-uris string-tokenize)) |
| 525 | string-tokenize) | 568 | (and=> (find-daemon-option "substitute-urls") ;admin |
| 569 | string-tokenize)) | ||
| 526 | ((urls ...) | 570 | ((urls ...) |
| 527 | urls) | 571 | urls) |
| 528 | (#f | 572 | (#f |
| @@ -558,8 +602,9 @@ is shorter than MAX elements, then it is directly returned." | |||
| 558 | (let* ((option (find-daemon-option "discover")) | 602 | (let* ((option (find-daemon-option "discover")) |
| 559 | (discover? (and option (string=? option "true")))) | 603 | (discover? (and option (string=? option "true")))) |
| 560 | (if discover? | 604 | (if discover? |
| 561 | (randomize-substitute-urls (read-substitute-urls)) | 605 | (randomize-substitute-urls (filter uri-safe? |
| 562 | '()))) | 606 | (read-substitute-urls))) |
| 607 | '()))) | ||
| 563 | 608 | ||
| 564 | (define substitute-urls | 609 | (define substitute-urls |
| 565 | ;; List of substitute URLs. | 610 | ;; List of substitute URLs. |
diff --git a/tests/substitute.scm b/tests/substitute.scm index cd75d56b981..10b74db084e 100644 --- a/tests/substitute.scm +++ b/tests/substitute.scm | |||
| @@ -206,6 +206,26 @@ a file for NARINFO." | |||
| 206 | ;; daemon. | 206 | ;; daemon. |
| 207 | (%reply-file-descriptor #f) | 207 | (%reply-file-descriptor #f) |
| 208 | 208 | ||
| 209 | (test-equal "file:// URI prohibited by default" | ||
| 210 | "not-found\n" | ||
| 211 | (with-output-to-string | ||
| 212 | (lambda () | ||
| 213 | (%allow-unsafe-substitute-uris? #f) | ||
| 214 | (let ((narinfo %narinfo)) | ||
| 215 | (with-narinfo (string-append narinfo "Signature: " | ||
| 216 | (signature-field narinfo) "\n") | ||
| 217 | (call-with-temporary-directory | ||
| 218 | (lambda (directory) | ||
| 219 | (with-input-from-string (string-append | ||
| 220 | "substitute " (%store-prefix) | ||
| 221 | "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo " | ||
| 222 | directory "/shouldnotbewritten\n") | ||
| 223 | (lambda () | ||
| 224 | (guix-substitute "--substitute")))))))))) | ||
| 225 | |||
| 226 | ;; Allow these for ease of testing for the rest of the tests | ||
| 227 | (%allow-unsafe-substitute-uris? #t) | ||
| 228 | |||
| 209 | 229 | ||
| 210 | (test-equal "query narinfo without signature" | 230 | (test-equal "query narinfo without signature" |
| 211 | "" ; not substitutable | 231 | "" ; not substitutable |
