Compare commits

..

No commits in common. "master" and "f/covfefe" have entirely different histories.

24 changed files with 1 additions and 1385 deletions

0
bite Normal file
View File

1
covfefe Normal file
View File

@ -0,0 +1 @@
fe

0
toto Normal file
View File

View File

@ -1,17 +0,0 @@
FROM reg.cadoles.com/proxy_cache/library/debian:12.10
RUN apt-get update \
&& 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 \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
CMD /var/ossec/bin/wazuh-control start

View File

@ -1 +0,0 @@
FROM golang:1.24 AS build

View File

@ -1,26 +0,0 @@
package main
import (
"log"
"os"
"forge.cadoles.com/cadoles/wazuh-agent-k8s-autoadd/internal/config"
"forge.cadoles.com/cadoles/wazuh-agent-k8s-autoadd/internal/wazuh"
)
func main() {
cfg, err := config.NewConfig()
if err != nil {
log.Print(err)
os.Exit(1)
}
// agent, err := wazuh_api.AddAgent
err = wazuh.AddAgent(cfg)
if err != nil {
log.Print(err)
os.Exit(2)
}
// wazuh_tmpl.Generate(agent)
}

View File

@ -1,5 +0,0 @@
module forge.cadoles.com/cadoles/wazuh-agent-k8s-autoadd
go 1.23.3
require github.com/caarlos0/env/v11 v11.3.1

View File

@ -1,2 +0,0 @@
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

@ -1,24 +0,0 @@
package config
import (
"github.com/caarlos0/env/v11"
"strings"
)
type Config struct {
BaseURL string `env:"WAZUH_MANAGER_BASE_URL,required,notEmpty"`
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"`
}
func NewConfig() (*Config, error) {
cfg := &Config{}
if err := env.Parse(cfg); err != nil {
return nil, err
}
cfg.BaseURL = strings.TrimSuffix(cfg.BaseURL, "/")
return cfg, nil
}

View File

