diff --git a/wazuh-agent-supervisor/Dockerfile b/wazuh-agent-supervisor/Dockerfile new file mode 100644 index 0000000..72ee48f --- /dev/null +++ b/wazuh-agent-supervisor/Dockerfile @@ -0,0 +1,46 @@ +# Build the tool + +FROM reg.cadoles.com/proxy_cache/library/golang:1.24 AS build + +ARG VERSION=v1.0.0 + +COPY go.mod . +COPY go.sum . +COPY src src + +RUN env CGO_ENABLED=0 go install -v -ldflags="-w -s -X main.version=${VERSION} -X 'main.BuildDate=$(/usr/bin/date --utc --rfc-3339=seconds)'" ./... + +# Copy the tool and install gotmpl and wazuh + +FROM reg.cadoles.com/proxy_cache/library/debian:12.10 + +ARG WAZUH_VERSION=4.11.1-1 + +RUN export DEBIAN_FRONTEND=noninteractive && \ + apt-get update -y && \ + apt-get install -y gpg curl && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +RUN curl -s https://packages.wazuh.com/key/GPG-KEY-WAZUH | gpg --no-default-keyring --keyring gnupg-ring:/usr/share/keyrings/wazuh.gpg --import && chmod 644 /usr/share/keyrings/wazuh.gpg + +RUN echo "deb [signed-by=/usr/share/keyrings/wazuh.gpg] https://packages.wazuh.com/4.x/apt/ stable main" | tee -a /etc/apt/sources.list.d/wazuh.list + +RUN apt-get update && \ + apt-get install -y wazuh-agent=$WAZUH_VERSION procps && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + + +ARG GOMPLATE_VERSION=v4.3.2 + +# Install gomplate (little file templating engine: https://github.com/hairyhenderson/gomplate) +RUN curl -o /usr/local/bin/gomplate -sSL https://github.com/hairyhenderson/gomplate/releases/download/$GOMPLATE_VERSION/gomplate_linux-amd64 \ + && chmod +x /usr/local/bin/gomplate + +COPY --from=build /go/bin/wazuh-agent-autoadd /usr/local/bin/ + +COPY init_wazuh_agent.sh . +RUN chmod 755 /init_wazuh_agent.sh + +ENTRYPOINT ["/init_wazuh_agent.sh"] diff --git a/wazuh-agent-supervisor/go.mod b/wazuh-agent-supervisor/go.mod new file mode 100644 index 0000000..6cd8363 --- /dev/null +++ b/wazuh-agent-supervisor/go.mod @@ -0,0 +1,5 @@ +module forge.cadoles.com/cadoles/wazuh-agent-k8s-autoadd + +go 1.24.3 + +require github.com/caarlos0/env/v11 v11.3.1 diff --git a/wazuh-agent-supervisor/go.sum b/wazuh-agent-supervisor/go.sum new file mode 100644 index 0000000..1724948 --- /dev/null +++ b/wazuh-agent-supervisor/go.sum @@ -0,0 +1,2 @@ +github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA= +github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U= diff --git a/wazuh-agent-supervisor/init_wazuh_agent.sh b/wazuh-agent-supervisor/init_wazuh_agent.sh new file mode 100644 index 0000000..18fe365 --- /dev/null +++ b/wazuh-agent-supervisor/init_wazuh_agent.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +set -e +set -o pipefail + +# Generate ossec configuration +TEMPLATE_CONF_FILES=$(find /var/ossec/etc -name '*.gotmpl') + +for TEMPLATE_FILE in $TEMPLATE_CONF_FILES; do + DEST_FILE=${TEMPLATE_FILE%'.gotmpl'} + echo 'Generating file '$DEST_FILE'...' + gomplate -f $TEMPLATE_FILE > $DEST_FILE + chown --reference=$TEMPLATE_FILE $DEST_FILE + chmod --reference=$TEMPLATE_FILE $DEST_FILE +done + +# Add agent to Wazuh manager server +wazuh-agent-autoadd +#wazuh-agent-supervisor +# TODO: rename autoadd -> supervisor + remove following lines + +while true; do + sleep 10 +done + +echo "ERROR: program exited. Container will stop in 5s." +sleep 5 diff --git a/wazuh-agent-supervisor/src/cmd/wazuh-agent-autoadd/wazuh-agent-autoadd.go b/wazuh-agent-supervisor/src/cmd/wazuh-agent-autoadd/wazuh-agent-autoadd.go new file mode 100644 index 0000000..d38ea71 --- /dev/null +++ b/wazuh-agent-supervisor/src/cmd/wazuh-agent-autoadd/wazuh-agent-autoadd.go @@ -0,0 +1,35 @@ +package main + +import ( + "log" + "os" + + "forge.cadoles.com/cadoles/wazuh-agent-k8s-autoadd/src/internal/config" + "forge.cadoles.com/cadoles/wazuh-agent-k8s-autoadd/src/internal/wazuh" +) + +func main() { + cfg, err := config.NewConfig() + if err != nil { + log.Print(err) + os.Exit(1) + } + + key, err := wazuh.AddAgent(cfg) + if err != nil { + log.Print(err) + os.Exit(2) + } + + err = wazuh.ImportAuthKey(key) + if err != nil { + log.Print(err) + os.Exit(2) + } + + err = wazuh.Start() + if err != nil { + log.Print(err) + os.Exit(2) + } +} diff --git a/wazuh-agent-supervisor/src/internal/config/config.go b/wazuh-agent-supervisor/src/internal/config/config.go new file mode 100644 index 0000000..610c25b --- /dev/null +++ b/wazuh-agent-supervisor/src/internal/config/config.go @@ -0,0 +1,27 @@ +package config + +import ( + "github.com/caarlos0/env/v11" + "strconv" +) + +type Config struct { + WazuhManagerHost string `env:"WAZUH_MANAGER_HOST,required,notEmpty"` + WazuhManagerAPIPort int `env:"WAZUH_MANAGER_API_PORT,notEmpty" envDefault:"55000"` + BaseURL string + User string `env:"WAZUH_MANAGER_USER,required,notEmpty"` + Passwd string `env:"WAZUH_MANAGER_PASSWD,required,notEmpty"` + SkipSSLVerification bool `env:"WAZUH_MANAGER_SKIP_SSL_VERIFICATION" envDefault:"false"` + NodeName string `env:"NODE_NAME,required,notEmpty"` +} + +func NewConfig() (*Config, error) { + cfg := &Config{} + if err := env.Parse(cfg); err != nil { + return nil, err + } + + cfg.BaseURL = "https://" + cfg.WazuhManagerHost + ":" + strconv.Itoa(cfg.WazuhManagerAPIPort) + + return cfg, nil +} diff --git a/wazuh-agent-supervisor/src/internal/wazuh/wazuh_api.go b/wazuh-agent-supervisor/src/internal/wazuh/wazuh_api.go new file mode 100644 index 0000000..d3fb774 --- /dev/null +++ b/wazuh-agent-supervisor/src/internal/wazuh/wazuh_api.go @@ -0,0 +1,255 @@ +package wazuh + +import ( + "bytes" + "crypto/tls" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + + "forge.cadoles.com/cadoles/wazuh-agent-k8s-autoadd/src/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) (string, 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, cfg.NodeName, token) + if err != nil { + return "", err + } + + if id != "" { + log.Print("ID for "+cfg.NodeName+" found: ", id) + key, err := getKey(cfg, id, token) + return key, err + } + + agent := struct { + Name string `json:"name"` + }{Name: cfg.NodeName} + 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)) + } + + log.Print("ID for "+cfg.NodeName+" created: ", agentInfo.Data.ID) + + return agentInfo.Data.Key, nil +} diff --git a/wazuh-agent-supervisor/src/internal/wazuh/wazuh_cmd.go b/wazuh-agent-supervisor/src/internal/wazuh/wazuh_cmd.go new file mode 100644 index 0000000..0d23662 --- /dev/null +++ b/wazuh-agent-supervisor/src/internal/wazuh/wazuh_cmd.go @@ -0,0 +1,40 @@ +package wazuh + +import ( + "io" + "log" + "os/exec" +) + +func ExecStatus() error { + return nil +} + +func ImportAuthKey(key string) error { + cmd := exec.Command("/var/ossec/bin/manage_agents", "-i", key) + stdin, err := cmd.StdinPipe() + if err != nil { + return err + } + + go func() { + defer stdin.Close() + io.WriteString(stdin, "y\n\n") + }() + + out, err := cmd.CombinedOutput() + if err != nil { + log.Print("Error while importing auth key:", out) + } + return err + +} + +func Start() error { + cmd := exec.Command("/var/ossec/bin/wazuh-control", "start") + out, err := cmd.CombinedOutput() + if err != nil { + log.Print("Error while starting wazuh client:", out) + } + return err +} diff --git a/wazuh-agent-supervisor/var_ossec_etc/ossec.conf b/wazuh-agent-supervisor/var_ossec_etc/ossec.conf new file mode 100644 index 0000000..cc74489 --- /dev/null +++ b/wazuh-agent-supervisor/var_ossec_etc/ossec.conf @@ -0,0 +1,8 @@ + + + +
wazuh-pp.in.nuonet.fr
+ 1514 +
+
+
\ No newline at end of file diff --git a/wazuh-agent-supervisor/var_ossec_etc/ossec.conf.gotmpl b/wazuh-agent-supervisor/var_ossec_etc/ossec.conf.gotmpl new file mode 100644 index 0000000..44b5506 --- /dev/null +++ b/wazuh-agent-supervisor/var_ossec_etc/ossec.conf.gotmpl @@ -0,0 +1,13 @@ + + + +
{{ getenv "WAZUH_MANAGER_HOST" }}
+ {{ getenv "WAZUH_MANAGER_PORT" "1514" }} +
+
+ + + /var/log/containers/*.log + syslog + +
\ No newline at end of file