1 package grub
2
3 import (
4 "context"
5 _ "embed"
6 "os"
7 "path/filepath"
8 "strings"
9 "testing"
10
11 "github.com/stretchr/testify/require"
12 "gotest.tools/v3/fs"
13 "sigs.k8s.io/controller-runtime/pkg/client/fake"
14
15 corev1 "k8s.io/api/core/v1"
16
17 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
18
19 kruntime "k8s.io/apimachinery/pkg/runtime"
20 utilruntime "k8s.io/apimachinery/pkg/util/runtime"
21 clientgoscheme "k8s.io/client-go/kubernetes/scheme"
22
23 grubsecret "edge-infra.dev/pkg/sds/clustersecrets/grub"
24 grubfs "edge-infra.dev/pkg/sds/clustersecrets/grub/filesystem"
25
26 v1ien "edge-infra.dev/pkg/sds/ien/k8s/apis/v1"
27
28 "edge-infra.dev/pkg/sds"
29 cc "edge-infra.dev/pkg/sds/clustersecrets/common"
30 "edge-infra.dev/pkg/sds/ien/k8s/controllers/nodeagent/config"
31 )
32
33 var p = Plugin{}
34
35
36 var contents []byte
37
38 type testCase struct {
39 secret *corev1.Secret
40 }
41
42 var tests = []testCase{
43 {
44 toSecret(
45 "b46899129f0378151909c9a5120112b7",
46 "34747062456d77436165724365447537",
47 "eH8DAQEGU2VjcmV0Af+AAAEIAQlQcmluY2lwYWwBDAABCkl0ZXJhdGlvbnMBBAABBFNhbHQBDAABBEhhc2gBDAABCEZ1bmN0aW9uAQwAAQhIYXNoVHlwZQEMAAEKR2VuZXJhdGlvbgEEAAEIUm90YXRpb24BAgAAAGj/gAEIcmVjb3ZlcnkB/Ql14AEgMzQ3NDcwNjI0NTZkNzc0MzYxNjU3MjQzNjU0NDc1MzcBIGI0Njg5OTEyOWYwMzc4MTUxOTA5YzlhNTEyMDExMmI3AQZwYmtkZjIBBnNoYTUxMgECAA==",
48 ),
49 },
50 }
51
52 var nodeHostname = "node-1"
53
54 func TestMain(_ *testing.M) {
55 mount = false
56 os.Setenv("HOSTNAME", nodeHostname)
57 }
58
59 func TestGrubLegacyApply(t *testing.T) {
60 grubCfgPath, cleanup := setupFilesystem(t, false)
61 defer cleanup()
62
63 for _, test := range tests {
64 c := fake.NewClientBuilder().WithScheme(createScheme()).WithObjects(test.secret).Build()
65
66 ctx := context.Background()
67 _, err := p.Reconcile(ctx, &v1ien.IENode{}, config.Data{Client: c})
68 require.NoError(t, err)
69
70 secret, err := grubsecret.FromSecret(test.secret)
71 require.NoError(t, err)
72
73 actualContents, err := os.ReadFile(grubCfgPath)
74 require.NoError(t, err)
75
76 requireGrubUserConfigured(t, secret, actualContents)
77 }
78 }
79
80 func TestGrubEfiApply(t *testing.T) {
81 grubCfgPath, cleanup := setupFilesystem(t, true)
82 defer cleanup()
83
84 for _, test := range tests {
85 c := fake.NewClientBuilder().WithScheme(createScheme()).WithObjects(test.secret).Build()
86
87 ctx := context.Background()
88 _, err := p.Reconcile(ctx, &v1ien.IENode{}, config.Data{Client: c})
89 require.NoError(t, err)
90
91 secret, err := grubsecret.FromSecret(test.secret)
92 require.NoError(t, err)
93
94 actualContents, err := os.ReadFile(grubCfgPath)
95 require.NoError(t, err)
96
97 requireGrubUserConfigured(t, secret, actualContents)
98 }
99 }
100
101 func setupFilesystem(t *testing.T, isEfi bool) (string, func()) {
102 rootMountPath := "host-boot"
103 rootDir := fs.NewDir(t, rootMountPath)
104 cleanupFunc := func() {
105 rootDir.Remove()
106 }
107 rootMountPath = rootDir.Path()
108
109 subPath := grubfs.LegacyPath
110 if isEfi {
111 subPath = grubfs.EfiPath
112 }
113 err := os.MkdirAll(filepath.Join(rootMountPath, subPath), 0744)
114 require.NoError(t, err)
115
116 grubCfgPath := filepath.Join(rootMountPath, subPath, grubfs.ConfigFilename)
117
118
119 err = os.WriteFile(grubCfgPath, contents, 0644)
120 require.NoError(t, err)
121
122 return grubCfgPath, cleanupFunc
123 }
124
125 func toSecret(hash, salt, serialised string) *corev1.Secret {
126 return &corev1.Secret{
127 ObjectMeta: metav1.ObjectMeta{
128 Name: grubsecret.HashedSecretName,
129 Namespace: sds.Namespace,
130 },
131 Data: map[string][]byte{
132 grubsecret.SecretKey: []byte(serialised),
133 cc.PrincipalKey: []byte(grubsecret.Username),
134 cc.HashFunctionKey: []byte("pbkdf2"),
135 cc.HashTypeKey: []byte("sha512"),
136 cc.SaltKey: []byte(salt),
137 cc.HashKey: []byte(hash),
138 cc.HashIterationsKey: []byte("310000"),
139 cc.SecretVersionKey: []byte("2"),
140 },
141 }
142 }
143
144 func requireGrubUserConfigured(t *testing.T, secret cc.Secret, cfgContents []byte) {
145 expectedEntry := secret.String()
146
147 entrySplit := strings.Split(expectedEntry, "\n")
148 expectedUsr := entrySplit[0]
149 expectedPwd := entrySplit[1]
150 expectedExportUsr := entrySplit[2]
151
152 usrFound, pwdFound, exportUsrFound := false, false, false
153 for _, line := range strings.Split(string(cfgContents), "\n") {
154 switch {
155 case strings.Contains(line, "set superusers="):
156 usrFound = true
157 require.Equal(t, line, expectedUsr)
158 case strings.Contains(line, "password_pbkdf2"):
159 pwdFound = true
160 require.Equal(t, line, expectedPwd)
161 case strings.Contains(line, "export superusers"):
162 exportUsrFound = true
163 require.Equal(t, line, expectedExportUsr)
164 }
165 }
166 require.True(t, usrFound)
167 require.True(t, pwdFound)
168 require.True(t, exportUsrFound)
169 }
170
171 func createScheme() *kruntime.Scheme {
172 scheme := kruntime.NewScheme()
173 utilruntime.Must(clientgoscheme.AddToScheme(scheme))
174 utilruntime.Must(v1ien.AddToScheme(scheme))
175 return scheme
176 }
177
View as plain text