Git Product home page Git Product logo

tragiccode-azure_key_vault's Introduction

azure_key_vault

Puppet Forge Version Puppet Forge Pdk Version Puppet Forge Downloads Puppet Forge Endorsement

Table of Contents

  1. Description
  2. Setup
  3. Managed Service Identity (MSI) vs Service Principal Credentials
  4. How it works
  5. How it's secure by default
  6. Usage
  7. Reference - An under-the-hood peek at what the module is doing and how
  8. Development - Guide for contributing to the module

Description

Secure secrets management is essential and critical in order to protect data in the cloud. Key Vault is Microsoft Azure's solution to make this happen. This module provides a Puppet function and a Hiera backend that allows you to easily fetch secrets securely on the puppet server and embed them into catalogs during compilation time.

Setup

The module requires the following:

Managed Service Identity (MSI) vs Service Principal Credentials

This module provides 2 ways for users to authenticate with azure key vault and pull secrets. These 2 options are Managed Service Identity ( MSI ) and Service Principal Credentials. We highly recommend you utilize Managed Service Identity over service principal credentials whenever possible. This is because you do not have to manage and secure a file on our machines that contain credentials! In some cases, Managed Service Identity ( MSI ) might not be an option for you. One example of this is if your Puppet server and some of your puppet agents are not hosted in Azure. In that case, you can create a Service Principal in Azure Active Directory, assign the appropriate permissions to this Service Principal, and both the function and Hiera Backend provided in this module can authenticate to Azure Keyvault using the credentials of this Service Principal.

How it works

Puppet Function

This module contains a Puppet 4 function that allows you to securely retrieve secrets from Azure Key Vault. In order to get started simply call the function in your manifests passing in the required parameters.

Using Managed Service Identity ( MSI )

$important_secret = azure_key_vault::secret('production-vault', 'important-secret', {
  vault_api_version    => '2016-10-01',
  metadata_api_version => '2018-04-02',
})

This example fetches the latest secret with the name "important-secret" from the vault named "production-vault". Under the covers it calls the Azure instance metadata service api to get an access token in order to make authenticated requests to the vault api on-behalf-of the MSI. Once the secret is returned you can begin to use it throughout your puppet code.

NOTE: In order to improve performance and avoid the request limit for the metadata service api the api token retrieved once then stored in a cache that exists for the duration of the puppet run.

In the above example the api_versions hash is important. It is pinning both of the Azure specific api's ( instance metadata api & vault api ) used under the hood to specific versions so that you have full control as to when your puppet code starts calling newer/older versions of the apis. In order to understand what versions are available to your regions please visit the azure documentation

Using Service Principal Credentials

$important_secret = azure_key_vault::secret('production-vault', 'important-secret', {
  vault_api_version    => '2016-10-01',
  service_principal_credentials => {
    tenant_id     => '00000000-0000-1234-1234-000000000000',
    client_id     => '00000000-0000-1234-1234-000000000000',
    client_secret => lookup('azure_client_secret'),
  }
})

This example show how to utilize service principal credentials if you for some reason are unable to use Managed Service Identity ( MSI ) at your organization. The client_secret must be of type "Sensitive". Please ensure you configure hiera to return the value wrapped in this type as this is what the secret function expects to ensure there is possibilty of leaking the client_secret.

Hiera Backend

This module contains a Hiera 5 backend that allows you to securely retrieve secrets from Azure key vault and use them in hiera.

Using Managed Service Identity ( MSI )

Add a new entry to the hierarchy hash in hiera.yaml providing the following required lookup options:

- name: 'Azure Key Vault Secrets'
    lookup_key: azure_key_vault::lookup
    options:
      vault_name: production-vault
      vault_api_version: '2016-10-01'
      metadata_api_version: '2018-04-02'
      key_replacement_token: '-'
      confine_to_keys:
        - '^azure_.*'
        - '^.*_password$'
        - '^password.*'

Using Service Principal Credentials

To utilize service principal credentials in hiera simply replace metadata_api_version with service_principal_credentials and ensure it points to a valid yaml file that contains the service principal credentials you would like to use.

- name: 'Azure Key Vault Secrets'
    lookup_key: azure_key_vault::lookup
    options:
      vault_name: production-vault
      vault_api_version: '2016-10-01'
      service_principal_credentials: '/etc/puppetlabs/puppet/azure_key_vault_credentials.yaml'
      key_replacement_token: '-'
      confine_to_keys:
        - '^azure_.*'
        - '^.*_password$'
        - '^password.*'

Below is the format of the file that is expected to contain your service principal credentials.

tenant_id: '00000000-0000-1234-1234-000000000000'
client_id: '00000000-0000-1234-1234-000000000000'
client_secret: some-secret

To retrieve a secret in puppet code you can use the lookup function:

notify { 'lookup':
  message => lookup('important-secret'),
}

The alias function can also be used in hiera files, for example to set class parameters:

some_class::password: "%{alias('important-secret')}"

NOTE: The alias function must be used in the above example. Attempting to use the lookup function inside of your hiera files will not work. This is because, when using lookup, the result is interpolated as a string. Since this module is safe by default, it always returns secrets as Sensitive[String]. The reason we have to use alias is because it will preserve the datatype of the value. More information can be found here

You can use a fact to specify different vaults for different groups of nodes. It is recommended to use a trusted fact such as trusted.extensions.pp_environment as these facts cannot be altered. Alternatively a custom trusted fact can be included in the certificate request

- name: 'Azure Key Vault Secrets from trusted fact'
    lookup_key: azure_key_vault::lookup
    options:
      vault_name: "%{trusted.extensions.pp_environment}"
      vault_api_version: '2016-10-01'
      metadata_api_version: '2018-04-02'
      key_replacement_token: '-'
      confine_to_keys:
        - '^azure_.*'
        - '^.*_password$'
        - '^password.*'

NOTE: While the above examples show manual lookups happening, it's recommended and considered a best practice to utilize Hiera's automatic parameter lookup (APL) within your puppet code

What is confine_to_keys?

By design, hiera will traverse the configured heiarchy for a given key until one is found. This means that there can be a potentially large number of web requests against azure key vault. In order to improve performance and prevent hitting the Azure KeyVault rate limits ( ex: currently there is a maximum of 2,000 lookups every 10 seconds allowed against a key vault), the confine_to_keys allows you to provide an array of regexs that help avoid making a remote call.

