Vault와 워크로드 아이덴티티 사용

Vault와 워크로드 아이덴티티 사용 (Use workload identities with Vault)

Nomad는 Vault와 통합해 워크로드용 정적(static) 및 동적(dynamic) 시크릿을 검색해요.

출처: 문서

본문

Nomad와 Vault의 프로덕션 배포는 클러스터에 대한 무단 접근을 방지하므로 항상 Access Control List (ACL) 시스템을 활성화한 상태로 실행해야 해요. ACL이 활성화되면 통합이 작동하도록 Nomad와 Vault가 모두 올바르게 구성되어야 해요.

Nomad는 작업용 워크로드 아이덴티티(workload identities)를 생성할 수 있으며, 이는 Nomad가 서명한 JSON Web Tokens (JWT)로 표현돼요. 이 아이덴티티는 제3자에게 워크로드가 실제로 Nomad가 만들고 관리한다는 증명으로 사용될 수 있어요. 제3자가 Nomad를 신뢰하도록 구성돼 있다면 Nomad 워크로드에 특정 접근과 권한을 자동으로 부여할 수 있어요.

이 가이드에서 다음을 수행할 거예요:

  • ACL이 활성화된 Nomad와 Vault 에이전트를 시작해요.
  • Nomad와 Vault에 접근하기 위한 ACL 토큰을 생성해요.
  • Nomad의 워크로드 아이덴티티를 수락하도록 Vault를 구성해요.
  • Vault에 접근해야 하는 작업에 워크로드 아이덴티티를 자동으로 생성하고 서명하도록 Nomad를 구성해요.
  • Vault와 상호작용하는 샘플 Nomad 작업을 배포해요.

사전 요구 사항 (Prerequisites)

이 가이드는 Nomad와 Vault에 대한 기본적인 익숙함을 요구해요. 이 도구가 처음이라면 이 가이드를 따르기 전에 Nomad Get Started와 Vault Get Started 튜토리얼을 완료해 주세요.

다음 도구가 설치되어 있어야 해요:

Vault 에이전트 시작 (Start the Vault agent)

Vault dev 서버를 시작해요.

==> Vault server configuration:

Administrative Namespace:
             Api Address: http://127.0.0.1:8200
                     Cgo: disabled
         Cluster Address: https://127.0.0.1:8201
   Environment Variables: CLICOLOR, COLORTERM, COMMAND_MODE, EDITOR, GODEBUG, GOPATH, HOME, LANG, LC_ALL, LOGNAME, LSCOLORS, LaunchInstanceID, OLDPWD, PATH, PWD, SECURITYSESSIONID, SHELL, SHLVL, SSH_AUTH_SOCK, TERM, TERM_PROGRAM, TERM_PROGRAM_VERSION, TMPDIR, USER, WINDOWID, XPC_FLAGS, XPC_SERVICE_NAME, _, __CFBundleIdentifier, __CF_USER_TEXT_ENCODING
              Go Version: go1.21.3
              Listener 1: tcp (addr: "127.0.0.1:8200", cluster address: "127.0.0.1:8201", max_request_duration: "1m30s", max_request_size: "33554432", tls: "disabled")
               Log Level:
                   Mlock: supported: false, enabled: false
           Recovery Mode: false
                 Storage: inmem
                 Version: Vault v1.15.2, built 2023-11-06T11:33:28Z
             Version Sha: cf1b5cafa047bc8e4a3f93444fcb4011593b92cb

==> Vault server started! Log data will stream in below:

2023-11-20T20:08:27.583-0500 [INFO]  proxy environment: http_proxy="" https_proxy="" no_proxy=""
2023-11-20T20:08:27.583-0500 [INFO]  incrementing seal generation: generation=1
...
WARNING! dev mode is enabled! In this mode, Vault runs entirely in-memory
and starts unsealed with a single unseal key. The root token is already
authenticated to the CLI, so you can immediately begin using Vault.

You may need to set the following environment variables:

    $ export VAULT_ADDR='http://127.0.0.1:8200'

...

Dev 에이전트

이 가이드는 빠르게 시작하기 위해 Vault와 Nomad용 개발 에이전트를 사용해요. 개발 에이전트는 일시적 상태를 가지며 프로덕션 환경에서 사용해서는 안 돼요. 또한 터미널의 포그라운드에서 실행되므로 터미널 창을 닫지 마세요. 닫으면 에이전트 구성 단계를 다시 실행해야 해요.

Root Token 값을 복사해요.

