amp4e_ldap_tool 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +52 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/README.md +95 -0
- data/Rakefile +6 -0
- data/amp4e_ldap_tool.gemspec +26 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/amp4e_ldap_tool +5 -0
- data/lib/amp4e_ldap_tool.rb +86 -0
- data/lib/amp4e_ldap_tool/cisco_amp.rb +151 -0
- data/lib/amp4e_ldap_tool/cli.rb +159 -0
- data/lib/amp4e_ldap_tool/endpoints.rb +49 -0
- data/lib/amp4e_ldap_tool/errors.rb +49 -0
- data/lib/amp4e_ldap_tool/ldap_scrape.rb +69 -0
- data/lib/amp4e_ldap_tool/version.rb +3 -0
- metadata +151 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e64128d5ab79fad912da68f5e31967613d469498
|
4
|
+
data.tar.gz: 827d8801707fc45dbf5b2d2a9b27eec7a10cfe5f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8c09590f6d6da7b58648168abc5450d0fb2e8a0845e957a2acfd66483035530c91ec263504f9c09019671a162701dab79ac1ddcc02e049a381e8d6b0c5cb8e1a
|
7
|
+
data.tar.gz: 4c151efff958954308be12402473c4df0cacdf4a9ad768964a91b60d4975bbbc1bd30e9373ebfb44a112aed2e3c9d0500f72fc44463297da7395bd9a334aeffc
|
data/.gitignore
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
*.swp
|
4
|
+
*.swo
|
5
|
+
/.config
|
6
|
+
/coverage/
|
7
|
+
/InstalledFiles
|
8
|
+
/pkg/
|
9
|
+
/spec/reports/
|
10
|
+
/spec/examples.txt
|
11
|
+
/test/tmp/
|
12
|
+
/test/version_tmp/
|
13
|
+
/tmp/
|
14
|
+
|
15
|
+
# Used by dotenv library to load environment variables.
|
16
|
+
# .env
|
17
|
+
|
18
|
+
## Specific to RubyMotion:
|
19
|
+
.dat*
|
20
|
+
.repl_history
|
21
|
+
build/
|
22
|
+
*.bridgesupport
|
23
|
+
build-iPhoneOS/
|
24
|
+
build-iPhoneSimulator/
|
25
|
+
|
26
|
+
## Specific to RubyMotion (use of CocoaPods):
|
27
|
+
#
|
28
|
+
# We recommend against adding the Pods directory to your .gitignore. However
|
29
|
+
# you should judge for yourself, the pros and cons are mentioned at:
|
30
|
+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
31
|
+
#
|
32
|
+
# vendor/Pods/
|
33
|
+
|
34
|
+
## Documentation cache and generated files:
|
35
|
+
/.yardoc/
|
36
|
+
/_yardoc/
|
37
|
+
/doc/
|
38
|
+
/rdoc/
|
39
|
+
|
40
|
+
## Environment normalization:
|
41
|
+
/.bundle/
|
42
|
+
/vendor/bundle
|
43
|
+
/lib/bundler/man/
|
44
|
+
|
45
|
+
# for a library or gem, you might want to ignore these files since the code is
|
46
|
+
# intended to run in multiple environments; otherwise, check them in:
|
47
|
+
Gemfile.lock
|
48
|
+
# .ruby-version
|
49
|
+
# .ruby-gemset
|
50
|
+
|
51
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
52
|
+
.rvmrc
|
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
# amp4e\_ldap\_tool
|
2
|
+
Ruby command line script to reconcile computers and groups on Cisco's AMP for Endpoints web portal with local LDAP servers.
|
3
|
+
|
4
|
+
`gem install amp4e_ldap_tool`
|
5
|
+
|
6
|
+
## Commands
|
7
|
+
### AMP
|
8
|
+
`amp4e_ldap_tool amp`
|
9
|
+
|
10
|
+
It makes an HTTP request to the AMP for Endpoints web portal. We must provide flags to tell amp what we want to receive:
|
11
|
+
|
12
|
+
- `-c` gets a list of Computers in the AMP system.
|
13
|
+
- `-g` gets a list of Groups as AMP sees them
|
14
|
+
- `-p` gets a list of policies
|
15
|
+
- `-t` provides any of the above options with formatted output.
|
16
|
+
|
17
|
+
### LDAP
|
18
|
+
`amp4e_ldap_tool`
|
19
|
+
|
20
|
+
It retrieves information from the LDAP server, using credentials provided in your config file. It also requires flags to tell it what to get from the server:
|
21
|
+
|
22
|
+
- `-c` gets computer names
|
23
|
+
- `-g` gets computer group names
|
24
|
+
- `-d` gets the fully distinguished name (LDAP)
|
25
|
+
- `-t` provides any of the above options with formatted output.
|
26
|
+
|
27
|
+
|
28
|
+
### Make\_changes
|
29
|
+
The command `make_changes` is the workhorse. Calling it on its own:
|
30
|
+
|
31
|
+
```
|
32
|
+
amp4e_ldap_tool make_changes
|
33
|
+
```
|
34
|
+
|
35
|
+
Displays the _dry run_, a list of changes that will be changed shown in aggregate. These changes will be formatted in easy-to-read tables as shown below:
|
36
|
+
|
37
|
+
```
|
38
|
+
+---------------------------------+--------------+
|
39
|
+
| Group Creates |
|
40
|
+
+---------------------------------+--------------+
|
41
|
+
| Group Name | Parent Group |
|
42
|
+
+---------------------------------+--------------+
|
43
|
+
| Computers.2k8sso.local | nil |
|
44
|
+
| local | nil |
|
45
|
+
| 2k8sso.local | nil |
|
46
|
+
| Domain Controllers.2k8sso.local | nil |
|
47
|
+
+---------------------------------+--------------+
|
48
|
+
+---------------------------------+------------+--------------+
|
49
|
+
| Group Moves |
|
50
|
+
+---------------------------------+------------+--------------+
|
51
|
+
| Group | Old Parent | New Parent |
|
52
|
+
+---------------------------------+------------+--------------+
|
53
|
+
| Computers.2k8sso.local | | 2k8sso.local |
|
54
|
+
| 2k8sso.local | | local |
|
55
|
+
| Domain Controllers.2k8sso.local | | 2k8sso.local |
|
56
|
+
+---------------------------------+------------+--------------+
|
57
|
+
+----------------+------------+------------------------+
|
58
|
+
| Computer Moves |
|
59
|
+
+----------------+------------+------------------------+
|
60
|
+
| # of computers | from group | to group |
|
61
|
+
+----------------+------------+------------------------+
|
62
|
+
| 2 | Protect | Computers.2k8sso.local |
|
63
|
+
| 2 | Audit | Computers.2k8sso.local |
|
64
|
+
+----------------+------------+------------------------+
|
65
|
+
```
|
66
|
+
|
67
|
+
Applying the -a option will tell the command to apply the changes, it will prompt the user to continue with a y/n after showing the _dry run_.
|
68
|
+
|
69
|
+
|
70
|
+
|
71
|
+
|
72
|
+
|
73
|
+
|
74
|
+
## Config File
|
75
|
+
|
76
|
+
The config file holds our user/password information for the API/LDAP servers. It follows a specific format and a template is provided below:
|
77
|
+
|
78
|
+
|
79
|
+
```
|
80
|
+
#config.yml
|
81
|
+
:ldap:
|
82
|
+
:host: # LDAP hostname
|
83
|
+
:domain: # domain of LDAP tree
|
84
|
+
:credentials:
|
85
|
+
:un: # server username
|
86
|
+
:pw: # server password
|
87
|
+
:schema:
|
88
|
+
:filter: "computer" # default as computer
|
89
|
+
:amp:
|
90
|
+
:host: # api url for AMP
|
91
|
+
:api:
|
92
|
+
:third_party: # third party code
|
93
|
+
:key: # api key
|
94
|
+
:version: "v1" # default version is v1
|
95
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'amp4e_ldap_tool/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "amp4e_ldap_tool"
|
8
|
+
spec.version = Amp4eLdapTool::VERSION
|
9
|
+
spec.authors = ["vbakala"]
|
10
|
+
spec.email = ["vbakala@cisco.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Write a short summary, because Rubygems requires one.}
|
13
|
+
spec.description = %q{Write a longer description or delete this line.}
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
16
|
+
spec.bindir = "exe"
|
17
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_development_dependency "bundler", "~> 1.12"
|
21
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
22
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
23
|
+
spec.add_dependency "thor", "~> 0.19.4"
|
24
|
+
spec.add_dependency "net-ldap", "~> 0.15.0"
|
25
|
+
spec.add_dependency "terminal-table", "~> 1.7", ">= 1.7.3"
|
26
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "amp4e_ldap_tool"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/exe/amp4e_ldap_tool
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
require "amp4e_ldap_tool/version"
|
2
|
+
require "amp4e_ldap_tool/errors"
|
3
|
+
require "amp4e_ldap_tool/cisco_amp"
|
4
|
+
require "amp4e_ldap_tool/ldap_scrape"
|
5
|
+
require "terminal-table"
|
6
|
+
|
7
|
+
module Amp4eLdapTool
|
8
|
+
|
9
|
+
def self.dry_run(amp, ldap)
|
10
|
+
#TODO validate input
|
11
|
+
data = {created_groups: [], group_moves: [], pc_moves: []}
|
12
|
+
adj = amp.make_list(amp.get(:computers), amp.get(:groups))
|
13
|
+
ldap.groups.each do |group|
|
14
|
+
if adj[group].nil?
|
15
|
+
data[:created_groups] << [group, "nil"]
|
16
|
+
adj[group] = { object: nil, parent: nil }
|
17
|
+
end
|
18
|
+
unless adj[group][:parent] == ldap.parent(group)
|
19
|
+
data[:group_moves] << [group, adj[group][:parent], ldap.parent(group)]
|
20
|
+
adj[group][:parent] = ldap.parent(group)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
counter = {}
|
24
|
+
ldap.entries.each do |entry|
|
25
|
+
computername = entry.dnshostname.first.downcase
|
26
|
+
unless adj[computername].nil?
|
27
|
+
if adj[computername][:parent] != ldap.parent(entry.dn)
|
28
|
+
old_name = adj[computername][:parent]
|
29
|
+
new_name = ldap.parent(entry.dn)
|
30
|
+
if counter[old_name].nil?
|
31
|
+
counter[old_name] = {new_name => 1}
|
32
|
+
else
|
33
|
+
counter[old_name][new_name].nil? ? counter[old_name][new_name] = 1
|
34
|
+
: counter[old_name][new_name] += 1
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
counter.keys.each do |old_name|
|
40
|
+
counter[old_name].keys.each do |new_name|
|
41
|
+
data[:pc_moves] << [counter[old_name][new_name], old_name, new_name]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
generate_table(data)
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.push_changes(amp, ldap)
|
48
|
+
#TODO validate input
|
49
|
+
adj = amp.make_list(amp.get(:computers), amp.get(:groups))
|
50
|
+
ldap.groups.each do |group|
|
51
|
+
if adj[group].nil?
|
52
|
+
puts "creating group..."
|
53
|
+
g = amp.create_group(group)
|
54
|
+
adj[group] = { object: g, parent: g.parent[:name] }
|
55
|
+
end
|
56
|
+
unless adj[group][:parent] == ldap.parent(group)
|
57
|
+
puts "updating group parent..."
|
58
|
+
amp.update_group(adj[group][:object].guid, adj[ldap.parent(group)][:object].guid)
|
59
|
+
adj[group][:parent] = ldap.parent(group)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
ldap.entries.each do |entry|
|
63
|
+
computername = entry.dnshostname.first.downcase
|
64
|
+
parent_name = ldap.parent(entry.dn)
|
65
|
+
unless adj[computername].nil?
|
66
|
+
if adj[computername][:parent] != parent_name
|
67
|
+
puts "moving computer..."
|
68
|
+
amp.update_computer(adj[computername][:object].guid, adj[parent_name][:object].guid)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def self.generate_table(table_data)
|
77
|
+
#TODO validate input
|
78
|
+
puts Terminal::Table.new(title: "Group Creates", rows: table_data[:created_groups],
|
79
|
+
headings: ["Group Name", "Parent Group"])
|
80
|
+
puts Terminal::Table.new(title: "Group Moves", rows: table_data[:group_moves],
|
81
|
+
headings: ["Group", "Old Parent", "New Parent"])
|
82
|
+
puts Terminal::Table.new(title: "Computer Moves", rows: table_data[:pc_moves],
|
83
|
+
headings: ["# of computers", "from group", "to group" ])
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'yaml'
|
3
|
+
require 'amp4e_ldap_tool/errors'
|
4
|
+
require 'amp4e_ldap_tool/endpoints'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
module Amp4eLdapTool
|
8
|
+
GUID = /\A\h{8}-\h{4}-\h{4}-\h{4}-\h{12}\z/
|
9
|
+
X_RATELIMIT_REMAINING = 'x-ratelimit-remaining'
|
10
|
+
X_RATELIMIT_RESET = 'x-ratelimit-reset'
|
11
|
+
|
12
|
+
class CiscoAMP
|
13
|
+
|
14
|
+
attr_reader :base_url, :version, :email, :third_party, :api_key
|
15
|
+
|
16
|
+
def initialize(config_file = "config.yml")
|
17
|
+
config = YAML.load_file(config_file)
|
18
|
+
confirm_config(config)
|
19
|
+
@base_url = config[:amp][:host]
|
20
|
+
@version = config[:amp][:api][:version]
|
21
|
+
@email = config[:amp][:email]
|
22
|
+
@third_party = config[:amp][:api][:third_party]
|
23
|
+
@api_key = config[:amp][:api][:key]
|
24
|
+
end
|
25
|
+
|
26
|
+
def get(endpoint)
|
27
|
+
url = URI(@base_url + "/#{@version}/#{endpoint}")
|
28
|
+
get = Net::HTTP::Get.new(url)
|
29
|
+
response = send(get, url)
|
30
|
+
parse_response(response, endpoint.to_sym)
|
31
|
+
end
|
32
|
+
|
33
|
+
def update_computer(computer_guid, new_guid)
|
34
|
+
validate_guid([computer_guid, new_guid])
|
35
|
+
url = URI(@base_url + "/#{@version}/computers/#{computer_guid}")
|
36
|
+
patch = Net::HTTP::Patch.new(url)
|
37
|
+
body = { group_guid: new_guid }
|
38
|
+
send(patch, url, body)
|
39
|
+
end
|
40
|
+
|
41
|
+
def update_group(group_guid, parent = nil)
|
42
|
+
validate_guid([group_guid])
|
43
|
+
url = URI(@base_url + "/#{@version}/groups/#{group_guid}/parent")
|
44
|
+
patch = Net::HTTP::Patch.new(url)
|
45
|
+
body = { parent_group_guid: parent }
|
46
|
+
response = send(patch, url, body)
|
47
|
+
end
|
48
|
+
|
49
|
+
def create_group(group_name, desc = "Imported from LDAP")
|
50
|
+
url = URI(@base_url + "/#{@version}/groups/")
|
51
|
+
post = Net::HTTP::Post.new(url)
|
52
|
+
body = { name: group_name, email: @email,
|
53
|
+
description: desc }
|
54
|
+
send(post, url, body)
|
55
|
+
end
|
56
|
+
|
57
|
+
def make_list(computers, groups)
|
58
|
+
adj = {}
|
59
|
+
groups.each do |group|
|
60
|
+
adj[group.name] = { object: group, parent: group.parent[:name] }
|
61
|
+
end
|
62
|
+
computers.each do |pc|
|
63
|
+
group = groups.find{ |g| g.guid == pc.group_guid }
|
64
|
+
adj[pc.name.downcase] = { object: pc, parent: group.name }
|
65
|
+
end
|
66
|
+
adj
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def send(http_request, url, body = {})
|
72
|
+
http_request.basic_auth(@third_party, @api_key)
|
73
|
+
http_request.set_form_data(body) unless body.empty?
|
74
|
+
check_response do
|
75
|
+
Net::HTTP.start(url.hostname, url.port) do |http|
|
76
|
+
http.request(http_request)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def check_response
|
82
|
+
begin
|
83
|
+
response = yield
|
84
|
+
response_head = response.to_hash
|
85
|
+
response_body = JSON.parse(response.body)
|
86
|
+
|
87
|
+
case response.msg.downcase.tr(" ","_").to_sym
|
88
|
+
when :ok
|
89
|
+
response_notification = response.body
|
90
|
+
when :created
|
91
|
+
response_notification = Amp4eLdapTool::AMP::Group.new(response_body["data"])
|
92
|
+
when :accepted
|
93
|
+
response_notification = response.msg
|
94
|
+
when :too_many_requests
|
95
|
+
raise AMPTooManyRequestsError
|
96
|
+
when :bad_request
|
97
|
+
raise AMPBadRequestError.new(msg: response_body["errors"])
|
98
|
+
when :unauthorized
|
99
|
+
raise AMPUnauthorizedError.new(msg: response_body["errors"])
|
100
|
+
else
|
101
|
+
raise AMPResponseError.new(msg: "code: " + response.msg + " body: " + response.body)
|
102
|
+
end
|
103
|
+
rescue AMPTooManyRequestsError
|
104
|
+
sleep_seconds = response_head[Amp4eLdapTool::X_RATELIMIT_RESET].to_i
|
105
|
+
puts "Ratelimit Reached, sleeping for #{sleep_seconds} second(s)"
|
106
|
+
sleep(sleep_seconds)
|
107
|
+
retry
|
108
|
+
end
|
109
|
+
response_notification
|
110
|
+
end
|
111
|
+
|
112
|
+
def parse_response(message, endpoint)
|
113
|
+
endpoints = []
|
114
|
+
parse = JSON.parse(message)
|
115
|
+
parse["data"].each do |item|
|
116
|
+
case endpoint
|
117
|
+
when :computers
|
118
|
+
endpoints << Amp4eLdapTool::AMP::Computer.new(item)
|
119
|
+
when :groups
|
120
|
+
endpoints << Amp4eLdapTool::AMP::Group.new(item)
|
121
|
+
when :policies
|
122
|
+
endpoints << Amp4eLdapTool::AMP::Policy.new(item)
|
123
|
+
else
|
124
|
+
raise AMPResponseError.new(msg: "Parsing GET error for #{endpoint}")
|
125
|
+
end
|
126
|
+
end
|
127
|
+
endpoints
|
128
|
+
end
|
129
|
+
|
130
|
+
def validate_guid(guids)
|
131
|
+
guids.each do |guid|
|
132
|
+
if Amp4eLdapTool::GUID.match(guid).nil?
|
133
|
+
raise AMPInvalidFormatError
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def confirm_config(config)
|
139
|
+
raise AMPConfigError if config[:amp][:api][:third_party].nil?
|
140
|
+
raise AMPConfigError if config[:amp][:api][:key].nil?
|
141
|
+
raise AMPConfigError if config[:amp][:api][:version].nil?
|
142
|
+
begin
|
143
|
+
Net::HTTP::Get.new(URI(config[:amp][:host]))
|
144
|
+
rescue TypeError
|
145
|
+
raise AMPBadURIError
|
146
|
+
rescue ArgumentError
|
147
|
+
raise AMPConfigError
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'amp4e_ldap_tool/cisco_amp'
|
3
|
+
require 'amp4e_ldap_tool/ldap_scrape'
|
4
|
+
require 'amp4e_ldap_tool'
|
5
|
+
require 'terminal-table'
|
6
|
+
|
7
|
+
module Amp4eLdapTool
|
8
|
+
class CLI < Thor
|
9
|
+
|
10
|
+
desc "amp --[groups|computers|policies]", "Gets groups, computer, and/or policies from AMP"
|
11
|
+
long_desc <<-LONGDESC
|
12
|
+
groupsync amp will get a list of groups, policies, and computers from AMP,
|
13
|
+
with the specified options above.
|
14
|
+
|
15
|
+
For example the following command will fetch the list of computers from amp:
|
16
|
+
|
17
|
+
> $ groupsync amp -c
|
18
|
+
LONGDESC
|
19
|
+
method_option :computers, aliases: "-c"
|
20
|
+
method_option :groups, aliases: "-g"
|
21
|
+
method_option :policies, aliases: "-p"
|
22
|
+
method_option :table, aliases: "-t"
|
23
|
+
def amp
|
24
|
+
display_resources(Amp4eLdapTool::CiscoAMP.new, options)
|
25
|
+
end
|
26
|
+
|
27
|
+
desc "ldap --[groups|computers|distinguished]", "Gets groups, computer, and/or distinguished names from LDAP"
|
28
|
+
long_desc <<-LONGDESC
|
29
|
+
groupsync ldap will get a list of groups, distinguished names, and computers from AMP,
|
30
|
+
with the specified options above.
|
31
|
+
|
32
|
+
For example the following command will fetch the list of computers from ldap:
|
33
|
+
|
34
|
+
> $ groupsync ldap -c
|
35
|
+
LONGDESC
|
36
|
+
method_option :computers, aliases: "-c"
|
37
|
+
method_option :groups, aliases: "-g"
|
38
|
+
method_option :distinguished, aliases: "-d"
|
39
|
+
method_option :table, aliases: "-t"
|
40
|
+
def ldap
|
41
|
+
ldap = Amp4eLdapTool::LDAPScrape.new
|
42
|
+
|
43
|
+
if options[:table]
|
44
|
+
options.keys.each do |name|
|
45
|
+
rows = []
|
46
|
+
unless name == 'table'
|
47
|
+
if name == 'computers'
|
48
|
+
ldap.entries.each {|entry| rows << [entry.dnshostname]}
|
49
|
+
elsif name == 'distinguished'
|
50
|
+
ldap.entries.each {|entry| rows << [entry.dn]}
|
51
|
+
elsif name == 'groups'
|
52
|
+
ldap.groups.each {|entry| rows << [entry]}
|
53
|
+
end
|
54
|
+
puts Terminal::Table.new(:headings => [name], :rows => rows)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
else
|
58
|
+
puts ldap.groups unless options[:groups].nil?
|
59
|
+
ldap.entries.each {|entry| puts entry.dnshostname} unless options[:computers].nil?
|
60
|
+
ldap.entries.each {|entry| puts entry.dn} unless options[:distinguished].nil?
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
long_desc <<-LONGDESC
|
65
|
+
Moves a computer to a specified group, requires the new group GUID.
|
66
|
+
|
67
|
+
For example the following command will move a specific computer(00000000-0000-0000-0000-000000000000)
|
68
|
+
to group(11111111-1111-1111-1111-111111111111).
|
69
|
+
|
70
|
+
> $ groupsync move 00000000-0000-0000-0000-000000000000 11111111-1111-1111-1111-111111111111
|
71
|
+
LONGDESC
|
72
|
+
desc "move PC GUID", "Moves a PC to a specified group, requires the new groups GUID"
|
73
|
+
def move(computer, new_guid)
|
74
|
+
amp = Amp4eLdapTool::CiscoAMP.new
|
75
|
+
puts amp.move_computer(computer, new_guid)
|
76
|
+
end
|
77
|
+
|
78
|
+
long_desc <<-LONGDESC
|
79
|
+
Creates a group with the specified name.
|
80
|
+
|
81
|
+
For example the following command will create a group with the name ExampleGroup:
|
82
|
+
|
83
|
+
> $ groupsync create ExampleGroup
|
84
|
+
LONGDESC
|
85
|
+
desc "create NAME", "Creates a group with the name of NAME"
|
86
|
+
method_option :desc, aliases: "-d"
|
87
|
+
def create(name)
|
88
|
+
amp = Amp4eLdapTool::CiscoAMP.new
|
89
|
+
puts amp.create_group(name).name unless options[:desc]
|
90
|
+
puts amp.create_group(name, options[:desc]).name if options[:desc]
|
91
|
+
end
|
92
|
+
|
93
|
+
long_desc <<-LONGDESC
|
94
|
+
Moves a specified group under a new parent.
|
95
|
+
|
96
|
+
For example the following command will move the group(00000000-0000-0000-0000-000000000000)
|
97
|
+
under parent(11111111-1111-1111-1111-111111111111):
|
98
|
+
|
99
|
+
> $ groupsync move_group 00000000-0000-0000-0000-000000000000 11111111-1111-1111-1111-111111111111
|
100
|
+
LONGDESC
|
101
|
+
desc "move_group GUID PARENT", "Moves a group under a new parent"
|
102
|
+
def move_group(guid, parent_guid)
|
103
|
+
amp = Amp4eLdapTool::CiscoAMP.new
|
104
|
+
puts amp.update_group(guid, parent_guid)
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
desc "make_changes", "Shows a dry run of changes, and prompts to execute"
|
109
|
+
long_desc <<-LONGDESC
|
110
|
+
Shows a dry run of changes in tabular form:
|
111
|
+
|
112
|
+
Executing 'make_changes' on its own produces a dry run, while the '-a' option
|
113
|
+
prompts the user to apply the changes. The '-f' option is used for
|
114
|
+
scripting and executes the changes without a prompt or dry-run report.
|
115
|
+
|
116
|
+
>$ amp4e_ldap_tool make_changes -a \x5
|
117
|
+
... \x5
|
118
|
+
Do you want to continue? [y, n]\x5
|
119
|
+
|
120
|
+
LONGDESC
|
121
|
+
method_option :apply, aliases: "-a"
|
122
|
+
method_option :force, aliases: "-f"
|
123
|
+
def make_changes
|
124
|
+
answer = "n"
|
125
|
+
amp = Amp4eLdapTool::CiscoAMP.new
|
126
|
+
ldap = Amp4eLdapTool::LDAPScrape.new
|
127
|
+
|
128
|
+
if (options.empty?)
|
129
|
+
Amp4eLdapTool.dry_run(amp, ldap)
|
130
|
+
elsif (options[:apply])
|
131
|
+
Amp4eLdapTool.dry_run(amp, ldap)
|
132
|
+
answer = ask("Do you want to continue?", limited_to: ["y","n"]) if options[:apply]
|
133
|
+
Amp4eLdapTool.push_changes(amp, ldap) if (answer == "y")
|
134
|
+
elsif (options[:force])
|
135
|
+
Amp4eLdapTool.push_changes(amp, ldap)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
|
141
|
+
def display_resources(amp, options)
|
142
|
+
if options[:table]
|
143
|
+
options.keys.each do |name|
|
144
|
+
rows = []
|
145
|
+
unless name == 'table'
|
146
|
+
amp.get(name).each {|endpoint| rows << [endpoint.name]}
|
147
|
+
puts Terminal::Table.new(:headings => [name], :rows => rows)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
else
|
151
|
+
options.keys.each do |endpoints|
|
152
|
+
amp.get(endpoints).each do |endpoint|
|
153
|
+
puts endpoint.name
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Amp4eLdapTool
|
2
|
+
module AMP
|
3
|
+
class Computer
|
4
|
+
attr_accessor :name, :guid, :link, :active,
|
5
|
+
:group_guid, :policy, :os
|
6
|
+
|
7
|
+
def initialize(json)
|
8
|
+
@name = json["hostname"]
|
9
|
+
@guid = json["connector_guid"]
|
10
|
+
@active = json["active"]
|
11
|
+
@group_guid = json["group_guid"]
|
12
|
+
@os = json["operating_system"]
|
13
|
+
@link = { computer: json["links"]["computer"],
|
14
|
+
trajectory: json["links"]["trajectory"],
|
15
|
+
group: json["links"]["group"]}
|
16
|
+
@policy = { name: json["policy"]["name"],
|
17
|
+
guid: json["policy"]["guid"] }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Group
|
22
|
+
attr_accessor :name, :description, :guid, :parent, :link
|
23
|
+
|
24
|
+
def initialize(json)
|
25
|
+
@name = json["name"]
|
26
|
+
@guid = json["guid"]
|
27
|
+
@description = json["description"]
|
28
|
+
@parent = (json["ancestry"].nil?) ? {} :
|
29
|
+
{name: json["ancestry"].first["name"],
|
30
|
+
guid: json["ancestry"].first["guid"]}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Policy
|
35
|
+
attr_accessor :name, :description, :guid, :product, :default,
|
36
|
+
:serial_number, :link
|
37
|
+
|
38
|
+
def initialize(json)
|
39
|
+
@name = json["name"]
|
40
|
+
@guid = json["guid"]
|
41
|
+
@description = json["description"]
|
42
|
+
@product = json["product"]
|
43
|
+
@default = json["default"]
|
44
|
+
@serial_number = json["serial_number"]
|
45
|
+
@link = json["links"]["policy"]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Amp4eLdapTool
|
2
|
+
class AMPConfigError < StandardError
|
3
|
+
def initialize(msg: "The AMP section of the configuration file is not formatted properly" )
|
4
|
+
super
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class LDAPConfigError < StandardError
|
9
|
+
def initialize(msg: "The LDAP Section of the configuration file is not formatted properly")
|
10
|
+
super
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class AMPTooManyRequestsError < StandardError
|
15
|
+
def initialize(msg: "The ratelimit has been excedded")
|
16
|
+
super
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class AMPBadURIError < StandardError
|
21
|
+
def initialize(msg: "The amp:host in your config file contains an invalid hostname")
|
22
|
+
super
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class AMPInvalidFormatError < ArgumentError
|
27
|
+
def initialize(msg: "The GUID provided does not follow the correct format")
|
28
|
+
super
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class AMPResponseError < StandardError
|
33
|
+
def initialize(msg: "The AMP Server returned a non-OK response")
|
34
|
+
super
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class AMPUnauthorizedError < AMPResponseError
|
39
|
+
def initialize(msg: "Third_party/API credentials appear to be invalid")
|
40
|
+
super
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class AMPBadRequestError < AMPResponseError
|
45
|
+
def initialize(msg: "A Bad request was made to the server")
|
46
|
+
super
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'net/ldap'
|
2
|
+
require 'yaml'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Amp4eLdapTool
|
6
|
+
class LDAPScrape
|
7
|
+
|
8
|
+
attr_reader :entries
|
9
|
+
|
10
|
+
def initialize(filename = 'config.yml')
|
11
|
+
cfg = YAML.load_file(filename)
|
12
|
+
|
13
|
+
attributes = ["cn", "dnshostname"]
|
14
|
+
base = make_dn(cfg[:ldap][:domain])
|
15
|
+
filter = Net::LDAP::Filter.eq('objectClass', cfg[:ldap][:schema][:filter])
|
16
|
+
server = Net::LDAP.new(
|
17
|
+
host: cfg[:ldap][:host],
|
18
|
+
auth: {
|
19
|
+
method: :simple,
|
20
|
+
username: "#{cfg[:ldap][:credentials][:un]}@#{cfg[:ldap][:domain]}",
|
21
|
+
password: cfg[:ldap][:credentials][:pw]}
|
22
|
+
)
|
23
|
+
@entries = server.search(base: base, filter: filter, attributes: attributes) do |entry|
|
24
|
+
entry
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def groups
|
29
|
+
dn_paths = []
|
30
|
+
@entries.each do |entry|
|
31
|
+
names = split_dn(entry.dn); names.shift; names.reverse!
|
32
|
+
temp_names = names.clone
|
33
|
+
names.each do
|
34
|
+
name = temp_names.inject{|glob, name| "#{name}.#{glob}"}
|
35
|
+
dn_paths << name
|
36
|
+
temp_names.pop
|
37
|
+
end
|
38
|
+
end
|
39
|
+
dn_paths.uniq.reverse
|
40
|
+
end
|
41
|
+
|
42
|
+
def parent(entry_name)
|
43
|
+
names = split_dn(entry_name) if entry_name.include?("=")
|
44
|
+
names = entry_name.split(".") if not entry_name.include?("=")
|
45
|
+
names.shift
|
46
|
+
unless names.empty?
|
47
|
+
parent_string = names.join(".")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def make_dn(domain_name)
|
54
|
+
output = ''
|
55
|
+
domain_name.split('.').each do |name|
|
56
|
+
output << "dc=#{name},"
|
57
|
+
end
|
58
|
+
output.chomp(',')
|
59
|
+
end
|
60
|
+
|
61
|
+
def split_dn(dn)
|
62
|
+
names = []
|
63
|
+
dn.split(",").each do |attribute|
|
64
|
+
names << attribute.split("=").last
|
65
|
+
end
|
66
|
+
names
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
metadata
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: amp4e_ldap_tool
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- vbakala
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-05-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.12'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.12'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: thor
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.19.4
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.19.4
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: net-ldap
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.15.0
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.15.0
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: terminal-table
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.7'
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: 1.7.3
|
93
|
+
type: :runtime
|
94
|
+
prerelease: false
|
95
|
+
version_requirements: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - "~>"
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '1.7'
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: 1.7.3
|
103
|
+
description: Write a longer description or delete this line.
|
104
|
+
email:
|
105
|
+
- vbakala@cisco.com
|
106
|
+
executables:
|
107
|
+
- amp4e_ldap_tool
|
108
|
+
extensions: []
|
109
|
+
extra_rdoc_files: []
|
110
|
+
files:
|
111
|
+
- ".gitignore"
|
112
|
+
- ".rspec"
|
113
|
+
- ".travis.yml"
|
114
|
+
- Gemfile
|
115
|
+
- README.md
|
116
|
+
- Rakefile
|
117
|
+
- amp4e_ldap_tool.gemspec
|
118
|
+
- bin/console
|
119
|
+
- bin/setup
|
120
|
+
- exe/amp4e_ldap_tool
|
121
|
+
- lib/amp4e_ldap_tool.rb
|
122
|
+
- lib/amp4e_ldap_tool/cisco_amp.rb
|
123
|
+
- lib/amp4e_ldap_tool/cli.rb
|
124
|
+
- lib/amp4e_ldap_tool/endpoints.rb
|
125
|
+
- lib/amp4e_ldap_tool/errors.rb
|
126
|
+
- lib/amp4e_ldap_tool/ldap_scrape.rb
|
127
|
+
- lib/amp4e_ldap_tool/version.rb
|
128
|
+
homepage:
|
129
|
+
licenses: []
|
130
|
+
metadata: {}
|
131
|
+
post_install_message:
|
132
|
+
rdoc_options: []
|
133
|
+
require_paths:
|
134
|
+
- lib
|
135
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - ">="
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '0'
|
140
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - ">="
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '0'
|
145
|
+
requirements: []
|
146
|
+
rubyforge_project:
|
147
|
+
rubygems_version: 2.4.5
|
148
|
+
signing_key:
|
149
|
+
specification_version: 4
|
150
|
+
summary: Write a short summary, because Rubygems requires one.
|
151
|
+
test_files: []
|