knative / serving

Kubernetes-based, scale-to-zero, request-driven compute
https://knative.dev/docs/serving/
Apache License 2.0
5.58k stars 1.16k forks source link

Multiple pullSecrets for same registry does not work #13126

Open Wouter0100 opened 2 years ago

Wouter0100 commented 2 years ago

When having multiple pullSecrets for the same registry in the same Service Account, Knative fails to detect the correct set of credentials and fails at the first. The kubelet has a loop build in that allows this setup.

What version of Knative?

1.1.0

Expected Behavior

To attempt each imagePullSecret in the associated Service Account when resolving the tag of an image and continue deploying the revision.

Actual Behavior

It fails at the first pull secret, from which the credentials do not match and does not attempt the next pull secret.

Steps to Reproduce the Problem

  1. Create 2 kubernetes.io/dockerconfigjson-secrets for the same registry with different username/password.
  2. Create a Service Account with both secrets.
  3. Create a kservice with the above created Service Account, and include 2 containers in the pod whereby:
    • The first container should only be allowed to be pulled from the first set of credentials (used in the first secret).
    • The second container should only be allowed to be pulled from the registry using the second set of credentials (used in the second secret).

Workaround

We disabled tag resolving by setting registries-skipping-tag-resolving in config-deployment to our registry.

dprotaso commented 2 years ago

/triage accepted

jwcesign commented 2 years ago

Can I take this? @dprotaso

dprotaso commented 2 years ago

Sure - the change will need to be done upstream in ggcr

https://github.com/google/go-containerregistry/blob/7196cf3dc436a7633687b0c83ba76318f70e26f2/pkg/authn/kubernetes/keychain.go#L273

/assign @jwcesign

dprotaso commented 2 years ago

I created an upstream issue and asked for input from the maintainers - https://github.com/google/go-containerregistry/issues/1431

dprotaso commented 2 years ago

@Wouter0100 another workaround is to specify the registry secret with the repository

ie. registry.com/foo and registry.com/bar can you try that?

jwcesign commented 2 years ago

I think only change the logic in knative serving is better. Because if change go-containerregistry, the API return results will be changed, it has influence with who use go-containerregistry:

file: serving/pkg/reconciler/revision/resolve.go
// Resolve resolves the image references that use tags to digests.
func (r *digestResolver) Resolve(
    ctx context.Context,
    image string,
    opt k8schain.Options,
    registriesToSkip sets.String) (string, error) {
    if _, err := name.NewDigest(image, name.WeakValidation); err == nil {
        // Already a digest
        return image, nil
    }

    tag, err := name.NewTag(image, name.WeakValidation)
    if err != nil {
        return "", fmt.Errorf("failed to parse image name %q into a tag: %w", image, err)
    }

    if registriesToSkip.Has(tag.Registry.RegistryStr()) {
        return "", nil
    }

    if len(opt.ImagePullSecrets) == 0 {
        resolveDigest, err := r.ResolveWithSingleImagePullSecret(ctx, tag, opt)
        if err != nil {
            return "", err
        }
        return resolveDigest, nil
    }

    var resolveErrors []error
    for _, v := range opt.ImagePullSecrets {
        singleImagePullSecretOpt := opt
        singleImagePullSecretOpt.ImagePullSecrets = []string{v}
        resolveDigest, err := r.ResolveWithSingleImagePullSecret(ctx, tag, singleImagePullSecretOpt)
        if err == nil {
            return resolveDigest, nil
        }
        resolveErrors = append(resolveErrors, err)
    }

    return "", utilerrors.NewAggregate(resolveErrors)
}

func (r *digestResolver) ResolveWithSingleImagePullSecret(
    ctx context.Context,
    tag name.Tag,
    opt k8schain.Options) (string, error) {
    kc, err := k8schain.New(ctx, r.client, opt)
    if err != nil {
        return "", err
    }

    desc, err := remote.Head(tag, remote.WithContext(ctx), remote.WithTransport(r.transport), remote.WithAuthFromKeychain(kc), remote.WithUserAgent(r.userAgent))
    if err != nil {
        return "", err
    }

    return fmt.Sprintf("%s@%s", tag.Repository.String(), desc.Digest), nil
}

cc @dprotaso , what u think

dprotaso commented 2 years ago

My only concern with that solution is that it'll enumerate over extra keychains excessively - and we've seen some keychains in the past be really slow (ie. 2s).

ie. https://github.com/google/go-containerregistry/blob/7196cf3dc436a7633687b0c83ba76318f70e26f2/pkg/authn/k8schain/k8schain.go#L98-L104

return authn.NewMultiKeychain(
    k8s,
    authn.DefaultKeychain,
    google.Keychain,
    amazonKeychain,
    azureKeychain,
), nil

I think the workaround I mentioned above is fine while we figure out a way to solve this upstream

jwcesign commented 2 years ago

go-containerregistry maintainer tries to work on abother approach, when finished, I will sync and add ut for this issue