Nomad 워크로드 아이덴티티로 AWS 접근 페더레이션

Nomad 워크로드 아이덴티티로 AWS 접근 페더레이션 (Federate access to AWS with Nomad Workload Identity)

이 페이지는 Nomad를 OIDC 공급자로서 AWS IAM과 통합하고 워크로드 아이덴티티(Workload Identity)를 사용해 AWS 리소스와 서비스에 대한 접근을 페더레이션(federate)하는 방법을 설명해요. 이 워크플로에서 Nomad는 OpenID Connect 공급자(OP 또는 OIDC provider)이며 워크로드 아이덴티티 역할을 하는 JSON Web Tokens (JWT)을 생성해요. AWS IAM은 신뢰 당사자(RP, Relying Party)이며, 리소스와 서비스에 대한 페더레이션 접근을 허용하기 전에 Nomad를 통해 이러한 워크로드 아이덴티티 토큰을 검증해요.

출처: 문서

본문

사전 요구 사항 (Prerequisites)

Nomad와 AWS IAM 아이덴티티를 통합하려면 다음 사전 요구 사항을 충족하는 실행 중인 Nomad 클러스터가 필요해요:

  • Nomad v1.7.x 이상
  • TLS 활성화

이 페이지의 지침은 또한 다음을 가정해요:

  • AWS 계정에 IAM 역할, 정책, 호스팅 영역(hosted zones), 인증서를 만들 수 있는 필요한 권한이 있음.
  • Terraform을 사용해 AWS 인프라를 관리하고 있고 AWS와 통신하도록 구성했음.

워크플로 (Workflow)

Nomad를 AWS의 OIDC 공급자로 통합하는 과정은 다음 단계로 구성돼요:

  1. 필요한 AWS 리소스 만들기:
  1. Nomad 서버 구성을 업데이트해요.
  2. AWS에 접근하는 jobspec 파일을 만들고 실행해 구성을 확인해요.

AWS 리소스 생성 및 구성 (Create and configure AWS resources)

Nomad를 아이덴티티 공급자로 사용하려면 클러스터가 사용하는 도메인에 대한 신뢰할 수 있는 SSL 인증서가 필요해요. 다음 예시는 AWS Certificate Manager (ACM)을 사용해요.

호스팅 영역 만들기 (Create a hosted zone)

aws_route53_zone 리소스를 사용해요.

    type = string
    default = "<DOMAIN_NAME>"
}

resource "aws_route53_zone" "example" {
    name = var.domain_name
}

이 구성에는 다음 정보가 필요해요:

  • <DOMAIN_NAME>: 프로토콜이나 포트가 없는 Nomad 클러스터의 도메인 이름. var.domain_name은 이 페이지의 예시 전체에서 <DOMAIN_NAME>을 참조하는 데 사용돼요.

SSL 인증서 생성 (Generate SSL certificates)

aws_acm_certificate와 aws_acm_certificate_validation 리소스를 사용해 SSL 인증서에 대한 요청을 만들고 검증을 제공해요.

  type    = string
  default = "<HOSTED_ZONE_ID>"
}

resource "aws_acm_certificate" "example" {
  domain_name       = var.domain_name
  validation_method = "DNS"

  lifecycle {
    create_before_destroy = true
  }
}

resource "aws_route53_record" "cert_dns" {
  allow_overwrite = true
  name            = tolist(aws_acm_certificate.example.domain_validation_options)[0].resource_record_name
  records         = [tolist(aws_acm_certificate.example.domain_validation_options)[0].resource_record_value]
  type            = tolist(aws_acm_certificate.example.domain_validation_options)[0].resource_record_type
  zone_id         = var.zone_id
  ttl             = 60
}

resource "aws_acm_certificate_validation" "example" {
  certificate_arn         = aws_acm_certificate.example.arn
  validation_record_fqdns = [aws_route53_record.cert_dns.fqdn]
}

이 구성에는 다음 정보가 필요해요:

  • <HOSTED_ZONE_ID>: 호스팅 영역의 ID. 이 페이지의 예시를 사용한다면 aws_route53_zone.example.zone_id가 될 거예요.

Application Load Balancer 만들기 (Create an Application Load Balancer)

aws_lb 리소스를 사용해요.

  name               = "test-lb-tf"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [<SECURITY_GROUP_ID>]
  subnets            = [<SUBNET_IDS>]

  enable_deletion_protection = true

  access_logs {
    bucket  = <S3_BUCKET_ID>
    prefix  = "test-lb"
    enabled = true
  }
}

