Skip to content

Commit b2ac3f7

Browse files
committed
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 <[email protected]> Change-Id: Id85caa1796fe61729eef89d5e88ca57ceafa23b6 Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1171747 Reviewed-by: Daniel Martí <[email protected]> TryBot-Result: CUEcueckoo <[email protected]> Unity-Result: CUE porcuepine <[email protected]>
1 parent 273602f commit b2ac3f7

File tree

9 files changed

+384
-27
lines changed

9 files changed

+384
-27
lines changed

cmd/cue/cmd/registry.go

+24-1
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ package cmd
33
import (
44
"fmt"
55
"os"
6+
"sync"
67

78
"cuelabs.dev/go/oci/ociregistry"
9+
"cuelabs.dev/go/oci/ociregistry/ociauth"
810
"cuelabs.dev/go/oci/ociregistry/ociclient"
911

1012
"cuelang.org/go/internal/cueexperiment"
@@ -25,9 +27,30 @@ func getRegistry() (ociregistry.Interface, error) {
2527
if err != nil {
2628
return nil, fmt.Errorf("bad value for $CUE_REGISTRY: %v", err)
2729
}
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+
2837
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+
}
2951
return ociclient.New(host, &ociclient.Options{
30-
Insecure: insecure,
52+
Insecure: insecure,
53+
Authorizer: auth,
3154
})
3255
}), nil
3356
}

cmd/cue/cmd/script_test.go

+21
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,21 @@ func TestScript(t *testing.T) {
9595
Dir: filepath.Join("testdata", "script"),
9696
UpdateScripts: cuetest.UpdateGoldenFiles,
9797
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+
},
98113
Setup: func(e *testscript.Env) error {
99114
// Set up a home dir within work dir with a . prefix so that the
100115
// Go/CUE pattern ./... does not descend into it.
@@ -248,6 +263,12 @@ func TestMain(m *testing.M) {
248263
}))
249264
}
250265

266+
func tsExpand(ts *testscript.TestScript, s string) string {
267+
return os.Expand(s, func(key string) string {
268+
return ts.Getenv(key)
269+
})
270+
}
271+
251272
// homeEnvName extracts the logic from os.UserHomeDir to get the
252273
// name of the environment variable that should be used when
253274
// setting the user's home directory
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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/[email protected]: module example.com/[email protected]: 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+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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/[email protected]: module example.com/[email protected]: 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+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
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"

go.mod

+6-5
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module cuelang.org/go
33
go 1.20
44

55
require (
6-
cuelabs.dev/go/oci/ociregistry v0.0.0-20231004130125-2c3ad8a6ecd3
6+
cuelabs.dev/go/oci/ociregistry v0.0.0-20231103182354-93e78c079a13
77
github.com/cockroachdb/apd/v3 v3.2.1
88
github.com/emicklei/proto v1.10.0
99
github.com/go-quicktest/qt v1.101.0
@@ -19,17 +19,18 @@ require (
1919
github.com/spf13/cobra v1.7.0
2020
github.com/spf13/pflag v1.0.5
2121
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
2424
golang.org/x/text v0.13.0
25-
golang.org/x/tools v0.13.0
25+
golang.org/x/tools v0.14.0
2626
gopkg.in/yaml.v3 v3.0.1
2727
)
2828

2929
require (
3030
github.com/inconshreveable/mousetrap v1.1.0 // indirect
3131
github.com/kr/text v0.2.0 // indirect
3232
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
3435
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
3536
)

0 commit comments

Comments
 (0)