image_policy_test.go RawHistoryFolder

1package domain_test
2
3import (
4	"testing"
5
6	"github.com/stretchr/testify/assert"
7	"github.com/stretchr/testify/require"
8
9	"github.com/bnema/gordon/internal/domain"
10)
11
12const testDigest202 = "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
13
14func TestImageSourcePolicy_DefaultRegistries(t *testing.T) {
15	policy := domain.ImageSourcePolicy{}
16	allowed := []string{
17		"nginx:1.25",
18		"docker.io/library/nginx:1.25",
19		"registry-1.docker.io/library/nginx@" + testDigest202,
20		"ghcr.io/example/app:1",
21		"quay.io/example/app:1",
22	}
23	for _, ref := range allowed {
24		require.NoError(t, policy.ValidateImageSource(ref), ref)
25	}
26	assert.ErrorIs(t, policy.ValidateImageSource("registry.example.com/team/app:1"), domain.ErrAppImageNotAllowed)
27}
28
29func TestImageSourcePolicy_AllowsExplicitPrivateRegistry(t *testing.T) {
30	policy := domain.ImageSourcePolicy{AllowedRegistries: []string{"REGISTRY.INTERNAL.:5000"}}
31	require.NoError(t, policy.ValidateImageSource("registry.internal:5000/team/app:1"))
32	assert.ErrorIs(t, policy.ValidateImageSource("registry.internal/team/app:1"), domain.ErrAppImageNotAllowed)
33}
34
35func TestImageSourcePolicy_EnforcesAllowlist(t *testing.T) {
36	policy := domain.ImageSourcePolicy{AllowedRegistries: []string{"registry.example.com"}}
37	require.NoError(t, policy.ValidateImageSource("registry.example.com/team/app:1.0"))
38	require.NoError(t, policy.ValidateImageSource("registry.example.com/team/app@"+testDigest202))
39	require.NoError(t, policy.ValidateImageSource("docker.io/library/nginx:1.0"))
40	assert.ErrorIs(t, policy.ValidateImageSource("other.example.com/team/app:1.0"), domain.ErrAppImageNotAllowed)
41}
42
43func TestImageSourcePolicy_AlwaysAllowsInstallationRegistry(t *testing.T) {
44	policy := domain.ImageSourcePolicy{
45		AllowedRegistries:    []string{"registry.example.com"},
46		InstallationRegistry: "gordon.example.com",
47	}
48	require.NoError(t, policy.ValidateImageSource("gordon.example.com/blog/web:1.4.2"))
49	require.NoError(t, policy.ValidateImageSource("gordon.example.com/blog/web@"+testDigest202))
50}
51
52func TestImageSourcePolicy_RequireDigest(t *testing.T) {
53	policy := domain.ImageSourcePolicy{
54		AllowedRegistries:    []string{"registry.example.com"},
55		RequireDigest:        true,
56		InstallationRegistry: "gordon.example.com",
57	}
58	require.NoError(t, policy.ValidateImageSource("registry.example.com/team/app@"+testDigest202))
59	require.NoError(t, policy.ValidateImageSource("gordon.example.com/team/app@"+testDigest202))
60	assert.ErrorIs(t, policy.ValidateImageSource("registry.example.com/team/app:1.0"), domain.ErrAppImageNotAllowed)
61	assert.ErrorIs(t, policy.ValidateImageSource("gordon.example.com/team/app:1.0"), domain.ErrAppImageNotAllowed)
62	assert.ErrorIs(t, policy.ValidateImageSource("nginx"), domain.ErrAppImageNotAllowed)
63}
64
65func TestImageSourcePolicy_RejectsMalformedDigests(t *testing.T) {
66	policy := domain.ImageSourcePolicy{InstallationRegistry: "gordon.example.com"}
67	for _, digest := range []string{
68		"sha256:short",
69		"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag",
70		"sha512:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
71	} {
72		assert.ErrorIs(t, policy.ValidateImageSource("gordon.example.com/team/app@"+digest), domain.ErrAppImageNotAllowed, digest)
73	}
74}
75
76func TestImageSourcePolicy_CanonicalizesHostAndDefaultPort(t *testing.T) {
77	policy := domain.ImageSourcePolicy{AllowedRegistries: []string{"registry.example.com"}}
78	require.NoError(t, policy.ValidateImageSource("REGISTRY.EXAMPLE.COM./team/app:1"))
79	require.NoError(t, policy.ValidateImageSource("registry.example.com:443/team/app:1"))
80}
81
82func TestImageSourcePolicy_RejectsMalformedAllowlistEntries(t *testing.T) {
83	for _, entry := range []string{"https://registry.example.com", "user@registry.example.com", "registry.example.com:", "registry.example.com:0443", "registry.example.com:443:80"} {
84		policy := domain.ImageSourcePolicy{AllowedRegistries: []string{entry}}
85		assert.ErrorIs(t, policy.Validate(), domain.ErrAppImageNotAllowed, entry)
86	}
87}
88
89func TestImageSourcePolicy_RejectsMalformedReferences(t *testing.T) {
90	policy := domain.ImageSourcePolicy{AllowedRegistries: []string{"registry.example.com"}}
91	refs := []string{"", "   ", "registry.example.com/BadRepo:1.0", "https://registry.example.com/app:1", "user@registry.example.com/app:1", "registry.example.com:/app:1", "registry.example.com:0443/app:1", "registry.example.com:443:80/app:1", "[::1/app:1"}
92	for _, ref := range refs {
93		assert.ErrorIs(t, policy.ValidateImageSource(ref), domain.ErrAppImageNotAllowed, ref)
94	}
95}
96
97func TestImageSourcePolicy_IsInstallationImageDockerHubAlias(t *testing.T) {
98	cases := []struct {
99		name         string
100		installation string
101		ref          string
102		want         bool
103	}{
104		{"implicit docker.io ref matches docker.io installation", "docker.io", "library/nginx:1", true},
105		{"registry-1 ref matches docker.io installation", "docker.io", "registry-1.docker.io/library/nginx:1", true},
106		{"docker.io ref matches registry-1 installation", "registry-1.docker.io", "docker.io/library/nginx:1", true},
107		{"installation registry is not docker.io", "gordon.example.com", "docker.io/library/nginx:1", false},
108		{"foreign ref does not match docker.io installation", "docker.io", "ghcr.io/example/app:1", false},
109	}
110	for _, tc := range cases {
111		t.Run(tc.name, func(t *testing.T) {
112			policy := domain.ImageSourcePolicy{InstallationRegistry: tc.installation}
113			assert.Equal(t, tc.want, policy.IsInstallationImage(tc.ref))
114		})
115	}
116}
117
118func TestIsLocalOrPrivateHost(t *testing.T) {
119	local := []string{"localhost", "sub.localhost", "127.0.0.1", "127.0.0.1:5000", "[::1]:5000", "10.0.0.1", "192.168.0.1", "172.31.0.1", "fc00::1", "0.0.0.0", "169.254.169.254"}
120	for _, host := range local {
121		assert.True(t, domain.IsLocalOrPrivateHost(host), "host %q should be local/private", host)
122	}
123	remote := []string{"registry.example.com", "registry.example.com:5000", "8.8.8.8", "docker.io", "2001:4860:4860::8888"}
124	for _, host := range remote {
125		assert.False(t, domain.IsLocalOrPrivateHost(host), "host %q should be public", host)
126	}
127}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close