Instructions and code that show you how to use Ansible playbooks to manage the lifecycle of a secret in IBM Secret Manager.

IBM Cloud Secrets Manager is built on open-source HashiCorp Vault, and it allows you to dynamically create and manage secrets. For infrastructure or services provisioned from IBM Cloud, you need to rotate the secrets on a timely basis.

What is an Ansible playbook?

Many IT teams have a common requirement to manage the configuration for systems in an automated and repeatable way. Ansible is one of the leaders for configuration management tools, and it has a growing community of developers and industries adopting it as part of their standards.

An Ansible playbook is an automation blueprint used in a repeatable and consistent way across OS distributions. This tool is based on Python and YAML, and any developer who has familiarity with Python or YAML will find Ansible easy to use. Ansible makes use of reusable configuration tasks that are commonly called modules. We call a “play” to the combination of one or more modules and one or more plays creates an Ansible playbook. If we make an abstraction, a playbook is a list of tasks that we execute on a group of servers that are defined on an Ansible inventory.

Performing secret management functions with Ansible playbooks

In this blog post, we will discuss how to perform various functions for secret management in IBM Cloud Secrets manager, including creating a Vault token, looking up a secret group and creating/rotating secrets using an Ansible playbook. Although there are several examples provided here for various languages, this example code simplifies the process by building different tasks to accomplish the same thing using automation.

This playbook is oriented to work with a specific type of secret Key Value, but IBM Cloud Secrets Manager supports another type of secrets like arbitrary secrets, IAM credentials, SSL or TLS certificates.

Requirements

To use this Ansible playbook, you must have a valid IBM Cloud API key for services. These keys can also be stored, rotated, revoked or even leased if you only want to provide temporary access for other team members or services.

IBM Cloud API keys — combined with the right identity and access management (IAM) policy — enable access to cloud object storage, continuous delivery and other platform services. The IBM Cloud API Key is used to generate an IBM Cloud IAM token, which is an authentication token that lasts no more than one hour, by default. The IBM Cloud IAM token is used to authenticate with the IBM Secrets Vault API endpoints.

Setup

See the IBM Cloud Secrets Manager page to learn more about Secrets Manager and how to configure an instance:

---
- name: IBM Cloud secrets manager playbook
  hosts: localhost
  connection: local
  vars:
    api_key: "XXXXXXXXXXXXXX"
    hostname_vault: "https://XXXXXXXXXXX.XXXXXXX.secrets-manager.appdomain.cloud"
    secret_groups_name: "secrets_group_test"
    secret_name: "test-kv-secret-playbook"
  tasks:
    - name: Create IAM Token
      uri:
        url: https://iam.cloud.ibm.com/identity/token
        headers:
          Content-Type: application/x-www-form-urlencoded
          Accept: application/json
        body_format: form-urlencoded
        method: POST
        body:
          grant_type: "urn:ibm:params:oauth:grant-type:apikey"
          apikey: "{{ api_key }}"
      register: login

    - block:
      - name: Setting IAMtoken
        set_fact:
          iam_token: "{{ login.json.access_token }}"

      - name: Create Vault Token
        uri:
          url: "{{ hostname_vault }}/v1/auth/ibmcloud/login"
          headers:
            Content-Type: application/json
            Accept: application/json
          body_format: json
          method: PUT
          body: '{"token": "{{ iam_token }}" }'
        register: token_vault_rest_call

      - name: Set vault token
        set_fact:
          vault_token: "{{ token_vault_rest_call.json.auth.client_token }}"

      - name: Query available Secret groups
        uri:
          url: "{{ hostname_vault }}/v1/auth/ibmcloud/manage/groups"
          headers:
            Content-Type: application/json
            X-Vault-Token: "{{ vault_token }}"
          method: GET
        register: secrets_group_rest_call

      - name: Set secrets group id
        set_fact:
          secret_group_id: "{{ secrets_group_rest_call.json.data.groups | json_query(jmesquery) | join('') }}"
        vars:
          jmesquery: "[? name==`{{ secret_groups_name }}`].id"

      - block:
        - name: Create Secret group
          uri:
            url: "{{ hostname_vault }}/v1/auth/ibmcloud/manage/groups"
            headers:
              Accept: application/json
              X-Vault-Token: "{{ vault_token }}"
              Content-Type: application/json
            body_format: json
            method: PUT
            body: '{ "name": "{{ secret_groups_name }}", "description": "Extended description for the secret group." }'
          register: create_secret_group_rest_call

        - name: Set secrets group id
          set_fact:
            secret_group_id: "{{ create_secret_group_rest_call.json.data.id }}"

        when: secret_group_id | length == 0

      - name: Check if the secret is already created
        uri:
          url: "{{ hostname_vault }}/v1/ibmcloud/kv/secrets/groups/{{ secret_group_id }}"
          headers:
            Accept: application/json
            X-Vault-Token: "{{ vault_token }}"
          body_format: json
          method: GET
        register: query_existing_secrets_rest_call

      - name: Set secret id
        set_fact:
          secret_id: "{{ query_existing_secrets_rest_call.json.data.secrets | json_query(jmesquery) | join('') }}"
        vars:
          jmesquery: "[? name==`{{ secret_name }}`].id"


      - name: Create a secret on specific group if not present
        uri:
          url: "{{ hostname_vault }}/v1/ibmcloud/kv/secrets/groups/{{ secret_group_id }}"
          headers:
            Content-Type: application/json
            X-Vault-Token: "{{ vault_token }}"
          body_format: json
          method: POST
          body: '{
            "name": "{{ secret_name }}",
            "description": "Extended description for my secret.",
            "payload": {
              "secret": "This a secret you cannot see this"
            }
          }'
        register: create_secret_rest_call
        when: secret_id | length == 0

      - name: Set secrets ID
        set_fact:
          secret_id: "{{ create_secret_rest_call.json.data.id }}"
        when: create_secret_rest_call.skipped is not defined

      - name: Rotate the secret once
        uri:
          url: "{{ hostname_vault }}/v1/ibmcloud/kv/secrets/groups/{{ secret_group_id }}/{{ secret_id }}/rotate"
          headers:
            Content-Type: application/json
            X-Vault-Token: "{{ vault_token }}"
          body_format: json
          method: POST
          body: '{
            "payload": {
              "secret": "Now I have  new value"
            }
          }'
        register: rotate_secret_rest_call


      when: login.status == 200

