Change:
<!-- Please use this template while reporting a bug and provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner. Thanks! If the matter is security related, please disclose it privately via --What happened: When Managed Service Identity (MSI) is used to pull images from Azure Container Registry (ACR), it may fail with even though the MSI has reader access to the ACR. It will recover in 1-2 minutes. What you expected to happen: It should not have any unauthorized error. How to reproduce it (as minimally and precisely as possible): a k8s deployment using public docker image run a k8s deployment using ACR image the error in get events Kubelet log: Anything else we need to know?: When MSI is configured in kubelet and a non-ACR docker image is pulled, the code will skip the MSI authentication flow and assuming it's anonymous ACR auth. The resulting docker registry cfg is . So any image pull from ACR will use the staled docker registry cfg and got unauthorized authentication until the cache is refreshed Environment: Kubernetes version (use ): 1.15.10 Cloud provider or hardware configuration: AKS with MSI
/area provider/azure
/sig cloud-provider

Discussion:
"//vendor/github.com/Azure/azure-sdk-for-go/services/containerregistry/mgmt/2019-05-01/containerregistry:go_default_library", "//vendor/github.com/Azure/go-autorest/autorest/azure:go_default_library", "//vendor/github.com/Azure/go-autorest/autorest/to:go_default_library", <ins> "//vendor/github.com/stretchr/testify/assert:go_default_library", </ins> ], )

func init() { credentialprovider.RegisterCredentialProvider("azure", &credentialprovider.CachingDockerConfigProvider{ <del> Provider: NewACRProvider(flagConfigFile), Lifetime: 1 * time.Minute, </del> <ins> Provider:    NewACRProvider(flagConfigFile), Lifetime:    1 * time.Minute, ShouldCache: func(d credentialprovider.DockerConfig) bool { return len(d) > 0 }, </ins> }) }

klog.V(4).Infof("try to provide secret for image %s", image) cfg := credentialprovider.DockerConfig{} <ins> defaultConfigEntry := credentialprovider.DockerConfigEntry{ Username: "", Password: "", Email:    dummyRegistryEmail, } </ins> if a.config.UseManagedIdentityExtension { if loginServer := a.parseACRLoginServerFromImage(image); loginServer == "" { klog.V(4).Infof("image(%s) is not from ACR, skip MSI authentication", image)

if cred, err := getACRDockerEntryFromARMToken(a, loginServer); err == nil { cfg[loginServer] = *cred } <ins> // add ACR anonymous repo support: use empty username and password for anonymous access cfg["*.azurecr.*"] = defaultConfigEntry </ins> } } else { // Add our entry for each of the supported container registry URLs

cfg[customAcrSuffix] = *cred } } <del> } // add ACR anonymous repo support: use empty username and password for anonymous access cfg["*.azurecr.*"] = credentialprovider.DockerConfigEntry{ Username: "", Password: "", Email:    dummyRegistryEmail, </del> <ins> // add ACR anonymous repo support: use empty username and password for anonymous access cfg["*.azurecr.*"] = defaultConfigEntry </ins> } return cfg }

"github.com/Azure/azure-sdk-for-go/services/containerregistry/mgmt/2019-05-01/containerregistry" "github.com/Azure/go-autorest/autorest/azure" "github.com/Azure/go-autorest/autorest/to" <ins> "github.com/stretchr/testify/assert" </ins> ) type fakeClient struct {

} } <ins> func TestProvide(t *testing.T) { testCases := []struct { desc                string configStr           string expectedCredsLength int }{ { desc: "return multiple credentials using Service Principal", configStr: ` { "aadClientId": "foo", "aadClientSecret": "bar" }`, expectedCredsLength: 5, }, { desc: "retuen 0 credential for non-ACR image using Managed Identity", configStr: ` { "UseManagedIdentityExtension": true }`, expectedCredsLength: 0, }, } for i, test := range testCases { provider := &acrProvider{ registryClient: &fakeClient{}, } provider.loadConfig(bytes.NewBufferString(test.configStr)) creds := provider.Provide("busybox") assert.Equal(t, test.expectedCredsLength, len(creds), "TestCase[%d]: %s", i, test.desc) } } </ins> func TestParseACRLoginServerFromImage(t *testing.T) { configStr := ` {

Provider DockerConfigProvider Lifetime time.Duration <ins> // ShouldCache is an optional function that returns true if the specific config should be cached. // If nil, all configs are treated as cacheable. ShouldCache func(DockerConfig) bool </ins> // cache fields cacheDockerConfig DockerConfig expiration        time.Time

} klog.V(2).Infof("Refreshing cache for provider: %v", reflect.TypeOf(d.Provider).String()) <del> d.cacheDockerConfig = d.Provider.Provide(image) d.expiration = time.Now().Add(d.Lifetime) return d.cacheDockerConfig </del> <ins> config := d.Provider.Provide(image) if d.ShouldCache == nil || d.ShouldCache(config) { d.cacheDockerConfig = config d.expiration = time.Now().Add(d.Lifetime) } return config </ins> }