github.com/jwhonce/docker@v0.6.7-0.20190327063223-da823cf3a5a3/daemon/oci_windows_test.go (about) 1 package daemon 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "os" 7 "path/filepath" 8 "strings" 9 "testing" 10 11 "gotest.tools/fs" 12 13 containertypes "github.com/docker/docker/api/types/container" 14 "github.com/docker/docker/container" 15 swarmagent "github.com/docker/swarmkit/agent" 16 swarmapi "github.com/docker/swarmkit/api" 17 "github.com/opencontainers/runtime-spec/specs-go" 18 "golang.org/x/sys/windows/registry" 19 "gotest.tools/assert" 20 ) 21 22 func TestSetWindowsCredentialSpecInSpec(t *testing.T) { 23 // we need a temp directory to act as the daemon's root 24 tmpDaemonRoot := fs.NewDir(t, t.Name()).Path() 25 defer func() { 26 assert.NilError(t, os.RemoveAll(tmpDaemonRoot)) 27 }() 28 29 daemon := &Daemon{ 30 root: tmpDaemonRoot, 31 } 32 33 t.Run("it does nothing if there are no security options", func(t *testing.T) { 34 spec := &specs.Spec{} 35 36 err := daemon.setWindowsCredentialSpec(&container.Container{}, spec) 37 assert.NilError(t, err) 38 assert.Check(t, spec.Windows == nil) 39 40 err = daemon.setWindowsCredentialSpec(&container.Container{HostConfig: &containertypes.HostConfig{}}, spec) 41 assert.NilError(t, err) 42 assert.Check(t, spec.Windows == nil) 43 44 err = daemon.setWindowsCredentialSpec(&container.Container{HostConfig: &containertypes.HostConfig{SecurityOpt: []string{}}}, spec) 45 assert.NilError(t, err) 46 assert.Check(t, spec.Windows == nil) 47 }) 48 49 dummyContainerID := "dummy-container-ID" 50 containerFactory := func(secOpt string) *container.Container { 51 if !strings.Contains(secOpt, "=") { 52 secOpt = "credentialspec=" + secOpt 53 } 54 return &container.Container{ 55 ID: dummyContainerID, 56 HostConfig: &containertypes.HostConfig{ 57 SecurityOpt: []string{secOpt}, 58 }, 59 } 60 } 61 62 credSpecsDir := filepath.Join(tmpDaemonRoot, credentialSpecFileLocation) 63 dummyCredFileContents := `{"We don't need no": "education"}` 64 65 t.Run("happy path with a 'file://' option", func(t *testing.T) { 66 spec := &specs.Spec{} 67 68 // let's render a dummy cred file 69 err := os.Mkdir(credSpecsDir, os.ModePerm) 70 assert.NilError(t, err) 71 dummyCredFileName := "dummy-cred-spec.json" 72 dummyCredFilePath := filepath.Join(credSpecsDir, dummyCredFileName) 73 err = ioutil.WriteFile(dummyCredFilePath, []byte(dummyCredFileContents), 0644) 74 defer func() { 75 assert.NilError(t, os.Remove(dummyCredFilePath)) 76 }() 77 assert.NilError(t, err) 78 79 err = daemon.setWindowsCredentialSpec(containerFactory("file://"+dummyCredFileName), spec) 80 assert.NilError(t, err) 81 82 if assert.Check(t, spec.Windows != nil) { 83 assert.Equal(t, dummyCredFileContents, spec.Windows.CredentialSpec) 84 } 85 }) 86 87 t.Run("it's not allowed to use a 'file://' option with an absolute path", func(t *testing.T) { 88 spec := &specs.Spec{} 89 90 err := daemon.setWindowsCredentialSpec(containerFactory(`file://C:\path\to\my\credspec.json`), spec) 91 assert.ErrorContains(t, err, "invalid credential spec - file:// path cannot be absolute") 92 93 assert.Check(t, spec.Windows == nil) 94 }) 95 96 t.Run("it's not allowed to use a 'file://' option breaking out of the cred specs' directory", func(t *testing.T) { 97 spec := &specs.Spec{} 98 99 err := daemon.setWindowsCredentialSpec(containerFactory(`file://..\credspec.json`), spec) 100 assert.ErrorContains(t, err, fmt.Sprintf("invalid credential spec - file:// path must be under %s", credSpecsDir)) 101 102 assert.Check(t, spec.Windows == nil) 103 }) 104 105 t.Run("when using a 'file://' option pointing to a file that doesn't exist, it fails gracefully", func(t *testing.T) { 106 spec := &specs.Spec{} 107 108 err := daemon.setWindowsCredentialSpec(containerFactory("file://i-dont-exist.json"), spec) 109 assert.ErrorContains(t, err, fmt.Sprintf("credential spec for container %s could not be read from file", dummyContainerID)) 110 assert.ErrorContains(t, err, "The system cannot find") 111 112 assert.Check(t, spec.Windows == nil) 113 }) 114 115 t.Run("happy path with a 'registry://' option", func(t *testing.T) { 116 valueName := "my-cred-spec" 117 key := &dummyRegistryKey{ 118 getStringValueFunc: func(name string) (val string, valtype uint32, err error) { 119 assert.Equal(t, valueName, name) 120 return dummyCredFileContents, 0, nil 121 }, 122 } 123 defer setRegistryOpenKeyFunc(t, key)() 124 125 spec := &specs.Spec{} 126 assert.NilError(t, daemon.setWindowsCredentialSpec(containerFactory("registry://"+valueName), spec)) 127 128 if assert.Check(t, spec.Windows != nil) { 129 assert.Equal(t, dummyCredFileContents, spec.Windows.CredentialSpec) 130 } 131 assert.Check(t, key.closed) 132 }) 133 134 t.Run("when using a 'registry://' option and opening the registry key fails, it fails gracefully", func(t *testing.T) { 135 dummyError := fmt.Errorf("dummy error") 136 defer setRegistryOpenKeyFunc(t, &dummyRegistryKey{}, dummyError)() 137 138 spec := &specs.Spec{} 139 err := daemon.setWindowsCredentialSpec(containerFactory("registry://my-cred-spec"), spec) 140 assert.ErrorContains(t, err, fmt.Sprintf("registry key %s could not be opened: %v", credentialSpecRegistryLocation, dummyError)) 141 142 assert.Check(t, spec.Windows == nil) 143 }) 144 145 t.Run("when using a 'registry://' option pointing to a value that doesn't exist, it fails gracefully", func(t *testing.T) { 146 valueName := "my-cred-spec" 147 key := &dummyRegistryKey{ 148 getStringValueFunc: func(name string) (val string, valtype uint32, err error) { 149 assert.Equal(t, valueName, name) 150 return "", 0, registry.ErrNotExist 151 }, 152 } 153 defer setRegistryOpenKeyFunc(t, key)() 154 155 spec := &specs.Spec{} 156 err := daemon.setWindowsCredentialSpec(containerFactory("registry://"+valueName), spec) 157 assert.ErrorContains(t, err, fmt.Sprintf("registry credential spec %q for container %s was not found", valueName, dummyContainerID)) 158 159 assert.Check(t, key.closed) 160 }) 161 162 t.Run("when using a 'registry://' option and reading the registry value fails, it fails gracefully", func(t *testing.T) { 163 dummyError := fmt.Errorf("dummy error") 164 valueName := "my-cred-spec" 165 key := &dummyRegistryKey{ 166 getStringValueFunc: func(name string) (val string, valtype uint32, err error) { 167 assert.Equal(t, valueName, name) 168 return "", 0, dummyError 169 }, 170 } 171 defer setRegistryOpenKeyFunc(t, key)() 172 173 spec := &specs.Spec{} 174 err := daemon.setWindowsCredentialSpec(containerFactory("registry://"+valueName), spec) 175 assert.ErrorContains(t, err, fmt.Sprintf("error reading credential spec %q from registry for container %s: %v", valueName, dummyContainerID, dummyError)) 176 177 assert.Check(t, key.closed) 178 }) 179 180 t.Run("happy path with a 'config://' option", func(t *testing.T) { 181 configID := "my-cred-spec" 182 183 dependencyManager := swarmagent.NewDependencyManager() 184 dependencyManager.Configs().Add(swarmapi.Config{ 185 ID: configID, 186 Spec: swarmapi.ConfigSpec{ 187 Data: []byte(dummyCredFileContents), 188 }, 189 }) 190 191 task := &swarmapi.Task{ 192 Spec: swarmapi.TaskSpec{ 193 Runtime: &swarmapi.TaskSpec_Container{ 194 Container: &swarmapi.ContainerSpec{ 195 Configs: []*swarmapi.ConfigReference{ 196 { 197 ConfigID: configID, 198 }, 199 }, 200 }, 201 }, 202 }, 203 } 204 205 cntr := containerFactory("config://" + configID) 206 cntr.DependencyStore = swarmagent.Restrict(dependencyManager, task) 207 208 spec := &specs.Spec{} 209 err := daemon.setWindowsCredentialSpec(cntr, spec) 210 assert.NilError(t, err) 211 212 if assert.Check(t, spec.Windows != nil) { 213 assert.Equal(t, dummyCredFileContents, spec.Windows.CredentialSpec) 214 } 215 }) 216 217 t.Run("using a 'config://' option on a container not managed by swarmkit is not allowed, and results in a generic error message to hide that purely internal API", func(t *testing.T) { 218 spec := &specs.Spec{} 219 220 err := daemon.setWindowsCredentialSpec(containerFactory("config://whatever"), spec) 221 assert.Equal(t, errInvalidCredentialSpecSecOpt, err) 222 223 assert.Check(t, spec.Windows == nil) 224 }) 225 226 t.Run("happy path with a 'raw://' option", func(t *testing.T) { 227 spec := &specs.Spec{} 228 229 err := daemon.setWindowsCredentialSpec(containerFactory("raw://"+dummyCredFileContents), spec) 230 assert.NilError(t, err) 231 232 if assert.Check(t, spec.Windows != nil) { 233 assert.Equal(t, dummyCredFileContents, spec.Windows.CredentialSpec) 234 } 235 }) 236 237 t.Run("it's not case sensitive in the option names", func(t *testing.T) { 238 spec := &specs.Spec{} 239 240 err := daemon.setWindowsCredentialSpec(containerFactory("CreDENtiaLSPeC=rAw://"+dummyCredFileContents), spec) 241 assert.NilError(t, err) 242 243 if assert.Check(t, spec.Windows != nil) { 244 assert.Equal(t, dummyCredFileContents, spec.Windows.CredentialSpec) 245 } 246 }) 247 248 t.Run("it rejects unknown options", func(t *testing.T) { 249 spec := &specs.Spec{} 250 251 err := daemon.setWindowsCredentialSpec(containerFactory("credentialspe=config://whatever"), spec) 252 assert.ErrorContains(t, err, "security option not supported: credentialspe") 253 254 assert.Check(t, spec.Windows == nil) 255 }) 256 257 t.Run("it rejects unsupported credentialspec options", func(t *testing.T) { 258 spec := &specs.Spec{} 259 260 err := daemon.setWindowsCredentialSpec(containerFactory("idontexist://whatever"), spec) 261 assert.Equal(t, errInvalidCredentialSpecSecOpt, err) 262 263 assert.Check(t, spec.Windows == nil) 264 }) 265 266 for _, option := range []string{"file", "registry", "config", "raw"} { 267 t.Run(fmt.Sprintf("it rejects empty values for %s", option), func(t *testing.T) { 268 spec := &specs.Spec{} 269 270 err := daemon.setWindowsCredentialSpec(containerFactory(option+"://"), spec) 271 assert.Equal(t, errInvalidCredentialSpecSecOpt, err) 272 273 assert.Check(t, spec.Windows == nil) 274 }) 275 } 276 } 277 278 /* Helpers below */ 279 280 type dummyRegistryKey struct { 281 getStringValueFunc func(name string) (val string, valtype uint32, err error) 282 closed bool 283 } 284 285 func (k *dummyRegistryKey) GetStringValue(name string) (val string, valtype uint32, err error) { 286 return k.getStringValueFunc(name) 287 } 288 289 func (k *dummyRegistryKey) Close() error { 290 k.closed = true 291 return nil 292 } 293 294 // setRegistryOpenKeyFunc replaces the registryOpenKeyFunc package variable, and returns a function 295 // to be called to revert the change when done with testing. 296 func setRegistryOpenKeyFunc(t *testing.T, key *dummyRegistryKey, err ...error) func() { 297 previousRegistryOpenKeyFunc := registryOpenKeyFunc 298 299 registryOpenKeyFunc = func(baseKey registry.Key, path string, access uint32) (registryKey, error) { 300 // this should always be called with exactly the same arguments 301 assert.Equal(t, registry.LOCAL_MACHINE, baseKey) 302 assert.Equal(t, credentialSpecRegistryLocation, path) 303 assert.Equal(t, uint32(registry.QUERY_VALUE), access) 304 305 if len(err) > 0 { 306 return nil, err[0] 307 } 308 return key, nil 309 } 310 311 return func() { 312 registryOpenKeyFunc = previousRegistryOpenKeyFunc 313 } 314 }