같은 디렉터리에서 다른 터미널 창을 열고 루트 토큰을 환경 변수 VAULT_TOKEN으로 설정해요. 이 터미널은 명령을 실행할 메인 터미널 세션으로 작동해요.

환경 변수 VAULT_ADDR을 설정해요.

Nomad 에이전트 시작 (Start the Nomad agent)

nomad.hcl이라는 파일을 만들어요. 다음 내용을 추가하고 저장해요.

nomad.hcl

  enabled = true
}

vault {
  enabled = true
  address = "http://127.0.0.1:8200"

  default_identity {
    aud = ["vault.io"]
    ttl = "1h"
  }
}

nomad.hcl

  enabled = true
}

vault {
  enabled = true
  address = "http://127.0.0.1:8200"

  default_identity {
    aud = ["vault.io"]
    ttl = "1h"
  }
}

nomad.hcl

  enabled = true
}

vault {
  enabled = true
  address = "http://127.0.0.1:8200"

  default_identity {
    aud = ["vault.io"]
    ttl = "1h"
  }
}

plugin "docker" {
  config {
    allow_caps = [
      "CHOWN", "DAC_OVERRIDE", "FSETID", "FOWNER", "MKNOD",
      "SETGID", "SETUID", "SETFCAP", "SETPCAP", "NET_BIND_SERVICE",
      "SYS_CHROOT", "KILL", "AUDIT_WRITE", "NET_RAW",
    ]
  }
}

이 구성 파일의 vault 블록은 Nomad가 Vault에 연결하는 데 필요한 정보를 제공해요.

또한 Vault에 접근해야 하는 작업에 자동으로 추가되는 기본 워크로드 아이덴티티인 default_identity를 정의해요. 이 아이덴티티가 없으면 Vault에 접근해야 하는 모든 작업에 대해 작업에서 identity 블록을 정의해야 해요.

같은 디렉터리에서 다른 터미널 창을 열고 Nomad dev 에이전트를 시작해요.

==> Loaded configuration from nomad.hcl
==> Starting Nomad agent...
==> Nomad agent configuration:

       Advertise Addrs: HTTP: 127.0.0.1:4646; RPC: 127.0.0.1:4647; Serf: 127.0.0.1:4648
            Bind Addrs: HTTP: [127.0.0.1:4646]; RPC: 127.0.0.1:4647; Serf: 127.0.0.1:4648
                Client: true
             Log Level: DEBUG
               Node Id: af9bef00-2e83-b704-2d6c-c62bc005431f
                Region: global (DC: dc1)
                Server: true
               Version: 1.7.0

==> Nomad agent started! Log data will stream in below:
...
==> Loaded configuration from nomad.hcl
==> Starting Nomad agent...
==> Nomad agent configuration:

       Advertise Addrs: HTTP: 127.0.0.1:4646; RPC: 127.0.0.1:4647; Serf: 127.0.0.1:4648
            Bind Addrs: HTTP: [127.0.0.1:4646]; RPC: 127.0.0.1:4647; Serf: 127.0.0.1:4648
                Client: true
             Log Level: DEBUG
               Node Id: af9bef00-2e83-b704-2d6c-c62bc005431f
                Region: global (DC: dc1)
                Server: true
               Version: 1.7.0

==> Nomad agent started! Log data will stream in below:
...
==> Loaded configuration from nomad.hcl
==> Starting Nomad agent...
==> Nomad agent configuration:

       Advertise Addrs: HTTP: 127.0.0.1:4646; RPC: 127.0.0.1:4647; Serf: 127.0.0.1:4648
            Bind Addrs: HTTP: [127.0.0.1:4646]; RPC: 127.0.0.1:4647; Serf: 127.0.0.1:4648
                Client: true
             Log Level: DEBUG
               Node Id: af9bef00-2e83-b704-2d6c-c62bc005431f
                Region: global (DC: dc1)
                Server: true
               Version: 1.7.0

==> Nomad agent started! Log data will stream in below:
...

메인 터미널 창으로 돌아가 Nomad ACL 시스템을 부트스트랩해요.

Accessor ID  = d1de8625-8556-0932-a25c-3aa71bfc0134
Secret ID    = 7f10099a-936c-3f3a-8783-f0980493e54b
Name         = Bootstrap Token
Type         = management
Global       = true
Create Time  = 2023-11-16 01:09:26.565422 +0000 UTC
Expiry Time  = <none>
Create Index = 23
Modify Index = 23
Policies     = n/a
Roles        = n/a

Secret ID 값을 복사해 환경 변수 NOMAD_TOKEN으로 설정해요.

