diff --git a/cve-vulner-manager/Dockerfile b/cve-vulner-manager/Dockerfile index ba1d7bcd3ed6f3e63e1119b79ba585cf06aa49ba..c7614074f920238f186588dc011c1df77bc0b489 100644 --- a/cve-vulner-manager/Dockerfile +++ b/cve-vulner-manager/Dockerfile @@ -15,6 +15,7 @@ RUN dnf -y update && \ useradd -u 1000 -g manager -s /bin/bash -m manager COPY --chown=manager ./conf/product_app.conf /opt/app/conf/app.conf +COPY --chown=manager ./sh/epoch.sh /opt/app/epoch.sh COPY --chown=manager --from=BUILDER /go/src/gitee.com/openeuler/cve-manager/cve-manager /opt/app/cve-manager USER manager diff --git a/cve-vulner-manager/cve-ddd/app/bulletin.go b/cve-vulner-manager/cve-ddd/app/bulletin.go index d9bcc106bfc58f0e6d50dd405ca2190aac556a21..569224feeaf3009bca66eda5030f126b08936994 100644 --- a/cve-vulner-manager/cve-ddd/app/bulletin.go +++ b/cve-vulner-manager/cve-ddd/app/bulletin.go @@ -21,6 +21,7 @@ import ( "cvevulner/cve-ddd/domain/obs" "cvevulner/cve-ddd/domain/repository" "cvevulner/cve-ddd/domain/testresult" + "cvevulner/cve-ddd/domain/updateinfo" ) const ( @@ -42,6 +43,7 @@ func NewBulletinService( t testresult.Result, bd backend.Backend, l *logrus.Entry, + u updateinfo.UpdateInfo, ) *bulletinService { service := &bulletinService{ obs: o, @@ -50,6 +52,7 @@ func NewBulletinService( bulletin: b, testResult: t, backend: bd, + updateinfo: u, log: l, giteeToken: beego.AppConfig.String("gitee::git_token"), } @@ -66,6 +69,7 @@ type bulletinService struct { bulletin bulletin.Bulletin testResult testresult.Result backend backend.Backend + updateinfo updateinfo.UpdateInfo releaseDate sync.Map giteeToken string @@ -143,6 +147,8 @@ func (b *bulletinService) GenerateBulletins(cveNum []string, date string) (strin } updateFixedFiles = append(updateFixedFiles, v.PathAppendToIndexFile()) + + b.uploadUpdateInfoFile(&v) } b.uploadIndexAndFixed(uploadDir, indexContent, updateFixedFiles) @@ -150,6 +156,33 @@ func (b *bulletinService) GenerateBulletins(cveNum []string, date string) (strin return uploadDir, nil } +func (b *bulletinService) uploadUpdateInfoFile(bulletin *domain.SecurityBulletin) { + for _, branch := range bulletin.AffectedVersion { + filePath := domain.UpdateinfoRootDir + branch + "/updateinfo.xml" + downloadBys, err := b.obs.Download(filePath) + if err != nil { + b.log.Error(err) + continue + } + + data, err := b.updateinfo.UploadUpdateInfoXml(domain.UpdateParam{ + Sb: bulletin, + Branch: branch, + DownloadBys: downloadBys, + }) + + if err != nil { + b.log.Error(err) + continue + } + + if err = b.obs.Upload(filePath, data); err != nil { + b.log.Error(err) + continue + } + } +} + func (b *bulletinService) uploadIndexAndFixed(uploadDir, indexContent string, updateFixedFiles []string) { updateFixedContent := strings.TrimSpace(strings.Join(updateFixedFiles, EOF)) newIndexContent := strings.TrimSpace(indexContent) + EOF + updateFixedContent diff --git a/cve-vulner-manager/cve-ddd/domain/updateinfo.go b/cve-vulner-manager/cve-ddd/domain/updateinfo.go new file mode 100644 index 0000000000000000000000000000000000000000..bf5dfea84fe7f3da161c0c6562f9bac2c808f214 --- /dev/null +++ b/cve-vulner-manager/cve-ddd/domain/updateinfo.go @@ -0,0 +1,31 @@ +package domain + +import ( + "regexp" +) + +const ( + UpdateinfoRootDir = "earlyupdateinfo/" + NoticeTypeCVE = "cve" + NoticeTypeBug = "bug" + CveUrlPrefix = "https://nvd.nist.gov/vuln/detail/" + PkgUrl = "https://repo.openeuler.org/%s/update/%s/Packages/%s" + ScriptPath = "/opt/app/epoch.sh" +) + +var ( + Severity = map[string]string{ + "critical": "Critical", + "high": "Important", + "medium": "Moderate", + "low": "Low", + } + + Num = regexp.MustCompile(`\d+`) +) + +type UpdateParam struct { + Sb *SecurityBulletin + Branch string + DownloadBys []byte +} diff --git a/cve-vulner-manager/cve-ddd/domain/updateinfo/updateinfo.go b/cve-vulner-manager/cve-ddd/domain/updateinfo/updateinfo.go index 27c1ff756bd9b91e1a3b84e61218a546065f94aa..bf5ed2692c48d68b911eb9e2a8a39781efd3c8c4 100644 --- a/cve-vulner-manager/cve-ddd/domain/updateinfo/updateinfo.go +++ b/cve-vulner-manager/cve-ddd/domain/updateinfo/updateinfo.go @@ -1,8 +1,11 @@ package updateinfo -import "cvevulner/cve-ddd/domain" +import ( + "cvevulner/cve-ddd/domain" +) type UpdateInfo interface { Generate(cves domain.CvesByVersion) ([]byte, error) GenerateCollectExcel(map[string]domain.CollectedDataSlice) ([]byte, error) + UploadUpdateInfoXml(up domain.UpdateParam) (data []byte, err error) } diff --git a/cve-vulner-manager/cve-ddd/infrastructure/updateinfoimpl/generate_updateinfoxml.go b/cve-vulner-manager/cve-ddd/infrastructure/updateinfoimpl/generate_updateinfoxml.go new file mode 100644 index 0000000000000000000000000000000000000000..10c55765c5ad912eb60edbbd104b371cd30e9c0d --- /dev/null +++ b/cve-vulner-manager/cve-ddd/infrastructure/updateinfoimpl/generate_updateinfoxml.go @@ -0,0 +1,223 @@ +package updateinfoimpl + +import ( + "bytes" + "encoding/xml" + "fmt" + "path/filepath" + "sort" + "strconv" + "strings" + "time" + + libutils "github.com/opensourceways/server-common-lib/utils" + + "cvevulner/cve-ddd/domain" + "cvevulner/cve-ddd/domain/dp" + "cvevulner/taskhandler" + "cvevulner/util" +) + +func (impl updateInfoImpl) UploadUpdateInfoXml(param domain.UpdateParam) (data []byte, err error) { + var u Updates + + err = xml.Unmarshal(param.DownloadBys, &u) + if err != nil { + return nil, err + } + + up := impl.updateXml(param.Sb, param.Branch) + + i := impl.numberIndex(&u, param.Sb.Identification) + if i == -1 { + u.Updatex = append(u.Updatex, up) + } else { + if up.Description == "" { + up.Description = u.Updatex[i].Description + } + if up.Title == "" { + up.Title = u.Updatex[i].Title + } + u.Updatex[i] = up + } + + sort.Slice(u.Updatex, func(i, j int) bool { + return u.Updatex[i].Id < u.Updatex[j].Id + }) + + uploadBys, err := xml.MarshalIndent(u, "", " ") + if err != nil { + return nil, err + } + + headerBytes := []byte(xml.Header) + headerBytes = append(headerBytes, uploadBys...) + + return headerBytes, nil +} + +func (impl updateInfoImpl) updateXml(sb *domain.SecurityBulletin, branch string) Update { + var cveNums []string + var description string + var highestLevelIndex int + for _, cve := range sb.Cves { + cveNums = append(cveNums, cve.CveNum) + + subDescription := strings.ReplaceAll(cve.Description, "\n\n", "\r\n\r\n") + subDescription = taskhandler.XmlSpecCharHand(subDescription) + dSplit := strings.Split(subDescription, "Security Fix(es):") + if len(dSplit) > 1 { + if !strings.Contains(description, dSplit[0]) { + description = dSplit[0] + "Security Fix(es):" + description + } + if !strings.Contains(description, dSplit[1]) { + description += dSplit[1] + } + } + + // Choose the highest security level in cves, as security level in bulletin + for k, v := range dp.SequenceSeverityLevel { + if v == cve.SeverityLevel && k > highestLevelIndex { + highestLevelIndex = k + } + } + } + introduction := fmt.Sprintf("An update for %s is now available for", sb.Component) + + var descr string + + title := introduction + branch + + if impl.IsCveNotice(sb.Identification) { + if i := strings.Index(description, "Security Fix(es):"); i > 0 { + descr = util.TrimStringNR(description[i+17:]) + } + } else { + descr = description + } + + var up = Update{ + From: "openeuler.org", + Type: "security", + Status: "stable", + Id: sb.Identification, + Title: title, + Severity: domain.Severity[strings.ToLower(dp.SequenceSeverityLevel[highestLevelIndex])], + Release: "openEuler", + Issued: &Issued{Date: sb.Date}, + Description: descr, + } + + var ref []Reference + for _, s := range cveNums { + ref = append(ref, Reference{ + Href: domain.CveUrlPrefix + s, + Id: s, + Title: s, + Type: "cve", + }) + } + + up.References = &References{Reference: ref} + + var pack []Package + for arch, pl := range sb.ProductTree { + if arch == "src" { + continue + } + + for _, productPackage := range pl { + var pe Package + pe.Filename = productPackage.FullName + packVersionList := strings.Split(productPackage.FullName, "-") + if len(packVersionList) >= 3 { + pe.Version = packVersionList[len(packVersionList)-2] + rpmName := packVersionList[len(packVersionList)-1][:len(packVersionList[len(packVersionList)-1])-4] + lastIndex := strings.LastIndexAny(rpmName, ".") + if lastIndex != -1 { + pe.Release = rpmName[:lastIndex] + pe.Arch = rpmName[lastIndex+1:] + } + pe.Name = strings.Join(packVersionList[0:len(packVersionList)-2], "-") + } + + if !strings.Contains(pe.Filename, "kernel") { + epoch, err := impl.findEpoch(domain.ScriptPath, branch, pe.Filename, pe.Arch, 1) + if err == nil && len(epoch) > 0 { + pe.Epoch = string(epoch) + } + } + + pack = append(pack, pe) + } + } + + up.Pkglist = &Pkglist{Collection: &Collection{Name: "openEuler", Package: pack}} + + return up +} + +func (impl updateInfoImpl) numberIndex(u *Updates, securityNumber string) (index int) { + index = -1 + for k, v := range u.Updatex { + if strings.EqualFold(v.Id, securityNumber) { + index = k + return + } + } + + return +} + +func (impl updateInfoImpl) IsCveNotice(securityNoticeNo string) bool { + return impl.GenNoticeType(securityNoticeNo) == domain.NoticeTypeCVE +} + +func (impl updateInfoImpl) GenNoticeType(securityNoticeNo string) string { + if strings.Contains(securityNoticeNo, "BA") { + return domain.NoticeTypeBug + } + + if strings.Contains(securityNoticeNo, "HotPatchSA") { + return domain.NoticeTypeCVE + } + + if strings.Contains(securityNoticeNo, "SA") { + return domain.NoticeTypeCVE + } + + return "" +} + +func (impl updateInfoImpl) findEpoch(script, branch, filename, arch string, i int) ([]byte, error) { + var archs = []string{arch} + if arch == "noarch" { + archs = []string{"aarch64", "x86_64"} + } + for _, a := range archs { + epoch, err, _ := libutils.RunCmd( + script, + filepath.Join("/opt/app/", branch, strconv.Itoa(i), time.Now().Format("150405.999")), + fmt.Sprintf(domain.PkgUrl, branch, a, filename), + ) + + if err != nil { + return nil, fmt.Errorf("failed to get epoch, pkgUrl is %s", fmt.Sprintf(domain.PkgUrl, branch, a, filename)) + } + + if err == nil { + if strings.Contains(string(epoch), "404") || strings.Contains(string(epoch), "502") { + continue + } + if ix := bytes.Index(epoch, []byte("NOKEY")); ix > 0 { + epoch = bytes.TrimSpace(epoch[ix+5:]) + } else { + epoch = bytes.TrimSpace(epoch) + } + + return domain.Num.Find(epoch), nil + } + } + + return nil, nil +} diff --git a/cve-vulner-manager/cve-ddd/infrastructure/updateinfoimpl/updateinfoxml.go b/cve-vulner-manager/cve-ddd/infrastructure/updateinfoimpl/updateinfoxml.go new file mode 100644 index 0000000000000000000000000000000000000000..465f38bd838ce62946e9b7de9bb7826dbd0b7f52 --- /dev/null +++ b/cve-vulner-manager/cve-ddd/infrastructure/updateinfoimpl/updateinfoxml.go @@ -0,0 +1,62 @@ +package updateinfoimpl + +import "encoding/xml" + +type Updates struct { + XMLName xml.Name `xml:"updates,omitempty"` + Updatex []Update `xml:"update,omitempty"` +} + +type Update struct { + XMLName xml.Name `xml:"update,omitempty"` + From string `xml:"from,attr"` + Type string `xml:"type,attr"` + Status string `xml:"status,attr"` + Id string `xml:"id"` + Title string `xml:"title"` + Severity string `xml:"severity"` + Release string `xml:"release"` + Issued *Issued `xml:"issued,omitempty"` + References *References `xml:"references,omitempty"` + Description string `xml:"description"` + Pkglist *Pkglist `xml:"pkglist,omitempty"` +} + +type Issued struct { + XMLName xml.Name `xml:"issued,omitempty"` + Date string `xml:"date,attr"` +} + +type References struct { + XMLName xml.Name `xml:"references,omitempty"` + Reference []Reference `xml:"reference,omitempty"` +} + +type Reference struct { + XMLName xml.Name `xml:"reference,omitempty"` + Href string `xml:"href,attr"` + Id string `xml:"id,attr"` + Title string `xml:"title,attr"` + Type string `xml:"type,attr"` +} + +type Pkglist struct { + XMLName xml.Name `xml:"pkglist,omitempty"` + Collection *Collection `xml:"collection,omitempty"` +} + +type Collection struct { + XMLName xml.Name `xml:"collection,omitempty"` + Name string `xml:"name"` + Package []Package `xml:"package,omitempty"` +} + +type Package struct { + XMLName xml.Name `xml:"package,omitempty"` + Epoch string `xml:"epoch,attr,omitempty"` + Arch string `xml:"arch,attr"` + Name string `xml:"name,attr"` + Release string `xml:"release,attr"` + Version string `xml:"version,attr"` + Filename string `xml:"filename"` +} diff --git a/cve-vulner-manager/routers/new_router.go b/cve-vulner-manager/routers/new_router.go index ab6e86df4b8627f15e48c64399fdb99458cf9ed9..0861a958005ec9e28676c861751b31ee593cdcf4 100644 --- a/cve-vulner-manager/routers/new_router.go +++ b/cve-vulner-manager/routers/new_router.go @@ -61,6 +61,7 @@ func InitNewRouter() { testresultimpl.NewTestResultImpl(logBulletin), backendimpl.NewBackendImpl(), logBulletin, + updateinfoimpl.NewUpdateInfoImpl(), ) hotPatchService := app.NewRefactorHotPatchService( diff --git a/cve-vulner-manager/sh/epoch.sh b/cve-vulner-manager/sh/epoch.sh new file mode 100644 index 0000000000000000000000000000000000000000..2c796ca811d42ea9ac97726dab09f040e7f669b6 --- /dev/null +++ b/cve-vulner-manager/sh/epoch.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +repo=$1 +url=$2 + +if [ ! -d "$repo" ]; then + mkdir -p "$repo" +fi + +cd "$repo" + +curl -LO -s "$url" + +v=$(rpm -qpi *.rpm | grep Epoch | awk {'print $3'}) + +i=$? + +cd .. && rm -rf "$repo" + +if [ "$i" != 0 ]; then +exit 1 +fi + +echo "$v" \ No newline at end of file