As an example, if you defined your confine_to_keys as shown below, hiera will only make a web request to get the secret in azure key vault when the key being lookedup matches atleast one of the provided regular expressions in the confine_to_keys array.

- name: 'Azure Key Vault Secrets from trusted fact'
    lookup_key: azure_key_vault::lookup
    options:
      vault_name: "%{trusted.extensions.pp_environment}"
      vault_api_version: '2016-10-01'
      metadata_api_version: '2018-04-02'
      key_replacement_token: '-'
      confine_to_keys:
        - '^azure_.*'
        - '^.*_password$'
        - '^password.*'

NOTE: The confine_to_keys is very important to make you sure get right. As a best practice, come up with some conventions to avoid having a large number of regexs you have to add/update/remove and also test. The above example provides a great starting point.

What is key_replacement_token?

KeyVault secret names can only contain the characters 0-9, a-z, A-Z, and -.

When relying on automatic parameter lookup, this is almost always going to contain the module delimiter (::) or underscores.

This module will automatically convert the variable name to a valid value by replacing every invalid character with the key_replacement_token value, which defaults to -.

For example, the hiera variable puppetdb::master::config::puppetdb_server will automatically be converted to puppetdb--master--config--puppetdb-server before being queried up in KeyVault.

When troubleshooting, you can run hiera from the commandline with the --explain option to see the key name being used :

  Using normalized KeyVault secret key for lookup: puppetdb--master--config--puppetdb-server

How it's secure by default

In order to prevent accidental leakage of your secrets throughout all of the locations puppet stores information the returned value of the azure_key_vault::secret function & Hiera backend return a string wrapped in a Sensitive data type. Lets look at an example of what this means and why it's important. Below is an example of pulling a secret and trying to output the value in a notice function.

$secret = azure_key_vault::secret('production-vault', 'important-secret', {
  metadata_api_version => '2018-04-02',
  vault_api_version    => '2016-10-01',
})
notice($secret)

This outputs Notice: Scope(Class[main]): Sensitive [value redacted]

However, Sometimes you need to unwrap the secret to get to the original data. This is typically needed under the following but not limited to circumstances.

  1. You need to programatically change/alter/append to the secret that was retrieved.
  2. The resource you are passing the secret to does not natively handle the Sensitive data type.

These 2 special cases are discussed in detail next.

Special Case 1 - Programatically Changing/Altering/Appending to a secret

In order to change the original secret you always follow the same 3 step process.

  1. unwrap
  2. alter/change
  3. rewrap
$secret = azure_key_vault::secret('production-vault', 'important-secret', {
  metadata_api_version => '2018-04-02',
  vault_api_version    => '2016-10-01',
})

$rewraped_secret = Sensitive("password: ${secret.unwrap}")

file { 'C:\\DataForApplication.secret':
  content   => $rewraped_secret,
  ensure    => file,
}

Special Case 2 - A Resource doesn't natively support Puppet's Sensitive Data type

Unfortunately, All resource's don't magically handle the sensitive data type. In order to know if a resource supports it or not simply read the documentation or browse through the code if it's available. If you are using a resource that doesn't support the sensitive data type you can unwrap the secret but but you are no longer guaranteed the secret will not get leaked in logs/reports depending on what the resource does with the secret you passed to it. Below is an example of an imaginary resource that doesn't support the sensitive data type and how you can unwrap to handle this situation.

$admin_password_secret = azure_key_vault::secret('production-vault', 'important-secret', {
  metadata_api_version => '2018-04-02',
  vault_api_version    => '2016-10-01',
})

resource_not_supporting_sensitive { 'my_resource':
    username => 'admin',
    password => $admin_password_secret.unwrap,
}

NOTE: Whatever resource you run into that doesn't support the sensitive data type you should open a issue/ticket with the person/organization maintaining the resource.

Usage

Embedding a secret in a file

Below shows an example of how to retrieve a secret and place it in a file on a node. This is typically done because some application/process is expecting the file to exist with the secret in order to get some work done ( such as connecting to a database ).

file { 'C:\\DataForApplication.secret':
  content   => azure_key_vault::secret('production-vault', 'important-secret', {
    metadata_api_version => '2018-04-02',
    vault_api_version    => '2016-10-01',
  }),
  ensure    => file,
}

Retrieving a specific version of a secret

By Default, the latest secret is always retrieved from the vault. If you want to ensure only a specific version of a secret is retrieved simply pass a parameter to specify the exact version you want.

$admin_password_secret = azure_key_vault::secret('production-vault', 'admin-password', {
  metadata_api_version => '2018-04-02',
  vault_api_version    => '2016-10-01',
},
'067e89990f0a4a50a7bd854b40a56089')

NOTE: Retrieving a specific version of a secret is currently not available via the hiera backend

Retrieving a certificate

Azure Key Vault stores certificates "under-the-covers" as secrets. This means you retrieving certificates can be done using the same azure_key_vault::secret function. One thing to keep in mind is that the certificate will be based64 encoded and will need to be decoded before usage to have a valid certificate file.

$certificate_secret = azure_key_vault::secret('production-vault', "webapp-certificate", {
  metadata_api_version => '2018-04-02',
  vault_api_version    => '2016-10-01',
})

file { "C:/tmp/webapp-certificate.pfx" :
  content   => base64('decode', "${certificate_secret.unwrap}"),
  ensure    => file,
}

sslcertificate { "Install-WebApp-Certificate" :
  name       => "${filename}",
  location   => 'C:\tmp',
  root_store => 'LocalMachine',
  thumbprint => "${certificate_thumbprint}"
}

NOTE: Retrieving a specific version of a secret is currently not available via the hiera backend

Reference

See REFERENCE.md

Development

