Skip to content
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

Verify manifest with remote layers #13001

Conversation

legionus
Copy link
Contributor

Problem
We pass all requests (including HEAD) to the remote service if the pullthrough is enabled. On the other hand when docker client pushes the manifest we check the presence of all these layers locally. The client checks a blob existance by HEAD request before sending it to the server.

If client image is based on the imported image (but not present in local registry) dockerregistry will say that it has all the layers from the base image. In this case docker client never send them to server, but manifest verification requires them locally. It means that the verification will always fail for remote layers.

Solution
Manifest verification must to take into account the possibility that the layers may not be local and check them on remote registry server before before give up.

We can't use pullthroughBlobStore because verification happens in ManifestService. So we need to move common code that gets the blobs from the remote server to BlobGetterService and use it for pullthrough and for verification.

@miminar PTAL

@legionus
Copy link
Contributor Author

[test]

os::cmd::expect_success "oc login -u user0 -p pass"
os::cmd::expect_success "oc new-project project0"
os::cmd::expect_success "oc new-build -D \$'FROM busybox:glibc\nRUN echo abc >/tmp/foo'"
os::cmd::try_until_success "oc get build/busybox-1 -o go-template={{.metadata.name}} 2>&1 |grep busybox-1"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

os::cmd::try_until_text "oc get build/busybox-1 -o 'jsonpath={.metadata.name}'" "busybox-1"

os::cmd::expect_success "oc new-project project0"
os::cmd::expect_success "oc new-build -D \$'FROM busybox:glibc\nRUN echo abc >/tmp/foo'"
os::cmd::try_until_success "oc get build/busybox-1 -o go-template={{.metadata.name}} 2>&1 |grep busybox-1"
os::cmd::try_until_not_text "oc get build/busybox-1 -o go-template='{{.status.phase}}' 2>&1" "^(Pending|Running)\$" "$(( 10*TIME_MIN ))"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

os::cmd::try_until_not_text "oc get build/busybox-1 -o 'jsonpath={.status.phase}'" "(Pending|Running)" "$(( 10*TIME_MIN ))"

Although -- this seems a little weird, if we follow this up with expecting Complete we should just do one wait

os::cmd::expect_success "oc new-build -D \$'FROM busybox:glibc\nRUN echo abc >/tmp/foo'"
os::cmd::try_until_success "oc get build/busybox-1 -o go-template={{.metadata.name}} 2>&1 |grep busybox-1"
os::cmd::try_until_not_text "oc get build/busybox-1 -o go-template='{{.status.phase}}' 2>&1" "^(Pending|Running)\$" "$(( 10*TIME_MIN ))"
os::cmd::expect_success_and_text "oc get build/busybox-1 -o go-template='{{.status.phase}}'" '^Complete$'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

os::cmd::try_until_text "oc get build/busybox-1 -o jsonpath='{.status.phase}'" 'Complete' "$(( 10*TIME_MIN ))"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stevekuznetsov

Just after oc new-build ... output of oc get builds looks like this:

RUN: oc get builds
No resources found.

RUN: oc get builds
NAME        TYPE      FROM         STATUS    STARTED   DURATION
busybox-1   Docker    Dockerfile   Pending             

RUN: oc get builds
NAME        TYPE      FROM         STATUS    STARTED   DURATION
busybox-1   Docker    Dockerfile   Pending             

RUN: oc get builds
NAME        TYPE      FROM         STATUS    STARTED        DURATION
busybox-1   Docker    Dockerfile   Running   1 second ago   

RUN: oc get builds
NAME        TYPE      FROM         STATUS     STARTED         DURATION
busybox-1   Docker    Dockerfile   Complete   2 seconds ago   1s

If I leave only the os::cmd::try_until_text "oc get build/busybox-1 -o jsonpath='{.status.phase}'" 'Complete' "$(( 10*TIME_MIN ))", in case of an error, the test will wait a very long time.

In my test first os::cmd::try_until_success waits for the appearance of the build. Second os::cmd::try_until_not_text waits for any completion of build. And the last os::cmd::expect_success_and_text checks status of build.

/cc @mfojtik

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stevekuznetsov @legionus comment makes sense to me :-) we don't want to wait 10 minutes in case this test fails ;-)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we almost always just take the long way -- see also the conformance timeout. It's more readable and expresses your intent better. I don't feel too strongly.

@soltysh
Copy link
Contributor

soltysh commented Feb 20, 2017

The changes lgtm, although the test error worries me, since it failed on pullthrough test, specifically and this PR modifies that part. Aside from that Steve's comments needs addressing.

"github.com/openshift/origin/pkg/image/importer"
)

type BlobGetterService interface {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

godoc

distribution.BlobServer
}

