atmos

Création d’un service SFTP dans Azure

Jan 15, 2021

Nicolas Bailly

Si vous avez besoin d’avoir un serveur FTP(ou SFTP) pour transférer des fichiers, il y a plusieurs solutions et vous vous tournerez probablement vers la création d’une VM, ce qui a l’inconvénient du coût de maintenance et d’exploitation de celle-ci.
Il n’existe malheureusement pas de service managé prévu à cet effet dans Azure (à l’heure où j’écris cet article). Nous allons voir comment combiner 2 services managés pour remédier à ce manque : un compte de stockage et une instance conteneur. Le tout scripté dans du terraform pour simplifier les déploiements.

Création d’un partage réseau

Dans un premier temps, nous allons créer un partage réseau sur un compte de stockage. Ce compte de stockage servira à stocker les fichiers échangés lors des transfert sur le Service SFTP. Sur ce compte de stockage nous allons créer un partage réseau qui sera ensuite monté sur le service SFTP.

Voici le code terraform pour le compte de stockage :

resource "azurerm_resource_group" "infra_resource_group" {
  name     = "rg-demo-sftp"
  location = "North Europe"
}

resource "azurerm_storage_account" "storage_account" {
  name                      = "stademoftp"
  location                  = azurerm_resource_group.infra_resource_group.location
  resource_group_name       = azurerm_resource_group.infra_resource_group.name
  account_tier              = "Standard"
  account_replication_type  = "LRS"
  account_kind              = "StorageV2"
  min_tls_version           = "TLS1_2"
  enable_https_traffic_only = true
}

resource "azurerm_storage_share" "share" {
  name                 = "sharesftp"
  storage_account_name = azurerm_storage_account.storage_account.name
  quota                = 10
}

Ce script fait 3 actions

  • Création d’un groupe de ressource en Europe du Nord
  • Création d’un compte de stockage. On fait le choix ici d’avoir une tarification au minimum : LRS Standard. A vous de mettre les paramètres qui vous conviennent
  • Création d’un partage réseau (« File Share »), sur ce compte de stockage avec un quota de 10 Go.

Création d’une instance conteneur exposant un serveur SFTP

Nous allons maintenant créer une instance container à partir de l’image disponible « atmoz/sftp ». Cette image est prévu à cet effet. Nous allons pouvoir monter le partage réseau sur celle-ci. Voici le code terraform correspondant :

locals {
  sftp_login = "demo-user"
  sftp_password = "Welcom@123"
}

resource "azurerm_container_group" "sftp" {
  name                = "cgp-demo-sftp"
  location            = azurerm_resource_group.infra_resource_group.location
  resource_group_name = azurerm_resource_group.infra_resource_group.name
  ip_address_type     = "Public"
  dns_name_label      = "dcubesftpdemo"

  os_type             = "Linux"
  restart_policy      = "OnFailure"

  container {
    name   = "sftp"
    image  = "atmoz/sftp:latest"
    cpu    = "0.5"
    memory = "1"

    ports {
      protocol = "TCP"
      port     = 22
    }

    secure_environment_variables = {
      SFTP_USERS = "${local.sftp_login}:${local.sftp_password}:1001"
    }

    volume {
      name = "sftpvolume"
      storage_account_name = azurerm_storage_account.storage_account.name
      storage_account_key = azurerm_storage_account.storage_account.primary_access_key
      share_name = azurerm_storage_share.share.name
      mount_path = "/home/${local.sftp_login}/upload"
      read_only = false
    }
  }
}

Voyons en détail ce que fait ce script :

  • Création d’un groupe de container accessible publiquement par l’URL dcubesftpdemo.northeurope.azurecontainer.io
  • Le port 22 est ouvert sur le protocol TCP
  • Dans la partie « Volume », on pointe sur le partage de fichiers que nous avons créé dans le chapitre précédent
  • Dans la variable « SFTP_USERS », on passe le login et le mot de passe qui serviront à la connexion. Attention, il faut bien mettre cette variable dans la propriété « secure_environment_variables » et non dans la propriété « environment_variables » comme on peut le voir sur certains articles. Sinon les identifiants seront accessibles en clair pour quiconque accède aux propriétés du conteneur

C’est prêt, on peut tester

A partir de là, on a un serveur SFTP qui est prêt à l’emploi.

On peut voir ici que quand on dépose un fichier sur le serveur SFTP, on retrouve bien ce fichier sur le partage du compte de stockage

