|
| 1 | +--- |
| 2 | +slug: sftp-server-in-kubernetes-with-sftpgo |
| 3 | +title: SFTP server in Kubernetes with SFTPGo |
| 4 | +authors: maría |
| 5 | +tags: [DevOps, PlatformEngineering, SFTPGo, Kubernetes, DeveloperExperience, ExternalSecrets, Terraform, AWS] |
| 6 | +image: URLimage.png |
| 7 | +--- |
| 8 | + |
| 9 | +Have you ever needed to create an SFTP server? How do you do it? |
| 10 | + |
| 11 | +For those who have created an SFTP server before, you probably know that it is not easy to create and maintain an SFTP server. There are many ways to do it, but in this case, we are going to install SFTPGo in our Kubernetes cluster. |
| 12 | + |
| 13 | +SFTPGo is an open-source SFTP server that allows users to securely transfer files over SSH. It is written in Go (Golang) and is designed to be lightweight, easy to configure, and highly customizable. It supports multiple storage backends, including local filesystems, cloud storage (like S3, Google Cloud Storage, etc.), and more. |
| 14 | + |
| 15 | +The deployment of SFTPGo on an EKS cluster begins with provisioning the required resources, so let's start by creating the necessary infrastructure with Terraform. |
| 16 | + |
| 17 | +<!--truncate--> |
| 18 | + |
| 19 | +# External Secret |
| 20 | + |
| 21 | +For managing secrets in EKS, we are using [External Secrets Operator](https://external-secrets.io/). To store the secrets for our SFTP server, we will use AWS Secrets Manager. An external secret is a Kubernetes resource that allows you to manage secrets from an external secret manager, in this case, AWS Secrets Manager. |
| 22 | + |
| 23 | +Here is the code to create the secret in AWS Secrets Manager: |
| 24 | + |
| 25 | +```hcl |
| 26 | +resource "aws_secretsmanager_secret" "sftpgo" { |
| 27 | + name = "sftpgo" |
| 28 | + description = "Secrets for sftpgo in EKS production cluster" |
| 29 | +} |
| 30 | +``` |
| 31 | + |
| 32 | +Once created, we will need to enter the following secrets: |
| 33 | + |
| 34 | +- `SFTPGO_DEFAULT_ADMIN_USERNAME`: example_user |
| 35 | +- `SFTPGO_DEFAULT_ADMIN_PASSWORD`: example_password |
| 36 | +- `SFTPGO_DATA_PROVIDER__DRIVER`: postgresql |
| 37 | +- `SFTPGO_DATA_PROVIDER__NAME`: sftpgo.db |
| 38 | +- `SFTPGO_DATA_PROVIDER__HOST`: sftpgo-postgresql.sftpgo.svc.cluster.local *(this might change, it depends on your needs!)* |
| 39 | +- `SFTPGO_DATA_PROVIDER__PORT`: 5432 |
| 40 | +- `SFTPGO_DATA_PROVIDER__USERNAME`: sftpgo |
| 41 | +- `SFTPGO_DATA_PROVIDER__PASSWORD`: sftpgo_pg_pwd |
| 42 | + |
| 43 | +As you can see, we are using a PostgreSQL database to store the users and the configuration for our SFTP server. And of course, we need to create the database. We will do this in the next section. |
| 44 | + |
| 45 | +# SFTPGo resources |
| 46 | + |
| 47 | +On the other hand, it is necessary to create any other resource related to this new SFTP (policies, IAM role, permissions, etc.). |
| 48 | + |
| 49 | +We will create one role to access the **AWS Secrets Manager** and another to **access an S3 bucket**. |
| 50 | + |
| 51 | +> The **s3 bucket** is used for storing the documents managed in the SFTP. We can use a single bucket for everything, but it is possible to use multiple buckets. Each user inside the SFTP can have access to a different bucket, or even a different folder inside the same bucket. |
| 52 | +
|
| 53 | +Our code would look something like this: |
| 54 | + |
| 55 | +```hcl |
| 56 | +data "aws_iam_policy_document" "sftpgo" { |
| 57 | + statement { |
| 58 | + actions = [ |
| 59 | + "secretsmanager:GetResourcePolicy", |
| 60 | + "secretsmanager:GetSecretValue", |
| 61 | + "secretsmanager:DescribeSecret", |
| 62 | + "secretsmanager:ListSecretVersionIds" |
| 63 | + ] |
| 64 | + resources = [aws_secretsmanager_secret.sftpgo.arn] |
| 65 | + } |
| 66 | +} |
| 67 | +
|
| 68 | +resource "aws_iam_policy" "sftpgo" { |
| 69 | + name = "sftpgo" |
| 70 | + path = "/" |
| 71 | + description = "Policy to get sftpgo secrets" |
| 72 | + policy = data.aws_iam_policy_document.sftpgo.json |
| 73 | +} |
| 74 | +
|
| 75 | +resource "aws_iam_role" "sftpgo" { |
| 76 | + name = "external-secrets-sftpgo" |
| 77 | +
|
| 78 | + assume_role_policy = jsonencode({ |
| 79 | + Version = "2012-10-17" |
| 80 | + Statement = [ |
| 81 | + { |
| 82 | + Action = "sts:AssumeRole" |
| 83 | + Effect = "Allow" |
| 84 | + Principal = { |
| 85 | + AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/external-secrets" |
| 86 | + } |
| 87 | + } |
| 88 | + ] |
| 89 | + }) |
| 90 | +} |
| 91 | +
|
| 92 | +resource "aws_iam_policy_attachment" "sftpgo" { |
| 93 | + name = "sftpgo" |
| 94 | + roles = [aws_iam_role.sftpgo.name] |
| 95 | + policy_arn = aws_iam_policy.sftpgo.arn |
| 96 | +} |
| 97 | +
|
| 98 | +resource "aws_s3_bucket" "sftpgo" { |
| 99 | + bucket = "sftpgo" # TODO: change this to the name of the bucket you want to use |
| 100 | +} |
| 101 | +
|
| 102 | +data "aws_iam_policy_document" "s3_full_access" { |
| 103 | + statement { |
| 104 | + actions = [ |
| 105 | + "s3:*", |
| 106 | + ] |
| 107 | + resources = [ |
| 108 | + aws_s3_bucket.sftpgo.arn, |
| 109 | + "${aws_s3_bucket.sftpgo.arn}/*", |
| 110 | + ] |
| 111 | + } |
| 112 | +} |
| 113 | +
|
| 114 | +resource "aws_iam_policy" "s3_full_access" { |
| 115 | + name = "S3AccessPolicy-${aws_s3_bucket.sftpgo.bucket}" |
| 116 | + policy = data.aws_iam_policy_document.s3_full_access.json |
| 117 | +} |
| 118 | +
|
| 119 | +resource "aws_iam_role" "irsa_role" { |
| 120 | + name = "sftpgo" |
| 121 | +
|
| 122 | + assume_role_policy = jsonencode({ |
| 123 | + Version = "2012-10-17" |
| 124 | + Statement = [ |
| 125 | + { |
| 126 | + Effect = "Allow" |
| 127 | + Principal = { |
| 128 | + Federated = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/${replace(data.aws_eks_cluster.cluster.identity[0].oidc[0].issuer, "https://", "")}" |
| 129 | + } |
| 130 | + Action = "sts:AssumeRoleWithWebIdentity" |
| 131 | + Condition = { |
| 132 | + StringEquals = { |
| 133 | + "${replace(data.aws_eks_cluster.cluster.identity[0].oidc[0].issuer, "https://", "")}:sub" = "system:serviceaccount:sftpgo:sftpgo" |
| 134 | + } |
| 135 | + } |
| 136 | + } |
| 137 | + ] |
| 138 | + }) |
| 139 | +} |
| 140 | +
|
| 141 | +resource "aws_iam_role_policy_attachment" "attach_s3_policy" { |
| 142 | + role = aws_iam_role.irsa_role.name |
| 143 | + policy_arn = aws_iam_policy.s3_full_access.arn |
| 144 | +} |
| 145 | +
|
| 146 | +resource "aws_s3_bucket_public_access_block" "sftpgo" { |
| 147 | + bucket = aws_s3_bucket.sftpgo.id |
| 148 | +
|
| 149 | + block_public_acls = true |
| 150 | + block_public_policy = true |
| 151 | + ignore_public_acls = true |
| 152 | + restrict_public_buckets = true |
| 153 | +} |
| 154 | +``` |
| 155 | + |
| 156 | +>**¿What´s the IRSA Role?**: This Terraform code creates an IAM role that can be assumed by a ServiceAccount in an EKS cluster via IRSA. The role has a policy attached to it that allows access to S3. This is useful for applications running on Kubernetes that need to access AWS resources, such as S3, securely and without needing to store credentials directly on the cluster. |
| 157 | +
|
| 158 | +To continue, we must also add a new data reference to our EKS cluster in our `data.tf`: |
| 159 | + |
| 160 | +```hcl |
| 161 | +data "aws_eks_cluster" "cluster" { |
| 162 | + name = "example_name_cluster" # TODO: change this to the name of your cluster |
| 163 | +} |
| 164 | +``` |
| 165 | + |
| 166 | +--- |
| 167 | + |
| 168 | +# SFTPGo Helm Chart |
| 169 | + |
| 170 | +When everything mentioned above has been created, we can continue creating the necessary chart to set up our SFTP server in Kubernetes. |
| 171 | + |
| 172 | +Let's start by creating a new folder called `sftpgo`. Inside this folder, we will begin by creating the two main files: `values.yaml` and `Chart.yaml`. |
| 173 | + |
| 174 | +### Chart.yaml |
| 175 | + |
| 176 | +here will be listed the different dependencies that we will be using and their versions. In this case, **sftpgo** and **postgresql**. |
| 177 | + |
| 178 | +>**PostgreSQL** is required to store the new users who will use this SFTP and the configuration for the SFTP server. |
| 179 | +
|
| 180 | +It should look something like this: |
| 181 | + |
| 182 | +```yml |
| 183 | +apiVersion: v2 |
| 184 | +name: sftpgo |
| 185 | +description: SFTPGo - Secure SFTP Server |
| 186 | +type: application |
| 187 | +version: 0.23.1 |
| 188 | +appVersion: "2.5.4" |
| 189 | +dependencies: |
| 190 | + - name: sftpgo |
| 191 | + version: 0.23.1 |
| 192 | + repository: "https://charts.sagikazarmark.dev" |
| 193 | + - name: postgresql |
| 194 | + version: 16.4 |
| 195 | + repository: "https://charts.bitnami.com/bitnami" |
| 196 | +``` |
| 197 | +
|
| 198 | +### Values.yaml |
| 199 | +
|
| 200 | +On the other hand, there is the `values.yaml`, where the ingress, the serviceAccount, variables (in this case, retrieved from a secret stored in AWS) etc. are collected. |
| 201 | + |
| 202 | +It should looks like this: |
| 203 | + |
| 204 | +```yml |
| 205 | +sftpgo: |
| 206 | + config: |
| 207 | + common: |
| 208 | + proxy_protocol: 1 |
| 209 | + data_provider: |
| 210 | + create_default_admin: true |
| 211 | +
|
| 212 | + envFrom: |
| 213 | + - secretRef: |
| 214 | + name: sftpgo |
| 215 | +
|
| 216 | + serviceAccount: |
| 217 | + annotations: |
| 218 | + eks.amazonaws.com/role-arn: arn:aws:iam::ACCOUNT_ID:role/sftpgo # TODO: change this to the IRSA role |
| 219 | +
|
| 220 | + ui: |
| 221 | + ingress: |
| 222 | + enabled: true |
| 223 | + className: external |
| 224 | + annotations: |
| 225 | + kubernetes.io/external-dns.create: "true" |
| 226 | + hosts: |
| 227 | + - host: sftpgo.example.com # TODO: change this to the domain you want to use |
| 228 | + paths: |
| 229 | + - path: / |
| 230 | + pathType: ImplementationSpecific |
| 231 | +``` |
| 232 | + |
| 233 | +Once we have these two files, we will create the `Chart.lock` and the `charts` with the following command: |
| 234 | + |
| 235 | +```sh |
| 236 | +helm dep up |
| 237 | +``` |
| 238 | + |
| 239 | +With this, a folder with the Helm Chart dependencies called `charts` and a `Chart.lock` file should have been created inside our `sftpgo` folder. |
| 240 | + |
| 241 | +A folder called **templates** is also needed. There will be created both `externalsecret.yaml` and `secretstore.yaml`. This will allow us to manage the secrets we have previously stored in our **AWS Secret Manager**. |
| 242 | + |
| 243 | +### secretstore.yaml |
| 244 | + |
| 245 | +The secret store is necessary to be able to manage our secrets. Its code would look something like the following: |
| 246 | + |
| 247 | +```yml |
| 248 | +apiVersion: external-secrets.io/v1beta1 |
| 249 | +kind: SecretStore |
| 250 | +metadata: |
| 251 | + name: external-secrets-sftpgo |
| 252 | +spec: |
| 253 | + provider: |
| 254 | + aws: |
| 255 | + service: SecretsManager |
| 256 | + role: arn:aws:iam::ACCOUNT_ID:role/external-secrets-sftpgo # TODO: change this to the IRSA role |
| 257 | + region: us-east-1 # TODO: change this to the region of your cluster |
| 258 | +``` |
| 259 | + |
| 260 | +### externalsecret.yaml |
| 261 | + |
| 262 | +Finally, the `externalsecret.yaml` file that contains all the secrets we mentioned earlier. |
| 263 | + |
| 264 | +```yml |
| 265 | +apiVersion: external-secrets.io/v1beta1 |
| 266 | +kind: ExternalSecret |
| 267 | +
|
| 268 | +metadata: |
| 269 | + name: sftpgo |
| 270 | + namespace: sftpgo |
| 271 | +
|
| 272 | +spec: |
| 273 | + refreshInterval: "10m" |
| 274 | + secretStoreRef: |
| 275 | + name: external-secrets-sftpgo |
| 276 | + kind: SecretStore |
| 277 | +
|
| 278 | + target: |
| 279 | + name: sftpgo |
| 280 | +
|
| 281 | + dataFrom: |
| 282 | + - extract: |
| 283 | + key: sftpgo |
| 284 | +``` |
| 285 | + |
| 286 | +# Let's test it! |
| 287 | + |
| 288 | +With all our resources created, we can prove that everything is going well with several steps: |
| 289 | + |
| 290 | +### 1. Enter the SFTPGo host |
| 291 | +We can enter the host that we created earlier in the `values.yaml` file (sftpgo.example.com) and see a login screen like the one in the following image. With what we have done, it should be possible to log in as the admin user using the credentials stored in our AWS Secret Manager. |
| 292 | + |
| 293 | + |
| 294 | + |
| 295 | +### 2. Send a file from our local to SFTPGo. |
| 296 | +>**Remember**: We are doing everything from a user login, so we first need to create a user in the SFTPGo UI. |
| 297 | + |
| 298 | +To save a test file in SFTPGo, go to our terminal and log in with the following command: |
| 299 | + |
| 300 | +```sh |
| 301 | + |
| 302 | +``` |
| 303 | + |
| 304 | +Later, after creating a test .txt file, we will save it in SFTPGo inside a folder called "example" *(It's can also be saved directly in the root directory; it's just a test to see the possibility of navigating and organizing files within SFTPGo)*. |
| 305 | + |
| 306 | +```sh |
| 307 | +sftp> mkdir /example |
| 308 | +sftp> cd /home/example |
| 309 | +sftp> put test_file.txt |
| 310 | +``` |
| 311 | + |
| 312 | +If everything has gone well, it should be possible to see our file in the SFTPGo UI and if you go to the S3 bucket, you should also be able to see the file stored there. |
| 313 | + |
| 314 | +# Resources |
| 315 | + |
| 316 | +- [SFTPGo Docs](https://docs.sftpgo.com/latest/tutorials/postgresql-s3/) |
| 317 | +- [SFTPGo Helm Chart](https://artifacthub.io/packages/helm/sagikazarmark/sftpgo) |
0 commit comments