1// Copyright 2016-2020 The Libsacloud Authors
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
15package sacloud
16
17import (
18	"crypto/x509"
19	"encoding/json"
20	"encoding/pem"
21	"fmt"
22	"strconv"
23	"strings"
24	"time"
25)
26
27// ProxyLB ProxyLB(CommonServiceItem)
28type ProxyLB struct {
29	*Resource        // ID
30	propName         // 名称
31	propDescription  // 説明
32	propServiceClass // サービスクラス
33	propIcon         // アイコン
34	propTags         // タグ
35	propCreatedAt    // 作成日時
36	propModifiedAt   // 変更日時
37	propAvailability // 有効状態
38
39	Status   *ProxyLBStatus  `json:",omitempty"` // ステータス
40	Provider ProxyLBProvider `json:",omitempty"` // プロバイダ
41	Settings ProxyLBSettings `json:",omitempty"` // ProxyLB設定
42
43}
44
45// ProxyLBSettings ProxyLB設定
46type ProxyLBSettings struct {
47	ProxyLB ProxyLBSetting `json:",omitempty"` // ProxyLB ProxyLBエントリー
48}
49
50// ProxyLBStatus ProxyLBステータス
51type ProxyLBStatus struct {
52	FQDN             string   `json:",omitempty"` // 割り当てられたFQDN(site-*******.proxylb?.sakura.ne.jp) UseVIPFailoverがtrueの場合のみ有効
53	VirtualIPAddress string   `json:",omitempty"` // 割り当てられたVIP UseVIPFailoverがfalseの場合のみ有効
54	ProxyNetworks    []string `json:",omitempty"` // プロキシ元ネットワークアドレス(CIDR)
55	UseVIPFailover   bool     // VIPフェイルオーバ
56}
57
58// ProxyLBProvider プロバイダ
59type ProxyLBProvider struct {
60	Class string `json:",omitempty"` // クラス
61}
62
63// CreateNewProxyLB ProxyLB作成
64func CreateNewProxyLB(name string) *ProxyLB {
65	return &ProxyLB{
66		Resource: &Resource{},
67		propName: propName{Name: name},
68		Provider: ProxyLBProvider{
69			Class: "proxylb",
70		},
71		Settings: ProxyLBSettings{
72			ProxyLB: ProxyLBSetting{
73				HealthCheck: defaultProxyLBHealthCheck,
74				SorryServer: ProxyLBSorryServer{},
75				Servers:     []ProxyLBServer{},
76				//LetsEncrypt:   ProxyLBACMESetting{},
77				StickySession: ProxyLBSessionSetting{},
78			},
79		},
80	}
81}
82
83// ProxyLBPlan ProxyLBプラン
84type ProxyLBPlan int
85
86var (
87	// ProxyLBPlan100 100cpsプラン
88	ProxyLBPlan100 = ProxyLBPlan(100)
89	// ProxyLBPlan500 500cpsプラン
90	ProxyLBPlan500 = ProxyLBPlan(500)
91	// ProxyLBPlan1000 1,000cpsプラン
92	ProxyLBPlan1000 = ProxyLBPlan(1000)
93	// ProxyLBPlan5000 5,000cpsプラン
94	ProxyLBPlan5000 = ProxyLBPlan(5000)
95	// ProxyLBPlan10000 10,000cpsプラン
96	ProxyLBPlan10000 = ProxyLBPlan(10000)
97	// ProxyLBPlan50000 50,000cpsプラン
98	ProxyLBPlan50000 = ProxyLBPlan(50000)
99	// ProxyLBPlan100000 100,000cpsプラン
100	ProxyLBPlan100000 = ProxyLBPlan(100000)
101	// ProxyLBPlan400000 400,000cpsプラン
102	ProxyLBPlan400000 = ProxyLBPlan(400000)
103)
104
105// AllowProxyLBPlans 有効なプランIDリスト
106var AllowProxyLBPlans = []int{
107	int(ProxyLBPlan100),
108	int(ProxyLBPlan500),
109	int(ProxyLBPlan1000),
110	int(ProxyLBPlan5000),
111	int(ProxyLBPlan10000),
112	int(ProxyLBPlan50000),
113	int(ProxyLBPlan100000),
114	int(ProxyLBPlan400000),
115}
116
117// GetPlan プラン取得(デフォルト: 1000cps)
118func (p *ProxyLB) GetPlan() ProxyLBPlan {
119	classes := strings.Split(p.ServiceClass, "/")
120	class, err := strconv.Atoi(classes[len(classes)-1])
121	if err != nil {
122		return ProxyLBPlan1000
123	}
124	return ProxyLBPlan(class)
125}
126
127// SetPlan プラン指定
128func (p *ProxyLB) SetPlan(plan ProxyLBPlan) {
129	p.ServiceClass = fmt.Sprintf("cloud/proxylb/plain/%d", plan)
130}
131
132// SetHTTPHealthCheck HTTPヘルスチェック 設定
133func (p *ProxyLB) SetHTTPHealthCheck(hostHeader, path string, delayLoop int) {
134	if delayLoop <= 0 {
135		delayLoop = 10
136	}
137
138	p.Settings.ProxyLB.HealthCheck.Protocol = "http"
139	p.Settings.ProxyLB.HealthCheck.Host = hostHeader
140	p.Settings.ProxyLB.HealthCheck.Path = path
141	p.Settings.ProxyLB.HealthCheck.DelayLoop = delayLoop
142}
143
144// SetTCPHealthCheck TCPヘルスチェック 設定
145func (p *ProxyLB) SetTCPHealthCheck(delayLoop int) {
146	if delayLoop <= 0 {
147		delayLoop = 10
148	}
149
150	p.Settings.ProxyLB.HealthCheck.Protocol = "tcp"
151	p.Settings.ProxyLB.HealthCheck.Host = ""
152	p.Settings.ProxyLB.HealthCheck.Path = ""
153	p.Settings.ProxyLB.HealthCheck.DelayLoop = delayLoop
154}
155
156// SetSorryServer ソーリーサーバ 設定
157func (p *ProxyLB) SetSorryServer(ipaddress string, port int) {
158	var pt *int
159	if port > 0 {
160		pt = &port
161	}
162	p.Settings.ProxyLB.SorryServer = ProxyLBSorryServer{
163		IPAddress: ipaddress,
164		Port:      pt,
165	}
166}
167
168// ClearSorryServer ソーリーサーバ クリア
169func (p *ProxyLB) ClearSorryServer() {
170	p.SetSorryServer("", 0)
171}
172
173// HasProxyLBServer ProxyLB配下にサーバーを保持しているか判定
174func (p *ProxyLB) HasProxyLBServer() bool {
175	return len(p.Settings.ProxyLB.Servers) > 0
176}
177
178// ClearProxyLBServer ProxyLB配下のサーバーをクリア
179func (p *ProxyLB) ClearProxyLBServer() {
180	p.Settings.ProxyLB.Servers = []ProxyLBServer{}
181}
182
183// AddBindPort バインドポート追加
184func (p *ProxyLB) AddBindPort(mode string, port int, redirectToHTTPS, supportHTTP2 bool, addResponseHeader []*ProxyLBResponseHeader) {
185	p.Settings.ProxyLB.AddBindPort(mode, port, redirectToHTTPS, supportHTTP2, addResponseHeader)
186}
187
188// DeleteBindPort バインドポート削除
189func (p *ProxyLB) DeleteBindPort(mode string, port int) {
190	p.Settings.ProxyLB.DeleteBindPort(mode, port)
191}
192
193// ClearBindPorts バインドポート クリア
194func (p *ProxyLB) ClearBindPorts() {
195	p.Settings.ProxyLB.BindPorts = []*ProxyLBBindPorts{}
196}
197
198// AddServer ProxyLB配下のサーバーを追加
199func (p *ProxyLB) AddServer(ip string, port int, enabled bool, serverGroup string) {
200	p.Settings.ProxyLB.AddServer(ip, port, enabled, serverGroup)
201}
202
203// DeleteServer ProxyLB配下のサーバーを削除
204func (p *ProxyLB) DeleteServer(ip string, port int) {
205	p.Settings.ProxyLB.DeleteServer(ip, port)
206}
207
208// ProxyLBSetting ProxyLBセッティング
209type ProxyLBSetting struct {
210	HealthCheck   ProxyLBHealthCheck  // ヘルスチェック
211	SorryServer   ProxyLBSorryServer  // ソーリーサーバー
212	BindPorts     []*ProxyLBBindPorts // プロキシ方式(プロトコル&ポート)
213	Servers       []ProxyLBServer     // サーバー
214	Rules         []ProxyLBRule       // 振り分けルール
215	LetsEncrypt   *ProxyLBACMESetting `json:",omitempty"` // Let's encryptでの証明書取得設定
216	StickySession ProxyLBSessionSetting
217	Timeout       *ProxyLBTimeout `json:",omitempty"` // タイムアウト
218}
219
220// ProxyLBSorryServer ソーリーサーバ
221type ProxyLBSorryServer struct {
222	IPAddress string // IPアドレス
223	Port      *int   // ポート
224}
225
226// AddBindPort バインドポート追加
227func (s *ProxyLBSetting) AddBindPort(mode string, port int, redirectToHTTPS, supportHTTP2 bool, addResponseHeader []*ProxyLBResponseHeader) {
228	var isExist bool
229	for i := range s.BindPorts {
230		if s.BindPorts[i].ProxyMode == mode && s.BindPorts[i].Port == port {
231			isExist = true
232		}
233	}
234
235	if !isExist {
236		s.BindPorts = append(s.BindPorts, &ProxyLBBindPorts{
237			ProxyMode:         mode,
238			Port:              port,
239			RedirectToHTTPS:   redirectToHTTPS,
240			SupportHTTP2:      supportHTTP2,
241			AddResponseHeader: addResponseHeader,
242		})
243	}
244}
245
246// DeleteBindPort バインドポート削除
247func (s *ProxyLBSetting) DeleteBindPort(mode string, port int) {
248	var res []*ProxyLBBindPorts
249	for i := range s.BindPorts {
250		if s.BindPorts[i].ProxyMode != mode || s.BindPorts[i].Port != port {
251			res = append(res, s.BindPorts[i])
252		}
253	}
254	s.BindPorts = res
255}
256
257// AddServer ProxyLB配下のサーバーを追加
258func (s *ProxyLBSetting) AddServer(ip string, port int, enabled bool, serverGroup string) {
259	var record ProxyLBServer
260	var isExist = false
261	for i := range s.Servers {
262		if s.Servers[i].IPAddress == ip && s.Servers[i].Port == port {
263			isExist = true
264			s.Servers[i].Enabled = enabled
265		}
266	}
267
268	if !isExist {
269		record = ProxyLBServer{
270			IPAddress:   ip,
271			Port:        port,
272			Enabled:     enabled,
273			ServerGroup: serverGroup,
274		}
275		s.Servers = append(s.Servers, record)
276	}
277}
278
279// DeleteServer ProxyLB配下のサーバーを削除
280func (s *ProxyLBSetting) DeleteServer(ip string, port int) {
281	var res []ProxyLBServer
282	for i := range s.Servers {
283		if s.Servers[i].IPAddress != ip || s.Servers[i].Port != port {
284			res = append(res, s.Servers[i])
285		}
286	}
287
288	s.Servers = res
289}
290
291// AllowProxyLBBindModes プロキシ方式
292var AllowProxyLBBindModes = []string{"http", "https", "tcp"}
293
294// ProxyLBBindPorts プロキシ方式
295type ProxyLBBindPorts struct {
296	ProxyMode         string                   `json:",omitempty"`      // モード(プロトコル)
297	Port              int                      `json:",omitempty"`      // ポート
298	RedirectToHTTPS   bool                     `json:"RedirectToHttps"` // HTTPSへのリダイレクト(モードがhttpの場合のみ)
299	SupportHTTP2      bool                     `json:"SupportHttp2"`    // HTTP/2のサポート(モードがhttpsの場合のみ)
300	AddResponseHeader []*ProxyLBResponseHeader `json:",omitempty"`      // レスポンスヘッダ
301}
302
303// ProxyLBResponseHeader ポートごとの追加レスポンスヘッダ
304type ProxyLBResponseHeader struct {
305	Header string // ヘッダ名称(英字, 数字, ハイフン)
306	Value  string // 値(英字, 数字, 半角スペース, 一部記号(!#$%&'()*+,-./:;<=>?@[]^_`{|}~))
307}
308
309// ProxyLBServer ProxyLB配下のサーバー
310type ProxyLBServer struct {
311	IPAddress   string `json:",omitempty"` // IPアドレス
312	Port        int    `json:",omitempty"` // ポート
313	ServerGroup string // サーバグループ
314	Enabled     bool   // 有効/無効
315}
316
317// NewProxyLBServer ProxyLB配下のサーバ作成
318func NewProxyLBServer(ipaddress string, port int) *ProxyLBServer {
319	return &ProxyLBServer{
320		IPAddress: ipaddress,
321		Port:      port,
322		Enabled:   true,
323	}
324}
325
326// ProxyLBRule ProxyLBの振り分けルール
327type ProxyLBRule struct {
328	Host        string `json:",omitempty"` // ホストヘッダのパターン(ワイルドカードとして?と*が利用可能)
329	Path        string `json:",omitempty"` // パス
330	ServerGroup string
331}
332
333// ProxyLBACMESetting Let's Encryptでの証明書取得設定
334type ProxyLBACMESetting struct {
335	Enabled    bool
336	CommonName string `json:",omitempty"`
337}
338
339// ProxyLBSessionSetting セッション維持機能設定
340type ProxyLBSessionSetting struct {
341	Enabled bool
342	Method  string `json:",omitempty"`
343}
344
345// ProxyLBTimeout 実サーバの通信タイムアウト
346type ProxyLBTimeout struct {
347	InactiveSec int `json:",omitempty"` // 10から600まで1秒刻みで設定可
348}
349
350// ProxyLBStickySessionDefaultMethod セッション維持のデフォルトメソッド(クッキー)
351const ProxyLBStickySessionDefaultMethod = "cookie"
352
353// AllowProxyLBHealthCheckProtocols プロキシLBで利用できるヘルスチェックプロトコル
354var AllowProxyLBHealthCheckProtocols = []string{"http", "tcp"}
355
356// ProxyLBHealthCheck ヘルスチェック
357type ProxyLBHealthCheck struct {
358	Protocol  string `json:",omitempty"` // プロトコル
359	Host      string `json:",omitempty"` // 対象ホスト
360	Path      string `json:",omitempty"` // HTTPの場合のリクエストパス
361	DelayLoop int    `json:",omitempty"` // 監視間隔
362
363}
364
365var defaultProxyLBHealthCheck = ProxyLBHealthCheck{
366	Protocol:  "http",
367	Host:      "",
368	Path:      "/",
369	DelayLoop: 10,
370}
371
372// ProxyLBAdditionalCerts additional certificates
373type ProxyLBAdditionalCerts []*ProxyLBCertificate
374
375// ProxyLBCertificates ProxyLBのSSL証明書
376type ProxyLBCertificates struct {
377	PrimaryCert     *ProxyLBCertificate
378	AdditionalCerts ProxyLBAdditionalCerts
379}
380
381// UnmarshalJSON UnmarshalJSON(AdditionalCertsが空の場合に空文字を返す問題への対応)
382func (p *ProxyLBAdditionalCerts) UnmarshalJSON(data []byte) error {
383	targetData := strings.Replace(strings.Replace(strings.Replace(string(data), " ", "", -1), "\n", "", -1), `""`, ``, -1)
384	if targetData == `` {
385		return nil
386	}
387
388	var certs []*ProxyLBCertificate
389	if err := json.Unmarshal(data, &certs); err != nil {
390		return err
391	}
392
393	*p = certs
394	return nil
395}
396
397// SetPrimaryCert PrimaryCertを設定
398func (p *ProxyLBCertificates) SetPrimaryCert(cert *ProxyLBCertificate) {
399	p.PrimaryCert = cert
400}
401
402// SetPrimaryCertValue PrimaryCertを設定
403func (p *ProxyLBCertificates) SetPrimaryCertValue(serverCert, intermediateCert, privateKey string) {
404	p.PrimaryCert = &ProxyLBCertificate{
405		ServerCertificate:       serverCert,
406		IntermediateCertificate: intermediateCert,
407		PrivateKey:              privateKey,
408	}
409}
410
411// AddAdditionalCert AdditionalCertを追加
412func (p *ProxyLBCertificates) AddAdditionalCert(serverCert, intermediateCert, privateKey string) {
413	p.AdditionalCerts = append(p.AdditionalCerts, &ProxyLBCertificate{
414		ServerCertificate:       serverCert,
415		IntermediateCertificate: intermediateCert,
416		PrivateKey:              privateKey,
417	})
418}
419
420// RemoveAdditionalCertAt 指定のインデックスを持つAdditionalCertを削除
421func (p *ProxyLBCertificates) RemoveAdditionalCertAt(index int) {
422	var certs []*ProxyLBCertificate
423	for i, cert := range p.AdditionalCerts {
424		if i != index {
425			certs = append(certs, cert)
426		}
427	}
428	p.AdditionalCerts = certs
429}
430
431// RemoveAdditionalCert 指定の内容を持つAdditionalCertを削除
432func (p *ProxyLBCertificates) RemoveAdditionalCert(serverCert, intermediateCert, privateKey string) {
433	var certs []*ProxyLBCertificate
434	for _, cert := range p.AdditionalCerts {
435		if !(cert.ServerCertificate == serverCert && cert.IntermediateCertificate == intermediateCert && cert.PrivateKey == privateKey) {
436			certs = append(certs, cert)
437		}
438	}
439	p.AdditionalCerts = certs
440}
441
442// RemoveAdditionalCerts AdditionalCertsを全て削除
443func (p *ProxyLBCertificates) RemoveAdditionalCerts() {
444	p.AdditionalCerts = []*ProxyLBCertificate{}
445}
446
447// ParseServerCertificate サーバ証明書のパース
448func (p *ProxyLBCertificates) ParseServerCertificate() (*x509.Certificate, error) {
449	cert, e := p.parseCertificate(p.PrimaryCert.ServerCertificate)
450	if e != nil {
451		return nil, e
452	}
453	return cert, nil
454}
455
456// ParseIntermediateCertificate 中間証明書のパース
457func (p *ProxyLBCertificates) ParseIntermediateCertificate() (*x509.Certificate, error) {
458	cert, e := p.parseCertificate(p.PrimaryCert.IntermediateCertificate)
459	if e != nil {
460		return nil, e
461	}
462	return cert, nil
463}
464
465func (p *ProxyLBCertificates) parseCertificate(certPEM string) (*x509.Certificate, error) {
466	block, _ := pem.Decode([]byte(certPEM))
467	if block != nil {
468		return x509.ParseCertificate(block.Bytes)
469	}
470	return nil, fmt.Errorf("can't decode certificate")
471}
472
473// ProxyLBCertificate ProxyLBのSSL証明書詳細
474type ProxyLBCertificate struct {
475	ServerCertificate       string    // サーバ証明書
476	IntermediateCertificate string    // 中間証明書
477	PrivateKey              string    // 秘密鍵
478	CertificateEndDate      time.Time `json:",omitempty"` // 有効期限
479	CertificateCommonName   string    `json:",omitempty"` // CommonName
480}
481
482// UnmarshalJSON UnmarshalJSON(CertificateEndDateのtime.TimeへのUnmarshal対応)
483func (p *ProxyLBCertificate) UnmarshalJSON(data []byte) error {
484	var tmp map[string]interface{}
485	if err := json.Unmarshal(data, &tmp); err != nil {
486		return err
487	}
488
489	p.ServerCertificate = tmp["ServerCertificate"].(string)
490	p.IntermediateCertificate = tmp["IntermediateCertificate"].(string)
491	p.PrivateKey = tmp["PrivateKey"].(string)
492	p.CertificateCommonName = tmp["CertificateCommonName"].(string)
493	endDate := tmp["CertificateEndDate"].(string)
494	if endDate != "" {
495		date, err := time.Parse("Jan _2 15:04:05 2006 MST", endDate)
496		if err != nil {
497			return err
498		}
499		p.CertificateEndDate = date
500	}
501
502	return nil
503}
504
505// ParseServerCertificate サーバ証明書のパース
506func (p *ProxyLBCertificate) ParseServerCertificate() (*x509.Certificate, error) {
507	cert, e := p.parseCertificate(p.ServerCertificate)
508	if e != nil {
509		return nil, e
510	}
511	return cert, nil
512}
513
514// ParseIntermediateCertificate 中間証明書のパース
515func (p *ProxyLBCertificate) ParseIntermediateCertificate() (*x509.Certificate, error) {
516	cert, e := p.parseCertificate(p.IntermediateCertificate)
517	if e != nil {
518		return nil, e
519	}
520	return cert, nil
521}
522
523func (p *ProxyLBCertificate) parseCertificate(certPEM string) (*x509.Certificate, error) {
524	block, _ := pem.Decode([]byte(certPEM))
525	if block != nil {
526		return x509.ParseCertificate(block.Bytes)
527	}
528	return nil, fmt.Errorf("can't decode certificate")
529}
530
531// ProxyLBHealth ProxyLBのヘルスチェック戻り値
532type ProxyLBHealth struct {
533	ActiveConn int                    // アクティブなコネクション数
534	CPS        int                    // 秒あたりコネクション数
535	Servers    []*ProxyLBHealthServer // 実サーバのステータス
536	CurrentVIP string                 // 現在のVIP
537}
538
539// ProxyLBHealthServer ProxyLBの実サーバのステータス
540type ProxyLBHealthServer struct {
541	ActiveConn int    // アクティブなコネクション数
542	Status     string // ステータス(UP or DOWN)
543	IPAddress  string // IPアドレス
544	Port       string // ポート
545	CPS        int    // 秒あたりコネクション数
546}
547