forwarder/conversion.go (162 lines of code) (raw):
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"log"
"strconv"
"time"
"cloud.google.com/go/bigtable"
securityreport "github.com/GoogleCloudPlatform/reporting-api-processor/forwarder/proto"
"github.com/labstack/echo/v4"
"google.golang.org/protobuf/proto"
)
const (
tableName = "security_report"
columnFamily = "description"
column = "data"
)
func mapToSHA256HexString(m map[string]interface{}) (string, error) {
deserialized, err := json.Marshal(m)
if err != nil {
return "", err
}
checksum := sha256.Sum256(deserialized)
return hex.EncodeToString(checksum[:]), nil
}
func mapToSecurityReport(logger echo.Logger, m map[string]interface{}) *securityreport.SecurityReport {
sr := &securityreport.SecurityReport{}
now := time.Now().UnixMilli()
checksum, err := mapToSHA256HexString(m)
if err != nil {
logger.Errorf("failed to marshal map[string]interface{}: %v", m)
return nil
}
sr.ReportChecksum = checksum
sr.ReportCount = int64(1)
sr.Disposition = securityreport.SecurityReport_DISPOSITION_UNKNOWN
// the report has "age" field that is the offset from the report's timestamp.
// https://w3c.github.io/reporting/#serialize-reports
//
// NOTE: currently the report doesn't have "timestamp" field, so use server side
// current time.
if age, ok := m["age"].(float64); ok {
sr.ReportTime = now - int64(age)
}
if ua, ok := m["user_agent"].(string); ok {
sr.UserAgent = ua
}
var typ string
var body map[string]interface{}
var ok bool
if typ, ok = m["type"].(string); !ok {
logger.Errorf("unexpected report type: %v", m)
return sr
}
if body, ok = m["body"].(map[string]interface{}); !ok {
logger.Errorf("unexpected report type: %v", m)
return sr
}
switch typ {
case "csp-violation":
csp := &securityreport.CspReport{}
if duri, ok := body["documentURL"].(string); ok {
csp.DocumentUri = duri
} else {
logger.Warnf("unexpected documentURL: %#v", body["documentURL"])
}
if ref, ok := body["referrer"].(string); ok {
csp.Referrer = ref
} else {
logger.Warnf("unexpected referrer: %#v", body["referrer"])
}
if buri, ok := body["blockedURL"].(string); ok {
csp.BlockedUri = buri
} else {
logger.Warnf("unexpected blockedURL: %#v", body["blockedURL"])
}
if vd, ok := body["violatedDirective"].(string); ok {
csp.ViolatedDirective = vd
} else {
logger.Warnf("unexpected violatedDirective: %#v", body["violatedDirective"])
}
if ed, ok := body["effectiveDirective"].(string); ok {
csp.EffectiveDirective = ed
} else {
logger.Warnf("unexpected effectiveDirective: %#v", body["effectiveDirective"])
}
if sf, ok := body["sourceFile"].(string); ok {
csp.SourceFile = sf
} else {
logger.Warnf("unexpected sourceFile: %#v", body["sourceFile"])
}
if ln, ok := body["lineNumber"].(float64); ok {
csp.LineNumber = int32(ln)
} else {
logger.Warnf("unexpected lineNumber: %#v", body["lineNumber"])
}
if cn, ok := body["columnNumber"].(float64); ok {
csp.ColumnNumber = int32(cn)
} else {
logger.Warnf("unexpected columnNumber: %#v", body["columnNumber"])
}
if ss, ok := body["scriptSample"].(string); ok {
csp.ScriptSample = ss
} else {
logger.Warnf("unexpected scriptSample: %#v", body["scriptSample"])
}
sr.ReportExtension = &securityreport.SecurityReport_CspReport{CspReport: csp}
switch body["disposition"].(string) {
case "enforce":
sr.Disposition = securityreport.SecurityReport_ENFORCED
case "report":
sr.Disposition = securityreport.SecurityReport_REPORTING
default:
}
case "deprecation":
dep := &securityreport.DeprecationReport{}
if id, ok := body["id"].(string); ok {
dep.Id = id
} else {
logger.Warnf("unexpected id: %#v", body["id"])
}
if ar, ok := body["anticipatedRemoval"].(string); ok {
dep.AnticipatedRemoval = ar
} else {
logger.Warnf("unexpected anticipatedRemoval: %#v", body["anticipatedRemoval"])
}
if ln, ok := body["lineNumber"].(float64); ok {
dep.LineNumber = int32(ln)
} else {
logger.Warnf("unexpected lineNumber: %#v", body["lineNumber"])
}
if cn, ok := body["columnNumber"].(float64); ok {
dep.ColumnNumber = int32(cn)
} else {
logger.Warnf("unexpected columnNumber: %#v", body["columnNumber"])
}
if m, ok := body["message"].(string); ok {
dep.Message = m
} else {
logger.Warnf("unexpected message: %#v", body["message"])
}
if sf, ok := body["sourceFile"].(string); ok {
dep.SourceFile = sf
} else {
logger.Warnf("unexpected sourceFile: %#v", body["sourceFile"])
}
sr.ReportExtension = &securityreport.SecurityReport_DeprecationReport{DeprecationReport: dep}
}
return sr
}
func generateRowKey(r *securityreport.SecurityReport) string {
ext := "csp"
if r.GetDeprecationReport() != nil {
ext = "dep"
}
// row key format: report_extension#checksum#timestamp
return ext + "#" + r.GetReportChecksum() + "#" + strconv.FormatInt(r.GetReportTime(), 10)
}
func setSecurityReport(m *bigtable.Mutation, r *securityreport.SecurityReport) {
now := bigtable.Now()
serialized, err := proto.Marshal(r)
if err != nil {
log.Printf("failed to serialize SecurityReport: %v", err)
return
}
m.Set(columnFamily, column, now, serialized)
}