diff --git a/lib/auth/join_azure.go b/lib/auth/join_azure.go index 81d8aea501354..e044d4e810a69 100644 --- a/lib/auth/join_azure.go +++ b/lib/auth/join_azure.go @@ -25,6 +25,7 @@ import ( "encoding/pem" "net/url" "slices" + "strings" "time" "github.com/Azure/azure-sdk-for-go/sdk/azcore" @@ -286,15 +287,31 @@ func checkAzureAllowRules(vm *azure.VirtualMachine, token string, allowRules []* if rule.Subscription != vm.Subscription { continue } - if len(rule.ResourceGroups) > 0 { - if !slices.Contains(rule.ResourceGroups, vm.ResourceGroup) { - continue - } + if !azureResourceGroupIsAllowed(rule.ResourceGroups, vm.ResourceGroup) { + continue } return nil } return trace.AccessDenied("instance %v did not match any allow rules in token %v", vm.Name, token) } +func azureResourceGroupIsAllowed(allowedResourceGroups []string, vmResourceGroup string) bool { + if len(allowedResourceGroups) == 0 { + return true + } + + // ResourceGroups are case insensitive. + // https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/frequently-asked-questions#are-resource-group-names-case-sensitive + // The API returns them using capital case, but docs don't mention a specific case. + // Converting everything to the same case will ensure a proper comparison. + resourceGroup := strings.ToUpper(vmResourceGroup) + for _, allowedResourceGroup := range allowedResourceGroups { + if strings.EqualFold(resourceGroup, allowedResourceGroup) { + return true + } + } + + return false +} func (a *Server) checkAzureRequest(ctx context.Context, challenge string, req *proto.RegisterUsingAzureMethodRequest, cfg *azureRegisterConfig) error { requestStart := a.clock.Now() diff --git a/lib/auth/join_azure_test.go b/lib/auth/join_azure_test.go index 3c3e7d9078b8e..5fe5d487fcf3a 100644 --- a/lib/auth/join_azure_test.go +++ b/lib/auth/join_azure_test.go @@ -190,7 +190,7 @@ func TestAuth_RegisterUsingAzureMethod(t *testing.T) { tokenName: "test-token", requestTokenName: "test-token", subscription: subID, - resourceGroup: "rg", + resourceGroup: "RG", tokenSpec: types.ProvisionTokenSpecV2{ Roles: []types.SystemRole{types.RoleNode}, Azure: &types.ProvisionTokenSpecV2Azure{ @@ -207,12 +207,34 @@ func TestAuth_RegisterUsingAzureMethod(t *testing.T) { certs: []*x509.Certificate{tlsConfig.Certificate}, assertError: require.NoError, }, + { + name: "resource group is case insensitive", + tokenName: "test-token", + requestTokenName: "test-token", + subscription: subID, + resourceGroup: "my-RESOURCE-GROUP", + tokenSpec: types.ProvisionTokenSpecV2{ + Roles: []types.SystemRole{types.RoleNode}, + Azure: &types.ProvisionTokenSpecV2Azure{ + Allow: []*types.ProvisionTokenSpecV2Azure_Rule{ + { + Subscription: subID, + ResourceGroups: []string{"MY-resource-group"}, + }, + }, + }, + JoinMethod: types.JoinMethodAzure, + }, + verify: mockVerifyToken(nil), + certs: []*x509.Certificate{tlsConfig.Certificate}, + assertError: require.NoError, + }, { name: "wrong token", tokenName: "test-token", requestTokenName: "wrong-token", subscription: subID, - resourceGroup: "rg", + resourceGroup: "RG", tokenSpec: types.ProvisionTokenSpecV2{ Roles: []types.SystemRole{types.RoleNode}, Azure: &types.ProvisionTokenSpecV2Azure{ @@ -233,7 +255,7 @@ func TestAuth_RegisterUsingAzureMethod(t *testing.T) { tokenName: "test-token", requestTokenName: "test-token", subscription: subID, - resourceGroup: "rg", + resourceGroup: "RG", tokenSpec: types.ProvisionTokenSpecV2{ Roles: []types.SystemRole{types.RoleNode}, Azure: &types.ProvisionTokenSpecV2Azure{ @@ -255,7 +277,7 @@ func TestAuth_RegisterUsingAzureMethod(t *testing.T) { tokenName: "test-token", requestTokenName: "test-token", subscription: "some-junk", - resourceGroup: "rg", + resourceGroup: "RG", tokenSpec: types.ProvisionTokenSpecV2{ Roles: []types.SystemRole{types.RoleNode}, Azure: &types.ProvisionTokenSpecV2Azure{ @@ -276,7 +298,7 @@ func TestAuth_RegisterUsingAzureMethod(t *testing.T) { tokenName: "test-token", requestTokenName: "test-token", subscription: subID, - resourceGroup: "wrong-rg", + resourceGroup: "WRONG-RG", tokenSpec: types.ProvisionTokenSpecV2{ Roles: []types.SystemRole{types.RoleNode}, Azure: &types.ProvisionTokenSpecV2Azure{ @@ -298,7 +320,7 @@ func TestAuth_RegisterUsingAzureMethod(t *testing.T) { tokenName: "test-token", requestTokenName: "test-token", subscription: subID, - resourceGroup: "rg", + resourceGroup: "RG", tokenSpec: types.ProvisionTokenSpecV2{ Roles: []types.SystemRole{types.RoleNode}, Azure: &types.ProvisionTokenSpecV2Azure{ @@ -322,7 +344,7 @@ func TestAuth_RegisterUsingAzureMethod(t *testing.T) { tokenName: "test-token", requestTokenName: "test-token", subscription: subID, - resourceGroup: "rg", + resourceGroup: "RG", tokenSpec: types.ProvisionTokenSpecV2{ Roles: []types.SystemRole{types.RoleNode}, Azure: &types.ProvisionTokenSpecV2Azure{ @@ -343,7 +365,7 @@ func TestAuth_RegisterUsingAzureMethod(t *testing.T) { tokenName: "test-token", requestTokenName: "test-token", subscription: subID, - resourceGroup: "rg", + resourceGroup: "RG", vmID: "vm-id", tokenSpec: types.ProvisionTokenSpecV2{ Roles: []types.SystemRole{types.RoleNode}, @@ -358,7 +380,7 @@ func TestAuth_RegisterUsingAzureMethod(t *testing.T) { }, vmResult: &azure.VirtualMachine{ Subscription: subID, - ResourceGroup: "rg", + ResourceGroup: "RG", VMID: "different-id", }, verify: mockVerifyToken(nil),