Search code examples
authenticationhashicorp-vault

HashiCorp Vault AppRole based authentication Unwrap secret_id got permission denied


I am using this code as an example to use AppRole based authentification to Vault. For the secret_id I wanna use an wrapped token to be more secure

    import unittest
    from hvac import Client

    URL = "https://p.vault.myfine-company.de"
    JENKINS_TOKEN = "mylovelytoken"

    def test_ci_startup(self):

      # Jenkins authentifies with token as secure instance
      jenkins_client = Client(url=URL, token=JENKINS_TOKEN)

      # fetch the role_id and stores this somewhere in the image of the app
      resp = jenkins_client.auth.approle.read_role_id(role_name='workshop')
      role_id = resp["data"]["role_id"]

      # get a wrapped secret_id and passes this to the starting app
      result = jenkins_client.write(path='auth/approle/role/workshop/secret-id',wrap_ttl="2s")
      unwrap_token = result['wrap_info']['token']

      # No the app comes in place
      app_client = Client(url=URL) # , token=JENKINS_TOKEN)

      # unwrap the secret_id
      unwrap_response = app_client.sys.unwrap(unwrap_token) # !!! Here I get permission denied
      secret_id = unwrap_response['data']['secret_id']

      # use role_id and secret_id to login
      login_result = app_client.auth.approle.login(role_id=role_id, secret_id=secret_id)
      client_token = login_result['auth']['client_token']

      # Read the database credential
      read_response = app_client.secrets.kv.v2.read_secret_version(path='test/webapp')
      self.assertEqual("users", read_response['data']['data']['db_name'])

      return

Unfortunatly when try to unwrap the secret_id with app_client.sys.unwrap(unwrap_token) there is an 403 "permission denied" When I use the app_client-Connection with app_client = Client(url=URL), token=JENKINS_TOKEN) everything works fine. But this of course this not the way the AppRole based authentication should be used. All this is bases on the following Tutorials and Best Practices :

https://developer.hashicorp.com/vault/tutorials/recommended-patterns/pattern-approle https://developer.hashicorp.com/vault/tutorials/auth-methods/approle?in=vault%2Fauth-methods

I think is somewhat related to policies. But I did not find the solution yet.


Solution

  • Bash window 1:

    $ export VAULT_ADDR="https://p.vault.myfine-company.de"
    $ export VAULT_TOKEN="my-fine-token"
    $ # This creates a secret 
    $ vault kv put secret/mysql/webapp db_name="users" username="admin" password="passw0rd" 
    $ # this writes the policy to access the secret
    $ vault policy write jenkins -<<EOF\n# Read-only permission on secrets stored at 'secret/data/mysql/webapp'\npath "secret/data/mysql/webapp" {\n  capabilities = [ "read" ]\n}\nEOF\n
    $ # This creates an approle jenkins
    $ vault write auth/approle/role/jenkins token_policies="jenkins" \\n    token_ttl=1h token_max_ttl=4h\n
    $ # this reads the role-id
    $ vault read auth/approle/role/jenkins/role-id
      Key        Value
      ---        -----
      role_id    fcff5e13-wonderfull-my-fine-role-id
    $ This creates a wrapping token with TTL 120s 
    $ vault write -wrap-ttl=120s -force auth/approle/role/jenkins/secret-id
      Key                              Value
      ---                              -----
      wrapping_token:                  hvs.wonderfull-msgiIp9nu91c1fLrcwGh4KHGh2cy5HMmw4bkh2-my-fine-wrapping-token
      wrapping_accessor:               ddXZLPFmy-fine-accessor
      wrapping_token_ttl:              2m
      wrapping_token_creation_time:    2022-11-23 12:19:46.958503493 +0000 UTC
      wrapping_token_creation_path:    auth/approle/role/jenkins/secret-id
      wrapped_accessor:                5superp-6-my-fine-wrapped-accessor
    

    role_id and wrapping_token is now used in bash window 2:

    $ # unwrap token to obtain the secret-id
    $ VAULT_TOKEN=hvs.wonderfull-msgiIp9nu91c1fLrcwGh4KHGh2cy5HMmw4bkh2-my-fine-wrapping-token vault unwrap
      Key                   Value
      ---                   -----
      secret_id             ac8b3594-my-wundervolles-secret-id
      secret_id_accessor    485423my-wundervolles-secret-accessor
      secret_id_ttl         0s
    $ # login to vault and get a APP_TOKEN to be used to read secrets
    $ vault write auth/approle/login role_id=fcff5e13-wonderfull-my-fine-role-id secret_id=ac8b3594-my-wundervolles-secret-id
      Key                     Value
      ---                     -----
      token                   hvs.CAESIEW-so-a-nice-token-lEgXb9ZIf-my-wonderfull-token
      token_accessor          MehfK-my-wonderfull-accessor
      token_duration          1h
      token_renewable         true
      token_policies          ["default" "jenkins"]
      identity_policies       []
      policies                ["default" "jenkins"]
      token_meta_role_name    jenkins
    $ export APP_TOKEN=hvs.CAESIEW-so-a-nice-token-lEgXb9ZIf-my-wonderfull-token
    $ # Read a secret
    $ VAULT_TOKEN=$APP_TOKEN vault kv get secret/mysql/webapp
      ====== Secret Path ======
      secret/data/mysql/webapp
      ======= Metadata =======
      Key                Value
      ---                -----
      created_time       2022-11-23T09:44:24.429733013Z
      custom_metadata    <nil>
      deletion_time      n/a
      destroyed          false
      version            1
      ====== Data ======
      Key         Value
      ---         -----
      db_name     users
      password    passw0rd
      username    admin
    

    The advantage with this wrapped_token is that the CI Pipeline passes this short lived token to the app. The app uses this token to retrieve the secret-id (by unwrapping it) and performs the login together with the role-idto obtain the access token to read the database secret.

    Solution :

    The Problem is solved by providing the unwrap_token to app_client = Client(url=URL) token=unwrap_token as mentioned in a still open pull-request to hvac (as of 2022-11-30) :

    import hvac
    
    client = hvac.Client(url='https://127.0.0.1:8200')
    client.token = "hvs.wonderfull-msgiIp9nu91c1fLrcwGh4KHGh2cy5HMmw4bkh2-my-fine-wrapping-token"
    
    # When authenticating with just the wrapping token, should not pass token into unwrap call
    unwrap_response = client.sys.unwrap()
    print('Unwrapped approle role token secret id accessor: "%s"' % unwrap_response['data']['secret_id_accessor'])