this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

cmd/cue: wire up OCI authorization

This makes the cue command aware of authorization
conventions when talking to registries: specifically,
it will use the docker configuration file to find authorization
information.

We add test cases in cmd/cue for three different scenarios:
- a module that requires auth
- split-horizon modules requiring different auth for each
- when the auth file is corrupt.

Signed-off-by: Roger Peppe <rogpeppe@gmail.com>
Change-Id: Id85caa1796fe61729eef89d5e88ca57ceafa23b6
Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1171747
Reviewed-by: Daniel Martí <mvdan@mvdan.cc>
TryBot-Result: CUEcueckoo <cueckoo@cuelang.org>
Unity-Result: CUE porcuepine <cue.porcuepine@gmail.com>

+384 -27
+24 -1
cmd/cue/cmd/registry.go
··· 3 3 import ( 4 4 "fmt" 5 5 "os" 6 + "sync" 6 7 7 8 "cuelabs.dev/go/oci/ociregistry" 9 + "cuelabs.dev/go/oci/ociregistry/ociauth" 8 10 "cuelabs.dev/go/oci/ociregistry/ociclient" 9 11 10 12 "cuelang.org/go/internal/cueexperiment" ··· 25 27 if err != nil { 26 28 return nil, fmt.Errorf("bad value for $CUE_REGISTRY: %v", err) 27 29 } 30 + // If the user isn't doing anything that requires a registry, we 31 + // shouldn't complain about reading a bad configuration file, 32 + // so check only when required. 33 + var auth ociauth.Authorizer 34 + var authErr error 35 + var authOnce sync.Once 36 + 28 37 return modmux.New(resolver, func(host string, insecure bool) (ociregistry.Interface, error) { 38 + authOnce.Do(func() { 39 + config, err := ociauth.Load(nil) 40 + if err != nil { 41 + authErr = fmt.Errorf("cannot load OCI auth configuration: %v", err) 42 + return 43 + } 44 + auth = ociauth.NewStdAuthorizer(ociauth.StdAuthorizerParams{ 45 + Config: config, 46 + }) 47 + }) 48 + if authErr != nil { 49 + return nil, authErr 50 + } 29 51 return ociclient.New(host, &ociclient.Options{ 30 - Insecure: insecure, 52 + Insecure: insecure, 53 + Authorizer: auth, 31 54 }) 32 55 }), nil 33 56 }
+21
cmd/cue/cmd/script_test.go
··· 95 95 Dir: filepath.Join("testdata", "script"), 96 96 UpdateScripts: cuetest.UpdateGoldenFiles, 97 97 RequireExplicitExec: true, 98 + Cmds: map[string]func(ts *testscript.TestScript, neg bool, args []string){ 99 + // env-fill rewrites its argument files to replace any environment variable 100 + // references with their values, using the same algorithm as cmpenv. 101 + "env-fill": func(ts *testscript.TestScript, neg bool, args []string) { 102 + if neg || len(args) == 0 { 103 + ts.Fatalf("usage: env-fill args...") 104 + } 105 + for _, arg := range args { 106 + path := ts.MkAbs(arg) 107 + data := ts.ReadFile(path) 108 + data = tsExpand(ts, data) 109 + ts.Check(os.WriteFile(path, []byte(data), 0o666)) 110 + } 111 + }, 112 + }, 98 113 Setup: func(e *testscript.Env) error { 99 114 // Set up a home dir within work dir with a . prefix so that the 100 115 // Go/CUE pattern ./... does not descend into it. ··· 246 261 return 0 247 262 }, 248 263 })) 264 + } 265 + 266 + func tsExpand(ts *testscript.TestScript, s string) string { 267 + return os.Expand(s, func(key string) string { 268 + return ts.Getenv(key) 269 + }) 249 270 } 250 271 251 272 // homeEnvName extracts the logic from os.UserHomeDir to get the
+54
cmd/cue/cmd/testdata/script/registry_auth.txtar
··· 1 + # Test that we can authenticate to a registry with basic auth. 2 + 3 + env DOCKER_CONFIG=$WORK/dockerconfig 4 + env-fill $DOCKER_CONFIG/config.json 5 + exec cue export . 6 + cmp stdout expect-stdout 7 + 8 + # Sanity-check that we get an error when using the wrong password. 9 + env-fill dockerconfig/badpassword.json 10 + cp dockerconfig/badpassword.json dockerconfig/config.json 11 + ! exec cue export . 12 + stderr 'instance: cannot resolve dependencies: example.com/e@v0.0.1: module example.com/e@v0.0.1: error response: 401 Unauthorized: authentication required' 13 + 14 + -- dockerconfig/config.json -- 15 + { 16 + "auths": { 17 + "${DEBUG_REGISTRY_HOST}": { 18 + "username": "someone", 19 + "password": "something" 20 + } 21 + } 22 + } 23 + -- dockerconfig/badpassword.json -- 24 + { 25 + "auths": { 26 + "${DEBUG_REGISTRY_HOST}": { 27 + "username": "someone", 28 + "password": "wrongpassword" 29 + } 30 + } 31 + } 32 + -- expect-stdout -- 33 + "ok" 34 + -- main.cue -- 35 + package main 36 + import "example.com/e" 37 + 38 + e.foo 39 + 40 + -- cue.mod/module.cue -- 41 + module: "test.org" 42 + deps: "example.com/e": v: "v0.0.1" 43 + -- _registry/auth.json -- 44 + {"username": "someone", "password": "something"} 45 + -- _registry_prefix -- 46 + somewhere/other 47 + -- _registry/example.com_e_v0.0.1/cue.mod/module.cue -- 48 + module: "example.com/e@v0" 49 + 50 + -- _registry/example.com_e_v0.0.1/main.cue -- 51 + package e 52 + 53 + foo: "ok" 54 +
+48
cmd/cue/cmd/testdata/script/registry_lazy_config.txtar
··· 1 + # Test that a bad docker config file isn't a problem until we actually 2 + # need to talk to a registry. 3 + 4 + # Initially the code doesn't use any modules, so there should 5 + # be no need to use a registry. 6 + env DOCKER_CONFIG=$WORK/dockerconfig 7 + exec cue export . 8 + cmp stdout expect-stdout 9 + 10 + # The new code uses modules, so we should get a warning 11 + # when the config file is read. 12 + cp OTHER/main.cue main.cue 13 + cp OTHER/cue.mod/module.cue cue.mod/module.cue 14 + ! exec cue export . 15 + stderr 'instance: cannot resolve dependencies: example.com/e@v0.0.1: module example.com/e@v0.0.1: cannot make client: cannot load OCI auth configuration: invalid config file ".*config.json": decode failed: .*' 16 + 17 + -- dockerconfig/config.json -- 18 + should be JSON but isn't 19 + -- expect-stdout -- 20 + "ok" 21 + -- main.cue -- 22 + package main 23 + "ok" 24 + 25 + -- cue.mod/module.cue -- 26 + module: "test.org" 27 + 28 + -- OTHER/main.cue -- 29 + package main 30 + import "example.com/e" 31 + e.foo 32 + 33 + -- OTHER/cue.mod/module.cue -- 34 + module: "test.org" 35 + deps: "example.com/e": v: "v0.0.1" 36 + 37 + -- _registry/auth.json -- 38 + {"username": "someone", "password": "something"} 39 + -- _registry_prefix -- 40 + somewhere/other 41 + -- _registry/example.com_e_v0.0.1/cue.mod/module.cue -- 42 + module: "example.com/e@v0" 43 + 44 + -- _registry/example.com_e_v0.0.1/main.cue -- 45 + package e 46 + 47 + foo: "ok" 48 +
+152
cmd/cue/cmd/testdata/script/registry_mux_auth.txtar
··· 1 + # Test that authorization works when there are two 2 + # registries that both require different credentials. 3 + 4 + env CUE_REGISTRY=${CUE_REGISTRY1},baz.org=$CUE_REGISTRY2 5 + env DOCKER_CONFIG=$WORK/dockerconfig 6 + env-fill $DOCKER_CONFIG/config.json 7 + exec cue eval . 8 + cmp stdout expect-stdout 9 + -- expect-stdout -- 10 + main: "main" 11 + "foo.com/bar/hello@v0": "v0.2.3" 12 + "bar.com@v0": "v0.5.0" 13 + "baz.org@v0": "v0.10.1 in registry2" 14 + "example.com@v0": "v0.0.1" 15 + -- dockerconfig/config.json -- 16 + { 17 + "auths": { 18 + "${DEBUG_REGISTRY1_HOST}": { 19 + "username": "registry1user", 20 + "password": "registry1password" 21 + }, 22 + "${DEBUG_REGISTRY2_HOST}": { 23 + "username": "registry2user", 24 + "password": "registry2password" 25 + } 26 + } 27 + } 28 + -- cue.mod/module.cue -- 29 + module: "main.org" 30 + 31 + deps: "example.com@v0": v: "v0.0.1" 32 + 33 + -- main.cue -- 34 + package main 35 + import "example.com@v0:main" 36 + 37 + main 38 + 39 + -- _registry1/auth.json -- 40 + {"username": "registry1user", "password": "registry1password"} 41 + -- _registry1/example.com_v0.0.1/cue.mod/module.cue -- 42 + module: "example.com@v0" 43 + deps: { 44 + "foo.com/bar/hello@v0": v: "v0.2.3" 45 + "bar.com@v0": v: "v0.5.0" 46 + } 47 + 48 + -- _registry1/example.com_v0.0.1/top.cue -- 49 + package main 50 + 51 + // Note: import without a major version takes 52 + // the major version from the module.cue file. 53 + import a "foo.com/bar/hello" 54 + a 55 + main: "main" 56 + "example.com@v0": "v0.0.1" 57 + 58 + -- _registry1/foo.com_bar_hello_v0.2.3/cue.mod/module.cue -- 59 + module: "foo.com/bar/hello@v0" 60 + deps: { 61 + "bar.com@v0": v: "v0.0.2" 62 + "baz.org@v0": v: "v0.10.1" 63 + } 64 + 65 + -- _registry1/foo.com_bar_hello_v0.2.3/x.cue -- 66 + package hello 67 + import ( 68 + a "bar.com/bar@v0" 69 + b "baz.org@v0:baz" 70 + ) 71 + "foo.com/bar/hello@v0": "v0.2.3" 72 + a 73 + b 74 + 75 + 76 + -- _registry1/bar.com_v0.0.2/cue.mod/module.cue -- 77 + module: "bar.com@v0" 78 + deps: "baz.org@v0": v: "v0.0.2" 79 + 80 + -- _registry1/bar.com_v0.0.2/bar/x.cue -- 81 + package bar 82 + import a "baz.org@v0:baz" 83 + "bar.com@v0": "v0.0.2" 84 + a 85 + 86 + 87 + -- _registry1/bar.com_v0.5.0/cue.mod/module.cue -- 88 + module: "bar.com@v0" 89 + deps: "baz.org@v0": v: "v0.5.0" 90 + 91 + -- _registry1/bar.com_v0.5.0/bar/x.cue -- 92 + package bar 93 + import a "baz.org@v0:baz" 94 + "bar.com@v0": "v0.5.0" 95 + a 96 + 97 + 98 + -- _registry1/baz.org_v0.0.2/cue.mod/module.cue -- 99 + module: "baz.org@v0" 100 + 101 + -- _registry1/baz.org_v0.0.2/baz.cue -- 102 + package baz 103 + "baz.org@v0": "v0.0.2" 104 + 105 + -- _registry1/baz.org_v0.1.2/cue.mod/module.cue -- 106 + module: "baz.org@v0" 107 + 108 + -- _registry1/baz.org_v0.1.2/baz.cue -- 109 + package baz 110 + "baz.org@v0": "v0.1.2" 111 + 112 + 113 + -- _registry1/baz.org_v0.5.0/cue.mod/module.cue -- 114 + module: "baz.org@v0" 115 + 116 + -- _registry1/baz.org_v0.5.0/baz.cue -- 117 + package baz 118 + "baz.org@v0": "v0.5.0" 119 + 120 + -- _registry1/baz.org_v0.10.1/cue.mod/module.cue -- 121 + module: "baz.org@v0" 122 + 123 + -- _registry1/baz.org_v0.10.1/baz.cue -- 124 + package baz 125 + "baz.org@v0": "v0.10.1" 126 + 127 + -- _registry2/auth.json -- 128 + {"username": "registry2user", "password": "registry2password"} 129 + 130 + -- _registry2/baz.org_v0.0.2/cue.mod/module.cue -- 131 + module: "baz.org@v0" 132 + 133 + -- _registry2/baz.org_v0.0.2/baz.cue -- 134 + package baz 135 + "baz.org@v0": "v0.0.2" 136 + 137 + -- _registry2/baz.org_v0.1.2/cue.mod/module.cue -- 138 + module: "baz.org@v0" 139 + 140 + -- _registry2/baz.org_v0.5.0/cue.mod/module.cue -- 141 + module: "baz.org@v0" 142 + 143 + -- _registry2/baz.org_v0.5.0/baz.cue -- 144 + package baz 145 + "baz.org@v0": "v0.5.0 in registry2" 146 + 147 + -- _registry2/baz.org_v0.10.1/cue.mod/module.cue -- 148 + module: "baz.org@v0" 149 + 150 + -- _registry2/baz.org_v0.10.1/baz.cue -- 151 + package baz 152 + "baz.org@v0": "v0.10.1 in registry2"
+6 -5
go.mod
··· 3 3 go 1.20 4 4 5 5 require ( 6 - cuelabs.dev/go/oci/ociregistry v0.0.0-20231004130125-2c3ad8a6ecd3 6 + cuelabs.dev/go/oci/ociregistry v0.0.0-20231103182354-93e78c079a13 7 7 github.com/cockroachdb/apd/v3 v3.2.1 8 8 github.com/emicklei/proto v1.10.0 9 9 github.com/go-quicktest/qt v1.101.0 ··· 19 19 github.com/spf13/cobra v1.7.0 20 20 github.com/spf13/pflag v1.0.5 21 21 github.com/tetratelabs/wazero v1.0.2 22 - golang.org/x/mod v0.12.0 23 - golang.org/x/net v0.15.0 22 + golang.org/x/mod v0.13.0 23 + golang.org/x/net v0.16.0 24 24 golang.org/x/text v0.13.0 25 - golang.org/x/tools v0.13.0 25 + golang.org/x/tools v0.14.0 26 26 gopkg.in/yaml.v3 v3.0.1 27 27 ) 28 28 ··· 30 30 github.com/inconshreveable/mousetrap v1.1.0 // indirect 31 31 github.com/kr/text v0.2.0 // indirect 32 32 github.com/mitchellh/go-wordwrap v1.0.1 // indirect 33 - golang.org/x/sys v0.12.0 // indirect 33 + golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect 34 + golang.org/x/sys v0.13.0 // indirect 34 35 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect 35 36 )
+13 -13
go.sum
··· 1 - cuelabs.dev/go/oci/ociregistry v0.0.0-20230928144906-bef4f4e03886 h1:v4I5Vu5DMkQXGaKAYun/2eFWQZ3Yg8IB3CSxyBee5ww= 2 - cuelabs.dev/go/oci/ociregistry v0.0.0-20230928144906-bef4f4e03886/go.mod h1:oqwWmDcccWVB2yC2eCHFNrQR44/AVB7gHOwtBsWMo0g= 3 - cuelabs.dev/go/oci/ociregistry v0.0.0-20231004130125-2c3ad8a6ecd3 h1:jJJsm3hgxosEoI5wXrwt8mv21j23vInE3owbSYKhR2c= 4 - cuelabs.dev/go/oci/ociregistry v0.0.0-20231004130125-2c3ad8a6ecd3/go.mod h1:oqwWmDcccWVB2yC2eCHFNrQR44/AVB7gHOwtBsWMo0g= 1 + cuelabs.dev/go/oci/ociregistry v0.0.0-20231103182354-93e78c079a13 h1:zkiIe8AxZ/kDjqQN+mDKc5BxoVJOqioSdqApjc+eB1I= 2 + cuelabs.dev/go/oci/ociregistry v0.0.0-20231103182354-93e78c079a13/go.mod h1:XGKYSMtsJWfqQYPwq51ZygxAPqpEUj/9bdg16iDPTAA= 5 3 github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= 6 4 github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= 7 5 github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= ··· 45 43 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 46 44 github.com/tetratelabs/wazero v1.0.2 h1:lpwL5zczFHk2mxKur98035Gig+Z3vd9JURk6lUdZxXY= 47 45 github.com/tetratelabs/wazero v1.0.2/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ= 48 - golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= 49 - golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 50 - golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= 51 - golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= 52 - golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= 53 - golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= 54 - golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 46 + golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= 47 + golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= 48 + golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= 49 + golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 50 + golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos= 51 + golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= 52 + golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= 53 + golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= 54 + golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 55 55 golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= 56 56 golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 57 - golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= 58 - golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 57 + golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= 58 + golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= 59 59 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 60 60 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 61 61 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+10 -2
internal/mod/modmux/mux.go
··· 116 116 return cr.PushBlob(ctx, repo, desc, rd) 117 117 } 118 118 119 - func (r *registry) PushBlobChunked(ctx context.Context, repo string, id string, chunkSize int) (ociregistry.BlobWriter, error) { 119 + func (r *registry) PushBlobChunked(ctx context.Context, repo string, chunkSize int) (ociregistry.BlobWriter, error) { 120 120 cr, repo, err := r.resolve(repo) 121 121 if err != nil { 122 122 return nil, err 123 123 } 124 - return cr.PushBlobChunked(ctx, repo, id, chunkSize) 124 + return cr.PushBlobChunked(ctx, repo, chunkSize) 125 + } 126 + 127 + func (r *registry) PushBlobChunkedResume(ctx context.Context, repo, id string, offset int64, chunkSize int) (ociregistry.BlobWriter, error) { 128 + cr, repo, err := r.resolve(repo) 129 + if err != nil { 130 + return nil, err 131 + } 132 + return cr.PushBlobChunkedResume(ctx, repo, id, offset, chunkSize) 125 133 } 126 134 127 135 func (r *registry) MountBlob(ctx context.Context, fromRepo, toRepo string, digest ociregistry.Digest) (ociregistry.Descriptor, error) {
+56 -6
internal/registrytest/registry.go
··· 3 3 import ( 4 4 "bytes" 5 5 "context" 6 + "encoding/json" 6 7 "errors" 7 8 "fmt" 8 9 "io" 9 10 "io/fs" 11 + "net/http" 10 12 "net/http/httptest" 11 13 "net/url" 12 14 "strings" ··· 24 26 "cuelang.org/go/internal/mod/zip" 25 27 ) 26 28 29 + // AuthConfig specifies authorization requirements for the server. 30 + // Currently it only supports basic auth. 31 + type AuthConfig struct { 32 + Username string `json:"username"` 33 + Password string `json:"password"` 34 + } 35 + 27 36 // New starts a registry instance that serves modules found inside fsys. 28 37 // It serves the OCI registry protocol. 29 38 // If prefix is non-empty, all module paths will be prefixed by that, ··· 33 42 // slashes in path have been replaced with underscores and should 34 43 // contain a cue.mod/module.cue file holding the module info. 35 44 // 45 + // If there's a file named auth.json in the root directory, 46 + // it will cause access to the server to be gated by the 47 + // specified authorization. See the AuthConfig type for 48 + // details. 49 + // 36 50 // The Registry should be closed after use. 37 51 func New(fsys fs.FS, prefix string) (*Registry, error) { 38 52 r := ocimem.New() 39 53 client := modregistry.NewClient(ocifilter.Sub(r, prefix)) 40 54 41 - mods, err := getModules(fsys) 55 + mods, authConfigData, err := getModules(fsys) 42 56 if err != nil { 43 57 return nil, fmt.Errorf("invalid modules: %v", err) 44 58 } 59 + 45 60 if err := pushContent(client, mods); err != nil { 46 61 return nil, fmt.Errorf("cannot push modules: %v", err) 47 62 } 48 - srv := httptest.NewServer(ociserver.New(r, nil)) 63 + var handler http.Handler = ociserver.New(r, nil) 64 + if authConfigData != nil { 65 + var cfg AuthConfig 66 + if err := json.Unmarshal(authConfigData, &cfg); err != nil { 67 + return nil, fmt.Errorf("invalid auth.json: %v", err) 68 + } 69 + handler = authMiddleware(handler, &cfg) 70 + } 71 + srv := httptest.NewServer(handler) 49 72 u, err := url.Parse(srv.URL) 50 73 if err != nil { 51 74 return nil, err ··· 56 79 }, nil 57 80 } 58 81 82 + func authMiddleware(handler http.Handler, cfg *AuthConfig) http.Handler { 83 + if cfg.Username == "" { 84 + return handler 85 + } 86 + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 87 + if req.Header.Get("Authorization") == "" { 88 + w.Header().Set("Www-Authenticate", "Basic service=registry") 89 + http.Error(w, "no credentials", http.StatusUnauthorized) 90 + return 91 + } 92 + username, password, ok := req.BasicAuth() 93 + if !ok || username != cfg.Username || password != cfg.Password { 94 + http.Error(w, "invalid credentials", http.StatusUnauthorized) 95 + return 96 + } 97 + handler.ServeHTTP(w, req) 98 + }) 99 + } 100 + 59 101 func pushContent(client *modregistry.Client, mods map[module.Version]*moduleContent) error { 60 102 pushed := make(map[module.Version]bool) 61 103 for v := range mods { ··· 114 156 modules []*moduleContent 115 157 } 116 158 117 - func getModules(fsys fs.FS) (map[module.Version]*moduleContent, error) { 159 + func getModules(fsys fs.FS) (map[module.Version]*moduleContent, []byte, error) { 160 + var authConfig []byte 118 161 ctx := cuecontext.New() 119 162 modules := make(map[string]*moduleContent) 120 163 if err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error { ··· 129 172 if d.IsDir() { 130 173 return nil // we're only interested in regular files, not their parent directories 131 174 } 175 + if path == "auth.json" { 176 + authConfig, err = fs.ReadFile(fsys, path) 177 + if err != nil { 178 + return err 179 + } 180 + return nil 181 + } 132 182 modver, rest, ok := strings.Cut(path, "/") 133 183 if !ok { 134 184 return fmt.Errorf("registry should only contain directories, but found regular file %q", path) ··· 148 198 }) 149 199 return nil 150 200 }); err != nil { 151 - return nil, err 201 + return nil, nil, err 152 202 } 153 203 for modver, content := range modules { 154 204 if err := content.init(ctx, modver); err != nil { 155 - return nil, fmt.Errorf("cannot initialize module %q: %v", modver, err) 205 + return nil, nil, fmt.Errorf("cannot initialize module %q: %v", modver, err) 156 206 } 157 207 } 158 208 byVer := map[module.Version]*moduleContent{} 159 209 for _, m := range modules { 160 210 byVer[m.version] = m 161 211 } 162 - return byVer, nil 212 + return byVer, authConfig, nil 163 213 } 164 214 165 215 type moduleContent struct {