plugins: use yaml.v3 (#2969)

* plugins: use yaml.v3

* lint
This commit is contained in:
mmetc 2024-04-25 17:34:49 +02:00 committed by GitHub
parent 2abc078e53
commit d2c4bc55fc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 68 additions and 28 deletions

View file

@ -82,11 +82,6 @@ linters-settings:
- "!**/pkg/apiserver/controllers/v1/errors.go" - "!**/pkg/apiserver/controllers/v1/errors.go"
yaml: yaml:
files: files:
- "!**/cmd/notification-dummy/main.go"
- "!**/cmd/notification-email/main.go"
- "!**/cmd/notification-http/main.go"
- "!**/cmd/notification-slack/main.go"
- "!**/cmd/notification-splunk/main.go"
- "!**/pkg/acquisition/acquisition.go" - "!**/pkg/acquisition/acquisition.go"
- "!**/pkg/acquisition/acquisition_test.go" - "!**/pkg/acquisition/acquisition_test.go"
- "!**/pkg/acquisition/modules/appsec/appsec.go" - "!**/pkg/acquisition/modules/appsec/appsec.go"

View file

@ -5,10 +5,11 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/crowdsecurity/crowdsec/pkg/protobufs"
"github.com/hashicorp/go-hclog" "github.com/hashicorp/go-hclog"
plugin "github.com/hashicorp/go-plugin" plugin "github.com/hashicorp/go-plugin"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v3"
"github.com/crowdsecurity/crowdsec/pkg/protobufs"
) )
type PluginConfig struct { type PluginConfig struct {
@ -32,6 +33,7 @@ func (s *DummyPlugin) Notify(ctx context.Context, notification *protobufs.Notifi
if _, ok := s.PluginConfigByName[notification.Name]; !ok { if _, ok := s.PluginConfigByName[notification.Name]; !ok {
return nil, fmt.Errorf("invalid plugin config name %s", notification.Name) return nil, fmt.Errorf("invalid plugin config name %s", notification.Name)
} }
cfg := s.PluginConfigByName[notification.Name] cfg := s.PluginConfigByName[notification.Name]
if cfg.LogLevel != nil && *cfg.LogLevel != "" { if cfg.LogLevel != nil && *cfg.LogLevel != "" {
@ -42,19 +44,22 @@ func (s *DummyPlugin) Notify(ctx context.Context, notification *protobufs.Notifi
logger.Debug(notification.Text) logger.Debug(notification.Text)
if cfg.OutputFile != nil && *cfg.OutputFile != "" { if cfg.OutputFile != nil && *cfg.OutputFile != "" {
f, err := os.OpenFile(*cfg.OutputFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) f, err := os.OpenFile(*cfg.OutputFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil { if err != nil {
logger.Error(fmt.Sprintf("Cannot open notification file: %s", err)) logger.Error(fmt.Sprintf("Cannot open notification file: %s", err))
} }
if _, err := f.WriteString(notification.Text + "\n"); err != nil { if _, err := f.WriteString(notification.Text + "\n"); err != nil {
f.Close() f.Close()
logger.Error(fmt.Sprintf("Cannot write notification to file: %s", err)) logger.Error(fmt.Sprintf("Cannot write notification to file: %s", err))
} }
err = f.Close() err = f.Close()
if err != nil { if err != nil {
logger.Error(fmt.Sprintf("Cannot close notification file: %s", err)) logger.Error(fmt.Sprintf("Cannot close notification file: %s", err))
} }
} }
fmt.Println(notification.Text) fmt.Println(notification.Text)
return &protobufs.Empty{}, nil return &protobufs.Empty{}, nil
@ -64,11 +69,12 @@ func (s *DummyPlugin) Configure(ctx context.Context, config *protobufs.Config) (
d := PluginConfig{} d := PluginConfig{}
err := yaml.Unmarshal(config.Config, &d) err := yaml.Unmarshal(config.Config, &d)
s.PluginConfigByName[d.Name] = d s.PluginConfigByName[d.Name] = d
return &protobufs.Empty{}, err return &protobufs.Empty{}, err
} }
func main() { func main() {
var handshake = plugin.HandshakeConfig{ handshake := plugin.HandshakeConfig{
ProtocolVersion: 1, ProtocolVersion: 1,
MagicCookieKey: "CROWDSEC_PLUGIN_KEY", MagicCookieKey: "CROWDSEC_PLUGIN_KEY",
MagicCookieValue: os.Getenv("CROWDSEC_PLUGIN_KEY"), MagicCookieValue: os.Getenv("CROWDSEC_PLUGIN_KEY"),

View file

@ -2,15 +2,17 @@ package main
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"os" "os"
"time" "time"
"github.com/crowdsecurity/crowdsec/pkg/protobufs"
"github.com/hashicorp/go-hclog" "github.com/hashicorp/go-hclog"
plugin "github.com/hashicorp/go-plugin" plugin "github.com/hashicorp/go-plugin"
mail "github.com/xhit/go-simple-mail/v2" mail "github.com/xhit/go-simple-mail/v2"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v3"
"github.com/crowdsecurity/crowdsec/pkg/protobufs"
) )
var baseLogger hclog.Logger = hclog.New(&hclog.LoggerOptions{ var baseLogger hclog.Logger = hclog.New(&hclog.LoggerOptions{
@ -72,19 +74,20 @@ func (n *EmailPlugin) Configure(ctx context.Context, config *protobufs.Config) (
} }
if d.Name == "" { if d.Name == "" {
return nil, fmt.Errorf("name is required") return nil, errors.New("name is required")
} }
if d.SMTPHost == "" { if d.SMTPHost == "" {
return nil, fmt.Errorf("SMTP host is not set") return nil, errors.New("SMTP host is not set")
} }
if d.ReceiverEmails == nil || len(d.ReceiverEmails) == 0 { if d.ReceiverEmails == nil || len(d.ReceiverEmails) == 0 {
return nil, fmt.Errorf("receiver emails are not set") return nil, errors.New("receiver emails are not set")
} }
n.ConfigByName[d.Name] = d n.ConfigByName[d.Name] = d
baseLogger.Debug(fmt.Sprintf("Email plugin '%s' use SMTP host '%s:%d'", d.Name, d.SMTPHost, d.SMTPPort)) baseLogger.Debug(fmt.Sprintf("Email plugin '%s' use SMTP host '%s:%d'", d.Name, d.SMTPHost, d.SMTPPort))
return &protobufs.Empty{}, nil return &protobufs.Empty{}, nil
} }
@ -92,6 +95,7 @@ func (n *EmailPlugin) Notify(ctx context.Context, notification *protobufs.Notifi
if _, ok := n.ConfigByName[notification.Name]; !ok { if _, ok := n.ConfigByName[notification.Name]; !ok {
return nil, fmt.Errorf("invalid plugin config name %s", notification.Name) return nil, fmt.Errorf("invalid plugin config name %s", notification.Name)
} }
cfg := n.ConfigByName[notification.Name] cfg := n.ConfigByName[notification.Name]
logger := baseLogger.Named(cfg.Name) logger := baseLogger.Named(cfg.Name)
@ -117,6 +121,7 @@ func (n *EmailPlugin) Notify(ctx context.Context, notification *protobufs.Notifi
server.ConnectTimeout, err = time.ParseDuration(cfg.ConnectTimeout) server.ConnectTimeout, err = time.ParseDuration(cfg.ConnectTimeout)
if err != nil { if err != nil {
logger.Warn(fmt.Sprintf("invalid connect timeout '%s', using default '10s'", cfg.ConnectTimeout)) logger.Warn(fmt.Sprintf("invalid connect timeout '%s', using default '10s'", cfg.ConnectTimeout))
server.ConnectTimeout = 10 * time.Second server.ConnectTimeout = 10 * time.Second
} }
} }
@ -125,15 +130,18 @@ func (n *EmailPlugin) Notify(ctx context.Context, notification *protobufs.Notifi
server.SendTimeout, err = time.ParseDuration(cfg.SendTimeout) server.SendTimeout, err = time.ParseDuration(cfg.SendTimeout)
if err != nil { if err != nil {
logger.Warn(fmt.Sprintf("invalid send timeout '%s', using default '10s'", cfg.SendTimeout)) logger.Warn(fmt.Sprintf("invalid send timeout '%s', using default '10s'", cfg.SendTimeout))
server.SendTimeout = 10 * time.Second server.SendTimeout = 10 * time.Second
} }
} }
logger.Debug("making smtp connection") logger.Debug("making smtp connection")
smtpClient, err := server.Connect() smtpClient, err := server.Connect()
if err != nil { if err != nil {
return &protobufs.Empty{}, err return &protobufs.Empty{}, err
} }
logger.Debug("smtp connection done") logger.Debug("smtp connection done")
email := mail.NewMSG() email := mail.NewMSG()
@ -146,12 +154,14 @@ func (n *EmailPlugin) Notify(ctx context.Context, notification *protobufs.Notifi
if err != nil { if err != nil {
return &protobufs.Empty{}, err return &protobufs.Empty{}, err
} }
logger.Info(fmt.Sprintf("sent email to %v", cfg.ReceiverEmails)) logger.Info(fmt.Sprintf("sent email to %v", cfg.ReceiverEmails))
return &protobufs.Empty{}, nil return &protobufs.Empty{}, nil
} }
func main() { func main() {
var handshake = plugin.HandshakeConfig{ handshake := plugin.HandshakeConfig{
ProtocolVersion: 1, ProtocolVersion: 1,
MagicCookieKey: "CROWDSEC_PLUGIN_KEY", MagicCookieKey: "CROWDSEC_PLUGIN_KEY",
MagicCookieValue: os.Getenv("CROWDSEC_PLUGIN_KEY"), MagicCookieValue: os.Getenv("CROWDSEC_PLUGIN_KEY"),

View file

@ -12,10 +12,11 @@ import (
"os" "os"
"strings" "strings"
"github.com/crowdsecurity/crowdsec/pkg/protobufs"
"github.com/hashicorp/go-hclog" "github.com/hashicorp/go-hclog"
plugin "github.com/hashicorp/go-plugin" plugin "github.com/hashicorp/go-plugin"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v3"
"github.com/crowdsecurity/crowdsec/pkg/protobufs"
) )
type PluginConfig struct { type PluginConfig struct {
@ -90,18 +91,23 @@ func getTLSClient(c *PluginConfig) error {
tlsConfig.Certificates = []tls.Certificate{cert} tlsConfig.Certificates = []tls.Certificate{cert}
} }
transport := &http.Transport{ transport := &http.Transport{
TLSClientConfig: tlsConfig, TLSClientConfig: tlsConfig,
} }
if c.UnixSocket != "" { if c.UnixSocket != "" {
logger.Info(fmt.Sprintf("Using socket '%s'", c.UnixSocket)) logger.Info(fmt.Sprintf("Using socket '%s'", c.UnixSocket))
transport.DialContext = func(_ context.Context, _, _ string) (net.Conn, error) { transport.DialContext = func(_ context.Context, _, _ string) (net.Conn, error) {
return net.Dial("unix", strings.TrimSuffix(c.UnixSocket, "/")) return net.Dial("unix", strings.TrimSuffix(c.UnixSocket, "/"))
} }
} }
c.Client = &http.Client{ c.Client = &http.Client{
Transport: transport, Transport: transport,
} }
return nil return nil
} }
@ -109,6 +115,7 @@ func (s *HTTPPlugin) Notify(ctx context.Context, notification *protobufs.Notific
if _, ok := s.PluginConfigByName[notification.Name]; !ok { if _, ok := s.PluginConfigByName[notification.Name]; !ok {
return nil, fmt.Errorf("invalid plugin config name %s", notification.Name) return nil, fmt.Errorf("invalid plugin config name %s", notification.Name)
} }
cfg := s.PluginConfigByName[notification.Name] cfg := s.PluginConfigByName[notification.Name]
if cfg.LogLevel != nil && *cfg.LogLevel != "" { if cfg.LogLevel != nil && *cfg.LogLevel != "" {
@ -121,11 +128,14 @@ func (s *HTTPPlugin) Notify(ctx context.Context, notification *protobufs.Notific
if err != nil { if err != nil {
return nil, err return nil, err
} }
for headerName, headerValue := range cfg.Headers { for headerName, headerValue := range cfg.Headers {
logger.Debug(fmt.Sprintf("adding header %s: %s", headerName, headerValue)) logger.Debug(fmt.Sprintf("adding header %s: %s", headerName, headerValue))
request.Header.Add(headerName, headerValue) request.Header.Add(headerName, headerValue)
} }
logger.Debug(fmt.Sprintf("making HTTP %s call to %s with body %s", cfg.Method, cfg.URL, notification.Text)) logger.Debug(fmt.Sprintf("making HTTP %s call to %s with body %s", cfg.Method, cfg.URL, notification.Text))
resp, err := cfg.Client.Do(request.WithContext(ctx)) resp, err := cfg.Client.Do(request.WithContext(ctx))
if err != nil { if err != nil {
logger.Error(fmt.Sprintf("Failed to make HTTP request : %s", err)) logger.Error(fmt.Sprintf("Failed to make HTTP request : %s", err))
@ -135,7 +145,7 @@ func (s *HTTPPlugin) Notify(ctx context.Context, notification *protobufs.Notific
respData, err := io.ReadAll(resp.Body) respData, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read response body got error %s", err) return nil, fmt.Errorf("failed to read response body got error %w", err)
} }
logger.Debug(fmt.Sprintf("got response %s", string(respData))) logger.Debug(fmt.Sprintf("got response %s", string(respData)))
@ -143,6 +153,7 @@ func (s *HTTPPlugin) Notify(ctx context.Context, notification *protobufs.Notific
if resp.StatusCode < 200 || resp.StatusCode >= 300 { if resp.StatusCode < 200 || resp.StatusCode >= 300 {
logger.Warn(fmt.Sprintf("HTTP server returned non 200 status code: %d", resp.StatusCode)) logger.Warn(fmt.Sprintf("HTTP server returned non 200 status code: %d", resp.StatusCode))
logger.Debug(fmt.Sprintf("HTTP server returned body: %s", string(respData))) logger.Debug(fmt.Sprintf("HTTP server returned body: %s", string(respData)))
return &protobufs.Empty{}, nil return &protobufs.Empty{}, nil
} }
@ -151,21 +162,25 @@ func (s *HTTPPlugin) Notify(ctx context.Context, notification *protobufs.Notific
func (s *HTTPPlugin) Configure(ctx context.Context, config *protobufs.Config) (*protobufs.Empty, error) { func (s *HTTPPlugin) Configure(ctx context.Context, config *protobufs.Config) (*protobufs.Empty, error) {
d := PluginConfig{} d := PluginConfig{}
err := yaml.Unmarshal(config.Config, &d) err := yaml.Unmarshal(config.Config, &d)
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = getTLSClient(&d) err = getTLSClient(&d)
if err != nil { if err != nil {
return nil, err return nil, err
} }
s.PluginConfigByName[d.Name] = d s.PluginConfigByName[d.Name] = d
logger.Debug(fmt.Sprintf("HTTP plugin '%s' use URL '%s'", d.Name, d.URL)) logger.Debug(fmt.Sprintf("HTTP plugin '%s' use URL '%s'", d.Name, d.URL))
return &protobufs.Empty{}, err return &protobufs.Empty{}, err
} }
func main() { func main() {
var handshake = plugin.HandshakeConfig{ handshake := plugin.HandshakeConfig{
ProtocolVersion: 1, ProtocolVersion: 1,
MagicCookieKey: "CROWDSEC_PLUGIN_KEY", MagicCookieKey: "CROWDSEC_PLUGIN_KEY",
MagicCookieValue: os.Getenv("CROWDSEC_PLUGIN_KEY"), MagicCookieValue: os.Getenv("CROWDSEC_PLUGIN_KEY"),

View file

@ -5,12 +5,12 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/crowdsecurity/crowdsec/pkg/protobufs"
"github.com/hashicorp/go-hclog" "github.com/hashicorp/go-hclog"
plugin "github.com/hashicorp/go-plugin" plugin "github.com/hashicorp/go-plugin"
"github.com/slack-go/slack" "github.com/slack-go/slack"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v3"
"github.com/crowdsecurity/crowdsec/pkg/protobufs"
) )
type PluginConfig struct { type PluginConfig struct {
@ -33,13 +33,16 @@ func (n *Notify) Notify(ctx context.Context, notification *protobufs.Notificatio
if _, ok := n.ConfigByName[notification.Name]; !ok { if _, ok := n.ConfigByName[notification.Name]; !ok {
return nil, fmt.Errorf("invalid plugin config name %s", notification.Name) return nil, fmt.Errorf("invalid plugin config name %s", notification.Name)
} }
cfg := n.ConfigByName[notification.Name] cfg := n.ConfigByName[notification.Name]
if cfg.LogLevel != nil && *cfg.LogLevel != "" { if cfg.LogLevel != nil && *cfg.LogLevel != "" {
logger.SetLevel(hclog.LevelFromString(*cfg.LogLevel)) logger.SetLevel(hclog.LevelFromString(*cfg.LogLevel))
} }
logger.Info(fmt.Sprintf("found notify signal for %s config", notification.Name)) logger.Info(fmt.Sprintf("found notify signal for %s config", notification.Name))
logger.Debug(fmt.Sprintf("posting to %s webhook, message %s", cfg.Webhook, notification.Text)) logger.Debug(fmt.Sprintf("posting to %s webhook, message %s", cfg.Webhook, notification.Text))
err := slack.PostWebhookContext(ctx, n.ConfigByName[notification.Name].Webhook, &slack.WebhookMessage{ err := slack.PostWebhookContext(ctx, n.ConfigByName[notification.Name].Webhook, &slack.WebhookMessage{
Text: notification.Text, Text: notification.Text,
}) })
@ -52,16 +55,19 @@ func (n *Notify) Notify(ctx context.Context, notification *protobufs.Notificatio
func (n *Notify) Configure(ctx context.Context, config *protobufs.Config) (*protobufs.Empty, error) { func (n *Notify) Configure(ctx context.Context, config *protobufs.Config) (*protobufs.Empty, error) {
d := PluginConfig{} d := PluginConfig{}
if err := yaml.Unmarshal(config.Config, &d); err != nil { if err := yaml.Unmarshal(config.Config, &d); err != nil {
return nil, err return nil, err
} }
n.ConfigByName[d.Name] = d n.ConfigByName[d.Name] = d
logger.Debug(fmt.Sprintf("Slack plugin '%s' use URL '%s'", d.Name, d.Webhook)) logger.Debug(fmt.Sprintf("Slack plugin '%s' use URL '%s'", d.Name, d.Webhook))
return &protobufs.Empty{}, nil return &protobufs.Empty{}, nil
} }
func main() { func main() {
var handshake = plugin.HandshakeConfig{ handshake := plugin.HandshakeConfig{
ProtocolVersion: 1, ProtocolVersion: 1,
MagicCookieKey: "CROWDSEC_PLUGIN_KEY", MagicCookieKey: "CROWDSEC_PLUGIN_KEY",
MagicCookieValue: os.Getenv("CROWDSEC_PLUGIN_KEY"), MagicCookieValue: os.Getenv("CROWDSEC_PLUGIN_KEY"),

View file

@ -10,11 +10,11 @@ import (
"os" "os"
"strings" "strings"
"github.com/crowdsecurity/crowdsec/pkg/protobufs"
"github.com/hashicorp/go-hclog" "github.com/hashicorp/go-hclog"
plugin "github.com/hashicorp/go-plugin" plugin "github.com/hashicorp/go-plugin"
"gopkg.in/yaml.v3"
"gopkg.in/yaml.v2" "github.com/crowdsecurity/crowdsec/pkg/protobufs"
) )
var logger hclog.Logger = hclog.New(&hclog.LoggerOptions{ var logger hclog.Logger = hclog.New(&hclog.LoggerOptions{
@ -44,6 +44,7 @@ func (s *Splunk) Notify(ctx context.Context, notification *protobufs.Notificatio
if _, ok := s.PluginConfigByName[notification.Name]; !ok { if _, ok := s.PluginConfigByName[notification.Name]; !ok {
return &protobufs.Empty{}, fmt.Errorf("splunk invalid config name %s", notification.Name) return &protobufs.Empty{}, fmt.Errorf("splunk invalid config name %s", notification.Name)
} }
cfg := s.PluginConfigByName[notification.Name] cfg := s.PluginConfigByName[notification.Name]
if cfg.LogLevel != nil && *cfg.LogLevel != "" { if cfg.LogLevel != nil && *cfg.LogLevel != "" {
@ -53,6 +54,7 @@ func (s *Splunk) Notify(ctx context.Context, notification *protobufs.Notificatio
logger.Info(fmt.Sprintf("received notify signal for %s config", notification.Name)) logger.Info(fmt.Sprintf("received notify signal for %s config", notification.Name))
p := Payload{Event: notification.Text} p := Payload{Event: notification.Text}
data, err := json.Marshal(p) data, err := json.Marshal(p)
if err != nil { if err != nil {
return &protobufs.Empty{}, err return &protobufs.Empty{}, err
@ -65,6 +67,7 @@ func (s *Splunk) Notify(ctx context.Context, notification *protobufs.Notificatio
req.Header.Add("Authorization", fmt.Sprintf("Splunk %s", cfg.Token)) req.Header.Add("Authorization", fmt.Sprintf("Splunk %s", cfg.Token))
logger.Debug(fmt.Sprintf("posting event %s to %s", string(data), req.URL)) logger.Debug(fmt.Sprintf("posting event %s to %s", string(data), req.URL))
resp, err := s.Client.Do(req.WithContext(ctx)) resp, err := s.Client.Do(req.WithContext(ctx))
if err != nil { if err != nil {
return &protobufs.Empty{}, err return &protobufs.Empty{}, err
@ -73,15 +76,19 @@ func (s *Splunk) Notify(ctx context.Context, notification *protobufs.Notificatio
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
content, err := io.ReadAll(resp.Body) content, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return &protobufs.Empty{}, fmt.Errorf("got non 200 response and failed to read error %s", err) return &protobufs.Empty{}, fmt.Errorf("got non 200 response and failed to read error %w", err)
} }
return &protobufs.Empty{}, fmt.Errorf("got non 200 response %s", string(content)) return &protobufs.Empty{}, fmt.Errorf("got non 200 response %s", string(content))
} }
respData, err := io.ReadAll(resp.Body) respData, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return &protobufs.Empty{}, fmt.Errorf("failed to read response body got error %s", err) return &protobufs.Empty{}, fmt.Errorf("failed to read response body got error %w", err)
} }
logger.Debug(fmt.Sprintf("got response %s", string(respData))) logger.Debug(fmt.Sprintf("got response %s", string(respData)))
return &protobufs.Empty{}, nil return &protobufs.Empty{}, nil
} }
@ -90,11 +97,12 @@ func (s *Splunk) Configure(ctx context.Context, config *protobufs.Config) (*prot
err := yaml.Unmarshal(config.Config, &d) err := yaml.Unmarshal(config.Config, &d)
s.PluginConfigByName[d.Name] = d s.PluginConfigByName[d.Name] = d
logger.Debug(fmt.Sprintf("Splunk plugin '%s' use URL '%s'", d.Name, d.URL)) logger.Debug(fmt.Sprintf("Splunk plugin '%s' use URL '%s'", d.Name, d.URL))
return &protobufs.Empty{}, err return &protobufs.Empty{}, err
} }
func main() { func main() {
var handshake = plugin.HandshakeConfig{ handshake := plugin.HandshakeConfig{
ProtocolVersion: 1, ProtocolVersion: 1,
MagicCookieKey: "CROWDSEC_PLUGIN_KEY", MagicCookieKey: "CROWDSEC_PLUGIN_KEY",
MagicCookieValue: os.Getenv("CROWDSEC_PLUGIN_KEY"), MagicCookieValue: os.Getenv("CROWDSEC_PLUGIN_KEY"),