// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"). You may // not use this file except in compliance with the License. A copy of the // License is located at // // http://aws.amazon.com/apache2.0/ // // or in the "license" file accompanying this file. This file is distributed // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either // express or implied. See the License for the specific language governing // permissions and limitations under the License. package ipamd import ( "encoding/json" "net" "net/http" "os" "strconv" "strings" "time" "github.com/aws/amazon-vpc-cni-k8s/pkg/eniconfig" "github.com/aws/amazon-vpc-cni-k8s/pkg/k8sapi" "github.com/aws/amazon-vpc-cni-k8s/pkg/networkutils" "github.com/aws/amazon-vpc-cni-k8s/pkg/utils/retry" ) const ( // defaultIntrospectionAddress is listening on localhost 61679 for ipamd introspection defaultIntrospectionBindAddress = "127.0.0.1:61679" // Environment variable to define the bind address for the introspection endpoint introspectionBindAddress = "INTROSPECTION_BIND_ADDRESS" // Environment variable to disable the introspection endpoints envDisableIntrospection = "DISABLE_INTROSPECTION" ) type rootResponse struct { AvailableCommands []string } // LoggingHandler is a object for handling http request type LoggingHandler struct { h http.Handler } func (lh LoggingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { log.Infof("Handling http request: %s, from: %s, URI: %s", r.Method, r.RemoteAddr, r.RequestURI) lh.h.ServeHTTP(w, r) } // ServeIntrospection sets up ipamd introspection endpoints func (c *IPAMContext) ServeIntrospection() { if disableIntrospection() { log.Info("Introspection endpoints disabled") return } server := c.setupIntrospectionServer() for { _ = retry.WithBackoff(retry.NewSimpleBackoff(time.Second, time.Minute, 0.2, 2), func() error { var ln net.Listener var err error if strings.HasPrefix(server.Addr, "unix:") { socket := strings.TrimPrefix(server.Addr, "unix:") ln, err = net.Listen("unix", socket) } else { ln, err = net.Listen("tcp", server.Addr) } if err == nil { err = server.Serve(ln) } return err }) } } func (c *IPAMContext) setupIntrospectionServer() *http.Server { serverFunctions := map[string]func(w http.ResponseWriter, r *http.Request){ "/v1/enis": eniV1RequestHandler(c), "/v1/eni-configs": eniConfigRequestHandler(c), "/v1/networkutils-env-settings": networkEnvV1RequestHandler(), "/v1/ipamd-env-settings": ipamdEnvV1RequestHandler(), } paths := make([]string, 0, len(serverFunctions)) for path := range serverFunctions { paths = append(paths, path) } availableCommands := &rootResponse{paths} // Autogenerated list of the above serverFunctions paths availableCommandResponse, err := json.Marshal(&availableCommands) if err != nil { log.Errorf("Failed to marshal: %v", err) } defaultHandler := func(w http.ResponseWriter, r *http.Request) { logErr(w.Write(availableCommandResponse)) } serveMux := http.NewServeMux() serveMux.HandleFunc("/", defaultHandler) for key, fn := range serverFunctions { serveMux.HandleFunc(key, fn) } // Log all requests and then pass through to serveMux loggingServeMux := http.NewServeMux() loggingServeMux.Handle("/", LoggingHandler{serveMux}) addr, ok := os.LookupEnv(introspectionBindAddress) if !ok { addr = defaultIntrospectionBindAddress } log.Infof("Serving introspection endpoints on %s", addr) server := &http.Server{ Addr: addr, Handler: loggingServeMux, ReadTimeout: 5 * time.Second, WriteTimeout: 5 * time.Second, } return server } func eniV1RequestHandler(ipam *IPAMContext) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { responseJSON, err := json.Marshal(ipam.dataStore.GetENIInfos()) if err != nil { log.Errorf("Failed to marshal ENI data: %v", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } logErr(w.Write(responseJSON)) } } func eniConfigRequestHandler(ipam *IPAMContext) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() node, err := k8sapi.GetNode(ctx, ipam.cachedK8SClient) if err != nil { log.Errorf("Failed to get host node: %v", err) http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } myENIConfig, err := eniconfig.GetNodeSpecificENIConfigName(node) if err != nil { log.Errorf("Failed to get ENI config: %v", err) http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } responseJSON, err := json.Marshal(myENIConfig) if err != nil { log.Errorf("Failed to marshal ENI config: %v", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } logErr(w.Write(responseJSON)) } } func networkEnvV1RequestHandler() func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { responseJSON, err := json.Marshal(networkutils.GetConfigForDebug()) if err != nil { log.Errorf("Failed to marshal network env var data: %v", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } logErr(w.Write(responseJSON)) } } func ipamdEnvV1RequestHandler() func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { responseJSON, err := json.Marshal(GetConfigForDebug()) if err != nil { log.Errorf("Failed to marshal ipamd env var data: %v", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } logErr(w.Write(responseJSON)) } } func logErr(_ int, err error) { if err != nil { log.Errorf("Write failed: %v", err) } } // disableIntrospection returns true if we should disable the introspection func disableIntrospection() bool { return getEnvBoolWithDefault(envDisableIntrospection, false) } func getEnvBoolWithDefault(envName string, def bool) bool { if strValue := os.Getenv(envName); strValue != "" { parsedValue, err := strconv.ParseBool(strValue) if err == nil { return parsedValue } log.Errorf("Failed to parse %s, using default `%t`: %v", envName, def, err.Error()) } return def }