docker_container_windows_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. /*
  2. Copyright 2019 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package dockershim
  14. import (
  15. "bytes"
  16. "fmt"
  17. "regexp"
  18. "testing"
  19. dockertypes "github.com/docker/docker/api/types"
  20. "github.com/stretchr/testify/assert"
  21. "github.com/stretchr/testify/require"
  22. "golang.org/x/sys/windows/registry"
  23. runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
  24. )
  25. type dummyRegistryKey struct {
  26. setStringValueError error
  27. setStringValueArgs [][]string
  28. deleteValueFunc func(name string) error
  29. deleteValueArgs []string
  30. readValueNamesError error
  31. readValueNamesReturn []string
  32. readValueNamesArgs []int
  33. closed bool
  34. }
  35. func (k *dummyRegistryKey) SetStringValue(name, value string) error {
  36. k.setStringValueArgs = append(k.setStringValueArgs, []string{name, value})
  37. return k.setStringValueError
  38. }
  39. func (k *dummyRegistryKey) DeleteValue(name string) error {
  40. k.deleteValueArgs = append(k.deleteValueArgs, name)
  41. if k.deleteValueFunc == nil {
  42. return nil
  43. }
  44. return k.deleteValueFunc(name)
  45. }
  46. func (k *dummyRegistryKey) ReadValueNames(n int) ([]string, error) {
  47. k.readValueNamesArgs = append(k.readValueNamesArgs, n)
  48. return k.readValueNamesReturn, k.readValueNamesError
  49. }
  50. func (k *dummyRegistryKey) Close() error {
  51. k.closed = true
  52. return nil
  53. }
  54. func TestApplyGMSAConfig(t *testing.T) {
  55. dummyCredSpec := "test cred spec contents"
  56. randomBytes := []byte{0x19, 0x0, 0x25, 0x45, 0x18, 0x52, 0x9e, 0x2a, 0x3d, 0xed, 0xb8, 0x5c, 0xde, 0xc0, 0x3c, 0xe2, 0x70, 0x55, 0x96, 0x47, 0x45, 0x9a, 0xb5, 0x31, 0xf0, 0x7a, 0xf5, 0xeb, 0x1c, 0x54, 0x95, 0xfd, 0xa7, 0x9, 0x43, 0x5c, 0xe8, 0x2a, 0xb8, 0x9c}
  57. expectedHex := "1900254518529e2a3dedb85cdec03ce270559647459ab531f07af5eb1c5495fda709435ce82ab89c"
  58. expectedValueName := "k8s-cred-spec-" + expectedHex
  59. containerConfigWithGMSAAnnotation := &runtimeapi.ContainerConfig{
  60. Windows: &runtimeapi.WindowsContainerConfig{
  61. SecurityContext: &runtimeapi.WindowsContainerSecurityContext{
  62. CredentialSpec: dummyCredSpec,
  63. },
  64. },
  65. }
  66. t.Run("happy path", func(t *testing.T) {
  67. key := &dummyRegistryKey{}
  68. defer setRegistryCreateKeyFunc(t, key)()
  69. defer setRandomReader(randomBytes)()
  70. createConfig := &dockertypes.ContainerCreateConfig{}
  71. cleanupInfo := &containerCleanupInfo{}
  72. err := applyGMSAConfig(containerConfigWithGMSAAnnotation, createConfig, cleanupInfo)
  73. assert.Nil(t, err)
  74. // the registry key should have been properly created
  75. if assert.Equal(t, 1, len(key.setStringValueArgs)) {
  76. assert.Equal(t, []string{expectedValueName, dummyCredSpec}, key.setStringValueArgs[0])
  77. }
  78. assert.True(t, key.closed)
  79. // the create config's security opt should have been populated
  80. if assert.NotNil(t, createConfig.HostConfig) {
  81. assert.Equal(t, createConfig.HostConfig.SecurityOpt, []string{"credentialspec=registry://" + expectedValueName})
  82. }
  83. // and the name of that value should have been saved to the cleanup info
  84. assert.Equal(t, expectedValueName, cleanupInfo.gMSARegistryValueName)
  85. })
  86. t.Run("happy path with a truly random string", func(t *testing.T) {
  87. defer setRegistryCreateKeyFunc(t, &dummyRegistryKey{})()
  88. createConfig := &dockertypes.ContainerCreateConfig{}
  89. cleanupInfo := &containerCleanupInfo{}
  90. err := applyGMSAConfig(containerConfigWithGMSAAnnotation, createConfig, cleanupInfo)
  91. assert.Nil(t, err)
  92. if assert.NotNil(t, createConfig.HostConfig) && assert.Equal(t, 1, len(createConfig.HostConfig.SecurityOpt)) {
  93. secOpt := createConfig.HostConfig.SecurityOpt[0]
  94. expectedPrefix := "credentialspec=registry://k8s-cred-spec-"
  95. assert.Equal(t, expectedPrefix, secOpt[:len(expectedPrefix)])
  96. hex := secOpt[len(expectedPrefix):]
  97. hexRegex := regexp.MustCompile("^[0-9a-f]{80}$")
  98. assert.True(t, hexRegex.MatchString(hex))
  99. assert.NotEqual(t, expectedHex, hex)
  100. assert.Equal(t, "k8s-cred-spec-"+hex, cleanupInfo.gMSARegistryValueName)
  101. }
  102. })
  103. t.Run("when there's an error generating the random value name", func(t *testing.T) {
  104. defer setRandomReader([]byte{})()
  105. err := applyGMSAConfig(containerConfigWithGMSAAnnotation, &dockertypes.ContainerCreateConfig{}, &containerCleanupInfo{})
  106. require.NotNil(t, err)
  107. assert.Contains(t, err.Error(), "error when generating gMSA registry value name: unable to generate random string")
  108. })
  109. t.Run("if there's an error opening the registry key", func(t *testing.T) {
  110. defer setRegistryCreateKeyFunc(t, &dummyRegistryKey{}, fmt.Errorf("dummy error"))()
  111. err := applyGMSAConfig(containerConfigWithGMSAAnnotation, &dockertypes.ContainerCreateConfig{}, &containerCleanupInfo{})
  112. require.NotNil(t, err)
  113. assert.Contains(t, err.Error(), "unable to open registry key")
  114. })
  115. t.Run("if there's an error writing to the registry key", func(t *testing.T) {
  116. key := &dummyRegistryKey{}
  117. key.setStringValueError = fmt.Errorf("dummy error")
  118. defer setRegistryCreateKeyFunc(t, key)()
  119. err := applyGMSAConfig(containerConfigWithGMSAAnnotation, &dockertypes.ContainerCreateConfig{}, &containerCleanupInfo{})
  120. if assert.NotNil(t, err) {
  121. assert.Contains(t, err.Error(), "unable to write into registry value")
  122. }
  123. assert.True(t, key.closed)
  124. })
  125. t.Run("if there is no GMSA annotation", func(t *testing.T) {
  126. createConfig := &dockertypes.ContainerCreateConfig{}
  127. err := applyGMSAConfig(&runtimeapi.ContainerConfig{}, createConfig, &containerCleanupInfo{})
  128. assert.Nil(t, err)
  129. assert.Nil(t, createConfig.HostConfig)
  130. })
  131. }
  132. func TestRemoveGMSARegistryValue(t *testing.T) {
  133. valueName := "k8s-cred-spec-1900254518529e2a3dedb85cdec03ce270559647459ab531f07af5eb1c5495fda709435ce82ab89c"
  134. cleanupInfoWithValue := &containerCleanupInfo{gMSARegistryValueName: valueName}
  135. t.Run("it does remove the registry value", func(t *testing.T) {
  136. key := &dummyRegistryKey{}
  137. defer setRegistryCreateKeyFunc(t, key)()
  138. err := removeGMSARegistryValue(cleanupInfoWithValue)
  139. assert.Nil(t, err)
  140. // the registry key should have been properly deleted
  141. if assert.Equal(t, 1, len(key.deleteValueArgs)) {
  142. assert.Equal(t, []string{valueName}, key.deleteValueArgs)
  143. }
  144. assert.True(t, key.closed)
  145. })
  146. t.Run("if there's an error opening the registry key", func(t *testing.T) {
  147. defer setRegistryCreateKeyFunc(t, &dummyRegistryKey{}, fmt.Errorf("dummy error"))()
  148. err := removeGMSARegistryValue(cleanupInfoWithValue)
  149. require.NotNil(t, err)
  150. assert.Contains(t, err.Error(), "unable to open registry key")
  151. })
  152. t.Run("if there's an error deleting from the registry key", func(t *testing.T) {
  153. key := &dummyRegistryKey{}
  154. key.deleteValueFunc = func(name string) error { return fmt.Errorf("dummy error") }
  155. defer setRegistryCreateKeyFunc(t, key)()
  156. err := removeGMSARegistryValue(cleanupInfoWithValue)
  157. if assert.NotNil(t, err) {
  158. assert.Contains(t, err.Error(), "unable to remove registry value")
  159. }
  160. assert.True(t, key.closed)
  161. })
  162. t.Run("if there's no registry value to be removed, it does nothing", func(t *testing.T) {
  163. key := &dummyRegistryKey{}
  164. defer setRegistryCreateKeyFunc(t, key)()
  165. err := removeGMSARegistryValue(&containerCleanupInfo{})
  166. assert.Nil(t, err)
  167. assert.Equal(t, 0, len(key.deleteValueArgs))
  168. })
  169. }
  170. func TestRemoveAllGMSARegistryValues(t *testing.T) {
  171. cred1 := "k8s-cred-spec-1900254518529e2a3dedb85cdec03ce270559647459ab531f07af5eb1c5495fda709435ce82ab89c"
  172. cred2 := "k8s-cred-spec-8891436007c795a904fdf77b5348e94305e4c48c5f01c47e7f65e980dc7edda85f112715891d65fd"
  173. cred3 := "k8s-cred-spec-2f11f1c9e4f8182fe13caa708bd42b2098c8eefc489d6cc98806c058ccbe4cb3703b9ade61ce59a1"
  174. cred4 := "k8s-cred-spec-dc532f189598a8220a1e538f79081eee979f94fbdbf8d37e36959485dee57157c03742d691e1fae2"
  175. t.Run("it removes the keys matching the k8s creds pattern", func(t *testing.T) {
  176. key := &dummyRegistryKey{readValueNamesReturn: []string{cred1, "other_creds", cred2}}
  177. defer setRegistryCreateKeyFunc(t, key)()
  178. errors := removeAllGMSARegistryValues()
  179. assert.Equal(t, 0, len(errors))
  180. assert.Equal(t, []string{cred1, cred2}, key.deleteValueArgs)
  181. assert.Equal(t, []int{0}, key.readValueNamesArgs)
  182. assert.True(t, key.closed)
  183. })
  184. t.Run("it ignores errors and does a best effort at removing all k8s creds", func(t *testing.T) {
  185. key := &dummyRegistryKey{
  186. readValueNamesReturn: []string{cred1, cred2, cred3, cred4},
  187. deleteValueFunc: func(name string) error {
  188. if name == cred1 || name == cred3 {
  189. return fmt.Errorf("dummy error")
  190. }
  191. return nil
  192. },
  193. }
  194. defer setRegistryCreateKeyFunc(t, key)()
  195. errors := removeAllGMSARegistryValues()
  196. assert.Equal(t, 2, len(errors))
  197. for _, err := range errors {
  198. assert.Contains(t, err.Error(), "unable to remove registry value")
  199. }
  200. assert.Equal(t, []string{cred1, cred2, cred3, cred4}, key.deleteValueArgs)
  201. assert.Equal(t, []int{0}, key.readValueNamesArgs)
  202. assert.True(t, key.closed)
  203. })
  204. t.Run("if there's an error opening the registry key", func(t *testing.T) {
  205. defer setRegistryCreateKeyFunc(t, &dummyRegistryKey{}, fmt.Errorf("dummy error"))()
  206. errors := removeAllGMSARegistryValues()
  207. require.Equal(t, 1, len(errors))
  208. assert.Contains(t, errors[0].Error(), "unable to open registry key")
  209. })
  210. t.Run("if it's unable to list the registry values", func(t *testing.T) {
  211. key := &dummyRegistryKey{readValueNamesError: fmt.Errorf("dummy error")}
  212. defer setRegistryCreateKeyFunc(t, key)()
  213. errors := removeAllGMSARegistryValues()
  214. if assert.Equal(t, 1, len(errors)) {
  215. assert.Contains(t, errors[0].Error(), "unable to list values under registry key")
  216. }
  217. assert.True(t, key.closed)
  218. })
  219. }
  220. // setRegistryCreateKeyFunc replaces the registryCreateKeyFunc package variable, and returns a function
  221. // to be called to revert the change when done with testing.
  222. func setRegistryCreateKeyFunc(t *testing.T, key *dummyRegistryKey, err ...error) func() {
  223. previousRegistryCreateKeyFunc := registryCreateKeyFunc
  224. registryCreateKeyFunc = func(baseKey registry.Key, path string, access uint32) (registryKey, bool, error) {
  225. // this should always be called with exactly the same arguments
  226. assert.Equal(t, registry.LOCAL_MACHINE, baseKey)
  227. assert.Equal(t, credentialSpecRegistryLocation, path)
  228. assert.Equal(t, uint32(registry.SET_VALUE), access)
  229. if len(err) > 0 {
  230. return nil, false, err[0]
  231. }
  232. return key, false, nil
  233. }
  234. return func() {
  235. registryCreateKeyFunc = previousRegistryCreateKeyFunc
  236. }
  237. }
  238. // setRandomReader replaces the randomReader package variable with a dummy reader that returns the provided
  239. // byte slice, and returns a function to be called to revert the change when done with testing.
  240. func setRandomReader(b []byte) func() {
  241. previousRandomReader := randomReader
  242. randomReader = bytes.NewReader(b)
  243. return func() {
  244. randomReader = previousRandomReader
  245. }
  246. }