Skip to content

Commit

Permalink
Add support for blocking specific app/version/label combinations
Browse files Browse the repository at this point in the history
  • Loading branch information
kegsay committed May 9, 2024
1 parent ae1ce0c commit 6384ca1
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 21 deletions.
57 changes: 56 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ import (
"golang.org/x/oauth2"

"gopkg.in/yaml.v2"

_ "embed"
)
import _ "embed"

// DefaultIssueBodyTemplate is the default template used for `issue_body_template_file` in the config.
//
Expand All @@ -63,6 +64,8 @@ type config struct {
// Allowed rageshake app names
AllowedAppNames []string `yaml:"allowed_app_names"`

RejectionConditions []RejectionCondition `yaml:"rejection_conditions"`

// A GitHub personal access token, to create a GitHub issue for each report.
GithubToken string `yaml:"github_token"`

Expand Down Expand Up @@ -93,6 +96,49 @@ type config struct {
GenericWebhookURLs []string `yaml:"generic_webhook_urls"`
}

type RejectionCondition struct {

Check failure on line 99 in main.go

View workflow job for this annotation

GitHub Actions / lint

exported type RejectionCondition should have comment or be unexported
Version string `yaml:"version"`
Label string `yaml:"label"`
App string `yaml:"app"`
}

// shouldReject returns true if the app name AND version AND labels all match the rejection condition.
// If any one of these do not match the condition, it is not rejected.
func (c RejectionCondition) shouldReject(appName, version string, labels []string) bool {
if appName != c.App {
return false
}
// version was a condition and it doesn't match => accept it
if version != c.Version && c.Version != "" {
return false
}

// label was a condition and no label matches it => accept it
if c.Label != "" {
labelMatch := false
for _, l := range labels {
if l == c.Label {
labelMatch = true
break
}
}
if !labelMatch {
return false
}
}

return true
}

func (c *config) matchesRejectionCondition(p *payload) bool {
for _, rc := range c.RejectionConditions {
if rc.shouldReject(p.AppName, p.Data["Version"], p.Labels) {
return true
}
}
return false
}

func basicAuth(handler http.Handler, username, password, realm string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user, pass, ok := r.BasicAuth() // pull creds from the request
Expand Down Expand Up @@ -264,5 +310,14 @@ func loadConfig(configPath string) (*config, error) {
if err = yaml.Unmarshal(contents, &cfg); err != nil {
return nil, err
}
// sanity check rejection conditions
for _, rc := range cfg.RejectionConditions {
if rc.App == "" {
fmt.Println("rejection_condition missing an app field so will never match anything.")
}
if rc.Label == "" && rc.Version == "" {
fmt.Println("rejection_condition missing both label and version so will always match, specify label and/or version")
}
}
return &cfg, nil
}
10 changes: 10 additions & 0 deletions rageshake.sample.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ listings_auth_pass: secret
# An empty or missing list will retain legacy behaviour and permit reports from any application name.
allowed_app_names: []

# If any submission matches one of these rejection conditions, the submission is rejected.
rejection_conditions:
- app: my-app
version: "0.4.9" # if the submission has a Version which is exactly this value, reject the submission.
- app: my-app
label: "0.4.9" # if any label matches this value, the submission is rejected.
- app: my-app
version: "0.4.9"
label: "nightly" # both label and Version must match for this condition to be true

# a GitHub personal access token (https://github.com/settings/tokens), which
# will be used to create a GitHub issue for each report. It requires
# `public_repo` scope. If omitted, no issues will be created.
Expand Down
49 changes: 29 additions & 20 deletions submit.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ type issueBodyTemplatePayload struct {
type genericWebhookPayload struct {
payload
// If a github/gitlab report is generated, this is set.
ReportURL string `json:"report_url"`
ReportURL string `json:"report_url"`
// Complete link to the listing URL that contains all uploaded logs
ListingURL string `json:"listing_url"`
}
Expand All @@ -109,24 +109,24 @@ type genericWebhookPayload struct {
// !!! Since this is inherited by `issueBodyTemplatePayload`, remember to keep it in step
// with the documentation in `templates/README.md` !!!
type payload struct {
// A unique ID for this payload, generated within this server
ID string `json:"id"`
// A multi-line string containing the user description of the fault.
UserText string `json:"user_text"`
// A unique ID for this payload, generated within this server
ID string `json:"id"`
// A multi-line string containing the user description of the fault.
UserText string `json:"user_text"`
// A short slug to identify the app making the report
AppName string `json:"app"`
AppName string `json:"app"`
// Arbitrary data to annotate the report
Data map[string]string `json:"data"`
Data map[string]string `json:"data"`
// Short labels to group reports
Labels []string `json:"labels"`
Labels []string `json:"labels"`
// A list of names of logs recognised by the server
Logs []string `json:"logs"`
Logs []string `json:"logs"`
// Set if there are log parsing errors
LogErrors []string `json:"logErrors"`
LogErrors []string `json:"logErrors"`
// A list of other files (not logs) uploaded as part of the rageshake
Files []string `json:"files"`
Files []string `json:"files"`
// Set if there are file parsing errors
FileErrors []string `json:"fileErrors"`
FileErrors []string `json:"fileErrors"`
}

func (p payload) WriteTo(out io.Writer) {
Expand Down Expand Up @@ -222,6 +222,15 @@ func (s *submitServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
http.Error(w, "This server does not accept rageshakes from your application. See https://github.com/matrix-org/rageshake/blob/master/docs/blocked_rageshake.md", 400)
return
}
if s.cfg.matchesRejectionCondition(p) {
log.Printf("Blocking rageshake from app %s because it matches a rejection_condition", p.AppName)
if err := os.RemoveAll(reportDir); err != nil {
log.Printf("Unable to remove report dir %s after rejected upload: %v\n",
reportDir, err)
}
http.Error(w, "This server does not accept rageshakes from your application + version. See https://github.com/matrix-org/rageshake/blob/master/docs/blocked_rageshake.md", 400)
return
}

// We use this prefix (eg, 2022-05-01/125223-abcde) as a unique identifier for this rageshake.
// This is going to be used to uniquely identify rageshakes, even if they are not submitted to
Expand Down Expand Up @@ -422,11 +431,11 @@ func formPartToPayload(field, data string, p *payload) {

// we use a quite restrictive regexp for the filenames; in particular:
//
// * a limited set of extensions. We are careful to limit the content-types
// we will serve the files with, but somebody might accidentally point an
// Apache or nginx at the upload directory, which would serve js files as
// application/javascript and open XSS vulnerabilities. We also allow gzipped
// text and json on the same basis (there's really no sense allowing gzipped images).
// - a limited set of extensions. We are careful to limit the content-types
// we will serve the files with, but somebody might accidentally point an
// Apache or nginx at the upload directory, which would serve js files as
// application/javascript and open XSS vulnerabilities. We also allow gzipped
// text and json on the same basis (there's really no sense allowing gzipped images).
//
// * no silly characters (/, ctrl chars, etc)
//
Expand Down Expand Up @@ -551,9 +560,9 @@ func (s *submitServer) submitGenericWebhook(p payload, listingURL string, report
return nil
}
genericHookPayload := genericWebhookPayload{
payload: p,
ReportURL: reportURL,
ListingURL: listingURL,
payload: p,
ReportURL: reportURL,
ListingURL: listingURL,
}
for _, url := range s.cfg.GenericWebhookURLs {
// Enrich the payload with a reportURL and listingURL, to convert a single struct
Expand Down

0 comments on commit 6384ca1

Please sign in to comment.