Ajout wazuh-agent-supervisor
A la place de wazuh-agent-add
This commit is contained in:
parent
49a474d300
commit
e2236fd701
46
wazuh-agent-supervisor/Dockerfile
Normal file
46
wazuh-agent-supervisor/Dockerfile
Normal 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"]
|
5
wazuh-agent-supervisor/go.mod
Normal file
5
wazuh-agent-supervisor/go.mod
Normal 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
|
2
wazuh-agent-supervisor/go.sum
Normal file
2
wazuh-agent-supervisor/go.sum
Normal 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=
|
27
wazuh-agent-supervisor/init_wazuh_agent.sh
Normal file
27
wazuh-agent-supervisor/init_wazuh_agent.sh
Normal 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
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
27
wazuh-agent-supervisor/src/internal/config/config.go
Normal file
27
wazuh-agent-supervisor/src/internal/config/config.go
Normal 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
|
||||||
|
}
|
255
wazuh-agent-supervisor/src/internal/wazuh/wazuh_api.go
Normal file
255
wazuh-agent-supervisor/src/internal/wazuh/wazuh_api.go
Normal 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
|
||||||
|
}
|
40
wazuh-agent-supervisor/src/internal/wazuh/wazuh_cmd.go
Normal file
40
wazuh-agent-supervisor/src/internal/wazuh/wazuh_cmd.go
Normal 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
|
||||||
|
}
|
8
wazuh-agent-supervisor/var_ossec_etc/ossec.conf
Normal file
8
wazuh-agent-supervisor/var_ossec_etc/ossec.conf
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<ossec_config>
|
||||||
|
<client>
|
||||||
|
<server>
|
||||||
|
<address>wazuh-pp.in.nuonet.fr</address>
|
||||||
|
<port>1514</port>
|
||||||
|
</server>
|
||||||
|
</client>
|
||||||
|
</ossec_config>
|
13
wazuh-agent-supervisor/var_ossec_etc/ossec.conf.gotmpl
Normal file
13
wazuh-agent-supervisor/var_ossec_etc/ossec.conf.gotmpl
Normal 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>
|
Loading…
x
Reference in New Issue
Block a user