github.com/yanndegat/hiera@v0.6.8/hieraserver/rest/rest.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"crypto/tls"
     7  	"crypto/x509"
     8  	"errors"
     9  	"fmt"
    10  	"io/ioutil"
    11  	"net/http"
    12  	"os"
    13  	"regexp"
    14  	"strconv"
    15  
    16  	"github.com/yanndegat/hiera/config"
    17  
    18  	sdk "github.com/lyraproj/hierasdk/hiera"
    19  	"github.com/spf13/cobra"
    20  	"github.com/yanndegat/hiera/api"
    21  	"github.com/yanndegat/hiera/hiera"
    22  	"github.com/yanndegat/hiera/provider"
    23  )
    24  
    25  func main() {
    26  	cmd := newCommand()
    27  	err := cmd.Execute()
    28  	if err != nil {
    29  		fmt.Println(cmd.OutOrStderr(), err)
    30  		os.Exit(1)
    31  	}
    32  }
    33  
    34  var (
    35  	logLevel         string
    36  	addr             string
    37  	configPath       string
    38  	sslKey           string
    39  	sslCert          string
    40  	clientCA         string
    41  	clientCertVerify bool
    42  	cmdOpts          hiera.CommandOptions
    43  	port             int
    44  )
    45  
    46  func newCommand() *cobra.Command {
    47  	cmd := &cobra.Command{
    48  		Use:   "server",
    49  		Short: `Server - Start a Hiera REST server`,
    50  		Long: `Server - Start a REST server that performs lookups in a Hiera data storage.
    51    Responds to key lookups under the /lookup endpoint`,
    52  		Run:  startServer,
    53  		Args: cobra.NoArgs}
    54  
    55  	flags := cmd.Flags()
    56  	flags.StringVar(&logLevel, `loglevel`, `error`,
    57  		`error/warn/info/debug`)
    58  	flags.StringVar(&configPath, `config`, `/hiera/`+config.FileName,
    59  		`path to the hiera config file. Overrides /hiera/`+config.FileName)
    60  	flags.StringArrayVar(&cmdOpts.VarPaths, `vars`, nil,
    61  		`path to a JSON or YAML file that contains key-value mappings to become variables for this lookup`)
    62  	flags.StringArrayVar(&cmdOpts.Variables, `var`, nil,
    63  		`variable as a key:value or key=value where value is a literal expressed in Puppet DSL`)
    64  	flags.StringVar(&addr, `addr`, ``, `ip address to listen on`)
    65  	flags.StringVar(&sslKey, `ssl-key`, ``, `ssl private key`)
    66  	flags.StringVar(&sslCert, `ssl-cert`, ``, `ssl certificate`)
    67  	flags.StringVar(&clientCA, `ca`, ``, `certificate authority to use to verify clients`)
    68  	flags.BoolVar(&clientCertVerify, `clientCertVerify`, false, `verify client certificate`)
    69  	flags.IntVar(&port, `port`, 8080, `port number to listen to`)
    70  	return cmd
    71  }
    72  
    73  var keyPattern = regexp.MustCompile(`^/lookup/(.*)$`)
    74  
    75  func startServer(_ *cobra.Command, _ []string) {
    76  	configOptions := map[string]interface{}{
    77  		provider.LookupKeyFunctions: []sdk.LookupKey{provider.ConfigLookupKey, provider.Environment},
    78  		api.HieraConfig:             configPath}
    79  
    80  	hiera.DoWithParent(context.Background(), provider.MuxLookupKey, configOptions, func(hs api.Session) {
    81  		router := CreateRouter(hs)
    82  
    83  		server := &http.Server{
    84  			Addr:    addr + ":" + strconv.Itoa(port),
    85  			Handler: router,
    86  		}
    87  
    88  		tlsConfig, err := makeTLSconfig()
    89  		if err != nil {
    90  			panic(err)
    91  		}
    92  
    93  		if tlsConfig == nil {
    94  			err = server.ListenAndServe()
    95  		} else {
    96  			server.TLSConfig = tlsConfig
    97  			err = server.ListenAndServeTLS("", "")
    98  		}
    99  		if err != nil {
   100  			panic(err)
   101  		}
   102  	})
   103  }
   104  
   105  // CreateRouter creates the http.Handler for the Hiera RESTful service
   106  func CreateRouter(ctx api.Session) http.Handler {
   107  	doLookup := func(w http.ResponseWriter, r *http.Request) {
   108  		ks := keyPattern.FindStringSubmatch(r.URL.Path)
   109  		if ks == nil {
   110  			http.NotFound(w, r)
   111  			return
   112  		}
   113  		key := ks[1]
   114  
   115  		defer func() {
   116  			if r := recover(); r != nil {
   117  				var err error
   118  				if er, ok := r.(error); ok {
   119  					err = er
   120  				} else if es, ok := r.(string); ok {
   121  					err = errors.New(es)
   122  				} else {
   123  					panic(r)
   124  				}
   125  				http.Error(w, err.Error(), http.StatusInternalServerError)
   126  			}
   127  		}()
   128  
   129  		opts := cmdOpts
   130  		params := r.URL.Query()
   131  		if dflt, ok := params[`default`]; ok && len(dflt) > 0 {
   132  			opts.Default = &dflt[0]
   133  		}
   134  		opts.Merge = params.Get(`merge`)
   135  		opts.Type = params.Get(`type`)
   136  		opts.Variables = append(opts.Variables, params[`var`]...)
   137  		opts.RenderAs = `json`
   138  		out := bytes.Buffer{}
   139  		if hiera.LookupAndRender(ctx, &opts, []string{key}, &out) {
   140  			w.Header().Set("Content-Type", "application/json")
   141  			_, _ = w.Write(out.Bytes())
   142  		} else {
   143  			http.Error(w, `404 value not found`, http.StatusNotFound)
   144  		}
   145  	}
   146  
   147  	router := http.NewServeMux()
   148  	router.HandleFunc("/lookup/", doLookup)
   149  	return router
   150  }
   151  
   152  func loadCertPool(pemFile string) (*x509.CertPool, error) {
   153  	data, err := ioutil.ReadFile(pemFile)
   154  	if err != nil {
   155  		return nil, err
   156  	}
   157  
   158  	certPool := x509.NewCertPool()
   159  	ok := certPool.AppendCertsFromPEM(data)
   160  	if !ok {
   161  		return nil, fmt.Errorf("failed to load certificate %q", pemFile)
   162  	}
   163  
   164  	return certPool, nil
   165  }
   166  
   167  func makeTLSconfig() (*tls.Config, error) {
   168  	if sslCert == "" || sslKey == "" {
   169  		return nil, nil
   170  	}
   171  	tlsConfig := new(tls.Config)
   172  
   173  	cert, err := tls.LoadX509KeyPair(sslCert, sslKey)
   174  	if err != nil {
   175  		return tlsConfig, err
   176  	}
   177  
   178  	tlsConfig.Certificates = []tls.Certificate{cert}
   179  
   180  	if clientCertVerify {
   181  		tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
   182  	}
   183  
   184  	if clientCA != "" {
   185  		certPool, err := loadCertPool(clientCA)
   186  		if err != nil {
   187  			return tlsConfig, err
   188  		}
   189  
   190  		tlsConfig.ClientCAs = certPool
   191  	}
   192  
   193  	return tlsConfig, nil
   194  }