github.com/ttpreport/gvisor-ligolo@v0.0.0-20240123134145-a858404967ba/runsc/boot/mount_hints.go (about) 1 // Copyright 2022 The gVisor Authors. 2 // 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 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package boot 16 17 import ( 18 "fmt" 19 "strings" 20 21 specs "github.com/opencontainers/runtime-spec/specs-go" 22 "github.com/ttpreport/gvisor-ligolo/pkg/log" 23 "github.com/ttpreport/gvisor-ligolo/pkg/sentry/fsimpl/tmpfs" 24 "github.com/ttpreport/gvisor-ligolo/pkg/sentry/vfs" 25 "github.com/ttpreport/gvisor-ligolo/runsc/config" 26 "github.com/ttpreport/gvisor-ligolo/runsc/specutils" 27 ) 28 29 // MountPrefix is the annotation prefix for mount hints. 30 const MountPrefix = "dev.gvisor.spec.mount." 31 32 // shareType indicates who can access/mutate the volume contents. 33 type shareType int 34 35 const ( 36 invalid shareType = iota 37 38 // container shareType indicates that the mount is used by a single 39 // container. There are no external observers. 40 container 41 42 // pod shareType indicates that the mount is used by more than one container 43 // inside the pod. There are no external observers. 44 pod 45 46 // shared shareType indicates that the mount can also be shared with a process 47 // outside the pod, e.g. NFS. 48 shared 49 ) 50 51 func (s shareType) String() string { 52 switch s { 53 case invalid: 54 return "invalid" 55 case container: 56 return "container" 57 case pod: 58 return "pod" 59 case shared: 60 return "shared" 61 default: 62 return fmt.Sprintf("invalid share value %d", s) 63 } 64 } 65 66 // lifecycleType indicates whether creation/deletion of the volume is tied to 67 // the pod or container's lifecycle. 68 type lifecycleType int 69 70 const ( 71 // sharedLife indicates that the volume's lifecycle is not tied to the pod. 72 // The volume persists beyond the pod's life. This is the safe default. 73 sharedLife lifecycleType = iota 74 75 // podLife indicates that the volume's lifecycle is tied to the pod's 76 // lifecycle. The volume is destroyed with the pod. 77 podLife 78 79 // containerLife indicates that the volume's lifecycle is tied to the 80 // container's lifecycle. The volume is destroyed with the container. 81 containerLife 82 ) 83 84 func (o lifecycleType) String() string { 85 switch o { 86 case sharedLife: 87 return "shared" 88 case podLife: 89 return "pod" 90 case containerLife: 91 return "container" 92 default: 93 return fmt.Sprintf("invalid lifecycle value %d", o) 94 } 95 } 96 97 // PodMountHints contains a collection of mountHints for the pod. 98 type PodMountHints struct { 99 mounts map[string]*MountHint 100 } 101 102 // NewPodMountHints instantiates PodMountHints using spec. 103 func NewPodMountHints(spec *specs.Spec) (*PodMountHints, error) { 104 mnts := make(map[string]*MountHint) 105 for k, v := range spec.Annotations { 106 // Look for 'dev.gvisor.spec.mount' annotations and parse them. 107 if strings.HasPrefix(k, MountPrefix) { 108 // Remove the prefix and split the rest. 109 parts := strings.Split(k[len(MountPrefix):], ".") 110 if len(parts) != 2 { 111 return nil, fmt.Errorf("invalid mount annotation: %s=%s", k, v) 112 } 113 name := parts[0] 114 if len(name) == 0 { 115 return nil, fmt.Errorf("invalid mount name: %s", name) 116 } 117 mnt := mnts[name] 118 if mnt == nil { 119 mnt = &MountHint{name: name} 120 mnts[name] = mnt 121 } 122 if err := mnt.setField(parts[1], v); err != nil { 123 log.Warningf("ignoring invalid mount annotation (name = %q, key = %q, value = %q): %v", name, parts[1], v, err) 124 } 125 } 126 } 127 128 // Validate all the parsed hints. 129 for name, m := range mnts { 130 log.Infof("Mount annotation found, name: %s, source: %q, type: %s, share: %v", name, m.mount.Source, m.mount.Type, m.share) 131 if m.share == invalid || len(m.mount.Source) == 0 || len(m.mount.Type) == 0 { 132 log.Warningf("ignoring mount annotations for %q because of missing required field(s)", name) 133 delete(mnts, name) 134 continue 135 } 136 137 // Check for duplicate mount sources. 138 for name2, m2 := range mnts { 139 if name != name2 && m.mount.Source == m2.mount.Source { 140 return nil, fmt.Errorf("mounts %q and %q have the same mount source %q", m.name, m2.name, m.mount.Source) 141 } 142 } 143 } 144 145 return &PodMountHints{mounts: mnts}, nil 146 } 147 148 // MountHint represents extra information about mounts that are provided via 149 // annotations. They can override mount type, and provide sharing information 150 // so that mounts can be correctly shared inside the pod. 151 type MountHint struct { 152 name string 153 share shareType 154 mount specs.Mount 155 lifecycle lifecycleType 156 157 // vfsMount is the master mount for the volume. For mounts with 'pod' share 158 // the master volume is bind mounted inside the containers. 159 vfsMount *vfs.Mount 160 } 161 162 func (m *MountHint) setField(key, val string) error { 163 switch key { 164 case "source": 165 if len(val) == 0 { 166 return fmt.Errorf("source cannot be empty") 167 } 168 m.mount.Source = val 169 case "type": 170 return m.setType(val) 171 case "share": 172 return m.setShare(val) 173 case "options": 174 m.mount.Options = specutils.FilterMountOptions(strings.Split(val, ",")) 175 case "lifecycle": 176 return m.setLifecycle(val) 177 default: 178 return fmt.Errorf("invalid mount annotation: %s=%s", key, val) 179 } 180 return nil 181 } 182 183 func (m *MountHint) setType(val string) error { 184 switch val { 185 case tmpfs.Name, Bind: 186 m.mount.Type = val 187 default: 188 return fmt.Errorf("invalid type %q", val) 189 } 190 return nil 191 } 192 193 func (m *MountHint) setShare(val string) error { 194 switch val { 195 case container.String(): 196 m.share = container 197 case pod.String(): 198 m.share = pod 199 case shared.String(): 200 m.share = shared 201 default: 202 return fmt.Errorf("invalid share value %q", val) 203 } 204 return nil 205 } 206 207 func (m *MountHint) setLifecycle(val string) error { 208 switch val { 209 case containerLife.String(): 210 m.lifecycle = containerLife 211 case podLife.String(): 212 m.lifecycle = podLife 213 case sharedLife.String(): 214 m.lifecycle = sharedLife 215 default: 216 return fmt.Errorf("invalid lifecycle %q", val) 217 } 218 return nil 219 } 220 221 // shouldShareMount returns true if this mount should be configured as a shared 222 // mount that is shared among multiple containers in a pod. 223 func (m *MountHint) shouldShareMount() bool { 224 // TODO(b/142076984): Only support tmpfs for now. Bind mounts require a 225 // common gofer to mount all shared volumes. 226 return m.mount.Type == tmpfs.Name && m.share == pod 227 } 228 229 // ShouldOverlay returns true if this mount should be overlaid. 230 func (m *MountHint) ShouldOverlay() bool { 231 // TODO(b/142076984): Only support share=container for now. Once shared gofer 232 // support is added, we can overlay shared bind mounts too. 233 return m.mount.Type == Bind && m.share == container && m.lifecycle != sharedLife 234 } 235 236 // checkCompatible verifies that shared mount is compatible with master. 237 // Master options must be the same or less restrictive than the container mount, 238 // e.g. master can be 'rw' while container mounts as 'ro'. 239 func (m *MountHint) checkCompatible(replica *specs.Mount) error { 240 masterOpts := ParseMountOptions(m.mount.Options) 241 replicaOpts := ParseMountOptions(replica.Options) 242 243 if masterOpts.ReadOnly && !replicaOpts.ReadOnly { 244 return fmt.Errorf("cannot mount read-write shared mount because master is read-only, mount: %+v", replica) 245 } 246 if masterOpts.Flags.NoExec && !replicaOpts.Flags.NoExec { 247 return fmt.Errorf("cannot mount exec enabled shared mount because master is noexec, mount: %+v", replica) 248 } 249 if masterOpts.Flags.NoATime && !replicaOpts.Flags.NoATime { 250 return fmt.Errorf("cannot mount atime enabled shared mount because master is noatime, mount: %+v", replica) 251 } 252 return nil 253 } 254 255 // Precondition: m.mount.Type == Bind. 256 func (m *MountHint) fileAccessType() config.FileAccessType { 257 if m.share == shared { 258 return config.FileAccessShared 259 } 260 if m.shouldShareMount() { 261 return config.FileAccessExclusive 262 } 263 if m.share == container { 264 return config.FileAccessExclusive 265 } 266 return config.FileAccessShared 267 } 268 269 // FindMount finds the MountHint that applies to this mount. 270 func (p *PodMountHints) FindMount(mount *specs.Mount) *MountHint { 271 for _, m := range p.mounts { 272 if m.mount.Source == mount.Source { 273 return m 274 } 275 } 276 return nil 277 }