Contributing

  1. Fork it ( https://github.com/tragiccode/tragiccode-azure_key_vault/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

tragiccode-azure_key_vault's People

Contributors

dowlingw avatar github-actions[bot] avatar kev-in-shu avatar lupyana avatar tragiccode avatar turbodog avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

tragiccode-azure_key_vault's Issues

Loading Key Vaults dynamically

We are creating Key Vault names like
PROD-KEY-VAULT-0001
DEV-KEY-VAULT-3458
UAT-KEY-VAULT-7893

Due to uniqueness, we are adding random number to Key Vault name.

how do I load random Key Vault names in Hiera.yaml ?

For resource User password setup via key vault

Hi ,

Could you please help me ,

for user resource, azure key vault password nit working as expected
User is getting created, password not accepting. For file resource perfectly working.

Error retrieving access token with "Temporarily throttled, too many requests" message

Hello we are seeing an error on the puppet agent with description: "Temporarily throttled, too many requests".

We checked the puppet master log and it points to the exception occurring on line 14 in azure.rb

We ran the curl commands from here on the puppet master and they worked. We were able to retrieve the access token and secret in ssh in plaintext.
https://stackoverflow.com/questions/51440297/how-to-set-the-value-of-an-azure-keyvault-secret-using-curl

Kind Regards.

Hiera Automatic Parameter Lookup (APL) broken

This module has 2 methods for pulling secrets

  1. Puppet 4 function
  2. Hiera Lookup Function

Currently, all Automatic Parameter lookups that a user attempts to do will fail in the background. This is because when hiera does automatic parameter lookup for classes it uses a double colon for key names which are not valid characters for a secret name in an azure key vault. Also when the ruby attempts to craft the url for the secret location the colons are causing the GET request to go to the wrong location returning the following content as shown below.

Debug: importing 'C:/Users/tragiccode/test_azure/manifests/init.pp' in environment production
Debug: Automatically imported test_azure from test_azure into production
Warning: <!DOCTYPE html>
<html>
    <head>
        <title>Runtime Error</title>
        <meta name="viewport" content="width=device-width" />
        <style>
         body {font-family:"Verdana";font-weight:normal;font-size: .7em;color:black;}
         p {font-family:"Verdana";font-weight:normal;color:black;margin-top: -5px}
         b {font-family:"Verdana";font-weight:bold;color:black;margin-top: -5px}
         H1 { font-family:"Verdana";font-weight:normal;font-size:18pt;color:red }
         H2 { font-family:"Verdana";font-weight:normal;font-size:14pt;color:maroon }
         pre {font-family:"Consolas","Lucida Console",Monospace;font-size:11pt;margin:0;padding:0.5em;line-height:14pt}
         .marker {font-weight: bold; color: black;text-decoration: none;}
         .version {color: gray;}
         .error {margin-bottom: 10px;}
         .expandable { text-decoration:underline; font-weight:bold; color:navy; cursor:hand; }
         @media screen and (max-width: 639px) {
          pre { width: 440px; overflow: auto; white-space: pre-wrap; word-wrap: break-word; }
         }
         @media screen and (max-width: 479px) {
          pre { width: 280px; }
         }
        </style>
    </head>

    <body bgcolor="white">

            <span><H1>Server Error in '/' Application.<hr width=100% size=1 color=silver></H1>

            <h2> <i>Runtime Error</i> </h2></span>

            <font face="Arial, Helvetica, Geneva, SunSans-Regular, sans-serif ">

            <b> Description: </b>An application error occurred on the server. The current custom error settings for this
 application prevent the details of the application error from being viewed remotely (for security reasons). It could, h
owever, be viewed by browsers running on the local server machine.
            <br><br>

            <b>Details:</b> To enable the details of this specific error message to be viewable on remote machines, plea
se create a &lt;customErrors&gt; tag within a &quot;web.config&quot; configuration file located in the root directory of
 the current web application. This &lt;customErrors&gt; tag should then have its &quot;mode&quot; attribute set to &quot
;Off&quot;.<br><br>

            <table width=100% bgcolor="#ffffcc">
               <tr>
                  <td>
                      <code><pre>

&lt;!-- Web.Config Configuration File --&gt;

&lt;configuration&gt;
    &lt;system.web&gt;
        &lt;customErrors mode=&quot;Off&quot;/&gt;
    &lt;/system.web&gt;
&lt;/configuration&gt;</pre></code>

                  </td>
               </tr>
            </table>

            <br>

            <b>Notes:</b> The current error page you are seeing can be replaced by a custom error page by modifying the
&quot;defaultRedirect&quot; attribute of the application&#39;s &lt;customErrors&gt; configuration tag to point to a cust
om error page URL.<br><br>

            <table width=100% bgcolor="#ffffcc">
               <tr>
                  <td>
                      <code><pre>

&lt;!-- Web.Config Configuration File --&gt;

&lt;configuration&gt;
    &lt;system.web&gt;
        &lt;customErrors mode=&quot;RemoteOnly&quot; defaultRedirect=&quot;mycustompage.htm&quot;/&gt;
    &lt;/system.web&gt;
&lt;/configuration&gt;</pre></code>

                  </td>
               </tr>
            </table>

            <br>

    </body>
</html>

Debug: Automatic Parameter Lookup of 'test_azure::secret'

On Strategy for being able to utilize APL is to do a bit of indirection as shown below

sqlserver.pp

class profile::windows::sqlserver(
   Sensitive $sensitive_sql_user_password,
) {
# code here
}

common.yaml

---
profile::windows::sqlserver::sql_user_password: '"%{lookup('sql_user_password')}"

lookup_options:
  '^profile::.+::sensitive_\w+$':
    convert_to: 'Sensitive'

NOTE: The APL indirection should have an explicit call out in the documentation about this strategy

In order deal with the colon issue and allow people to utilize APL we have a couple of options.

  1. Convert all keys that contain a double colon to an underscore and then perform the lookup. This would require calling out explicitly in the documentation that if using APL you must convert a key of profile::windows::sqlserver::sql_user_password to a secret name of profile_windows_sqlserver_sql_user_password

  2. It might be possible for the user to provide a lookup_options key. This could could indicate the following:

- name: 'Azure Key Vault Secrets'
    lookup_key: azure_key_vault::lookup
    options:
      vault_name: production-vault
      vault_api_version: '2016-10-01'
      metadata_api_version: '2018-04-02'
      convert_to 'Sensitive'
      key_replacement_token: '_'

a. What replacement token to use
b. Convert it to a Sensitive type

TODO: Finish this

Update documentation with new metadata service api version

The metadata service api has a newer version. We should update all references to this version in the documentation in order to ensure people are using the newest version.

Update all references to 2018-02-01 to 2018-04-02 in the readme.md

NOTE: It looks like some more locked down regions are 1 version behind but i think this is fine as most people will not be in the locked down regions.

azure keyvault integration with puppet

i am facing issue. trying to integrate azure keyvault with puppet, i have setup manage identity between azure key vault and puppet master vm, after that using puppet mainfest code am trying to acces that secrate in puppet.

$secret = azure_key_vault::secret('demo-test1', 'onkar', {
metadata_api_version => '2018-04-02',
vault_api_version => '2016-10-01',
})

$rewraped_secret = Sensitive("password: ${secret.unwrap}")

file { 'C:\DataForApplication.secret':
content => $rewraped_secret,
ensure => file,
}

Facing error when retriving data in client node Now, run the following command on the client node to retrieve the configurations.

sudo /opt/puppetlabs/bin/puppet agent --test

image

Puppet integration Net::ReadTimeout error

Hello TraGicCode

We are facing the issue of trying to integrate azure KeyVault with Puppet.

The Puppet server is deployed in the AKS cluster. Version is 6.18.1
tragiccode-azure_key_vault version: v3.1.1

Configure manage identity between Azure KeyVault and puppet master.

Our manifest:

$admin_pass = azure_key_vault::secret("my-kv","AdminPass", {
metadata_api_version => '2018-04-02',
vault_api_version => '2016-10-01',
})

notify{"The ADMIN PASS from KeyVAULT is: $admin_pass": }

$rewraped_secret = Sensitive("password: ${admin_pass.unwrap}")

file { 'C:\Config\secret.secret':
content => $rewraped_secret,
ensure => file,
}
}