Ajouter d’autres points de montage

Il est possible d’avoir des accès à plusieurs partages de fichiers avec des identifiants de connexion pour chacun d’eux. Pour cela, il faut mettre les différents utilisateurs dans la variable SFTP_USERS en les séparant par des espaces puis ajouter des propriétés « Volume ». Voici un exemple :

resource "azurerm_container_group" "sftp" {
  name                = "cgp-demo-sftp"
  location            = azurerm_resource_group.infra_resource_group.location
  resource_group_name = azurerm_resource_group.infra_resource_group.name
  ip_address_type     = "Public"
  dns_name_label      = "dcubesftpdemo"

  os_type             = "Linux"
  restart_policy      = "OnFailure"

  container {
    name   = "sftp"
    image  = "atmoz/sftp:latest"
    cpu    = "0.5"
    memory = "1"

    ports {
      protocol = "TCP"
      port     = 22
    }

    secure_environment_variables = {
      SFTP_USERS = "${local.sftp_login1}:${local.sftp_password1}:1001 ${local.sftp_login2}:${local.sftp_password2}:1002 ${local.sftp_login3}:${local.sftp_password3}:1003"
    }

    volume {
      name = "sftpvolume"
      storage_account_name = azurerm_storage_account.storage_account.name
      storage_account_key = azurerm_storage_account.storage_account.primary_access_key
      share_name = azurerm_storage_share.share1.name
      mount_path = "/home/${local.sftp_login1}/upload"
      read_only = false
    }

    volume {
      name = "sftpvolume"
      storage_account_name = azurerm_storage_account.storage_account.name
      storage_account_key = azurerm_storage_account.storage_account.primary_access_key
      share_name = azurerm_storage_share.share2.name
      mount_path = "/home/${local.sftp_login2}/upload"
      read_only = false
    }

    volume {
      name = "sftpvolume"
      storage_account_name = azurerm_storage_account.storage_account.name
      storage_account_key = azurerm_storage_account.storage_account.primary_access_key
      share_name = azurerm_storage_share.share3.name
      mount_path = "/home/${local.sftp_login3}/upload"
      read_only = false
    }
  }
}

Par rapport au script précédent, dans ce script on a ajouté 2 fois la propriété « volume » qui permet d’ajouter des points de montage vers de nouveaux partages de fichiers et nous avons ajouté 2 utilisateurs dans la variable SFTP_USERS.

Et si on sécurisait ?

Le problème des conteneurs Azure, c’est qu’on ne peut pas filtrer les IP si c’est accessible en public. Il va falloir mettre le groupe de conteneurs sur un subnet pour qu’il n’y ait pas d’IP publique directement accessible.
Une fois que c’est sur un subnet, vous pourrez mettre en place un firewall qui pourra filtrer les appels entrants. Pour cela, il faut créer un profil réseau que l’on rattache au groupe de conteneurs. Voici le script terraform qui permet de le faire :

data "azurerm_virtual_network" "vnet" {
  name                = "demo-vnet"
  resource_group_name = "azurerm_resource_group.infra_resource_group.name"
}

resource "azurerm_subnet" "subnet_ftp" {
  name                 = "sub-demo-sftp"
  resource_group_name  = azurerm_resource_group.infra_resource_group.name
  virtual_network_name = azurerm_virtual_network.vnet.name
  address_prefixes     = ["10.30.2.0/27"]
  service_endpoints    = ["Microsoft.Storage"]
  delegation {
    name = "containergroupdelegation"

    service_delegation {
      name    = "Microsoft.ContainerInstance/containerGroups"
      actions = ["Microsoft.Network/virtualNetworks/subnets/action"] 
    }
  }
}

resource "azurerm_network_profile" "network_profile" {
  name                = "sftpnetprofile"
  location            = azurerm_resource_group.infra_resource_group.location
  resource_group_name = azurerm_resource_group.infra_resource_group.name

  container_network_interface {
    name = "ftpnic"

    ip_configuration {
      name      = "netpsftp"
      subnet_id = azurerm_subnet.subnet_ftp.id
    }
  }
}