이 구성에는 다음 정보가 필요해요:

  • <SECURITY_GROUP_ID>: 로드 밸런서에 적용하려는 보안 그룹 ID 목록.
  • <SUBNET_IDS>: 로드 밸런서에 연결하려는 서브넷 ID 목록.
  • <S3_BUCKET_ID>: 로드 밸런서 접근 로그를 저장할 S3 버킷의 ID.

로드 밸런서 리스너 만들기 (Create a load balancer listener)

aws_lb_listener 리소스를 사용해요.

  load_balancer_arn = <LB_ARN>
  port              = "443"
  protocol          = "HTTPS"
  ssl_policy        = "ELBSecurityPolicy-2016-08"
  certificate_arn   = <CERT_ARN>

  default_action {
    type             = "forward"
    target_group_arn = <LB_TARGET_GROUP_ARN>
  }
}

이 구성에는 다음 정보가 필요해요:

  • <LB_ARN>: 로드 밸런서의 AWS 리소스 이름. 이 페이지의 예시를 사용한다면 aws_lb.test.arn이 될 거예요.
  • <CERT_ARN>: 로드 밸런서 인증서의 AWS 리소스 이름. 이 페이지의 예시를 사용한다면 aws_acm_certificate_validation.example.certificate_arn이 될 거예요.
  • <LB_TARGET_GROUP_ARN>: 로드 밸런서가 트래픽을 라우팅할 대상 그룹의 AWS 리소스 이름. 이 그룹에는 Nomad 서버 인스턴스가 포함돼요.

DNS 별칭 만들기 (Create a DNS Alias)

aws_route53_record 리소스를 사용해요.

  zone_id = <HOSTED_ZONE_ID>
  name    = var.domain_name
  type    = "A"

  alias {
    name                   = <LB_ALIAS_DNS_NAME>
    zone_id                = <LB_ALIAS_ZONE_ID>
    evaluate_target_health = true
  }
}

이 구성에는 다음 정보가 필요해요:

  • <HOSTED_ZONE_ID>: 호스팅 영역의 ID. 이 페이지의 예시를 사용한다면 aws_route53_zone.example.zone_id가 될 거예요.
  • <LB_ALIAS_DNS_NAME>: 로드 밸런서의 DNS 이름. 이 페이지의 예시를 사용한다면 aws_lb.test.dns_name이 될 거예요.
  • <LB_ALIAS_ZONE_ID>: 로드 밸런서의 영역 ID. 이 페이지의 예시를 사용한다면 aws_lb.test.zone_id가 될 거예요.

OIDC 아이덴티티 공급자 만들기 (Create an OIDC Identity Provider)

aws_iam_openid_connect_provider 리소스를 사용해요.

  url = <CERT_DOMAIN_NAME>
}

resource "aws_iam_openid_connect_provider" "nomad" {
  # Nomad HTTPS URL
  url = <CERT_DOMAIN_NAME>

  client_id_list = [
    "aws",
  ]

  thumbprint_list = [data.tls_certificate.example.certificates.0.sha1_fingerprint]
}

이 구성에는 다음 정보가 필요해요:

  • <CERT_DOMAIN_NAME>: 로드 밸런서 인증서의 도메인 이름. 이 페이지의 예시를 사용한다면 aws_acm_certificate.example.domain_name이 될 거예요.

OIDC 페더레이션 사용자를 위한 IAM 정책 만들기 (Create an IAM policy for OIDC Federated Users)

aws_iam_role 리소스를 사용해 페더레이션 사용자로 작동하는 워크로드에 적절한 IAM 역할을 만들어요. 이것은 사용 사례에 따라 달라져요. 다음 예시는 워크로드가 S3 버킷에 접근할 수 있게 해줘요.

variable "oidc_provider" {
  description = "The OIDC provider URL"
  type        = string
  default     = "<DOMAIN_NAME>"
}

variable "aws_account_id" {
  description = "AWS account ID"
  type        = string
  default     = "<AWS_ACCOUNT_ID>"
}

data "aws_iam_policy_document" "assume_role" {
  statement {
    effect = "Allow"

    principals {
      type        = "Federated"
      identifiers = ["arn:aws:iam::${var.aws_account_id}:oidc-provider/${var.oidc_provider}"]
    }

    actions = ["sts:AssumeRoleWithWebIdentity"]

    condition {
      test     = "StringEquals"
      variable = "${var.oidc_provider}:aud"
      values   = ["aws"]
    }
  }
}

