@@ -2,11 +2,19 @@ package daemon
22
33import (
44 "fmt"
5+ "io/ioutil"
56 "path/filepath"
7+ "strings"
68
79 "github.com/docker/docker/container"
810 "github.com/docker/docker/layer"
911 "github.com/docker/docker/libcontainerd"
12+ "golang.org/x/sys/windows/registry"
13+ )
14+
15+ const (
16+ credentialSpecRegistryLocation = `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\Containers\CredentialSpecs`
17+ credentialSpecFileLocation = "CredentialSpecs"
1018)
1119
1220func (daemon * Daemon ) getLibcontainerdCreateOptions (container * container.Container ) (* []libcontainerd.CreateOption , error ) {
@@ -80,7 +88,50 @@ func (daemon *Daemon) getLibcontainerdCreateOptions(container *container.Contain
8088 }
8189 }
8290
83- // Now build the full set of options
91+ // Read and add credentials from the security options if a credential spec has been provided.
92+ if container .HostConfig .SecurityOpt != nil {
93+ for _ , sOpt := range container .HostConfig .SecurityOpt {
94+ sOpt = strings .ToLower (sOpt )
95+ if ! strings .Contains (sOpt , "=" ) {
96+ return nil , fmt .Errorf ("invalid security option: no equals sign in supplied value %s" , sOpt )
97+ }
98+ var splitsOpt []string
99+ splitsOpt = strings .SplitN (sOpt , "=" , 2 )
100+ if len (splitsOpt ) != 2 {
101+ return nil , fmt .Errorf ("invalid security option: %s" , sOpt )
102+ }
103+ if splitsOpt [0 ] != "credentialspec" {
104+ return nil , fmt .Errorf ("security option not supported: %s" , splitsOpt [0 ])
105+ }
106+
107+ credentialsOpts := & libcontainerd.CredentialsOption {}
108+ var (
109+ match bool
110+ csValue string
111+ err error
112+ )
113+ if match , csValue = getCredentialSpec ("file://" , splitsOpt [1 ]); match {
114+ if csValue == "" {
115+ return nil , fmt .Errorf ("no value supplied for file:// credential spec security option" )
116+ }
117+ if credentialsOpts .Credentials , err = readCredentialSpecFile (container .ID , daemon .root , filepath .Clean (csValue )); err != nil {
118+ return nil , err
119+ }
120+ } else if match , csValue = getCredentialSpec ("registry://" , splitsOpt [1 ]); match {
121+ if csValue == "" {
122+ return nil , fmt .Errorf ("no value supplied for registry:// credential spec security option" )
123+ }
124+ if credentialsOpts .Credentials , err = readCredentialSpecRegistry (container .ID , csValue ); err != nil {
125+ return nil , err
126+ }
127+ } else {
128+ return nil , fmt .Errorf ("invalid credential spec security option - value must be prefixed file:// or registry:// followed by a value" )
129+ }
130+ createOptions = append (createOptions , credentialsOpts )
131+ }
132+ }
133+
134+ // Now add the remaining options.
84135 createOptions = append (createOptions , & libcontainerd.FlushOption {IgnoreFlushesDuringBoot : ! container .HasBeenStartedBefore })
85136 createOptions = append (createOptions , hvOpts )
86137 createOptions = append (createOptions , layerOpts )
@@ -90,3 +141,52 @@ func (daemon *Daemon) getLibcontainerdCreateOptions(container *container.Contain
90141
91142 return & createOptions , nil
92143}
144+
145+ // getCredentialSpec is a helper function to get the value of a credential spec supplied
146+ // on the CLI, stripping the prefix
147+ func getCredentialSpec (prefix , value string ) (bool , string ) {
148+ if strings .HasPrefix (value , prefix ) {
149+ return true , strings .TrimPrefix (value , prefix )
150+ }
151+ return false , ""
152+ }
153+
154+ // readCredentialSpecRegistry is a helper function to read a credential spec from
155+ // the registry. If not found, we return an empty string and warn in the log.
156+ // This allows for staging on machines which do not have the necessary components.
157+ func readCredentialSpecRegistry (id , name string ) (string , error ) {
158+ var (
159+ k registry.Key
160+ err error
161+ val string
162+ )
163+ if k , err = registry .OpenKey (registry .LOCAL_MACHINE , credentialSpecRegistryLocation , registry .QUERY_VALUE ); err != nil {
164+ return "" , fmt .Errorf ("failed handling spec %q for container %s - %s could not be opened" , name , id , credentialSpecRegistryLocation )
165+ }
166+ if val , _ , err = k .GetStringValue (name ); err != nil {
167+ if err == registry .ErrNotExist {
168+ return "" , fmt .Errorf ("credential spec %q for container %s as it was not found" , name , id )
169+ }
170+ return "" , fmt .Errorf ("error %v reading credential spec %q from registry for container %s" , err , name , id )
171+ }
172+ return val , nil
173+ }
174+
175+ // readCredentialSpecFile is a helper function to read a credential spec from
176+ // a file. If not found, we return an empty string and warn in the log.
177+ // This allows for staging on machines which do not have the necessary components.
178+ func readCredentialSpecFile (id , root , location string ) (string , error ) {
179+ if filepath .IsAbs (location ) {
180+ return "" , fmt .Errorf ("invalid credential spec - file:// path cannot be absolute" )
181+ }
182+ base := filepath .Join (root , credentialSpecFileLocation )
183+ full := filepath .Join (base , location )
184+ if ! strings .HasPrefix (full , base ) {
185+ return "" , fmt .Errorf ("invalid credential spec - file:// path must be under %s" , base )
186+ }
187+ bcontents , err := ioutil .ReadFile (full )
188+ if err != nil {
189+ return "" , fmt .Errorf ("credential spec '%s' for container %s as the file could not be read: %q" , full , id , err )
190+ }
191+ return string (bcontents [:]), nil
192+ }
0 commit comments