Module: Legion::Crypt::VaultJwtAuth

Extended by:
Logging::Helper
Defined in:
lib/legion/crypt/vault_jwt_auth.rb

Overview

Vault JWT auth backend integration.

Allows Legion workers to authenticate to Vault using JWT tokens via Vault’s JWT/OIDC auth method. The worker presents a signed JWT and receives a Vault token with policies scoped to the worker’s role.

Vault config prerequisites:

vault auth enable jwt
vault write auth/jwt/config jwks_url="..." (or bound_issuer + jwt_validation_pubkeys)
vault write auth/jwt/role/legion-worker bound_audiences="legion" ...

Defined Under Namespace

Classes: AuthError

Constant Summary collapse

DEFAULT_AUTH_PATH =
'auth/jwt/login'
DEFAULT_ROLE =
'legion-worker'

Constants included from Logging::Helper

Logging::Helper::CompatLogger

Class Method Summary collapse

Methods included from Logging::Helper

handle_exception, log

Class Method Details

.login(jwt:, role: DEFAULT_ROLE, auth_path: DEFAULT_AUTH_PATH) ⇒ Hash

Authenticate to Vault using a JWT token. Returns a Vault token string on success.

Parameters:

  • jwt (String)

    Signed JWT token (issued by Legion or Entra ID)

  • role (String) (defaults to: DEFAULT_ROLE)

    Vault JWT auth role name (default: ‘legion-worker’)

  • auth_path (String) (defaults to: DEFAULT_AUTH_PATH)

    Vault auth mount path (default: ‘auth/jwt/login’)

Returns:

  • (Hash)

    { token:, lease_duration:, policies:, metadata: }



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/legion/crypt/vault_jwt_auth.rb', line 32

def self.(jwt:, role: DEFAULT_ROLE, auth_path: DEFAULT_AUTH_PATH)
  raise AuthError, 'Vault is not connected' unless vault_connected?

  log.info "[crypt:vault_jwt] authenticating role=#{role} auth_path=#{auth_path}"
  response = ::Vault.logical.write(
    auth_path,
    role: role,
    jwt:  jwt
  )

  raise AuthError, 'Vault JWT auth returned no auth data' unless response&.auth

  {
    token:          response.auth.client_token,
    lease_duration: response.auth.lease_duration,
    renewable:      response.auth.renewable?,
    policies:       response.auth.policies,
    metadata:       response.auth.
  }
rescue ::Vault::HTTPClientError => e
  handle_exception(e, level: :warn, operation: 'crypt.vault_jwt_auth.login', role: role, auth_path: auth_path,
                      category: 'client_error')
  raise AuthError, "Vault JWT auth failed: #{e.message}"
rescue ::Vault::HTTPServerError => e
  handle_exception(e, level: :warn, operation: 'crypt.vault_jwt_auth.login', role: role, auth_path: auth_path,
                      category: 'server_error')
  raise AuthError, "Vault server error during JWT auth: #{e.message}"
rescue StandardError => e
  handle_exception(e, level: :error, operation: 'crypt.vault_jwt_auth.login', role: role, auth_path: auth_path)
  raise if e.is_a?(AuthError)

  raise AuthError, "Vault JWT auth failed: #{e.message}"
end

.login!(jwt:, role: DEFAULT_ROLE, auth_path: DEFAULT_AUTH_PATH) ⇒ Hash

Authenticate and set the Vault client token for subsequent operations. This replaces the current Vault token with the JWT-authenticated one.

Returns:

  • (Hash)

    Same as login



70
71
72
73
74
75
# File 'lib/legion/crypt/vault_jwt_auth.rb', line 70

def self.login!(jwt:, role: DEFAULT_ROLE, auth_path: DEFAULT_AUTH_PATH)
  result = (jwt: jwt, role: role, auth_path: auth_path)
  ::Vault.token = result[:token]
  log.info "[crypt:vault_jwt] authenticated via JWT auth, policies=#{result[:policies].join(',')}"
  result
end

.worker_login(worker_id:, owner_msid:, role: DEFAULT_ROLE) ⇒ Hash

Issue a Legion JWT and use it to authenticate to Vault in one step. Convenience method for workers that need Vault access.

Parameters:

  • worker_id (String)

    Digital worker ID

  • owner_msid (String)

    Worker’s owner MSID

  • role (String) (defaults to: DEFAULT_ROLE)

    Vault JWT auth role name

Returns:

  • (Hash)

    Same as login



84
85
86
87
88
89
90
91
92
93
94
# File 'lib/legion/crypt/vault_jwt_auth.rb', line 84

def self.(worker_id:, owner_msid:, role: DEFAULT_ROLE)
  log.info "[crypt:vault_jwt] worker login requested role=#{role} worker_id=#{worker_id}"
  jwt = Legion::Crypt::JWT.issue(
    { worker_id: worker_id, sub: owner_msid, scope: 'vault', aud: 'legion' },
    signing_key: Legion::Crypt.cluster_secret,
    ttl:         300,
    issuer:      'legion'
  )

  (jwt: jwt, role: role)
end