cloudflare_client_rb 4.0.0

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.
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