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  }