부트스트랩 토큰 사용

Vault와 Nomad의 초기 ACL 부트스트랩 토큰은 클러스터에 대한 전체 접근을 가지며 프로덕션 환경의 일상적인 작업에 사용해서는 안 돼요. 이 가이드에서는 설명을 위해 사용돼요. 필요한 작업을 수행하는 데 필요한 수준의 접근만 가진 ACL 정책과 토큰을 만드는 것을 권장해요.

Nomad 워크로드 아이덴티티를 수락하도록 Vault 구성 (Configure Vault to accept Nomad workload identities)

Vault ACL auth method 만들기 (Create a Vault ACL auth method)

jwt-nomad 경로 아래의 Vault에서 jwt auth method를 활성화해요.

Success! Enabled jwt auth method at: jwt-nomad/

JWT auth method는 Nomad 클라이언트가 Nomad 워크로드 아이덴티티 JWT를 Vault ACL 토큰으로 교환하는 데 사용하는 엔드포인트를 만들어요.

vault-auth-method-jwt-nomad.json이라는 파일을 만들어요. 다음 내용을 추가하고 저장해요.

vault-auth-method-jwt-nomad.json

  "jwks_url": "http://127.0.0.1:4646/.well-known/jwks.json",
  "jwt_supported_algs": ["RS256", "EdDSA"],
  "default_role": "nomad-workloads"
}

이 구성 파일에는 중요한 정보가 포함돼 있어요.

  • jwks_url은 Vault가 Nomad에 연락해 Nomad 워크로드 아이덴티티를 검증하는 데 필요한 데이터를 검색하는 데 사용하는 URL이에요. 프로덕션 환경에서는 단일 실패 지점을 방지하기 위해 역방향 프록시, 로드 밸런서 또는 DNS 항목을 통해 여러 Nomad 에이전트로 해석되어야 해요.
  • default_role은 이 auth method가 생성한 토큰에 기본적으로 적용되는 Vault ACL 역할이에요. 다음 섹션에서 nomad-workloads 역할을 만들 거예요.

구성 파일 vault-auth-method-jwt-nomad.json을 jwt-nomad auth method에 적용해요.

Success! Data written to: auth/jwt-nomad/config

Vault ACL 역할 만들기 (Create a Vault ACL role)

vault-role-nomad-workloads.json이라는 파일을 만들어요. 다음 내용을 추가하고 저장해요.

vault-role-nomad-workloads.json

  "role_type": "jwt",
  "bound_audiences": ["vault.io"],
  "user_claim": "/nomad_job_id",
  "user_claim_json_pointer": true,
  "claim_mappings": {
    "nomad_namespace": "nomad_namespace",
    "nomad_job_id": "nomad_job_id",
    "nomad_task": "nomad_task"
  },
  "token_type": "service",
  "token_policies": ["nomad-workloads"],
  "token_period": "30m",
  "token_explicit_max_ttl": 0
}

이것은 Nomad 작업에 사용되는 Vault ACL 토큰의 속성을 정의해요.

  • bound_audiences는 audience 값이 vault.io인 JWT만 수락하도록 Vault를 구성해요. Nomad 에이전트 구성과 작업에 있는 aud 값과 일치해야 해요.
  • claim_mappings는 Nomad 워크로드 아이덴티티의 값이에요. 이 역할에 대한 Vault ACL 정책을 만들 때 참조할 거예요.
  • token_period는 토큰이 만료되기 전에 유효한 시간을 결정해요. Nomad는 토큰이 만료되기 전에 자동으로 갱신해요.
  • token_explicit_max_ttl은 토큰이 유효한 최대 시간이에요. 워크로드가 실행되는 동안 Nomad가 토큰을 갱신할 수 있도록 0으로 설정해야 해요.
  • token_policies는 토큰에 적용되는 ACL 정책이에요. 이 역할이 있는 토큰이 가지는 권한을 지정해요. 다음 섹션에서 nomad-workloads 정책을 만들 거예요.

vault-role-nomad-workloads.json 파일을 사용해 nomad-workloads라는 Vault ACL 역할을 만들어요.

Success! Data written to: auth/jwt-nomad/role/nomad-workloads

이 역할은 jwt-nomad auth method에서 생성된 Vault ACL 토큰에 기본적으로 적용돼요.

Vault ACL 정책 만들기 (Create a Vault ACL policy)

Vault에 등록된 모든 auth method를 나열해요.