Also have used Service Principal for testing but had the same errors in both cases.

Facing error when running puppet agent on the node

2023-03-10 12:48:39,185 ERROR [puppetserver] Puppet Evaluation Error: Error while evaluating a Function Call, Net::ReadTimeout (file: /etc/puppetlabs/code/environments/env/modules/users/manifests/install.pp, line: 14, column: 17) on node xx-xx-xx-xx-xxxx
2023-03-10 12:48:39,186 ERROR [puppetserver] Puppet Server Error: Evaluation Error: Error while evaluating a Function Call, Net::ReadTimeout (file: /etc/puppetlabs/code/environments/env/modules/users/manifests/install.pp, line: 14, column: 17) on node xx-xx-xx-xx-xxxx

Retrieve the certificates from key vault ?

Hi,

Just wondering if this puppet module has the ability to retrieve the certificates from the key vault similar to how it retrieves secrets from the vault? Since key vault not only stores secrets, it also stores certificates, if it got this ability, it would be awesome to add in the puppet manifest to get and install certificates from the key vault.

Thanks.

Add the feature for using multiple Managed Service Identities

Hi,

For Puppet Server is needed to assign multiple Managed Service Identities, it is needed when using different Azure Vnets and needed to create separate Managed Service Identity for each of Vnets from the security perspective.

Example of URL request:
http://169.254.169.254/metadata/identity/oauth2/token?api-version=2019-08-15&resource=https%3A%2F%2Fvault.azure.net&object_id=a5d01234-c512-4bab-95d0-f5a481234567

Could you add the parametr to the Puppet module for using multiple Managed Service Identities?

Example of the hiera.yaml file:

  • name: 'Azure Key Vault Secrets'
    lookup_key: azure_key_vault::lookup
    options:
    vault_name: puppet-kv
    vault_api_version: '2019-09-01'
    metadata_api_version: '2019-08-15'
    managed_identity_object_id: 'a5d01234-c512-4bab-95d0-f5a481234567'

Thanks

Access token never cached

Puppet apply fails with the following error:

Could not retrieve catalog from remote server: Error 500 on SERVER: Server Error: Puppet::Parser::Compiler failed with error RuntimeError: {"error":"invalid_request","error_description":"Temporarily throttled, too many requests"} on node XXXX

This is caused as the access_token is never cached inside lookup.rb, causing this module to hit the rate limit for the instance metadata service.

Environment

Version:
Module version 1.1.0
Puppet Enterprise v2019.8.5

hiera.yaml contents:

---
version: 5

defaults:
  datadir: "data"

hierarchy:
  - name: "Yaml backend"
    data_hash: yaml_data
    paths:
      - "nodes/%{trusted.certname}.yaml"
      - "common.yaml"
  - name: 'Azure Key Vault Secrets'
    lookup_key: azure_key_vault::lookup
    options:
      vault_name: casual-finch-keyvault
      vault_api_version: '2016-10-01'
      metadata_api_version: '2018-02-01'

Troubleshooting

Looking at the Puppet server log we can see the corresponding log entries indicating this is failing in the lookup.rb file:

2021-04-18T15:57:45.856Z ERROR [qtp852575914-224] [puppetserver] Puppet Puppet::Parser::Compiler failed with error RuntimeError: {"error":"invalid_request","error_description":"Temporarily throttled, too many requests"} on node XXXX
2021-04-18T15:57:45.858Z ERROR [qtp852575914-224] [puppetserver] Puppet Server Error: Puppet::Parser::Compiler failed with error RuntimeError: {"error":"invalid_request","error_description":"Temporarily throttled, too many requests"} on
 node XXXX
/etc/puppetlabs/code/environments/production/modules/azure_key_vault/lib/puppet_x/tragiccode/azure.rb:15:in `get_access_token'
/etc/puppetlabs/code/environments/production/modules/azure_key_vault/lib/puppet/functions/azure_key_vault/lookup.rb:24:in `lookup_key'

Adding in some print debug code I was able to confirm that get_access_token was being called multiple times per puppet run:

[root@puppet-test tmp]# grep lookup_key /tmp/kvdebug | wc -l
42
[root@puppet-test tmp]# grep get_access_token /tmp/kvdebug | wc -l
41

Commentary

This is the lookup.rb equivalent of #58, caused due to the Instance Metadata service only allowing 5 requests per second, as per Microsoft documentation.

In general, requests to IMDS are limited to 5 requests per second. Requests exceeding this threshold will be rejected with 429 responses. Requests to the Managed Identity category are limited to 20 requests per second and 5 concurrent requests.

