Last active
September 15, 2026 22:08
-
-
Save DoumanAsh/c8eacc4a58d1682b17dbbdd2655d7d68 to your computer and use it in GitHub Desktop.
Terraform module to deploy nginx-sftp pod to provide ability to test ssh/sftp alongside having easy access via HTTP listing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| variable "storage_class_name" { | |
| description = "Storage class to use to request persistent volume" | |
| type = string | |
| nullable = true | |
| default = null | |
| } | |
| variable "ssh_public_key" { | |
| description = "SSH public key for authentication" | |
| type = string | |
| } | |
| variable "user_name" { | |
| description = "Username for the SSH user" | |
| type = string | |
| default = "user" | |
| } | |
| variable "password" { | |
| description = "Password for the HTTP user. SSH user doesn't have password auth enabled" | |
| type = string | |
| sensitive = true | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| output "sftp_service_port" { | |
| description = "Internal Port of the SSH service" | |
| value = local.sftp_ext_port | |
| } | |
| output "sftp_service_name" { | |
| description = "Service name of the SSH service" | |
| value = kubernetes_service_v1.openssh_server.metadata.0.name | |
| } | |
| output "http_service_port" { | |
| description = "External Port of the HTTP service" | |
| value = local.http_ext_port | |
| } | |
| output "http_service_name" { | |
| description = "Service name of the HTTP service" | |
| value = kubernetes_service_v1.http_server.metadata.0.name | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| terraform { | |
| required_providers { | |
| htpasswd = { | |
| source = "loafoe/htpasswd" | |
| version = ">= 1, < 2" | |
| } | |
| kubernetes = { | |
| source = "hashicorp/kubernetes" | |
| version = ">= 2" | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| locals { | |
| sftp_http_service_name = "dev-sftp-http-server" | |
| http_service_name = "dev-file-server" | |
| sftp_service_name = "dev-openssh-server" | |
| sftp_ext_port = 12222 | |
| http_ext_port = 12223 | |
| } | |
| # Persistent volume for sftp data | |
| resource "kubernetes_persistent_volume_claim_v1" "sftp_file_data" { | |
| metadata { | |
| name = "sftp-server-data" | |
| } | |
| spec { | |
| access_modes = ["ReadWriteOnce"] | |
| resources { | |
| requests = { | |
| storage = "128Mi" | |
| } | |
| } | |
| storage_class_name = var.storage_class_name | |
| } | |
| # Dynamic provisioning means you will mount volume when pod requests it | |
| wait_until_bound = false | |
| } | |
| # Prepare httpasswd file for basic auth in nginx | |
| resource "random_password" "salt" { | |
| length = 8 | |
| special = false | |
| } | |
| resource "htpasswd_password" "password" { | |
| password = var.password | |
| salt = random_password.salt.result | |
| } | |
| resource "kubernetes_secret_v1" "nginx_htpasswd" { | |
| metadata { | |
| name = "nginx-htpasswd" | |
| } | |
| data = { | |
| "password" = "${var.password}" | |
| ".htpasswd" = "${var.user_name}:${htpasswd_password.password.bcrypt}" | |
| } | |
| type = "Opaque" | |
| } | |
| # Prepare persistent host certificates to ensure stable fingerprint | |
| resource "tls_private_key" "sftp_host_ecdsa_key" { | |
| algorithm = "ECDSA" | |
| ecdsa_curve = "P256" | |
| } | |
| resource "tls_private_key" "sftp_host_rsa_key" { | |
| algorithm = "RSA" | |
| rsa_bits = 4096 | |
| } | |
| resource "kubernetes_secret_v1" "sftp_host_keys" { | |
| metadata { | |
| name = "sftp-host-keys" | |
| } | |
| data = { | |
| "ssh_host_ecdsa_key" = "${tls_private_key.sftp_host_ecdsa_key.private_key_openssh}" | |
| "ssh_host_ecdsa_key.pub" = "${tls_private_key.sftp_host_ecdsa_key.public_key_openssh}" | |
| "ssh_host_rsa_key" = "${tls_private_key.sftp_host_rsa_key.private_key_openssh}" | |
| "ssh_host_rsa_key.pub" = "${tls_private_key.sftp_host_rsa_key.public_key_openssh}" | |
| } | |
| type = "Opaque" | |
| } | |
| # Deploy sftp + nginx together | |
| resource "kubernetes_deployment_v1" "sftp_http_server" { | |
| metadata { | |
| name = local.sftp_http_service_name | |
| } | |
| spec { | |
| replicas = 1 | |
| strategy { | |
| type = "Recreate" | |
| } | |
| selector { | |
| match_labels = { | |
| app = local.sftp_http_service_name | |
| } | |
| } | |
| template { | |
| metadata { | |
| labels = { | |
| app = local.sftp_http_service_name | |
| } | |
| } | |
| spec { | |
| container { | |
| name = local.sftp_http_service_name | |
| image = "quay.io/doumanash/nginx-sftp:3.24" | |
| port { | |
| container_port = local.http_ext_port | |
| } | |
| port { | |
| container_port = local.sftp_ext_port | |
| } | |
| # Define readiness by SSH service | |
| readiness_probe { | |
| tcp_socket { | |
| port = local.sftp_ext_port | |
| } | |
| initial_delay_seconds = 10 | |
| period_seconds = 10 | |
| } | |
| # Ping HTTP for probing liveness just in case | |
| liveness_probe { | |
| http_get { | |
| path = "/" | |
| port = local.http_ext_port | |
| } | |
| initial_delay_seconds = 15 | |
| period_seconds = 30 | |
| } | |
| # no need for heavy usage so minimize resources limits | |
| resources { | |
| requests = { | |
| cpu = "50m" | |
| memory = "64Mi" | |
| } | |
| limits = { | |
| cpu = "100m" | |
| memory = "128Mi" | |
| } | |
| } | |
| # Common definitions for user account | |
| env { | |
| name = "USER_NAME" | |
| value = var.user_name | |
| } | |
| env { | |
| name = "SUDO_ACCESS" | |
| value = "true" | |
| } | |
| env { | |
| name = "PUID" | |
| value = "2000" | |
| } | |
| env { | |
| name = "PGID" | |
| value = "2000" | |
| } | |
| env { | |
| name = "UMASK" | |
| value = "0022" | |
| } | |
| env { | |
| name = "TZ" | |
| value = "Etc/UTC" | |
| } | |
| # SSH settings | |
| env { | |
| name = "SSH_PORT" | |
| value = local.sftp_ext_port | |
| } | |
| env { | |
| name = "PUBLIC_KEY" | |
| value = var.ssh_public_key | |
| } | |
| env { | |
| name = "PASSWORD_ACCESS" | |
| value = "true" | |
| } | |
| env { | |
| name = "USER_PASSWORD" | |
| value_from { | |
| secret_key_ref { | |
| key = "password" | |
| name = kubernetes_secret_v1.nginx_htpasswd.metadata[0].name | |
| } | |
| } | |
| } | |
| # Define how to serve static files | |
| env { | |
| name = "HTTP_PORT" | |
| value = local.http_ext_port | |
| } | |
| env { | |
| name = "HTTP_SERVE_ROUTE" | |
| value = "/sftp" | |
| } | |
| env { | |
| name = "HTTP_SERVE_UPLOAD_ROUTE" | |
| value = "/upload-sftp" | |
| } | |
| env { | |
| name = "HTTP_SERVE_FOLDER" | |
| value = "/sftp-data" | |
| } | |
| # Keep data only for 14 days to avoid polluting it with too much sftp uploads | |
| env { | |
| name = "HTTP_SERVE_FOLDER_RETENTION_DAYS" | |
| value = "14" | |
| } | |
| volume_mount { | |
| name = "shared-data" | |
| mount_path = "/sftp-data" | |
| } | |
| volume_mount { | |
| name = "htpasswd" | |
| mount_path = "/config/nginx/.htpasswd/" | |
| sub_path = ".htpasswd" | |
| } | |
| volume_mount { | |
| name = "config-folder" | |
| mount_path = "/config" | |
| } | |
| # SFTP host keys | |
| volume_mount { | |
| name = "ssh-host-ecdsa-key" | |
| mount_path = "/config/ssh_host_keys/ssh_host_ecdsa_key" | |
| sub_path = "ssh_host_ecdsa_key" | |
| } # ssh-host-ecdsa-key | |
| volume_mount { | |
| name = "ssh-host-ecdsa-key-pub" | |
| mount_path = "/config/ssh_host_keys/ssh_host_ecdsa_key.pub" | |
| sub_path = "ssh_host_ecdsa_key.pub" | |
| } # ssh-host-ecdsa-key-pub | |
| volume_mount { | |
| name = "ssh-host-rsa-key" | |
| mount_path = "/config/ssh_host_keys/ssh_host_rsa_key" | |
| sub_path = "ssh_host_rsa_key" | |
| } # ssh-host-rsa-key | |
| volume_mount { | |
| name = "ssh-host-rsa-key-pub" | |
| mount_path = "/config/ssh_host_keys/ssh_host_rsa_key.pub" | |
| sub_path = "ssh_host_rsa_key.pub" | |
| } # ssh-host-rsa-key-pub | |
| } | |
| security_context { | |
| fs_group = 2000 | |
| } | |
| volume { | |
| name = "shared-data" | |
| persistent_volume_claim { | |
| claim_name = kubernetes_persistent_volume_claim_v1.sftp_file_data.metadata[0].name | |
| } | |
| } //shared-data | |
| volume { | |
| name = "htpasswd" | |
| secret { | |
| secret_name = kubernetes_secret_v1.nginx_htpasswd.metadata[0].name | |
| items { | |
| key = ".htpasswd" | |
| path = ".htpasswd" | |
| } | |
| } | |
| } //httpasswd | |
| volume { | |
| name = "config-folder" | |
| empty_dir { | |
| size_limit = "32Mi" | |
| } | |
| } //config-folder | |
| volume { | |
| name = "ssh-host-ecdsa-key" | |
| secret { | |
| secret_name = kubernetes_secret_v1.sftp_host_keys.metadata[0].name | |
| items { | |
| key = "ssh_host_ecdsa_key" | |
| path = "ssh_host_ecdsa_key" | |
| } | |
| } | |
| } //ssh-host-ecdsa-key | |
| volume { | |
| name = "ssh-host-ecdsa-key-pub" | |
| secret { | |
| secret_name = kubernetes_secret_v1.sftp_host_keys.metadata[0].name | |
| items { | |
| key = "ssh_host_ecdsa_key.pub" | |
| path = "ssh_host_ecdsa_key.pub" | |
| } | |
| } | |
| } //ssh-host-ecdsa-key-pub | |
| volume { | |
| name = "ssh-host-rsa-key" | |
| secret { | |
| secret_name = kubernetes_secret_v1.sftp_host_keys.metadata[0].name | |
| items { | |
| key = "ssh_host_rsa_key" | |
| path = "ssh_host_rsa_key" | |
| } | |
| } | |
| } //ssh-host-rsa-key | |
| volume { | |
| name = "ssh-host-rsa-key-pub" | |
| secret { | |
| secret_name = kubernetes_secret_v1.sftp_host_keys.metadata[0].name | |
| items { | |
| key = "ssh_host_rsa_key.pub" | |
| path = "ssh_host_rsa_key.pub" | |
| } | |
| } | |
| } //ssh-host-rsa-key-pub | |
| } | |
| } | |
| } | |
| } | |
| ###### | |
| ## Services | |
| ###### | |
| resource "kubernetes_service_v1" "openssh_server" { | |
| metadata { | |
| name = local.sftp_service_name | |
| } | |
| spec { | |
| selector = { | |
| app = kubernetes_deployment_v1.sftp_http_server.metadata.0.name | |
| } | |
| port { | |
| port = local.sftp_ext_port | |
| target_port = local.sftp_ext_port | |
| protocol = "TCP" | |
| } | |
| type = "ClusterIP" | |
| } | |
| } | |
| resource "kubernetes_service_v1" "http_server" { | |
| # Use it if you plan to expose HTTP server via load balancer. If you do not need external connection then remove this line | |
| wait_for_load_balancer = true | |
| metadata { | |
| name = local.http_service_name | |
| } | |
| spec { | |
| selector = { | |
| app = kubernetes_deployment_v1.sftp_http_server.metadata.0.name | |
| } | |
| port { | |
| port = local.http_ext_port | |
| target_port = local.http_ext_port | |
| protocol = "TCP" | |
| } | |
| type = "ClusterIP" | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment