Skip to content

Commit

Permalink
fix idempotency of policy and user creation (#9)
Browse files Browse the repository at this point in the history
* fix idempotency of policy and user creation
  • Loading branch information
Stromweld authored Mar 4, 2024
1 parent c94b0d1 commit 72cad8f
Show file tree
Hide file tree
Showing 11 changed files with 261 additions and 85 deletions.
28 changes: 20 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,43 @@ jobs:
cookstylelint:
uses: Stromweld/github-workflows/.github/workflows/cookstyle-lint.yml@main

integration-dokken:
integration:
runs-on: ubuntu-latest
strategy:
matrix:
os:
- amazonlinux-2023
- centos-7
- centos-stream-8
- centos-stream-9
- almalinux-8
- almalinux-9
- rockylinux-8
- rockylinux-9
- ubuntu-2004
- ubuntu-2204
suite:
- default
- server
- automate
- supermarket
exclude:
- os: almalinux-9
suite: supermarket
- os: rockylinux-8
suite: server
- os: rockylinux-8
suite: supermarket
- os: rockylinux-9
suite: server
- os: rockylinux-9
suite: supermarket
- os: ubuntu-2204
suite: supermarket
fail-fast: false
steps:
- name: Check out code
uses: actions/checkout@main
- name: Install Vagrant
run: |
sudo apt-get update
sudo apt-get install -y vagrant virtualbox
- name: Install Chef
uses: actionshub/chef-install@main
- name: Test-Kitchen Converge
Expand All @@ -48,7 +62,6 @@ jobs:
action: converge
env:
CHEF_LICENSE: accept-no-persist
KITCHEN_LOCAL_YAML: kitchen.dokken.yml
- name: Test-Kitchen Verify
uses: actionshub/test-kitchen@main
with:
Expand All @@ -57,7 +70,6 @@ jobs:
action: verify
env:
CHEF_LICENSE: accept-no-persist
KITCHEN_LOCAL_YAML: kitchen.dokken.yml

check:
if: always()
Expand All @@ -66,7 +78,7 @@ jobs:
- yamllint
- jsonlint
- cookstylelint
- integration-dokken
- integration
runs-on: Ubuntu-latest
steps:
- name: Decide whether the needed jobs succeeded or failed
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

This file is used to list changes made in each version of the chef_software cookbook.

## 2.2.0 (2024-03-04)

- [Corey Hemminger] - Moved iam_policy and iam_user creation to resources, fixed idempotency in resources

## 2.1.2 (2023-03-31)

- [Corey Hemminger] - Hacky way of adding idempotency to chef-server org admin association
Expand Down
18 changes: 0 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,6 @@ Please refer to the chef-ingredient cookbook <https://github.com/chef-cookbooks/

- Linux

## Attributes

### default attributes

| Attribute | Default | Comment |
| ------------- | ------------- | ------------- |
| ['chef_software']['chef_server_api_fqdn'] | 'chef-server.example.com' | (String) Hostname to connect to chef-server |
| ['chef_software']['chef_automate_api_fqdn'] | 'chef-automate.example.com' | (String) Hostname to connect to chef-automatev2 |
| ['chef_software']['chef_supermarket_api_fqdn'] | 'chef-supermarket.example.com' | (String) Hostname to connect to chef-supermarket |
| ['chef_software']['automate_admin_token'] | nil | (String) Token used for api access by cookbook |
| ['chef_software']['chef_automatev2'] | {accept_license: true, config: <<~EOC [global.v1] fqdn = "#{node['chef_software']['chef_automate_api_fqdn']}" EOC} | (Hash) Used to add configuration options to chef-automatev2 |
| ['chef_software']['chef_automatev2']['local_users'] | {test1:{ full_name: 'Test 1', password: 'Test1234!',},} | (Hash) Hash of hashes definign automatev2 users |
| ['chef_software']['chef_automatev2']['iam_policies'] | {team_ldap: {policy_json: {subjects: ['user:local:test1'], action: '*', resource: '*',},},} | (Hash) Hash of hashes defining automate IAM policies in json format |
| ['chef_software']['chef_server'] | {accept_license: true, addons: {'manage' => {accept_license: true,},}, config: <<~EOC api_fqdn "#{node['chef_software']['chef_server_api_fqdn']}" topology "standalone" #{"data_collector['root_url'] = 'https://#{node['chef_software']['chef_automate_api_fqdn']}/data-collector/v0/' data_collector['proxy'] = true profiles['root_url'] = 'https://#{node['chef_software']['chef_supermarket_api_fqdn']}'" if node['chef_software']['chef_automate_api_fqdn']} #{"oc_id['applications'] ||= {} oc_id['applications']['supermarket'] = {'redirect_uri' => 'https://#{node['chef_software']['chef_supermarket_api_fqdn']}/auth/chef_oauth2/callback',}" if node['chef_software']['chef_supermarket_api_fqdn']} EOC} | (Hash) Used to add configuration options to chef-server |
| ['chef_software']['chef_user'] | {test1: {first_name: 'Test',last_name: '1',email: 'test1@example.com',password: 'Test1234!',},} | (Hash) Hash of hashes used to manage chef-server users |
| ['chef_software']['chef_org'] | {testing: {org_full_name: 'Testing Chef Server', admins: %w(test1), users: %w(),},} | (Hash) Hash of hashes used to manage chef-server organizations |
| ['chef_software']['chef_supermarket'] | {chef_server_url: "https://#{node['chef_software']['chef_server_api_fqdn']}", chef_oauth2_app_id: 'testGUID', chef_oauth2_secret: 'testGUID', chef_oauth2_verify_ssl: false, accept_license: true, config: {fqdn: node['chef_software']['chef_supermarket_api_fqdn'], smtp_address: 'localhost', smtp_port: 25, from_email: 'chef-supermarket.example.com', features: 'tools,gravatar,github,announcement,fieri', fieri_key: 'randomstuff', fieri_supermarket_endpoint: node['chef_software']['chef_supermarket_api_fqdn'],},} | (Hash) Used to add configuration options to chef-supermarket |

## Recipes

### default recipe
Expand Down
34 changes: 18 additions & 16 deletions attributes/default.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@
default['chef_software']['automate_admin_token'] = nil

default['chef_software']['chef_automatev2'] = {
products: %w(automate infra-server builder desktop),
products: %w(automate infra-server builder),
accept_license: true,
config: (<<~EOC
config: <<~EOC,
[global.v1]
fqdn = "#{node['chef_software']['chef_automate_api_fqdn']}"
EOC
),
EOC

}

default['chef_software']['automatev2_local_users'] = {
Expand All @@ -53,24 +53,26 @@
effect: 'ALLOW',
actions: ['*'],
projects: ['*'],
role: 'owner'
}
]
role: 'owner',
},
],
},
},
}

