Skip to content

Commit 14cc629

Browse files
cypharruncom
authored andcommitted
SUSE: implement SUSE container secrets
This allows for us to pass in host credentials to a container, allowing for SUSEConnect to work with containers. THIS PATCH IS NOT TO BE UPSTREAMED, DUE TO THE FACT THAT IT IS SUSE-SPECIFIC, AND UPSTREAM DOES NOT APPROVE OF THIS CONCEPT BECAUSE IT MAKES BUILDS NOT ENTIRELY REPRODUCIBLE. Signed-off-by: Aleksa Sarai <asarai@suse.de> Signed-off-by: Antonio Murdaca <runcom@redhat.com>
1 parent 3d325ea commit 14cc629

3 files changed

Lines changed: 268 additions & 4 deletions

File tree

daemon/container_operations_unix.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -177,10 +177,6 @@ func (daemon *Daemon) setupSecretDir(c *container.Container) (setupErr error) {
177177
}
178178

179179
targetPath := filepath.Clean(s.File.Name)
180-
// ensure that the target is a filename only; no paths allowed
181-
if targetPath != filepath.Base(targetPath) {
182-
return fmt.Errorf("error creating secret: secret must not be a path")
183-
}
184180

185181
fPath := filepath.Join(localMountPath, targetPath)
186182
if err := idtools.MkdirAllAs(filepath.Dir(fPath), 0700, rootUID, rootGID); err != nil {
@@ -219,6 +215,8 @@ func (daemon *Daemon) setupSecretDir(c *container.Container) (setupErr error) {
219215
}
220216
}
221217

