Skip to content

Creating SFTP Users Where PGP Encryption is Used

Introduction

This document outlines the procedure for creating SFTP users for the AWS File Transfer SFTP Server when pgp encryption is used. These users will have specific permissions and access levels to ensure secure file transfers while maintaining compliance with organisational policies.

Procedure

The procedure has two stages, one for Platform Support and one for the service platform engineer:

Platform Support

  1. Clone Gitlab repo and create a branch.

  2. Create a new policy in the iam-policy-pgp-users.tf file using the example below. You will need to update the s3 bucket details with the correct bucket details:

module "sftp_ohs_dwp_iam_policy" {
source  = "terraform-aws-modules/iam/aws//modules/iam-policy"
version = "6.4.0"

name        = "NHSBSA_SFTPOHSDWPUserPolicy"
path        = "/"
description = "Policy for OHS DWP SFTP user"

policy = data.aws_iam_policy_document.sftp_ohs_dwp_user.json
}

data "aws_iam_policy_document" "sftp_ohs_dwp_user" {

  statement {
    sid     = "S3ListWorkflow"
    effect  = "Allow"
    actions = [
      "s3:ListBucket",
    ]
    resources = [
        "arn:aws:s3:::${local.workflow_bucket}"
    ]

    condition {
    test     = "StringLike"
    variable = "s3:prefix"
    values = [
      "decrypted/${local.ohs_dwp_username}",
      "decrypted/${local.ohs_dwp_username}/*",
      "workflow/${local.ohs_dwp_username}",
      "workflow/${local.ohs_dwp_username}/*"
    ]
  }
  }

  statement {
    sid     = "S3GetWorkflow"
    effect  = "Allow"
    actions = [
      "s3:Get*",
    ]
    resources = [
      "arn:aws:s3:::${local.workflow_bucket}/decrypted/${local.ohs_dwp_username}/*"
    ]
  }

  statement {
    sid     = "S3PutWorkflow"
    effect  = "Allow"
    actions = [
      "s3:Put*",
      "s3:Get*",
      "s3:Delete*",
    ]
    resources = [
      "arn:aws:s3:::${local.workflow_bucket}/workflow/${local.ohs_dwp_username}/*"
    ]
  }

  statement {
    sid     = "S3ListDestinationBucket"
    effect  = "Allow"
    actions = [
      "s3:ListBucket",
    ]
    resources = [
      "arn:aws:s3:::${local.dirty_bucket}"
    ]

    condition {
    test     = "StringLike"
    variable = "s3:prefix"
    values = [
      "${var.ohs_dwp_folder[terraform.workspace]}",
      "${var.ohs_dwp_folder[terraform.workspace]}/*"
    ]
  }
  }

  statement {
    sid     = "S3PutDestinationBucket"
    effect  = "Allow"
    actions = [
      "s3:Get*",
      "s3:Put*",
      "s3:Delete*",
      "s3:AbortMultipartUpload"
    ]
    resources = [
      "arn:aws:s3:::${local.dirty_bucket}/${var.ohs_dwp_folder[terraform.workspace]}",
      "arn:aws:s3:::${local.dirty_bucket}/${var.ohs_dwp_folder[terraform.workspace]}/*"
    ]
  }

  statement {
    sid     = "KMSDestEncrypt"
    effect  = "Allow"
    actions = [
      "kms:Encrypt",
      "kms:GenerateDataKey",
      "kms:ReEncrypt*",
      "kms:DescribeKey"
    ]
    resources = [data.aws_kms_key.bucket_av_kms_key.arn]
  }

  statement {
    sid    = "CloudWatchLogs"
    effect = "Allow"

    actions = [
      "logs:CreateLogGroup",
      "logs:CreateLogStream",
      "logs:PutLogEvents",
      "logs:DescribeLogStreams"
    ]

    resources = ["*"]
  }
}

