Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow multiple API servers/URLs with token #167

Merged
merged 1 commit into from
Apr 2, 2024
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
62 changes: 50 additions & 12 deletions REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@

* [`vas::api_fetch`](#vas--api_fetch): Query a remote HTTP-based service for entries to be added to users_allow.

### Data types

* [`Vas::API::Config`](#Vas--API--Config): API configuration

## Classes

### <a name="vas"></a>`vas`
Expand Down Expand Up @@ -167,6 +171,7 @@ The following parameters are available in the `vas` class:
* [`api_users_allow_url`](#-vas--api_users_allow_url)
* [`api_token`](#-vas--api_token)
* [`api_ssl_verify`](#-vas--api_ssl_verify)
* [`api_config`](#-vas--api_config)

##### <a name="-vas--manage_nis"></a>`manage_nis`

Expand Down Expand Up @@ -1186,6 +1191,7 @@ Default value: `false`
Data type: `Optional[Stdlib::HTTPSUrl]`

The URL towards the API.
Deprecated parameter, replaced by $api_config. Will be removed next major releaase.

Default value: `undef`

Expand All @@ -1194,6 +1200,7 @@ Default value: `undef`
Data type: `Optional[String[1]]`

Security token for authenticated access to the API.
Deprecated parameter, replaced by $api_config. Will be removed next major releaase.

Default value: `undef`

Expand All @@ -1202,9 +1209,18 @@ Default value: `undef`
Data type: `Boolean`

Whether TLS connections should be verified or not.
Deprecated parameter, replaced by $api_config. Will be removed next major releaase

Default value: `false`

##### <a name="-vas--api_config"></a>`api_config`

Data type: `Optional[Vas::API::Config]`

API configuration

Default value: `undef`

## Functions

### <a name="vas--api_fetch"></a>`vas::api_fetch`
Expand All @@ -1218,10 +1234,19 @@ Query a remote HTTP-based service for entries to be added to users_allow.
##### Calling the function

```puppet
vas::api_fetch("https://host.domain.tld/api/${facts['trusted.certname']}")
vas::api_fetch([{'url' => "https://host.domain.tld/api/${facts['trusted.certname']}"}])
```

##### Multiple servers with different tokens, ssl_verify enabled

```puppet
vas::api_fetch([
{'url' => "https://host1.domain.tld/api/${facts['trusted.certname']}", 'token' => 'token123', 'ssl_verify' => true},
{'url' => "https://host2.domain.tld/api/${facts['trusted.certname']}", 'token' => 'token321', 'ssl_verify' => true},
])
```

#### `vas::api_fetch(Stdlib::HTTPUrl $url, String[1] $token, Optional[Boolean] $ssl_verify)`
#### `vas::api_fetch(Vas::API::Config $config)`

Query a remote HTTP-based service for entries to be added to users_allow.

Expand All @@ -1232,24 +1257,37 @@ Returns: `Hash` Key 'content' with [Array] if API responds. Key 'errors' with [A
###### Calling the function

```puppet
vas::api_fetch("https://host.domain.tld/api/${facts['trusted.certname']}")
vas::api_fetch([{'url' => "https://host.domain.tld/api/${facts['trusted.certname']}"}])
```

##### `url`
###### Multiple servers with different tokens, ssl_verify enabled

Data type: `Stdlib::HTTPUrl`
```puppet
vas::api_fetch([
{'url' => "https://host1.domain.tld/api/${facts['trusted.certname']}", 'token' => 'token123', 'ssl_verify' => true},
{'url' => "https://host2.domain.tld/api/${facts['trusted.certname']}", 'token' => 'token321', 'ssl_verify' => true},
])
```

URL to connect to
##### `config`

##### `token`
Data type: `Vas::API::Config`

Data type: `String[1]`
Hash with API configuration

Token used for authentication
## Data types

##### `ssl_verify`
### <a name="Vas--API--Config"></a>`Vas::API::Config`

Data type: `Optional[Boolean]`
API configuration

Alias of

Whether TLS connections should be verified or not
```puppet
Array[Struct[
url => Stdlib::HttpsUrl,
token => Optional[String[1]],
ssl_verify => Optional[Boolean],
]]
```

78 changes: 44 additions & 34 deletions lib/puppet/functions/vas/api_fetch.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,52 +3,62 @@
require 'net/http'
require 'net/https'
require 'openssl'
# @param url URL to connect to
# @param token Token used for authentication
# @param ssl_verify Whether TLS connections should be verified or not
# @param config Hash with API configuration
# @return [Hash] Key 'content' with [Array] if API responds. Key 'errors' with [Array[String]] if errors happens.
# @example Calling the function
# vas::api_fetch("https://host.domain.tld/api/${facts['trusted.certname']}")
# vas::api_fetch([{'url' => "https://host.domain.tld/api/${facts['trusted.certname']}"}])
# @example Multiple servers with different tokens, ssl_verify enabled
# vas::api_fetch([
# {'url' => "https://host1.domain.tld/api/${facts['trusted.certname']}", 'token' => 'token123', 'ssl_verify' => true},
# {'url' => "https://host2.domain.tld/api/${facts['trusted.certname']}", 'token' => 'token321', 'ssl_verify' => true},
# ])
#
dispatch :api_fetch do
param 'Stdlib::HTTPUrl', :url
param 'String[1]', :token
optional_param 'Boolean', :ssl_verify
param 'Vas::API::Config', :config
return_type 'Hash'
end

def api_fetch(url, token, ssl_verify = false)
uri = URI.parse(url)
def api_fetch(config)
data = {}

req = Net::HTTP::Get.new(uri.to_s)
req['Authorization'] = "Bearer #{token}"
req['Accept'] = 'text/plain'
config.shuffle.each do |entry|
url = entry['url']
uri = URI.parse(url)

https = Net::HTTP.new(uri.host, uri.port)
https.use_ssl = true
unless ssl_verify
https.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
https.open_timeout = 2
https.read_timeout = 2
req = Net::HTTP::Get.new(uri.to_s)
req['Authorization'] = "Bearer #{entry['token']}" if entry.key?('token')
req['Accept'] = 'text/plain'

data = {}
begin
response = https.start do |cx|
cx.request(req)
https = Net::HTTP.new(uri.host, uri.port)
https.use_ssl = true
# Set SSL::VERIFY_NONE if key ssl_verify is not present or if set to false
# Should be true by default in next major release
if !entry.key?('ssl_verify') || !entry['ssl_verify']
https.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
https.open_timeout = 2
https.read_timeout = 2

begin
response = https.start do |cx|
cx.request(req)
end

case response
when Net::HTTPSuccess
data['content'] = if response.body.empty?
[]
else
response.body.split("\n")
end
else
(data['errors'] ||= []) << "#{url} returns HTTP code: #{response.code}"
case response
when Net::HTTPSuccess
data['content'] = if response.body.empty?
[]
else
response.body.split("\n")
end
# Successful response received, break loop
break
else
(data['errors'] ||= []) << "#{url} returns HTTP code: #{response.code}"
end
rescue => error
(data['errors'] ||= []) << "#{url} connection failed: #{error.message}"
end
rescue => error
(data['errors'] ||= []) << "#{url} connection failed: #{error.message}"
end

data
Expand Down
27 changes: 23 additions & 4 deletions manifests/init.pp
Original file line number Diff line number Diff line change
Expand Up @@ -465,12 +465,18 @@
#
# @param api_users_allow_url
# The URL towards the API.
# Deprecated parameter, replaced by $api_config. Will be removed next major releaase.
#
# @param api_token
# Security token for authenticated access to the API.
# Deprecated parameter, replaced by $api_config. Will be removed next major releaase.
#
# @param api_ssl_verify
# Whether TLS connections should be verified or not.
# Deprecated parameter, replaced by $api_config. Will be removed next major releaase
#
# @param api_config
# API configuration
class vas (
Boolean $manage_nis = true,
String[1] $package_version = 'installed',
Expand Down Expand Up @@ -587,6 +593,7 @@
Array[String[1]] $kpasswd_servers = [],
Stdlib::Port $kpasswd_server_port = 464,
Boolean $api_enable = false,
Optional[Vas::API::Config] $api_config = undef,
Optional[Stdlib::HTTPSUrl] $api_users_allow_url = undef,
Optional[String[1]] $api_token = undef,
Boolean $api_ssl_verify = false,
Expand Down Expand Up @@ -673,10 +680,22 @@
}

# functionality
if $api_enable == true and ($api_users_allow_url == undef or $api_token == undef) {
fail('vas::api_enable is set to true but required parameters vas::api_users_allow_url and/or vas::api_token missing')
} elsif $api_enable == true {
$api_users_allow_data = vas::api_fetch($api_users_allow_url, $api_token, $api_ssl_verify)
if $api_enable == true {
if $api_config {
$api_config_real = $api_config
} elsif $api_users_allow_url and $api_token {
warning('$api_users_allow_url and $api_token deprecated and will be removed next major release. Use $api_config')

$api_config_real = [{
'url' => $api_users_allow_url,
'token' => $api_token,
'ssl_verify' => $api_ssl_verify,
}]
} else {
fail('vas::api_enable is set to true but required parameter $api_config missing')
}

$api_users_allow_data = vas::api_fetch($api_config_real)

if $api_users_allow_data['content'] {
$manage_users_allow = true
Expand Down
21 changes: 10 additions & 11 deletions spec/classes/data_types_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,6 @@
],
}

headers = {
'Accept' => 'text/plain',
'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
'Authorization' => 'Bearer somesecret',
'User-Agent' => 'Ruby'
}

on_supported_os(test_on).each do |_os, os_facts|
describe 'variable data type and content validations' do
let(:node) { 'data-types.example.com' }
Expand Down Expand Up @@ -49,7 +42,7 @@
},
'Boolean/API' => {
name: ['api_enable'],
params: { api_users_allow_url: 'https://api.example.local', api_token: 'somesecret', },
params: { api_config: [{ 'url': 'https://api.example.local', 'token': 'somesecret' }] },
valid: [true, false],
invalid: ['true', 'false', ['array'], { 'ha' => 'sh' }, 3, 2.42, nil],
message: 'expects a Boolean',
Expand Down Expand Up @@ -172,7 +165,15 @@
invalid: ['true', 'false', 'string', ['array'], { 'ha' => 'sh' }, 3, 2.42],
message: 'expects a value of type Boolean or Enum',
},

'Vas::API::Config' => {
name: ['api_config'],
params: { api_enable: true },
valid: [[{ 'url': 'https://test.ing' }],
[{ 'url': 'https://test.ing', 'token': 'mysecret', 'ssl_verify': true }],
[{ 'url': 'https://test.ing', 'token': 'mysecret', 'ssl_verify': true }, { 'url': 'https://test.ing' }]],
invalid: ['http://str.ing', 'string', ['array'], { 'ha' => 'sh' }, 3, 2.42, false],
message: "(parameter 'api_config' expects a Vas::API::Config|parameter 'api_config' index 0 expects a Struct value)",
},
}
validations.sort.each do |type, var|
mandatory_params = {} if mandatory_params.nil?
Expand All @@ -184,10 +185,8 @@

it do
stub_request(:get, 'https://test.ing/')
.with(headers: headers)

stub_request(:get, 'https://api.example.local')
.with(headers: headers)

is_expected.to compile
end
Expand Down
Loading