Ajout wazuh-agent-supervisor

A la place de wazuh-agent-add
This commit is contained in:
Laurent Gourvenec 2025-05-16 14:23:15 +02:00
parent 49a474d300
commit e2236fd701
10 changed files with 458 additions and 0 deletions

View File

@ -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"]

View File

@ -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

View File

@ -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=

View File

@ -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

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -0,0 +1,8 @@
<ossec_config>
<client>
<server>
<address>wazuh-pp.in.nuonet.fr</address>
<port>1514</port>
</server>
</client>
</ossec_config>

View File

@ -0,0 +1,13 @@
<ossec_config>
<client>
<server>
<address>{{ getenv "WAZUH_MANAGER_HOST" }}</address>
<port>{{ getenv "WAZUH_MANAGER_PORT" "1514" }}</port>
</server>
</client>
<localfile>
<location>/var/log/containers/*.log</location>
<log_format>syslog</log_format>
</localfile>
</ossec_config>