218+
label.Relabel(localMountPath, c.MountLabel, false)
219+
222220
// remount secrets ro
223221
if err := mount.Mount("tmpfs", localMountPath, "tmpfs", "remount,ro,"+tmpfsOwnership); err != nil {
224222
return errors.Wrap(err, "unable to remount secret dir as readonly")

daemon/start.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,13 @@ func (daemon *Daemon) containerStart(container *container.Container, checkpoint
146146
return err
147147
}
148148

149+
if daemon.configStore.EnableSecrets {
150+
// SUSE:secrets -- inject the SUSE secret store
151+
if err := daemon.injectSuseSecretStore(container); err != nil {
152+
return err
153+
}
154+
}
155+
149156
spec, err := daemon.createSpec(container)
150157
if err != nil {
151158
return err

daemon/suse_secrets.go

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
/*
2+
* suse-secrets: patch for Docker to implement SUSE secrets
3+
* Copyright (C) 2017 SUSE LLC.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package daemon
19+
20+
import (
21+
"fmt"
22+
"io/ioutil"
23+
"os"
24+
"path/filepath"
25+
"syscall"
26+
27+
"github.com/Sirupsen/logrus"
28+
"github.com/docker/distribution/digest"
29+
"github.com/docker/docker/container"
30+
31+
swarmtypes "github.com/docker/docker/api/types/swarm"
32+
swarmexec "github.com/docker/swarmkit/agent/exec"
33+
swarmapi "github.com/docker/swarmkit/api"
34+
)
35+
36+
func init() {
37+
// Output to tell us in logs that SUSE:secrets is enabled.
38+
logrus.Infof("SUSE:secrets :: enabled")
39+
}
40+
41+
// Creating a fake file.
42+
type SuseFakeFile struct {
43+
Path string
44+
Uid int
45+
Gid int
46+
Mode os.FileMode
47+
Data []byte
48+
}
49+
50+
func (s SuseFakeFile) id() string {
51+
return fmt.Sprintf("suse::%s:%s", digest.FromBytes(s.Data), s.Path)
52+
}
53+
54+
func (s SuseFakeFile) toSecret() *swarmapi.Secret {
55+
return &swarmapi.Secret{
56+
ID: s.id(),
57+
Internal: true,
58+
Spec: swarmapi.SecretSpec{
59+
Data: s.Data,
60+
},
61+
}
62+
}
63+
64+
func (s SuseFakeFile) toSecretReference() *swarmtypes.SecretReference {
65+
return &swarmtypes.SecretReference{
66+
SecretID: s.id(),
67+
SecretName: s.id(),
68+
File: &swarmtypes.SecretReferenceFileTarget{
69+
Name: s.Path,
70+
UID: fmt.Sprintf("%d", s.Uid),
71+
GID: fmt.Sprintf("%d", s.Gid),
72+
Mode: s.Mode,
73+
},
74+
}
75+
}
76+
77+
// readDir will recurse into a directory prefix/dir, and return the set of secrets
78+
// in that directory. The Path attribute of each has the prefix stripped. Symlinks
79+
// are evaluated.
80+
func readDir(prefix, dir string) ([]*SuseFakeFile, error) {
81+
var suseFiles []*SuseFakeFile
82+
83+
path := filepath.Join(prefix, dir)
84+
85+
fi, err := os.Stat(path)
86+
if err != nil {
87+
// Ignore dangling symlinks.
88+
if os.IsNotExist(err) {
89+
logrus.Warnf("SUSE:secrets :: dangling symlink: %s", path)
90+
return suseFiles, nil
91+
}
92+
return nil, err
93+
}
94+
95+
stat, ok := fi.Sys().(*syscall.Stat_t)
96+
if !ok {
97+
logrus.Warnf("SUSE:secrets :: failed to cast directory stat_t: defaulting to owned by root:root: %s", path)
98+
}
99+
100+
suseFiles = append(suseFiles, &SuseFakeFile{
101+
Path: dir,
102+
Uid: int(stat.Uid),
103+
Gid: int(stat.Gid),
104+
Mode: fi.Mode(),
105+
})
106+
107+
files, err := ioutil.ReadDir(path)
108+
if err != nil {
109+
return nil, err
110+
}
111+
112+
for _, f := range files {
113+
subpath := filepath.Join(dir, f.Name())
114+
115+
if f.IsDir() {
116+
secrets, err := readDir(prefix, subpath)
117+
if err != nil {
118+
return nil, err
119+
}
120+
suseFiles = append(suseFiles, secrets...)
121+
} else {
122+
secrets, err := readFile(prefix, subpath)
123+
if err != nil {
124+
return nil, err
125+
}
126+
suseFiles = append(suseFiles, secrets...)
127+
}
128+
}
129+
130+
return suseFiles, nil
131+
}
132+
133+
// readFile returns a secret given a file under a given prefix.
134+
func readFile(prefix, file string) ([]*SuseFakeFile, error) {
135+
var suseFiles []*SuseFakeFile
136+
137+
path := filepath.Join(prefix, file)
138+
fi, err := os.Stat(path)
139+
if err != nil {
140+
// Ignore dangling symlinks.
141+
if os.IsNotExist(err) {
142+
logrus.Warnf("SUSE:secrets :: dangling symlink: %s", path)
143+
return suseFiles, nil
144+
}
145+
return nil, err
146+
}
147+
148+
stat, ok := fi.Sys().(*syscall.Stat_t)
149+
if !ok {
150+
logrus.Warnf("SUSE:secrets :: failed to cast file stat_t: defaulting to owned by root:root: %s", path)
151+
}
152+
153+
if fi.IsDir() {
154+
secrets, err := readDir(prefix, file)
155+
if err != nil {
156+
return nil, err
157+
}
158+
suseFiles = append(suseFiles, secrets...)
159+
} else {
160+
bytes, err := ioutil.ReadFile(path)
161+
if err != nil {
162+
return nil, err
163+
}
164+
suseFiles = append(suseFiles, &SuseFakeFile{
165+
Path: file,
166+
Uid: int(stat.Uid),
167+
Gid: int(stat.Gid),
168+
Mode: fi.Mode(),
169+
Data: bytes,
170+
})
171+
}
172+
173+
return suseFiles, nil
174+
}
175+
176+
// getHostSuseSecretData returns the list of SuseFakeFiles the need to be added
177+
// as SUSE secrets.
178+
func getHostSuseSecretData() ([]*SuseFakeFile, error) {
179+
secrets := []*SuseFakeFile{}
180+
181+
for _, p := range []string{
182+
"/usr/share/rhel/secrets",
183+
"/etc/container/rhel/secrets",
184+
} {
185+
prefix := p
186+
dir := ""
187+
path := filepath.Join(prefix, dir)
188+
189+
files, err := ioutil.ReadDir(path)
190+
if err != nil {
191+
if os.IsNotExist(err) {
192+
continue
193+
}
194+
return nil, err
195+
}
196+
197+
for _, f := range files {
198+
subpath := filepath.Join(dir, f.Name())
199+
200+
if f.IsDir() {
201+
s, err := readDir(prefix, subpath)
202+
if err != nil {
203+
return nil, err
204+
}
205+
secrets = append(secrets, s...)
206+
} else {
207+
s, err := readFile(prefix, subpath)
208+
if err != nil {
209+
return nil, err
210+
}
211+
secrets = append(secrets, s...)
212+
}
213+
}
214+
}
215+
216+
return secrets, nil
217+
}
218+
219+
// In order to reduce the amount of code touched outside of this file, we
220+
// implement the swarm API for SecretGetter. This asserts that this requirement
221+
// will always be matched.
222+
var _ swarmexec.SecretGetter = &suseSecretGetter{}
223+
224+
type suseSecretGetter struct {
225+
dfl swarmexec.SecretGetter
226+
secrets map[string]*swarmapi.Secret
227+
}
228+
229+
func (s *suseSecretGetter) Get(id string) *swarmapi.Secret {
230+
logrus.Debugf("SUSE:secrets :: id=%s requested from suseSecretGetter", id)
231+
232+
secret, ok := s.secrets[id]
233+
if !ok {
234+
// fallthrough
235+
return s.dfl.Get(id)
236+
}
237+
238+
return secret
239+
}
240+
241+
func (daemon *Daemon) injectSuseSecretStore(c *container.Container) error {
242+
newSecretStore := &suseSecretGetter{
243+
dfl: c.SecretStore,
244+
secrets: make(map[string]*swarmapi.Secret),
245+
}
246+
247+
secrets, err := getHostSuseSecretData()
248+
if err != nil {
249+
return err
250+
}
251+
252+
for _, secret := range secrets {
253+
newSecretStore.secrets[secret.id()] = secret.toSecret()
254+
c.SecretReferences = append(c.SecretReferences, secret.toSecretReference())
255+
}
256+
257+
c.SecretStore = newSecretStore
258+
return nil
259+
}

0 commit comments

Comments
 (0)