package wazuh import ( "bytes" "crypto/tls" "encoding/json" "fmt" "io" "log" "net/http" "forge.cadoles.com/cadoles/wazuh-agent-k8s-autoadd/internal/config" ) const APIAuthenticate = "/security/user/authenticate" const APIAgents = "/agents" // TODO: add APIAgentsSearch and APIAgentsGetKey type AuthResponse struct { Data struct { Token string `json:"token"` } `json:"data"` Error int `json:"error"` } type AddAgentResponse struct { Data struct { ID string `json:"id"` Key string `json:"key"` } `json:"data"` Error int `json:"error"` } type ListAgentsResponse struct { Data struct { AffectedItems []struct { ID string `json:"id"` } `json:"affected_items"` TotalAffectedItems int `json:"total_affected_items"` TotalFailedItems int `json:"total_failed_items"` FailedItems []struct{} `json:"failed_items"` } `json:"data"` Message string `json: message` Error int `json:"error"` } type GetAgentKeyResponse struct { Data struct { AffectedItems []struct { ID string `json:"id"` Key string `json:"key"` } `json:"affected_items"` TotalAffectedItems int `json:"total_affected_items"` TotalFailedItems int `json:"total_failed_items"` FailedItems []struct{} `json:"failed_items"` } `json:"data"` Message string `json: message` Error int `json:"error"` } func responseToError(res *http.Response) error { bytedata, err := io.ReadAll(res.Body) if err != nil { log.Print("Error while reading a response's body") } reqBody := string(bytedata) return fmt.Errorf("Bad status on %v: %d\n%+v", res.Request.URL.Host + res.Request.URL.Path, res.StatusCode, reqBody) } func sendRequest(cfg *config.Config, req *http.Request) (*http.Response, error) { client := http.DefaultClient if cfg.SkipSSLVerification { tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } client = &http.Client{Transport: tr} } res, err := client.Do(req) return res, err } func getJWT(cfg *config.Config) (string, error) { req, err := http.NewRequest(http.MethodPost, cfg.BaseURL+APIAuthenticate, http.NoBody) if err != nil { return "", fmt.Errorf("Cannot create request for %v : %+v", cfg.BaseURL+APIAuthenticate, err) } req.SetBasicAuth(cfg.User, cfg.Passwd) res, err := sendRequest(cfg, req) if err != nil { return "", err } defer res.Body.Close() if res.StatusCode != http.StatusOK { return "", responseToError(res) } body, err := io.ReadAll(res.Body) if err != nil { return "", err } var authInfo AuthResponse if err := json.Unmarshal(body, &authInfo); err != nil { return "", fmt.Errorf("Cannot unmarshal JSON: %v", string(body)) } return authInfo.Data.Token, nil } func searchAgent(cfg *config.Config, name string, token string) (string, error) { url := cfg.BaseURL+APIAgents+"?q=name="+name+"&select=id" req, err := http.NewRequest(http.MethodGet, url, http.NoBody) if err != nil { return "", fmt.Errorf("cannot create request for %v : %+v", url, err) } req.Header.Set("Authorization", "Bearer "+token) res, err := sendRequest(cfg, req) if err != nil { return "", err } defer res.Body.Close() if res.StatusCode != http.StatusOK { return "", responseToError(res) } body, err := io.ReadAll(res.Body) if err != nil { return "", err } var agents ListAgentsResponse if err := json.Unmarshal(body, &agents); err != nil { return "", fmt.Errorf("Cannot unmarshal JSON: %v", string(body)) } if agents.Data.TotalAffectedItems == 0 { return "", nil } if agents.Data.TotalAffectedItems > 1 { log.Print("Warning: more than 1 agent has name"+name) } return agents.Data.AffectedItems[0].ID, nil } func getKey(cfg *config.Config, id string, token string) (string, error) { url := cfg.BaseURL+APIAgents+"/"+id+"/key" req, err := http.NewRequest(http.MethodGet, url, http.NoBody) if err != nil { return "", fmt.Errorf("Cannot create request for %v : %+v", url, err) } req.Header.Set("Authorization", "Bearer "+token) res, err := sendRequest(cfg, req) if err != nil { return "", err } defer res.Body.Close() if res.StatusCode != http.StatusOK { return "", responseToError(res) } body, err := io.ReadAll(res.Body) if err != nil { return "", err } var agent GetAgentKeyResponse if err := json.Unmarshal(body, &agent); err != nil { return "", fmt.Errorf("Cannot unmarshal JSON: %v", string(body)) } if agent.Data.TotalAffectedItems == 0 { return "", fmt.Errorf("Error: no key found for id: %s", id) } if agent.Data.TotalAffectedItems > 1 { log.Print("Warning: more than 1 agent has id: ", id) } return agent.Data.AffectedItems[0].Key, nil } func AddAgent(cfg *config.Config) error { token, err := getJWT(cfg) if err != nil { return err } log.Print(token) ///////////////////////TODO: remove // First : look an agent is already registered with the same name // Second a : If agent is already registered, get token // Second b : Elese, add it simply // Outside : templatiser le fichier de conf ossec.conf // Outside : import key? Ou l'écrire dans un fichier ? // Au démarrage du conteneur, manage_agents pour importer la clé + wazuh-control pour restart wazuh agent ? id, err := searchAgent(cfg, "Test", token) if err != nil { return err } if id != "" { log.Print("ID for "+"Test"+" found: ", id) key, err := getKey(cfg, id, token) if err != nil { return err } log.Print("key: ", key) return nil } agent := struct { Name string `json:"name"` }{Name: "Test"} json_agent, err := json.Marshal(agent) if err != nil { return fmt.Errorf("Cannot marshal struct %+v: %+v", agent, err) } req, err := http.NewRequest(http.MethodPost, cfg.BaseURL+APIAgents, bytes.NewReader(json_agent)) if err != nil { return fmt.Errorf("Cannot create request for %v : %+v", cfg.BaseURL+APIAgents, err) } req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer "+token) res, err := sendRequest(cfg, req) if err != nil { return err } defer res.Body.Close() if res.StatusCode != http.StatusOK { return responseToError(res) } body, err := io.ReadAll(res.Body) if err != nil { return err } var agentInfo AddAgentResponse if err := json.Unmarshal(body, &agentInfo); err != nil { return fmt.Errorf("Cannot unmarshal JSON: %v", string(body)) } fmt.Printf("%+v", agentInfo) return nil }