Source: https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service?tabs=windows#rate-limiting

Write better unit tests for the puppet 4 function to reduce regression & increase quality

Right now, we really have no "useful" unit tests for the azure_key_vault::secret puppet 4 function. It would be helpful for someone to configure faking/stubbing/mocking of the http calls performed inside of this function so that we can write unit tests that are actually valuable and can reduce regressions and increase quality of this module in general.

The following tasks should be done.

  1. Research popular ruby mocking libraries for http calls
  2. Find other puppet modules using these mocking libraries in their tests
    • node_manager
  3. Attempt to write unit tests for my function using the patterns identified

Document using the new puppet 6 Deferred data type

Straight from puppetize live 2018.

notify { title:  message => โ€œI am a secret agent.โ€ }

  $secret = Deferred(โ€œazure_key_vault::secretโ€, [โ€˜turbodog-keyvaultโ€™, โ€˜fooโ€™, {
     metadata_api_version => โ€˜2018-02-01โ€™,
    vault_api_version    => โ€˜2016-10-01โ€™,
  }])
  notify { secret: message => $secret }

Debugging output rather chatty

Logging should be changed to Puppet.debug instead of Puppet.info. This will reduce the unwanted and unexpected noise that is happening.

Failed with Bolt

I am trying to use this module with bolt targetting remote node. But this fails with the following error.

valuation Error: Error while evaluating a Function Call, Failed to open TCP connection to 169.254.169.254:80 (Connection refused - connect(2) for "169.254.169.254" port 80) (file: /etc/puppet/site-modules/roles/manifests/mymodule.pp, line: 5, column: 13) on node X.X.X.X

My code is very simple as

$secret = azure_key_vault::secret('myvault', 'PassMain', {
   metadata_api_version => '2018-04-02',
   vault_api_version    => '2016-10-01',
 })
 $rewraped_secret = Sensitive("${secret.unwrap}")
 notice($rewraped_secret)

If i execute the same command using puppet apply that works fine in that remote node. Any suggestions?

Error message: "undefined method `empty?'"

