strongdm 1.0.0 → 1.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (190) hide show
  1. checksums.yaml +4 -4
  2. data/doc/LICENSE.html +45 -136
  3. data/doc/Object.html +623 -105
  4. data/doc/README_md.html +15 -7
  5. data/doc/SDM.html +16 -24
  6. data/doc/SDM/AKS.html +150 -52
  7. data/doc/SDM/AKSBasicAuth.html +130 -36
  8. data/doc/SDM/AKSServiceAccount.html +125 -32
  9. data/doc/SDM/AccountAttachment.html +77 -27
  10. data/doc/SDM/AccountAttachmentCreateOptions.html +62 -13
  11. data/doc/SDM/AccountAttachmentCreateResponse.html +75 -24
  12. data/doc/SDM/AccountAttachmentDeleteResponse.html +69 -19
  13. data/doc/SDM/AccountAttachmentGetResponse.html +75 -23
  14. data/doc/SDM/AccountAttachments.html +62 -61
  15. data/doc/SDM/AccountCreateResponse.html +80 -27
  16. data/doc/SDM/AccountDeleteResponse.html +69 -17
  17. data/doc/SDM/AccountGetResponse.html +74 -21
  18. data/doc/SDM/AccountGrant.html +89 -39
  19. data/doc/SDM/AccountGrantCreateResponse.html +75 -23
  20. data/doc/SDM/AccountGrantDeleteResponse.html +69 -18
  21. data/doc/SDM/AccountGrantGetResponse.html +75 -22
  22. data/doc/SDM/AccountGrants.html +63 -58
  23. data/doc/SDM/AccountUpdateResponse.html +74 -21
  24. data/doc/SDM/Accounts.html +74 -68
  25. data/doc/SDM/AlreadyExistsError.html +13 -10
  26. data/doc/SDM/AmazonEKS.html +163 -48
  27. data/doc/SDM/AmazonES.html +117 -40
  28. data/doc/SDM/Athena.html +117 -40
  29. data/doc/SDM/AuroraMysql.html +122 -44
  30. data/doc/SDM/AuroraPostgres.html +127 -48
  31. data/doc/SDM/AuthenticationError.html +13 -10
  32. data/doc/SDM/BadRequestError.html +13 -10
  33. data/doc/SDM/BigQuery.html +117 -40
  34. data/doc/SDM/Cassandra.html +122 -44
  35. data/doc/SDM/Citus.html +409 -0
  36. data/doc/SDM/Client.html +29 -48
  37. data/doc/SDM/Clustrix.html +122 -44
  38. data/doc/SDM/Cockroach.html +127 -48
  39. data/doc/SDM/CreateResponseMetadata.html +59 -9
  40. data/doc/SDM/DB2.html +391 -0
  41. data/doc/SDM/DeadlineExceededError.html +13 -10
  42. data/doc/SDM/DeleteResponseMetadata.html +60 -10
  43. data/doc/SDM/Druid.html +117 -40
  44. data/doc/SDM/DynamoDB.html +117 -40
  45. data/doc/SDM/Elastic.html +122 -44
  46. data/doc/SDM/ElasticacheRedis.html +117 -40
  47. data/doc/SDM/Gateway.html +108 -39
  48. data/doc/SDM/GetResponseMetadata.html +60 -10
  49. data/doc/SDM/GoogleGKE.html +135 -40
  50. data/doc/SDM/Greenplum.html +127 -48
  51. data/doc/SDM/HTTPAuth.html +122 -44
  52. data/doc/SDM/HTTPBasicAuth.html +127 -48
  53. data/doc/SDM/HTTPNoAuth.html +117 -40
  54. data/doc/SDM/InternalError.html +13 -10
  55. data/doc/SDM/Kubernetes.html +150 -52
  56. data/doc/SDM/KubernetesBasicAuth.html +130 -36
  57. data/doc/SDM/KubernetesServiceAccount.html +125 -32
  58. data/doc/SDM/Maria.html +122 -44
  59. data/doc/SDM/Memcached.html +107 -32
  60. data/doc/SDM/Memsql.html +122 -44
  61. data/doc/SDM/MongoHost.html +127 -48
  62. data/doc/SDM/MongoLegacyHost.html +132 -52
  63. data/doc/SDM/MongoLegacyReplicaset.html +137 -56
  64. data/doc/SDM/MongoReplicaSet.html +137 -56
  65. data/doc/SDM/Mysql.html +122 -44
  66. data/doc/SDM/NodeCreateResponse.html +80 -27
  67. data/doc/SDM/NodeDeleteResponse.html +69 -17
  68. data/doc/SDM/NodeGetResponse.html +74 -21
  69. data/doc/SDM/NodeUpdateResponse.html +74 -21
  70. data/doc/SDM/Nodes.html +75 -70
  71. data/doc/SDM/NotFoundError.html +13 -10
  72. data/doc/SDM/Oracle.html +127 -48
  73. data/doc/SDM/PermissionError.html +13 -10
  74. data/doc/SDM/Plumbing.html +4177 -3576
  75. data/doc/SDM/Postgres.html +127 -48
  76. data/doc/SDM/Presto.html +127 -48
  77. data/doc/SDM/RDP.html +117 -40
  78. data/doc/SDM/RPCError.html +12 -8
  79. data/doc/SDM/RateLimitError.html +13 -10
  80. data/doc/SDM/RateLimitMetadata.html +81 -29
  81. data/doc/SDM/Redis.html +112 -36
  82. data/doc/SDM/Redshift.html +127 -48
  83. data/doc/SDM/Relay.html +96 -27
  84. data/doc/SDM/ResourceCreateResponse.html +74 -21
  85. data/doc/SDM/ResourceDeleteResponse.html +69 -17
  86. data/doc/SDM/ResourceGetResponse.html +74 -21
  87. data/doc/SDM/ResourceUpdateResponse.html +74 -22
  88. data/doc/SDM/Resources.html +71 -63
  89. data/doc/SDM/Role.html +96 -27
  90. data/doc/SDM/RoleAttachment.html +77 -26
  91. data/doc/SDM/RoleAttachmentCreateResponse.html +75 -24
  92. data/doc/SDM/RoleAttachmentDeleteResponse.html +69 -19
  93. data/doc/SDM/RoleAttachmentGetResponse.html +75 -22
  94. data/doc/SDM/RoleAttachments.html +63 -60
  95. data/doc/SDM/RoleCreateResponse.html +75 -23
  96. data/doc/SDM/RoleDeleteResponse.html +69 -17
  97. data/doc/SDM/RoleGetResponse.html +75 -22
  98. data/doc/SDM/RoleGrant.html +77 -24
  99. data/doc/SDM/RoleGrantCreateResponse.html +75 -23
  100. data/doc/SDM/RoleGrantDeleteResponse.html +69 -18
  101. data/doc/SDM/RoleGrantGetResponse.html +75 -22
  102. data/doc/SDM/RoleGrants.html +63 -60
  103. data/doc/SDM/RoleUpdateResponse.html +75 -23
  104. data/doc/SDM/Roles.html +76 -74
  105. data/doc/SDM/SQLServer.html +132 -52
  106. data/doc/SDM/SSH.html +135 -40
  107. data/doc/SDM/SSHCert.html +373 -0
  108. data/doc/SDM/Service.html +95 -25
  109. data/doc/SDM/Snowflake.html +122 -44
  110. data/doc/SDM/Sybase.html +117 -40
  111. data/doc/SDM/SybaseIQ.html +117 -40
  112. data/doc/SDM/Teradata.html +117 -40
  113. data/doc/SDM/UpdateResponseMetadata.html +60 -10
  114. data/doc/SDM/User.html +104 -31
  115. data/doc/V1.html +35 -11
  116. data/doc/V1/AccountAttachments.html +10 -6
  117. data/doc/V1/AccountAttachments/Service.html +11 -8
  118. data/doc/V1/AccountGrants.html +10 -6
  119. data/doc/V1/AccountGrants/Service.html +11 -9
  120. data/doc/V1/Accounts.html +10 -6
  121. data/doc/V1/Accounts/Service.html +12 -10
  122. data/doc/V1/Nodes.html +10 -6
  123. data/doc/V1/Nodes/Service.html +13 -13
  124. data/doc/V1/Resources.html +10 -6
  125. data/doc/V1/Resources/Service.html +10 -6
  126. data/doc/V1/RoleAttachments.html +10 -6
  127. data/doc/V1/RoleAttachments/Service.html +11 -11
  128. data/doc/V1/RoleGrants.html +10 -6
  129. data/doc/V1/RoleGrants/Service.html +11 -11
  130. data/doc/V1/Roles.html +10 -6
  131. data/doc/V1/Roles/Service.html +11 -12
  132. data/doc/V1/Tags.html +113 -0
  133. data/doc/created.rid +45 -37
  134. data/doc/css/fonts.css +6 -6
  135. data/doc/css/rdoc.css +22 -1
  136. data/doc/examples/Gemfile.html +14 -4
  137. data/doc/examples/Gemfile_lock.html +14 -4
  138. data/doc/examples/README_md.html +14 -4
  139. data/doc/examples/okta-sync/Gemfile.html +105 -0
  140. data/doc/examples/okta-sync/Gemfile_lock.html +146 -0
  141. data/doc/index.html +23 -10
  142. data/doc/js/darkfish.js +23 -100
  143. data/doc/js/navigation.js +4 -41
  144. data/doc/js/navigation.js.gz +0 -0
  145. data/doc/js/search.js +32 -31
  146. data/doc/js/search_index.js +1 -1
  147. data/doc/js/search_index.js.gz +0 -0
  148. data/doc/js/searcher.js +7 -6
  149. data/doc/js/searcher.js.gz +0 -0
  150. data/doc/lib/version.html +16 -6
  151. data/doc/strongdm_gemspec.html +15 -5
  152. data/doc/table_of_contents.html +1587 -771
  153. data/examples/Gemfile +2 -2
  154. data/examples/ldap-sync/ldapSync.rb +290 -0
  155. data/examples/listUsers.rb +8 -8
  156. data/examples/okta-sync/Gemfile +4 -0
  157. data/examples/okta-sync/Gemfile.lock +38 -0
  158. data/examples/okta-sync/matchers.yml +11 -0
  159. data/examples/okta-sync/oktaSync.rb +173 -0
  160. data/examples/panicButton.rb +103 -119
  161. data/lib/errors/errors.rb +55 -53
  162. data/lib/grpc/account_attachments_pb.rb +9 -14
  163. data/lib/grpc/account_attachments_services_pb.rb +7 -8
  164. data/lib/grpc/account_grants_pb.rb +10 -10
  165. data/lib/grpc/account_grants_services_pb.rb +8 -9
  166. data/lib/grpc/accounts_pb.rb +12 -9
  167. data/lib/grpc/accounts_services_pb.rb +7 -8
  168. data/lib/grpc/drivers_pb.rb +104 -7
  169. data/lib/grpc/nodes_pb.rb +12 -9
  170. data/lib/grpc/nodes_services_pb.rb +7 -8
  171. data/lib/grpc/options_pb.rb +16 -5
  172. data/lib/grpc/plumbing.rb +4367 -4084
  173. data/lib/grpc/protoc-gen-swagger/options/annotations_pb.rb +4 -4
  174. data/lib/grpc/resources_pb.rb +9 -9
  175. data/lib/grpc/resources_services_pb.rb +7 -8
  176. data/lib/grpc/role_attachments_pb.rb +9 -9
  177. data/lib/grpc/role_attachments_services_pb.rb +7 -8
  178. data/lib/grpc/role_grants_pb.rb +9 -9
  179. data/lib/grpc/role_grants_services_pb.rb +7 -8
  180. data/lib/grpc/roles_pb.rb +11 -9
  181. data/lib/grpc/roles_services_pb.rb +7 -8
  182. data/lib/grpc/spec_pb.rb +7 -8
  183. data/lib/grpc/tags_pb.rb +36 -0
  184. data/lib/models/porcelain.rb +4966 -4017
  185. data/lib/strongdm.rb +94 -95
  186. data/lib/svc.rb +1200 -1224
  187. data/lib/version +16 -2
  188. data/lib/version.rb +6 -6
  189. data/strongdm.gemspec +10 -10
  190. metadata +27 -15
