1
// Copyright (c) 2016-2019 Uber Technologies, Inc.
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
7
// http://www.apache.org/licenses/LICENSE-2.0
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.
27
"github.com/uber/kraken/nginx/config"
28
"github.com/uber/kraken/utils/httputil"
29
"github.com/uber/kraken/utils/log"
33
_genDir = "/tmp/nginx"
36
var _clientCABundle = path.Join(_genDir, "ca.crt")
38
// Config defines nginx configuration.
40
Binary string `yaml:"binary"`
42
Root bool `yaml:"root"`
44
// Name defines the default nginx template for each component.
45
Name string `yaml:"name"`
47
// TemplatePath takes precedence over Name, overwrites default template.
48
TemplatePath string `yaml:"template_path"`
50
CacheDir string `yaml:"cache_dir"`
52
LogDir string `yaml:"log_dir"`
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"`
59
tls httputil.TLSConfig
62
func (c *Config) applyDefaults() error {
64
c.Binary = "/usr/sbin/nginx"
66
if c.StdoutLogPath == "" {
68
return errors.New("one of log_dir or stdout_log_path must be set")
70
c.StdoutLogPath = filepath.Join(c.LogDir, "nginx-stdout.log")
72
if c.AccessLogPath == "" {
74
return errors.New("one of log_dir or access_log_path must be set")
76
c.AccessLogPath = filepath.Join(c.LogDir, "nginx-access.log")
78
if c.ErrorLogPath == "" {
80
return errors.New("one of log_dir or error_log_path must be set")
82
c.ErrorLogPath = filepath.Join(c.LogDir, "nginx-error.log")
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)
93
params["cache_dir"] = c.CacheDir
94
params["access_log_path"] = c.AccessLogPath
95
params["error_log_path"] = c.ErrorLogPath
99
// GetTemplate returns the template content.
100
func (c *Config) getTemplate() (string, error) {
101
if c.TemplatePath != "" {
102
b, err := ioutil.ReadFile(c.TemplatePath)
104
return "", fmt.Errorf("read template: %s", err)
106
return string(b), nil
108
tmpl, err := config.GetDefaultTemplate(c.Name)
110
return "", fmt.Errorf("get default template: %s", err)
115
// Build builds nginx config.
116
func (c *Config) Build(params map[string]interface{}) ([]byte, error) {
117
tmpl, err := c.getTemplate()
119
return nil, fmt.Errorf("get template: %s", err)
121
if _, ok := params["client_verification"]; !ok {
122
params["client_verification"] = config.DefaultClientVerification
124
site, err := populateTemplate(tmpl, params)
126
return nil, fmt.Errorf("populate template: %s", err)
129
// Build nginx config with base template and component specific template.
130
tmpl, err = config.GetDefaultTemplate("base")
132
return nil, fmt.Errorf("get default base template: %s", err)
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,
143
return nil, fmt.Errorf("populate base: %s", err)
148
// Option allows setting optional nginx configuration.
149
type Option func(*Config)
151
// WithTLS configures nginx configuration with tls.
152
func WithTLS(tls httputil.TLSConfig) Option {
153
return func(c *Config) { c.tls = tls }
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)
161
if config.Name == "" && config.TemplatePath == "" {
162
return errors.New("invalid config: name or template_path required")
164
if config.CacheDir == "" {
165
return errors.New("invalid config: cache_dir required")
167
for _, opt := range opts {
171
// Create root directory for generated files for nginx.
172
if err := os.MkdirAll(_genDir, 0775); err != nil {
176
if config.tls.Server.Disabled {
177
log.Warn("Server TLS is disabled")
179
for _, s := range append(
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)
189
// Concat all ca files into bundle.
190
cabundle, err := os.Create(_clientCABundle)
192
return fmt.Errorf("create cabundle: %s", err)
194
if err := config.tls.WriteCABundle(cabundle); err != nil {
195
return fmt.Errorf("write cabundle: %s", err)
200
if err := os.MkdirAll(config.CacheDir, 0775); err != nil {
204
if err := config.inject(params); err != nil {
208
src, err := config.Build(params)
210
return fmt.Errorf("build nginx config: %s", err)
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)
218
stdout, err := os.OpenFile(config.StdoutLogPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
220
return fmt.Errorf("open stdout log: %s", err)
223
args := []string{config.Binary, "-g", "daemon off;", "-c", conf}
225
args = append([]string{"sudo"}, args...)
227
cmd := exec.Command(args[0], args[1:]...)
233
func populateTemplate(tmpl string, args map[string]interface{}) ([]byte, error) {
234
t, err := template.New("nginx").Parse(tmpl)
236
return nil, fmt.Errorf("parse: %s", err)
238
out := &bytes.Buffer{}
239
if err := t.Execute(out, args); err != nil {
240
return nil, fmt.Errorf("exec: %s", err)
242
return out.Bytes(), nil
245
// GetServer returns a string for an nginx server directive value.
246
func GetServer(net, addr string) string {
248
return "unix:" + addr