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

Move knife-acl gem commands into chef in their own namespaces #9292

Merged
merged 1 commit into from Jan 30, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 0 additions & 2 deletions lib/chef/knife.rb
Expand Up @@ -279,8 +279,6 @@ def subcommand_not_found!(args)

if CHEF_ORGANIZATION_MANAGEMENT.include?(args[0])
list_commands("CHEF ORGANIZATION MANAGEMENT")
elsif OPSCODE_HOSTED_CHEF_ACCESS_CONTROL.include?(args[0])
list_commands("OPSCODE HOSTED CHEF ACCESS CONTROL")
elsif category_commands = guess_category(args)
list_commands(category_commands)
elsif OFFICIAL_PLUGINS.include?(args[0]) # command was an uninstalled official chef knife plugin
Expand Down
57 changes: 57 additions & 0 deletions lib/chef/knife/acl_add.rb
@@ -0,0 +1,57 @@
#
# Author:: Steven Danna (steve@chef.io)
# Author:: Jeremiah Snapp (jeremiah@chef.io)
# Copyright:: Copyright 2011-2020 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# 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.
#

require_relative "../knife"

class Chef
class Knife
class AclAdd < Chef::Knife
category "acl"
banner "knife acl add MEMBER_TYPE MEMBER_NAME OBJECT_TYPE OBJECT_NAME PERMS"

deps do
require_relative "acl_base"
include Chef::Knife::AclBase
end

def run
member_type, member_name, object_type, object_name, perms = name_args

if name_args.length != 5
show_usage
ui.fatal "You must specify the member type [client|group], member name, object type, object name and perms"
exit 1
end

unless %w{client group}.include?(member_type)
ui.fatal "ERROR: To enforce best practice, knife-acl can only add a client or a group to an ACL."
ui.fatal " See the knife-acl README for more information."
exit 1
end
validate_perm_type!(perms)
validate_member_name!(member_name)
validate_object_name!(object_name)
validate_object_type!(object_type)
validate_member_exists!(member_type, member_name)

add_to_acl!(member_type, member_name, object_type, object_name, perms)
end
end
end
end
183 changes: 183 additions & 0 deletions lib/chef/knife/acl_base.rb
@@ -0,0 +1,183 @@
#
# Author:: Steven Danna (steve@chef.io)
# Author:: Jeremiah Snapp (<jeremiah@chef.io>)
# Copyright:: Copyright 2011-2020 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# 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.
#

require_relative "../knife"

class Chef
class Knife
module AclBase

PERM_TYPES = %w{create read update delete grant}.freeze unless defined? PERM_TYPES
MEMBER_TYPES = %w{client group user}.freeze unless defined? MEMBER_TYPES
OBJECT_TYPES = %w{clients containers cookbooks data environments groups nodes roles policies policy_groups}.freeze unless defined? OBJECT_TYPES
OBJECT_NAME_SPEC = /^[\-[:alnum:]_\.]+$/.freeze unless defined? OBJECT_NAME_SPEC

def validate_object_type!(type)
unless OBJECT_TYPES.include?(type)
ui.fatal "Unknown object type \"#{type}\". The following types are permitted: #{OBJECT_TYPES.join(", ")}"
exit 1
end
end

def validate_object_name!(name)
unless OBJECT_NAME_SPEC.match(name)
ui.fatal "Invalid name: #{name}"
exit 1
end
end

def validate_member_type!(type)
unless MEMBER_TYPES.include?(type)
ui.fatal "Unknown member type \"#{type}\". The following types are permitted: #{MEMBER_TYPES.join(", ")}"
exit 1
end
end

def validate_member_name!(name)
# Same rules apply to objects and members
validate_object_name!(name)
end

def validate_perm_type!(perms)
perms.split(",").each do |perm|
unless PERM_TYPES.include?(perm)
ui.fatal "Invalid permission \"#{perm}\". The following permissions are permitted: #{PERM_TYPES.join(",")}"
exit 1
end
end
end

def validate_member_exists!(member_type, member_name)
true if rest.get_rest("#{member_type}s/#{member_name}")
rescue NameError
# ignore "NameError: uninitialized constant Chef::ApiClient" when finding a client
true
rescue
ui.fatal "#{member_type} '#{member_name}' does not exist"
exit 1
end

def is_usag?(gname)
gname.length == 32 && gname =~ /^[0-9a-f]+$/
end

def get_acl(object_type, object_name)
rest.get_rest("#{object_type}/#{object_name}/_acl?detail=granular")
end

def get_ace(object_type, object_name, perm)
get_acl(object_type, object_name)[perm]
end

def add_to_acl!(member_type, member_name, object_type, object_name, perms)
acl = get_acl(object_type, object_name)
perms.split(",").each do |perm|
ui.msg "Adding '#{member_name}' to '#{perm}' ACE of '#{object_name}'"
ace = acl[perm]

