Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 51 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
* [Manual Lookups](#manual-lookups)
1. [Configuration Options](#configuration-options)
* [API Versions](#api-versions)
* [cloud_type](#cloud_type)
* [confine_to_keys](#confine_to_keys)
* [key_replacement_token](#key_replacement_token)
* [strip_from_keys](#strip_from_keys)
Expand All @@ -33,7 +34,8 @@
* [Retrieving a Specific Version](#retrieving-a-specific-version)
* [Retrieving a Certificate](#retrieving-a-certificate)
1. [Reference](#reference)
1. [Development and Contributing](#development-and-contributing)
1. [Contributors](#contributors)
1. [Contributing](#contributing)

## Description

Expand Down Expand Up @@ -215,6 +217,45 @@ The `vault_api_version` and `metadata_api_version` parameters pin the Azure APIs
* **Instance Metadata Service Versions**: [Azure documentation](https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service)
* **Vault API Versions**: Check the Azure Key Vault documentation for available versions

### cloud_type

The `cloud_type` parameter specifies which Azure cloud environment should be used when communicating with Azure Key Vault and Azure authentication services.

By default, the module uses Azure's public cloud.

Supported values:

| Value | Description |
|---------|-------------|
| `AzureCloud` | Azure public cloud (default) |
| `AzureUSGovernment` | Azure US Government cloud |
| `AzureChinaCloud` | Azure China cloud |

**Examples:**

```yaml
- name: 'Azure Key Vault Secrets'
lookup_key: azure_key_vault::lookup
options:
vault_name: "prod-key-vault"
vault_api_version: '2016-10-01'
metadata_api_version: '2018-04-02'
cloud_type: AzureUSGovernment # AzureCloud (Default when not specified), AzureChinaCloud, or AzureUSGovernment
# Other options omitted for brevity
```

When using the Puppet function directly:

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

This setting controls the Azure Key Vault and authentication endpoints used by the module. Most users should leave this value set to the default (`AzureCloud`).

### confine_to_keys

By design, Hiera traverses the configured hierarchy for each key until one is found. This can result in many web requests to Azure Key Vault. The `confine_to_keys` option improves performance and prevents rate limiting (Azure Key Vault allows 2,000 lookups every 10 seconds per vault) by specifying regular expressions that determine when to query Key Vault.
Expand Down Expand Up @@ -449,4 +490,12 @@ See [REFERENCE.md](https://github.com/tragiccode/tragiccode-azure_key_vault/blob
<a href="https://github.com/TraGicCode/tragiccode-azure_key_vault/graphs/contributors">
<img src="https://contrib.rocks/image?repo=TraGicCode/tragiccode-azure_key_vault" />
</a>
<br/><br/>
<br/><br/>

## Contributing

1. Fork it ( <https://github.com/tragiccode/busly-cli/fork> )
1. Create your feature branch (`git checkout -b my-new-feature`)
1. Commit your changes (`git commit -am 'Add some feature'`)
1. Push to the branch (`git push origin my-new-feature`)
1. Create a new Pull Request
4 changes: 4 additions & 0 deletions REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ The azure_key_vault::lookup function.
vault_name => String,
vault_api_version => String,
Optional[metadata_api_version] => String,
Optional[cloud_type] => Enum["AzureCloud", "AzureUSGovernment", "AzureChinaCloud"],
confine_to_keys => Array[String],
Optional[strip_from_keys] => Array[String],
Optional[key_replacement_token] => String,
Expand Down Expand Up @@ -48,6 +49,7 @@ Struct[{
vault_name => String,
vault_api_version => String,
Optional[metadata_api_version] => String,
Optional[cloud_type] => Enum["AzureCloud", "AzureUSGovernment", "AzureChinaCloud"],
confine_to_keys => Array[String],
Optional[strip_from_keys] => Array[String],
Optional[key_replacement_token] => String,
Expand All @@ -74,6 +76,7 @@ Retrieves secrets from Azure's Key Vault.
#### `azure_key_vault::secret(String $vault_name, String $secret_name, Struct[{
vault_api_version => String,
Optional[metadata_api_version] => String,
Optional[cloud_type] => Enum["AzureCloud", "AzureUSGovernment", "AzureChinaCloud"],
Optional[service_principal_credentials] => Struct[{
tenant_id => String,
client_id => String,
Expand Down Expand Up @@ -106,6 +109,7 @@ Data type:
Struct[{
vault_api_version => String,
Optional[metadata_api_version] => String,
Optional[cloud_type] => Enum["AzureCloud", "AzureUSGovernment", "AzureChinaCloud"],
Optional[service_principal_credentials] => Struct[{
tenant_id => String,
client_id => String,
Expand Down
11 changes: 8 additions & 3 deletions lib/puppet/functions/azure_key_vault/lookup.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
vault_name => String,
vault_api_version => String,
Optional[metadata_api_version] => String,
Optional[cloud_type] => Enum["AzureCloud", "AzureUSGovernment", "AzureChinaCloud"],
confine_to_keys => Array[String],
Optional[strip_from_keys] => Array[String],
Optional[key_replacement_token] => String,
Expand All @@ -22,6 +23,8 @@ def lookup_key(secret_name, options, context)
# This is a reserved key name in hiera
return context.not_found if secret_name == 'lookup_options'

cloud_type = options['cloud_type'] || 'AzureCloud'

confine_keys = options['confine_to_keys']
if confine_keys
raise ArgumentError, 'confine_to_keys must be an array' unless confine_keys.is_a?(Array)
Expand Down Expand Up @@ -85,11 +88,11 @@ def lookup_key(secret_name, options, context)

if service_principal_credentials
credentials = YAML.load_file(service_principal_credentials)
access_token = TragicCode::Azure.get_access_token_service_principal(credentials)
access_token = TragicCode::Azure.get_access_token_service_principal(credentials, cloud_type)
elsif use_azure_arc_authentication
access_token = TragicCode::Azure.get_access_token_azure_arc(metadata_api_version)
access_token = TragicCode::Azure.get_access_token_azure_arc(metadata_api_version, cloud_type)
else
access_token = TragicCode::Azure.get_access_token(metadata_api_version)
access_token = TragicCode::Azure.get_access_token(metadata_api_version, cloud_type)
end
context.cache('access_token', access_token)
end
Expand All @@ -103,6 +106,7 @@ def lookup_key(secret_name, options, context)
options['vault_api_version'],
access_token,
'',
cloud_type,
)
break unless secret_value.nil?
end
Expand All @@ -113,6 +117,7 @@ def lookup_key(secret_name, options, context)
options['vault_api_version'],
access_token,
'',
cloud_type,
)
end
rescue RuntimeError => e
Expand Down
10 changes: 7 additions & 3 deletions lib/puppet/functions/azure_key_vault/secret.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
param 'Struct[{
vault_api_version => String,
Optional[metadata_api_version] => String,
Optional[cloud_type] => Enum["AzureCloud", "AzureUSGovernment", "AzureChinaCloud"],
Optional[service_principal_credentials] => Struct[{
tenant_id => String,
client_id => String,
Expand All @@ -26,10 +27,12 @@
end

def secret(cache, vault_name, secret_name, api_endpoint_hash, secret_version = '')
cloud_type = api_endpoint_hash['cloud_type'] || 'AzureCloud'
Puppet.debug("vault_name: #{vault_name}")
Puppet.debug("secret_name: #{secret_name}")
Puppet.debug("secret_version: #{secret_version}")
Puppet.debug("metadata_api_version: #{api_endpoint_hash['metadata_api_version']}")
Puppet.debug("cloud_type: #{cloud_type}")
Puppet.debug("vault_api_version: #{api_endpoint_hash['vault_api_version']}")
if api_endpoint_hash['service_principal_credentials']
partial_credentials = api_endpoint_hash['service_principal_credentials'].slice('tenant_id', 'client_id')
Expand Down Expand Up @@ -63,11 +66,11 @@ def secret(cache, vault_name, secret_name, api_endpoint_hash, secret_version = '
end

access_token = if service_principal_credentials
TragicCode::Azure.get_access_token_service_principal(service_principal_credentials)
TragicCode::Azure.get_access_token_service_principal(service_principal_credentials, cloud_type)
elsif use_azure_arc_authentication
TragicCode::Azure.get_access_token_azure_arc(metadata_api_version)
TragicCode::Azure.get_access_token_azure_arc(metadata_api_version, cloud_type)
else
TragicCode::Azure.get_access_token(metadata_api_version)
TragicCode::Azure.get_access_token(metadata_api_version, cloud_type)
end
cache_hash[access_token_id] = access_token
end
Expand All @@ -78,6 +81,7 @@ def secret(cache, vault_name, secret_name, api_endpoint_hash, secret_version = '
api_endpoint_hash['vault_api_version'],
cache_hash[access_token_id],
secret_version,
cloud_type,
)

raise Puppet::Error, "The secret named #{secret_name} could not be found in a vault named #{vault_name}" if secret_value.nil?
Expand Down
46 changes: 34 additions & 12 deletions lib/puppet_x/tragiccode/azure.rb
Original file line number Diff line number Diff line change
@@ -1,18 +1,37 @@
require 'net/http'
require 'json'
require 'uri'

module TragicCode
# Azure API functions
class Azure
@azure_arc_instance_metadata_endpoint_ip = '127.0.0.1'.freeze

def self.cloud_environments
{
'AzureCloud' => {
'vault_dns_suffix' => 'vault.azure.net',
'login_endpoint' => 'login.microsoftonline.com',
},
'AzureUSGovernment' => {
'vault_dns_suffix' => 'vault.usgovcloudapi.net',
'login_endpoint' => 'login.microsoftonline.us',
},
'AzureChinaCloud' => {
'vault_dns_suffix' => 'vault.azure.cn',
'login_endpoint' => 'login.chinacloudapi.cn',
},
}
end

def self.normalize_object_name(object_name, replacement)
object_name.gsub(%r{[^0-9a-zA-Z-]}, replacement)
end

def self.get_access_token_azure_arc(api_version)
def self.get_access_token_azure_arc(api_version, cloud_type = 'AzureCloud')
# Generate File and Read Challenge Token
uri = URI("http://#{@azure_arc_instance_metadata_endpoint_ip}/metadata/identity/oauth2/token?api-version=#{api_version}&resource=https%3A%2F%2Fvault.azure.net")
vault_resource = "https://#{cloud_environments.fetch(cloud_type, cloud_environments['AzureCloud'])['vault_dns_suffix']}"
uri = URI("http://#{@azure_arc_instance_metadata_endpoint_ip}/metadata/identity/oauth2/token?api-version=#{api_version}&resource=#{URI.encode_www_form_component(vault_resource)}")
req = Net::HTTP::Get.new(uri.request_uri)
req['Metadata'] = 'true'
res = Net::HTTP.start(uri.hostname, uri.port) do |http|
Expand All @@ -27,15 +46,16 @@ def self.get_access_token_azure_arc(api_version)
challenge_token = File.read(challenge_token_file_path)

# Get Access Token using challenge token
internal_get_access_token(api_version, @azure_arc_instance_metadata_endpoint_ip, { 'Authorization' => "Basic #{challenge_token}" })
internal_get_access_token(api_version, @azure_arc_instance_metadata_endpoint_ip, { 'Authorization' => "Basic #{challenge_token}" }, cloud_type)
end

def self.get_access_token(api_version)
internal_get_access_token(api_version, '169.254.169.254')
def self.get_access_token(api_version, cloud_type = 'AzureCloud')
internal_get_access_token(api_version, '169.254.169.254', {}, cloud_type)
end

def self.internal_get_access_token(api_version, instance_metadata_service_endpoint = '169.254.169.254', extra_http_headers_hash = {})
uri = URI("http://#{instance_metadata_service_endpoint}/metadata/identity/oauth2/token?api-version=#{api_version}&resource=https%3A%2F%2Fvault.azure.net")
def self.internal_get_access_token(api_version, instance_metadata_service_endpoint = '169.254.169.254', extra_http_headers_hash = {}, cloud_type = 'AzureCloud')
vault_resource = "https://#{cloud_environments.fetch(cloud_type, cloud_environments['AzureCloud'])['vault_dns_suffix']}"
uri = URI("http://#{instance_metadata_service_endpoint}/metadata/identity/oauth2/token?api-version=#{api_version}&resource=#{URI.encode_www_form_component(vault_resource)}")
req = Net::HTTP::Get.new(uri.request_uri)
req['Metadata'] = 'true'
extra_http_headers_hash.each do |key, value|
Expand All @@ -48,13 +68,14 @@ def self.internal_get_access_token(api_version, instance_metadata_service_endpoi
JSON.parse(res.body)['access_token']
end

def self.get_access_token_service_principal(credentials)
uri = URI("https://login.microsoftonline.com/#{credentials.fetch('tenant_id')}/oauth2/v2.0/token")
def self.get_access_token_service_principal(credentials, cloud_type = 'AzureCloud')
cloud_config = cloud_environments.fetch(cloud_type, cloud_environments['AzureCloud'])
uri = URI("https://#{cloud_config['login_endpoint']}/#{credentials.fetch('tenant_id')}/oauth2/v2.0/token")
data = {
'grant_type': 'client_credentials',
'client_id': credentials.fetch('client_id'),
'client_secret': credentials.fetch('client_secret'),
'scope': 'https://vault.azure.net/.default'
'scope': "https://#{cloud_config['vault_dns_suffix']}/.default"
}
req = Net::HTTP::Post.new(uri.request_uri)
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
Expand All @@ -64,9 +85,10 @@ def self.get_access_token_service_principal(credentials)
JSON.parse(res.body)['access_token']
end

def self.get_secret(vault_name, secret_name, vault_api_version, access_token, secret_version)
def self.get_secret(vault_name, secret_name, vault_api_version, access_token, secret_version, cloud_type = 'AzureCloud')
vault_dns_suffix = cloud_environments.fetch(cloud_type, cloud_environments['AzureCloud'])['vault_dns_suffix']
version_parameter = secret_version.empty? ? secret_version : "/#{secret_version}"
uri = URI("https://#{vault_name}.vault.azure.net/secrets/#{secret_name}#{version_parameter}?api-version=#{vault_api_version}")
uri = URI("https://#{vault_name}.#{vault_dns_suffix}/secrets/#{secret_name}#{version_parameter}?api-version=#{vault_api_version}")
req = Net::HTTP::Get.new(uri.request_uri)
req['Authorization'] = "Bearer #{access_token}"
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
Expand Down
Loading
Loading