Path          Type     Accessor               Description                Version
----          ----     --------               -----------                -------
jwt-nomad/    jwt      auth_jwt_d34481ad      n/a                        n/a
token/        token    auth_token_510d42ca    token based credentials    n/a

jwt-nomad/ 경로에 있는 auth method의 Accessor 값을 복사해요.

vault-policy-nomad-workloads.hcl이라는 파일을 만들어요. 다음 내용을 추가하고, AUTH_METHOD_ACCESSOR 자리 표시자의 모든 인스턴스를 출력의 Accessor 값으로 대체한 후 저장해요. 업데이트할 곳이 5곳이에요.

vault-policy-nomad-workloads.hcl

  capabilities = ["read"]
}

path "kv/data/{{identity.entity.aliases.AUTH_METHOD_ACCESSOR.metadata.nomad_namespace}}/{{identity.entity.aliases.AUTH_METHOD_ACCESSOR.metadata.nomad_job_id}}" {
  capabilities = ["read"]
}

path "kv/metadata/{{identity.entity.aliases.AUTH_METHOD_ACCESSOR.metadata.nomad_namespace}}/*" {
  capabilities = ["list"]
}

path "kv/metadata/*" {
  capabilities = ["list"]
}

이 파일은 Vault ACL 역할의 claim_mappings에 매핑된 속성을 기반으로 Nomad 워크로드에 시크릿 접근을 자동으로 부여하는 템플릿 Vault ACL 정책이에요.

