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 POSIX group membership, and authentication method management.

Local users belong to storage accounts and reference file shares that must exist separately. The examples are intentionally small. Combine them with your own storage accounts, file shares, and security 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 Files 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

The sshAuthorizedKeys property defines public keys for authentication. The permissionScopes array grants access to specific file shares, where permissions uses a string of letters (r=read, w=write, d=delete), resourceName identifies the share, and service specifies “file” for Azure Files. Setting hasSshPassword to true enables password authentication alongside SSH keys.

Enable NFSv3 access with supplementary group membership

NFS workflows require POSIX-style group membership to control file system access.

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

The isNFSv3Enabled property activates NFSv3 protocol support. The extendedGroups array defines supplementary group IDs beyond the primary group, allowing the user to access files owned by multiple groups. This mirrors traditional Unix group membership for NFS mounts.

Disable authentication methods and update group membership

Security policies may require disabling authentication methods or updating group membership without recreating the user.

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: false,
    extendedGroups: [
        1001,
        1005,
        2005,
    ],
    groupId: 3000,
    hasSharedKey: false,
    hasSshKey: false,
    hasSshPassword: false,
    homeDirectory: "homedirectory2",
    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",
    allow_acl_authorization=False,
    extended_groups=[
        1001,
        1005,
        2005,
    ],
    group_id=3000,
    has_shared_key=False,
    has_ssh_key=False,
    has_ssh_password=False,
    home_directory="homedirectory2",
    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"),
			AllowAclAuthorization: pulumi.Bool(false),
			ExtendedGroups: pulumi.IntArray{
				pulumi.Int(1001),
				pulumi.Int(1005),
				pulumi.Int(2005),
			},
			GroupId:           pulumi.Int(3000),
			HasSharedKey:      pulumi.Bool(false),
			HasSshKey:         pulumi.Bool(false),
			HasSshPassword:    pulumi.Bool(false),
			HomeDirectory:     pulumi.String("homedirectory2"),
			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",
        AllowAclAuthorization = false,
        ExtendedGroups = new[]
        {
            1001,
            1005,
            2005,
        },
        GroupId = 3000,
        HasSharedKey = false,
        HasSshKey = false,
        HasSshPassword = false,
        HomeDirectory = "homedirectory2",
        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")
            .allowAclAuthorization(false)
            .extendedGroups(            
                1001,
                1005,
                2005)
            .groupId(3000)
            .hasSharedKey(false)
            .hasSshKey(false)
            .hasSshPassword(false)
            .homeDirectory("homedirectory2")
            .isNFSv3Enabled(true)
            .resourceGroupName("res6977")
            .username("user1")
            .build());

    }
}
resources:
  localUser:
    type: azure-native:storage:LocalUser
    properties:
      accountName: sto2527
      allowAclAuthorization: false
      extendedGroups:
        - 1001
        - 1005
        - 2005
      groupId: 3000
      hasSharedKey: false
      hasSshKey: false
      hasSshPassword: false
      homeDirectory: homedirectory2
      isNFSv3Enabled: true
      resourceGroupName: res6977
      username: user1

Setting hasSharedKey, hasSshKey, and hasSshPassword to false disables those authentication methods. The groupId property sets the primary POSIX group, while extendedGroups adds supplementary groups. The allowAclAuthorization property controls whether the user can use ACL-based authorization instead of permission scopes.

Beyond these examples

These snippets focus on specific local user features: SSH and NFSv3 authentication, file share permission scopes, and POSIX group membership. They’re intentionally minimal rather than full identity management solutions.

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

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

  • Shared key generation and rotation (hasSharedKey)
  • Home directory path configuration (homeDirectory)
  • ACL authorization controls (allowAclAuthorization)
  • SSH password management (hasSshPassword)

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

Configuration & Constraints
What properties can't I change after creating a local user?
The accountName, username, and resourceGroupName properties are immutable. Changing any of these requires recreating the resource.
What are the username requirements for local users?
Usernames must contain only lowercase letters and numbers, and must be unique within the storage account.
What are the storage account name requirements?
Storage account names must be 3 to 24 characters long and contain only lowercase letters and numbers.
What are the sid and userId properties?
These are unique identifiers automatically generated by the server. You cannot set them manually; they appear only in the resource outputs.
Authentication & Access
How do I set up SFTP access for a local user?
Configure sshAuthorizedKeys with your SSH public keys, set hasSshPassword to true if using password authentication, and define permissionScopes to grant access to specific file shares.
How do I set up NFSv3 access for a local user?
Set isNFSv3Enabled to true. You can optionally configure extendedGroups to specify supplementary group membership for the user.
How do I remove SSH keys, passwords, or shared keys from a local user?
Set the corresponding property to false: hasSshKey to remove SSH keys, hasSshPassword to remove SSH passwords, or hasSharedKey to remove shared keys.
What's the difference between NFSv3 and SFTP access?
NFSv3 access uses the isNFSv3Enabled property and supports extendedGroups for group membership. SFTP access uses sshAuthorizedKeys and hasSshPassword for authentication. These are distinct access patterns for different protocols.
Permissions & Authorization
How do I grant a local user permissions to file shares?
Use the permissionScopes property to define access. Each scope specifies a service (e.g., “file”), resourceName (the share name), and permissions (e.g., “rwd” for read/write/delete).
What does the allowAclAuthorization property do?
It controls whether ACL-based authorization is allowed for the user. Set it to false to disallow ACL authorization.

Using a different cloud?

Explore security guides for other cloud providers: