cloudflare_client_rb 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +7 -0
  2. data/lib/cloudflare_client.rb +211 -0
  3. data/lib/cloudflare_client/certificate.rb +40 -0
  4. data/lib/cloudflare_client/organization.rb +26 -0
  5. data/lib/cloudflare_client/organization/access_rule.rb +76 -0
  6. data/lib/cloudflare_client/organization/invite.rb +53 -0
  7. data/lib/cloudflare_client/organization/member.rb +37 -0
  8. data/lib/cloudflare_client/organization/railgun.rb +79 -0
  9. data/lib/cloudflare_client/organization/role.rb +19 -0
  10. data/lib/cloudflare_client/railgun.rb +63 -0
  11. data/lib/cloudflare_client/version.rb +3 -0
  12. data/lib/cloudflare_client/virtual_dns_cluster.rb +133 -0
  13. data/lib/cloudflare_client/virtual_dns_cluster/analytic.rb +38 -0
  14. data/lib/cloudflare_client/zone.rb +129 -0
  15. data/lib/cloudflare_client/zone/analytics.rb +56 -0
  16. data/lib/cloudflare_client/zone/base.rb +9 -0
  17. data/lib/cloudflare_client/zone/custom_hostname.rb +86 -0
  18. data/lib/cloudflare_client/zone/custom_page.rb +28 -0
  19. data/lib/cloudflare_client/zone/custom_ssl.rb +62 -0
  20. data/lib/cloudflare_client/zone/dns.rb +66 -0
  21. data/lib/cloudflare_client/zone/firewall.rb +3 -0
  22. data/lib/cloudflare_client/zone/firewall/access_rule.rb +87 -0
  23. data/lib/cloudflare_client/zone/firewall/waf_package.rb +46 -0
  24. data/lib/cloudflare_client/zone/firewall/waf_package/base.rb +9 -0
  25. data/lib/cloudflare_client/zone/firewall/waf_package/rule.rb +46 -0
  26. data/lib/cloudflare_client/zone/firewall/waf_package/rule_group.rb +42 -0
  27. data/lib/cloudflare_client/zone/keyless_ssl.rb +56 -0
  28. data/lib/cloudflare_client/zone/log.rb +51 -0
  29. data/lib/cloudflare_client/zone/page_rule.rb +64 -0
  30. data/lib/cloudflare_client/zone/railgun_connections.rb +43 -0
  31. data/lib/cloudflare_client/zone/rate_limit.rb +73 -0
  32. data/lib/cloudflare_client/zone/ssl.rb +28 -0
  33. data/lib/cloudflare_client/zone/ssl/certificate_pack.rb +32 -0
  34. data/lib/cloudflare_client/zone/subscription.rb +55 -0
  35. metadata +76 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 53055d6f6789ba960ef43045e6b102ffb4e97baf