The S3ListWorkflow, S3GetWorkflow, S3PutWorkflow and CloudWatchLogs sections must remain, as these are used by the workflow to decrypt the uploaded files and allow the initial upload to complete. The KMSDestEncrypt section is only needed if your destination bucket has a custom kms key, and should reflect the key being used. In this example the destination is the BucketAV clean/dirty buckets. The S3ListDestinationBucket and S3PutDestinationBucket are the final destination of your files and resource values should be changed accordingly.

  1. Create a role that utilises the policy from step 2, as per the example below:
module "sftp_ohs_dwp_iam_role" {
source  = "terraform-aws-modules/iam/aws//modules/iam-role"
version = "6.4.0"

name = "NHSBSA_SFTPOHSDWPUserRole"

use_name_prefix = false

trust_policy_permissions = {
  TrustTransferFamilyToAssume = {
    actions = [
      "sts:AssumeRole",
      "sts:TagSession"
    ]
    principals = [
      {
        type        = "Service"
        identifiers = ["transfer.amazonaws.com"]
      },
      {
        type        = "AWS"
        identifiers = [module.sftp_workflow_role.iam_role_arn]
      }
    ]
  }
}

policies = {
  sftp_ohs_dwp_user = module.sftp_ohs_dwp_iam_policy.arn
}

}
  1. Create a new block in the resource "aws_secretsmanager_secret_version" "ft_sftp_users" for the new user as per the below example:
"ohs-dwp-${terraform.workspace}" = {
    auth_mode    = "key_only"
    public_keys  = [local.dwp_ssh["dwp"]]
    role         = module.sftp_ohs_dwp_iam_role.arn
    target_path  = "/${module.sftp_workflow_bucket.s3_bucket_id}/workflow/ohs-dwp-${terraform.workspace}"
    use_workflow = true
    copy_to = {
      bucket     = local.dirty_bucket
      key_prefix = "${var.ohs_dwp_folder[terraform.workspace]}/"
    }
  }

There are the following options for auth_mode:

auth_mode value Description
password_or_key Password OR SSH key (default)
password_only Password only
key_only SSH key only
password_and_key Requires both password + SSH key

If you have a password in your auth your secret block must include the password field. This should be pulled from secrets manager. If you are using an ssh key not already in use you will also need to create a secret for that as per the example below, the value for this should be populated manually in AWS:

resource "aws_secretsmanager_secret" "sftp_operations_ssh" {
name = "ft_sftp_${terraform.workspace}_operations_ssh"
description = "SFTP operations ssh secret"
}

resource "aws_secretsmanager_secret_version" "sftp_operations_ssh" {
  secret_id     = aws_secretsmanager_secret.sftp_operations_ssh.id
  secret_string = <<EOF
{
  "operations": ""
}
EOF
}
  1. You should then update the following block in the iam-policy-core.tf file and add your new role into the resources:
statement {
  sid     = "AssumePerUserDestinationRoles"
  effect  = "Allow"
  actions = ["sts:AssumeRole"]
  resources = [
    module.sftp_ohs_dwp_iam_role.arn
  ]
}
  1. Push your branch back into the repo and the pipeline should run. If all looks well after the pipeline has run, merge the branch into main and run the deploy job at the end of the pipeline.

Service Platform Engineer

  1. The platform engineer will need to add the following policy to their s3 bucket:
{
  "Version": "2012-10-17",
    "Statement": [
      {
          "Sid": "BucketPolicyForTransferFamily",
          "Effect": "Allow",
          "Principal": {
              "AWS": "arn:aws:iam::${var.sftp_accounts[terraform.workspace]}:NHSBSA_SFTPTestingUserRole"
          },
          "Action": [
              "s3:PutObject*",
              "s3:GetObject*",
              "s3:DeleteObject*",
              "s3:ListBucket",
              "s3:GetBucketLocation"
          ],
          "Resource": [
              "arn:aws:s3:::bucket-name-here",
              "arn:aws:s3:::bucket-name-here/*"
          ]
      }
  ]
}
  1. They will also need to add the following to their variables.tf file:
variable "sftp_accounts" {
  default = {
    dev   = 571451610712
    test  = 571451610712
    stage = 402422010573
    prod  = 402422010573
  }
}