kraken

Форк
0
/
nginx.go 
251 строка · 6.7 Кб
1
// Copyright (c) 2016-2019 Uber Technologies, 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
package nginx
15

16
import (
17
	"bytes"
18
	"errors"
19
	"fmt"
20
	"io/ioutil"
21
	"os"
22
	"os/exec"
23
	"path"
24
	"path/filepath"
25
	"text/template"
26

27
	"github.com/uber/kraken/nginx/config"
28
	"github.com/uber/kraken/utils/httputil"
29
	"github.com/uber/kraken/utils/log"
30
)
31

32
const (
33
	_genDir = "/tmp/nginx"
34
)
35

36
var _clientCABundle = path.Join(_genDir, "ca.crt")
37

38
// Config defines nginx configuration.
39
type Config struct {
40
	Binary string `yaml:"binary"`
41

42
	Root bool `yaml:"root"`
43

44
	// Name defines the default nginx template for each component.
45
	Name string `yaml:"name"`
46

47
	// TemplatePath takes precedence over Name, overwrites default template.
48
	TemplatePath string `yaml:"template_path"`
49

50
	CacheDir string `yaml:"cache_dir"`
51

52
	LogDir string `yaml:"log_dir"`
53

54
	// Optional log path overrides.
55
	StdoutLogPath string `yaml:"stdout_log_path"`
56
	AccessLogPath string `yaml:"access_log_path"`
57
	ErrorLogPath  string `yaml:"error_log_path"`
58

59
	tls httputil.TLSConfig
60
}
61

62
func (c *Config) applyDefaults() error {
63
	if c.Binary == "" {
64
		c.Binary = "/usr/sbin/nginx"
65
	}
66
	if c.StdoutLogPath == "" {
67
		if c.LogDir == "" {
68
			return errors.New("one of log_dir or stdout_log_path must be set")
69
		}
70
		c.StdoutLogPath = filepath.Join(c.LogDir, "nginx-stdout.log")
71
	}
72
	if c.AccessLogPath == "" {
73
		if c.LogDir == "" {
74
			return errors.New("one of log_dir or access_log_path must be set")
75
		}
76
		c.AccessLogPath = filepath.Join(c.LogDir, "nginx-access.log")
77
	}
78
	if c.ErrorLogPath == "" {
79
		if c.LogDir == "" {
80
			return errors.New("one of log_dir or error_log_path must be set")
81
		}
82
		c.ErrorLogPath = filepath.Join(c.LogDir, "nginx-error.log")
83
	}
84
	return nil
85
}
86

87
func (c *Config) inject(params map[string]interface{}) error {
88
	for _, s := range []string{"cache_dir", "access_log_path", "error_log_path"} {
89
		if _, ok := params[s]; ok {
90
			return fmt.Errorf("invalid params: %s is reserved", s)
91
		}
92
	}
93
	params["cache_dir"] = c.CacheDir
94
	params["access_log_path"] = c.AccessLogPath
95
	params["error_log_path"] = c.ErrorLogPath
96
	return nil
97
}
98

99
// GetTemplate returns the template content.
100
func (c *Config) getTemplate() (string, error) {
101
	if c.TemplatePath != "" {
102
		b, err := ioutil.ReadFile(c.TemplatePath)
103
		if err != nil {
104
			return "", fmt.Errorf("read template: %s", err)
105
		}
106
		return string(b), nil
107
	}
108
	tmpl, err := config.GetDefaultTemplate(c.Name)
109
	if err != nil {
110
		return "", fmt.Errorf("get default template: %s", err)
111
	}
112
	return tmpl, nil
113
}
114

115
// Build builds nginx config.
116
func (c *Config) Build(params map[string]interface{}) ([]byte, error) {
117
	tmpl, err := c.getTemplate()
118
	if err != nil {
119
		return nil, fmt.Errorf("get template: %s", err)
120
	}
121
	if _, ok := params["client_verification"]; !ok {
122
		params["client_verification"] = config.DefaultClientVerification
123
	}
124
	site, err := populateTemplate(tmpl, params)
125
	if err != nil {
126
		return nil, fmt.Errorf("populate template: %s", err)
127
	}
128

129
	// Build nginx config with base template and component specific template.
130
	tmpl, err = config.GetDefaultTemplate("base")
131
	if err != nil {
132
		return nil, fmt.Errorf("get default base template: %s", err)
133
	}
134
	src, err := populateTemplate(tmpl, map[string]interface{}{
135
		"site":                   string(site),
136
		"ssl_enabled":            !c.tls.Server.Disabled,
137
		"ssl_certificate":        c.tls.Server.Cert.Path,
138
		"ssl_certificate_key":    c.tls.Server.Key.Path,
139
		"ssl_password_file":      c.tls.Server.Passphrase.Path,
140
		"ssl_client_certificate": _clientCABundle,
141
	})
142
	if err != nil {
143
		return nil, fmt.Errorf("populate base: %s", err)
144
	}
145
	return src, nil
146
}
147

