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 }