1 /* 2 Copyright 2014 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // TODO(thockin): This whole pkg is pretty linux-centric. As soon as we have 18 // an alternate platform, we will need to abstract further. 19 20 package mount 21 22 import ( 23 "fmt" 24 "os" 25 "path/filepath" 26 "strings" 27 28 utilexec "k8s.io/utils/exec" 29 ) 30 31 const ( 32 // Default mount command if mounter path is not specified. 33 defaultMountCommand = "mount" 34 // Log message where sensitive mount options were removed 35 sensitiveOptionsRemoved = "<masked>" 36 ) 37 38 // Interface defines the set of methods to allow for mount operations on a system. 39 type Interface interface { 40 // Mount mounts source to target as fstype with given options. 41 // options MUST not contain sensitive material (like passwords). 42 Mount(source string, target string, fstype string, options []string) error 43 // MountSensitive is the same as Mount() but this method allows 44 // sensitiveOptions to be passed in a separate parameter from the normal 45 // mount options and ensures the sensitiveOptions are never logged. This 46 // method should be used by callers that pass sensitive material (like 47 // passwords) as mount options. 48 MountSensitive(source string, target string, fstype string, options []string, sensitiveOptions []string) error 49 // Unmount unmounts given target. 50 Unmount(target string) error 51 // List returns a list of all mounted filesystems. This can be large. 52 // On some platforms, reading mounts directly from the OS is not guaranteed 53 // consistent (i.e. it could change between chunked reads). This is guaranteed 54 // to be consistent. 55 List() ([]MountPoint, error) 56 // IsLikelyNotMountPoint uses heuristics to determine if a directory 57 // is not a mountpoint. 58 // It should return ErrNotExist when the directory does not exist. 59 // IsLikelyNotMountPoint does NOT properly detect all mountpoint types 60 // most notably linux bind mounts and symbolic link. For callers that do not 61 // care about such situations, this is a faster alternative to calling List() 62 // and scanning that output. 63 IsLikelyNotMountPoint(file string) (bool, error) 64 // GetMountRefs finds all mount references to pathname, returning a slice of 65 // paths. Pathname can be a mountpoint path or a normal directory 66 // (for bind mount). On Linux, pathname is excluded from the slice. 67 // For example, if /dev/sdc was mounted at /path/a and /path/b, 68 // GetMountRefs("/path/a") would return ["/path/b"] 69 // GetMountRefs("/path/b") would return ["/path/a"] 70 // On Windows there is no way to query all mount points; as long as pathname is 71 // a valid mount, it will be returned. 72 GetMountRefs(pathname string) ([]string, error) 73 } 74 75 // Compile-time check to ensure all Mounter implementations satisfy 76 // the mount interface. 77 var _ Interface = &Mounter{} 78 79 // MountPoint represents a single line in /proc/mounts or /etc/fstab. 80 type MountPoint struct { // nolint: golint 81 Device string 82 Path string 83 Type string 84 Opts []string // Opts may contain sensitive mount options (like passwords) and MUST be treated as such (e.g. not logged). 85 Freq int 86 Pass int 87 } 88 89 type MountErrorType string // nolint: golint 90 91 const ( 92 FilesystemMismatch MountErrorType = "FilesystemMismatch" 93 HasFilesystemErrors MountErrorType = "HasFilesystemErrors" 94 UnformattedReadOnly MountErrorType = "UnformattedReadOnly" 95 FormatFailed MountErrorType = "FormatFailed" 96 GetDiskFormatFailed MountErrorType = "GetDiskFormatFailed" 97 UnknownMountError MountErrorType = "UnknownMountError" 98 ) 99 100 type MountError struct { // nolint: golint 101 Type MountErrorType 102 Message string 103 } 104 105 func (mountError MountError) String() string { 106 return mountError.Message 107 } 108 109 func (mountError MountError) Error() string { 110 return mountError.Message 111 } 112 113 func NewMountError(mountErrorValue MountErrorType, format string, args ...interface{}) error { 114 mountError := MountError{ 115 Type: mountErrorValue, 116 Message: fmt.Sprintf(format, args...), 117 } 118 return mountError 119 } 120 121 // SafeFormatAndMount probes a device to see if it is formatted. 122 // Namely it checks to see if a file system is present. If so it 123 // mounts it otherwise the device is formatted first then mounted. 124 type SafeFormatAndMount struct { 125 Interface 126 Exec utilexec.Interface 127 } 128 129 // FormatAndMount formats the given disk, if needed, and mounts it. 130 // That is if the disk is not formatted and it is not being mounted as 131 // read-only it will format it first then mount it. Otherwise, if the 132 // disk is already formatted or it is being mounted as read-only, it 133 // will be mounted without formatting. 134 // options MUST not contain sensitive material (like passwords). 135 func (mounter *SafeFormatAndMount) FormatAndMount(source string, target string, fstype string, options []string) error { 136 return mounter.FormatAndMountSensitive(source, target, fstype, options, nil /* sensitiveOptions */) 137 } 138 139 // FormatAndMountSensitive is the same as FormatAndMount but this method allows 140 // sensitiveOptions to be passed in a separate parameter from the normal mount 141 // options and ensures the sensitiveOptions are never logged. This method should 142 // be used by callers that pass sensitive material (like passwords) as mount 143 // options. 144 func (mounter *SafeFormatAndMount) FormatAndMountSensitive(source string, target string, fstype string, options []string, sensitiveOptions []string) error { 145 return mounter.formatAndMountSensitive(source, target, fstype, options, sensitiveOptions) 146 } 147 148 // getMountRefsByDev finds all references to the device provided 149 // by mountPath; returns a list of paths. 150 // Note that mountPath should be path after the evaluation of any symblolic links. 151 func getMountRefsByDev(mounter Interface, mountPath string) ([]string, error) { 152 mps, err := mounter.List() 153 if err != nil { 154 return nil, err 155 } 156 157 // Finding the device mounted to mountPath. 158 diskDev := "" 159 for i := range mps { 160 if mountPath == mps[i].Path { 161 diskDev = mps[i].Device 162 break 163 } 164 } 165 166 // Find all references to the device. 167 var refs []string 168 for i := range mps { 169 if mps[i].Device == diskDev || mps[i].Device == mountPath { 170 if mps[i].Path != mountPath { 171 refs = append(refs, mps[i].Path) 172 } 173 } 174 } 175 return refs, nil 176 } 177 178 // GetDeviceNameFromMount given a mnt point, find the device from /proc/mounts 179 // returns the device name, reference count, and error code. 180 func GetDeviceNameFromMount(mounter Interface, mountPath string) (string, int, error) { 181 mps, err := mounter.List() 182 if err != nil { 183 return "", 0, err 184 } 185 186 // Find the device name. 187 // FIXME if multiple devices mounted on the same mount path, only the first one is returned. 188 device := "" 189 // If mountPath is symlink, need get its target path. 190 slTarget, err := filepath.EvalSymlinks(mountPath) 191 if err != nil { 192 slTarget = mountPath 193 } 194 for i := range mps { 195 if mps[i].Path == slTarget { 196 device = mps[i].Device 197 break 198 } 199 } 200 201 // Find all references to the device. 202 refCount := 0 203 for i := range mps { 204 if mps[i].Device == device { 205 refCount++ 206 } 207 } 208 return device, refCount, nil 209 } 210 211 // IsNotMountPoint determines if a directory is a mountpoint. 212 // It should return ErrNotExist when the directory does not exist. 213 // IsNotMountPoint is more expensive than IsLikelyNotMountPoint. 214 // IsNotMountPoint detects bind mounts in linux. 215 // IsNotMountPoint enumerates all the mountpoints using List() and 216 // the list of mountpoints may be large, then it uses 217 // isMountPointMatch to evaluate whether the directory is a mountpoint. 218 func IsNotMountPoint(mounter Interface, file string) (bool, error) { 219 // IsLikelyNotMountPoint provides a quick check 220 // to determine whether file IS A mountpoint. 221 notMnt, notMntErr := mounter.IsLikelyNotMountPoint(file) 222 if notMntErr != nil && os.IsPermission(notMntErr) { 223 // We were not allowed to do the simple stat() check, e.g. on NFS with 224 // root_squash. Fall back to /proc/mounts check below. 225 notMnt = true 226 notMntErr = nil 227 } 228 if notMntErr != nil { 229 return notMnt, notMntErr 230 } 231 // identified as mountpoint, so return this fact. 232 if notMnt == false { 233 return notMnt, nil 234 } 235 236 // Resolve any symlinks in file, kernel would do the same and use the resolved path in /proc/mounts. 237 resolvedFile, err := filepath.EvalSymlinks(file) 238 if err != nil { 239 return true, err 240 } 241 242 // check all mountpoints since IsLikelyNotMountPoint 243 // is not reliable for some mountpoint types. 244 mountPoints, mountPointsErr := mounter.List() 245 if mountPointsErr != nil { 246 return notMnt, mountPointsErr 247 } 248 for _, mp := range mountPoints { 249 if isMountPointMatch(mp, resolvedFile) { 250 notMnt = false 251 break 252 } 253 } 254 return notMnt, nil 255 } 256 257 // MakeBindOpts detects whether a bind mount is being requested and makes the remount options to 258 // use in case of bind mount, due to the fact that bind mount doesn't respect mount options. 259 // The list equals: 260 // 261 // options - 'bind' + 'remount' (no duplicate) 262 func MakeBindOpts(options []string) (bool, []string, []string) { 263 bind, bindOpts, bindRemountOpts, _ := MakeBindOptsSensitive(options, nil /* sensitiveOptions */) 264 return bind, bindOpts, bindRemountOpts 265 } 266 267 // MakeBindOptsSensitive is the same as MakeBindOpts but this method allows 268 // sensitiveOptions to be passed in a separate parameter from the normal mount 269 // options and ensures the sensitiveOptions are never logged. This method should 270 // be used by callers that pass sensitive material (like passwords) as mount 271 // options. 272 func MakeBindOptsSensitive(options []string, sensitiveOptions []string) (bool, []string, []string, []string) { 273 // Because we have an FD opened on the subpath bind mount, the "bind" option 274 // needs to be included, otherwise the mount target will error as busy if you 275 // remount as readonly. 276 // 277 // As a consequence, all read only bind mounts will no longer change the underlying 278 // volume mount to be read only. 279 bindRemountOpts := []string{"bind", "remount"} 280 bindRemountSensitiveOpts := []string{} 281 bind := false 282 bindOpts := []string{"bind"} 283 284 // _netdev is a userspace mount option and does not automatically get added when 285 // bind mount is created and hence we must carry it over. 286 if checkForNetDev(options, sensitiveOptions) { 287 bindOpts = append(bindOpts, "_netdev") 288 } 289 290 for _, option := range options { 291 switch option { 292 case "bind": 293 bind = true 294 case "remount": // Do nothing. 295 default: 296 bindRemountOpts = append(bindRemountOpts, option) 297 } 298 } 299 300 for _, sensitiveOption := range sensitiveOptions { 301 switch sensitiveOption { 302 case "bind": 303 bind = true 304 case "remount": // Do nothing. 305 default: 306 bindRemountSensitiveOpts = append(bindRemountSensitiveOpts, sensitiveOption) 307 } 308 } 309 310 return bind, bindOpts, bindRemountOpts, bindRemountSensitiveOpts 311 } 312 313 func checkForNetDev(options []string, sensitiveOptions []string) bool { 314 for _, option := range options { 315 if option == "_netdev" { 316 return true 317 } 318 } 319 for _, sensitiveOption := range sensitiveOptions { 320 if sensitiveOption == "_netdev" { 321 return true 322 } 323 } 324 return false 325 } 326 327 // PathWithinBase checks if give path is within given base directory. 328 func PathWithinBase(fullPath, basePath string) bool { 329 rel, err := filepath.Rel(basePath, fullPath) 330 if err != nil { 331 return false 332 } 333 if StartsWithBackstep(rel) { 334 // Needed to escape the base path. 335 return false 336 } 337 return true 338 } 339 340 // StartsWithBackstep checks if the given path starts with a backstep segment. 341 func StartsWithBackstep(rel string) bool { 342 // normalize to / and check for ../ 343 return rel == ".." || strings.HasPrefix(filepath.ToSlash(rel), "../") 344 } 345 346 // sanitizedOptionsForLogging will return a comma separated string containing 347 // options and sensitiveOptions. Each entry in sensitiveOptions will be 348 // replaced with the string sensitiveOptionsRemoved 349 // e.g. o1,o2,<masked>,<masked> 350 func sanitizedOptionsForLogging(options []string, sensitiveOptions []string) string { 351 separator := "" 352 if len(options) > 0 && len(sensitiveOptions) > 0 { 353 separator = "," 354 } 355 356 sensitiveOptionsStart := "" 357 sensitiveOptionsEnd := "" 358 if len(sensitiveOptions) > 0 { 359 sensitiveOptionsStart = strings.Repeat(sensitiveOptionsRemoved+",", len(sensitiveOptions)-1) 360 sensitiveOptionsEnd = sensitiveOptionsRemoved 361 } 362 363 return strings.Join(options, ",") + 364 separator + 365 sensitiveOptionsStart + 366 sensitiveOptionsEnd 367 } 368