Understanding the tasks in the Ansible playbook

Let’s break down the code to understand each one of the modules in the playbook to manage the lifecycle of the secrets on the IBM Cloud Secrets Manager.

In the vars section, we define the values that are specific for each one of the Secrets Manager instances and specify the API Key to be used, the hostname of the Secrets Manager and the secrets group name and, finally, the secret name to be created/rotated:

  vars:
    api_key: "XXXXXXXXXXXXXX"
    hostname_vault: "https://XXXXXXXXXXX.XXXXXXX.secrets-manager.appdomain.cloud"
    secret_groups_name: "secrets_group_test"
    secret_name: "test-kv-secret-playbook"

The first modules used in the playbook are responsible for creating an IAM Token using the API token. This is making use of the URI Ansible module to call the REST APIs on IBM Cloud and register the output from the API rest call in a variable called login.  There is an Ansible block to manage the calls when the return from the API call is successful:

    - name: Create IAM Token
      uri:
        url: https://iam.cloud.ibm.com/identity/token
        headers:
          Content-Type: application/x-www-form-urlencoded
          Accept: application/json
        body_format: form-urlencoded
        method: POST
        body:
          grant_type: "urn:ibm:params:oauth:grant-type:apikey"
          apikey: "{{ api_key }}"
      register: login

    - block:
. . .
      when: login.status == 200

Once the IAM Token is created, the playbook creates an Ansible fact (Variable) that has the value of the IAM token. Then there is another URI module that uses the token to generate a Vault token to authenticate with the IBM Cloud Secrets Manager endpoint (HashiCorp compatible). The Vault token value is stored on a variable called vault_token:

      - name: Setting IAMtoken
        set_fact:
          iam_token: "{{ login.json.access_token }}"

      - name: Create Vault Token
        uri:
          url: "{{ hostname_vault }}/v1/auth/ibmcloud/login"
          headers:
            Content-Type: application/json
            Accept: application/json
          body_format: json
          method: PUT
          body: '{"token": "{{ iam_token }}" }'
        register: token_vault_rest_call

      - name: Set vault token
        set_fact:
          vault_token: "{{ token_vault_rest_call.json.auth.client_token }}"

Once the Vault token is generated, the playbook needs to get the secret group ID where the secret will be created/rotated. The playbook performs another API call to retrieve all the existent group IDs on the Secrets Manager instance, and based on the value of the variable set in the vars block, the playbook searches the value of the secret group ID using the name of the group ID. If the group is not found, the playbook creates the secret group (not a common scenario added for illustration purposes). The playbook stores the secret group ID in a variable called secret_group_id

      - name: Query available Secret groups
        uri:
          url: "{{ hostname_vault }}/v1/auth/ibmcloud/manage/groups"
          headers:
            Content-Type: application/json
            X-Vault-Token: "{{ vault_token }}"
          method: GET
        register: secrets_group_rest_call

      - name: Set secrets group id
        set_fact:
          secret_group_id: "{{ secrets_group_rest_call.json.data.groups | json_query(jmesquery) | join('') }}"
        vars:
          jmesquery: "[? name==`{{ secret_groups_name }}`].id"

      - block:
        - name: Create Secret group
          uri:
            url: "{{ hostname_vault }}/v1/auth/ibmcloud/manage/groups"
            headers:
              Accept: application/json
              X-Vault-Token: "{{ vault_token }}"
              Content-Type: application/json
            body_format: json
            method: PUT
            body: '{ "name": "{{ secret_groups_name }}", "description": "Extended description for the secret group." }'
          register: create_secret_group_rest_call

        - name: Set secrets group id
          set_fact:
            secret_group_id: "{{ create_secret_group_rest_call.json.data.id }}"

        when: secret_group_id | length == 0

