From 369312989fd94c964bd151d6bdfc73f7c8cb7a8b Mon Sep 17 00:00:00 2001 From: "Brian C. Lane" Date: Tue, 12 May 2020 11:31:21 -0700 Subject: [PATCH] blueprints: Fix handling of invalid blueprint names in the API code Empty names are not allowed, and blueprint names should only contain characters matching: ^[a-zA-Z0-9._-]+$ This also adds tests for the various places where the blueprint name could potentially be wrong. --- internal/client/blueprints_test.go | 237 +++++++++++++++++++++++++++++ internal/client/compose_test.go | 48 ++++++ internal/weldr/api.go | 130 +++++++++++++--- internal/weldr/api_test.go | 1 + 4 files changed, 392 insertions(+), 24 deletions(-) diff --git a/internal/client/blueprints_test.go b/internal/client/blueprints_test.go index 2f932bd86..fe8b993e7 100644 --- a/internal/client/blueprints_test.go +++ b/internal/client/blueprints_test.go @@ -63,6 +63,42 @@ func TestPostEmptyTOMLBlueprintV0(t *testing.T) { require.False(t, resp.Status, "did not return an error") } +// POST a TOML blueprint with an empty name +func TestPostEmptyNameTOMLBlueprintV0(t *testing.T) { + // Use a blueprint with an empty Name + bp := ` + name="" + version="0.0.1" + [package] + name="bash" + version="*" + ` + resp, err := PostTOMLBlueprintV0(testState.socket, bp) + require.NoError(t, err, "failed with a client error") + require.NotNil(t, resp, "did not return an error") + require.False(t, resp.Status, "wrong Status (true)") + require.Equal(t, "InvalidChars", resp.Errors[0].ID) + require.Contains(t, resp.Errors[0].Msg, "Invalid characters in API path") +} + +// POST a TOML blueprint with an invalid name chars +func TestPostInvalidNameTOMLBlueprintV0(t *testing.T) { + // Use a blueprint with invalid Name + bp := ` + name="I ο½—π’Šll πŸ‰ΞΏπ˜ π› ο½π”°κœ± π˜π’‰πΈπšœ" + version="0.0.1" + [package] + name="bash" + version="*" + ` + resp, err := PostTOMLBlueprintV0(testState.socket, bp) + require.NoError(t, err, "failed with a client error") + require.NotNil(t, resp, "did not return an error") + require.False(t, resp.Status, "wrong Status (true)") + require.Equal(t, "InvalidChars", resp.Errors[0].ID) + require.Contains(t, resp.Errors[0].Msg, "Invalid characters in API path") +} + // POST a new JSON blueprint func TestPostJSONBlueprintV0(t *testing.T) { bp := `{ @@ -100,6 +136,40 @@ func TestPostEmptyJSONBlueprintV0(t *testing.T) { require.False(t, resp.Status, "did not return an error") } +// POST a JSON blueprint with an empty name +func TestPostEmptyNameJSONBlueprintV0(t *testing.T) { + // Use a blueprint with an empty Name + bp := `{ + "name": "", + "version": "0.0.1", + "modules": [{"name": "util-linux", "version": "*"}] + }` + + resp, err := PostJSONBlueprintV0(testState.socket, bp) + require.NoError(t, err, "failed with a client error") + require.NotNil(t, resp, "did not return an error") + require.False(t, resp.Status, "wrong Status (true)") + require.Equal(t, "InvalidChars", resp.Errors[0].ID) + require.Contains(t, resp.Errors[0].Msg, "Invalid characters in API path") +} + +// POST a JSON blueprint with invalid name chars +func TestPostInvalidNameJSONBlueprintV0(t *testing.T) { + // Use a blueprint with an empty Name + bp := `{ + "name": "I ο½—π’Šll πŸ‰ΞΏπ˜ π› ο½π”°κœ± π˜π’‰πΈπšœ", + "version": "0.0.1", + "modules": [{"name": "util-linux", "version": "*"}] + }` + + resp, err := PostJSONBlueprintV0(testState.socket, bp) + require.NoError(t, err, "failed with a client error") + require.NotNil(t, resp, "did not return an error") + require.False(t, resp.Status, "wrong Status (true)") + require.Equal(t, "InvalidChars", resp.Errors[0].ID) + require.Contains(t, resp.Errors[0].Msg, "Invalid characters in API path") +} + // POST a blueprint to the workspace as TOML func TestPostTOMLWorkspaceV0(t *testing.T) { bp := ` @@ -145,6 +215,42 @@ func TestPostEmptyTOMLWorkspaceV0(t *testing.T) { require.False(t, resp.Status, "did not return an error") } +// POST a TOML blueprint with an empty name to the workspace +func TestPostEmptyNameTOMLWorkspaceV0(t *testing.T) { + // Use a blueprint with an empty Name + bp := ` + name="" + version="0.0.1" + [package] + name="bash" + version="*" + ` + resp, err := PostTOMLWorkspaceV0(testState.socket, bp) + require.NoError(t, err, "failed with a client error") + require.NotNil(t, resp, "did not return an error") + require.False(t, resp.Status, "wrong Status (true)") + require.Equal(t, "InvalidChars", resp.Errors[0].ID) + require.Contains(t, resp.Errors[0].Msg, "Invalid characters in API path") +} + +// POST a TOML blueprint with an invalid name chars to the workspace +func TestPostInvalidNameTOMLWorkspaceV0(t *testing.T) { + // Use a blueprint with invalid Name + bp := ` + name="I ο½—π’Šll πŸ‰ΞΏπ˜ π› ο½π”°κœ± π˜π’‰πΈπšœ" + version="0.0.1" + [package] + name="bash" + version="*" + ` + resp, err := PostTOMLWorkspaceV0(testState.socket, bp) + require.NoError(t, err, "failed with a client error") + require.NotNil(t, resp, "did not return an error") + require.False(t, resp.Status, "wrong Status (true)") + require.Equal(t, "InvalidChars", resp.Errors[0].ID) + require.Contains(t, resp.Errors[0].Msg, "Invalid characters in API path") +} + // POST a blueprint to the workspace as JSON func TestPostJSONWorkspaceV0(t *testing.T) { bp := `{ @@ -182,6 +288,40 @@ func TestPostEmptyJSONWorkspaceV0(t *testing.T) { require.False(t, resp.Status, "did not return an error") } +// POST a JSON blueprint with an empty name to the workspace +func TestPostEmptyNameJSONWorkspaceV0(t *testing.T) { + // Use a blueprint with an empty Name + bp := `{ + "name": "", + "version": "0.0.1", + "modules": [{"name": "util-linux", "version": "*"}] + }` + + resp, err := PostJSONWorkspaceV0(testState.socket, bp) + require.NoError(t, err, "failed with a client error") + require.NotNil(t, resp, "did not return an error") + require.False(t, resp.Status, "wrong Status (true)") + require.Equal(t, "InvalidChars", resp.Errors[0].ID) + require.Contains(t, resp.Errors[0].Msg, "Invalid characters in API path") +} + +// POST a JSON blueprint with invalid name chars to the workspace +func TestPostInvalidNameJSONWorkspaceV0(t *testing.T) { + // Use a blueprint with an empty Name + bp := `{ + "name": "I ο½—π’Šll πŸ‰ΞΏπ˜ π› ο½π”°κœ± π˜π’‰πΈπšœ", + "version": "0.0.1", + "modules": [{"name": "util-linux", "version": "*"}] + }` + + resp, err := PostJSONWorkspaceV0(testState.socket, bp) + require.NoError(t, err, "failed with a client error") + require.NotNil(t, resp, "did not return an error") + require.False(t, resp.Status, "wrong Status (true)") + require.Equal(t, "InvalidChars", resp.Errors[0].ID) + require.Contains(t, resp.Errors[0].Msg, "Invalid characters in API path") +} + // delete a blueprint func TestDeleteBlueprintV0(t *testing.T) { // POST a blueprint to delete @@ -211,6 +351,16 @@ func TestDeleteNonBlueprint0(t *testing.T) { require.False(t, resp.Status, "did not return an error") } +// delete a blueprint with invalid name characters +func TestDeleteInvalidBlueprintV0(t *testing.T) { + resp, err := DeleteBlueprintV0(testState.socket, "I ο½—π’Šll πŸ‰ΞΏπ˜ π› ο½π”°κœ± π˜π’‰πΈπšœ") + require.NoError(t, err, "failed with a client error") + require.NotNil(t, resp, "did not return an error") + require.False(t, resp.Status, "wrong Status (true)") + require.Equal(t, "InvalidChars", resp.Errors[0].ID) + require.Contains(t, resp.Errors[0].Msg, "Invalid characters in API path") +} + // delete a new blueprint from the workspace func TestDeleteNewWorkspaceV0(t *testing.T) { // POST a blueprint to delete @@ -291,6 +441,23 @@ func TestDeleteChangesWorkspaceV0(t *testing.T) { require.Equal(t, "deleteBlueprintChangesWSV0", info.Blueprints[0].Description, "original blueprint not returned") } +// delete a non-existent blueprint workspace +func TestDeleteNonWorkspace0(t *testing.T) { + resp, err := DeleteWorkspaceV0(testState.socket, "test-delete-non-blueprint-ws-v0") + require.NoError(t, err, "failed with a client error") + require.False(t, resp.Status, "did not return an error") +} + +// delete a blueprint with invalid name characters +func TestDeleteInvalidWorkspaceV0(t *testing.T) { + resp, err := DeleteWorkspaceV0(testState.socket, "I ο½—π’Šll πŸ‰ΞΏπ˜ π› ο½π”°κœ± π˜π’‰πΈπšœ") + require.NoError(t, err, "failed with a client error") + require.NotNil(t, resp, "did not return an error") + require.False(t, resp.Status, "wrong Status (true)") + require.Equal(t, "InvalidChars", resp.Errors[0].ID) + require.Contains(t, resp.Errors[0].Msg, "Invalid characters in API path") +} + // list blueprints func TestListBlueprintsV0(t *testing.T) { // Post a couple of blueprints @@ -361,6 +528,16 @@ func TestGetNonTOMLBlueprintV0(t *testing.T) { require.False(t, api.Status, "wrong Status (true)") } +// get blueprint with invalid name characters +func TestGetInvalidTOMLBlueprintV0(t *testing.T) { + _, api, err := GetBlueprintInfoTOMLV0(testState.socket, "I ο½—π’Šll πŸ‰ΞΏπ˜ π› ο½π”°κœ± π˜π’‰πΈπšœ") + require.NoError(t, err, "failed with a client error") + require.NotNil(t, api, "did not return an error") + require.False(t, api.Status, "wrong Status (true)") + require.Equal(t, "InvalidChars", api.Errors[0].ID) + require.Contains(t, api.Errors[0].Msg, "Invalid characters in API path") +} + // get blueprint contents as JSON func TestGetJSONBlueprintV0(t *testing.T) { bp := `{ @@ -490,6 +667,16 @@ func TestBlueprintNonChangesV0(t *testing.T) { require.Greater(t, len(resp.Errors), 0, "failed with no error: %#v", resp) } +// get changes with invalid name characters +func TestInvalidBlueprintChangesV0(t *testing.T) { + _, api, err := GetBlueprintsChangesV0(testState.socket, []string{"I ο½—π’Šll πŸ‰ΞΏπ˜ π› ο½π”°κœ± π˜π’‰πΈπšœ"}) + require.NoError(t, err, "failed with a client error") + require.NotNil(t, api, "did not return an error") + require.False(t, api.Status, "wrong Status (true)") + require.Equal(t, "InvalidChars", api.Errors[0].ID) + require.Contains(t, api.Errors[0].Msg, "Invalid characters in API path") +} + // Undo blueprint changes func TestUndoBlueprintV0(t *testing.T) { bps := []string{`{ @@ -589,6 +776,26 @@ func TestUndoNonBlueprintV0(t *testing.T) { require.False(t, resp.Status, "did not return an error") } +// undo a blueprint with invalid name characters +func TestUndoInvalidBlueprintV0(t *testing.T) { + resp, err := UndoBlueprintChangeV0(testState.socket, "I ο½—π’Šll πŸ‰ΞΏπ˜ π› ο½π”°κœ± π˜π’‰πΈπšœ", "FFFF") + require.NoError(t, err, "failed with a client error") + require.NotNil(t, resp, "did not return an error") + require.False(t, resp.Status, "wrong Status (true)") + require.Equal(t, "InvalidChars", resp.Errors[0].ID) + require.Contains(t, resp.Errors[0].Msg, "Invalid characters in API path") +} + +// undo a blueprint with invalid commit characters +func TestUndoInvalidBlueprintCommitV0(t *testing.T) { + resp, err := UndoBlueprintChangeV0(testState.socket, "test-undo-non-blueprint-v0", "I ο½—π’Šll πŸ‰ΞΏπ˜ π› ο½π”°κœ± π˜π’‰πΈπšœ") + require.NoError(t, err, "failed with a client error") + require.NotNil(t, resp, "did not return an error") + require.False(t, resp.Status, "wrong Status (true)") + require.Equal(t, "InvalidChars", resp.Errors[0].ID) + require.Contains(t, resp.Errors[0].Msg, "Invalid characters in API path") +} + // Tag a blueprint with a new revision // The blueprint revision tag cannot be reset, it always increments by one, and cannot be deleted. // So to test tagging we tag two blueprint changes and make sure the second is first +1 @@ -660,6 +867,16 @@ func TestNonBlueprintTagV0(t *testing.T) { require.False(t, tagResp.Status, "did not return an error") } +// tag a blueprint with invalid name characters +func TestTagInvalidBlueprintV0(t *testing.T) { + resp, err := TagBlueprintV0(testState.socket, "I ο½—π’Šll πŸ‰ΞΏπ˜ π› ο½π”°κœ± π˜π’‰πΈπšœ") + require.NoError(t, err, "failed with a client error") + require.NotNil(t, resp, "did not return an error") + require.False(t, resp.Status, "wrong Status (true)") + require.Equal(t, "InvalidChars", resp.Errors[0].ID) + require.Contains(t, resp.Errors[0].Msg, "Invalid characters in API path") +} + // depsolve a blueprint with packages and modules func TestBlueprintDepsolveV0(t *testing.T) { bp := `{ @@ -695,6 +912,16 @@ func TestNonBlueprintDepsolveV0(t *testing.T) { require.Greater(t, len(resp.Errors), 0, "failed with no error: %#v", resp) } +// depsolve a blueprint with invalid name characters +func TestDepsolveInvalidBlueprintV0(t *testing.T) { + _, api, err := DepsolveBlueprintV0(testState.socket, "I ο½—π’Šll πŸ‰ΞΏπ˜ π› ο½π”°κœ± π˜π’‰πΈπšœ") + require.NoError(t, err, "failed with a client error") + require.NotNil(t, api, "did not return an error") + require.False(t, api.Status, "wrong Status (true)") + require.Equal(t, "InvalidChars", api.Errors[0].ID) + require.Contains(t, api.Errors[0].Msg, "Invalid characters in API path") +} + // freeze a blueprint func TestBlueprintFreezeV0(t *testing.T) { if testState.unitTest { @@ -758,4 +985,14 @@ func TestNonBlueprintFreezeV0(t *testing.T) { require.Greater(t, len(resp.Errors), 0, "failed with no error: %#v", resp) } +// freeze a blueprint with invalid name characters +func TestFreezeInvalidBlueprintV0(t *testing.T) { + _, api, err := FreezeBlueprintV0(testState.socket, "I ο½—π’Šll πŸ‰ΞΏπ˜ π› ο½π”°κœ± π˜π’‰πΈπšœ") + require.NoError(t, err, "failed with a client error") + require.NotNil(t, api, "did not return an error") + require.False(t, api.Status, "wrong Status (true)") + require.Equal(t, "InvalidChars", api.Errors[0].ID) + require.Contains(t, api.Errors[0].Msg, "Invalid characters in API path") +} + // TODO diff of blueprint changes diff --git a/internal/client/compose_test.go b/internal/client/compose_test.go index de73cd040..69a8ef605 100644 --- a/internal/client/compose_test.go +++ b/internal/client/compose_test.go @@ -89,6 +89,36 @@ func TestComposeInvalidBlueprintV0(t *testing.T) { require.Contains(t, resp.Errors[0].Msg, "test-invalid-bp-compose-v0") } +// Test compose for empty blueprint fails +func TestComposeEmptyBlueprintV0(t *testing.T) { + compose := `{ + "blueprint_name": "", + "compose_type": "qcow2", + "branch": "master" + }` + resp, err := PostComposeV0(testState.socket, compose) + require.NoError(t, err, "failed with a client error") + require.NotNil(t, resp) + require.False(t, resp.Status, "POST did not fail") + require.Equal(t, len(resp.Errors), 1) + require.Contains(t, resp.Errors[0].Msg, "Invalid characters in API path") +} + +// Test compose for blueprint with invalid characters fails +func TestComposeInvalidCharsBlueprintV0(t *testing.T) { + compose := `{ + "blueprint_name": "I ο½—π’Šll πŸ‰ΞΏπ˜ π› ο½π”°κœ± π˜π’‰πΈπšœ", + "compose_type": "qcow2", + "branch": "master" + }` + resp, err := PostComposeV0(testState.socket, compose) + require.NoError(t, err, "failed with a client error") + require.NotNil(t, resp) + require.False(t, resp.Status, "POST did not fail") + require.Equal(t, len(resp.Errors), 1) + require.Contains(t, resp.Errors[0].Msg, "Invalid characters in API path") +} + // Test compose cancel for unknown uuid fails // Is cancel implemented at all? @@ -162,6 +192,24 @@ func TestComposeInvalidStatusV0(t *testing.T) { require.Equal(t, 0, len(status)) } +// Test status filter for unknown blueprint +func TestComposeUnknownBlueprintStatusV0(t *testing.T) { + status, resp, err := GetComposeStatusV0(testState.socket, "*", "unknown-blueprint-test", "", "") + require.NoError(t, err, "failed with a client error") + require.Nil(t, resp) + require.Equal(t, 0, len(status)) +} + +// Test status filter for blueprint with invalid characters +func TestComposeInvalidBlueprintStatusV0(t *testing.T) { + status, resp, err := GetComposeStatusV0(testState.socket, "*", "I ο½—π’Šll πŸ‰ΞΏπ˜ π› ο½π”°κœ± π˜π’‰πΈπšœ", "", "") + require.NoError(t, err, "failed with a client error") + require.NotNil(t, resp) + require.Equal(t, "InvalidChars", resp.Errors[0].ID) + require.Contains(t, resp.Errors[0].Msg, "Invalid characters in API path") + require.Equal(t, 0, len(status)) +} + // Helper for searching compose results for a UUID func UUIDInComposeResults(buildID uuid.UUID, results []weldr.ComposeEntryV0) bool { for idx := range results { diff --git a/internal/weldr/api.go b/internal/weldr/api.go index f080493f4..0ae09569c 100644 --- a/internal/weldr/api.go +++ b/internal/weldr/api.go @@ -11,6 +11,7 @@ import ( "net" "net/http" "net/url" + "regexp" "sort" "strconv" "strings" @@ -43,6 +44,8 @@ type API struct { router *httprouter.Router } +var ValidBlueprintName = regexp.MustCompile(`^[a-zA-Z0-9._-]+$`) + func New(rpmmd rpmmd.RPMMD, arch distro.Arch, distro distro.Distro, repos []rpmmd.RepoConfig, logger *log.Logger, store *store.Store, workers *worker.Server) *API { api := &API{ store: store, @@ -232,6 +235,24 @@ type responseError struct { Msg string `json:"msg"` } +// verifyStringsWithRegex checks a slive of strings against a regex of allowed characters +// it writes the InvalidChars error to the writer and returns false if any of them fail the check +// It will also return an error if the string is empty +func verifyStringsWithRegex(writer http.ResponseWriter, strings []string, re *regexp.Regexp) bool { + for _, s := range strings { + if len(s) > 0 && re.MatchString(s) { + continue + } + errors := responseError{ + ID: "InvalidChars", + Msg: "Invalid characters in API path", + } + statusResponseError(writer, http.StatusBadRequest, errors) + return false + } + return true +} + func statusResponseError(writer http.ResponseWriter, code int, errors ...responseError) { type reply struct { Status bool `json:"status"` @@ -807,6 +828,13 @@ func (api *API) blueprintsInfoHandler(writer http.ResponseWriter, request *http. return } + // Remove the leading / from the first entry (check above ensures it is not just a / + names[0] = names[0][1:] + + if !verifyStringsWithRegex(writer, names, ValidBlueprintName) { + return + } + query, err := url.ParseQuery(request.URL.RawQuery) if err != nil { errors := responseError{ @@ -821,12 +849,7 @@ func (api *API) blueprintsInfoHandler(writer http.ResponseWriter, request *http. changes := []change{} blueprintErrors := []responseError{} - for i, name := range names { - // remove leading / from first name - if i == 0 { - name = name[1:] - } - + for _, name := range names { blueprint, changed := api.store.GetBlueprint(name) if blueprint == nil { blueprintErrors = append(blueprintErrors, responseError{ @@ -902,13 +925,16 @@ func (api *API) blueprintsDepsolveHandler(writer http.ResponseWriter, request *h return } + // Remove the leading / from the first entry (check above ensures it is not just a / + names[0] = names[0][1:] + + if !verifyStringsWithRegex(writer, names, ValidBlueprintName) { + return + } + blueprints := []entry{} blueprintsErrors := []responseError{} - for i, name := range names { - // remove leading / from first name - if i == 0 { - name = name[1:] - } + for _, name := range names { blueprint, _ := api.store.GetBlueprint(name) if blueprint == nil { blueprintsErrors = append(blueprintsErrors, responseError{ @@ -987,13 +1013,16 @@ func (api *API) blueprintsFreezeHandler(writer http.ResponseWriter, request *htt return } + // Remove the leading / from the first entry (check above ensures it is not just a / + names[0] = names[0][1:] + + if !verifyStringsWithRegex(writer, names, ValidBlueprintName) { + return + } + blueprints := []blueprintFrozen{} errors := []responseError{} - for i, name := range names { - // remove leading / from first name - if i == 0 { - name = name[1:] - } + for _, name := range names { bp, _ := api.store.GetBlueprint(name) if bp == nil { rerr := responseError{ @@ -1076,8 +1105,19 @@ func (api *API) blueprintsDiffHandler(writer http.ResponseWriter, request *http. } name := params.ByName("blueprint") + if !verifyStringsWithRegex(writer, []string{name}, ValidBlueprintName) { + return + } + fromCommit := params.ByName("from") + if !verifyStringsWithRegex(writer, []string{fromCommit}, ValidBlueprintName) { + return + } + toCommit := params.ByName("to") + if !verifyStringsWithRegex(writer, []string{toCommit}, ValidBlueprintName) { + return + } if len(name) == 0 || len(fromCommit) == 0 || len(toCommit) == 0 { errors := responseError{ @@ -1178,6 +1218,13 @@ func (api *API) blueprintsChangesHandler(writer http.ResponseWriter, request *ht return } + // Remove the leading / from the first entry (check above ensures it is not just a / + names[0] = names[0][1:] + + if !verifyStringsWithRegex(writer, names, ValidBlueprintName) { + return + } + offset, limit, err := parseOffsetAndLimit(request.URL.Query()) if err != nil { errors := responseError{ @@ -1190,11 +1237,7 @@ func (api *API) blueprintsChangesHandler(writer http.ResponseWriter, request *ht allChanges := []change{} errors := []responseError{} - for i, name := range names { - // remove leading / from first name - if i == 0 { - name = name[1:] - } + for _, name := range names { bpChanges := api.store.GetBlueprintChanges(name) // Reverse the changes, newest first reversed := make([]blueprint.Change, 0, len(bpChanges)) @@ -1269,6 +1312,10 @@ func (api *API) blueprintsNewHandler(writer http.ResponseWriter, request *http.R return } + if !verifyStringsWithRegex(writer, []string{blueprint.Name}, ValidBlueprintName) { + return + } + commitMsg := "Recipe " + blueprint.Name + ", version " + blueprint.Version + " saved." err = api.store.PushBlueprint(blueprint, commitMsg) if err != nil { @@ -1326,6 +1373,10 @@ func (api *API) blueprintsWorkspaceHandler(writer http.ResponseWriter, request * return } + if !verifyStringsWithRegex(writer, []string{blueprint.Name}, ValidBlueprintName) { + return + } + err = api.store.PushBlueprintToWorkspace(blueprint) if err != nil { errors := responseError{ @@ -1345,7 +1396,15 @@ func (api *API) blueprintUndoHandler(writer http.ResponseWriter, request *http.R } name := params.ByName("blueprint") + if !verifyStringsWithRegex(writer, []string{name}, ValidBlueprintName) { + return + } + commit := params.ByName("commit") + if !verifyStringsWithRegex(writer, []string{commit}, ValidBlueprintName) { + return + } + bpChange, err := api.store.GetBlueprintChange(name, commit) if err != nil { errors := responseError{ @@ -1375,7 +1434,12 @@ func (api *API) blueprintDeleteHandler(writer http.ResponseWriter, request *http return } - if err := api.store.DeleteBlueprint(params.ByName("blueprint")); err != nil { + name := params.ByName("blueprint") + if !verifyStringsWithRegex(writer, []string{name}, ValidBlueprintName) { + return + } + + if err := api.store.DeleteBlueprint(name); err != nil { errors := responseError{ ID: "BlueprintsError", Msg: err.Error(), @@ -1391,7 +1455,12 @@ func (api *API) blueprintDeleteWorkspaceHandler(writer http.ResponseWriter, requ return } - if err := api.store.DeleteBlueprintFromWorkspace(params.ByName("blueprint")); err != nil { + name := params.ByName("blueprint") + if !verifyStringsWithRegex(writer, []string{name}, ValidBlueprintName) { + return + } + + if err := api.store.DeleteBlueprintFromWorkspace(name); err != nil { errors := responseError{ ID: "BlueprintsError", Msg: err.Error(), @@ -1408,7 +1477,12 @@ func (api *API) blueprintsTagHandler(writer http.ResponseWriter, request *http.R return } - err := api.store.TagBlueprint(params.ByName("blueprint")) + name := params.ByName("blueprint") + if !verifyStringsWithRegex(writer, []string{name}, ValidBlueprintName) { + return + } + + err := api.store.TagBlueprint(name) if err != nil { errors := responseError{ ID: "BlueprintsError", @@ -1472,6 +1546,10 @@ func (api *API) composeHandler(writer http.ResponseWriter, request *http.Request return } + if !verifyStringsWithRegex(writer, []string{cr.BlueprintName}, ValidBlueprintName) { + return + } + composeID := uuid.New() var targets []*target.Target @@ -1727,6 +1805,10 @@ func (api *API) composeStatusHandler(writer http.ResponseWriter, request *http.R } filterBlueprint := q.Get("blueprint") + if len(filterBlueprint) > 0 && !verifyStringsWithRegex(writer, []string{filterBlueprint}, ValidBlueprintName) { + return + } + filterStatus := q.Get("status") filterImageType, filterImageTypeExists := common.ImageTypeFromCompatString(q.Get("type")) diff --git a/internal/weldr/api_test.go b/internal/weldr/api_test.go index b33bc1db9..9c56c0684 100644 --- a/internal/weldr/api_test.go +++ b/internal/weldr/api_test.go @@ -79,6 +79,7 @@ func TestBlueprintsNew(t *testing.T) { {"POST", "/api/v0/blueprints/new", `{"name":"test","description":"Test","packages":[],"version":""}`, http.StatusOK, `{"status":true}`}, {"POST", "/api/v0/blueprints/new", `{"name":"test","description":"Test","packages":[{"name":"httpd","version":"2.4.*"}],"version":"0.0.0"}`, http.StatusOK, `{"status":true}`}, {"POST", "/api/v0/blueprints/new", `{"name":"test","description":"Test","packages:}`, http.StatusBadRequest, `{"status":false,"errors":[{"id":"BlueprintsError","msg":"400 Bad Request: The browser (or proxy) sent a request that this server could not understand: unexpected EOF"}]}`}, + {"POST", "/api/v0/blueprints/new", `{"name":"","description":"Test","packages":[{"name":"httpd","version":"2.4.*"}],"version":"0.0.0"}`, http.StatusBadRequest, `{"status":false,"errors":[{"id":"InvalidChars","msg":"Invalid characters in API path"}]}`}, {"POST", "/api/v0/blueprints/new", ``, http.StatusBadRequest, `{"status":false,"errors":[{"id":"BlueprintsError","msg":"Missing blueprint"}]}`}, }