@ -1,84 +0,0 @@
package wazuh
import (
"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"
type AuthResponse struct {
Data struct {
Token string `json:"token"`
} `json:"data"`
Error int `json:"error"`
}
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)
client := http.DefaultClient
if cfg.SkipSSLVerification {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client = &http.Client{Transport: tr}
}
res, err := client.Do(req)
if err != nil {
return "", err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return "", fmt.Errorf("Bad status on %v: %d", cfg.BaseURL+APIAuthenticate, res.StatusCode)
}
body, err := io.ReadAll(res.Body)
if err != nil {
log.Fatal(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 AddAgent(cfg *config.Config) error {
token, err := getJWT(cfg)
if err != nil {
return err
}
print(token) /*
resp, err := http.DefaultClient.Post(cfg.BaseURL + APIAgents)
if err != nil {
return err
}
defer resp.Body.Close()
switch resp.StatusCode {
case http.StatusOK:
return nil
default:
return false, fmt.Errorf("Bad status: %d", resp.StatusCode)
}
*/
return nil
}

View File

@ -1,259 +0,0 @@
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
}

View File

@ -1,16 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ./resources/daemonset.yaml
secretGenerator:
- name: wazuh-agent-secret
literals:
- A=A
configMapGenerator:
- name: wazuh-agent-env
literals:
- A=A

View File

@ -1,66 +0,0 @@
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: wazuh-agent
namespace: kube-system
labels:
app.kubernetes.io/name: wazuh-agent
spec:
selector:
matchLabels:
app.kubernetes.io/name: wazuh-agent
template:
metadata:
labels:
app.kubernetes.io/name: wazuh-agent
spec:
initContainers:
- name: wazuh-register
image: ??
envFrom:
- configMapRef:
name: wazuh-agent-env
- secretRef: # Peut-être à décortiquer plutôt
name: wazuh-agent-secret
resources:
limits:
memory: 200Mi
cpu: 500m
requests:
memory: 100Mi
cpu: 100m
volumeMounts:
- name: ossec-etc
mountPath: /var/ossec/etc/
securityContext:
allowPrivilegeEscalation: false
runAsNonRoot: true
runAsUser: 1000
containers:
- name: wazuh-agent
image: ??
envFrom:
- configMapRef:
name: wazuh-agent-env # nécessaire ?
- secretRef: # Peut-être à décortiquer plutôt
name: wazuh-agent-secret
# TODO: add liveness, readiness, startup probes with ports if necessary
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 200Mi
volumeMounts:
- name: var-log
mountPath: /var/log
readOnly: true
terminationGracePeriodSeconds: 30
volumes:
- name: var-log
hostPath:
path: /var/log
- name: ossec-etc
emptyDir:
sizeLimit: 1Mi

View File

@ -1,46 +0,0 @@
# 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

@ -1,5 +0,0 @@
module forge.cadoles.com/cadoles/wazuh-agent-k8s-autoadd
go 1.24.3
require github.com/caarlos0/env/v11 v11.3.1

View File

@ -1,2 +0,0 @@
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

@ -1,27 +0,0 @@
#!/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

@ -1,35 +0,0 @@
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

@ -1,27 +0,0 @@
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

@ -1,255 +0,0 @@
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

@ -1,40 +0,0 @@
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

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

View File

@ -1,13 +0,0 @@
<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>

427
wazuh.md
View File

@ -1,427 +0,0 @@
# Wazuh
## But
Il y a 2 sources de logs qui nous intéressent :
- les logs des conteneurs des pods ;
- l'audit de l'apiserver.
Il n'est pas possible de récupérer les logs des pods à travers l'audit de l'apiserver (à part peut-être de façon bancale en interrogeant l'endpoint /logs mais ce n'est pas viable).
La centralisation des logs de conteneurs de pods a plusieurs objectifs :
- l'aide au débogage des applications tournant dans le cluster (ex: portail)
- l'aide au débogage des applications "système" tournant dans le cluster (ex: coredns)
- la rétention d'informations légales (tentatives de connexion par exemple)
L'audit de l'apiserveur a plusieurs objectifs :
- la détection de comportement suspicieux (accès/modifications/tentatives d'accès aux ressources)
- l'aide au débogage en cas de grave disfonctionnement du cluster
## Audit de l'apiserver
### Solutions explorées
2 solutions ont été explorées :
1. Installer sur chaque noeud le client wazuh et créer un objet k8s de type audit policy ;
2. Installer sur le serveur wazuh un listener et créer un objet k8s de type audit policy.
La solution 1 n'est pas envisageable, les noeuds étant immutables.
Après recherches et discussion sur le slack de Wazuh, il semblerait qu'aucun agent wazuh ne supporte un environnement k8s et que la solution 2 soit la meilleure solution.
### Lab local
Pour un test local, la manière la plus simple est de suivre les docs suivantes :
- https://documentation.wazuh.com/4.11/deployment-options/docker/wazuh-container.html#single-node-deployment
- https://wazuh.com/blog/auditing-kubernetes-with-wazuh/
avec quelques modifications cependant pour ajouter un listener sur le port 1580 dans le conteneur de wazuh afin d'envoyer les logs à la socket unix de wazuh.
Attention : cette procédure est un POC largement améliorable. Cela reste avant tout une piste de démarrage pour travailler sur le sujet.
#### Installation du serveur wazuh
Suivre le tutoriel https://documentation.wazuh.com/4.11/deployment-options/docker/wazuh-container.html#single-node-deployment pour la génération des certificats.
Ajouter localement les fichiers suivants :
- custom-webhook.py
```
#!/var/ossec/framework/python/bin/python3
import json
from socket import socket, AF_UNIX, SOCK_DGRAM
from flask import Flask, request
# CONFIG
PORT = 1580
CERT = '/var/ossec/api/configuration/ssl/server.crt'
CERT_KEY = '/var/ossec/api/configuration/ssl/server.key'
# Analysisd socket address
socket_addr = '/var/ossec/queue/sockets/queue'
def send_event(msg):
string = '1:k8s:{0}'.format(json.dumps(msg))
sock = socket(AF_UNIX, SOCK_DGRAM)
sock.connect(socket_addr)
sock.send(string.encode())
sock.close()
return True
app = Flask(__name__)
context = (CERT, CERT_KEY)
@app.route('/', methods=['POST'])
def webhook():
if request.method == 'POST':
if send_event(request.json):
print("Request sent to Wazuh")
else:
print("Failed to send request to Wazuh")
return "Webhook received!"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=PORT, ssl_context=context)
```
- local_rules.xml
```
<group name="k8s_audit,">
<rule id="110002" level="0">
<location>k8s</location>
<field name="apiVersion">audit.k8s.io/v1</field>
<description>Kubernetes audit log.</description>
</rule>
<rule id="110003" level="5">
<if_sid>110002</if_sid>
<regex type="pcre2">requestURI\":.+", \"verb\": \"create</regex>
<description>Kubernetes request to create resource</description>
</rule>
<rule id="110004" level="5">
<if_sid>110002</if_sid>
<regex type="pcre2">requestURI\":.+", \"verb\": \"delete</regex>
<description>Kubernetes request to delete resource</description>
</rule>
</group>
```
Dans le docker-compose.yml, ajouter :
- le mapping de port "1580:1580" au wazuh.manager
- le mapping ./custom-webhook.py:/var/ossec/integrations/custom-webhook.py
- le mapping ./local_rules.xml:/var/ossec/etc/rules/local_rules.xml
Ce qui donne :
```
# Wazuh App Copyright (C) 2017, Wazuh Inc. (License GPLv2)
version: '3.7'
services:
wazuh.manager:
image: wazuh/wazuh-manager:4.11.1
hostname: wazuh.manager
restart: always
ulimits:
memlock:
soft: -1
hard: -1
nofile:
soft: 655360
hard: 655360
ports:
- "1514:1514"
- "1515:1515"
- "1580:1580"
- "514:514/udp"
- "55000:55000"
environment:
- INDEXER_URL=https://wazuh.indexer:9200
- INDEXER_USERNAME=admin
- INDEXER_PASSWORD=SecretPassword
- FILEBEAT_SSL_VERIFICATION_MODE=full
- SSL_CERTIFICATE_AUTHORITIES=/etc/ssl/root-ca.pem
- SSL_CERTIFICATE=/etc/ssl/filebeat.pem
- SSL_KEY=/etc/ssl/filebeat.key
- API_USERNAME=wazuh-wui
- API_PASSWORD=MyS3cr37P450r.*-
volumes:
- wazuh_api_configuration:/var/ossec/api/configuration
- wazuh_etc:/var/ossec/etc
- wazuh_logs:/var/ossec/logs
- wazuh_queue:/var/ossec/queue
- wazuh_var_multigroups:/var/ossec/var/multigroups
- wazuh_integrations:/var/ossec/integrations
- wazuh_active_response:/var/ossec/active-response/bin
- wazuh_agentless:/var/ossec/agentless
- wazuh_wodles:/var/ossec/wodles
- filebeat_etc:/etc/filebeat
- filebeat_var:/var/lib/filebeat
- ./config/wazuh_indexer_ssl_certs/root-ca-manager.pem:/etc/ssl/root-ca.pem
- ./config/wazuh_indexer_ssl_certs/wazuh.manager.pem:/etc/ssl/filebeat.pem
- ./config/wazuh_indexer_ssl_certs/wazuh.manager-key.pem:/etc/ssl/filebeat.key
- ./config/wazuh_cluster/wazuh_manager.conf:/wazuh-config-mount/etc/ossec.conf
- ./custom-webhook.py:/var/ossec/integrations/custom-webhook.py
- ./wazuh-webhook.service:/lib/systemd/system/wazuh-webhook.service
- ./local_rules.xml:/var/ossec/etc/rules/local_rules.xml
wazuh.indexer:
image: wazuh/wazuh-indexer:4.11.1
hostname: wazuh.indexer
restart: always
ports:
- "9200:9200"
environment:
- "OPENSEARCH_JAVA_OPTS=-Xms1g -Xmx1g"
ulimits:
memlock:
soft: -1
hard: -1
nofile:
soft: 65536
hard: 65536
volumes:
- wazuh-indexer-data:/var/lib/wazuh-indexer
- ./config/wazuh_indexer_ssl_certs/root-ca.pem:/usr/share/wazuh-indexer/certs/root-ca.pem
- ./config/wazuh_indexer_ssl_certs/wazuh.indexer-key.pem:/usr/share/wazuh-indexer/certs/wazuh.indexer.key
- ./config/wazuh_indexer_ssl_certs/wazuh.indexer.pem:/usr/share/wazuh-indexer/certs/wazuh.indexer.pem
- ./config/wazuh_indexer_ssl_certs/admin.pem:/usr/share/wazuh-indexer/certs/admin.pem
- ./config/wazuh_indexer_ssl_certs/admin-key.pem:/usr/share/wazuh-indexer/certs/admin-key.pem
- ./config/wazuh_indexer/wazuh.indexer.yml:/usr/share/wazuh-indexer/opensearch.yml
- ./config/wazuh_indexer/internal_users.yml:/usr/share/wazuh-indexer/opensearch-security/internal_users.yml
wazuh.dashboard:
image: wazuh/wazuh-dashboard:4.11.1
hostname: wazuh.dashboard
restart: always
ports:
- 443:5601
environment:
- INDEXER_USERNAME=admin
- INDEXER_PASSWORD=SecretPassword
- WAZUH_API_URL=https://wazuh.manager
- DASHBOARD_USERNAME=kibanaserver
- DASHBOARD_PASSWORD=kibanaserver
- API_USERNAME=wazuh-wui
- API_PASSWORD=MyS3cr37P450r.*-
volumes:
- ./config/wazuh_indexer_ssl_certs/wazuh.dashboard.pem:/usr/share/wazuh-dashboard/certs/wazuh-dashboard.pem
- ./config/wazuh_indexer_ssl_certs/wazuh.dashboard-key.pem:/usr/share/wazuh-dashboard/certs/wazuh-dashboard-key.pem
- ./config/wazuh_indexer_ssl_certs/root-ca.pem:/usr/share/wazuh-dashboard/certs/root-ca.pem
- ./config/wazuh_dashboard/opensearch_dashboards.yml:/usr/share/wazuh-dashboard/config/opensearch_dashboards.yml
- ./config/wazuh_dashboard/wazuh.yml:/usr/share/wazuh-dashboard/data/wazuh/config/wazuh.yml
- wazuh-dashboard-config:/usr/share/wazuh-dashboard/data/wazuh/config
- wazuh-dashboard-custom:/usr/share/wazuh-dashboard/plugins/wazuh/public/assets/custom
depends_on:
- wazuh.indexer
links:
- wazuh.indexer:wazuh.indexer
- wazuh.manager:wazuh.manager
volumes:
wazuh_api_configuration:
wazuh_etc:
wazuh_logs:
wazuh_queue:
wazuh_var_multigroups:
wazuh_integrations:
wazuh_active_response:
wazuh_agentless:
wazuh_wodles:
filebeat_etc:
filebeat_var:
wazuh-indexer-data:
wazuh-dashboard-config:
wazuh-dashboard-custom:
```
Ensuite, lancer le serveur avec
```
docker-compose up
```
Se connecter au conteneur pour installer flask (pour notre listener) :
```
docker exec single-node-wazuh.manager-1 /var/ossec/framework/python/bin/pip3 install flask
```
puis démarrer le serveur webhook :
```
docker exec single-node-wazuh.manager-1 /var/ossec/framework/python/bin/python3 /var/ossec/integrations/custom-webhook.py
```
A ce moment là, le serveur est prêt à recevoir des logs d'audit k8s.
Le serveur devrait être accessible sur https://127.0.0.1/app/login (admin/SecretPassword).
Note : il reste un problème avec les dashboard qui ne s'affichent pas dans l'interface web. Ce n'est pas grave, les logs reste accessibles dans l'exploration de données.
#### Lien entre le cluster et le serveur wazuh
Pour lier le cluster et le serveur wazuh en réseau, sans s'embêter avec les configurations réseau, on peut faire un tunnel (remplacer l'ip par l'ip du contenur wazuh-server) :
```
ssh -L 0.0.0.0:1581:172.21.0.3:1580 127.0.0.1
```
#### Création du cluster k8s "kind"
On va créer un cluster kind nommé "wazuh" avec 1 CP et 1 Worker. On ajoute 2 fichiers de conf pour kubeapi-server : audit-policy.yaml et audit-webhook.yaml.
wazuh/audit-policy.yaml:
```
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
# Dont log requests to the following API endpoints
- level: None
nonResourceURLs:
- '/healthz*'
- '/metrics'
- '/swagger*'
- '/version'
# Limit requests containing tokens to Metadata level so the token is not included in the log
#- level: Metadata
# omitStages:
# - RequestReceived
# resources:
# - group: authentication.k8s.io
# resources:
# - tokenreviews
# Extended audit of auth delegation
#- level: RequestResponse
# omitStages:
# - RequestReceived
# resources:
# - group: authorization.k8s.io
# resources:
# - subjectaccessreviews
# Log changes to pods at RequestResponse level
- level: RequestResponse
omitStages:
- RequestReceived
resources:
# core API group; add third-party API services and your API services if needed
- group: ''
resources: ['pods']
verbs: ['create', 'patch', 'update', 'delete']
# Log everything else at Metadata level
- level: Metadata
omitStages:
- RequestReceived
- level: RequestResponse
nonResourceURLs:
- '/logs'
```
wazuh/audit-webhook.yaml (attention, changer MON_IP par 192.168.10.XX):
```
apiVersion: v1
kind: Config
preferences: {}
clusters:
- name: wazuh-webhook
cluster:
insecure-skip-tls-verify: true
server: https://MON_IP:1581
# kubeconfig files require a context. Provide one for the API server.
current-context: webhook
contexts:
- context:
cluster: wazuh-webhook
user: kube-apiserver # Replace with name of API server if its different
name: webhook
```
wazuh.yaml:
```
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: wazuh
nodes:
- role: control-plane
image: reg.cadoles.com/dh/kindest/node:v1.28.7
kubeadmConfigPatches:
- |
kind: ClusterConfiguration
apiServer:
extraArgs:
audit-webhook-batch-max-size: "1"
audit-webhook-config-file: "/etc/kubernetes/audit-webhook.yaml"
audit-policy-file: "/etc/kubernetes/policies/audit-policy.yaml"
extraVolumes:
- name: audit-policies
hostPath: "/etc/kubernetes/policies"
mountPath: "/etc/kubernetes/policies"
readOnly: true
pathType: "DirectoryOrCreate"
- name: audit-webhook
hostPath: "/etc/kubernetes/audit-webhook.yaml"
mountPath: "/etc/kubernetes/audit-webhook.yaml"
readOnly: true
- |
kind: InitConfiguration
nodeRegistration:
kubeletExtraArgs:
node-labels: "ingress-ready=true"
extraMounts:
- hostPath: "./wazuh/audit-policy.yaml"
containerPath: "/etc/kubernetes/policies/audit-policy.yaml"
readOnly: true
- hostPath: "./wazuh/audit-webhook.yaml"
containerPath: "/etc/kubernetes/audit-webhook.yaml"
readOnly: true
extraPortMappings:
- containerPort: 31000
hostPort: 31001
listenAddress: "0.0.0.0" # Optional, defaults to "0.0.0.0"
- containerPort: 80
hostPort: 8081
listenAddress: "0.0.0.0" # Optional, defaults to "0.0.0.0"
labels:
ingress-ready: true
- role: worker
image: reg.cadoles.com/dh/kindest/node:v1.28.7
kubeadmConfigPatches:
- |
kind: JoinConfiguration
nodeRegistration:
kubeletExtraArgs:
system-reserved: memory=2Gi
```
```
kind create cluster --config wazuh.yaml
```
À ce moment là, on doit pouvoir voir des logs coté de custom-webhook.py qui reçoit des logs du cluster. Ensuite, dans l'interface web de wazuh, on doit pouvoir retrouver les logs du cluster dans l'exploration de données. Un filtre sur "data.apiVersion=audit.k8s.io/v1" devrait retourner uniquement les logs serveur.
### Analyse
Comme indiqué en introduction, cette méthode permet de recevoir dans Wazuh toutes les activités passant par l'apiserver. Cependant :
- il faut prendre en compte une augmentation de la consommation mémoire sur les controle-plane ;
- il faut customizer audit-policy.yaml afin de filtrer les appels à l'apiserver réellement important et éviter de se retrouver avec trop de logs ;
- il faut trouver un moyen de filtrer par cluster (prod et pp)
> The audit logging feature increases the memory consumption of the API server because some context required for auditing is stored for each request. Memory consumption depends on the audit logging configuration.
## Journalisation au niveau cluster
### Solutions explorées
D'après la documentation [kubernetes](https://kubernetes.io/docs/concepts/cluster-administration/logging/#cluster-level-logging-architectures), la meilleure solution dans notre cas est d'installer un daemonset dans le cluster ayant accès au dossier /var/log/pods.
Reste à déterminer quel logiciel faire tourner dans le pod.
### DaemonSet
Après discussion sur le slack de Wazuh, il nous ait conseillé par un employé de Wazuh d'utiliser un DaemonSet géré par OpenNix https://opennix.org/en/opensource/.
OpenNix offre 2 possibilités : utiliser le DaemonSet ou utiliser un opérateur k8s.
L'opérateur n'ayant été pull que peu de fois, et après analyse du dépôt source (https://github.com/pyToshka/wazuh-agent-kubernetes-operator), il semblerait qu'il ne soit utile que pour produire un DaemonSet et un Secret avec les données passées en paramètre. L'ajout d'un DaemonSet et d'un Secret directement semble plus adapté.
OpenNix est une organization russe. Il a été décidé de réimplémenter une solution pour intégrer l'agent Wazuh dans kubernetes. Cela se présente sous la forme de projets maintenus par Cadoles :
- wazuh-agent-k8s-autoadd : programme go pour enregistrer puis démarrer un agent Wazuh + Dockerfile
- wazuh-agent-kustom : kustomization d'un DaemonSet, d'une ConfigMap et d'un Secret
- wazuh-agent-container : Dockerfile pour créer un conteneur wazuh-agent
DaemonSet: 2 images, l'une register et produit le fichier de conf ossec, l'autre démarre ensuite avec l'agent wazuh. Health endpoint?
Demander :
- version du wazuh manager (la version de l'agent doit être inférieure ou égale)
- certificats ?
- nom/addresse du manager wazuh ou endpoint