The playbook has another module to verify if the secret is already created with the name defined on the vars section and stores the ID in a variable called secret_id. If the secret is not created, the variable value is null:

      - name: Check if the secret is already created
        uri:
          url: "{{ hostname_vault }}/v1/ibmcloud/kv/secrets/groups/{{ secret_group_id }}"
          headers:
            Accept: application/json
            X-Vault-Token: "{{ vault_token }}"
          body_format: json
          method: GET
        register: query_existing_secrets_rest_call

      - name: Set secret id
        set_fact:
          secret_id: "{{ query_existing_secrets_rest_call.json.data.secrets | json_query(jmesquery) | join('') }}"
        vars:
          jmesquery: "[? name==`{{ secret_name }}`].id"

Finally, the lasts two modules create the secret (if it doesn’t already exist) and rotate the value of the secret. The value of the secret is passed as part of the payload of the JSON call:

      - name: Create a secret on specific group if not present
        uri:
          url: "{{ hostname_vault }}/v1/ibmcloud/kv/secrets/groups/{{ secret_group_id }}"
          headers:
            Content-Type: application/json
            X-Vault-Token: "{{ vault_token }}"
          body_format: json
          method: POST
          body: '{
            "name": "{{ secret_name }}",
            "description": "Extended description for my secret.",
            "payload": {
              "secret": "This a secret you cannot see this"
            }
          }'
        register: create_secret_rest_call
        when: secret_id | length == 0

      - name: Rotate the secret once
        uri:
          url: "{{ hostname_vault }}/v1/ibmcloud/kv/secrets/groups/{{ secret_group_id }}/{{ secret_id }}/rotate"
          headers:
            Content-Type: application/json
            X-Vault-Token: "{{ vault_token }}"
          body_format: json
          method: POST
          body: '{
            "payload": {
              "secret": "Now I have  new value"
            }
          }'
        register: rotate_secret_rest_call

Dependencies

The dependencies are managed using a requirements file. It would consist of the following packages:

  • ansible
  • ansible-lint
  • pylint
  • pyyaml
  • jmespath

GitHub code sample code

A GitHub repo with a copy of this code is available here.

Conclusion

IBM Cloud Secrets Manager is a SaaS offering that enables IBM Cloud customers to create, rotate, update and manage secrets from a central location. The instance is built on open-source HashiCorp Vault, which means that the Vault API is enabled to consume and manage the lifecycle of the secrets.

These APIs can be used to develop Ansible playbooks, and in this blog, we provided sample code that can be used as the base to create playbooks/roles that manage the lifecycle of a secret in IBM Secret Manager. The playbook allows you to create and look-up a secret group, create and look-up a secret and rotate the secret.

Was this article helpful?
YesNo

More from Cloud

How a US bank modernized its mainframe applications with IBM Consulting and Microsoft Azure

9 min read - As organizations strive to stay ahead of the curve in today's fast-paced digital landscape, mainframe application modernization has emerged as a critical component of any digital transformation strategy. In this blog, we'll discuss the example of a US bank which embarked on a journey to modernize its mainframe applications. This strategic project has helped it to transform into a more modern, flexible and agile business. In looking at the ways in which it approached the problem, you’ll gain insights into…

The power of the mainframe and cloud-native applications 

4 min read - Mainframe modernization refers to the process of transforming legacy mainframe systems, applications and infrastructure to align with modern technology and business standards. This process unlocks the power of mainframe systems, enabling organizations to use their existing investments in mainframe technology and capitalize on the benefits of modernization. By modernizing mainframe systems, organizations can improve agility, increase efficiency, reduce costs, and enhance customer experience.  Mainframe modernization empowers organizations to harness the latest technologies and tools, such as cloud computing, artificial intelligence,…

Modernize your mainframe applications with Azure

4 min read - Mainframes continue to play a vital role in many businesses' core operations. According to new research from IBM's Institute for Business Value, a significant 7 out of 10 IT executives believe that mainframe-based applications are crucial to their business and technology strategies. However, the rapid pace of digital transformation is forcing companies to modernize across their IT landscape, and as the pace of innovation continuously accelerates, organizations must react and adapt to these changes or risk being left behind. Mainframe…

IBM Newsletters

Get our newsletters and topic updates that deliver the latest thought leadership and insights on emerging trends.
Subscribe now More newsletters