How to reference artifacts that move? #31
Replies: 9 comments 3 replies
-
My own preference is to always qualify the reference with a registry, repo, and tag/sha. I look at this from the perspective of a Debian user that always worries a repository I add to apt will decide that they want to provide an updated critical library that would be seen as valid (not positive that attack is actually possible, but it's certainly not clear what repository I'm pulling from when I install new packages). To handle the use case of making a complete copy, that feels most appropriate for user tooling. This could be a pull through cache, or in my case, I've been working on a regclient project that includes an For using that mirror, I'd like to see this moved into the container runtimes where we specify mirrors to a project like containerd that then tries the local mirror before pulling from the upstream registry for any objects. It might be interesting to create a user or host definition of mirrors to allow non-image artifacts to use that mapping. In practice, if For situations where the upstream registry cannot be reached, the mirror configuration could specify only the local mirror and not fall back to an upstream registry. The other option is to use DNS or some other network level option for telling clients on that network that the upstream repository is found at the local mirror's IP, but that assumes you bypass any TLS protections. There's also this discussion on whether we want to sign tags that gets into the Notary v2 options when we copy an image, and rename it, in a way that mirror configs aren't easy to maintain: notaryproject/specifications#43 (comment) |
Beta Was this translation helpful? Give feedback.
-
What does the client do if they want to run product1 by vendor1, who says that the client must configure AFAICS the only sustainable way to distribute larger sets of container images is to avoid such conflicts; i.e. software1 must not just ask for Assuming a consensus on such a $NAMESPACE naming mechanism, it seems very attractive to use the existing Docker image references as a way to carry that, i.e. to keep all of the existing ecosystem unmodified: $NAMESPACE( |
Beta Was this translation helpful? Give feedback.
-
If an organization wants the image to be referenced with a different name, that feels like a good case to resign the image. I realize that's a pain, but in typical usage for my clients, the CI server would do the signing in dev and other early environments, while production may require a manual image signing, and the organization would want this separation for promotion between the environments. By not allowing the tag signature to move in those cases, you avoid deploying a development signed image to a production server. And in all cases, the digest has one or more signatures that follow it all the way through the chain. With something like a Helm chart, I often make my registry and repository templated variables to support moving the images. Then it's just a variable file that gets injected per environment. With that indirection, I'm not sure how a recursive copy of object would handle a templated image name, and that may just be forced to the user to manage the copying of their other images, or more complex tooling.
Not really, the
I'm really looking forward to OCI's Distribution and Artifact projects getting to GA.
Part of the workflow from my regclient/regsync command was looking at exactly that since I've run into those issues a few times. It manually updates the local mirror on the users schedule, so it's not a pull-through cache. And it allows automatic backups of the current image before overwriting, to allow an easy revert when upstream sends a breaking change. I haven't come across that logic in other solutions that are more of a pull-through cache, but hopefully this logic will become more common.
To me it feels more like a world where there's no GitHub, and instead everyone is hoping the repo name on their local Git server is the same as the Git server where another developer did their work. There's a value to having a universal name to avoid ambiguous references. Just as with Go includes we point back to a Git repo reference, and we can also mirror or vendor that reference, but those mirrors and vendored copies include the upstream name.
In the environments I've seen like that, the group that imports and approves the wabbit-networks image is interested in verifying the original signature. But once that is done, they want to resign the image with their acme keys, and nodes on their network would be configured to only run images signed by acme-rockets. At that point, we may still have data that indicates the object came from wabbit-networks, but I feel like the artifact we copied has changed ownership and a name change is appropriate.
I can see a lot of value in the goal you're trying to achieve, because it does solve a lot of problems. In much of this I'm ignoring a lot of the other artifact types where changing a name is a breaking change, and I'm assuming that resigning an image is an easy task, both of which gloss over the reality of many large orgs. But I'm just not sure this value is worth the trade-off. My concern comes as a user when I'm pulling images from more than one source, say both mcr.microsoft.com for dotnet and gcr.io for istio. If I'm pulling images without the registry name, what happens when Google decides they want to make their own dotnet image? Which image would I as a user be pulling? Similar to Debian, I could pin my dotnet image to only trust it when it's signed by Microsoft, but that gets very brittle when a Helm chart pulls in a dozen images and some image names get changed over time. And that's even worse when, as @mtrmac points out, various Helm charts from each repo each assume their own version of mysql or other common tool. We create namespace collisions where any two users in two completely separate registries create images with the same global name. Right now, the more maintainable solution for me is when I pull in a Helm chart from MCR that includes references to MCR, and I either mirror MCR or if I copy and rename it. If copying to a new name, then there's some references to change (or hopefully modify with a template variable) to point to my copy. It's not pretty, so I'd still be interested in a better option if it exists. But I fear effectively having a global namespace, without any global management of the names, creates a bigger problem than it solves. |
Beta Was this translation helpful? Give feedback.
-
I agree with @sudo-bmitch that a namespace that is just the final name ("ubuntu") is not large enough, and names need to be fully qualified so they don't collide (ie include a full domain), even if that is not the network address where you find the content. The Debian example where you can add additional repos under local control seems unlikely to work well for container images where there are a lot of images with similar names, because there are thousands or millions of different "mysql" containers, versus a much smaller number in Debian. However, I do also think that naming is difficult from the security point of view and relying predominantly on names is problematic... |
Beta Was this translation helpful? Give feedback.
-
@mtrmac forwarded me these two docs: After reading the docs, and all the above feedback, I realized there was a bunch of history in ordered resolution, that wasn't actually my intent to focus upon. Although I did include it in the initial examples, I'll pull them out. My hope is to provide a configuration mapping for deterministic resolution.
What I'm proposing is much more analogous to this environment approach, and I'll add more clarifying examples, avoiding the search list scenarios. Let's use the helm chart example for
{
"registries": [
{
"alias": "base-images",
"registry": "registry.acmerockets.io"
},
{
"alias": "apps",
"registry": "dev-registry.acmerockets.io"
}
],
"repo-mappings": [
{
"repo": "wordpress-chart",
"path": "[base-images]/charts"
},
{
"repo": "wordpress-cnab",
"path": "[base-images]/cnabs"
},
{
"repo": "wordpress",
"path": "[base-images]/"
},
{
"repo": "mysql",
"path": "[base-images]/"
},
{
"repo": "marketing-site",
"path": "[apps]/dev/"
}
]
}
{
"registries": [
{
"alias": "base-images",
"registry": "prod-registry.acmerockets.io"
},
{
"alias": "apps",
"registry": "prod-registry.acmerockets.io"
}
],
"repo-mappings": [
{
"repo": "wordpress-chart",
"path": "[base-images]/charts"
},
{
"repo": "wordpress-cnab",
"path": "[base-images]/cnabs"
},
{
"repo": "wordpress",
"path": "[base-images]/library/"
},
{
"repo": "mysql",
"path": "[base-images]/library/"
},
{
"repo": "marketing-site",
"path": "[apps]/marketing/"
}
]
} In this example:
|
Beta Was this translation helpful? Give feedback.
-
What name is contained in the signature? Probably not just |
Beta Was this translation helpful? Give feedback.
-
Ahh, that's the beauty of separating the name used for deployment, vs. verification of a signature.
An example signature would be: {
"typ": "x509",
"alg": "RS256",
"x5c": [
"MIIDszCCApugAwIBAgIUL1anEU/yJy67VJTbHkNX0bBNAnEwDQYJKoZIhvcNAQELBQAwaTEdMBsGA1UEAwwUcmVnaXN0cnkuZXhhbXBsZS5jb20xFDASBgNVBAoMC2V4YW1wbGUgaW5jMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2FzaGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTAeFw0yMDA3MjcxNDQzNDZaFw0yMTA3MjcxNDQzNDZaMGkxHTAbBgNVBAMMFHJlZ2lzdHJ5LmV4YW1wbGUuY29tMRQwEgYDVQQKDAtleGFtcGxlIGluYzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldhc2hpbmd0b24xEDAOBgNVBAcMB1NlYXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkKwAcV44psjN8nno1eZ3zv1ZKUhJAoxwBOIGfIxIe+iHtpXLvFFVwk5Jbxu+Pkig2N4B3Ilrj/Vryi0hxp4mag02M733bXLRENSOFONRkslpO8zHUN5pYdnhTSwYTLap1+1bgcFSuUXLWieqZB6qc7kiv3bj3SPaf42+s48V49t/OpXxLtgiWL9XkuDTZctpJJA4vHHk6Ou0bcg7iGm+L1xwIfb8Ml4oWvT0SF35fgW08bbLXZ2v1XCLRsrWUgbq4U+KxtEpG3XIYcYhKx1rIrUhfEJkuHzgPglM11gG5W+Cyfg+wfOJig5q6axIKWzIf6C8m8lmy6bM+N5EsD9SvAgMBAAGjUzBRMB0GA1UdDgQWBBTf1hM6/ibGF+u/SVAK88FUMjzRoTAfBgNVHSMEGDAWgBTf1hM6/ibGF+u/SVAK88FUMjzRoTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBgvVau5+2wAuCsmOyyG28h1zyC4IPmMmpRZTDOp/pLdwXeHjJr8kEC3l92qJEvc+WAboJ1RoucHycUe7RWh2C6ZF/WPCBLyWGwnlyqGyRM9/j86UJ1OgiuZl7kl9zxwWoaxPBCmHa0RHowdQB7AVlpqg1c7FhKjhUCBmGT4Ve8tV0hdZtrZoQV+6xHPbUd37KV1B1Bmfo3o4ekoJKhUu99Eo03OpE3JLtM13A1HxABEuQGHTI0tycDBBdRn3b03HoIhU0VnqjvpV1KPvsrgYi/0VStLNezZPgGe0fG3Xgy8yekdB9NMUn+zZLATI4+z8j4QH5Wj5ZPaUkyoAD2oUJO"
]
}.{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"digest": "sha256:c4516b8a311e85f1f2a60573abf4c6b740ca3ade4127e29b05616848de487d34",
"size": 528,
"references": [
"registry.wabbit-networks.io/net-monitor:latest",
"registry.wabbit-networks.io/net-monitor:v1.0"
],
"exp": 1628587119,
"iat": 1597051119,
"nbf": 1597051119
} |
Beta Was this translation helpful? Give feedback.
-
So the actual signature contains world-wide-unique names? If the ultimate consumer specifies only the short name in the deployment specification, and the registry/repo is fully variable, how does the signature verifier know which world-wide-unique name to accept (and, to begin which, which public key to trust for that short name)? It seems functionally equivalent to me, and clearly unambiguous, for both the signature and the deployment specification to use world-wide-unique names; that allows building exactly the same |
Beta Was this translation helpful? Give feedback.
-
Just saw the following report of an attack using ambiguous short names that reminds me of the RH concerns: https://www.bleepingcomputer.com/news/security/researcher-hacks-over-35-tech-firms-in-novel-supply-chain-attack/ Any solution to allow artifacts to move should be explicit from the client, specifying that calls to one registry/repository/tag get mapped to another, rather than a chain of fall throughs in different namespaces that would allow a name squatting attack. |
Beta Was this translation helpful? Give feedback.
-
In our Notary v2 scenarios, we call out the need for content movement within and across registries.
The community and industry have moved to multiple:
Much of the same content is already available across these multiple public registries, in addition to the private registries each customer should maintain. See Consuming Public Content for more detail.
The question we propose is how can we make this fact easier to use? How can we make it easier to secure a deployment?
I talk about the broader challenges, and questions in this post: Is It Time to Change How We Reference Container Images?.
Because our scripts, including Helm charts, have fully qualified references, I'd suggest we're in a more difficult and insecure situation. Even if I move a Helm chart to my environment (git or OCI registry), the helm chart references images from outside my organization. How do we make this easy for a person to import the content to their registry and then deploy it from their registry? How can they easily reconfigure things so staging comes from the staging registry, while prod deployments come from the prod registry/repo?
What I'd like to propose is we start down this journey with Notary v2 as one of the newest efforts for how artifacts are referenced. How we can decouple where the artifact may live, vs. what artifact we require.
The proposal follows the way other packages, even files, are referenced. We refer to the name of the artifact, in its most basic form (
repo:tag
) and enable configuration to identify where to find the artifact/tag. The Notary signature would sign the digest, which is unique regardless of where the artifact is persisted, so we don't lose any identity for the object. Just defer the question for where the artifact happens to live.The proposal goes like this:
Note: I've updated this to remove the fall-through/ordered search list reference
The registry and repo are configurable options. They are controlled by the client, enable through policy management.
connectionString
that changes between dev, staging and prod.See mapping discussion below
Marco pointed to this podman implementation, but I'd suggest this is just an example, not the end result.
If we were to decouple the location from the artifact, what would the issues be that we should account for?
Beta Was this translation helpful? Give feedback.
All reactions