This PR fixes a number of issues:
- validate descriptor digests before filesystem use
- validate container and exec ids before building paths in
ManagedContainer
- Check for attribute length when consuming extended attributes in
ContainerizationEXT4
- Check directory name length when formatting an ext4 fs
- Prevent OCI image layout load from reading host files through a
symlink
- Validate registry auth realm before exchanging credentials
---------
Signed-off-by: Kathryn Baldauf <k_baldauf@apple.com>
Co-authored-by: John Logan <john_logan@apple.com>
Co-authored-by: Raj Aryan Singh <rajaryan_singh@apple.com>
Co-authored-by: Raj <realrajaryan@gmail.com>
- Omit the redundant `v8` variant for arm64 when rendering
`description`, so equal arm64 platforms always describe as
`linux/arm64` — matching how Docker and containerd display
the platform. Other variants (`arm/v7`) and architectures
(`amd64`) are unaffected.
- Only the rendered `description` changes. The stored `variant`
and the `Codable` encoding are untouched, so OCI content
digests remain stable.
- Closesapple/container#1542 (normalization-consistency aspect).
This fixes a latent bug in the `Platform` types equality operator where
two platforms with differing OS's would be treated as the same if they
both had variant set to 'v8' or nil
Fixes#518.
## What
vminitd logs the full OCI spec and exec process at debug level in
`ManagedContainer` ("created bundle with spec …", "creating exec process
with …"), which puts every `NAME=value` environment entry into the boot
log. Environment variables routinely carry secrets, so `container logs
--boot web | grep PASSWORD` reproduces the leak exactly as described in
#518.
Rather than redacting at the call sites, this makes the redacted form
the *default* rendering of the types that own an environment: `Process`
and `Hook` conform to `CustomStringConvertible` with values masked and
names kept. `Spec` and `Hooks` inherit it, because Swift's
reflection-based description renders a nested value through that value's
own `description`.
The effect is that any `\(spec)` or `\(process)` is safe without the
author knowing this file exists, which is what stops a log line added
later from reintroducing the leak. The two existing log sites are
unchanged, so this no longer touches vminitd at all.
Two details worth calling out:
- **`Codable` is untouched.** `description` governs text rendering only,
so an encoded spec still carries the real values and nothing changes
about what is written to disk or sent to the guest. The unredacted
environment also remains available to callers through `process.env`.
- **`description` renders through a mirror** rather than a hand-written
field list. `Process` has 13 fields; listing them by hand would drop the
rest from the log line and would rot as fields are added.
## Verification
- New `SpecRedactionTests` (9 tests) cover: a whole `Spec` interpolated
into a log line never renders the values; `String(describing:)` and
`String(reflecting:)` are redacted too; variable names survive;
`NAME`-only inherit entries pass through; `NAME=` and values containing
further `=` are masked whole; encoding round-trips with the real values;
rendering does not mutate; and the other fields are still rendered.
- Negative control: with the redaction disabled the suite fails with 13
issues, and the output shows the secret in the clear, reproducing #518.
- Full `ContainerizationOCITests` passes, 58 tests in 9 suites.
- `swift format lint --strict --configuration .swift-format-nolint` is
clean, and `swift format` leaves both files unchanged.
Every line here is one I can explain and justify; the reasoning above is
the complete rationale for each change.
## Summary
`Platform.==` treats `arm64` with `nil` variant as equal to `arm64/v8`,
but `hash(into:)` used `description` which serializes them differently
(`linux/arm64` vs `linux/arm64/v8`). This violates the `Hashable`
contract — equal values must produce the same hash.
### Root cause
```swift
// == returns true for these two
let a = Platform(arch: "arm64", os: "linux", variant: nil)
let b = Platform(arch: "arm64", os: "linux", variant: "v8")
a == b // true ✓
// but hash was different — broken
a.hashValue == b.hashValue // false ✗ (before this fix)
```
This mismatch caused `Set<Platform>` and `Dictionary<Platform, ...>`
lookups to silently miss entries when one platform was decoded from JSON
(no `variant` field in the manifest) and another was created via
`Platform(from:)` or `Platform.current` (which both set `variant =
"v8"`).
### Practical consequence
In `apple/container`, this manifests as inconsistent platform-string
normalization across stages of a single `container build` — some stages
log `linux/arm64`, others `linux/arm64/v8` — which can cause `COPY
--from=<stage>` to fail to resolve the source stage under concurrent
builds. See apple/container#1542.
### Fix
`hash(into:)` now normalizes `arm64` with `nil` variant to `"v8"` before
hashing, matching the existing `==` behavior.
`.totalFileAllocatedSizeKey` returns nil for directories on Darwin but
on Linux it returns `st_blocks * st_blksize` (4 KB each) in Foundation.
The empty-store test summed three directory inodes on Linux and failed
with `#expect(size == 0)`. This change adds filter on the enumerator to
regular files only so the totals are content-only and will work for both
Darwin and Linux.
This PR adds `totalAllocatedSize()` to the `ContentStore` protocol so it
can be used to get the on-disk footprint without reaching past the
abstraction. `LocalContentStore` implements it by walking its base path,
covering both committed blobs and active ingest sessions.
If you're requesting on `/v2/` with basic auth, AWS ECR will return 400
Bad Request and won't provide `www-authenticate` information.
Retry the request after removing the Authorization header.
Continue PR #429Fixedapple/container#847
Signed-off-by: ChengHao Yang <17496418+tico88612@users.noreply.github.com>
We had a couple spots we were using this logic, probably better to shove
it in a function. The other thing this fixes is today .current was only
checking for arm64 which on a linux host uname with return aarch64, so
we'll fatalError trying to use it today..
- Will be used by the fix for apple/container#1253.
- We use a common keychain ID between the `container CLI and
`container-core-images`, but right now keychain entries created by the
CLI must be opted-in for `container-core-images`.
- Using an access group with a notarized application should give both
apps automatic access to the keychain.
This PR adds OCI Image Spec v1.1 artifact support. It extends Manifest,
Index, and Descriptor with the `subject` and `artifactType` fields, adds
a `referrers()` method to RegistryClient implementing the OCI
Distribution Spec v1.1 referrers API. Also, I've added some unit unit
tests for backward compatibility and roundtrip encpding of all new
fields
## What
Raise the constant from 127 to 255 in `Reference.swift` and update the
derived `referenceTotalLengthMax` accordingly.
## Why
Closes#453.
The [OCI distribution
spec](https://github.com/opencontainers/distribution-spec/blob/main/spec.md)
states that the **name** component of an image reference (registry host
+ repository path) may be at most 255 bytes. The previous hard-coded
limit of 127 characters incorrectly rejected valid references that used
long registry hostnames or deep path hierarchies.
The old `referenceTotalLengthMax = 255` was also inconsistent: with a
127-char name cap, a 255-char total reference would only allow a very
short tag. The new value is derived explicitly as `nameTotalLengthMax
(255) + separator (1) + tagLengthMax (128) = 384`.
## How
- `nameTotalLengthMax`: 127 → 255
- `tagLengthMax`: new constant (128) documenting the maximum tag length
already enforced by the tag regex (`{0,127}` + leading char = 128
chars).
- `referenceTotalLengthMax`: computed from the two constants above (384)
rather than hard-coded to 255.
## Testing
Added three new cases in `ReferenceTests.swift`:
- ✅ Name of 128 characters (registry + path) — previously rejected, now
accepted
- ✅ Name of exactly 255 characters — at the OCI spec maximum, accepted
- ❌ Name of 256 characters — one over the limit, rejected
- Add a `list` function that is needed for the `container registry list`
command
- Updates to `KeychainHelper` and `KeychainQuery` variable/parameter
names
- Updates to test cases
Closely related to https://github.com/apple/container/issues/305 I would
like to override the used SSL TrustRoots via standard env variables.
This here would add this configuration and would give an entrypoint for
an implementation of 305 to provide CLI flags or similar.
This has no tests yet, as this would require setting up something like a
MITM proxy when testing against a registry.
As I am unfamiliar with the codebase, I would be willing to do this, but
would require a first nudge on where to best implement this.
To actually use this, we would need to add the allowed env variables to
the `container system start` command env filter.
Allows decoding of minimal OCI specs with empty linux objects by
providing default values for missing fields. Also added unit test to
ensure that empty Linux struct {} works correctly with the fix.
Fixes#369
Per the OCI Image Spec, manifest descriptor annotations are optional.
Previously, archives without annotations would fail to import with
"Failed to import image".
**Changes:**
- Modified `getImageReferencefromDescriptor` to return digest-based
references (`untagged@sha256:...`) when annotations are missing
- Removed guard that skipped manifests without annotations
- Added test case with `scratch_no_annotations.tar`
**Testing:**
All 167 tests pass, including new test for images without annotations.
The `mediaType` field in the `Index` struct was defined as a required
field, but according to the [OCI Image Index
Specification](https://github.com/opencontainers/image-spec/blob/main/image-index.md),
this field is optional.
This caused failures when loading OCI archives where the `index.json`
omits the top-level `mediaType` field, which is valid per the spec.
Tools like skopeo can generate such archives.
## Error before fix
```
keyNotFound(CodingKeys(stringValue: "mediaType", intValue: nil))
```
## Changes
- Changed `Index.mediaType` from `String` to `String?`
- Updated initializer to accept optional `mediaType` parameter
- Added comment documenting that field is optional per OCI spec
## Testing
Verified that OCI archives without a top-level `mediaType` field in
`index.json` now load successfully.
Fixes https://github.com/apple/container/issues/330
Many fields on the various OCI types use "omitempty" for encoding and
decoding the json representation in golang. This PR adds custom json
decoder functions to allow for behavior similar to "omitempty".
---------
Signed-off-by: Kathryn Baldauf <k_baldauf@apple.com>
Fixes#320
Today we don't actually setup any cgroup limits, as because there's a
1-1 mapping from container<->vm we can just use the VMs resources as the
limit (can't use more than 1GB if that's all the guest sees :) ).
However, if we ever supported > 1 container in the guest it'd be
necessary to actually setup the cg limits. This change just sets a
memory limit and cpu toggles to match whatever was specific for the
container.
- Closes#255.
- Fixes ProxyUtils so that the environment variable to be used for proxy
selection is determined by the request scheme.
- RegistryClient uses ProxyUtils to get the proxy URL used by the
HTTPClient.
- Tweak hostname resolution error message to avoid misleading output if
the proxy hostname cannot be resolved.
swift-nio's public export of NIOFileSystem was removed in 2.86.1:
https://github.com/apple/swift-nio/pull/3370
NIOFileSystem was not yet supposed to be public, but _NIOFileSystem
depended on it as a public import. This made it possible for
`containerization` to see the `NIOFileSystem` package by accident.
Replacing the use of `NIOFileSystem` by `_NIOFileSystem`, as used
elsewhere, fixes the problem.
## Why does CI currently pass?
The change in `swift-nio` does not currently cause `containerization`'s
CI to fail because `Package.resolved` pins `swift-nio` to 2.83.0, before
the change was made. New versions of upstream dependencies will not be
tested until `Package.resolved` is explicitly updated.
When containerization is built as a dependency of a end-user project,
its `Package.resolved` file is ignored. Instead, the dependency
constraints from containerization's Package.swift file are combined with
those of the project and any other library dependencies, so SwiftPM or
Xcode can find a set of mutually compatible packages. This can lead to
new versions of containerization's upstream dependencies being used,
even though those versions have never been tested in CI.
The build failure can be demonstrated by creating a new package which
depends on `containerization` but does not constrain package versions:
```
% swift package init --type executable
Creating executable package: test
Creating Package.swift
Creating Sources
Creating Sources/test/test.swift
% cat > Package.swift <<EOF
heredoc> // swift-tools-version: 6.2
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "test",
platforms: [
.macOS(.v26),
],
dependencies: [
.package(url: "https://github.com/apple/containerization", from: "0.7.2"),
],
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
// Targets can depend on other targets in this package and products from dependencies.
.executableTarget(
name: "test",
dependencies: [
.product(name: "Containerization", package: "containerization"),
]
),
]
)
EOF
% swift build
...
/private/tmp/test/.build/checkouts/containerization/Sources/ContainerizationOCI/Client/RegistryClient+Fetch.swift:25:8: error: no such module 'NIOFileSystem'
23 |
24 | #if os(macOS)
25 | import NIOFileSystem
| `- error: no such module 'NIOFileSystem'
26 | #endif
27 |
```
In general I believe it's clever to synthesize an index for
single-platform image manifest, but we still need a way to distinguish
it. Add a dedicated annotation is the slightest change I've come up
with, and it's also OCI compliant. With this change come in, we can work
around https://github.com/apple/container/issues/212 and imitate the
behavior of other runtime with `container`.
Note that since `cctl` is meant to be a dedicated tool for inspecting
the Containerization framework itself, I didn't apply the indirection
for it, and it will be as-is with the genuine storage.
There was a bug where the `www-authenticate` header in the HTTP response
from a registry would not be parsed accurately.
Specifically, if the header value had more than one `<space>` character,
the entire header would be ignored. This PR fixes this bug and adds unit
test to detect this in the future.
Fixes https://github.com/apple/container/issues/240
And most likely fixes https://github.com/apple/container/issues/237
Signed-off-by: Aditya Ramani <a_ramani@apple.com>
Continue the allocations journey for anything that is in the codepaths
for pulling images. This time there's a couple spots in archive and ext4
we can get rid of some copies.
The digest is calculated based on written json data, but JSONEncoder
doesn't preserve order bby default, so the digest is not consistent.
Signed-off-by: Alexey <makhov.alex@gmail.com>
We were doing intermediate step copies to Data objects both for push and
pull. We only need the data for the lifetime of the singular writes to
update the state of the checksums and to write to disk somewhere, so we
can use a view into the buffer from the http client to satisfy this.