Hi tragicode, I am using your module with Puppet 5.5.8 to extract an SSL keypair from Azure Key vault, and getting this error:
Error: Could not retrieve catalog from remote server: Error 500 on SERVER: Server Error: Evaluation Error: Error while evaluating a Function Call, undefined method `empty?' for #<URI::HTTP:0x1852662a> (file: /etc/puppetlabs/code/environments/keyvault_test/manifests/stnint-web.pp, line: 42, column: 16) on node stnint-web00.xxxxx
The code identified is this:

    content => azure_key_vault::secret('ASC-HFC-KEYVAULT', 'int-www-stxxxxxxx-com-crt', {
      metadata_api_version  => '2018-04-02',
      vault_api_version     => '2016-10-01',
    }),
    ensure  => file,
  }

and this is my hiera.yaml file:

---
version: 5

defaults:
  datadir: data
  data_hash: yaml_data

hierarchy:
  - name: 'Per-node data'
    path: "nodes/%{trusted.certname}.yaml"

  - name: 'Per business unit (silo + tier) data'
    path: "bizunits/%{facts.business_unit}.yaml"

  - name: 'Azure Key Vault Secrets'
    lookup_key: azure_key_vault::lookup
    options:
      vault_name: ASC-HFC-KEYVAULT
      vault_api_version: '2016-10-01'
      metadata_api_version: '2018-04-02'

  - name: 'Common data'
    glob: '*.yaml'

What is causing this? This is an opaque error message, but it seems to be from the secrets function.

Thanks in advance for your assistance.

facing issue my puppet integration with key vault

Hi @TraGicCode

my code is work sucessfully but not able to verify where the secrete is store i am using following code for retrive secrete and i have add notify secrete fuction but its showing error please help me on this.

$secret = azure_key_vault::secret('production-vault', 'important-secret', {
metadata_api_version => '2018-04-02',
vault_api_version => '2016-10-01',
})
notice($secret)

for stdout i am using following code
notify { 'display-secret':
message => $secret.unwrap,
}

i have merge this code with first code but its showing error

can you please give me one example if i want to setup one SQL connection string for particular in puppet code how can i setup this retrive function methon on that code and how it work and how we can integrate this code

Thanks!

Move API versions and default vault name to a configuration file

It is awkward to have to specify the same API version on every function call.

Instead, why not have a configuration file ( such as puppet/azure.yaml ) on the puppetmaster, which can hold default values for the two API versions, and a default vault name, so that these do not have to be passed to every function call?

Getting too many requests when querying the instance metadata service inside azure VM

We are getting this error:

[puppetserver] Puppet Server Error: Evaluation Error: Error while evaluating a Resource Statement, Evaluation Error: Error while evaluating a Function Call, { "error": "Too many requests" }

see full stack trace at the end

It happens when querying the instance metadata service, we are getting error code 429. Reference:
https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service#error

the line that throws the exception:

raise res.body unless res.is_a?(Net::HTTPSuccess)

full stack trace:

2019-12-23T15:15:28.589Z ERROR [qtp586108716-141] [puppetserver] Puppet Server Error: Evaluation Error: Error while evaluating a Resource Statement, Evaluation Error: Error while evaluating a Function Call, { "error": "Too many requests" } (file: /etc/puppetlabs/code/modules/my_module/manifests/my_manifest.pp, line: 39, column: 3) (file: /etc/puppetlabs/code/modules/my_module/manifests/profile/my_profile.pp, line: 10) on node server-01.dom.internal
/etc/puppetlabs/code/environments/integration/modules/azure_key_vault/lib/puppet_x/tragiccode/azure.rb:14:in `get_access_token'
/etc/puppetlabs/code/environments/integration/modules/azure_key_vault/lib/puppet/functions/azure_key_vault/lookup.rb:17:in `lookup_key'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/functions/dispatch.rb:60:in `invoke'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/functions/dispatcher.rb:43:in `block in dispatch'
org/jruby/RubyKernel.java:1180:in `catch'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/functions/dispatcher.rb:42:in `dispatch'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/functions/function.rb:46:in `block in call'
org/jruby/RubyKernel.java:1180:in `catch'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/functions/function.rb:45:in `call'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/lookup/lookup_key_function_provider.rb:53:in `block in lookup_key'
org/jruby/RubyKernel.java:1180:in `catch'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/lookup/lookup_key_function_provider.rb:50:in `lookup_key'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/lookup/lookup_key_function_provider.rb:27:in `invoke_with_location'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/lookup/lookup_key_function_provider.rb:20:in `block in unchecked_key_lookup'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/merge_strategy.rb:219:in `block in lookup'
org/jruby/RubyKernel.java:1180:in `catch'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/merge_strategy.rb:219:in `block in lookup'
org/jruby/RubyArray.java:1801:in `each'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/merge_strategy.rb:219:in `lookup'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/lookup/lookup_key_function_provider.rb:19:in `block in unchecked_key_lookup'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/lookup/invocation.rb:129:in `with'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/lookup/lookup_key_function_provider.rb:18:in `unchecked_key_lookup'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/lookup/configured_data_provider.rb:56:in `block in unchecked_key_lookup'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/merge_strategy.rb:219:in `block in lookup'
org/jruby/RubyKernel.java:1180:in `catch'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/merge_strategy.rb:219:in `block in lookup'
org/jruby/RubyArray.java:1801:in `each'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/merge_strategy.rb:219:in `lookup'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/lookup/configured_data_provider.rb:55:in `block in unchecked_key_lookup'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/lookup/invocation.rb:129:in `with'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/lookup/configured_data_provider.rb:48:in `unchecked_key_lookup'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/lookup/data_provider.rb:26:in `block in key_lookup'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/lookup/invocation.rb:90:in `check'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/lookup/data_provider.rb:26:in `key_lookup'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/lookup/lookup_adapter.rb:150:in `lookup_in_environment'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/lookup/lookup_adapter.rb:325:in `block in do_lookup'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/merge_strategy.rb:219:in `block in lookup'
org/jruby/RubyKernel.java:1180:in `catch'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/merge_strategy.rb:219:in `block in lookup'
org/jruby/RubyArray.java:1801:in `each'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/merge_strategy.rb:219:in `lookup'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/lookup/lookup_adapter.rb:325:in `do_lookup'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/lookup/lookup_adapter.rb:67:in `block in lookup'
org/jruby/RubyKernel.java:1180:in `catch'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/lookup/lookup_adapter.rb:67:in `block in lookup'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/lookup/invocation.rb:129:in `with'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/lookup/lookup_adapter.rb:66:in `block in lookup'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/lookup/lookup_adapter.rb:91:in `convert_result'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/lookup/lookup_adapter.rb:65:in `block in lookup'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/lookup/invocation.rb:75:in `lookup'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/lookup/lookup_adapter.rb:52:in `lookup'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/lookup.rb:76:in `search_and_merge'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/resource/type.rb:357:in `block in inject_external_parameters'
org/jruby/RubyKernel.java:1180:in `catch'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/resource/type.rb:356:in `block in inject_external_parameters'
org/jruby/RubyHash.java:1396:in `each'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/resource/type.rb:352:in `inject_external_parameters'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/resource/type.rb:319:in `set_resource_parameters'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/resource/type.rb:123:in `evaluate_code'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/parser/resource.rb:78:in `block in evaluate'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/util/profiler/around_profiler.rb:58:in `profile'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/util/profiler.rb:51:in `profile'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/parser/resource.rb:70:in `evaluate'
org/jruby/RubyArray.java:1801:in `each'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/parser/compiler.rb:385:in `evaluate_classes'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/functions/include.rb:48:in `include'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/functions/dispatch.rb:60:in `invoke'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/functions/dispatcher.rb:43:in `block in dispatch'
org/jruby/RubyKernel.java:1180:in `catch'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/functions/dispatcher.rb:42:in `dispatch'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/functions/function.rb:46:in `block in call'
org/jruby/RubyKernel.java:1180:in `catch'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/functions/function.rb:45:in `call'
/etc/puppetlabs/code/modules/etoro_windows/manifests/deploy_website.pp:39:in `<eval>'
org/jruby/RubyKernel.java:1037:in `eval'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/evaluator/runtime3_support.rb:305:in `block in call_function'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/util/profiler/around_profiler.rb:58:in `profile'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/util/profiler.rb:51:in `profile'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/evaluator/runtime3_support.rb:303:in `call_function'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/evaluator/evaluator_impl.rb:976:in `call_function_with_block'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/evaluator/evaluator_impl.rb:945:in `eval_CallNamedFunctionExpression'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/visitor.rb:90:in `visit_this_1'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/evaluator/evaluator_impl.rb:81:in `evaluate'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/evaluator/evaluator_impl.rb:660:in `block in eval_BlockExpression'
org/jruby/RubyArray.java:1801:in `each'
org/jruby/RubyEnumerable.java:1048:in `inject'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/evaluator/evaluator_impl.rb:660:in `eval_BlockExpression'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/visitor.rb:90:in `visit_this_1'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/evaluator/evaluator_impl.rb:81:in `evaluate'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/pops/parser/evaluating_parser.rb:63:in `evaluate'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/parser/ast/pops_bridge.rb:32:in `evaluate'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/parser/ast/pops_bridge.rb:72:in `block in evaluate'
org/jruby/RubyKernel.java:1180:in `catch'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/parser/ast/pops_bridge.rb:71:in `block in evaluate'
org/jruby/RubyKernel.java:1180:in `catch'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/parser/ast/pops_bridge.rb:70:in `evaluate'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/parser/ast.rb:30:in `safeevaluate'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/resource/type.rb:136:in `evaluate_code'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/parser/resource.rb:78:in `block in evaluate'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/util/profiler/around_profiler.rb:58:in `profile'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/util/profiler.rb:51:in `profile'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/parser/resource.rb:70:in `evaluate'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/parser/compiler.rb:539:in `block in evaluate_definitions'
org/jruby/RubyArray.java:1801:in `each'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/parser/compiler.rb:537:in `block in evaluate_definitions'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/util/profiler/around_profiler.rb:58:in `profile'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/util/profiler.rb:51:in `profile'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/parser/compiler.rb:536:in `block in evaluate_definitions'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/util/errors.rb:115:in `exceptwrap'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/parser/compiler.rb:535:in `evaluate_definitions'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/parser/compiler.rb:568:in `block in evaluate_generators'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/util/profiler/around_profiler.rb:58:in `profile'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/util/profiler.rb:51:in `profile'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/parser/compiler.rb:565:in `block in evaluate_generators'
org/jruby/RubyKernel.java:1418:in `loop'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/parser/compiler.rb:562:in `evaluate_generators'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/parser/compiler.rb:178:in `block in compile'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/util/profiler/around_profiler.rb:58:in `profile'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/util/profiler.rb:51:in `profile'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/parser/compiler.rb:178:in `block in compile'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/context.rb:65:in `override'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet.rb:252:in `override'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/parser/compiler.rb:154:in `compile'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/parser/compiler.rb:34:in `compile'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/indirector/catalog/compiler.rb:303:in `block in compile'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/util/profiler/around_profiler.rb:58:in `profile'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/util/profiler.rb:51:in `profile'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/indirector/catalog/compiler.rb:301:in `block in compile'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/util.rb:231:in `block in benchmark'
uri:classloader:/META-INF/jruby.home/lib/ruby/stdlib/benchmark.rb:308:in `realtime'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/util.rb:230:in `benchmark'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/indirector/catalog/compiler.rb:299:in `compile'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/indirector/catalog/compiler.rb:54:in `block in find'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/node/environment.rb:433:in `with_text_domain'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/indirector/catalog/compiler.rb:53:in `find'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/indirector/indirection.rb:194:in `find'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/network/http/api/indirected_routes.rb:127:in `do_find'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/network/http/api/indirected_routes.rb:54:in `block in call'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/context.rb:65:in `override'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet.rb:252:in `override'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/network/http/api/indirected_routes.rb:53:in `call'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/network/http/route.rb:82:in `block in process'
org/jruby/RubyArray.java:1801:in `each'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/network/http/route.rb:81:in `process'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/network/http/route.rb:87:in `process'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/network/http/route.rb:87:in `process'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/network/http/handler.rb:87:in `block in process'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/network/http/handler.rb:70:in `block in with_request_profiling'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/util/profiler/around_profiler.rb:58:in `profile'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/util/profiler.rb:51:in `profile'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/network/http/handler.rb:66:in `with_request_profiling'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/network/http/handler.rb:86:in `block in process'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/network/http/handler.rb:93:in `respond_to_errors'
/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/network/http/handler.rb:85:in `process'
uri:classloader:/puppetserver-lib/puppet/server/master.rb:42:in `handleRequest'