@@ -1,3 +1,3 @@
1
- source 'https://rubygems.org'
1
+ source "https://rubygems.org"
2
2
 
3
- gem 'strongdm'
3
+ gem "strongdm"
@@ -0,0 +1,290 @@
1
+ # Copyright 2020 StrongDM Inc
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ #
15
+ require "yaml"
16
+ require "strongdm"
17
+ require "net/ldap"
18
+ require "optparse"
19
+ require "logger"
20
+
21
+ # This script reads from an LDAP server and does the following writes in StrongDM:
22
+ # - creates roles for each configured organizational unit (OU)
23
+ # - creates accounts for users in those OUs
24
+ # - attaches those accounts to their corresponding roles
25
+ # - grants resources to these roles based on configured filters
26
+ # - detaches accounts from roles, deletes accounts, and deletes grants as necessary
27
+
28
+ # IMPORTANT CAVEATS:
29
+ # - this script can pull existing StrongDM users into its purview. then, if the
30
+ # user is removed from LDAP, it will delete the user.
31
+ # - if you need to delete an entire role / OU, you'll need to do it manually.
32
+ # this script does not touch roles that are not in the config file.
33
+
34
+ # Example config file:
35
+
36
+ # organizationalUnits:
37
+ # - dn: OU=Other-OU,DC=j42,DC=xyz
38
+ # role: Other-OU
39
+ # resources:
40
+ # - name:*Other-OU*
41
+ # - name:*Multi*
42
+ # - dn: OU=admins,DC=j42,DC=xyz
43
+ # role: admins
44
+ # resources:
45
+ # - name:*admins*
46
+ # - dn: OU=People,DC=j42,DC=xyz
47
+ # role: People
48
+ # resources:
49
+ # - name:*People*
50
+
51
+ SDM_API_ACCESS_KEY = ENV.fetch("SDM_API_ACCESS_KEY", "")
52
+ SDM_API_SECRET_KEY = ENV.fetch("SDM_API_SECRET_KEY", "")
53
+ LDAP_HOST = ENV.fetch("LDAP_HOST", "")
54
+ LDAP_BIND_DN = ENV.fetch("LDAP_BIND_DN", "")
55
+ LDAP_PASSWORD = ENV.fetch("LDAP_PASSWORD", "")
56
+
57
+ # gets the first item in a list or generator
58
+ def first(attrib)
59
+ result = nil
60
+ attrib.each do |item|
61
+ if result == nil
62
+ result = item
63
+ end
64
+ end
65
+ result
66
+ end
67
+
68
+ def ldap_sync
69
+ if SDM_API_ACCESS_KEY == "" || SDM_API_SECRET_KEY == "" || LDAP_BIND_DN == ""
70
+ puts "SDM_API_ACCESS_KEY, SDM_API_SECRET_KEY, and LDAP_BIND_DN must be set"
71
+ exit 1
72
+ end
73
+
74
+ plan = false
75
+ verbose = false
76
+ configPath = "config.yml"
77
+ OptionParser.new do |opts|
78
+ opts.banner = "Usage ldapSync.rb [options]"
79
+ opts.on("-p", "--plan", "calculate changes but do not apply them") do |p|
80
+ plan = p
81
+ end
82
+ opts.on("-v", "--verbose", "print detailed report") do |v|
83
+ verbose = v
84
+ end
85
+ opts.on("-c", "--config FILE", "specify path to config YAML file (default: 'config.yml')") do |v|
86
+ configPath = v
87
+ end
88
+ end.parse!
89
+
90
+ begin
91
+ config = YAML.load(File.read(configPath))
92
+ rescue StandardError => ex
93
+ raise ex, "failed to parse #{configPath}"
94
+ end
95
+
96
+ begin
97
+ sdmClient = SDM::Client.new(SDM_API_ACCESS_KEY, SDM_API_SECRET_KEY, host: "api.strongdmdev.com:443")
98
+ rescue SDM::RPCError => ex
99
+ raise ex, "failed to create StrongDM client"
100
+ end
101
+
102
+ ldap = Net::LDAP.new
103
+ ldap.host = LDAP_HOST
104
+ ldap.auth LDAP_BIND_DN, LDAP_PASSWORD
105
+ if not ldap.bind
106
+ puts "failed to bind LDAP connection - authentication error"
107
+ exit 1
108
+ end
109
+
110
+ sdmRoles = {} # map of name to ID
111
+ sdmAccounts = {} # map of email to id
112
+ sdmResources = {} # map of ID to name
113
+ sdmAccountsById = {} # map of id to { :email, :firstName, :lastName }
114
+ sdmAccountsWithAttachments = {} # map of email to id of all accounts that are in the roles we're interested in
115
+ sdmAccountAttachments = {} # map of role name to list of emails
116
+ sdmRoleGrants = {} # map of role name to list of { :resourceId, :grantId }
117
+ ldapRoles = [] # list of names
118
+ ldapAccounts = {} # map of email to { :firstName, :lastName }
119
+ ldapAccountAttachments = {} # map of role name to list of emails
120
+ desiredRoleGrants = {} # map of role name to list of resource IDs
121
+
122
+ # get SDM accounts
123
+ sdmClient.accounts.list("").each do |account|
124
+ sdmAccounts[account.email] = account.id
125
+ sdmAccountsById[account.id] = { :email => account.email, :firstName => account.first_name, :lastName => account.last_name }
126
+ end
127
+
128
+ # get SDM resources
129
+ sdmClient.resources.list("").each do |resource|
130
+ sdmResources[resource.id] = resource.name
131
+ end
132
+
133
+ # loop through OUs
134
+ config["organizationalUnits"].each do |ou|
135
+
136
+ # get SDM state for this OU
137
+ role = first(sdmClient.roles.list("name:?", ou["role"]))
138
+ if role
139
+ sdmRoles[role.name] = role.id
140
+
141
+ # get accounts attached to this role
142
+ accountEmails = []
143
+ sdmClient.account_attachments.list("roleid:?", role.id).each do |attachment|
144
+ sdmAccount = sdmAccountsById[attachment.account_id]
145
+ email = sdmAccount[:email]
146
+ sdmAccountsWithAttachments[email] = attachment.account_id
147
+ accountEmails.push(email)
148
+ end
149
+ sdmAccountAttachments[role.name] = accountEmails
150
+
151
+ # get resources granted to this role
152
+ roleGrants = []
153
+ sdmClient.role_grants.list("roleid:?", role.id).each do |grant|
154
+ roleGrants.push({ :resourceId => grant.resource_id, :grantId => grant.id })
155
+ end
156
+ sdmRoleGrants[role.name] = roleGrants
157
+
158
+ # get resources that we want to grant to this role
159
+ filteredResources = {} # map of resource ID to true (to prevent duplicates)
160
+ filters = ou["resources"] # list of filter strings
161
+ if filters
162
+ filters.each do |filter|
163
+ sdmClient.resources.list(filter).each do |resource|
164
+ filteredResources[resource.id] = true
165
+ end
166
+ end
167
+ desiredRoleGrants[role.name] = filteredResources.keys
168
+ end
169
+ end
170
+
171
+ # get LDAP state for this OU
172
+ ldapRoles.push(ou["role"].to_s)
173
+ roleAccounts = []
174
+ ldap.search(:base => ou["dn"], :filter => Net::LDAP::Filter.eq("objectclass", "user"), :return_result => false) do |entry|
175
+ ldapAccounts[first(entry.mail).to_s] = {
176
+ :firstName => first(entry.givenname).to_s,
177
+ :lastName => first(entry.sn).to_s,
178
+ }
179
+ roleAccounts.push(first(entry.mail).to_s)
180
+ end
181
+ ldapAccountAttachments[ou["role"].to_s] = roleAccounts
182
+ end
183
+
184
+ # compute diff
185
+ report = {
186
+ :createRoles => [],
187
+ :deleteAccounts => [],
188
+ :updateAccounts => [],
189
+ :createAccounts => [],
190
+ :createAccountAttachments => [],
191
+ :deleteAccountAttachments => [],
192
+ :deleteRoleGrants => [],
193
+ :createRoleGrants => [],
194
+ }
195
+ # createRoles
196
+ ldapRoles.each do |roleName|
197
+ next if sdmRoles[roleName]
198
+ report[:createRoles].push(roleName)
199
+ next if plan
200
+ response = sdmClient.roles.create(SDM::Role.new(name: roleName))
201
+ sdmRoles[roleName] = response.role.id
202
+ end
203
+ # deleteAccounts
204
+ sdmAccountsWithAttachments.each do |email, id|
205
+ next if ldapAccounts[email]
206
+ report[:deleteAccounts].push(email)
207
+ next if plan
208
+ sdmClient.accounts.delete(id)
209
+ end
210
+ # updateAccounts
211
+ sdmAccountsWithAttachments.each do |email, id|
212
+ ldapAccount = ldapAccounts[email]
213
+ next if not ldapAccount
214
+ sdmAccount = sdmAccountsById[id]
215
+ next if sdmAccount[:firstName] == ldapAccount[:firstName] and sdmAccount[:lastName] == ldapAccount[:lastName]
216
+ report[:updateAccounts].push(email)
217
+ next if plan
218
+ sdmClient.accounts.update(SDM::User.new(id: id, first_name: ldapAccount[:firstName], last_name: ldapAccount[:lastName]))
219
+ end
220
+ # createAccounts
221
+ ldapAccounts.each do |email, account|
222
+ next if sdmAccounts[email]
223
+ report[:createAccounts].push(email)
224
+ next if plan
225
+ response = sdmClient.accounts.create(SDM::User.new(email: email, first_name: account[:firstName], last_name: account[:lastName]))
226
+ sdmAccounts[response.account.email] = response.account.id
227
+ end
228
+ # deleteAccountAttachments
229
+ sdmAccountAttachments.each do |roleName, accounts|
230
+ roleId = sdmRoles[roleName]
231
+ ldapAccountsInRole = ldapAccountAttachments[roleName]
232
+ accounts.each do |email|
233
+ next if ldapAccountsInRole and ldapAccountsInRole.include? email
234
+ report[:deleteAccountAttachments].push({ :role => roleName, :account => email })
235
+ next if plan
236
+ accountId = sdmAccounts[email]
237
+ attachment = first(sdmClient.account_attachments.list("accountid:? roleid:?", accountId, roleId))
238
+ next if not attachment # already deleted by the deleteAccounts step
239
+ sdmClient.account_attachments.delete(attachment.id)
240
+ end
241
+ end
242
+ # createAccountAttachments
243
+ ldapAccountAttachments.each do |roleName, accounts|
244
+ roleId = sdmRoles[roleName]
245
+ sdmAccountsInRole = sdmAccountAttachments[roleName]
246
+ accounts.each do |email|
247
+ next if sdmAccountsInRole and sdmAccountsInRole.include? email
248
+ report[:createAccountAttachments].push({ :role => roleName, :account => email })
249
+ accountId = sdmAccounts[email]
250
+ next if plan
251
+ sdmClient.account_attachments.create(SDM::AccountAttachment.new(account_id: accountId, role_id: roleId))
252
+ end
253
+ end
254
+ # deleteRoleGrants
255
+ sdmRoleGrants.each do |roleName, roleGrants|
256
+ desired = desiredRoleGrants[roleName]
257
+ roleGrants.each do |grant|
258
+ next if desired and desired.include? grant[:resourceId]
259
+ resourceName = sdmResources[grant[:resourceId]]
260
+ report[:deleteRoleGrants].push({ :role => roleName, :resource => resourceName })
261
+ next if plan
262
+ sdmClient.role_grants.delete(grant[:grantId])
263
+ end
264
+ end
265
+ # createRoleGrants
266
+ desiredRoleGrants.each do |roleName, roleGrants|
267
+ roleId = sdmRoles[roleName]
268
+ existing = sdmRoleGrants[roleName]
269
+ roleGrants.each do |resourceId|
270
+ next if existing and existing.find { |existingGrant| existingGrant[:resourceId] == resourceId }
271
+ resourceName = sdmResources[resourceId]
272
+ report[:createRoleGrants].push({ :role => roleName, :resource => resourceName })
273
+ next if plan
274
+ sdmClient.role_grants.create(SDM::RoleGrant.new(role_id: roleId, resource_id: resourceId))
275
+ end
276
+ end
277
+ if verbose
278
+ puts JSON.pretty_generate(report)
279
+ else
280
+ puts "Create #{report[:createRoles].length} roles"
281
+ puts "Delete #{report[:deleteAccounts].length} accounts"
282
+ puts "Create #{report[:createAccounts].length} accounts"
283
+ puts "Delete #{report[:deleteAccountAttachments].length} account attachments"
284
+ puts "Create #{report[:createAccountAttachments].length} account attachments"
285
+ puts "Delete #{report[:deleteRoleGrants].length} role grants"
286
+ puts "Create #{report[:createRoleGrants].length} role grants"
287
+ end
288
+ end
289
+
290
+ ldap_sync
@@ -1,21 +1,21 @@
1
1
  # Copyright 2020 StrongDM Inc