# Create an IAM role with the assume role policy generated above
resource "aws_iam_role" "s3_all_access_role" {
  name               = "s3_all_access_role"
  assume_role_policy = data.aws_iam_policy_document.assume_role.json

  tags = {
    tag-key = "tag-value"
  }
}

# Inline policy that defines what the role can do (full S3 access)
data "aws_iam_policy_document" "s3_access_policy" {
  statement {
    effect = "Allow"

    actions = [
      "s3:*",
      "s3-object-lambda:*"
    ]

    resources = ["*"] # You can scope this down to specific S3 buckets if necessary
  }
}

# Create a policy resource from the inline policy document above
resource "aws_iam_policy" "policy" {
  name        = "nomad-oidc-policy"
  description = "A policy for federated Nomad OIDC"
  policy      = data.aws_iam_policy_document.s3_access_policy.json
}

# Attach the S3 access policy to the IAM role
resource "aws_iam_role_policy_attachment" "test-attach" {
  role       = aws_iam_role.s3_all_access_role.name
  policy_arn = aws_iam_policy.policy.arn
}

이 구성에는 다음 정보가 필요해요:

  • <DOMAIN_NAME>: 프로토콜이나 포트가 없는 Nomad 클러스터의 도메인 이름.
  • <AWS_ACCOUNT_ID>: 이전 단계에서 IAM 역할을 만든 AWS 계정의 ID.

Nomad 서버 구성 업데이트 (Update the Nomad server configuration)

AWS를 구성한 후 Nomad 서버 구성 파일을 수정해요. oidc_issuer 속성을 추가하고 값을 Nomad 클러스터의 도메인 이름으로 설정해요. 이렇게 하면 서드파티가 Nomad의 OIDC 구성을 발견할 수 있게 하는 Nomad의 HTTP 엔드포인트를 활성화해요.

  enabled = true
  [...]
  oidc_issuer = "https://<DOMAIN_NAME>"
  [...]
}

이 구성에는 다음 정보가 필요해요:

  • <DOMAIN_NAME>: 프로토콜이나 포트가 없는 Nomad 클러스터의 도메인 이름.

구성 변경을 적용하려면 Nomad 서버 에이전트를 다시 시작해요.

샘플 jobspec 파일 만들고 실행 (Create and run a sample jobspec file)

구성을 검증하기 위해 jobspec 파일을 만들고 실행해요. 다음 파일은 s3-upload.nomad.hcl이라는 이름이며, 다음 구성을 추가하고 저장해요.

  type = "batch"
  group "bucket" {
    task "copy" {
      driver = "docker"
      config {
        image = "public.ecr.aws/aws-cli/aws-cli"
        command = "s3"
        args = ["cp", "/local/test.txt", "s3://<S3_BUCKET_NAME>/test-nomad.txt"]
      }

      identity {
        name = "aws"
        aud = ["aws"]
        file = true
        ttl = "1h"

        # AWS SDKs gracefully handle OIDC/WebIdentity reauthentication when the
        # session or token expire, therefore a restart isn't needed
        change_mode = "noop"
      }

      template {
        destination = "local/test.txt"
        change_mode = "restart"
        data        = <<EOF
Job:          {{ env "NOMAD_JOB_NAME" }}
Alloc:        {{ env "NOMAD_ALLOC_ID" }}
EOF
      }

      env {
        AWS_ROLE_ARN = "arn:aws:iam::<AWS_ACCOUNT_ID>:role/<IAM_ROLE_NAME>"
        # The format of the token file is nomad_$NAME_OF_IDENTITY.jwt
        AWS_WEB_IDENTITY_TOKEN_FILE = "${NOMAD_SECRETS_DIR}/nomad_aws.jwt"
      }

      resources {
        cpu    = 500
        memory = 256
      }
    }
  }
}

이 구성에는 다음 정보가 필요해요:

  • <S3_BUCKET_NAME>: 테스트 파일이 저장될 S3 버킷의 이름.
  • <AWS_ACCOUNT_ID>: 이전 단계에서 IAM 역할을 만든 AWS 계정의 ID.
  • <IAM_ROLE_NAME>: 이전 단계의 IAM 역할 이름. 이 페이지의 예시를 사용한다면 s3_all_access_role이 될 거예요.

작업을 Nomad에 제출해요.

작업이 성공적으로 완료됐는지 확인해요.

파일이 S3 버킷에도 업로드됐는지 확인해요.

더 알아보기 (Learn more)