github.com/uber/kraken@v0.1.4/nginx/nginx.go (about) 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 Root bool `yaml:"root"` 41 42 // Name defines the default nginx template for each component. 43 Name string `yaml:"name"` 44 45 // TemplatePath takes precedence over Name, overwrites default template. 46 TemplatePath string `yaml:"template_path"` 47 48 CacheDir string `yaml:"cache_dir"` 49 LogDir string `yaml:"log_dir"` 50 51 tls httputil.TLSConfig 52 } 53 54 func (c *Config) inject(params map[string]interface{}) error { 55 for _, s := range []string{"cache_dir", "log_dir"} { 56 if _, ok := params[s]; ok { 57 return fmt.Errorf("invalid params: %s is reserved", s) 58 } 59 } 60 params["cache_dir"] = c.CacheDir 61 params["log_dir"] = c.LogDir 62 return nil 63 } 64 65 // GetTemplate returns the template content. 66 func (c *Config) getTemplate() (string, error) { 67 if c.TemplatePath != "" { 68 b, err := ioutil.ReadFile(c.TemplatePath) 69 if err != nil { 70 return "", fmt.Errorf("read template: %s", err) 71 } 72 return string(b), nil 73 } 74 tmpl, err := config.GetDefaultTemplate(c.Name) 75 if err != nil { 76 return "", fmt.Errorf("get default template: %s", err) 77 } 78 return tmpl, nil 79 } 80 81 // Build builds nginx config. 82 func (c *Config) Build(params map[string]interface{}) ([]byte, error) { 83 tmpl, err := c.getTemplate() 84 if err != nil { 85 return nil, fmt.Errorf("get template: %s", err) 86 } 87 if _, ok := params["client_verification"]; !ok { 88 params["client_verification"] = config.DefaultClientVerification 89 } 90 site, err := populateTemplate(tmpl, params) 91 if err != nil { 92 return nil, fmt.Errorf("populate template: %s", err) 93 } 94 95 // Build nginx config with base template and component specific template. 96 tmpl, err = config.GetDefaultTemplate("base") 97 if err != nil { 98 return nil, fmt.Errorf("get default base template: %s", err) 99 } 100 src, err := populateTemplate(tmpl, map[string]interface{}{ 101 "site": string(site), 102 "ssl_enabled": !c.tls.Server.Disabled, 103 "ssl_certificate": c.tls.Server.Cert.Path, 104 "ssl_certificate_key": c.tls.Server.Key.Path, 105 "ssl_password_file": c.tls.Server.Passphrase.Path, 106 "ssl_client_certificate": _clientCABundle, 107 }) 108 if err != nil { 109 return nil, fmt.Errorf("populate base: %s", err) 110 } 111 return src, nil 112 } 113 114 // Option allows setting optional nginx configuration. 115 type Option func(*Config) 116 117 // WithTLS configures nginx configuration with tls. 118 func WithTLS(tls httputil.TLSConfig) Option { 119 return func(c *Config) { c.tls = tls } 120 } 121 122 // Run injects params into an nginx configuration template and runs it. 123 func Run(config Config, params map[string]interface{}, opts ...Option) error { 124 if config.Name == "" && config.TemplatePath == "" { 125 return errors.New("invalid config: name or template_path required") 126 } 127 if config.CacheDir == "" { 128 return errors.New("invalid config: cache_dir required") 129 } 130 if config.LogDir == "" { 131 return errors.New("invalid config: log_dir required") 132 } 133 for _, opt := range opts { 134 opt(&config) 135 } 136 137 // Create root directory for generated files for nginx. 138 if err := os.MkdirAll(_genDir, 0775); err != nil { 139 return err 140 } 141 142 if config.tls.Server.Disabled { 143 log.Warn("Server TLS is disabled") 144 } else { 145 for _, s := range append( 146 config.tls.CAs, 147 config.tls.Server.Cert, 148 config.tls.Server.Key, 149 config.tls.Server.Passphrase) { 150 if _, err := os.Stat(s.Path); err != nil { 151 return fmt.Errorf("invalid TLS config: %s", err) 152 } 153 } 154 155 // Concat all ca files into bundle. 156 cabundle, err := os.Create(_clientCABundle) 157 if err != nil { 158 return fmt.Errorf("create cabundle: %s", err) 159 } 160 if err := config.tls.WriteCABundle(cabundle); err != nil { 161 return fmt.Errorf("write cabundle: %s", err) 162 } 163 cabundle.Close() 164 } 165 166 if err := os.MkdirAll(config.CacheDir, 0775); err != nil { 167 return err 168 } 169 170 if err := config.inject(params); err != nil { 171 return err 172 } 173 174 src, err := config.Build(params) 175 if err != nil { 176 return fmt.Errorf("build nginx config: %s", err) 177 } 178 179 conf := filepath.Join(_genDir, config.Name) 180 if err := ioutil.WriteFile(conf, src, 0755); err != nil { 181 return fmt.Errorf("write src: %s", err) 182 } 183 184 stdoutLog := path.Join(config.LogDir, "nginx-stdout.log") 185 stdout, err := os.OpenFile(stdoutLog, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 186 if err != nil { 187 return fmt.Errorf("open stdout log: %s", err) 188 } 189 190 args := []string{"/usr/sbin/nginx", "-g", "daemon off;", "-c", conf} 191 if config.Root { 192 args = append([]string{"sudo"}, args...) 193 } 194 cmd := exec.Command(args[0], args[1:]...) 195 cmd.Stdout = stdout 196 cmd.Stderr = stdout 197 return cmd.Run() 198 } 199 200 func populateTemplate(tmpl string, args map[string]interface{}) ([]byte, error) { 201 t, err := template.New("nginx").Parse(tmpl) 202 if err != nil { 203 return nil, fmt.Errorf("parse: %s", err) 204 } 205 out := &bytes.Buffer{} 206 if err := t.Execute(out, args); err != nil { 207 return nil, fmt.Errorf("exec: %s", err) 208 } 209 return out.Bytes(), nil 210 } 211 212 // GetServer returns a string for an nginx server directive value. 213 func GetServer(net, addr string) string { 214 if net == "unix" { 215 return "unix:" + addr 216 } 217 return addr 218 }