2
- #
2
+ #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
5
5
  # You may obtain a copy of the License at
6
- #
6
+ #
7
7
  # http://www.apache.org/licenses/LICENSE-2.0
8
- #
8
+ #
9
9
  # Unless required by applicable law or agreed to in writing, software
10
10
  # distributed under the License is distributed on an "AS IS" BASIS,
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
- #
14
+ #
15
15
  require "strongdm"
16
16
 
17
- client = SDM::Client.new(ENV['SDM_API_ACCESS_KEY'], ENV['SDM_API_SECRET_KEY'])
18
- users = client.accounts.list('')
17
+ client = SDM::Client.new(ENV["SDM_API_ACCESS_KEY"], ENV["SDM_API_SECRET_KEY"])
18
+ users = client.accounts.list("")
19
19
  users.each { |user|
20
- p user
21
- }
20
+ p user
21
+ }
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "strongdm"
4
+ gem "oktakit"
@@ -0,0 +1,38 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ addressable (2.7.0)
5
+ public_suffix (>= 2.0.2, < 5.0)
6
+ faraday (1.0.0)
7
+ multipart-post (>= 1.2, < 3)
8
+ google-protobuf (3.11.4)
9
+ googleapis-common-protos-types (1.0.4)
10
+ google-protobuf (~> 3.0)
11
+ grpc (1.27.0)
12
+ google-protobuf (~> 3.11)
13
+ googleapis-common-protos-types (~> 1.0)
14
+ grpc-tools (1.27.0)
15
+ ipaddr (1.2.2)
16
+ multipart-post (2.1.1)
17
+ oktakit (0.2.0)
18
+ sawyer (~> 0.8.1)
19
+ openssl (2.1.2)
20
+ ipaddr
21
+ public_suffix (4.0.3)
22
+ sawyer (0.8.2)
23
+ addressable (>= 2.3.5)
24
+ faraday (> 0.8, < 2.0)
25
+ strongdm (1.0.0)
26
+ grpc (~> 1.27.0, >= 1.27.0)
27
+ grpc-tools (~> 1.27.0, >= 1.27.0)
28
+ openssl (~> 2.1.2, >= 2.1.2)
29
+
30
+ PLATFORMS
31
+ ruby
32
+
33
+ DEPENDENCIES
34
+ oktakit
35
+ strongdm
36
+
37
+ BUNDLED WITH
38
+ 1.17.2
@@ -0,0 +1,11 @@
1
+ ---
2
+ groups:
3
+ -
4
+ name: db/mongo
5
+ resources:
6
+ - type:mongo name:don*
7
+ - type:ssh name:dev*
8
+ -
9
+ name: app/web
10
+ resources:
11
+ - type:ssh name:dev-web*
@@ -0,0 +1,173 @@
1
+ # Copyright 2020 StrongDM Inc
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ #
15
+ require "yaml"
16
+ require "strongdm"
17
+ require "oktakit"
18
+ require "optparse"
19
+
20
+ SDM_API_ACCESS_KEY = ENV.fetch("SDM_API_ACCESS_KEY", "")
21
+ SDM_API_SECRET_KEY = ENV.fetch("SDM_API_SECRET_KEY", "")
22
+ OKTA_CLIENT_TOKEN = ENV.fetch("OKTA_CLIENT_TOKEN", "")
23
+ OKTA_CLIENT_ORGURL = ENV.fetch("OKTA_CLIENT_ORGURL", "")
24
+
25
+ def okta_sync
26
+ if SDM_API_ACCESS_KEY == "" || SDM_API_SECRET_KEY == "" || OKTA_CLIENT_TOKEN == "" || OKTA_CLIENT_ORGURL == ""
27
+ puts "SDM_API_ACCESS_KEY, SDM_API_SECRET_KEY, OKTA_CLIENT_TOKEN, and OKTA_CLIENT_ORGURL must be set"
28
+ exit
29
+ end
30
+
31
+ report = {
32
+ :start => Time.now,
33
+
34
+ :oktaUsersCount => 0,
35
+ :oktaUsers => [],
36
+
37
+ :sdmUsersCount => 0,
38
+ :sdmUsers => [],
39
+
40
+ :bothUsersCount => 0,
41
+
42
+ :sdmResourcesCount => 0,
43
+ :sdmResources => {},
44
+
45
+ :permissionsGranted => 0,
46
+ :permissionsRevoked => 0,
47
+ :grants => [],
48
+ :revocations => [],
49
+
50
+ :matchers => {},
51
+ }
52
+
53
+ plan = false
54
+ verbose = false
55
+ OptionParser.new do |opts|
56
+ opts.banner = "Usage oktaSync.rb [options]"
57
+ opts.on("-p", "--plan", "calculate changes but do not apply them") do |p|
58
+ plan = p
59
+ end
60
+ opts.on("-v", "--verbose", "print detailed report") do |v|
61
+ verbose = v
62
+ end
63
+ end.parse!
64
+
65
+ client = SDM::Client.new(SDM_API_ACCESS_KEY, SDM_API_SECRET_KEY)
66
+ okta_client = Oktakit.new(token: OKTA_CLIENT_TOKEN, api_endpoint: OKTA_CLIENT_ORGURL + "/api/v1")
67
+ matchers = YAML.load(File.read("matchers.yml"))
68
+ report[:matchers] = matchers
69
+
70
+ all_users = okta_client.list_users({
71
+ 'query': {
72
+ 'search': "profile.department eq \"Engineering\" and (status eq \"ACTIVE\")",
73
+ },
74
+ })
75
+
76
+ okta_users = Array.new()
77
+ all_users[0].each { |u|
78
+ groups = okta_client.get_member_groups(u.id)
79
+ group_names = Array.new()
80
+ groups[0].each { |ug|
81
+ group_names.push(ug.profile.name)
82
+ }
83
+ okta_users.push({ :login => u.profile.login, :first_name => u.profile.firstName, :last_name => u.profile.LastName, :groups => group_names })
84
+ }
85
+ report[:oktaUsers] = okta_users
86
+ report[:oktaUsersCount] = okta_users.size
87
+
88
+ accounts = client.accounts.list("type:user").map { |a| [a.email, a] }.to_h
89
+ report[:sdmUsers] = accounts
90
+ report[:sdmUsersCount] = accounts.size
91
+ grants = client.account_grants.list("").map { |ag| ag }
92
+
93
+ current = {}
94
+ grants.each { |g|
95
+ current[g.account_id] = [] if not current[g.account_id]
96
+ current[g.account_id].push({ :resource_id => g.resource_id, :id => g.id })
97
+ }
98
+
99
+ desired = {}
100
+ overlapping = 0
101
+ matchers["groups"].each { |group|
102
+ group["resources"].each { |resourceQuery|
103
+ client.resources.list(resourceQuery).each { |res|
104
+ report[:sdmResources][res.id] = res
105
+ okta_users.each { |u|
106
+ if u[:groups].include? group["name"]
107
+ account = accounts[u[:login]]
108
+ if account != nil
109
+ overlapping += 1
110
+ desired[account.id] = [] if not desired[account.id]
111
+ desired[account.id].push(res.id)
112
+ end
113
+ end
114
+ }
115
+ }
116
+ }
117
+ }
118
+ report[:bothUsersCount] = overlapping
119
+ report[:sdmResourcesCount] = report[:sdmResources].size
120
+
121
+ revocations = 0
122
+ current.each { |aid, curRes|
123
+ desRes = desired[aid]
124
+ desRes = [] if not desired[aid]
125
+ curRes.each { |r|
126
+ if not(desRes.include? r[:resource_id])
127
+ if plan
128
+ puts "Plan: revoke %s from user %s\n" % [r[:resource_id], aid]
129
+ else
130
+ client.account_grants.delete(r[:id])
131
+ end
132
+ report[:revocations].push(r[:id])
133
+ revocations += 1
134
+ end
135
+ }
136
+ }
137
+ report[:permissionsRevoked] = revocations
138
+
139
+ grants = 0
140
+ desired.each { |aid, desRes|
141
+ curRes = current[aid]
142
+ curRes = [] if not current[aid]
143
+ desRes.each { |r|
144
+ if not(curRes.map { |c| c[:resource_id] }.include? r)
145
+ ag = SDM::AccountGrant.new()
146
+ ag.account_id = aid
147
+ ag.resource_id = r
148
+ if plan
149
+ puts "Plan: grant %s to user %s\n" % [r, aid]
150
+ else
151
+ client.account_grants.create(ag)
152
+ end
153
+ report[:grants].push(ag)
154
+ grants += 1
155
+ end
156
+ }
157
+ }
158
+ report[:permissionsGranted] = grants
159
+
160
+ report[:complete] = Time.now
161
+
162
+ if verbose
163
+ puts report.to_json
164
+ else
165
+ puts "%d Okta users, %d strongDM users, %d overlapping users, %d grants, %d revocations" % [okta_users.size, accounts.size, overlapping, grants, revocations]
166
+ end
167
+ end
168
+
169
+ begin
170
+ okta_sync
171
+ rescue StandardError => ex
172
+ puts "cannot synchronize with okta: " + ex.to_s
173
+ end