4
+ data.tar.gz: 3ef8b8314e81ae120c8c3711b75997d56e5f0e0e
5
+ SHA512:
6
+ metadata.gz: ba0a7b3a325441795cbfe180f351a8385bf5770b79c9b8b6008770438f9fbe4497af207ded9bc20f207482769c6418dee161e1b4311e669a2632031a60f4d468
7
+ data.tar.gz: f26ff45885f8efb43349310c4ea943522742b0dbbd320154e531104c2e2846b0c1b339af79c605656bc09c9e148beb779eb687e415a9640f6b73df897dd19000
@@ -0,0 +1,211 @@
1
+ ##
2
+ # General class for the client
3
+ class CloudflareClient
4
+ require 'json'
5
+ require 'faraday'
6
+ require 'date'
7
+ require 'byebug'
8
+ Dir[File.expand_path('../cloudflare_client/*.rb', __FILE__)].each { |f| require f }
9
+
10
+ API_BASE = 'https://api.cloudflare.com/client/v4'.freeze
11
+ VALID_BUNDLE_METHODS = %w[ubiquitous optimal force].freeze
12
+ VALID_DIRECTIONS = %w[asc desc].freeze
13
+ VALID_MATCHES = %w[any all].freeze
14
+
15
+ POSSIBLE_API_SETTINGS = %w[
16
+ advanced_ddos
17
+ always_online
18
+ automatic_https_rewrites
19
+ browser_cache_ttl
20
+ browser_check
21
+ cache_level
22
+ challenge_ttl
23
+ development_mode
24
+ email_obfuscation
25
+ hotlink_protection
26
+ ip_geolocation
27
+ ipv6
28
+ minify
29
+ mobile_redirect
30
+ mirage
31
+ origin_error_page_pass_thru
32
+ opportunistic_encryption
33
+ polish
34
+ webp
35
+ prefetch_preload
36
+ response_buffering
37
+ rocket_loader
38
+ security_header
39
+ security_level
40
+ server_side_exclude
41
+ sort_query_string_for_cache
42
+ ssl
43
+ tls_1_2_only
44
+ tls_1_3
45
+ tls_client_auth
46
+ true_client_ip_header
47
+ waf
48
+ http2
49
+ pseudo_ipv4
50
+ websockets
51
+ ].freeze
52
+
53
+ def initialize(auth_key: nil, email: nil)
54
+ raise('Missing auth_key') if auth_key.nil?
55
+ raise('missing email') if email.nil?
56
+ @cf_client ||= build_client(auth_key: auth_key, email: email)
57
+ end
58
+
59
+ #TODO: add the time based stuff
60
+
61
+ #TODO: cloudflare IPs
62
+ #TODO: AML
63
+ #TODO: load balancer monitors
64
+ #TODO: load balancer pools
65
+ #TODO: org load balancer monitors
66
+ #TODO: org load balancer pools
67
+ #TODO: load balancers
68
+
69
+ private
70
+
71
+ def bundle_method_check(bundle_method)
72
+ unless VALID_BUNDLE_METHODS.include?(bundle_method)
73
+ raise("valid bundle methods are #{VALID_BUNDLE_METHODS.flatten}")
74
+ end
75
+ end
76
+
77
+ def date_rfc3339?(ts)
78
+ begin
79
+ DateTime.rfc3339(ts)
80
+ rescue ArgumentError
81
+ return false
82
+ end
83
+ true
84
+ end
85
+
86
+ def iso8601_check(name, ts)
87
+ DateTime.iso8601(ts)
88
+ rescue ArgumentError
89
+ raise "#{name} must be a valid iso8601 timestamp"
90
+ end
91
+
92
+ def timestamp_check(name, ts)
93
+ Time.at(ts).to_datetime
94
+ rescue TypeError
95
+ raise "#{name} must be a valid unix timestamp"
96
+ end
97
+
98
+ def id_check(name, id)
99
+ raise "#{name} required" if id.nil?
100
+ end
101
+
102
+ def valid_value_check(name, value, valid_values)
103
+ raise "#{name} must be one of #{valid_values}" unless valid_values.include?(value)
104
+ end
105
+
106
+ def non_empty_array_check(name, array)
107
+ raise "#{name} must be an array of #{name}" unless array.is_a?(Array) && !array.empty?
108
+ end
109
+
110
+ def non_empty_hash_check(name, hash)
111
+ raise "#{name} must be an hash of #{name}" unless hash.is_a?(Hash) && !hash.empty?
112
+ end
113
+
114
+ def basic_type_check(name, value, type)
115
+ raise "#{name} must be a #{type}" unless value.is_a?(type)
116
+ end
117
+
118
+ def max_length_check(name, value, max_length=32)
119
+ raise "the length of #{name} must not exceed #{max_length}" unless value.length <= max_length
120
+ end
121
+
122
+ def range_check(name, value, min=nil, max=nil)
123
+ if min && max
124
+ raise "#{name} must be between #{min} and #{max}" unless value >= min && value <= max
125
+ elsif min
126
+ raise "#{name} must be equal or larger than #{min}" unless value >= min
127
+ elsif max
128
+ raise "#{name} must be equal or less than #{max}" unless value <= max
129
+ end
130
+ end
131
+
132
+ def build_client(params)
133
+ raise('Missing auth_key') if params[:auth_key].nil?
134
+ raise('Missing auth email') if params[:email].nil?
135
+ # we need multipart form encoding for some of these operations
136
+ client = Faraday.new(url: API_BASE) do |conn|
137
+ conn.request :multipart
138
+ conn.request :url_encoded
139
+ conn.adapter :net_http
140
+ end
141
+ client.headers['X-Auth-Key'] = params[:auth_key]
142
+ client.headers['X-Auth-User-Service-Key '] = params[:auth_key] #FIXME, is this always the same?
143
+ client.headers['X-Auth-Email'] = params[:email]
144
+ client.headers['Content-Type'] = 'application/json'
145
+ client
146
+ end
147
+
148
+ def cf_post(path: nil, data: {})
149
+ raise('No data to post') if data.empty?
150
+ result = @cf_client.post do |request|
151
+ request.url(API_BASE + path) unless path.nil?
152
+ request.body = data.to_json
153
+ end
154
+ raise(JSON.parse(result.body).dig('errors').first.to_s) unless result.status == 200
155
+ JSON.parse(result.body, symbolize_names: true)
156
+ end
157
+
158
+ def cf_get(path: nil, params: {}, raw: nil, extra_headers: {})
159
+ result = @cf_client.get do |request|
160
+ request.headers.merge!(extra_headers) unless extra_headers.empty?
161
+ request.url(API_BASE + path) unless path.nil?
162
+ unless params.nil?
163
+ request.params = params if params.values.any? { |i| !i.nil? }
164
+ end
165
+ end
166
+ raise(JSON.parse(result.body).dig('errors').first.to_s) unless result.status == 200
167
+ unless raw.nil?
168
+ return result.body
169
+ end
170
+ # we ask for compressed logs. uncompress if we get them
171
+ # as the user can always ask for raw stuff
172
+ if result.headers["content-encoding"] == 'gzip'
173
+ return Zlib::GzipReader.new(StringIO.new(result.body.to_s)).read
174
+ end
175
+ JSON.parse(result.body, symbolize_names: true)
176
+ end
177
+
178
+ def cf_put(path: nil, data: nil)
179
+ result = @cf_client.put do |request|
180
+ request.url(API_BASE + path) unless path.nil?
181
+ request.body = data.to_json unless data.nil?
182
+ end
183
+ raise(JSON.parse(result.body).dig('errors').first.to_s) unless result.status == 200
184
+ JSON.parse(result.body, symbolize_names: true)
185
+ end
186
+
187
+ def cf_patch(path: nil, data: {})
188
+ valid_response_codes = [200, 202]
189
+ result = @cf_client.patch do |request|
190
+ request.url(API_BASE + path) unless path.nil?
191
+ request.body = data.to_json unless data.empty?
192
+ end
193
+ raise(JSON.parse(result.body).dig('errors').first.to_s) unless valid_response_codes.include?(result.status)
194
+ JSON.parse(result.body, symbolize_names: true)
195
+ end
196
+
197
+ def cf_delete(path: nil, data: {})
198
+ result = @cf_client.delete do |request|
199
+ request.url(API_BASE + path) unless path.nil?
200
+ request.body = data.to_json unless data.empty?
201
+ end
202
+ raise(JSON.parse(result.body).dig('errors').first.to_s) unless result.status == 200
203
+ JSON.parse(result.body, symbolize_names: true)
204
+ end
205
+
206
+ def valid_setting?(name = nil)
207
+ return false if name.nil?
208
+ return false unless POSSIBLE_API_SETTINGS.include?(name)
209
+ true
210
+ end
211
+ end
@@ -0,0 +1,40 @@
1
+ class CloudflareClient::Certificate < CloudflareClient
2
+ VALID_REQUESTED_VALIDITIES = [7, 30, 90, 365, 730, 1095, 5475].freeze
3
+ VALID_REQUEST_TYPES = %w[origin-rsa origin-ecc keyless-certificate].freeze
4
+
5
+ ##
6
+ # cloudflare CA
7
+
8
+ ##
9
+ # list certificates
10
+ def list(zone_id: nil)
11
+ cf_get(path: '/certificates', params: {zone_id: zone_id})
12
+ end
13
+
14
+ ##
15
+ # create a certificate
16
+ def create(hostnames:, requested_validity: 5475, request_type: 'origin-rsa', csr: nil)
17
+ non_empty_array_check(:hostnames, hostnames)
18
+ valid_value_check(:requested_validity, requested_validity, VALID_REQUESTED_VALIDITIES)
19
+ valid_value_check(:request_type, request_type, VALID_REQUEST_TYPES)
20
+
21
+ data = {hostnames: hostnames, requested_validity: requested_validity, request_type: request_type}
22
+ data[:csr] = csr unless csr.nil?
23
+
24
+ cf_post(path: '/certificates', data: data)
25
+ end
26
+
27
+ ##
28
+ # details of a certificate
29
+ def show(id:)
30
+ id_check(:id, id)
31
+ cf_get(path: "/certificates/#{id}")
32
+ end
33
+
34
+ ##
35
+ # revoke a cert
36
+ def revoke(id:)
37
+ id_check(:id, id)
38
+ cf_delete(path: "/certificates/#{id}")
39
+ end
40
+ end
@@ -0,0 +1,26 @@
1
+ class CloudflareClient::Organization < CloudflareClient
2
+ Dir[File.expand_path('../organization/*.rb', __FILE__)].each {|f| require f}
3
+
4
+ attr_reader :org_id
5
+
6
+ ##
7
+ # Organization based operations
8
+ def initialize(args)
9
+ @org_id = args.delete(:org_id)
10
+ id_check(:org_id, org_id)
11
+ super
12
+ end
13
+
14
+ ##
15
+ # get an org's details
16
+ def show
17
+ cf_get(path: "/organizations/#{org_id}")
18
+ end
19
+
20
+ ##
21
+ # update a given org (only supports name)
22
+ def update(name: nil)
23
+ data = name.nil? ? {} : {name: name}
24
+ cf_patch(path: "/organizations/#{org_id}", data: data)
25
+ end
26
+ end
@@ -0,0 +1,76 @@
1
+ class CloudflareClient::Organization::AccessRule < CloudflareClient::Organization
2
+ VALID_MODES = %w[block challenge whitelist].freeze
3
+ VALID_CONFIG_TARGETS = %w[ip ip_range country_code].freeze
4
+ VALID_ORDERS = %w[configuration_target configuration_value mode].freeze
5
+
6
+ ##
7
+ # org level firewall rules
8
+ #
9
+
10
+ ##
11
+ # list access rules
12
+ def list(notes: nil, mode: nil, match: 'all', configuration_value: nil, order: nil, page: 1, per_page: 50, configuration_target: nil, direction: 'desc')
13
+ params = {page: page, per_page: per_page}
14
+
15
+ unless notes.nil?
16
+ basic_type_check(:notes, notes, String)
17
+ params[:notes] = notes
18
+ end
19
+
20
+ unless mode.nil?
21
+ valid_value_check(:mode, mode, VALID_MODES)
22
+ params[:mode] = mode
23
+ end
24
+
25
+ unless match.nil?
26
+ valid_value_check(:match, match, VALID_MATCHES)
27
+ params[:match] = match
28
+ end
29
+
30
+ params[:configuration_value] = configuration_value unless configuration_value.nil?
31
+
32
+ #FIXME: check this against the api
33
+ unless order.nil?
34
+ valid_value_check(:order, order, VALID_ORDERS)
35
+ params[:order] = order
36
+ end
37
+
38
+ unless configuration_target.nil?
39
+ valid_value_check(:configuration_target, configuration_target, VALID_CONFIG_TARGETS)
40
+ params[:configuration_target] = configuration_target
41
+ end
42
+
43
+ unless direction.nil?
44
+ valid_value_check(:direction, direction, VALID_DIRECTIONS)
45
+ params[:direction] = direction
46
+ end
47
+
48
+ cf_get(path: "/organizations/#{org_id}/firewall/access_rules/rules", params: params)
49
+ end
50
+
51
+ ##
52
+ # create access rule
53
+ def create(mode:, configuration:, notes: nil)
54
+ non_empty_hash_check(:configuration, configuration)
55
+ valid_value_check(:mode, mode, VALID_MODES)
56
+
57
+ #TODO: validate config objects?
58
+ data = {mode: mode, configuration: configuration}
59
+ data[:notes] = notes unless notes.nil?
60
+
61
+ cf_post(path: "/organizations/#{org_id}/firewall/access_rules/rules", data: data)
62
+ end
63
+
64
+ ##
65
+ # update access rule
66
+ # def update
67
+ #
68
+ # end
69
+
70
+ ##
71
+ # delete org access rule
72
+ def delete(id:)
73
+ id_check('id', id)
74
+ cf_delete(path: "/organizations/#{org_id}/firewall/access_rules/rules/#{id}")
75
+ end
76
+ end
@@ -0,0 +1,53 @@
1
+ class CloudflareClient::Organization::Invite < CloudflareClient::Organization
2
+ ##
3
+ # org invites
4
+
5
+ ##
6
+ # create an org invite
7
+ def create(email:, roles:, auto_accept: nil)
8
+ basic_type_check(:email, email, String)
9
+ max_length_check(:email, email, 90)
10
+ non_empty_array_check(:roles, roles)
11
+
12
+ data = {invited_member_email: email, roles: roles}
13
+
14
+ unless auto_accept.nil?
15
+ valid_value_check(:auto_accept, auto_accept, [true, false])
16
+ data[:auto_accept] = auto_accept
17
+ end
18
+
19
+ cf_post(path: "/organizations/#{org_id}/invites", data: data)
20
+ end
21
+
22
+ ##
23
+ # org invites
24
+ def list
25
+ cf_get(path: "/organizations/#{org_id}/invites")
26
+ end
27
+
28
+ ##
29
+ # org invite details
30
+ def show(id:)
31
+ id_check(:id, id)
32
+
33
+ cf_get(path: "/organizations/#{org_id}/invites/#{id}")
34
+ end
35
+
36
+ ##
37
+ # update an organization invites roles
38
+ def update(id:, roles:)
39
+ id_check(:id, id)
40
+ non_empty_array_check(:roles, roles)
41
+
42
+ data = {roles: roles}
43
+
44
+ cf_patch(path: "/organizations/#{org_id}/invites/#{id}", data: data)
45
+ end
46
+
47
+ ##
48
+ # cancel an organization invite
49
+ def delete(id:)
50
+ id_check(:id, id)
51
+ cf_delete(path: "/organizations/#{org_id}/invites/#{id}")
52
+ end
53
+ end
@@ -0,0 +1,37 @@
1
+ class CloudflareClient::Organization::Member < CloudflareClient::Organization
2
+ ##
3
+ # org members
4
+
5
+ ##
6
+ # list org members
7
+ def list
8
+ cf_get(path: "/organizations/#{org_id}/members")
9
+ end
10
+
11
+ ##
12
+ # org member details
13
+ def show(id:)
14
+ id_check(:id, id)
15
+
16
+ cf_get(path: "/organizations/#{org_id}/members/#{id}")
17
+ end
18
+
19
+ ##
20
+ # update org member roles
21
+ def update(id:, roles:)
22
+ id_check(:id, id)
23
+ non_empty_array_check(:roles, roles)
24
+
25
+ data = {roles: roles}
26
+
27
+ cf_patch(path: "/organizations/#{org_id}/members/#{id}", data: data)
28
+ end
29
+
30
+ ##
31
+ # remove org member
32
+ def delete(id:)
33
+ id_check(:id, id)
34
+
35
+ cf_delete(path: "/organizations/#{org_id}/members/#{id}")
36
+ end
37
+ end