case member_type
when "client", "user"
# Our PUT body depends on the type of reply we get from _acl?detail=granular
# When the server replies with json attributes 'users' and 'clients',
# we'll want to modify entries under the same keys they arrived.- their presence
# in the body tells us that CS will accept them in a PUT.
# Older version of chef-server will continue to use 'actors' for a combined list
# and expect the same in the body.
key = "#{member_type}s"
key = "actors" unless ace.key? key
next if ace[key].include?(member_name)

ace[key] << member_name
when "group"
next if ace["groups"].include?(member_name)

ace["groups"] << member_name
end

update_ace!(object_type, object_name, perm, ace)
end
end

def remove_from_acl!(member_type, member_name, object_type, object_name, perms)
acl = get_acl(object_type, object_name)
perms.split(",").each do |perm|
ui.msg "Removing '#{member_name}' from '#{perm}' ACE of '#{object_name}'"
ace = acl[perm]

case member_type
when "client", "user"
key = "#{member_type}s"
key = "actors" unless ace.key? key
next unless ace[key].include?(member_name)

ace[key].delete(member_name)
when "group"
next unless ace["groups"].include?(member_name)

ace["groups"].delete(member_name)
end

update_ace!(object_type, object_name, perm, ace)
end
end

def update_ace!(object_type, object_name, ace_type, ace)
rest.put_rest("#{object_type}/#{object_name}/_acl/#{ace_type}", ace_type => ace)
end

def add_to_group!(member_type, member_name, group_name)
validate_member_exists!(member_type, member_name)
existing_group = rest.get_rest("groups/#{group_name}")
ui.msg "Adding '#{member_name}' to '#{group_name}' group"
unless existing_group["#{member_type}s"].include?(member_name)
existing_group["#{member_type}s"] << member_name
new_group = {
"groupname" => existing_group["groupname"],
"orgname" => existing_group["orgname"],
"actors" => {
"users" => existing_group["users"],
"clients" => existing_group["clients"],
"groups" => existing_group["groups"],
},
}
rest.put_rest("groups/#{group_name}", new_group)
end
end

def remove_from_group!(member_type, member_name, group_name)
validate_member_exists!(member_type, member_name)
existing_group = rest.get_rest("groups/#{group_name}")
ui.msg "Removing '#{member_name}' from '#{group_name}' group"
if existing_group["#{member_type}s"].include?(member_name)
existing_group["#{member_type}s"].delete(member_name)
new_group = {
"groupname" => existing_group["groupname"],
"orgname" => existing_group["orgname"],
"actors" => {
"users" => existing_group["users"],
"clients" => existing_group["clients"],
"groups" => existing_group["groups"],
},
}
rest.put_rest("groups/#{group_name}", new_group)
end
end
end
end
end
78 changes: 78 additions & 0 deletions lib/chef/knife/acl_bulk_add.rb
@@ -0,0 +1,78 @@
#
# Author:: Jeremiah Snapp (jeremiah@chef.io)
# Copyright:: Copyright 2011-2020 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# 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.
#

require_relative "../knife"

class Chef
class Knife
class AclBulkAdd < Chef::Knife
category "acl"
banner "knife acl bulk add MEMBER_TYPE MEMBER_NAME OBJECT_TYPE REGEX PERMS"

deps do
require_relative "acl_base"
include Chef::Knife::AclBase
end

def run
member_type, member_name, object_type, regex, perms = name_args
object_name_matcher = /#{regex}/

if name_args.length != 5
show_usage
ui.fatal "You must specify the member type [client|group], member name, object type, object name REGEX and perms"
exit 1
end

unless %w{client group}.include?(member_type)
ui.fatal "ERROR: To enforce best practice, knife-acl can only add a client or a group to an ACL."
ui.fatal " See the knife-acl README for more information."
exit 1
end
validate_perm_type!(perms)
validate_member_name!(member_name)
validate_object_type!(object_type)
validate_member_exists!(member_type, member_name)

if %w{containers groups}.include?(object_type)
ui.fatal "bulk modifying the ACL of #{object_type} is not permitted"
exit 1
end

objects_to_modify = []
all_objects = rest.get_rest(object_type)
objects_to_modify = all_objects.keys.select { |object_name| object_name =~ object_name_matcher }

if objects_to_modify.empty?
ui.info "No #{object_type} match the expression /#{regex}/"
exit 0
end

ui.msg("The ACL of the following #{object_type} will be modified:")
ui.msg("")
ui.msg(ui.list(objects_to_modify.sort, :columns_down))
ui.msg("")
ui.confirm("Are you sure you want to modify the ACL of these #{object_type}?")

objects_to_modify.each do |object_name|
add_to_acl!(member_type, member_name, object_type, object_name, perms)
end
end
end
end
end