Repository navigation
Conversation
|
@tonycoco Could you fix the conflicts? Thanks for the contribution, I need to take a closer look but this seems fine. One thing to note is that changing the Users would need to build and specify their own kernel that includes SMB and/or NFS client capability. One possible alternative/workaround would be if the public homebrew recipe (which isn't managed by the Containerization project maintainers, incidentally) were capable of building and installing a default kernel from using a more expansive config that it embeds. |
476a9c0 to
b2d6e10
Compare
|
@jglogan Merge conflicts are now fixed. I have commits for the kernel and upstream kata as well in the loop but at least this unlocks the container side of things. I also have some changes to the Reference: apple/containerization#681 Tracking: |
|
@tonycoco Thanks! I'll let @dcantah handle apple/containerization#681 as he's already working with you on it. |
|
Looks good and also kata-containers/kata-containers#12374 has been merged! |
|
@jglogan this change is ready to go |
f3ea0d3 to
90cb0b3
Compare
|
cleaned up the commit and rebased |
266ffef to
7a61de3
Compare
ccba96d to
94e947c
Compare
|
Ping for activity again. Let me know if anything else is needed for the merge. |
94e947c to
ed0b4a9
Compare
|
Resolved merge conflicts in |
ed0b4a9 to
c790431
Compare
|
Any update on this? |
|
@tonycoco Thanks for the pings, and for your patience. I'll put this into the plan for this sprint. IIRC I said I wanted to test it locally (which I haven't done yet). I also need to do a little thinking about the architecture for the volume model. I don't think it means any changes to what your PR does, but what we might want to do is mark these options as experimental so that folks don't get wedded to a particular CLI shape. |
|
@tonycoco Could you create an issue describing the feature and use case(s), and add something to the description above indicating that this PR closes the issue? |
e16eea7 to
1dd7340
Compare
|
@jglogan made a few changes and rebased to latest on this. Ready for merge. |
1dd7340 to
bf153be
Compare
There was a problem hiding this comment.
@tonycoco See the review comments for this. I think most of this is workable, but the one thing that gives me concern is that this implementation can store a secret value (the SMB share password) as plain text in mount options on the volume and container (possible if someone creates a container using the client API and attaches an .smb Filesystem).
Before making code changes for this, could you propose a design here in the issue or PR comments for how we can retain the secret value securely (this would likely involve the keychain)? Requirements include:
- Keep the password out of both VolumeConfiguration.options and the persisted ContainerConfiguration
- Retrieve the secret server-side at bootstrap and not in Utility.swift on the client
- Reject password keys in Filesystem for the direct-API case
- Ensure the keychain item gets deleted with the volume
| destination: parsed.destination, | ||
| options: parsed.options | ||
| ) | ||
| } else { |
There was a problem hiding this comment.
Should we validate "local" here? Would there be any backward compatibility issues if we did?
We have the server side validation, but fail-fast would be cheap here.
There was a problem hiding this comment.
Good call. Switched the if/else if/else to a switch with explicit "smb", "nfs", and "local" cases and a throwing default.
No backward-compatibility concern that I can find: driver is a required (non-optional) key in VolumeConfiguration's decoder, and before this PR the server only ever persisted "local" since _create threw driverNotSupported for anything else. So every stored volume already has driver == "local".
| } | ||
|
|
||
| let journalConfig = try driverOpts["journal"].map { try Self.parseJournalConfig($0) } | ||
| let volume: VolumeConfiguration |
There was a problem hiding this comment.
Could you create a ContainerResource.MountOptionValidator as a package enum that ensures that keys and values are syntactically valid?
Perhaps a key validation regex like ^[A-Za-z][A-Za-z0-9_-]{0,63}$ and a value regex like ^[^,\s\p{Cc}]{0,1024}$? The principal thing we want to avoid is injecting commas into values, so the regex rejects them for now (if we need escaped commas in values, we can add that as a separate feature later).
For defense in depth, the validator should also be invoked for smb and nfs mounts in ContainerService.create and in RuntimeService.
An alternative we could look into is to represent the driver and mount options using a type that performs validation on init and deserialization so that we know the options are correct by construction. The one thing we'd need to do with this approach is ensure that we gracefully handle deserialization errors at startup - one invalid option on disk shouldn't break the whole app.
There was a problem hiding this comment.
Added ContainerResource.MountOptionValidator as a package enum with the key/value regexes you suggested (^[A-Za-z][A-Za-z0-9_-]{0,63}$ and ^[^,\s\p{Cc}]{0,1024}$). Options are checked in key order so errors are deterministic, and the violation message names the key but never echoes the value, since it may be a credential.
One refinement: share is validated as a mount source rather than a mount option. It's handed to mount(2) as its own argument and never joined into the comma-separated data string (resolvedMountOptions already filters it out), so the comma/whitespace rule doesn't buy anything there and would reject common Windows share names like //server/My Share. validateShare instead requires non-empty and no control characters (which would truncate or corrupt the C string), with a 4096-character cap. Everything else gets the strict rule.
It's invoked at all four points: VolumesService._create for smb/nfs, ContainersService.create for every mount (covers the direct-API case), RuntimeService.configureContainer before building the guest mount (rejects a tampered bundle with .invalidState, mirroring AttachmentNameValidator.validateStored), and the CLI. The server and runtime call sites go through a new Filesystem.validateMountOptions(), which checks the .smb/.nfs associated share as a source and mountOptions as options, so the validated set is exactly what resolvedMountOptions emits. It's a no-op for non-network types.
I went with validation-at-the-boundary rather than the validate-on-init type for now, since it keeps the Filesystem Codable path unchanged and avoids the "one bad option on disk breaks startup" problem you mentioned. Tests in MountOptionValidatorTests (including that share leniency doesn't leak into username=), plus service-level coverage in VolumesServiceTests and a Filesystem test in FilesystemSMBTests.
bf153be to
b81b40f
Compare
|
@jglogan Here's a proposed design for keeping the SMB password out of persisted state. It follows the existing registry-credential pattern ( Summary
Storage
Write path —
|
|
@jglogan Fixed up all the feedback sans the Keychain stuff. Let me know what you want to do there and I can write up another commit to get that in the same PR, or push it in a different one. |
Add support for 'nfs' and 'smb' volume drivers in 'container volume create', allowing network shares to be mounted directly inside the guest Linux VM via kernel NFS and CIFS mounts without creating a host-side ext4 disk image. - Extend Filesystem.FSType with .nfs and .smb cases (including optional volume name for in-use volume tracking across delete, prune, and disk usage) and deterministic mount option resolution - Map .nfs to Mount.any(type: "nfs") and .smb to Mount.any(type: "cifs") in RuntimeService - Handle 'nfs' and 'smb' drivers in VolumesService._create, validating non-empty share options and rejecting unsupported size options - Redact password options in 'container volume list' table output - Add unit tests and update volume and command reference documentation Closes apple#1911 Closes apple#1413
Add ContainerResource.MountOptionValidator, which checks that network volume driver options are syntactically valid before they are joined into the guest mount options string. Keys must start with a letter and contain only letters, digits, '_' or '-'; values must not contain commas, whitespace, or control characters. The share is validated as a mount source rather than a mount option, since mount(2) receives it as a separate argument, so it may contain spaces and commas but not control characters. The validator runs in VolumesService when a volume is created, in ContainersService.create for filesystems attached through the client API, in RuntimeService before building the guest mount, and in the volume create command for a fail-fast CLI error. - Add VolumeError.invalidOption for option validation failures - Validate the "local" driver explicitly when resolving volume mounts - Split command reference examples into separate code blocks - Use active voice in the SMB and NFS volume documentation
9a8f643 to
db0078d
Compare
Keep the SMB password out of persisted state. VolumesService removes the password from the driver options when an smb volume is created, stores it in the macOS keychain under com.apple.container.volume keyed by volume name, and removes it again when the volume is deleted. The persisted VolumeConfiguration, the container bundle, and the output of volume inspect and volume list therefore never carry the secret. At bootstrap, ContainersService reads the credential for each SMB volume the container mounts and passes it to the runtime in the bootstrap message under a new mountCredentials key. RuntimeService adds it only to the guest mount it builds in configureContainer; the configuration it keeps and reports through its state stays as stored, and the bundle on disk is never rewritten. A volume without a stored credential mounts without a password, for shares that allow guest access. The secret keys (password, pass, credentials, cred) are rejected in ContainersService.create for filesystems attached directly through the client API, and the runtime refuses to start a container whose stored configuration carries one. Only password is accepted by volume create for smb, and it requires username; nfs has no password option, so the secret keys are rejected there outright. The credential store is a small protocol so the services can be tested without the keychain. If the keychain write fails, the volume is not created; if the volume cannot be stored after the credential is saved, the credential is removed again. Mark the smb and nfs drivers as experimental in the help text and documentation, and document that a password may not contain commas or whitespace.
|
I implemented the Keychain stuff in 56e6fdd so you can see the shape, happy to rework any of it based on the questions below. Should be contained in that one commit. |
|
Also, the containerization |
Add NFS and SMB volume drivers via direct guest mounts
Adds
--driver smband--driver nfstocontainer volume create. Both drivers are experimental: they mount network shares directly inside the Linux guest through the kernel's CIFS and NFS clients, so there is no host-side mount hop and no block image.How it works
VolumesServicestores the share and driver options as volume metadata. Atcontainer run, the client resolves the volume into aFilesystem.smborFilesystem.nfsmount, whichRuntimeServicetranslates into a.anyguest mount of typecifsornfs.A new
ContainerResource.MountOptionValidatorchecks that option keys match^[A-Za-z][A-Za-z0-9_-]{0,63}$and values contain no commas, whitespace, or control characters, so a value can't inject extra mount options. It runs in the CLI,VolumesService,ContainersService.create(for filesystems attached directly through the client API), andRuntimeServicebefore the guest mount is built. Thesharevalue is validated separately as a mount source — it is passed tomount(2)as its own argument and never joined into the options string — so share names containing spaces work.SMB passwords
The SMB password is stored in the macOS keychain under
com.apple.container.volume, keyed by volume name. It is never written toVolumeConfiguration, the container bundle, or shown byvolume inspect/volume list.ContainersServicereads it from the keychain at bootstrap and passes it to the runtime in the bootstrap message; the runtime adds it only to the in-memory guest mount. The keychain item is removed when the volume is deleted. A password requiresusername; without either, the share is mounted as a guest.Secret keys (
password,pass,credentials,cred) are rejected for filesystems attached directly through the client API and for NFS volumes, and the runtime refuses to start a container whose stored configuration carries one.Design discussion: #1428 (comment)
Usage
SMB:
NFS:
Boolean mount flags such as
nolockormfsymlinksare passed as--opt nolock=with an empty value and serialized as bare flags.Kernel requirement
The guest kernel needs
CONFIG_CIFS(SMB) andCONFIG_NFS_FS(NFS). The default kernel, Kata 3.32.0, includes both (CIFS via kata-containers/kata-containers#12374), and the containerization example config has CIFS enabled as of apple/containerization#681. If you use a custom kernel, verify these options are set; without them the mount fails withENODEV.Known limitations / follow-ups
,,), which can be added later.Mountfailure message includes the mount data string, so a wrong SMB password is echoed in the mount error. Follow-up in apple/containerization to redactpassword=.container volume update; to change a password, delete and recreate the volume.Type of Change
Motivation and Context
Closes #1911
Closes #1413
Testing