resource "azurerm_container_group" "sftp" {
  name                = "cgp-demo-sftp"
  location            = azurerm_resource_group.infra_resource_group.location
  resource_group_name = azurerm_resource_group.infra_resource_group.name
  ip_address_type     = "Private"
  network_profile_id  = azurerm_network_profile.network_profile.id

  os_type             = "Linux"
  restart_policy      = "OnFailure"

  container {
    name   = "sftp"
    image  = "atmoz/sftp:latest"
    cpu    = "0.5"
    memory = "1"

    ports {
      protocol = "TCP"
      port     = 22
    }

    secure_environment_variables = {
      SFTP_USERS = "${local.sftp_login}:${local.sftp_password}:1001"
    }

    volume {
      name = "sftpvolume"
      storage_account_name = azurerm_storage_account.storage_account.name
      storage_account_key = azurerm_storage_account.storage_account.primary_access_key
      share_name = azurerm_storage_share.share.name
      mount_path = "/home/${local.sftp_login}/upload"
      read_only = false
    }
  }
}

Qu’est-ce que fait ce script :

  • On récupère d’abord les informations d’un vnet existant
  • On crée un subnet sur ce vnet. Ce subnet doit avoir une délégation « Microsoft.ContainerInstance/containerGroups » pour pouvoir y héberger des conteneurs. Nous ajoutons également un service endpoint « Microsoft.Storage » pour permettre d’accéder au compte de stockage sans repasser par internet.
  • On crée un profil réseau qui est rattaché à ce subnet
  • Dans la création du group de conteneurs, on modifie la propriété « ip_address_type » pour la passer à « Private », on supprime la propriété « dns_name_label » (incompatible avec le mode Private) et on ajoute la propriété « network_profile_id »

Quand vous exécutez ce script, une IP privée va être affectée sur votre subnet. Dans notre exemple, notre subnet est sur l’adressage 10.30.2.0/27 et comme nous venons de le créer, l’IP sera la première disponible, c’est-à-dire 10.30.2.4.
Pour tester, vous devez utiliser cette IP depuis votre réseau, par exemple une VM qui serait sur le même vnet.
Pour le rendre disponible en public, il vous faudra configurer votre Firewall ou en ajouter un si vous n’en avez pas. Ainsi, vous pourrez filtrer les IP entrantes.

Le compte de stockage peut, lui aussi, être isolé sur le Vnet. Ca ne change rien au script de création du conteneur, il faut ajouter un private endpoint au compte de stockage. Par contre, il faudra vérifier que votre DNS puisse résoudre le nom privé du compte de stockage afin que le conteneur puisse y accéder.

Problèmes rencontrés

Si la création du conteneur prend plus de 5 minutes, c’est qu’il y a un problème(Lors de mes tests ça n’a jamais pris plus de 2 minutes). Dans ce cas, vous pouvez arrêter la création et le supprimer sinon vous allez devoir attendre le timeout qui est par défaut d’une demi-heure.
J’ai rencontré 2 problèmes où la création du conteneur n’aboutissait pas :

  • en ajoutant des comptes utilisateurs, j’ai fait l’erreur de créer des comptes dont le login était déjà utilisé. Ca ne remonte aucune erreur, il faut donc être vigilant en ajoutant les logins permettant d’accéder au service SFTP.
  • en mettant le compte de stockage sur un private link, le conteneur ne pouvait plus y accéder. C’était parce que j’ai un mon propre serveur DNS et je n’avais pas enregistrer l’IP privée de celui-ci, du coup le conteneur n’arrivait pas à résoudre le nom.

Conclusion

Nous avons ici un service qui ne coûte pas grand chose et demande peu de maintenance. Il est même possible d’éteindre le conteur ou de le supprimer pour économiser les coûts azure, tant que les fichiers restent sur le compte de stockage.

Liens annexes

https://docs.microsoft.com/en-us/samples/azure-samples/sftp-creation-template/sftp-on-azure/
https://github.com/atmoz/sftp

1 Commentaire

  1. Terry H.

    Very useful and underrated post. I was working for a client with legacy FTP and they asked me to improve a script pushing files into it. Rather than flooding their FTP I’ve created mine with this solution.

    Works like a charm !

    Réponse

Soumettre un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Découvrez nos autres articles

git et la face cachée du Rebase

git et la face cachée du Rebase

"Faire une rebase ? *sight* heu... ok..." Jean-Michel Fullstack - Développeur fébrile Jean-Michel est inquiet. En effet, lorsque nous collaborons à plusieurs sur un projet, quelque soit les technologies utilisées, il est important de garder à l'esprit que notre...

lire plus
Aller au contenu principal