Bootstrap FreeKB - Hashicorp Vault - Update a secret using Python hvac
Hashicorp Vault - Update a secret using Python hvac

Updated:   |  Hashicorp Vault articles

This assumes you are familiar with the Python hvac client. If not, check out my article Hashicorp Vault - Getting Started with Python hvac.

This assumes the following has already been done.

Let's say the secrets engine has been enabled with -path=secret/

~]# vault secrets enable -path=secret/ kv
Success! Enabled the kv secrets engine at: secret/

 

And let's say approle has been enabled and there is a role named "my-role" and contains a policy named "my-policy".

~]$ vault read auth/approle/role/my-role
Key                        Value
---                        -----
policies                   [my-policy]

 

In this example, since the secrets engine has been enabled with -path=secret/ the policy path will need to begin with secret/

Let's say "my-policy" permits the following capabilities to "secret/my_path/*".

~]$ vault policy read my-policy
path "secret/my_path/*" {
  capabilities = ["create", "delete", "list", "patch", "read", "update"]
}

 

There are two similar methods that can be used to update a secret

  • client.secrets.kv.v2.patch (I use this method)
  • client.secrets.kv.v2.create_or_update_secret

Let's say you have a secret named my_secret that contains two key/value pairs

{'foo': 'hello', 'bar': 'world'}

 

With client.secrets.kv.v2.patch, let's say you pass in {'foo': 'goodbye'}.

client.secrets.kv.v2.patch(
  mount_point="my_path",
  path="my_secret",
  secret={'foo': 'goodbye'}
)

 

In this scenario, the foo key was updated and the bar key/value pairs remains.

{'foo': 'goodbye', 'bar': 'world'}

 

On the other hand, with client.secrets.kv.v2.create_or_update_secret, let's say you pass in {'foo': 'goodbye'}.

client.secrets.kv.v2.create_or_update_secret(
  mount_point="my_path",
  path="my_secret",
  secret={'foo': 'goodbye'}
)

 

In this scenario, the foo key was updated and the bar key/value pairs is gone.

{'foo': 'goodbye'}

 

I first use approle login with the role ID and secret ID for my-role and then use client.secrets.kv.v2.read_secret_version to get the list of keys and values in the secret.

  • mount_path='my_path' is used here since my-policy has secret/my_path/*
  • path='my_secret' is used to get the keys and values of my_secret at secret/my_path/my_secret

Check out my article Hashicorp Vault - Error Handling using Python hvac for details on how to include Error Handling.

#!/usr/bin/python3
import hvac

client = hvac.Client(url='http://vault.example.com:8200')

client.auth.approle.login(
  role_id="b4a68549-1464-7aac-b0cd-d22954985aa8",
  secret_id="6039e2e2-6017-8db9-2e1b-dd6bd449f901"
)

response = client.secrets.kv.v2.read_secret_version(
  mount_path='my_path'
  path='my_secret'
)

print(f"response = {response}")

client.logout()

 

Something like this should be returned. In this example, my_first_secret contains two key/value pairs, foo: hello and bar: world.

{
  'request_id': '9a951153-a408-5d12-e3fc-a9449da14e7e', 
  'lease_id': '', 
  'renewable': False, 
  'lease_duration': 0, 
  'data': {
    'data': {
      'foo': 'hello', 
      'bar': 'world'
    }, 
    'metadata': {
      'created_time': '2024-03-20T11:48:08.69342151Z', 
      'custom_metadata': None, 
      'deletion_time': '', 
      'destroyed': False, 
      'version': 18
    }
  }, 
  'wrap_info': None, 
  'warnings': None, 
  'auth': None
}

 

Then I would do something like this.

  • if the response dictionary contains a key named 'foo' then update the value of the foo key to be 'goodbye'
  • if the response dictionary does NOT contain a key named 'foo' then append foo: goodbye to the response dictionary
if 'foo' in response['data']['data']:
  for key in response['data']['data']:
    if key == 'foo':
      if response['data']['data']['foo'] == 'goodbye':
        print(f"the foo key already contains a value of goodbye")
        sys.exit(1)
      else:
        response['data']['data']['foo'] = 'goodbye'
else:
  response['data']['data']['foo'] = 'goodbye'

 

You could print just the response['data']['data'] key to validate that the response dictionary has been updated.

print(response['data']['data'])

 

Which should return something like this.

{'foo': 'goodbye', 'bar': 'world'}

 

Now you are ready to use client.secrets.kv.v2.create_or_update_secret to update the secret with the updated response dictionary.

  • mount_path='my_path' is used here since my-policy has secret/my_path/*
  • the name of the secret will be my_secret and the path to the secret will be secret/my_path/my_secret
  • the secret will contain the updated response['data']['data'] dictionary
client.secrets.kv.v2.create_or_update_secret(
  mount_point="my_path",
  path='my_secret',
  secret=dict(response['data']['data'])
)

 

If the secret is successfully updated, something like this should be returned. Notice in this example that the version is 3.

{
 'request_id': 'bb772050-6722-33f2-4419-efa6c1886d82', 
 'lease_id': '', 
 'renewable': False, 
 'lease_duration': 0, 
 'data': {
   'created_time': '2024-03-20T08:29:09.697056103Z', 
   'custom_metadata': None, 
   'deletion_time': '', 
   'destroyed': False, 
   'version': 3
 },
 'wrap_info': None, 
 'warnings': None, 
 'auth': None
}

 

The response dictionary can be used to run a basic test to determine if the secret was updated.

try:
  response['data']['created_time']
except KeyError:
  print(f"got KeyError")
else:
  print(f"Successfully created key foo in secret my_secret at {response['data']['created_time']}")

 

 




Did you find this article helpful?

If so, consider buying me a coffee over at Buy Me A Coffee



Comments


Add a Comment


Please enter c8f869 in the box below so that we can be sure you are a human.