Document where to find service api versions

In order to help prevent confusion with the 2 api versions along with helping the user pick the right one the documentation should be updated to discuss how to get the api versions the azure service support.

az cli

az provider show --namespace Microsoft.KeyVault --query "resourceTypes[?resourceType=='vaults'].apiVersions | [0]" --out table

azure powershell

((Get-AzureRmResourceProvider -ProviderNamespace Microsoft.KeyVault).ResourceTypes | Where-Object ResourceTypeName -eq vaults).ApiVersions

Resources:

  • Key Vault API versions

https://docs.microsoft.com/en-us/azure/key-vault/key-vault-versions

  • Instance Metadata Service Versions

https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service#service-availability

  • Other

https://zimmergren.net/developing-with-azure-resource-manager-part-5-tip-get-the-available-api-version-for-the-arm-endpoints/

Update path in to reference.md in readme to be absolute

When you specify a relative path for the reference.md file from the modules readme.md the link will not work correctly when published to the forge. The current recomendation is the specify an absolute path to the file on github like so.

https://github.com/tragiccode/tragiccode-azure_key_vault/blob/master/REFERENCE.md

Example Module doing this:
https://forge.puppet.com/puppetlabs/apt ( which actually points to the puppetlabs-motd module on mistake :( )

SecretNotFound error messages with two KeyVaults

Hi,

Thanks for the module. I am using a latest version of this module.

I have an issue with warning messages regarding SecretNotFound .

I have added two key vaults in hiera.yaml

  • name: 'Subscription Key Vault Secrets'
    lookup_key: azure_key_vault::lookup
    options:
    vault_name: "subscription_test"
    vault_api_version: '2016-10-01'
    metadata_api_version: '2018-04-02'
    key_replacement_token: '-'
    confine_to_keys:
    • "^.*-password$"
    • "^vm-.*"
    • "^svc-.*"
    • "^.*-key$"
  • name: 'Platform Key Vault Secrets'
    lookup_key: azure_key_vault::lookup
    options:
    vault_name: "platform_test"
    vault_api_version: '2016-10-01'
    metadata_api_version: '2018-04-02'
    key_replacement_token: '-'
    confine_to_keys:
    • "^.*-password$"
    • "^vm-.*"
    • "^svc-.*"
    • "^.*-key$"

Calling secrets from hierdata - choco_password: "%{lookup('chocolatey-password')}"

chocolatey-password password is configured in Platform Key Vault Secrets

  • While puppet run, if value is not found in Subscription Key Vault Secret. it is sending below warning message to puppetserver.log
    2022-05-27T11:52:05.758+01:00 WARN [qtp742071835-1131] [puppetserver] Puppet {"error":{"code":"SecretNotFound","message":"A secret with (name/id) domain-join-password was not found in this key vault. If you recently deleted this secret you may be able to recover it using the correct recovery command. For help resolving this issue, please see https://go.microsoft.com/fwlink/?linkid=2125182"}}

Any chance to modify this behaviour, check secrets in both KeyVaults. If not found secrets in both KeyVaults, then need to trigger the warning message.

Thanks,
Hari

Evaluation Error: Unknown function: 'azure_key_vault::secret'.

Hi, we are getting this error (Evaluation Error: Unknown function: 'azure_key_vault::secret'.) when we include the secret function in our code as below:

