Skip to content

Commit

Permalink
cmd/cue: add testscript for registry auth via logins.json
Browse files Browse the repository at this point in the history
We had testscripts like registry_auth.txtar which authorized via
$DOCKER_CONFIG/config.json, and we had the internal/e2e tests
which used $CUE_CONFIG_DIR/logins.json to publish and fetch
from registry.cue.works.

However, we did not have any local testscript which used logins.json.
In particular, that code path had a concurrent map write data race
fixed by https://cuelang.org/cl/1176581, which we hadn't spotted earlier
due to the lack of such a local test that we would run with -race.

Add it, making it fetch three modules from the local registry.
While the race doesn't always show itself via `go test` without the fix,
it does appear with `go test -race` almost every single time.

Note that this required teaching registrytest to use bearer tokens,
as up until now it only knew how to require basic auth.

Signed-off-by: Daniel Martí <mvdan@mvdan.cc>
Change-Id: I3d0b720301fb0505f1323a0a77a46aa2f8a91eab
Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1176656
TryBot-Result: CUEcueckoo <cueckoo@gmail.com>
Unity-Result: CUE porcuepine <cue.porcuepine@gmail.com>
Reviewed-by: Paul Jolly <paul@myitcv.io>
  • Loading branch information
mvdan committed Feb 10, 2024
1 parent 3d1c0cb commit 64c5656
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 8 deletions.
74 changes: 74 additions & 0 deletions cmd/cue/cmd/testdata/script/registry_auth_logins.txtar
@@ -0,0 +1,74 @@
# Test that we can authenticate to a registry with bearer token auth via logins.json.
# We use multiple dependencies to test concurrent fetches as well, to catch races.

env CUE_CONFIG_DIR=$WORK/cueconfig
env-fill $CUE_CONFIG_DIR/logins.json
exec cue export .
cmp stdout expect-stdout

# Sanity-check that we get an error when using the wrong token.
env CUE_MODCACHE=$WORK/.tmp/different-cache
env-fill cueconfig/badtoken.json
cp cueconfig/badtoken.json cueconfig/logins.json
! exec cue export .
stderr 'import failed: cannot find package .* 401 Unauthorized; body: "invalid credentials'
-- cueconfig/logins.json --
{
"registries": {
"${DEBUG_REGISTRY_HOST}": {
"access_token": "goodtoken"
}
}
}
-- cueconfig/badtoken.json --
{
"registries": {
"${DEBUG_REGISTRY_HOST}": {
"access_token": "badtoken"
}
}
}
-- expect-stdout --
[
"ok1",
"ok2",
"ok3"
]
-- main.cue --
package main
import "example.com/e1"
import "example.com/e2"
import "example.com/e3"

[e1.foo, e2.foo, e3.foo]

-- cue.mod/module.cue --
module: "test.org"
deps: "example.com/e1": v: "v0.0.1"
deps: "example.com/e2": v: "v0.0.1"
deps: "example.com/e3": v: "v0.0.1"
-- _registry/auth.json --
{"bearerToken": "goodtoken"}
-- _registry_prefix --
somewhere/other
-- _registry/example.com_e1_v0.0.1/cue.mod/module.cue --
module: "example.com/e1@v0"

-- _registry/example.com_e1_v0.0.1/main.cue --
package e1

foo: "ok1"
-- _registry/example.com_e2_v0.0.1/cue.mod/module.cue --
module: "example.com/e2@v0"

-- _registry/example.com_e2_v0.0.1/main.cue --
package e2

foo: "ok2"
-- _registry/example.com_e3_v0.0.1/cue.mod/module.cue --
module: "example.com/e3@v0"

-- _registry/example.com_e3_v0.0.1/main.cue --
package e3

foo: "ok3"
33 changes: 25 additions & 8 deletions internal/registrytest/registry.go
Expand Up @@ -28,10 +28,12 @@ import (
)

// AuthConfig specifies authorization requirements for the server.
// Currently it only supports basic auth.
// Currently it only supports basic and bearer auth.
type AuthConfig struct {
Username string `json:"username"`
Password string `json:"password"`

BearerToken string `json:"bearerToken"`
}

// Upload uploads the modules found inside fsys (stored
Expand Down Expand Up @@ -100,19 +102,34 @@ func New(fsys fs.FS, prefix string) (*Registry, error) {
// in cfg. If cfg is nil or there are no auth requirements, it returns handler
// unchanged.
func AuthHandler(handler http.Handler, cfg *AuthConfig) http.Handler {
if cfg == nil || cfg.Username == "" {
if cfg == nil || (*cfg == AuthConfig{}) {
return handler
}
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
if req.Header.Get("Authorization") == "" {
w.Header().Set("Www-Authenticate", "Basic service=registry")
auth := req.Header.Get("Authorization")
if auth == "" {
if cfg.BearerToken != "" {
// Note that this lacks information like the realm,
// but we don't need it for our test cases yet.
w.Header().Set("Www-Authenticate", "Bearer service=registry")
} else {
w.Header().Set("Www-Authenticate", "Basic service=registry")
}
http.Error(w, "no credentials", http.StatusUnauthorized)
return
}
username, password, ok := req.BasicAuth()
if !ok || username != cfg.Username || password != cfg.Password {
http.Error(w, "invalid credentials", http.StatusUnauthorized)
return
if cfg.BearerToken != "" {
token, ok := strings.CutPrefix(auth, "Bearer ")
if !ok || token != cfg.BearerToken {
http.Error(w, "invalid credentials", http.StatusUnauthorized)
return
}
} else {
username, password, ok := req.BasicAuth()
if !ok || username != cfg.Username || password != cfg.Password {
http.Error(w, "invalid credentials", http.StatusUnauthorized)
return
}
}
handler.ServeHTTP(w, req)
})
Expand Down

0 comments on commit 64c5656

Please sign in to comment.