diff --git a/veinmind-runner/cmd/cli.go b/veinmind-runner/cmd/cli.go index d03c820c..35db0fa4 100644 --- a/veinmind-runner/cmd/cli.go +++ b/veinmind-runner/cmd/cli.go @@ -4,7 +4,12 @@ import ( "context" "encoding/json" "errors" - "github.com/chaitin/libveinmind/go" + "os" + "path" + "path/filepath" + "strings" + + api "github.com/chaitin/libveinmind/go" "github.com/chaitin/libveinmind/go/cmd" "github.com/chaitin/libveinmind/go/containerd" "github.com/chaitin/libveinmind/go/docker" @@ -19,10 +24,6 @@ import ( "github.com/chaitin/veinmind-tools/veinmind-runner/pkg/reporter" "github.com/distribution/distribution/reference" "github.com/spf13/cobra" - "os" - "path" - "path/filepath" - "strings" ) const ( @@ -160,6 +161,35 @@ var authCmd = &cmd.Command{ return server.Run() }, } + +var webhookCmd = &cmd.Command{ + Use: "webhook", + Short: "webhook for harbor", + RunE: func(cmd *cobra.Command, args []string) error { + path, err := cmd.Flags().GetString("config") + if err != nil { + return err + } + + config, err := authz.NewHarborWebhookConfig(path) + if err != nil { + return err + } + + options := []authz.ServerOption{ + authz.WithHarborPolicy(config.Policies...), + authz.WithAuthLog(config.Log.AuthZLogPath), + authz.WithPluginLog(config.Log.PluginLogPath), + authz.WithPort(config.Port.Port), + authz.WithPassword(config.Password.Password), + authz.WithAuthInfo(config.DockerAuth), + authz.WithMailServer(config.MailConf), + } + + server := authz.NewHarborWebhook(options...) + return server.Run() + }, +} var listCmd = &cmd.Command{ Use: "list", Short: "list relevant information", @@ -226,7 +256,7 @@ var scanRegistryCmd = &cmd.Command{ if config == "" { c, err = commonRuntime.NewDockerClient() } else { - c, err = commonRuntime.NewDockerClient(commonRuntime.WithAuth(config)) + c, err = commonRuntime.NewDockerClient(commonRuntime.WithAuthFromPath(config)) } if err != nil { return err @@ -467,6 +497,8 @@ func init() { rootCmd.AddCommand(scanRegistryCmd) rootCmd.AddCommand(authCmd) authCmd.Flags().StringP("config", "c", "", "authz config path") + rootCmd.AddCommand(webhookCmd) + webhookCmd.Flags().StringP("config", "c", "", "webhook config path") rootCmd.AddCommand(listCmd) rootCmd.PersistentFlags().IntP("exit-code", "e", 0, "exit-code when veinmind-runner find security issues") listCmd.AddCommand(listPluginCmd) diff --git a/veinmind-runner/config.toml b/veinmind-runner/config.toml new file mode 100644 index 00000000..4b93f1c5 --- /dev/null +++ b/veinmind-runner/config.toml @@ -0,0 +1,31 @@ +[log] +plugin_log_path = "plugin.log" +auth_log_path = "auth.log" + +[port] +port = "8081" + +[webhook_password] +password = "asdqwe123" + +[[policies]] +action = "PUSH_ARTIFACT" +enabled_plugins = ["veinmind-weakpass"] +plugin_params = ["veinmind-weakpass:scan.serviceName=ssh"] +risk_level_filter = ["High"] +block = true +alert = true +send_mail = true + +[docker_auth] +registry = "10.9.33.98" +username = "admin" +password = "asdqwe123" + +[mail_conf] +host = "smtp.qq.com" +port = 465 +username = "369212851@qq.com" +password = "test" +send_to = ["220205328@seu.edu.cn"] + diff --git a/veinmind-runner/go.mod b/veinmind-runner/go.mod index b57fdccd..af5573a1 100644 --- a/veinmind-runner/go.mod +++ b/veinmind-runner/go.mod @@ -5,13 +5,15 @@ go 1.16 require ( github.com/BurntSushi/toml v0.4.1 github.com/chaitin/libveinmind v1.1.1 - github.com/chaitin/veinmind-common-go v1.0.5 + github.com/chaitin/veinmind-common-go v1.1.0 github.com/distribution/distribution v2.8.1+incompatible github.com/docker/docker v20.10.17+incompatible github.com/gin-gonic/gin v1.8.1 github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.4.0 + gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect + gopkg.in/mail.v2 v2.3.1 gotest.tools/v3 v3.1.0 // indirect ) diff --git a/veinmind-runner/go.sum b/veinmind-runner/go.sum index fa4afd0c..330b1bf1 100644 --- a/veinmind-runner/go.sum +++ b/veinmind-runner/go.sum @@ -203,8 +203,8 @@ github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cb github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chaitin/libveinmind v1.1.1 h1:DoMJXAjw3xzOHcrAiBBQpbqudsumw5pGfRQko6f6USc= github.com/chaitin/libveinmind v1.1.1/go.mod h1:bUUjhkyZyZ9sTetpm5rOfj5TU3hr5moE3VQM+IgHrbw= -github.com/chaitin/veinmind-common-go v1.0.5 h1:OA8c9IDMPGXUBQksiFck6tx5eEtfDz9LB1+FcmaSMDY= -github.com/chaitin/veinmind-common-go v1.0.5/go.mod h1:pmtVj6duS6B3spaBPhMMXOZOzDyyABtcEIQEF4KpZaI= +github.com/chaitin/veinmind-common-go v1.1.0 h1:YKE+KBVyP48IBKFf4dH5Ve8ayHz8rQ8S07iFPF8z60U= +github.com/chaitin/veinmind-common-go v1.1.0/go.mod h1:Ap6KTM2qqKv+8tLeb38pX9DFWt/P8/1gmCO2b9tjNZo= github.com/charithe/durationcheck v0.0.9/go.mod h1:SSbRIBVfMjCi/kEB6K65XEA83D6prSM8ap1UCpNKtgg= github.com/chavacava/garif v0.0.0-20210405164556-e8a0a408d6af/go.mod h1:Qjyv4H3//PWVzTeCezG2b9IRn6myJxJSr4TD/xo6ojU= github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= @@ -2047,6 +2047,8 @@ google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscL google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/cenkalti/backoff.v2 v2.2.1 h1:eJ9UAg01/HIHG987TwxvnzK2MgxXq97YY6rYDpY9aII= gopkg.in/cenkalti/backoff.v2 v2.2.1/go.mod h1:S0QdOvT2AlerfSBkp0O+dk+bbIMaNbEmVk876gPCthU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -2069,6 +2071,8 @@ gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI= gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk= +gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/rethinkdb/rethinkdb-go.v6 v6.2.1 h1:d4KQkxAaAiRY2h5Zqis161Pv91A37uZyJOx73duwUwM= diff --git a/veinmind-runner/pkg/authz/action/harbor_webhook.go b/veinmind-runner/pkg/authz/action/harbor_webhook.go new file mode 100644 index 00000000..990ee257 --- /dev/null +++ b/veinmind-runner/pkg/authz/action/harbor_webhook.go @@ -0,0 +1,39 @@ +package action + +import ( + "errors" + + "github.com/chaitin/libveinmind/go/plugin/log" + "github.com/chaitin/veinmind-common-go/pkg/auth" + "github.com/chaitin/veinmind-common-go/runtime" + "github.com/gin-gonic/gin" +) + +// get secrect from Authorization field and check +func CheckPassword(c *gin.Context, password string) error { + if password == "" { + return nil + } + if c.Request.Header.Get("Authorization") == password { + return nil + } + return errors.New("error passowrd") +} + +// download relevant images +func GetImagesFromHarbor(authentity auth.Auth, imageNames []string) error { + authConfig := auth.AuthConfig{ + Auths: []auth.Auth{authentity}} + dockerclient, err := runtime.NewDockerClient(runtime.WithAuth(authConfig)) + if err != nil { + return err + } + for _, img := range imageNames { + _, err := dockerclient.Pull(img) + if err != nil { + log.Error(err) + continue + } + } + return nil +} diff --git a/veinmind-runner/pkg/authz/authz_config.go b/veinmind-runner/pkg/authz/authz_config.go index 4a78f8fa..10ce029a 100644 --- a/veinmind-runner/pkg/authz/authz_config.go +++ b/veinmind-runner/pkg/authz/authz_config.go @@ -12,6 +12,7 @@ const ( defaultPluginPath = "plugin.log" defaultAuthLogPath = "auth.log" defaultSockListenAddr = "/run/docker/plugins/veinmind-broker.sock" + defaultPort = "8080" ) type Policy struct { @@ -23,6 +24,11 @@ type Policy struct { Alert bool `toml:"alert"` } +type HarborPolicy struct { + Policy + SendMail bool `toml:"send_mail"` +} + type Log struct { AuthZLogPath string `toml:"auth_log_path"` PluginLogPath string `toml:"plugin_log_path"` @@ -31,13 +37,33 @@ type Log struct { type Listener struct { ListenAddr string `toml:"listener_addr"` } - +type Port struct { + Port string `toml:"port"` +} +type Password struct { + Password string `toml:"webhook_password"` +} +type MailConf struct { + Host string `toml:"host"` + Port int `toml:"port"` + Name string `toml:"username"` + Password string `toml:"password"` + SendTo []string `toml:"send_to"` +} type DockerPluginConfig struct { Log Log `toml:"log"` Listener Listener `toml:"listener"` DockerAuth auth.Auth `toml:"docker_auth"` Policies []Policy `toml:"policies"` } +type HarborWebhookConfig struct { + Log Log `toml:"log"` + Port Port `toml:"port"` + DockerAuth auth.Auth `toml:"docker_auth"` + Policies []HarborPolicy `toml:"policies"` + Password Password `toml:"password"` + MailConf MailConf `toml:"mail_conf"` +} func NewDockerPluginConfig(paths ...string) (*DockerPluginConfig, error) { if len(paths) < 1 { @@ -57,3 +83,21 @@ func NewDockerPluginConfig(paths ...string) (*DockerPluginConfig, error) { return result, nil } +func NewHarborWebhookConfig(paths ...string) (*HarborWebhookConfig, error) { + if len(paths) < 1 { + return nil, errors.New("config path can't be empty") + } + + path := defaultConfigPath + if paths[0] != "" { + path = paths[0] + } + + result := &HarborWebhookConfig{} + _, err := toml.DecodeFile(path, result) + if err != nil { + return nil, err + } + + return result, nil +} diff --git a/veinmind-runner/pkg/authz/authz_report.go b/veinmind-runner/pkg/authz/authz_report.go index f9b2415c..f1ed2edf 100644 --- a/veinmind-runner/pkg/authz/authz_report.go +++ b/veinmind-runner/pkg/authz/authz_report.go @@ -4,6 +4,8 @@ import ( "fmt" "io" + gomail "gopkg.in/mail.v2" + "github.com/chaitin/libveinmind/go/plugin/log" "github.com/chaitin/veinmind-common-go/service/report" "github.com/chaitin/veinmind-tools/veinmind-runner/pkg/reporter" @@ -25,30 +27,62 @@ func toLevelStr(level report.Level) string { } func handleReportEvents(eventListCh <-chan []reporter.ReportEvent, policy Policy, - pluginLog io.Writer) { + pluginLog io.Writer) (filter bool, results []reporter.ReportEvent) { riskLevelFilter := make(map[string]struct{}) for _, level := range policy.RiskLevelFilter { riskLevelFilter[level] = struct{}{} } select { case events := <-eventListCh: - filter := true for _, event := range events { if _, ok := riskLevelFilter[toLevelStr(event.Level)]; !ok { continue } - - filter = false + filter = true + results = append(results, event) + } + if err := reporter.WriteEvents2Log(events, pluginLog); err != nil { + log.Warn(err) + } + } + return filter, results +} +func handleDockerPluginReportEvents(eventListCh <-chan []reporter.ReportEvent, bpolicy Policy, + pluginLog io.Writer) { + filter, _ := handleReportEvents(eventListCh, bpolicy, pluginLog) + if filter { + if bpolicy.Alert { + log.Warn(fmt.Sprintf("Action %s has risks!", bpolicy.Action)) } + } +} - if !filter { - if policy.Alert { - log.Warn(fmt.Sprintf("Action %s has risks!", policy.Action)) +func handleHarborWebhookReportEvents(eventListCh <-chan []reporter.ReportEvent, hpolicy HarborPolicy, + pluginLog io.Writer, mailconf MailConf) { + filter, events := handleReportEvents(eventListCh, hpolicy.Policy, pluginLog) + if filter { + if hpolicy.Alert { + log.Warn(fmt.Sprintf("Action %s has risks!", hpolicy.Action)) + } + if hpolicy.SendMail { + err := sendReport2Mail(events, mailconf) + if err != nil { + log.Error(err) } } + } - if err := reporter.WriteEvents2Log(events, pluginLog); err != nil { - log.Warn(err) - } +} + +func sendReport2Mail(events []reporter.ReportEvent, mailconf MailConf) error { + d := gomail.NewDialer(mailconf.Host, mailconf.Port, mailconf.Name, mailconf.Password) + m := gomail.NewMessage() + m.SetHeader("From", mailconf.Name) + m.SetHeader("To", mailconf.SendTo...) + m.SetHeader("Subject", "Harbor webhook Report") + m.SetBody("text/plain", fmt.Sprintf("%#v", events)) + if err := d.DialAndSend(m); err != nil { + return err } + return nil } diff --git a/veinmind-runner/pkg/authz/authz_server.go b/veinmind-runner/pkg/authz/authz_server.go index fbe0d241..e315a696 100644 --- a/veinmind-runner/pkg/authz/authz_server.go +++ b/veinmind-runner/pkg/authz/authz_server.go @@ -9,6 +9,7 @@ import ( "sync" "syscall" + "github.com/chaitin/veinmind-common-go/pkg/auth" "github.com/chaitin/veinmind-tools/veinmind-runner/pkg/reporter" ) @@ -19,6 +20,35 @@ type serverOption struct { pluginLog io.WriteCloser policies sync.Map listener net.Listener + port string + password string + authInfo auth.Auth + mailConf MailConf +} + +func WithMailServer(mailconf MailConf) ServerOption { + return func(option *serverOption) error { + option.mailConf = mailconf + return nil + } +} +func WithAuthInfo(auth auth.Auth) ServerOption { + return func(option *serverOption) error { + option.authInfo = auth + return nil + } +} +func WithPassword(password string) ServerOption { + return func(option *serverOption) error { + option.password = password + return nil + } +} +func WithPort(port string) ServerOption { + return func(option *serverOption) error { + option.port = port + return nil + } } func WithPolicy(policies ...Policy) ServerOption { @@ -31,6 +61,15 @@ func WithPolicy(policies ...Policy) ServerOption { } } +func WithHarborPolicy(policies ...HarborPolicy) ServerOption { + return func(option *serverOption) error { + for _, policy := range policies { + option.policies.Store(policy.Action, policy) + } + return nil + } +} + func WithAuthLog(path string) ServerOption { return func(option *serverOption) error { _, err := os.Stat(path) @@ -138,6 +177,8 @@ func (s *defaultServer) init() error { switch srv := s.server.(type) { case *dockerPluginServer: result = srv.option + case *harborWebhookServer: + result = srv.option default: return errors.New("not support the server") } @@ -152,9 +193,17 @@ func (s *defaultServer) init() error { if result.pluginLog == nil { defaultOptions = append(defaultOptions, WithPluginLog(defaultPluginPath)) } - if result.listener == nil { - defaultOptions = append(defaultOptions, WithListenerUnix(defaultSockListenAddr)) + switch s.server.(type) { + case *dockerPluginServer: + if result.listener == nil { + defaultOptions = append(defaultOptions, WithListenerUnix(defaultSockListenAddr)) + } + case *harborWebhookServer: + if result.port == "" { + defaultOptions = append(defaultOptions, WithPort(defaultPort)) + } } + if err := WithServerOptions(defaultOptions...)(result); err != nil { return err } diff --git a/veinmind-runner/pkg/authz/harbor_webhook.go b/veinmind-runner/pkg/authz/harbor_webhook.go new file mode 100644 index 00000000..79b1fdcf --- /dev/null +++ b/veinmind-runner/pkg/authz/harbor_webhook.go @@ -0,0 +1,127 @@ +package authz + +import ( + "context" + "io" + "os" + + "github.com/chaitin/libveinmind/go/plugin/log" + "github.com/chaitin/veinmind-tools/veinmind-runner/pkg/authz/action" + "github.com/chaitin/veinmind-tools/veinmind-runner/pkg/authz/route" + "github.com/chaitin/veinmind-tools/veinmind-runner/pkg/reporter" + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" +) + +type harborWebhookServer struct { + option *serverOption +} + +func NewHarborWebhook(opts ...ServerOption) Server { + s := &harborWebhookServer{ + option: new(serverOption), + } + return newDefaultServer(s, opts...) +} + +func (s *harborWebhookServer) init() error { + return nil +} + +func (s *harborWebhookServer) start() error { + multiWriter := io.MultiWriter(s.option.authLog, os.Stdout) + + logger := logrus.New() + logger.Out = multiWriter + + log.SetDefaultLogger(log.NewLogrus(logger)) + gin.DefaultWriter = multiWriter + + engine := s.registerRouter() + + go func() { + err := engine.Run(":" + s.option.port) + if err != nil { + log.Error(err) + } + }() + return nil +} + +func (s *harborWebhookServer) wait() error { + return nil +} + +func (s *harborWebhookServer) close() { + err := s.option.authLog.Close() + if err != nil { + log.Error(err) + } + + err = s.option.pluginLog.Close() + if err != nil { + log.Error(err) + } +} + +func (s *harborWebhookServer) registerRouter() *gin.Engine { + engine := gin.Default() + engine.POST("/api", func(c *gin.Context) { + if err := action.CheckPassword(c, s.option.password); err != nil { + log.Error(err) + return + } + postData, err := route.ParseHarborwebhookPostdata(c) + if err != nil { + log.Error(err) + return + } + imageNames, err := route.GetImageNames(postData) + if err != nil { + log.Error(err) + return + } + err = action.GetImagesFromHarbor(s.option.authInfo, imageNames) + if err != nil { + log.Error(err) + return + } + val, ok := s.option.policies.Load(postData.Type) + if !ok { + log.Error(err) + return + } + hpolicy := val.(HarborPolicy) + var eventListCh chan []reporter.ReportEvent + switch postData.Type { + case "PUSH_ARTIFACT": + eventListCh, err = HandleWebhookImagePush(context.Background(), hpolicy.Policy, postData) + // TODO: other type's process + default: + return + } + if err != nil { + log.Error(err) + return + } + go func() { + handleHarborWebhookReportEvents(eventListCh, hpolicy, + s.option.pluginLog, s.option.mailConf) + }() + + }) + // //get post data content from this url + // engine.POST("/", func(c *gin.Context) { + // var body map[string]interface{} + // data, _ := ioutil.ReadAll(c.Request.Body) + // if err := json.Unmarshal(data, &body); err != nil { + // fmt.Println(err) + // } + // fmt.Println("body data => ", string(data)) + // for k, v := range c.Request.Header { + // fmt.Println(k, v) + // } + // c.JSON(http.StatusOK, struct{}{}) + // }) + return engine +} diff --git a/veinmind-runner/pkg/authz/harbor_webhook_auth.go b/veinmind-runner/pkg/authz/harbor_webhook_auth.go new file mode 100644 index 00000000..26f243e3 --- /dev/null +++ b/veinmind-runner/pkg/authz/harbor_webhook_auth.go @@ -0,0 +1,33 @@ +package authz + +import ( + "context" + + "github.com/chaitin/libveinmind/go/plugin/log" + "github.com/chaitin/veinmind-tools/veinmind-runner/pkg/authz/route" + "github.com/chaitin/veinmind-tools/veinmind-runner/pkg/reporter" + "github.com/chaitin/veinmind-tools/veinmind-runner/pkg/scan" +) + +func HandleWebhookImagePush(ctx context.Context, policy Policy, postData route.PullandPushData) (chan []reporter.ReportEvent, error) { + eventListCh := make(chan []reporter.ReportEvent, 1) + if postData.Operator == "webhook" || postData.Type != "PUSH_ARTIFACT" { + return nil, nil + } + imageNames, err := route.GetImageNames(postData) + if err != nil { + return nil, err + } + var result []reporter.ReportEvent + for _, img := range imageNames { + report, err := scan.ScanLocalImage(ctx, img, + policy.EnabledPlugins, policy.PluginParams) + if err != nil { + log.Error(err) + continue + } + result = append(result, report...) + } + eventListCh <- result + return eventListCh, nil +} diff --git a/veinmind-runner/pkg/authz/route/harbor_webhook_image.go b/veinmind-runner/pkg/authz/route/harbor_webhook_image.go new file mode 100644 index 00000000..cbeffd13 --- /dev/null +++ b/veinmind-runner/pkg/authz/route/harbor_webhook_image.go @@ -0,0 +1,52 @@ +package route + +import ( + "encoding/json" + "errors" + "io/ioutil" + + "github.com/gin-gonic/gin" +) + +// only for push and pull api +// TODO: parse scan/helm/etc. api post data +type PullandPushData struct { + Type string `json:"type"` + OccurAt int `json:"occur_at"` + Operator string `json:"operator"` + EventData struct { + Resources []struct { + Digest string `json:"digest"` + Tag string `json:"tag"` + ResourceURL string `json:"resource_url"` + } `json:"resources"` + Repository struct { + DateCreated int `json:"date_created"` + Name string `json:"name"` + Namespace string `json:"namespace"` + RepoFullName string `json:"repo_full_name"` + RepoType string `json:"repo_type"` + } `json:"repository"` + } `json:"event_data"` +} + +func ParseHarborwebhookPostdata(c *gin.Context) (PullandPushData, error) { + postData := &PullandPushData{} + data, _ := ioutil.ReadAll(c.Request.Body) + if err := json.Unmarshal(data, &postData); err != nil { + return PullandPushData{}, err + } + return *postData, nil +} + +func GetImageNames(data PullandPushData) ([]string, error) { + resources := data.EventData.Resources + if len(resources) < 1 { + return []string{}, errors.New("no image choosed") + } + var imagenames []string + for _, resource := range resources { + imagenames = append(imagenames, resource.ResourceURL) + } + return imagenames, nil +}