file { 'C:\temp\test.xml':
ensure => file,
content => azure_key_vault::secret('puppetvault','oursecret', {
metadata_api_version => '2018-02-01',
vault_api_version => '2016-10-01',
}),

we have updated the puppetfile and we have attempted to run it manually by installing the module directly on a puppet agent and using a apply command.

Each time we get the error. We are quite new to puppet so I accept if we have done some simply wrong but we are unable to see why this is not working; given that we have taken the code directly from description of the module.

We have also test with other module functions (such as base64 from the stdlib) and we are able to use these; so we are very confused why the secret function is just not being found by Puppet.

We are using Puppet Agent 5.5.4 on a Windows 2016 box. I have also tested it against a CentOS 7.3 box.

In both cases the module is definitely installed as I can see it in the module directory and I can list it using: puppet module list.

Add tags to metadata.json to help SEO on puppet forge

Currently, this module has no tags in the metadata.json. We should add the following tags in order to help boost the modules ranking to show up higher in search results and show up more often when a tag matches a keyword.

"tags": [
    "azure",
    "vault",
    "azure key vault",
    "azure vault"
  ]

Unwrap through hiera

Hi TraGicCode,

I would like to use this module mostly through hiera. Using alias function works fine when resource is handling Sensitive data type, but I haven't been able to find a way to proceed with those that are not (Special case 2 described in documentation). Do you have a suggestion how to handle this through hiera? Maybe I'm missing something.

Thanks,
Karolina

Add create/update functionality

It would be great to be able to create and update secrets using these functions.

This would allow Puppet to fully deploy a system, storing newly-created credentials into a Keyvault for use elsewhere (a better solution than using exported resources for this purpose)
Puppet could also then be used to manage OS passwords (such as root, oracle, etc) and rotate them when they are too old, storing the new password in the keyvault.

Lookup function not working through hiera

Hi TraGicCode,

Thank you for writing this module. Unfortunately, I haven't been able to make it work through hiera as expected.

Puppet version: 6.26.0

tragiccode-azure_key_vault version: v3.1.0

Section in hiera.yaml hierarchy:

  - name: 'Azure Key Vault Secrets'
    lookup_key: azure_key_vault::lookup
    options:
      vault_name: qahub-key-vault
      vault_api_version: '2016-10-01'
      metadata_api_version: '2018-04-02'
      key_replacement_token: '-'
      confine_to_keys:
        - '^azure.*'
        - '^.*_password$'
        - '^password.*'

This is the code I've been testing:

class infra::akv (
)
{

file { '/opt/akv':
    ensure => 'directory',
}

$password = lookup('azure-secret-password')

file { '/opt/akv/testpass':
  content => $password,
  ensure    => file,
}
}

Used like this it gives the output, as expected:

root@puppetserver:/etc/puppetlabs/code/environments/infra# puppet agent -t
Info: Using environment 'infra'
Info: Retrieving pluginfacts
Info: Retrieving plugin
Info: Retrieving locales
Info: Loading facts
Info: Caching catalog for puppetserver
Info: Applying configuration version '12345....'
Notice: /Stage[main]/Infra::Akv/File[/opt/akv/testpass]/content: [diff redacted]
Info: Computing checksum on file /opt/akv/testpass
Info: /Stage[main]/Infra::Akv/File[/opt/akv/testpass]: Filebucketed /opt/akv/testpass to puppet with sum 5c453af9ce37794e697cefd0392dbcf8
Notice: /Stage[main]/Infra::Akv/File[/opt/akv/testpass]/content: changed [redacted] to [redacted]
Notice: Applied catalog in 21.74 seconds

Also, file is correctly updated.

But, if used through hiera, with editing data/nodes/puppetserver.yaml file with line:

infra::akv::password: "%{lookup('azure-secret-password')}"

With manifest then looking like this:

class infra::akv (
  $password,
)
{

file { '/opt/akv':
    ensure => 'directory',
}

file { '/opt/akv/testpass':
  content => $password,
  ensure    => file,
}
}

Output is:

................
Notice: /Stage[main]/Infra::Akv/File[/opt/akv/testpass]/content:
--- /opt/akv/testpass   2023-01-09 09:41:27.076211584 +0000
+++ /tmp/puppet-file20230109-25695-sr98eh       2023-01-09 10:23:20.661981354 +0000
@@ -1 +1 @@
-this_is_secret
\ No newline at end of file
+Sensitive [value redacted]
\ No newline at end of file

Info: Computing checksum on file /opt/akv/testpass
Info: /Stage[main]/Infra::Akv/File[/opt/akv/testpass]: Filebucketed /opt/akv/testpass to puppet with sum 9486ce60ddb022349d8a9e04788da9e8
Notice: /Stage[main]/Infra::Akv/File[/opt/akv/testpass]/content: content changed '{md5}9486ce60ddb022349d8a9e04788da9e8' to '{md5}5c453af9ce37794e697cefd0392dbcf8'
................

So, nothing is reducted and literally the string "Sensitive [value redacted]" is written in the file.

Can you help me with this?

Feature suggestion: Implement back-off to avoid throttling

Suggest implementing throttling in the KeyVault client to ensure clients deal with being rate limited against the service limits: https://docs.microsoft.com/en-us/azure/key-vault/general/overview-throttling#how-to-throttle-your-app-in-response-to-service-limits

As discussed in #69 - automatic parameter lookup can very quickly generate hundreds of lookups against KeyVault, and larger sites using the azure_key_vault::secret secret may be able to hit this also.

Implementing rate limiting in TragicCode::Azure::get_secret will make this solution more resilient.

Feature suggestion: Automatic parameter lookup - allow list for lookups

Suggest adding an optional allowed_keys to the lookup_options parameters - when specified, this would prevent lookups for anything that does not match entries (either strings or regular expressions) in the list.

Automatic parameter lookup will (by default) look up any variables not found in the hierarchy. Even on relatively small sites, this can mean hundreds or thousands of queries against the KeyVault when using this feature.

As an example, instrumenting azure_key_vault::lookup with the following code:

open('/tmp/lookup_trace', 'a') do |f|
      f.puts "get_access_token called for: " + secret_name
    end

Shows that when generating a catalog for a puppet master running Puppet Enterprise v2019.8.5, this results in 776 lookups against Azure KeyVault.

Azure KeyVault service limits allow a maximum of 2,000 lookups every 10 seconds against a key vault: https://docs.microsoft.com/en-us/azure/key-vault/general/service-limits#secrets-managed-storage-account-keys-and-vault-transactions

Fix typo in readme.md

The readme contain a extra character that should not be there. This got missed during the pull-request review.

image

This should be a simple fix.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.