|
1 | 1 | package handlers |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "bytes" |
4 | 5 | "compress/gzip" |
5 | 6 | "context" |
| 7 | + "crypto/sha256" |
| 8 | + "encoding/base64" |
6 | 9 | "encoding/json" |
7 | 10 | "fmt" |
8 | 11 | "io" |
@@ -1063,3 +1066,124 @@ func (h *HandlersTLS) EnrollPackageHandler(w http.ResponseWriter, r *http.Reques |
1063 | 1066 | return |
1064 | 1067 | } |
1065 | 1068 | } |
| 1069 | + |
| 1070 | +// OsqueryConfigEndpointHandler - Function to handle the osquery configuration endpoint |
| 1071 | +func (h *HandlersTLS) OsqueryConfigEndpointHandler(w http.ResponseWriter, r *http.Request) { |
| 1072 | + // Retrieve environment variable |
| 1073 | + envVar := r.PathValue("env") |
| 1074 | + if envVar == "" { |
| 1075 | + utils.HTTPResponse(w, "", http.StatusBadRequest, []byte("")) |
| 1076 | + return |
| 1077 | + } |
| 1078 | + // To prevent abuse, check if the received UUID is valid |
| 1079 | + if !utils.CheckUUID(envVar) { |
| 1080 | + utils.HTTPResponse(w, "", http.StatusBadRequest, []byte("")) |
| 1081 | + return |
| 1082 | + } |
| 1083 | + // Extract secret |
| 1084 | + secretVar := r.PathValue("secret") |
| 1085 | + if secretVar == "" { |
| 1086 | + utils.HTTPResponse(w, "", http.StatusBadRequest, []byte("")) |
| 1087 | + return |
| 1088 | + } |
| 1089 | + confirmed := false |
| 1090 | + integrityCheck := false |
| 1091 | + for _, confEndpoint := range *h.ConfigEndpoints { |
| 1092 | + if confEndpoint.Environment == envVar && confEndpoint.Secret == secretVar { |
| 1093 | + confirmed = true |
| 1094 | + integrityCheck = confEndpoint.IntegrityCheck |
| 1095 | + break |
| 1096 | + } |
| 1097 | + } |
| 1098 | + if !confirmed { |
| 1099 | + utils.HTTPResponse(w, "", http.StatusForbidden, []byte("")) |
| 1100 | + return |
| 1101 | + } |
| 1102 | + // If we are here, the secret is confirmed, so we can proceed to get the environment |
| 1103 | + env, err := h.Envs.GetByUUID(envVar) |
| 1104 | + if err != nil { |
| 1105 | + log.Err(err).Msg("error getting environment") |
| 1106 | + utils.HTTPResponse(w, "", http.StatusInternalServerError, []byte("")) |
| 1107 | + return |
| 1108 | + } |
| 1109 | + // Debug HTTP |
| 1110 | + if h.DebugHTTPConfig.EnableHTTP { |
| 1111 | + utils.DebugHTTPDump(h.DebugHTTP, r, h.DebugHTTPConfig.ShowBody) |
| 1112 | + } |
| 1113 | + // Decode read POST body |
| 1114 | + var o types.OsqueryConfigRequest |
| 1115 | + body, err := io.ReadAll(r.Body) |
| 1116 | + if err != nil { |
| 1117 | + log.Err(err).Msg("error reading POST body") |
| 1118 | + utils.HTTPResponse(w, "", http.StatusInternalServerError, []byte("")) |
| 1119 | + return |
| 1120 | + } |
| 1121 | + if err := json.Unmarshal(body, &o); err != nil { |
| 1122 | + log.Err(err).Msg("error parsing POST body") |
| 1123 | + utils.HTTPResponse(w, "", http.StatusInternalServerError, []byte("")) |
| 1124 | + return |
| 1125 | + } |
| 1126 | + // Decode base64 configuration |
| 1127 | + configDecoded, err := base64.StdEncoding.DecodeString(o.Configuration) |
| 1128 | + if err != nil { |
| 1129 | + log.Err(err).Msg("error decoding base64 configuration") |
| 1130 | + utils.HTTPResponse(w, "", http.StatusInternalServerError, []byte("")) |
| 1131 | + return |
| 1132 | + } |
| 1133 | + // Unzip configuration |
| 1134 | + gzipReader, err := gzip.NewReader(bytes.NewReader(configDecoded)) |
| 1135 | + if err != nil { |
| 1136 | + log.Err(err).Msg("error decoding gzip configuration") |
| 1137 | + utils.HTTPResponse(w, "", http.StatusInternalServerError, []byte("")) |
| 1138 | + return |
| 1139 | + } |
| 1140 | + defer gzipReader.Close() |
| 1141 | + const maxConfigSize = 500 * 1024 |
| 1142 | + limitedReader := io.LimitReader(gzipReader, maxConfigSize+1) |
| 1143 | + configuration, err := io.ReadAll(limitedReader) |
| 1144 | + if err != nil { |
| 1145 | + log.Err(err).Msg("error reading unzipped configuration") |
| 1146 | + utils.HTTPResponse(w, "", http.StatusInternalServerError, []byte("")) |
| 1147 | + return |
| 1148 | + } |
| 1149 | + if len(configuration) > maxConfigSize { |
| 1150 | + log.Error().Msg("unzipped configuration is larger than 500KB") |
| 1151 | + utils.HTTPResponse(w, "", http.StatusRequestEntityTooLarge, []byte("")) |
| 1152 | + return |
| 1153 | + } |
| 1154 | + // Verify integrity of the configuration using the provided hash |
| 1155 | + if integrityCheck { |
| 1156 | + hash := sha256.Sum256(configuration) |
| 1157 | + computedIntegrity := fmt.Sprintf("%x", hash) |
| 1158 | + if o.Integrity != computedIntegrity { |
| 1159 | + log.Warn(). |
| 1160 | + Str("expected_integrity", o.Integrity). |
| 1161 | + Str("computed_integrity", computedIntegrity). |
| 1162 | + Msg("configuration integrity check failed") |
| 1163 | + utils.HTTPResponse(w, "", http.StatusBadRequest, []byte("")) |
| 1164 | + return |
| 1165 | + } |
| 1166 | + } |
| 1167 | + // Parse configuration |
| 1168 | + cnf, err := h.Envs.GenStructConf(configuration) |
| 1169 | + if err != nil { |
| 1170 | + log.Err(err).Msg("error parsing configuration") |
| 1171 | + utils.HTTPResponse(w, "", http.StatusInternalServerError, []byte("")) |
| 1172 | + return |
| 1173 | + } |
| 1174 | + // Update full configuration |
| 1175 | + if err := h.Envs.UpdateConfiguration(env.UUID, cnf); err != nil { |
| 1176 | + log.Err(err).Msg("error saving configuration") |
| 1177 | + utils.HTTPResponse(w, "", http.StatusInternalServerError, []byte("")) |
| 1178 | + return |
| 1179 | + } |
| 1180 | + // Update all configuration parts |
| 1181 | + if err := h.Envs.UpdateConfigurationParts(env.UUID, cnf); err != nil { |
| 1182 | + log.Err(err).Msg("error saving configuration parts") |
| 1183 | + utils.HTTPResponse(w, "", http.StatusInternalServerError, []byte("")) |
| 1184 | + return |
| 1185 | + } |
| 1186 | + response := TLSResponse{Message: "configuration saved successfully"} |
| 1187 | + // Send response |
| 1188 | + utils.HTTPResponse(w, utils.JSONApplicationUTF8, http.StatusOK, response) |
| 1189 | +} |
0 commit comments