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}