First Impressions about Azure sFTP

SSH File Transfer Protocol is a very common protocol used by many customers for secured file transfer over a secure shell. Microsoft did not have a fully managed SFTP service in Azure, but now is it possible to do it with Azure Blob Storage.

So, you will be able to use an SFTP client to connect to that storage account and manage the objects inside and even specify permissions for each user.

But before beginning, you will need to register the SFTP feature in your subscription, to do that you have to type the following:

# Set the Azure context for the desired subscription
az account set --subscription "xxxx-xxxx-xxxx-xxxx"

# Check if the live tier feature is registered first
az feature show --namespace Microsoft.Storage --name AllowSFTP

# Register the live tier feature on your subscription
az feature register --namespace Microsoft.Storage --name AllowSFTP

Also, you can check that information in Preview Features option in the Azure Portal:

Once you have that, you will need to enable the hierarchical namespace in the storage account, note that you can’t enable that on an existing storage account…

BEFORE

AFTER

At the time of writing, I couldn’t create the FTPS service through the Azure Portal or event with template, when I select WestEurope as destination, in the future I’m sure that would be supported

Now, we can deploy the ARM template to the RG previously created in Azure, but, previously to do that, you have to decide if your user will connect through Password or a SSH Key. In my case, I decided to implement it with an ARM Template with SSH key, but first you need to generate an SSH key pair:

next I provide the two ARM templates for both types of implementation

Template FOR PASSWORD Implementation:

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "storageAccountType": {
      "type": "string",
      "defaultValue": "Standard_LRS",
      "allowedValues": ["Standard_LRS", "Standard_ZRS"],
      "metadata": { "description": "Storage Account type" }
    },
    "location": {
      "type": "string",
      "defaultValue": "northeurope",
      "allowedValues": ["westeurope", "northcentralus", "eastus2", "eastus2euap", "centralus", "canadaeast", "canadacentral", "northeurope", "australiaeast", "switzerlandnorth", "germanywestcentral", "eastasia", "francecentral"],
      "metadata": { "description": "Region" }
    },
    "storageAccountName": {
      "type": "string",
      "metadata": { "description": "Storage Account Name" }
    },
    "userName": {
      "type": "string",
      "metadata": { "description": "Username of primary user" }
    },
    "homeDirectory": {
      "type": "string",
      "metadata": { "description": "Home directory of primary user. Should be a container." }
    }
  },
  "resources": [
    {
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2021-02-01",
      "name": "[parameters('storageAccountName')]",
      "location": "[parameters('location')]",
      "sku": {
        "name": "[parameters('storageAccountType')]"
      },
      "kind": "StorageV2",
      "properties": {
          "isHnsEnabled": true,
          "isSftpEnabled": true
      },
      "resources": [
        {
          "type": "blobServices/containers",
          "apiVersion": "2021-02-01",
          "name": "[concat('default/', parameters('homeDirectory'))]",
          "dependsOn": ["[parameters('storageAccountName')]"],
          "properties": {
            "publicAccess": "None"
          }
        },
        {
          "type": "localUsers",
          "apiVersion": "2021-02-01",
          "name": "[parameters('userName')]",
          "properties": {
            "permissionScopes": [
                {
                  "permissions": "rcwdl",
                  "service": "blob",
                  "resourceName": "[parameters('homeDirectory')]"
                }
            ],
            "homeDirectory": "[parameters('homeDirectory')]",
            "hasSharedKey": false
          },
          "dependsOn": ["[parameters('storageAccountName')]"]
        }
      ]
    }
  ],
  "outputs": {
    "defaultContainer": {
      "type": "string",
      "value": "[parameters('homeDirectory')]"
    },
    "user": {
      "type": "object",
      "value": "[reference(
        resourceId('Microsoft.Storage/storageAccounts/localUsers', parameters('storageAccountName'), parameters('userName'))
      )]"
    }
  }
}

Template for SSH Implementation

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "storageAccountType": {
      "type": "string",
      "defaultValue": "Standard_LRS",
      "allowedValues": ["Standard_LRS", "Standard_ZRS"],
      "metadata": { "description": "Storage Account type" }
    },
    "location": {
      "type": "string",
      "defaultValue": "northeurope",
      "allowedValues": ["westeurope", "northcentralus", "eastus2", "eastus2euap", "centralus", "canadaeast", "canadacentral", "northeurope", "australiaeast", "switzerlandnorth", "germanywestcentral", "eastasia", "francecentral"],
      "metadata": { "description": "Region" }
    },
    "storageAccountName": {
      "type": "string",
      "metadata": { "description": "Storage Account Name" }
    },
    "userName": {
      "type": "string",
      "metadata": { "description": "Username of primary user" }
    },
    "homeDirectory": {
      "type": "string",
      "metadata": { "description": "Home directory of primary user. Should be a container." }
    },
    "publicKey": {
      "type": "string",
      "metadata": { "description": "SSH Public Key for primary user." }
    }
  },
  "resources": [
    {
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2019-06-01",
      "name": "[parameters('storageAccountName')]",
      "location": "[parameters('location')]",
      "sku": {
        "name": "[parameters('storageAccountType')]"
      },
      "kind": "StorageV2",
      "properties": {
          "isHnsEnabled": true,
          "isLocalUserEnabled": true,
          "isSftpEnabled": true
      },
      "resources": [
        {
          "type": "blobServices/containers",
          "apiVersion": "2019-06-01",
          "name": "[concat('default/', parameters('homeDirectory'))]",
          "dependsOn": ["[parameters('storageAccountName')]"],
          "properties": {
            "publicAccess": "None"
          }
        },
        {
          "type": "localUsers",
          "apiVersion": "2019-06-01",
          "name": "[parameters('userName')]",
          "properties": {
            "permissionScopes": [
                {
                  "permissions": "rcwdl",
                  "service": "blob",
                  "resourceName": "[parameters('homeDirectory')]"
                }
            ],
            "homeDirectory": "[parameters('homeDirectory')]",
            "sshAuthorizedKeys": [
              {
                "description": "localuser public key",
                "key": "[parameters('publicKey')]"
              }
            ],
            "hasSharedKey": false
          },
          "dependsOn": ["[parameters('storageAccountName')]"]
        }
      ]
    }
  ],
  "outputs": {
    "defaultContainer": {
      "type": "string",
      "value": "[parameters('homeDirectory')]"
    },
    "user": {
      "type": "object",
      "value": "[reference(
        resourceId('Microsoft.Storage/storageAccounts/localUsers', parameters('storageAccountName'), parameters('userName'))
      )]"
    },

    "keys": {
      "type": "object",
      "value": "[listKeys(resourceId('Microsoft.Storage/storageAccounts/localUsers', parameters('storageAccountName'), parameters('userName')), '2019-06-01')]"
    }
  }
}

Once you have deployed the template, you can go to the portal to configure the user permission:

Remember to keep the password, without that you can’t be able to connect to the SFTP

And now, you can connect to the SFTP via PS or other preferred tool

And play with some of the files:

We can check the blob itself to review the information about the recent uploads:

As you have seen, now you’re able to deploy your SFTP for Azure Blob Storage without worrying about Container Solutions or other weird experiments.

Till next time!