Change:
What happened: An aks-engine cluster with a user assigned managed service cluster identity fails to authenticate to azure container registries in private clouds when the proper (acrpull) azure role assignments are in place and when there are no image pull secrets. What you expected to happen: The cluster should be able to authenticate and pull from an ACR hosted in the same azure cloud without needing additional pull secrets when the cluster identity has the proper acrpull azure role assignment, no matter which azure cloud it is operating in. How to reproduce it (as minimally and precisely as possible): Assume you have a private azure cloud where every url ends with "". That would mean the ACR suffix would be . Let's pretend you have an ACR at and you try to deploy a pod in your cluster referencing the image . This will fail to pull when you have no image pull secrets. Anything else we need to know?: The problem stems from the following lines where expected ACR suffix domains are hardcoded: Environment: Kubernetes version (use ): 1.16.9 Cloud provider or hardware configuration: Azure private OS: ubuntu 16.0.4 Network plugin and version: Azure CNI
/sig provider-azure
The label(s) cannot be applied, because the repository doesn't have them <detailsIn response to : /sig provider-azure Instructions for interacting with me using PR comments are available . If you have questions or suggestions related to my behavior, please file an issue against the repository. </details
/area provider/azure /sig cloud-provider
It looks like we may be able to obtain the acr suffix from the azure environment:
there is a security issue here: if we support self defined ACR domain, e.g. , then service principal secret could be sent to that remote server. The reason why we use hardcoded domains is that we could always make sure the following 4 domains are owned by Azure:

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

"io/ioutil" "os" "regexp" <ins> "strings" </ins> "time" "github.com/Azure/azure-sdk-for-go/services/containerregistry/mgmt/2019-05-01/containerregistry"

cfg := credentialprovider.DockerConfig{} if a.config.UseManagedIdentityExtension { <del> if loginServer := parseACRLoginServerFromImage(image); loginServer == "" { </del> <ins> if loginServer := a.parseACRLoginServerFromImage(image); loginServer == "" { </ins> klog.V(4).Infof("image(%s) is not from ACR, skip MSI authentication", image) } else { if cred, err := getACRDockerEntryFromARMToken(a, loginServer); err == nil {

} cfg[url] = *cred } <ins> // Handle the custom cloud case // In clouds where ACR is not yet deployed, the string will be empty if a.environment != nil && strings.Contains(a.environment.ContainerRegistryDNSSuffix, ".azurecr.") { customAcrSuffix := "*" + a.environment.ContainerRegistryDNSSuffix hasBeenAdded := false for _, url := range containerRegistryUrls { if strings.EqualFold(url, customAcrSuffix) { hasBeenAdded = true break } } if !hasBeenAdded { cred := &credentialprovider.DockerConfigEntry{ Username: a.config.AADClientID, Password: a.config.AADClientSecret, Email:    dummyRegistryEmail, } cfg[customAcrSuffix] = *cred } } </ins> } // add ACR anonymous repo support: use empty username and password for anonymous access

// parseACRLoginServerFromImage takes image as parameter and returns login server of it. // Parameter `image` is expected in following format: foo.azurecr.io/bar/imageName:version // If the provided image is not an acr image, this function will return an empty string. <del> func parseACRLoginServerFromImage(image string) string { </del> <ins> func (a *acrProvider) parseACRLoginServerFromImage(image string) string { </ins> match := acrRE.FindAllString(image, -1) if len(match) == 1 { return match[0] } <ins> // handle the custom cloud case if a != nil && a.environment != nil { cloudAcrSuffix := a.environment.ContainerRegistryDNSSuffix cloudAcrSuffixLength := len(cloudAcrSuffix) if cloudAcrSuffixLength > 0 { customAcrSuffixIndex := strings.Index(image, cloudAcrSuffix) if customAcrSuffixIndex != -1 { endIndex := customAcrSuffixIndex + cloudAcrSuffixLength return image[0:endIndex] } } } </ins> return "" }

"testing" "github.com/Azure/azure-sdk-for-go/services/containerregistry/mgmt/2019-05-01/containerregistry" <ins> "github.com/Azure/go-autorest/autorest/azure" </ins> "github.com/Azure/go-autorest/autorest/to" )

} func TestParseACRLoginServerFromImage(t *testing.T) { <ins> configStr := ` { "aadClientId": "foo", "aadClientSecret": "bar" }` result := []containerregistry.Registry{ { Name: to.StringPtr("foo"), RegistryProperties: &containerregistry.RegistryProperties{ LoginServer: to.StringPtr("*.azurecr.io"), }, }, } fakeClient := &fakeClient{ results: result, } provider := &acrProvider{ registryClient: fakeClient, } provider.loadConfig(bytes.NewBufferString(configStr)) provider.environment = &azure.Environment{ ContainerRegistryDNSSuffix: ".azurecr.my.cloud", } </ins> tests := []struct { image    string expected string

image:    "foo.azurecr.us/bar/image:version", expected: "foo.azurecr.us", }, <ins> { image:    "foo.azurecr.my.cloud/bar/image:version", expected: "foo.azurecr.my.cloud", }, </ins> } for _, test := range tests { <del> if loginServer := parseACRLoginServerFromImage(test.image); loginServer != test.expected { </del> <ins> if loginServer := provider.parseACRLoginServerFromImage(test.image); loginServer != test.expected { </ins> t.Errorf("function parseACRLoginServerFromImage returns "%s" for image %s, expected "%s"", loginServer, test.image, test.expected) } }