diff --git a/cmd/cloud-init-server/metadata_handlers.go b/cmd/cloud-init-server/metadata_handlers.go index 90f3086d..559ae698 100644 --- a/cmd/cloud-init-server/metadata_handlers.go +++ b/cmd/cloud-init-server/metadata_handlers.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "net/http" "strings" @@ -51,29 +52,54 @@ func MetaDataHandler(smd smdclient.SMDClientInterface, store cistore.Store) http var err error // If this request includes an id, it can be interrpreted as an impersonation request if urlId == "" { + log.Debug().Msg("no id specified in request, attempting to identify based on requesting IP") ip := getActualRequestIP(r) + log.Debug().Msgf("requesting IP is: %s", ip) // Get the component information from the SMD client id, err = smd.IDfromIP(ip) if err != nil { - log.Print(err) - http.Error(w, "Failed to retrieve node ID from IP address", http.StatusUnprocessableEntity) + log.Printf("did not find id from ip %s: %v", ip, err) + w.WriteHeader(http.StatusUnprocessableEntity) return } else { log.Printf("xname %s with ip %s found\n", id, ip) } + } else { + id = urlId } log.Debug().Msgf("Getting metadata for id: %s", id) smdComponent, err := smd.ComponentInformation(id) if err != nil { - log.Debug().Msgf("Failed to get component information for %s: %s", id, err) - // If the component information is not available, return a 404 - http.Error(w, "Node not found in SMD. Instance-data not available", http.StatusNotFound) + if esr, ok := err.(smdclient.ErrSMDResponse); ok { + var msg string + var status int + switch esr.HTTPResponse.StatusCode { + case http.StatusNotFound: + msg = fmt.Sprintf("node %s not found in SMD", id) + status = http.StatusNotFound + default: + msg = fmt.Sprintf("failed to get component information for node %s: %v", id, err) + status = http.StatusInternalServerError + } + http.Error(w, msg, status) + } else { + log.Debug().Msgf("failed to get component information for node %s: %s", id, err) + http.Error(w, fmt.Sprintf("internal error occurred fetching component information for node %s", id), http.StatusInternalServerError) + } return } groups, err := smd.GroupMembership(id) if err != nil { - // If the group information is not available, return an empty list - groups = []string{} + if esr, ok := err.(smdclient.ErrSMDResponse); ok { + switch esr.HTTPResponse.StatusCode { + case http.StatusBadRequest: + http.Error(w, fmt.Sprintf("%s is not a valid xname for SMD", id), http.StatusBadRequest) + return + default: + // If the group information is not available, return an empty list + groups = []string{} + } + } } bootIP, err := smd.IPfromID(id) if err != nil { diff --git a/cmd/cloud-init-server/userdata_handlers.go b/cmd/cloud-init-server/userdata_handlers.go index 354b4a0a..9549c042 100644 --- a/cmd/cloud-init-server/userdata_handlers.go +++ b/cmd/cloud-init-server/userdata_handlers.go @@ -57,7 +57,7 @@ func GroupUserDataHandler(smd smdclient.SMDClientInterface, store cistore.Store) } if !isUserInGroup(id, group, smd) { - http.Error(w, "Group not found", http.StatusNotFound) + http.Error(w, fmt.Sprintf("node %s is not a member of group %s (node and/or group may not exist in SMD)", id, group), http.StatusNotFound) return } diff --git a/cmd/cloud-init-server/vendordata_handlers.go b/cmd/cloud-init-server/vendordata_handlers.go index 9b9e0f3b..4aa4aa69 100644 --- a/cmd/cloud-init-server/vendordata_handlers.go +++ b/cmd/cloud-init-server/vendordata_handlers.go @@ -32,11 +32,13 @@ func VendorDataHandler(smd smdclient.SMDClientInterface, store cistore.Store) ht var err error // If this request includes an id, it can be interrpreted as an impersonation request if urlId == "" { + log.Debug().Msg("no id specified in request, attempting to identify based on requesting IP") ip := getActualRequestIP(r) + log.Debug().Msgf("requesting IP is: %s", ip) // Get the component information from the SMD client id, err = smd.IDfromIP(ip) if err != nil { - log.Print(err) + log.Printf("did not find id from ip %s: %v", ip, err) w.WriteHeader(http.StatusUnprocessableEntity) return } else { @@ -45,7 +47,18 @@ func VendorDataHandler(smd smdclient.SMDClientInterface, store cistore.Store) ht } groups, err := smd.GroupMembership(id) if err != nil { - log.Debug().Msgf("Error getting group membership: %s", err) + if esr, ok := err.(smdclient.ErrSMDResponse); ok { + switch esr.HTTPResponse.StatusCode { + case http.StatusNotFound: + log.Warn().Msgf("node %s not found in SMD, include list will be empty", id) + case http.StatusBadRequest: + log.Warn().Msgf("node %s is an invalid xname in SMD, include list will be empty", id) + default: + log.Error().Err(err).Msg("unhandled HTTP response from SMD, include list will be empty") + } + } else { + log.Error().Err(err).Msgf("failed to get group membership for id %s, include list will be empty", id) + } } clusterDefaults, err := store.GetClusterDefaults() @@ -57,7 +70,7 @@ func VendorDataHandler(smd smdclient.SMDClientInterface, store cistore.Store) ht } extendedInstanceData, err := store.GetInstanceInfo(id) if err != nil { - log.Err(err).Msg("Error getting instance info") + log.Err(err).Msgf("Error getting instance info for id %s", id) } if extendedInstanceData.CloudInitBaseURL != "" { baseUrl = extendedInstanceData.CloudInitBaseURL diff --git a/internal/smdclient/SMDclient.go b/internal/smdclient/SMDclient.go index 7c466677..3e0e65ff 100644 --- a/internal/smdclient/SMDclient.go +++ b/internal/smdclient/SMDclient.go @@ -38,10 +38,6 @@ type SMDClientInterface interface { // golang lint // Expand this client to handle more of the SMD API and work more directly with the resources it manages -var ( - ErrUnmarshal = errors.New("cannot unmarshal JSON") -) - // SMDClient is a client for SMD type SMDClient struct { clusterName string @@ -206,6 +202,10 @@ func (s *SMDClient) getSMD(ep string, smd interface{}) error { defer func() { _ = resp.Body.Close() // ignoring error on deferred Close }() + if resp.StatusCode >= 400 { + log.Error().Msgf("SMD GET request went through, but returned unsuccessful HTTP response (HTTP %d)", resp.StatusCode) + return ErrSMDResponse{HTTPResponse: resp} + } body, err := io.ReadAll(resp.Body) if err != nil { log.Error().Err(err).Msg("failed to read response body") @@ -339,7 +339,9 @@ func (s *SMDClient) MACfromID(id string) (string, error) { // GroupMembership returns the group labels for the xname with the given ID func (s *SMDClient) GroupMembership(id string) ([]string, error) { if id == "" { - log.Err(errors.New("ID is empty")).Msg("ID is empty") + err := errors.New("ID is empty") + log.Err(err).Msg("failed to get group membership") + return []string{}, err } ml := new(sm.Membership) ep := "/hsm/v2/memberships/" + id @@ -352,6 +354,9 @@ func (s *SMDClient) GroupMembership(id string) ([]string, error) { func (s *SMDClient) ComponentInformation(id string) (base.Component, error) { var node base.Component + if strings.Trim(id, " \t") == "" { + return node, ErrEmptyID + } ep := "/hsm/v2/State/Components/" + id err := s.getSMD(ep, &node) if err != nil { diff --git a/internal/smdclient/errors.go b/internal/smdclient/errors.go new file mode 100644 index 00000000..0c52e054 --- /dev/null +++ b/internal/smdclient/errors.go @@ -0,0 +1,21 @@ +package smdclient + +import ( + "errors" + "fmt" + "net/http" +) + +var ( + ErrUnmarshal = errors.New("cannot unmarshal JSON") + ErrEmptyID = errors.New("empty id") +) + +// ErrSMDResponse contains the HTTP response of a REST API request to SMD. +type ErrSMDResponse struct { + HTTPResponse *http.Response +} + +func (esr ErrSMDResponse) Error() string { + return fmt.Sprintf("SMD response returned %s", esr.HTTPResponse.Status) +}