Configure Azure Storage Local Users

The azure-native:storage:LocalUser resource, part of the Pulumi Azure Native provider, defines local users for Azure Storage accounts, controlling authentication methods and access permissions for SFTP and NFSv3 protocols. This guide focuses on three capabilities: SSH key authentication for SFTP, NFSv3 access with Unix-style groups, and permission scoping to file shares.

Local users belong to storage accounts and reference file shares or containers that must exist separately. The examples are intentionally small. Combine them with your own storage accounts and access control policies.

Create a user with SSH access and file share permissions

Teams building SFTP workflows provision users who authenticate with SSH keys and access specific Azure File shares with granular permissions.

import * as pulumi from "@pulumi/pulumi";
import * as azure_native from "@pulumi/azure-native";

const localUser = new azure_native.storage.LocalUser("localUser", {
    accountName: "sto2527",
    allowAclAuthorization: true,
    groupId: 2000,
    hasSshPassword: true,
    homeDirectory: "homedirectory",
    permissionScopes: [
        {
            permissions: "rwd",
            resourceName: "share1",
            service: "file",
        },
        {
            permissions: "rw",
            resourceName: "share2",
            service: "file",
        },
    ],
    resourceGroupName: "res6977",
    sshAuthorizedKeys: [{
        description: "key name",
        key: "ssh-rsa keykeykeykeykey=",
    }],
    username: "user1",
});
import pulumi
import pulumi_azure_native as azure_native

local_user = azure_native.storage.LocalUser("localUser",
    account_name="sto2527",
    allow_acl_authorization=True,
    group_id=2000,
    has_ssh_password=True,
    home_directory="homedirectory",
    permission_scopes=[
        {
            "permissions": "rwd",
            "resource_name": "share1",
            "service": "file",
        },
        {
            "permissions": "rw",
            "resource_name": "share2",
            "service": "file",
        },
    ],
    resource_group_name="res6977",
    ssh_authorized_keys=[{
        "description": "key name",
        "key": "ssh-rsa keykeykeykeykey=",
    }],
    username="user1")
package main

