secrets.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. /*
  2. Copyright 2020 Docker Compose CLI 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 compose
  14. import (
  15. "archive/tar"
  16. "bytes"
  17. "context"
  18. "fmt"
  19. "strconv"
  20. "strings"
  21. "time"
  22. "github.com/compose-spec/compose-go/v2/types"
  23. "github.com/docker/docker/api/types/container"
  24. )
  25. type mountType string
  26. const (
  27. secretMount mountType = "secret"
  28. configMount mountType = "config"
  29. )
  30. func (s *composeService) injectSecrets(ctx context.Context, project *types.Project, service types.ServiceConfig, id string) error {
  31. return s.injectFileReferences(ctx, project, service, id, secretMount)
  32. }
  33. func (s *composeService) injectConfigs(ctx context.Context, project *types.Project, service types.ServiceConfig, id string) error {
  34. return s.injectFileReferences(ctx, project, service, id, configMount)
  35. }
  36. func (s *composeService) injectFileReferences(ctx context.Context, project *types.Project, service types.ServiceConfig, id string, mountType mountType) error {
  37. mounts, sources := s.getFilesAndMap(project, service, mountType)
  38. var ctrConfig *container.Config
  39. for _, mount := range mounts {
  40. content, err := s.resolveFileContent(project, sources[mount.Source], mountType)
  41. if err != nil {
  42. return err
  43. }
  44. if content == "" {
  45. continue
  46. }
  47. if service.ReadOnly {
  48. return fmt.Errorf("cannot create %s %q in read-only service %s: `file` is the sole supported option", mountType, sources[mount.Source].Name, service.Name)
  49. }
  50. s.setDefaultTarget(&mount, mountType)
  51. ctrConfig, err = s.setFileOwnership(ctx, id, &mount, ctrConfig)
  52. if err != nil {
  53. return err
  54. }
  55. if err := s.copyFileToContainer(ctx, id, content, mount); err != nil {
  56. return err
  57. }
  58. }
  59. return nil
  60. }
  61. func (s *composeService) getFilesAndMap(project *types.Project, service types.ServiceConfig, mountType mountType) ([]types.FileReferenceConfig, map[string]types.FileObjectConfig) {
  62. var files []types.FileReferenceConfig
  63. var fileMap map[string]types.FileObjectConfig
  64. switch mountType {
  65. case secretMount:
  66. files = make([]types.FileReferenceConfig, len(service.Secrets))
  67. for i, config := range service.Secrets {
  68. files[i] = types.FileReferenceConfig(config)
  69. }
  70. fileMap = make(map[string]types.FileObjectConfig)
  71. for k, v := range project.Secrets {
  72. fileMap[k] = types.FileObjectConfig(v)
  73. }
  74. case configMount:
  75. files = make([]types.FileReferenceConfig, len(service.Configs))
  76. for i, config := range service.Configs {
  77. files[i] = types.FileReferenceConfig(config)
  78. }
  79. fileMap = make(map[string]types.FileObjectConfig)
  80. for k, v := range project.Configs {
  81. fileMap[k] = types.FileObjectConfig(v)
  82. }
  83. }
  84. return files, fileMap
  85. }
  86. func (s *composeService) resolveFileContent(project *types.Project, source types.FileObjectConfig, mountType mountType) (string, error) {
  87. if source.Content != "" {
  88. // inlined, or already resolved by include
  89. return source.Content, nil
  90. }
  91. if source.Environment != "" {
  92. env, ok := project.Environment[source.Environment]
  93. if !ok {
  94. return "", fmt.Errorf("environment variable %q required by %s %q is not set", source.Environment, mountType, source.Name)
  95. }
  96. return env, nil
  97. }
  98. return "", nil
  99. }
  100. func (s *composeService) setDefaultTarget(file *types.FileReferenceConfig, mountType mountType) {
  101. if file.Target == "" {
  102. if mountType == secretMount {
  103. file.Target = "/run/secrets/" + file.Source
  104. } else {
  105. file.Target = "/" + file.Source
  106. }
  107. } else if mountType == secretMount && !isAbsTarget(file.Target) {
  108. file.Target = "/run/secrets/" + file.Target
  109. }
  110. }
  111. func (s *composeService) setFileOwnership(ctx context.Context, id string, file *types.FileReferenceConfig, ctrConfig *container.Config) (*container.Config, error) {
  112. if file.UID != "" || file.GID != "" {
  113. return ctrConfig, nil
  114. }
  115. if ctrConfig == nil {
  116. ctr, err := s.apiClient().ContainerInspect(ctx, id)
  117. if err != nil {
  118. return nil, err
  119. }
  120. ctrConfig = ctr.Config
  121. }
  122. parts := strings.Split(ctrConfig.User, ":")
  123. if len(parts) > 0 {
  124. file.UID = parts[0]
  125. }
  126. if len(parts) > 1 {
  127. file.GID = parts[1]
  128. }
  129. return ctrConfig, nil
  130. }
  131. func (s *composeService) copyFileToContainer(ctx context.Context, id, content string, file types.FileReferenceConfig) error {
  132. b, err := createTar(content, file)
  133. if err != nil {
  134. return err
  135. }
  136. return s.apiClient().CopyToContainer(ctx, id, "/", &b, container.CopyToContainerOptions{
  137. CopyUIDGID: true,
  138. })
  139. }
  140. func createTar(env string, config types.FileReferenceConfig) (bytes.Buffer, error) {
  141. value := []byte(env)
  142. b := bytes.Buffer{}
  143. tarWriter := tar.NewWriter(&b)
  144. mode := types.FileMode(0o444)
  145. if config.Mode != nil {
  146. mode = *config.Mode
  147. }
  148. var uid, gid int
  149. if config.UID != "" {
  150. v, err := strconv.Atoi(config.UID)
  151. if err != nil {
  152. return b, err
  153. }
  154. uid = v
  155. }
  156. if config.GID != "" {
  157. v, err := strconv.Atoi(config.GID)
  158. if err != nil {
  159. return b, err
  160. }
  161. gid = v
  162. }
  163. header := &tar.Header{
  164. Name: config.Target,
  165. Size: int64(len(value)),
  166. Mode: int64(mode),
  167. ModTime: time.Now(),
  168. Uid: uid,
  169. Gid: gid,
  170. }
  171. err := tarWriter.WriteHeader(header)
  172. if err != nil {
  173. return bytes.Buffer{}, err
  174. }
  175. _, err = tarWriter.Write(value)
  176. if err != nil {
  177. return bytes.Buffer{}, err
  178. }
  179. err = tarWriter.Close()
  180. return b, err
  181. }