-
Notifications
You must be signed in to change notification settings - Fork 926
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Experimental support for using an OCI repository as an alternative provider installation method #2170
base: main
Are you sure you want to change the base?
Conversation
d64d3a7
to
2e883cb
Compare
c08e220
to
ba2fce1
Compare
Just a note to self for later: Upstream in Go there's a recently-accepted proposal to add a safer filesystem abstraction called This likely won't be available soon enough for us to use it for our first round here, but we can try to design our extraction code to be compatible with the shape of that API so that we can adopt it later, so that we won't need to maintain a "safe file I/O" cross-platform abstraction forever ourselves. |
This extends our existing concept of "experimental features" to the CLI Configuration language, so that we can (in future commits) have configuration features that are not yet fully implemented or not yet ready to be constrained by our compatibility promises. As with all other uses of "experiments" in this codebase, these are available only if explicitly activated at build time and so experimental CLI config features would not be available in official release builds. Signed-off-by: Martin Atkins <[email protected]>
0e21c3c
to
b8a81df
Compare
This now has some basic real implementations of both the provider source and the "package location" type. As things currently stand there are two quirks:
I've run out of time for today, but I'll probably continue poking at this tomorrow. I'm currently testing using the following root module: terraform {
required_providers {
assume = {
source = "ghcr.io.opentofu-oci.example.com/apparentlymart/assume"
}
}
} ...and the following CLI Configuration to opt in to the ability to install a provider directly from an OCI repository without having to set up an provider_installation {
direct {
oci_registry_experiment = true
}
} |
@apparentlymart thanks, I'll try to debug why this is happening and I'll also add some content-type checks, maybe we need to ignore layers that are not tarballs. |
@apparentlymart found the bug, libregistry had a foreach-variable-reference issue when selecting the multi-arch manifest. Try running this:
|
9f93b4c
to
a7dcf9c
Compare
With the updated
Since we don't currently have any means to distribute the provider developer's signed checksums this currently falls into the same warning as for the other alternative installation methods where the dependency lock file ends up incomplete and needs to be explicitly completed using the Unfortunately because that command intentionally bypasses the CLI-configuration-level provider installation settings (because by default it assumes you want to pull the checksums from the origin registry) that command doesn't currently work, but if we decide to move forward with this design then I don't expect it will be too difficult to update that command to use the new (We still have the thing about returning the digest-based reference instead of the tag-based reference from the metadata request, but we're going to deal with that part soon with some further updates to libregistry.) |
This is a new provider installation method which is similar to network_mirror but uses a set of conventions for fetching a provider from an OCI distribution repository, instead of a wholly OpenTofu-specific protocol. This commit only introduces the CLI configuration block and the decoding and validation of its contents, and only for experimental builds. If someone tries to use this in an experimental build then it will return an error saying that the new method isn't supported yet. Full support for it will follow in later commits. Signed-off-by: Martin Atkins <[email protected]>
This is a stub for a future provider installation method that will use a user-provided template to translate a provider source address into an OCI distribution repository address and then use the OCI distribution registry protocol to obtain package metadata and, eventually, the packages themselves. Signed-off-by: Martin Atkins <[email protected]>
Previously we treated PackageLocation only as pure data, describing a location from which something could be retrieved. Unexported functions in package providercache then treated PackageLocation values as a closed union dispatched using a type switch. That strategy has been okay for our relatively-generic package locations so far, but in a future commit we intend to add a new kind of package location referring to a manifest in an OCI distribution repository, and installing from _that_ will require a nontrivial amount of OCI-distribution-specific logic that will be more tightly coupled to the getproviders.Source that will return such locations, and so we're switching to a model where _the location object itself_ is responsible for knowing how to install a provider package from its location, as a method of PackageLocation. The implementation of installing from each location type now moves from package providercache to package getproviders, which is arguably a better thematic home for that functionality anyway. For now these remain tested only indirectly through the tests in package providercache, since we didn't previously have any independent tests for these unexported functions. We might want to add more tightly-scoped unit tests for these to package getproviders in future, but for now this is not materially worse than it was before. Signed-off-by: Martin Atkins <[email protected]>
Previously we were using a string consisting of a hostname and a name concatenated with a slash, but the OCI distribution spec doesn't define any such address syntax -- that's something we've essentially invented for OpenTofu, albeit with strong inspiration from Docker -- so we'll use a struct type to represent these addresses internally and then deal with parsing the slash-concatenated form at the same time as we're doing all of the other parsing, for consistency. This means that it's now the responsibility of whatever is dealing with the address template to also deal with the splitting of the hostname and the repository name. Signed-off-by: Martin Atkins <[email protected]>
In addition to the OCI mirror and the ability to use service discovery for a fully-custom mapping from OpenTofu provider address to OCI repository address on a particular hostname, this introduces a special hostname suffix that can be used after any existing OCI registry hostname to achieve an opinionated default mapping to that registry's namespace of OCI repositories. We don't actually have a real domain to use for this yet, so for now we're using opentofu-oci.example.com as a placeholder. If we decide to move forward with this approach then we'd switch this to using a domain we actually own. Signed-off-by: Martin Atkins <[email protected]>
a38298f
to
d61e0f2
Compare
We actually got the tag vs. digest thing sorted today after all, so that's now in and this is basically working for a first installation from nothing. The verification of packages against checksums already recorded in the dependency lock file on subsequent installation doesn't seem to be working quite right yet: it's reporting a checksum failure even though the checksum actually matches. I'll figure out what's causing that next. Edit: I fixed the checksum verification problem, so this now supports reinstallation of a provider version previously recorded in the lock file. With that done, I think this is now minimally functional, and so I'm going to start researching what our options might be for actually authenticating the packages (to achieve something similar to the GPG-based authentication OpenTofu does for providers installed using its own registry protocol, but in a more container-ecosystem-idiomatic way). |
This is an initial implementation of OCIMirrorSource in terms of libregistry's OCI client code. It has the minimum required to select a suitable image manifest from a multi-platform manifest and return it as a PackageOCIObject location. It does not yet support package authentication at all, and it also currently fails because libregistry's client is returning a tag-based image manifest address, rather than the digest for the specific image it returned. We'll deal with both of those concerns in future commits. Co-authored-by: AbstractionFactory <[email protected]> Signed-off-by: Martin Atkins <[email protected]>
d61e0f2
to
057345b
Compare
This now uses the included OCI distribution client to pull the object by iterating over all of the directory entries in all of the layers that were included in the manifest. This is a minimal initial implementation for experimenting with, but it has numerous TODOs and FIXMEs to attend to before we could use this in a non-experimental capacity. Co-authored-by: AbstractionFactory <[email protected]> Signed-off-by: Martin Atkins <[email protected]>
057345b
to
5fcfef9
Compare
Some early notes on package authentication. Nothing below is a decision; I'm just writing down what I've learned so far for later reference. The prevailing conventions for container authentication are those implemented by Cosign. Here's what I've understood so far about the shape of that:
As a starting point I'm going to experiment with this new checksum type for capturing image manifest digests directly into the dependency lock file, purely as an additional verification method. I need to study Cosign's details more before getting stuck into the actual signature verification part, and I expect we'll need to grow the libregistry OCI client API a little more to expose the additional operations needed to find and fetch the signature and other cosign metadata for a multi-platform image manifest. |
Cross-linking the cosign issue: #307 |
fd0d2c0
to
84e0151
Compare
I encountered an interesting new design challenge while working through implementing the new hash scheme today: Previously we had one hash scheme that supported everything (the The new scheme I've added today, which for now is called This has a few different interesting implications, but the most troublesome one for right now is that the current package authentication/verification model doesn't differentiate between pre-installation and post-installation authentication/verification: it expects to be able to perform all checks before installation (e.g. based on the Therefore I expect to need to make some tweaks to the authentication model so that we can more cleanly separate the pre-install and post-install checks. For OCI manifest locations in particular we can only check It's getting near the end of my work week now so I don't really have time to get into solving that properly today, but I'll think about it some more next week. |
An OCI registry talks about the content of objects using its own special digest scheme, and we can't derive a "h1:" checksum directly from those, so instead we'll follow a similar approach as with the legacy "zh:" scheme we use to work around some comparable limitations of the OpenTofu provider registry protocol where it only knows how to talk about whole-zip-file checksums, and not checksums of their contents. As of this commit nothing is actually using this scheme, but OCIMirrorSource and PackageOCIObject will make more use of it in future commits to allow us to pre-populate hashes for all platforms whenever a container image is validly signed. Signed-off-by: Martin Atkins <[email protected]>
Our PackageAuthentication model was designed for a world where at least some authentication tasks need to wait until after we've fetched a package from a remote location, but for OCIMirrorSource we have the advantage that the repository is content-addressable and so we can check ahead of time whether the platform-specific manifest is signed and then we only need to make sure during installation that the manifest and layers actually match their digests. Therefore this introduces NewPrecheckedAuthentication to allow for this special situation where all of the authentication happens inside a Source.PackageMeta, rather than some being delayed until the package has been fetched. This PackageAuthentication implementation just returns exactly what it was given at instantiation, and so OCIMirrorSource.PackageMeta can for now report just that the checksum was verified because fetching the package implicitly verifies its checksum. In future commits we'll also integrate Cosign signature checks into OCIMirrorSource.PackageMeta, and if that succeeds we'll be able to report that the package is to be treated as signed as long as its checksum is valid during installation. Signed-off-by: Martin Atkins <[email protected]>
84e0151
to
ca2e9f5
Compare
After some further consideration today I realized that the main thing that's special about installing from an OCI repository is that the repository is content-addressable and so installing from the "location" returned by Therefore I've added a new special Since we don't yet have enough Tomorrow I'm going to investigate exactly what additional information we'd need to determine whether a particular manifest has a signature and, if so, to fetch the information required to verify that signature. At RFC time we'll also need to ponder whether the environment variable That environment variable was introduced to make OpenTofu fail if any package from the main OpenTofu registry lacks a valid signature, and so I expect that anyone who has set it would prefer similar treatment for installation from OCI repositories too, but the environment variable name is a bit of a misnomer for that case since we won't actually be using GPG for OCI object signing. For the sake of this experimental implementation I'm going to ignore that question and just make it return signingSkipped when there doesn't seem to be any signing metadata available for a particular package, since that's a plausible place to start. Despite that, it won't be effective for an attacker to delete the signature metadata for a previously-signed manifest, because we'll use the manifest digests from the dependency lock file to ensure that the manifests all still match what we had previously verified as signed. |
@apparentlymart there is an older signature verification mechanism that isn't as complex as cosign (which won't be available in libregistry as long as the outstanding issues regarding stability and support are resolved in their go library). I never managed to get it to work on previous projects (mainly due to a lack of tooling a the time), but Docker Content Trust may be worth looking at. |
My intention with Cosign for the moment was just to understand how their verification method works as a protocol, rather than as a specific Go library implementation, since we might be able to reimplement just that part either directly in OpenTofu or in libregistry, without depending on the reference implementation at all. Of course, whether that is viable will depend on how complicated the protocol is and how many different variations it supports. If it seems like a separate verification-only implementation would either be too complex or too hard to maintain over time then of course I'd consider other alternatives instead. |
We're intending to add at least two features that will interact with OCI registries, but it would be undesirable to have to configure each of those features separately. Therefore this introduces a new optional oci_registries block to the CLI configuration which, for this initial commit, deals only with the concern of OCI registry authentication. Since it's pretty likely that someone who is invested in the OCI ecosystem is also using Docker CLI in some way, this also includes a helpful fallback to infer the same settings from the Docker CLI configuration when there's no direct configuration for OpenTofu, to get a better "out of box" experience for that scenario. (In particular, this makes OpenTofu able to consume what "docker login" produces.) Signed-off-by: Martin Atkins <[email protected]>
fab85a6
to
3388afc
Compare
I was away from this for a while both because I was helping with some v1.9 release work and because of the US thanksgiving holiday. I haven't yet done the promised research into cosign and other container image signing strategies, since I picked up something a little more digestible today as a first task after the holiday. Today I slightly grew the scope of this PR to also include a general mechanism for configuring authentication mechanisms for OCI registries. Although I've implemented it in this provider-installation-focused PR, my intention is for it to be a cross-cutting configuration mechanism that would be used for any OpenTofu CLI feature that interacts with OCI registries1. The higher-level client in This actually supports two different modes of configuration, which I'll call the "explicit" and "implicit" modes for now:
The implicit mode is intended for the presumed-common case where someone who is invested in the OCI ecosystem is probably also using Docker and therefore is likely to have an already-configured Docker CLI. But explicit mode can deal with any situations where that doesn't apply: either the user doesn't use Docker CLI at all, or for whatever reason they'd prefer OpenTofu to use different settings than what Docker CLI would use. I've written this under the assumption that the client for Docker's credential helper protocol will live in Footnotes
|
Podman established a non-Docker-specific location for auth configuration, using the same file format as Docker CLI's file. Other tools in the ecosystem then followed Podman's lead. Therefore we'll also support Podman-style search paths, preferring them over Docker CLI's when both are present since the Podman one is the more general of the two. Signed-off-by: Martin Atkins <[email protected]>
1fcafd3
to
f4c8285
Compare
For now this is just a prototype of part of what we're discussing over in #2163.
It might eventually turn into a real implementation, but for now the goal is to just get the basic behavior in place so that we can try it out against some real OCI registry implementations, and we can get some experience with the new workflow for building and publishing providers into an OCI registry before we finalize the design in the RFC.
For now the new feature is guarded by the "experiments" mechanism, so it's only available when OpenTofu is built with experiments enabled. This is to reinforce that nothing here is final yet. It remains to be seen whether we'll merge with it still guarded in this way or if we'll wait until the design is finalized enough to enable it in release builds.
You can enable experiments when building the
tofu
executable, like this:go install -ldflags="-X 'main.experimentsAllowed=yes'" ./cmd/tofu