import (
	storage "github.com/pulumi/pulumi-azure-native-sdk/storage/v3"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := storage.NewLocalUser(ctx, "localUser", &storage.LocalUserArgs{
			AccountName:           pulumi.String("sto2527"),
			AllowAclAuthorization: pulumi.Bool(true),
			GroupId:               pulumi.Int(2000),
			HasSshPassword:        pulumi.Bool(true),
			HomeDirectory:         pulumi.String("homedirectory"),
			PermissionScopes: storage.PermissionScopeArray{
				&storage.PermissionScopeArgs{
					Permissions:  pulumi.String("rwd"),
					ResourceName: pulumi.String("share1"),
					Service:      pulumi.String("file"),
				},
				&storage.PermissionScopeArgs{
					Permissions:  pulumi.String("rw"),
					ResourceName: pulumi.String("share2"),
					Service:      pulumi.String("file"),
				},
			},
			ResourceGroupName: pulumi.String("res6977"),
			SshAuthorizedKeys: storage.SshPublicKeyArray{
				&storage.SshPublicKeyArgs{
					Description: pulumi.String("key name"),
					Key:         pulumi.String("ssh-rsa keykeykeykeykey="),
				},
			},
			Username: pulumi.String("user1"),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using AzureNative = Pulumi.AzureNative;

return await Deployment.RunAsync(() => 
{
    var localUser = new AzureNative.Storage.LocalUser("localUser", new()
    {
        AccountName = "sto2527",
        AllowAclAuthorization = true,
        GroupId = 2000,
        HasSshPassword = true,
        HomeDirectory = "homedirectory",
        PermissionScopes = new[]
        {
            new AzureNative.Storage.Inputs.PermissionScopeArgs
            {
                Permissions = "rwd",
                ResourceName = "share1",
                Service = "file",
            },
            new AzureNative.Storage.Inputs.PermissionScopeArgs
            {
                Permissions = "rw",
                ResourceName = "share2",
                Service = "file",
            },
        },
        ResourceGroupName = "res6977",
        SshAuthorizedKeys = new[]
        {
            new AzureNative.Storage.Inputs.SshPublicKeyArgs
            {
                Description = "key name",
                Key = "ssh-rsa keykeykeykeykey=",
            },
        },
        Username = "user1",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.azurenative.storage.LocalUser;
import com.pulumi.azurenative.storage.LocalUserArgs;
import com.pulumi.azurenative.storage.inputs.PermissionScopeArgs;
import com.pulumi.azurenative.storage.inputs.SshPublicKeyArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var localUser = new LocalUser("localUser", LocalUserArgs.builder()
            .accountName("sto2527")
            .allowAclAuthorization(true)
            .groupId(2000)
            .hasSshPassword(true)
            .homeDirectory("homedirectory")
            .permissionScopes(            
                PermissionScopeArgs.builder()
                    .permissions("rwd")
                    .resourceName("share1")
                    .service("file")
                    .build(),
                PermissionScopeArgs.builder()
                    .permissions("rw")
                    .resourceName("share2")
                    .service("file")
                    .build())
            .resourceGroupName("res6977")
            .sshAuthorizedKeys(SshPublicKeyArgs.builder()
                .description("key name")
                .key("ssh-rsa keykeykeykeykey=")
                .build())
            .username("user1")
            .build());

    }
}
resources:
  localUser:
    type: azure-native:storage:LocalUser
    properties:
      accountName: sto2527
      allowAclAuthorization: true
      groupId: 2000
      hasSshPassword: true
      homeDirectory: homedirectory
      permissionScopes:
        - permissions: rwd
          resourceName: share1
          service: file
        - permissions: rw
          resourceName: share2
          service: file
      resourceGroupName: res6977
      sshAuthorizedKeys:
        - description: key name
          key: ssh-rsa keykeykeykeykey=
      username: user1

When a user connects via SFTP, Azure validates their SSH key against sshAuthorizedKeys and enforces permissions defined in permissionScopes. Each scope targets a specific file share (resourceName) and grants read, write, or delete access (permissions: “rwd” or “rw”). The hasSshPassword property enables password authentication alongside SSH keys, and homeDirectory sets the user’s landing directory after login.

Enable NFSv3 access with supplementary group membership

NFS workflows require POSIX-style group membership to control file system access based on Unix permissions.

import * as pulumi from "@pulumi/pulumi";
import * as azure_native from "@pulumi/azure-native";

const localUser = new azure_native.storage.LocalUser("localUser", {
    accountName: "sto2527",
    extendedGroups: [
        1001,
        1005,
        2005,
    ],
    isNFSv3Enabled: true,
    resourceGroupName: "res6977",
    username: "user1",
});
import pulumi
import pulumi_azure_native as azure_native

local_user = azure_native.storage.LocalUser("localUser",
    account_name="sto2527",
    extended_groups=[
        1001,
        1005,
        2005,
    ],
    is_nf_sv3_enabled=True,
    resource_group_name="res6977",
    username="user1")
package main

import (
	storage "github.com/pulumi/pulumi-azure-native-sdk/storage/v3"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := storage.NewLocalUser(ctx, "localUser", &storage.LocalUserArgs{
			AccountName: pulumi.String("sto2527"),
			ExtendedGroups: pulumi.IntArray{
				pulumi.Int(1001),
				pulumi.Int(1005),
				pulumi.Int(2005),
			},
			IsNFSv3Enabled:    pulumi.Bool(true),
			ResourceGroupName: pulumi.String("res6977"),
			Username:          pulumi.String("user1"),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using AzureNative = Pulumi.AzureNative;

return await Deployment.RunAsync(() => 
{
    var localUser = new AzureNative.Storage.LocalUser("localUser", new()
    {
        AccountName = "sto2527",
        ExtendedGroups = new[]
        {
            1001,
            1005,
            2005,
        },
        IsNFSv3Enabled = true,
        ResourceGroupName = "res6977",
        Username = "user1",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.azurenative.storage.LocalUser;
import com.pulumi.azurenative.storage.LocalUserArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var localUser = new LocalUser("localUser", LocalUserArgs.builder()
            .accountName("sto2527")
            .extendedGroups(            
                1001,
                1005,
                2005)
            .isNFSv3Enabled(true)
            .resourceGroupName("res6977")
            .username("user1")
            .build());

    }
}
resources:
  localUser:
    type: azure-native:storage:LocalUser
    properties:
      accountName: sto2527
      extendedGroups:
        - 1001
        - 1005
        - 2005
      isNFSv3Enabled: true
      resourceGroupName: res6977
      username: user1

Setting isNFSv3Enabled to true allows the user to mount storage via NFSv3 protocol. The extendedGroups property assigns supplementary group IDs (1001, 1005, 2005) that determine file system access rights. This mirrors traditional Unix group membership, where a user belongs to multiple groups for different permission levels.

Beyond these examples

These snippets focus on specific local user features: SSH key authentication and password management, NFSv3 protocol access with group membership, and file share permission scoping. They’re intentionally minimal rather than full access control configurations.

The examples reference pre-existing infrastructure such as storage accounts with SFTP or NFSv3 enabled, and Azure File shares or blob containers. They focus on configuring the user rather than provisioning the storage infrastructure.

To keep things focused, common local user patterns are omitted, including:

  • ACL authorization (allowAclAuthorization)
  • Shared key authentication (hasSharedKey)
  • Primary group ID configuration (groupId)
  • Blob container permission scopes

These omissions are intentional: the goal is to illustrate how each local user feature is wired, not provide drop-in identity modules. See the LocalUser resource reference for all available configuration options.

Let's configure Azure Storage Local Users

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Try Pulumi Cloud for FREE

Frequently Asked Questions

Resource Management & Updates
What properties can't I change after creating a local user?
The username, accountName, and resourceGroupName properties are immutable. Changing any of these requires recreating the resource.
How do I disable an authentication method for an existing user?
Set the corresponding property to false: hasSshPassword, hasSshKey, or hasSharedKey. This removes the existing credential without recreating the user.
Authentication & Authorization
What authentication methods are available for local users?
Local users support three authentication methods: SSH password (hasSshPassword), SSH key (hasSshKey), and shared key (hasSharedKey). You can enable multiple methods simultaneously.
How do I configure SSH key authentication?
Set hasSshKey to true and provide SSH public keys in the sshAuthorizedKeys array. Each key can include a description and the public key string.
What does allowAclAuthorization control?
Setting allowAclAuthorization to false disallows ACL-based authorization for the user, restricting access to only the configured permissionScopes.
Access Protocols
What's the difference between NFSv3 and SFTP local users?
NFSv3 users require isNFSv3Enabled set to true and use extendedGroups for supplementary group membership. SFTP users use SSH-based authentication (hasSshPassword or sshAuthorizedKeys) and don’t require NFSv3 enablement.
What are extended groups used for?
Extended groups provide supplementary group membership for local users enabled for NFSv3 access. Specify them as an array of integer group IDs.
Permissions & File Access
How do I grant a local user access to specific file shares?
Configure permissionScopes with an array of objects specifying permissions (r, w, d combinations), resourceName (share name), and service (typically “file”).

Using a different cloud?

Explore security guides for other cloud providers: