terrafying-components 1.4.3 → 1.4.4

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.
@@ -0,0 +1,358 @@
1
+
2
+ require 'digest'
3
+ require 'netaddr'
4
+
5
+ require 'terrafying/components/ignition'
6
+ require 'terrafying/generator'
7
+
8
+
9
+ IN4MASK = 0xffffffff
10
+
11
+ def cidr_to_split_address(raw_cidr)
12
+ cidr = NetAddr::CIDR.create(raw_cidr)
13
+
14
+ masklen = 32 - cidr.bits
15
+ maskaddr = ((IN4MASK >> masklen) << masklen)
16
+
17
+ maskip = (0..3).map { |i|
18
+ (maskaddr >> (24 - 8 * i)) & 0xff
19
+ }.join('.')
20
+
21
+ return "#{cidr.first} #{maskip}"
22
+ end
23
+
24
+
25
+ module Terrafying
26
+
27
+ module Components
28
+
29
+ class VPN < Terrafying::Context
30
+
31
+ attr_reader :name, :cidr
32
+
33
+ def self.create_in(vpc, name, provider, options={})
34
+ VPN.new.create_in vpc, name, provider, options
35
+ end
36
+
37
+ def initialize()
38
+ super
39
+ end
40
+
41
+ def create_in(vpc, name, oauth2_provider, options={})
42
+ options = {
43
+ group: "uSwitch Developers",
44
+ cidr: "10.8.0.0/24",
45
+ public: true,
46
+ subnets: vpc.subnets.fetch(:public, []),
47
+ static: false,
48
+ route_all_traffic: false,
49
+ units: [],
50
+ tags: {},
51
+ service: {},
52
+ }.merge(options)
53
+
54
+ @name = name
55
+ @vpc = vpc
56
+ @cidr = options[:cidr]
57
+ @fqdn = vpc.zone.qualify(name)
58
+
59
+ if ! oauth2_provider.is_a?(Hash)
60
+ raise "You need to give a provider hash containing a type, client_id and client_secret"
61
+ end
62
+
63
+ has_provider = oauth2_provider[:type] != "none"
64
+
65
+ if has_provider and ! [:type, :client_id, :client_secret].all? {|k| oauth2_provider.has_key?(k) }
66
+ raise "You need to set type, client_id and client_secret"
67
+ end
68
+
69
+ units = [
70
+ openvpn_service,
71
+ openvpn_authz_service(options[:route_all_traffic]),
72
+ caddy_service(options[:ca])
73
+ ]
74
+ files = [
75
+ openvpn_conf,
76
+ openvpn_env,
77
+ openvpn_ip_delay,
78
+ caddy_conf(options[:ca], has_provider)
79
+ ]
80
+ keypairs = []
81
+
82
+ if has_provider
83
+ vpn_hash = Digest::SHA2.digest(vpc.name + name + oauth2_provider[:client_secret] + oauth2_provider[:client_id])
84
+ cookie_secret = Base64.strict_encode64(vpn_hash.byteslice(0,16))
85
+
86
+ units.push(oauth2_proxy_service(oauth2_provider, cookie_secret))
87
+ end
88
+
89
+ if options.has_key?(:ca)
90
+ keypairs.push(options[:ca].create_keypair_in(self, @fqdn))
91
+ end
92
+
93
+ if options[:static]
94
+ subnet = options[:subnets].first
95
+ instances = [{ subnet: subnet, ip_address: subnet.ip_addresses.first }]
96
+ else
97
+ instances = [{}]
98
+ end
99
+
100
+ @service = add! Service.create_in(
101
+ vpc, name,
102
+ {
103
+ public: options[:public],
104
+ ports: [22, 443, { number: 1194, type: "udp" }],
105
+ tags: options[:tags],
106
+ units: units + options[:units],
107
+ files: files,
108
+ keypairs: keypairs,
109
+ subnets: options[:subnets],
110
+ instances: instances,
111
+ iam_policy_statements: [
112
+ {
113
+ Effect: "Allow",
114
+ Action: [
115
+ "ec2:DescribeRouteTables",
116
+ ],
117
+ Resource: [
118
+ "*"
119
+ ]
120
+ }
121
+ ],
122
+ }.merge(options[:service])
123
+ )
124
+
125
+ if oauth2_provider[:type] == "azure" and oauth2_provider[:register]
126
+
127
+ provider :null, {}
128
+
129
+ resource :null_resource, "ad-app-configure", {
130
+ triggers: {
131
+ service_resources: @service.resources.join(","),
132
+ },
133
+ provisioner: [
134
+ {
135
+ "local-exec" => {
136
+ when: "create",
137
+ command: "#{File.expand_path(File.dirname(__FILE__))}/support/register-vpn '#{oauth2_provider[:client_id]}' '#{oauth2_provider[:tenant_id]}' '#{@fqdn}'"
138
+ },
139
+ },
140
+ {
141
+ "local-exec" => {
142
+ when: "destroy",
143
+ command: "#{File.expand_path(File.dirname(__FILE__))}/support/deregister-vpn '#{oauth2_provider[:client_id]}' '#{oauth2_provider[:tenant_id]}' '#{@fqdn}'"
144
+ }
145
+ },
146
+ ],
147
+ }
148
+ end
149
+
150
+ self
151
+ end
152
+
153
+ def openvpn_service
154
+ Ignition.container_unit(
155
+ "openvpn", "kylemanna/openvpn",
156
+ {
157
+ host_networking: true,
158
+ privileged: true,
159
+ volumes: [
160
+ "/etc/ssl/openvpn:/etc/ssl/openvpn:ro",
161
+ "/etc/openvpn:/etc/openvpn",
162
+ ],
163
+ required_units: [ "docker.service", "network-online.target", "openvpn-authz.service" ],
164
+ }
165
+ )
166
+ end
167
+
168
+ def openvpn_authz_service(route_all_traffic)
169
+ optional_arguments = []
170
+
171
+ if route_all_traffic
172
+ optional_arguments << "--route-all"
173
+ end
174
+
175
+ Ignition.container_unit(
176
+ "openvpn-authz", "quay.io/uswitch/openvpn-authz:stable",
177
+ {
178
+ host_networking: true,
179
+ volumes: [
180
+ "/etc/ssl/openvpn:/etc/ssl/openvpn",
181
+ "/var/openvpn-authz:/var/openvpn-authz",
182
+ ],
183
+ environment_variables: [
184
+ "AWS_REGION=#{aws.region}",
185
+ ],
186
+ arguments: optional_arguments + [
187
+ "--fqdn #{@fqdn}",
188
+ "--cache /var/openvpn-authz",
189
+ "/etc/ssl/openvpn",
190
+ ],
191
+ }
192
+ )
193
+ end
194
+
195
+ def oauth2_proxy_service(oauth2_provider, cookie_secret)
196
+ optional_arguments = []
197
+
198
+ if oauth2_provider.has_key?(:permit_groups)
199
+ optional_arguments << "-permit-groups '#{oauth2_provider[:permit_groups].join(",")}'"
200
+ end
201
+
202
+ Ignition.container_unit(
203
+ "oauth2_proxy", "quay.io/uswitch/oauth2_proxy:stable",
204
+ {
205
+ host_networking: true,
206
+ arguments: [
207
+ "-client-id='#{oauth2_provider[:client_id]}'",
208
+ "-client-secret='#{oauth2_provider[:client_secret]}'",
209
+ "-email-domain='*'",
210
+ "-cookie-secret='#{cookie_secret}'",
211
+ "-provider=#{oauth2_provider[:type]}",
212
+ "-http-address='0.0.0.0:4180'",
213
+ "-redirect-url='https://#{@fqdn}/oauth2/callback'",
214
+ "-upstream='http://localhost:8080'",
215
+ "-approval-prompt=''",
216
+ "-cookie-secure",
217
+ "-pass-access-token=true",
218
+ "-pass-groups",
219
+ ] + optional_arguments
220
+ }
221
+ )
222
+ end
223
+
224
+ def caddy_service(ca)
225
+ optional_volumes = []
226
+
227
+ if ca
228
+ optional_volumes << "/etc/ssl/#{ca.name}:/etc/ssl/#{ca.name}:ro"
229
+ end
230
+
231
+ Ignition.container_unit(
232
+ "caddy", "abiosoft/caddy:0.10.10",
233
+ {
234
+ host_networking: true,
235
+ volumes: [
236
+ "/etc/ssl/certs:/etc/ssl/cert:ro",
237
+ "/etc/caddy/Caddyfile:/etc/Caddyfile",
238
+ "/etc/caddy/certs:/etc/caddy/certs",
239
+ ] + optional_volumes,
240
+ environment_variables: [
241
+ "CADDYPATH=/etc/caddy/certs",
242
+ ],
243
+ }
244
+ )
245
+ end
246
+
247
+ def caddy_conf(ca, has_provider)
248
+ port = has_provider ? "4180" : "8080"
249
+ tls = ca ? "/etc/ssl/#{ca.name}/#{@fqdn}/cert /etc/ssl/#{ca.name}/#{@fqdn}/key" : "cloud@uswitch.com"
250
+ {
251
+ path: "/etc/caddy/Caddyfile",
252
+ mode: "0644",
253
+ contents: <<EOF
254
+ #{@fqdn}:443
255
+ tls #{tls}
256
+ proxy / localhost:#{port}
257
+ EOF
258
+ }
259
+ end
260
+
261
+ def openvpn_conf
262
+ {
263
+ path: "/etc/openvpn/openvpn.conf",
264
+ mode: "0644",
265
+ contents: <<EOF
266
+ server #{cidr_to_split_address(@cidr)}
267
+ verb 3
268
+
269
+ iproute /etc/openvpn/ovpn_ip.sh
270
+
271
+ key /etc/ssl/openvpn/server/key
272
+ ca /etc/ssl/openvpn/ca/cert
273
+ cert /etc/ssl/openvpn/server/cert
274
+ dh /etc/ssl/openvpn/dh.pem
275
+ tls-auth /etc/ssl/openvpn/ta.key
276
+
277
+ cipher AES-256-CBC
278
+ auth SHA512
279
+ tls-version-min 1.2
280
+
281
+ key-direction 0
282
+ keepalive 10 60
283
+ persist-key
284
+ persist-tun
285
+
286
+ proto udp
287
+ # Rely on Docker to do port mapping, internally always 1194
288
+ port 1194
289
+ dev tun0
290
+ status /tmp/openvpn-status.log
291
+
292
+ user nobody
293
+ group nogroup
294
+ EOF
295
+ }
296
+ end
297
+
298
+ def openvpn_env
299
+ {
300
+ path: "/etc/openvpn/ovpn_env.sh",
301
+ mode: "0644",
302
+ contents: <<EOF
303
+ declare -x OVPN_SERVER=#{@cidr}
304
+ EOF
305
+ }
306
+ end
307
+
308
+ # OpenVPN doesn't wait long enough for the tun0 device to init
309
+ # https://github.com/kylemanna/docker-openvpn/issues/370
310
+ def openvpn_ip_delay
311
+ {
312
+ path: '/etc/openvpn/ovpn_ip.sh',
313
+ mode: '0755',
314
+ contents: <<~IP_SCRIPT
315
+ #!/usr/bin/env bash
316
+ sleep 0.1
317
+ /sbin/ip $*
318
+ IP_SCRIPT
319
+ }
320
+ end
321
+
322
+ def with_endpoint_service(*args)
323
+ @service.with_endpoint_service(*args)
324
+ end
325
+
326
+ def security_group
327
+ @service.security_group
328
+ end
329
+
330
+ def ingress_security_group
331
+ @service.ingress_security_group
332
+ end
333
+
334
+ def egress_security_group
335
+ @service.egress_security_group
336
+ end
337
+
338
+ def pingable_by(*services)
339
+ @service.pingable_by(*services)
340
+ end
341
+
342
+ def used_by(*services)
343
+ @service.used_by(*services)
344
+ end
345
+
346
+ def pingable_by_cidr(*cidrs)
347
+ @service.pingable_by_cidr(*cidrs)
348
+ end
349
+
350
+ def used_by_cidr(*cidrs)
351
+ @service.used_by_cidr(*cidrs)
352
+ end
353
+
354
+ end
355
+
356
+ end
357
+
358
+ end
@@ -0,0 +1,144 @@
1
+ require 'terrafying/generator'
2
+
3
+ module Terrafying
4
+
5
+ module Components
6
+
7
+ class Zone < Terrafying::Context
8
+
9
+ attr_reader :id, :fqdn
10
+
11
+ def self.find(fqdn)
12
+ Zone.new.find fqdn
13
+ end
14
+
15
+ def self.find_by_tag(tag)
16
+ Zone.new.find_by_tag tag
17
+ end
18
+
19
+ def self.create(fqdn, options={})
20
+ Zone.new.create fqdn, options
21
+ end
22
+
23
+ def initialize()
24
+ super
25
+ end
26
+
27
+
28
+ def find(fqdn)
29
+ zone = aws.hosted_zone(fqdn)
30
+
31
+ @id = zone.id
32
+ @fqdn = fqdn
33
+
34
+ self
35
+ end
36
+
37
+ def find_by_tag(tag)
38
+ zone = aws.hosted_zone_by_tag(tag)
39
+ @id = zone.id
40
+ @fqdn = zone.name.chomp(".")
41
+
42
+ self
43
+ end
44
+
45
+ def create(fqdn, options={})
46
+ options = {
47
+ tags: {},
48
+ }.merge(options)
49
+
50
+ ident = tf_safe(fqdn)
51
+
52
+ @fqdn = fqdn
53
+ @id = resource :aws_route53_zone, ident, {
54
+ name: fqdn,
55
+ tags: options[:tags],
56
+ }
57
+
58
+ if options[:parent_zone]
59
+ ns = (0..3).map{ |i| output_of(:aws_route53_zone, ident, "name_servers.#{i}") }
60
+
61
+ resource :aws_route53_record, "#{ident}-ns", {
62
+ zone_id: options[:parent_zone].id,
63
+ name: fqdn,
64
+ type: "NS",
65
+ ttl: "30",
66
+ records: ns,
67
+ }
68
+ end
69
+
70
+ self
71
+ end
72
+
73
+ def add_record(name, records)
74
+ add_record_in(self, name,records)
75
+ end
76
+
77
+ def add_record_in(ctx, name,records)
78
+ fqdn = qualify(name)
79
+ ctx.resource :aws_route53_record, tf_safe(fqdn), {
80
+ zone_id: @id,
81
+ name: fqdn,
82
+ type: "A",
83
+ ttl: 300,
84
+ records: records,
85
+ }
86
+ end
87
+
88
+ def add_alias(name, config)
89
+ add_alias_in(self, name, config)
90
+ end
91
+
92
+ def add_alias_in(ctx, name, config)
93
+ fqdn = qualify(name)
94
+ ctx.resource :aws_route53_record, tf_safe(fqdn), {
95
+ zone_id: @id,
96
+ name: fqdn,
97
+ type: "A",
98
+ alias: config,
99
+ }
100
+ end
101
+
102
+ def add_srv(name, service_name, port, type, hosts)
103
+ add_srv_in(self, name, service_name, port, type, hosts)
104
+ end
105
+
106
+ def add_srv_in(ctx, name, service_name, port, type, hosts)
107
+ fqdn = qualify(name)
108
+ ident = tf_safe(fqdn)
109
+
110
+ ctx.resource :aws_route53_record, "srv-#{ident}-#{service_name}", {
111
+ zone_id: @id,
112
+ name: "_#{service_name}._#{type}.#{fqdn}",
113
+ type: "SRV",
114
+ ttl: "60",
115
+ records: hosts.map { |host| "0 0 #{port} #{host}" }
116
+ }
117
+ end
118
+
119
+ def add_cname(name, *records)
120
+ add_cname_in(self, name, *records)
121
+ end
122
+
123
+ def add_cname_in(ctx, name, *records)
124
+ fqdn = qualify(name)
125
+ ident = fqdn.tr('.', '-')
126
+ ctx.resource :aws_route53_record, ident,
127
+ {
128
+ zone_id: @id,
129
+ name: fqdn,
130
+ type: 'CNAME',
131
+ ttl: 300,
132
+ records: records
133
+ }
134
+ end
135
+
136
+ def qualify(name)
137
+ "#{name}.#{@fqdn}"
138
+ end
139
+
140
+ end
141
+
142
+ end
143
+
144
+ end