amp4e_ldap_tool 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|