148
// Option allows setting optional nginx configuration.
149
type Option func(*Config)
150

151
// WithTLS configures nginx configuration with tls.
152
func WithTLS(tls httputil.TLSConfig) Option {
153
	return func(c *Config) { c.tls = tls }
154
}
155

156
// Run injects params into an nginx configuration template and runs it.
157
func Run(config Config, params map[string]interface{}, opts ...Option) error {
158
	if err := config.applyDefaults(); err != nil {
159
		return fmt.Errorf("invalid config: %s", err)
160
	}
161
	if config.Name == "" && config.TemplatePath == "" {
162
		return errors.New("invalid config: name or template_path required")
163
	}
164
	if config.CacheDir == "" {
165
		return errors.New("invalid config: cache_dir required")
166
	}
167
	for _, opt := range opts {
168
		opt(&config)
169
	}
170

171
	// Create root directory for generated files for nginx.
172
	if err := os.MkdirAll(_genDir, 0775); err != nil {
173
		return err
174
	}
175

176
	if config.tls.Server.Disabled {
177
		log.Warn("Server TLS is disabled")
178
	} else {
179
		for _, s := range append(
180
			config.tls.CAs,
181
			config.tls.Server.Cert,
182
			config.tls.Server.Key,
183
			config.tls.Server.Passphrase) {
184
			if _, err := os.Stat(s.Path); err != nil {
185
				return fmt.Errorf("invalid TLS config: %s", err)
186
			}
187
		}
188

189
		// Concat all ca files into bundle.
190
		cabundle, err := os.Create(_clientCABundle)
191
		if err != nil {
192
			return fmt.Errorf("create cabundle: %s", err)
193
		}
194
		if err := config.tls.WriteCABundle(cabundle); err != nil {
195
			return fmt.Errorf("write cabundle: %s", err)
196
		}
197
		cabundle.Close()
198
	}
199

200
	if err := os.MkdirAll(config.CacheDir, 0775); err != nil {
201
		return err
202
	}
203

204
	if err := config.inject(params); err != nil {
205
		return err
206
	}
207

208
	src, err := config.Build(params)
209
	if err != nil {
210
		return fmt.Errorf("build nginx config: %s", err)
211
	}
212

213
	conf := filepath.Join(_genDir, config.Name)
214
	if err := ioutil.WriteFile(conf, src, 0755); err != nil {
215
		return fmt.Errorf("write src: %s", err)
216
	}
217

218
	stdout, err := os.OpenFile(config.StdoutLogPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
219
	if err != nil {
220
		return fmt.Errorf("open stdout log: %s", err)
221
	}
222

223
	args := []string{config.Binary, "-g", "daemon off;", "-c", conf}
224
	if config.Root {
225
		args = append([]string{"sudo"}, args...)
226
	}
227
	cmd := exec.Command(args[0], args[1:]...)
228
	cmd.Stdout = stdout
229
	cmd.Stderr = stdout
230
	return cmd.Run()
231
}
232

233
func populateTemplate(tmpl string, args map[string]interface{}) ([]byte, error) {
234
	t, err := template.New("nginx").Parse(tmpl)
235
	if err != nil {
236
		return nil, fmt.Errorf("parse: %s", err)
237
	}
238
	out := &bytes.Buffer{}
239
	if err := t.Execute(out, args); err != nil {
240
		return nil, fmt.Errorf("exec: %s", err)
241
	}
242
	return out.Bytes(), nil
243
}
244

245
// GetServer returns a string for an nginx server directive value.
246
func GetServer(net, addr string) string {
247
	if net == "unix" {
248
		return "unix:" + addr
249
	}
250
	return addr
251
}
252

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.