default['chef_software']['chef_server'] = {
accept_license: true,
config: (<<~EOC
api_fqdn "#{node['chef_software']['chef_server_api_fqdn']}"
topology "standalone"
#{"oc_id['applications'] ||= {}
oc_id['applications']['supermarket'] = {
'redirect_uri' => 'https://#{node['chef_software']['chef_supermarket_api_fqdn']}/auth/chef_oauth2/callback'
}" if node['chef_software']['chef_supermarket_api_fqdn']}
EOC
),
config: <<~EOC,
api_fqdn "#{node['chef_software']['chef_server_api_fqdn']}"
topology "standalone"
#{if node['chef_software']['chef_supermarket_api_fqdn']
"oc_id['applications'] ||= {}
oc_id['applications']['supermarket'] = {
'redirect_uri' => 'https://#{node['chef_software']['chef_supermarket_api_fqdn']}/auth/chef_oauth2/callback'
}"
end}
EOC

}

default['chef_software']['chef_user'] = {
Expand Down
8 changes: 5 additions & 3 deletions kitchen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,23 @@ platforms:
- name: ubuntu-22.04

suites:
- name: default
- name: server
named_run_list: 'chef_server'
driver:
customize:
memory: 3328
memory: 4096
verifier:
inspec_tests:
- test/integration/default
- test/integration/chef_server
attributes:
chef_software:
automate_admin_token: mIUYdbBD6U8a9wg3IAbXScEjiXs=
- name: automate
named_run_list: 'chef_automatev2'
driver:
customize:
memory: 3072
memory: 4096
verifier:
inspec_tests:
- test/integration/default
Expand Down
33 changes: 6 additions & 27 deletions libraries/helpers.rb
Original file line number Diff line number Diff line change
@@ -1,36 +1,15 @@
module ChefSoftware
module Helpers
def get_iam_user(user)
Mash.new(JSON.parse(shell_out("curl --insecure -s -H \"api-token: #{node['chef_software']['automate_admin_token']}\" https://localhost/apis/iam/v2/users/#{user}").stdout))
def get_iam_user(user, token)
Mash.new(JSON.parse(shell_out("curl --insecure -s -H \"api-token: #{token}\" https://localhost/apis/iam/v2/users/#{user}").stdout))
end

def get_iam_policy(policy_name)
Mash.new(JSON.parse(shell_out("curl --insecure -s -H \"api-token: #{node['chef_software']['automate_admin_token']}\" https://localhost/apis/iam/v2/policies/#{policy_name}").stdout))
def get_iam_policy(policy_name, token)
Mash.new(JSON.parse(shell_out("curl --insecure -s -H \"api-token: #{token}\" https://localhost/apis/iam/v2/policies/#{policy_name}").stdout))
end

def create_iam_user(user_json)
json = user_json.to_json
execute "create local user #{user_json['id']}" do
command "curl --insecure -s -H \"api-token: #{node['chef_software']['automate_admin_token']}\" -H \"Content-Type: application/json\" -d '#{json}' https://localhost/apis/iam/v2/users"
not_if { user_json['id'].eql?(get_iam_user(user_json['id'])['user']['id']) }
#sensitive true
end
end

def create_iam_policy(policy_json)
json = policy_json.to_json
test_policy_statements = get_iam_policy(policy_json['id'])['policy']['statements']&.each do |state|
state.delete('resources')
end
execute "generate iam policy #{policy_json['id']}" do
command "curl --insecure -s -H \"api-token: #{node['chef_software']['automate_admin_token']}\" -H \"Content-Type: application/json\" -d '#{json}' https://localhost/apis/iam/v2/policies"
not_if {
policy_json['id'].eql?(get_iam_policy(policy_json['id'])['policy']['id']) &&
policy_json['members'].eql?(get_iam_policy(policy_json['id'])['policy']['members']) &&
policy_json['statements'].eql?(test_policy_statements)
}
#sensitive true
end
def kitchen_create_api_token(name)
shell_out("chef-automate iam token create #{name} --admin").stdout.strip
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion metadata.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
maintainer_email 'hemminger@hotmail.com'
license 'Apache-2.0'
description 'Installs/Configures chef server, chef automate2, chef supermarket'
version '2.1.2'
version '2.2.0'
chef_version '>= 16.4'

issues_url 'https://github.com/Stromweld/chef_software/issues'
Expand Down
24 changes: 19 additions & 5 deletions recipes/chef_automatev2.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,27 @@
end
end

if node['chef_software']['automate_admin_token']
node['chef_software']['automatev2_local_users']&.each do |name, hash|
create_iam_user(hash['user_json'])
if kitchen?
ruby_block 'create_automate_admin_token' do
block do
node.run_state['automate_admin_token'] = kitchen_create_api_token('admin')
end
end
end

node['chef_software']['automatev2_local_users']&.each do |name, hash|
iam_user name do
user_hash hash['user_json']
api_token lazy { kitchen? ? node.run_state['automate_admin_token'] : node['chef_software']['automate_admin_token'] }
action :create
end
end

node['chef_software']['automatev2_iam_policies']&.each do |name, hash|
create_iam_policy(hash['policy_json'])
node['chef_software']['automatev2_iam_policies']&.each do |name, hash|
iam_policy name do
policy_hash hash['policy_json']
api_token lazy { kitchen? ? node.run_state['automate_admin_token'] : node['chef_software']['automate_admin_token'] }
action :create
end
end

Expand Down
100 changes: 100 additions & 0 deletions resources/iam_policy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# To learn more about Custom Resources, see https://docs.chef.io/custom_resources.html
#
# Author:: Corey Hemminger
# Cookbook:: chef_software
# Resource:: iam_policy
#
# Copyright:: 2024, Corey Hemminger
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
unified_mode true
provides :iam_policy

description 'manage IAM policies'

property :name, String,
name_property: true,
description: 'Name of the IAM policy'

property :policy_hash, Hash,
required: true,
description: 'Policy json in ruby hash format'

property :api_token, String,
required: true,
sensitive: true,
description: 'Automate API token'

action :create do
description 'Create a new IAM user'

name = new_resource.name
policy_hash = new_resource.policy_hash
policy_json = policy_hash.to_json
api_token = new_resource.api_token
# Try to fetch policy from server
srv_policy = get_iam_policy(policy_hash['id'], api_token)
# Test if policy on server exists and any errors contacting server
test_result = if srv_policy['error'].eql?("no policy with ID \"#{policy_hash['id']}\" found")
true
elsif srv_policy['error']
raise srv_policy['error'].inspect
elsif srv_policy['policy']['id'].eql?(policy_hash['id'])
false
else
raise "Unable to determine status of policy ensure this policy_hash id doesn't match an existing srv_policy\npolicy_hash: #{policy_hash['id'].inspect}\nsrv_policy: #{srv_policy['id'].inspect}\nor the error message from server says \"no policy with ID \"#{policy_hash['id']}\" found\"\nError_msg: #{srv_policy['error'].inspect}\n"
end
execute "create iam policy #{name}" do
command "curl --insecure -s -H \"api-token: #{api_token}\" -H \"Content-Type: application/json\" -d '#{policy_json}' https://localhost/apis/iam/v2/policies"
only_if { test_result }
sensitive true
end
end

action :update do
name = new_resource.name
policy_hash = new_resource.policy_hash
policy_json = policy_hash.to_json
api_token = new_resource.api_token
# Try to fetch policy from server
srv_policy = get_iam_policy(policy_json['id'], api_token)
Chef::Log.info("\nuserpolicy: #{policy_json.inspect}\nsrv_policy: #{srv_policy.inspect}\n")
# Test policy from server and desired policy match key by key from desired policy
test_result = if srv_policy['error']
raise srv_policy['error'].inspect
else
test = true
policy_hash.each_key do |key|
if key.eql?('statements')
policy_hash['statements'].each_index do |i|
policy_hash['statements'][i].each_key do |statement_key|
test = policy_hash['statements'][i][statement_key].eql?(srv_policy['policy']['statements'][i][statement_key])
break if test.eql?(false)
end
break if test.eql?(false)
end
break if test.eql?(false)
next
end
break if test.eql?(false)
test = policy_hash[key].eql?(srv_policy['policy'][key])
break if test.eql?(false)
end
test
end
execute "update iam policy #{name}" do
command "curl -X PUT --insecure -s -H \"api-token: #{api_token}\" -H \"Content-Type: application/json\" -d '#{policy_json}' https://localhost/apis/iam/v2/policies/#{policy_hash['id']}"
not_if { test_result }
sensitive true
end
end
Loading

0 comments on commit 72cad8f

Please sign in to comment.