보다 구체적으로, 이 정책은 kv/data/<job namespace>/<job name>/* 경로의 시크릿에 접근을 부여하며, 여기서 <job namespace>와 <job name>은 각 워크로드에 대해 동적으로 설정돼요.

vault-policy-nomad-workloads.hcl 파일을 사용해 nomad-workloads라는 Vault ACL 정책을 만들어요.

Success! Uploaded policy: nomad-workloads

Vault에서 시크릿을 읽는 Nomad 작업 실행 (Run a Nomad job to read secrets from Vault)

작업의 Nomad 워크로드 아이덴티티를 사용해 Vault에서 읽은 루트 비밀번호로 MongoDB 데이터베이스를 시작해요.

kv 시크릿 엔진을 활성화해요.

Success! Enabled the kv secrets engine at: kv/

default/mongo/config 경로의 Vault에 데이터베이스 루트 비밀번호용 시크릿을 작성해요.

======== Secret Path ========
kv/data/default/mongo/config

======= Metadata =======
Key                Value
---                -----
created_time       2023-11-21T02:52:42.061092Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            1

mongo.nomad.hcl이라는 파일을 만들어요. 다음 내용을 추가하고 저장해요.

mongo.nomad.hcl

  namespace = "default"

  group "db" {
    network {
      port "db" {
        static = 27017
      }
    }

    service {
      provider = "nomad"
      name     = "mongo"
      port     = "db"
    }

    task "mongo" {
      driver = "docker"

      config {
        image = "mongo:7"
        ports = ["db"]
      }

      vault {}

      template {
        data        = <<EOF
MONGO_INITDB_ROOT_USERNAME=root
MONGO_INITDB_ROOT_PASSWORD={{with secret "kv/data/default/mongo/config"}}{{.Data.data.root_password}}{{end}}
EOF
        destination = "secrets/env"
        env         = true
      }
    }
  }
}

mongo.nomad.hcl

  namespace = "default"

  group "db" {
    network {
      port "db" {
        static = 27017
      }
    }

    service {
      provider = "nomad"
      name     = "mongo"
      port     = "db"
    }

    task "mongo" {
      driver = "docker"

      config {
        image = "mongo:7"
        ports = ["db"]
      }

      vault {}

      template {
        data        = <<EOF
MONGO_INITDB_ROOT_USERNAME=root
MONGO_INITDB_ROOT_PASSWORD={{with secret "kv/data/default/mongo/config"}}{{.Data.data.root_password}}{{end}}
EOF
        destination = "secrets/env"
        env         = true
      }
    }
  }
}

mongo.nomad.hcl

  namespace = "default"

  group "db" {
    network {
      port "db" {
        static = 27017
      }
    }

    service {
      provider = "nomad"
      name     = "mongo"
      port     = "db"
    }

    task "mongo" {
      driver = "docker"

      config {
        image = "mongo:7"
        ports = ["db"]

        mount {
          type   = "bind"
          target = "C:\\mongosh"
          source = "local/mongosh/mongosh-2.1.0-win32-x64/"
        }
      }

      vault {}

      template {
        data        = <<EOF
MONGO_INITDB_ROOT_USERNAME=root
MONGO_INITDB_ROOT_PASSWORD={{with secret "kv/data/default/mongo/config"}}{{.Data.data.root_password}}{{end}}
EOF
        destination = "secrets/env"
        env         = true
      }

      artifact {
        source      = "https://downloads.mongodb.com/compass/mongosh-2.1.0-win32-x64.zip"
        destination = "local/mongosh"
      }

      resources {
        memory = 1000
      }
    }
  }
}
  • vault 블록은 작업이 Vault에 접근해야 하며 Nomad가 작업 워크로드 아이덴티티를 사용해 Vault ACL 토큰을 얻어야 함을 나타내요.
  • template 블록은 kv/data/default/mongo/config 경로 아래의 Vault에서 루트 비밀번호 시크릿을 읽어요.
  • 작업은 Nomad default 네임스페이스에서 실행되고 작업 이름은 mongo예요. nomad-workloads Vault 역할은 루트 비밀번호가 존재하는 kv/data/default/mongo/* 경로의 시크릿에 접근을 부여해요.
  • 작업은 Vault에 대한 어떤 아이덴티티도 지정하지 않으므로 Nomad는 에이전트 구성의 default_identity를 사용해요.

mongo.nomad.hcl 파일로 작업을 실행하고 배포가 완료될 때까지 기다려요.

==> 2023-11-20T22:09:16-05:00: Monitoring evaluation "34e72e7a"
    2023-11-20T22:09:16-05:00: Evaluation triggered by job "mongo"
    2023-11-20T22:09:17-05:00: Evaluation within deployment: "919f658b"
    2023-11-20T22:09:17-05:00: Allocation "49fc68d8" created: node "b8db12d2", group "db"
    2023-11-20T22:09:17-05:00: Evaluation status changed: "pending" -> "complete"
==> 2023-11-20T22:09:17-05:00: Evaluation "34e72e7a" finished with status "complete"
==> 2023-11-20T22:09:17-05:00: Monitoring deployment "919f658b"
  ✓ Deployment "919f658b" successful

    2023-11-20T22:09:28-05:00
    ID          = 919f658b
    Job ID      = mongo
    Job Version = 0
    Status      = successful
    Description = Deployment completed successfully

    Deployed
    Task Group  Desired  Placed  Healthy  Unhealthy  Progress Deadline
    db          1        1       1        0          2023-11-20T22:19:26-05:00

root 사용자 자격 증명을 사용해 데이터베이스에 쿼리를 실행할 수 있는지 확인해요.

{
  authInfo: {
    authenticatedUsers: [ { user: 'root', db: 'admin' } ],
    authenticatedUserRoles: [ { role: 'root', db: 'admin' } ]
  },
  ok: 1
}

root 사용자 자격 증명을 사용해 데이터베이스에 쿼리를 실행할 수 있는지 확인해요.

{
  authInfo: {
    authenticatedUsers: [ { user: 'root', db: 'admin' } ],
    authenticatedUserRoles: [ { role: 'root', db: 'admin' } ]
  },
  ok: 1
}

Vault 시크릿에서 작업에 설정된 환경 변수에서 자격 증명을 읽어 root 사용자를 만들어요.

{ ok: 1 }

root 사용자 자격 증명을 사용해 데이터베이스에 쿼리를 실행할 수 있는지 확인해요.

{
  authInfo: {
    authenticatedUsers: [ { user: 'root', db: 'admin' } ],
    authenticatedUserRoles: [ { role: 'root', db: 'admin' } ]
  },
  ok: 1
}

Nomad에서 작업 정의를 검색하고 출력을 필터링해 작업만 표시해요.

{
  "Name": "mongo",
  "Driver": "docker",
  "User": "",
  "Lifecycle": null,
  "Config": {
    "image": "mongo:7",
    "ports": [
      "db"
    ]
  },
...

Identities 목록에는 Nomad가 Nomad 서버 구성 파일의 default_identity 블록의 명세에 따라 주입하는 워크로드 아이덴티티가 포함돼요.

  "Name": "mongo",
  "Driver": "docker",
  "User": "",
  "Lifecycle": null,
  "Config": {
    "image": "mongo:7",
    "ports": [
      "db"
    ]
  },
...
  "Identities": [
    {
      "Name": "vault_default",
      "Audience": [
        "vault.io"
      ],
      "ChangeMode": "",
      "ChangeSignal": "",
      "Env": false,
      "File": false,
      "ServiceName": "",
      "TTL": 3600000000000
    }
  ],
  "Actions": null
}

Vault가 Nomad의 워크로드 아이덴티티를 수락하도록 구성함으로써 Nomad 작업은 auth method 기본 역할이 정의한 접근 수준으로 범위가 지정된 Vault ACL 토큰을 자동으로 받을 수 있었어요. 이 방법으로 Nomad 에이전트는 더 이상 장기 수명의(highly permissive) Vault 토큰이 필요하지 않아요.

MongoDB용 Vault 동적 시크릿 구성 (Configure Vault dynamic secrets for MongoDB)

MongoDB 데이터베이스는 기본 Vault ACL 역할의 권한을 사용해 Vault에서 시크릿을 읽어요.

어떤 상황에서는 작업에 부여된 권한을 이 기본값과 다르게 커스터마이즈해야 할 필요가 있을 수 있어요. 이는 Nomad 작업용 추가 Vault ACL 역할을 만들어 수행해요.

기본 Vault ACL 정책이 접근을 부여하지 않는 비-root MongoDB 사용자에 대한 동적 시크릿으로 Vault를 구성한 다음, 커스텀 Vault ACL 역할을 사용하는 Nomad 작업을 실행해 동적 시크릿에 접근할 수 있게 해요.

Vault database 시크릿 엔진을 활성화해요.

Success! Enabled the database secrets engine at: database/

vault-dynamic-secret-mongo.json이라는 파일을 만들어요. 다음 내용을 추가하고 저장해요.

vault-dynamic-secret-mongo.json

    "plugin_name": "mongodb-database-plugin",
    "allowed_roles": "mongo",
    "connection_url": "mongodb://{{username}}:{{password}}@127.0.0.1:27017/admin",
    "username": "root",
    "password": "secret-password"
}

Nomad에서 MongoDB의 IP 주소를 검색해요.

Job ID  Address              Tags  Node ID   Alloc ID
mongo   192.168.0.171:27017  []    b8db12d2  49fc68d8

vault-dynamic-secret-mongo.json이라는 파일을 만들어요. 다음 내용을 추가하고, MONGO_IP 자리 표시자 텍스트를 Nomad service info 명령의 IP 주소로 대체한 후 저장해요.

vault-dynamic-secret-mongo.json

    "plugin_name": "mongodb-database-plugin",
    "allowed_roles": "mongo",
    "connection_url": "mongodb://{{username}}:{{password}}@MONGO_IP:27017/admin",
    "username": "root",
    "password": "secret-password"
}

Nomad에서 MongoDB의 IP 주소를 검색해요.

Job ID  Address              Tags  Node ID   Alloc ID
mongo   192.168.0.171:27017  []    b8db12d2  49fc68d8

vault-dynamic-secret-mongo.json이라는 파일을 만들어요. 다음 내용을 추가하고, MONGO_IP 자리 표시자 텍스트를 Nomad service info 명령의 IP 주소로 대체한 후 저장해요.

vault-dynamic-secret-mongo.json

    "plugin_name": "mongodb-database-plugin",
    "allowed_roles": "mongo",
    "connection_url": "mongodb://{{username}}:{{password}}@MONGO_IP:27017/admin",
    "username": "root",
    "password": "secret-password"
}

MongoDB 동적 시크릿용 vault-dynamic-secret-mongo.json 구성을 작성해 데이터베이스에 연결해요.

Success! Data written to: database/config/mongo

vault-database-role-mongo.json이라는 파일을 만들어요. 다음 내용을 추가하고 저장해요.

vault-database-role-mongo.json

    "db_name": "mongo",
    "creation_statements": "{ \"db\": \"admin\", \"roles\": [{ \"role\": \"readWrite\" }, {\"role\": \"read\", \"db\": \"foo\"}] }",
    "default_ttl": "1h",
    "max_ttl": "24h"
}

vault-database-role-mongo.json 파일을 사용해 Vault database 역할을 만들어요.

Success! Data written to: database/roles/mongo

동적 시크릿에 접근할 Vault ACL 역할 만들기 (Create a Vault ACL role to access the dynamic secret)

vault-role-mongo-dynamic-secret.json이라는 파일을 만들어요. 다음 내용을 추가하고 저장해요.

vault-role-mongo-dynamic-secret.json

  "role_type": "jwt",
  "bound_audiences": ["vault.io"],
  "bound_claims": {
     "nomad_namespace": "default",
     "nomad_job_id": "mongo-query"
  },
  "user_claim": "/nomad_job_id",
  "user_claim_json_pointer": true,
  "claim_mappings": {
    "nomad_namespace": "nomad_namespace",
    "nomad_job_id": "nomad_job_id",
    "nomad_task": "nomad_task"
  },
  "token_type": "service",
  "token_policies": ["mongo-dynamic-secret"],
  "token_period": "30m",
  "token_explicit_max_ttl": 0
}

이 역할은 이 가이드 앞부분의 역할과 유사하지만, 다음 섹션에서 만들 mongo-dynamic-secret이라는 다른 정책을 사용해요.

또한 Nomad의 어떤 워크로드 아이덴티티가 이 역할을 사용할 수 있는지 제한하는 bound_claims 집합을 정의해요. 이 예시에서 역할은 Nomad 네임스페이스 default의 mongo-query 작업만 사용할 수 있게 해요.

vault-role-mongo-dynamic-secret.json 파일을 사용해 mongo-dynamic-secret ACL 역할을 만들어요.

Success! Data written to: auth/jwt-nomad/role/mongo-dynamic-secret

vault-policy-mongo-dynamic-secret.hcl이라는 파일을 만들어요. 다음 내용을 추가하고 저장해요.

vault-policy-mongo-dynamic-secret.hcl

  capabilities = ["read"]
}

이 ACL 정책은 jwt-nomad auth method가 사용하는 기본 역할에 포함되지 않은 database/creds/mongo라는 특정 경로에만 접근을 부여해요.

vault-policy-mongo-dynamic-secret.hcl 파일을 사용해 mongo-dynamic-secret ACL 정책을 만들어요.

Success! Uploaded policy: mongo-dynamic-secret

커스텀 Vault ACL 역할로 Nomad 작업 실행 (Run a Nomad job with a custom Vault ACL role)

mongo-query.nomad.hcl이라는 파일을 만들어요. 다음 내용을 추가하고 저장해요.

mongo-query.nomad.hcl

  namespace = "default"
  type      = "batch"

  group "mongo-query" {
    task "mongo-query" {
      driver = "docker"

      config {
        image   = "mongo:7"
        command = "mongosh"
        args = [
          "--username", "${MONGO_USERNAME}",
          "--password", "${MONGO_PASSWORD}",
          "--eval", "db.runCommand({connectionStatus : 1})",
          "--quiet",
          "${MONGO_URL}",
        ]
      }

      vault {
        role = "mongo-dynamic-secret"
      }

      template {
        data        = <<EOF
{{with secret "database/creds/mongo"}}
MONGO_USERNAME={{.Data.username}}
MONGO_PASSWORD={{.Data.password}}
{{end}}
{{range nomadService 1 (env "NOMAD_ALLOC_ID") "mongo"}}
MONGO_URL=mongodb://{{.Address}}:{{.Port}}
{{end}}
EOF
        destination = "secrets/env"
        env         = true
      }
    }
  }
}

mongo-query.nomad.hcl

  namespace = "default"
  type      = "batch"

  group "mongo-query" {
    task "mongo-query" {
      driver = "docker"

      config {
        image   = "mongo:7"
        command = "mongosh"
        args = [
          "--username", "${MONGO_USERNAME}",
          "--password", "${MONGO_PASSWORD}",
          "--eval", "db.runCommand({connectionStatus : 1})",
          "--quiet",
          "${MONGO_URL}",
        ]
      }

      vault {
        role = "mongo-dynamic-secret"
      }

      template {
        data        = <<EOF
{{with secret "database/creds/mongo"}}
MONGO_USERNAME={{.Data.username}}
MONGO_PASSWORD={{.Data.password}}
{{end}}
{{range nomadService 1 (env "NOMAD_ALLOC_ID") "mongo"}}
MONGO_URL=mongodb://{{.Address}}:{{.Port}}
{{end}}
EOF
        destination = "secrets/env"
        env         = true
      }
    }
  }
}

mongo-query.nomad.hcl

  namespace = "default"
  type      = "batch"

  group "mongo-query" {
    task "mongo-query" {
      driver = "docker"

      config {
        image   = "mongo:7"
        command = "C:\\mongosh\\bin\\mongosh.exe"
        args = [
          "--username", "${MONGO_USERNAME}",
          "--password", "${MONGO_PASSWORD}",
          "--eval", "db.runCommand({connectionStatus : 1})",
          "--quiet",
          "${MONGO_URL}",
        ]

        mount {
          type   = "bind"
          target = "C:\\mongosh"
          source = "local/mongosh/mongosh-2.1.0-win32-x64/"
        }
      }

      vault {
        role = "mongo-dynamic-secret"
      }

      template {
        data        = <<EOF
{{with secret "database/creds/mongo"}}
MONGO_USERNAME={{.Data.username}}
MONGO_PASSWORD={{.Data.password}}
{{end}}
{{range nomadService 1 (env "NOMAD_ALLOC_ID") "mongo"}}
MONGO_URL=mongodb://{{.Address}}:{{.Port}}
{{end}}
EOF
        destination = "secrets/env"
        env         = true
      }

      artifact {
        source      = "https://downloads.mongodb.com/compass/mongosh-2.1.0-win32-x64.zip"
        destination = "local/mongosh"
      }
    }
  }
}

vault 블록이 mongo-dynamic-secret 역할을 지정한다는 점을 참고해요. 작업은 MongoDB의 동적 자격 증명에만 접근할 수 있어요.

template 블록은 이러한 자격 증명을 읽고 이를 환경 변수로 작업에 노출해 사용할 수 있게 해요.

mongo-query.nomad.hcl 파일에서 Nomad 작업을 실행해요.

==> 2023-11-20T23:11:35-05:00: Monitoring evaluation "5fa56c67"
    2023-11-20T23:11:35-05:00: Evaluation triggered by job "mongo-query"
    2023-11-20T23:11:36-05:00: Allocation "909f0184" created: node "b8db12d2", group "mongo-query"
    2023-11-20T23:11:36-05:00: Evaluation status changed: "pending" -> "complete"
==> 2023-11-20T23:11:36-05:00: Evaluation "5fa56c67" finished with status "complete"

할당 정보를 검색하고 상태가 complete가 될 때까지 기다려요. 상태가 complete로 바뀌기 전에 명령을 몇 번 실행해야 할 수도 있어요.

ID        Node ID   Task Group   Version  Desired  Status    Created  Modified
909f0184  b8db12d2  mongo-query  0        run      complete  25s ago  24s ago

작업이 MongoDB 데이터베이스에 연결할 수 있었는지 확인하기 위해 쿼리 결과를 검색해요.

{
  authInfo: {
    authenticatedUsers: [
      {
        user: 'v-jwt-nomad-mongo-mongo-QbF7HWHjwOi6PJWmNo1x-1700544973',
        db: 'admin'
      }
    ],
    authenticatedUserRoles: [ { role: 'readWrite', db: 'admin' }, { role: 'read', db: 'foo' } ]
  },
  ok: 1
}

인증된 사용자가 이제 동적 사용자 자격 증명이라는 점을 참고해요.

템플릿 Vault ACL 정책은 접근 규칙을 정의할 때 큰 유연성을 제공하지만, 단일 정책이나 특정 정책 그룹이 모든 사용 사례를 다루기에는 부족할 수 있어요.

특정 Nomad 작업용 추가 Vault ACL 역할을 만드는 것은 Vault 시크릿에 대한 접근 제어를 더 잘 관리하는 데 도움이 될 수 있어요.

다음 단계 (Next steps)

이 가이드에서는 ACL을 활성화한 채 통신하도록 Nomad와 Vault를 구성했어요. 또한 Vault에 접근해야 하는 작업에 워크로드 아이덴티티를 자동으로 추가하도록 Nomad를 구성했어요.

그런 다음 다른 Vault ACL 역할과 정책을 사용해 Vault에서 정적 및 동적 시크릿을 읽는 Nomad 작업을 배포했어요. 두 작업 모두 워크로드 아이덴티티를 사용해 해야 할 작업에 적절히 범위가 지정된 Vault ACL 토큰을 받았어요.

이 과정은 여러 단계를 필요로 했으며, 모든 것이 제대로 작동하려면 특정 값들이 서로 일치해야 했어요.

이러한 단계를 자동화하는 데 도움이 되는 두 가지 리소스가 있어요.

  • Nomad CLI 명령 nomad setup vault는 개발 또는 테스트 클러스터를 위한 기본값으로 빠른 설정에 유용해요.
  • hashicorp-modules/nomad-setup/vault Terraform 모듈은 프로덕션 환경에 더 적합한 인프라스트럭처-애즈-코드 접근 방식으로 이러한 단계를 적용하는 기반을 제공해요. hashicorp-guides/nomad-workload-identity-terraform-demo 리포지토리는 이 모듈을 어떻게 사용할 수 있는지 보여줘요.

Nomad와 Vault 사이의 추가 통합을 계속 탐색하거나, ACL이 활성화된 상태로 Nomad와 Consul을 유사하게 통합하는 방법을 배울 수 있어요.

더 알아보기 (Learn more)