type remoteBlobGetterService struct {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

godoc

os::cmd::expect_success 'oc rollout status dc/docker-registry'
os::log::info "Registry configured to disble mirroring"

# verify local image based on remote image can be pushed to local registry
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for the comment since there's an info line just below. Though I like the text in the comment more.

os::log::info "Configure registry to disable mirroring"
os::cmd::expect_success "oc project '${CLUSTER_ADMIN_CONTEXT}'"
os::cmd::expect_success 'oc env -n default dc/docker-registry REGISTRY_MIDDLEWARE_REPOSITORY_OPENSHIFT_MIRRORPULLTHROUGH=false'
os::cmd::expect_success 'oc env -n default dc/docker-registry REGISTRY_MIDDLEWARE_REPOSITORY_OPENSHIFT_PULLTHROUGH=true'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This holds true by default.

os::cmd::try_until_success "oc get build/busybox-1 -o go-template={{.metadata.name}} 2>&1 |grep busybox-1"
os::cmd::try_until_not_text "oc get build/busybox-1 -o go-template='{{.status.phase}}' 2>&1" "^(Pending|Running)\$" "$(( 10*TIME_MIN ))"
os::cmd::expect_success_and_text "oc get build/busybox-1 -o go-template='{{.status.phase}}'" '^Complete$'

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also ensure the istag exists?

@@ -123,130 +54,57 @@ func (r *pullthroughBlobStore) proxyStat(ctx context.Context, retriever importer
// success response with no actual body content.
// [1] https://docs.docker.com/registry/spec/api/#existing-layers
func (pbs *pullthroughBlobStore) ServeBlob(ctx context.Context, w http.ResponseWriter, req *http.Request, dgst digest.Digest) error {
store, ok := pbs.digestToStore[dgst.String()]
if !ok {
if _, err := pbs.BlobStore.Stat(ctx, dgst); err == nil {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no need for the Stat. It's the first thing the pbs.BlobStore.ServeBlob does. Just handle the error case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@miminar I know, but we need to figure out is this blob local or not to start mirroring. I need to change mirroring code to make the changes that you suggest. I don't want to do this before the release. The changes will be very large.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@miminar yep. You're right.

if !found {
err := fmt.Errorf("pullthroughBlobStore.ServeBlob: failed to retrieve remote getter from context")
context.GetLogger(ctx).Error(err)
return err
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't expose implementation details to clients (pullthroughBlobStore.ServeBlob: ).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@miminar Otherwise it is very difficult to understand where the error occurs.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with logging it. I'm not fine with returning it as an error.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've just accidentally commented from my private account. Nevertheless, I completely agree with my alter-ego.

return desc, nil
}

// Second attemt: looking for the blob on a remote server
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo

@@ -205,6 +205,13 @@ func newRepositoryWithClient(

// Manifests returns r, which implements distribution.ManifestService.
func (r *repository) Manifests(ctx context.Context, options ...distribution.ManifestServiceOption) (distribution.ManifestService, error) {
if r.pullthrough {
ctx = WithRemoteBlobGetter(ctx, &remoteBlobGetterService{
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain in a comment, why this is needed here?

@@ -243,6 +250,13 @@ func (r *repository) Blobs(ctx context.Context) distribution.BlobStore {
repo := repository(*r)
repo.ctx = ctx

if r.pullthrough {
ctx = WithRemoteBlobGetter(ctx, &remoteBlobGetterService{
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And again here.

@legionus legionus force-pushed the dockerregistry-remote-blob-verification branch 2 times, most recently from 07c126e to 9874c50 Compare February 20, 2017 17:03
@legionus
Copy link
Contributor Author

@miminar all your comments addressed.

if !found {
err := fmt.Errorf("pullthroughBlobStore.ServeBlob: failed to retrieve remote getter from context")
context.GetLogger(ctx).Error(err)
return err
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this return ErrBlobUnknown as well?

if r.pullthrough {
// Add to the context the BlobGetterService that provide access to remote servers.
// It will be used to validate manifest blobs. It only makes sense
// if the pullthrough is enabled.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mention that it needs to be instantiated here in order to share the cache among different stat calls made on manifest's dependencies.


if r.pullthrough {
// Add to the context the BlobGetterService that provide access to remote servers.
// It will be used in pullthroughBlobStore.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mention that it needs to be instantiated here in order to share the cache for multiple stat calls made in descendant blob stores.

@legionus legionus force-pushed the dockerregistry-remote-blob-verification branch from 9874c50 to eed05d4 Compare February 21, 2017 13:55
return desc, err
// only non-empty layers is wise to check for existence in the image stream.
// schema v2 has no empty layers.
if dgst.String() != digestSHA256GzippedEmptyTar.String() {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about digest.DigestSha256EmptyTar?

if dgst.String() != digestSHA256GzippedEmptyTar.String() {
// ensure it's referenced inside of corresponding image stream
if !imageStreamHasBlob(repo, dgst) {
return distribution.Descriptor{}, distribution.ErrBlobUnknown
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to fall-back to remote stat.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider:

  1. empty image stream ns/foo
  2. remote manifest with layers {A} tagged as docker.io/foo:v1
  3. remote manifest with layers {B} tagged as docker.io/foo:v2

and following scenario:

oc import-image --from=docker.io/foo:v1 foo:v1 --confirm
docker pull docker.io/foo:v2
docker tag docker.io/foo:v2 "${integrated_registry_url}/ns/foo:v2"
docker push "${integrated_registry_url}/ns/foo:v2"

This will fail because HEAD on B in ns/foo will succeed thanks to pullthrough. However, manifest verification will fail because B is not tagged in the image stream and it's not an empty layer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@miminar It means that imageStreamHasBlob should be called only for local blobs ?
It seems you have me confused.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@legionus yes, that's what I meant.

os::cmd::expect_success "oc project '${CLUSTER_ADMIN_CONTEXT}'"
os::cmd::expect_success 'oc env -n default dc/docker-registry REGISTRY_MIDDLEWARE_REPOSITORY_OPENSHIFT_MIRRORPULLTHROUGH=false'
os::cmd::expect_success 'oc rollout status dc/docker-registry'
os::log::info "Registry configured to disble mirroring"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo

os::cmd::expect_success 'oc rollout status dc/docker-registry'
os::log::info "Registry configured to disble mirroring"

os::log::info "Check the image based on a non-local base image."
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/non-local/remote/

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be more verbose? E.g.: Verify that an image based on a remote image can be pushed to the same image stream while pull-through enabled

os::log::info "Check the image based on a non-local base image."
os::cmd::expect_success "oc login -u user0 -p pass"
os::cmd::expect_success "oc new-project project0"
os::cmd::expect_success "oc new-build -D \$'FROM busybox:glibc\nRUN echo abc >/tmp/foo'"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a comment that this actually creates a source repository busybox for the source image and that the result will be pushed to the same repository.

@legionus legionus force-pushed the dockerregistry-remote-blob-verification branch 3 times, most recently from b204966 to ecc925b Compare February 22, 2017 13:54
@bparees
Copy link
Contributor

bparees commented Feb 22, 2017

either flake #13050 or this change is now causing that test to fail.

@legionus
Copy link
Contributor Author

@bparees It is not #13047 ?

@bparees
Copy link
Contributor

bparees commented Feb 22, 2017

@legionus there were multiple failures. some of them were 13047, but the networking one didn't appear to be related to that, no.

Copy link

@miminar miminar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@mfojtik
Copy link
Contributor

mfojtik commented Feb 22, 2017

[test]

@legionus
Copy link
Contributor Author

flake #12899

@legionus
Copy link
Contributor Author

[test]

1 similar comment
@mfojtik
Copy link
Contributor

mfojtik commented Feb 23, 2017

[test]

@legionus
Copy link
Contributor Author

@mfojtik \o/
merge?

Copy link
Contributor

@stevekuznetsov stevekuznetsov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have not addressed any of my comments on your tests. Please do so.

@legionus legionus force-pushed the dockerregistry-remote-blob-verification branch from e2f3983 to 462bd16 Compare February 23, 2017 13:05
@legionus
Copy link
Contributor Author

@stevekuznetsov I believe that the proposed changes are wrong, but I made them. At least I know who to blame for the slow test.

please review again.

@stevekuznetsov
Copy link
Contributor

I was OK with the way you had the wait written. I said as much in the comments. It was the other comments re: go-template, etc that I want to see addressed.

@legionus legionus force-pushed the dockerregistry-remote-blob-verification branch from 462bd16 to 82db14d Compare February 23, 2017 14:15
@legionus
Copy link
Contributor Author

@stevekuznetsov Oh. I didn't understand you correctly. Now looks better ?

@legionus
Copy link
Contributor Author

flake #12923 re[test]

@mfojtik
Copy link
Contributor

mfojtik commented Feb 23, 2017

@legionus can you rebase against master? @bparees fixed the issue that is causing fork_ami to fail, but for some reason I can see it happening for your branch... which is strange, because I pick "master" as the branch to rebase your PR again...

Signed-off-by: Gladkov Alexey <agladkov@redhat.com>
@legionus legionus force-pushed the dockerregistry-remote-blob-verification branch from 82db14d to b4718ae Compare February 24, 2017 10:24
@legionus
Copy link
Contributor Author

@mfojtik done

@openshift-bot
Copy link
Contributor

Evaluated for origin test up to b4718ae

@openshift-bot
Copy link
Contributor

continuous-integration/openshift-jenkins/test SUCCESS (https://ci.openshift.redhat.com/jenkins/job/test_pull_requests_origin_future/536/) (Base Commit: f1dd3c7)

@legionus
Copy link
Contributor Author

@mfojtik
Copy link
Contributor

mfojtik commented Feb 27, 2017

QA verified the fork_ami

[merge]

@openshift-bot
Copy link
Contributor

Evaluated for origin merge up to b4718ae

@openshift-bot
Copy link
Contributor

openshift-bot commented Feb 27, 2017

continuous-integration/openshift-jenkins/merge SUCCESS (https://ci.openshift.redhat.com/jenkins/job/test_pull_requests_origin_future/571/) (Base Commit: 5357357) (Image: devenv-rhel7_5985)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants