diff --git a/server/server.go b/server/server.go index db929271d4..ab7e266e95 100644 --- a/server/server.go +++ b/server/server.go @@ -18,6 +18,8 @@ import ( "crypto/tls" + "net/url" + "github.com/gorilla/mux" "github.com/open-policy-agent/opa/ast" "github.com/open-policy-agent/opa/server/authorizer" @@ -82,10 +84,9 @@ func New() *Server { s.registerHandlerV1(router, "/data/{path:.+}", "POST", s.v1DataPost) s.registerHandlerV1(router, "/data", "POST", s.v1DataPost) s.registerHandlerV1(router, "/policies", "GET", s.v1PoliciesList) - s.registerHandlerV1(router, "/policies/{id}", "DELETE", s.v1PoliciesDelete) - s.registerHandlerV1(router, "/policies/{id}", "GET", s.v1PoliciesGet) - s.registerHandlerV1(router, "/policies/{id}/raw", "GET", s.v1PoliciesRawGet) - s.registerHandlerV1(router, "/policies/{id}", "PUT", s.v1PoliciesPut) + s.registerHandlerV1(router, "/policies/{path:.+}", "DELETE", s.v1PoliciesDelete) + s.registerHandlerV1(router, "/policies/{path:.+}", "GET", s.v1PoliciesGet) + s.registerHandlerV1(router, "/policies/{path:.+}", "PUT", s.v1PoliciesPut) s.registerHandlerV1(router, "/query", "GET", s.v1QueryGet) router.HandleFunc("/", s.indexGet).Methods("GET") s.Handler = router @@ -297,7 +298,7 @@ func (s *Server) v1DataGet(w http.ResponseWriter, r *http.Request) { ctx := r.Context() vars := mux.Vars(r) path := stringPathToDataRef(vars["path"]) - pretty := getPretty(r.URL.Query()["pretty"]) + pretty := getBoolParam(r.URL, types.ParamPrettyV1, true) explainMode := getExplain(r.URL.Query()["explain"]) input, nonGround, err := parseInput(r.URL.Query()[types.ParamInputV1]) @@ -410,7 +411,7 @@ func (s *Server) v1DataPost(w http.ResponseWriter, r *http.Request) { ctx := r.Context() vars := mux.Vars(r) path := stringPathToDataRef(vars["path"]) - pretty := getPretty(r.URL.Query()["pretty"]) + pretty := getBoolParam(r.URL, types.ParamPrettyV1, true) explainMode := getExplain(r.URL.Query()["explain"]) input, err := readInput(r.Body) @@ -524,7 +525,7 @@ func (s *Server) v1DataPut(w http.ResponseWriter, r *http.Request) { func (s *Server) v1PoliciesDelete(w http.ResponseWriter, r *http.Request) { ctx := r.Context() vars := mux.Vars(r) - id := vars["id"] + id := vars["path"] txn, err := s.store.NewTransaction(ctx) if err != nil { @@ -563,7 +564,8 @@ func (s *Server) v1PoliciesDelete(w http.ResponseWriter, r *http.Request) { func (s *Server) v1PoliciesGet(w http.ResponseWriter, r *http.Request) { ctx := r.Context() vars := mux.Vars(r) - id := vars["id"] + path := vars["path"] + source := getBoolParam(r.URL, types.ParamSourceV1, true) txn, err := s.store.NewTransaction(ctx) if err != nil { @@ -573,47 +575,29 @@ func (s *Server) v1PoliciesGet(w http.ResponseWriter, r *http.Request) { defer s.store.Close(ctx, txn) - _, _, err = s.store.GetPolicy(txn, id) + _, bs, err := s.store.GetPolicy(txn, path) if err != nil { writer.ErrorAuto(w, err) return } + if source { + writer.Bytes(w, 200, bs) + return + } + c := s.Compiler() response := types.PolicyGetResponseV1{ Result: types.PolicyV1{ - ID: id, - Module: c.Modules[id], + ID: path, + Module: c.Modules[path], }, } writer.JSON(w, 200, response, true) } -func (s *Server) v1PoliciesRawGet(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - vars := mux.Vars(r) - id := vars["id"] - - txn, err := s.store.NewTransaction(ctx) - if err != nil { - writer.ErrorAuto(w, err) - return - } - - defer s.store.Close(ctx, txn) - - _, bs, err := s.store.GetPolicy(txn, id) - - if err != nil { - writer.ErrorAuto(w, err) - return - } - - writer.Bytes(w, 200, bs) -} - func (s *Server) v1PoliciesList(w http.ResponseWriter, r *http.Request) { policies := []types.PolicyV1{} @@ -638,7 +622,7 @@ func (s *Server) v1PoliciesList(w http.ResponseWriter, r *http.Request) { func (s *Server) v1PoliciesPut(w http.ResponseWriter, r *http.Request) { ctx := r.Context() vars := mux.Vars(r) - id := vars["id"] + path := vars["path"] buf, err := ioutil.ReadAll(r.Body) if err != nil { @@ -646,7 +630,7 @@ func (s *Server) v1PoliciesPut(w http.ResponseWriter, r *http.Request) { return } - parsedMod, err := ast.ParseModule(id, string(buf)) + parsedMod, err := ast.ParseModule(path, string(buf)) if err != nil { switch err := err.(type) { @@ -673,7 +657,7 @@ func (s *Server) v1PoliciesPut(w http.ResponseWriter, r *http.Request) { defer s.store.Close(ctx, txn) mods := s.store.ListPolicies(txn) - mods[id] = parsedMod + mods[path] = parsedMod c := ast.NewCompiler() @@ -682,7 +666,7 @@ func (s *Server) v1PoliciesPut(w http.ResponseWriter, r *http.Request) { return } - if err := s.store.InsertPolicy(txn, id, parsedMod, buf, s.persist); err != nil { + if err := s.store.InsertPolicy(txn, path, parsedMod, buf, s.persist); err != nil { writer.ErrorAuto(w, err) return } @@ -691,8 +675,8 @@ func (s *Server) v1PoliciesPut(w http.ResponseWriter, r *http.Request) { response := types.PolicyPutResponseV1{ Result: types.PolicyV1{ - ID: id, - Module: c.Modules[id], + ID: path, + Module: c.Modules[path], }, } @@ -702,7 +686,7 @@ func (s *Server) v1PoliciesPut(w http.ResponseWriter, r *http.Request) { func (s *Server) v1QueryGet(w http.ResponseWriter, r *http.Request) { ctx := r.Context() values := r.URL.Query() - pretty := getPretty(r.URL.Query()["pretty"]) + pretty := getBoolParam(r.URL, types.ParamPrettyV1, true) explainMode := getExplain(r.URL.Query()["explain"]) qStrs := values["q"] if len(qStrs) == 0 { @@ -874,12 +858,25 @@ func stringPathToRef(s string) (r ast.Ref) { return r } -func getPretty(p []string) bool { +func getBoolParam(url *url.URL, name string, ifEmpty bool) bool { + + p, ok := url.Query()[name] + if !ok { + return false + } + + // Query params w/o values are represented as slice (of len 1) with an + // empty string. + if len(p) == 1 && p[0] == "" { + return ifEmpty + } + for _, x := range p { if strings.ToLower(x) == "true" { return true } } + return false } diff --git a/server/server_test.go b/server/server_test.go index 9cb547085b..eae2f0ad2f 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -722,7 +722,7 @@ func TestPoliciesGetV1(t *testing.T) { } } -func TestPoliciesGetRawV1(t *testing.T) { +func TestPoliciesGetSourceV1(t *testing.T) { f := newFixture(t) put := newReqV1("PUT", "/policies/1", testMod) f.server.Handler.ServeHTTP(f.recorder, put) @@ -732,7 +732,7 @@ func TestPoliciesGetRawV1(t *testing.T) { } f.reset() - get := newReqV1("GET", "/policies/1/raw", "") + get := newReqV1("GET", "/policies/1?source", "") f.server.Handler.ServeHTTP(f.recorder, get) @@ -773,6 +773,16 @@ func TestPoliciesDeleteV1(t *testing.T) { } } +func TestPoliciesPathSlashes(t *testing.T) { + f := newFixture(t) + if err := f.v1("PUT", "/policies/a/b/c.rego", testMod, 200, ""); err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if err := f.v1("GET", "/policies/a/b/c.rego", testMod, 200, ""); err != nil { + t.Fatalf("Unexpected error: %v", err) + } +} + func TestQueryV1(t *testing.T) { f := newFixture(t) get := newReqV1("GET", `/query?q=a=[1,2,3]%3Ba[i]=x`, "") diff --git a/server/types/types.go b/server/types/types.go index e90c57edfc..8b45b3af4d 100644 --- a/server/types/types.go +++ b/server/types/types.go @@ -277,6 +277,14 @@ const ( // ParamInputV1 defines the name of the HTTP URL parameter that specifies // values for the "input" document. ParamInputV1 = "input" + + // ParamSourceV1 defines the name of the HTTP URL parameter that indicates + // the client wants to receive the raw (uncompiled) version of the module. + ParamSourceV1 = "source" + + // ParamPrettyV1 defines the name of the HTTP URL parameter that indicates + // the client wants to receive a pretty-printed version of the response. + ParamPrettyV1 = "pretty" ) // WriteConflictErr represents an error condition raised if the caller attempts diff --git a/site/_scripts/rest-examples/example1.rego b/site/_scripts/rest-examples/example1.rego index 27fe056791..5fdbfc5297 100644 --- a/site/_scripts/rest-examples/example1.rego +++ b/site/_scripts/rest-examples/example1.rego @@ -4,8 +4,9 @@ import data.servers import data.networks import data.ports -public_servers[server] :- - server = servers[_], - server.ports[_] = ports[k].id, - ports[k].networks[_] = networks[m].id, +public_servers[server] { + server = servers[_] + server.ports[_] = ports[k].id + ports[k].networks[_] = networks[m].id networks[m].public = true +} diff --git a/site/_scripts/rest-examples/example2.rego b/site/_scripts/rest-examples/example2.rego index 17ceba2ac7..a3c86be531 100644 --- a/site/_scripts/rest-examples/example2.rego +++ b/site/_scripts/rest-examples/example2.rego @@ -2,7 +2,8 @@ package opa.examples import data.servers -violations[server] :- - server = servers[_], - server.protocols[_] = "http", +violations[server] { + server = servers[_] + server.protocols[_] = "http" public_servers[server] +} diff --git a/site/_scripts/rest-examples/example3.rego b/site/_scripts/rest-examples/example3.rego index ae1fc0a482..5600b0b06d 100644 --- a/site/_scripts/rest-examples/example3.rego +++ b/site/_scripts/rest-examples/example3.rego @@ -2,4 +2,4 @@ package opa.examples import input.example.flag -allow_request :- flag = true +allow_request { flag = true } diff --git a/site/_scripts/rest-examples/example4.rego b/site/_scripts/rest-examples/example4.rego index 2ba681d66f..5454bdfe3e 100644 --- a/site/_scripts/rest-examples/example4.rego +++ b/site/_scripts/rest-examples/example4.rego @@ -2,8 +2,10 @@ package opa.examples import input.container -allow_container :- +allow_container { not seccomp_unconfined +} -seccomp_unconfined :- +seccomp_unconfined { container.HostConfig.SecurityOpt[_] = "seccomp:unconfined" +} diff --git a/site/_scripts/rest-examples/gen-examples.sh b/site/_scripts/rest-examples/gen-examples.sh index ec9cac2fc2..a0580f1671 100755 --- a/site/_scripts/rest-examples/gen-examples.sh +++ b/site/_scripts/rest-examples/gen-examples.sh @@ -15,7 +15,7 @@ function main() { # execute each of the examples from the reference list_policies get_a_policy - get_a_raw_policy + get_a_policy_source create_or_update_a_policy delete_a_policy get_a_document @@ -40,10 +40,10 @@ function get_a_policy() { echo "" } -function get_a_raw_policy() { - echo "### Get a Raw Policy" +function get_a_policy_source() { + echo "#### Example Request For Source" echo "" - curl $BASE_URL/policies/example1/raw -s -v + curl $BASE_URL/policies/example1?source=true -s -v echo "" } diff --git a/site/documentation/references/deployments/index.md b/site/documentation/references/deployments/index.md index cb9e29181b..5c9ce2d0d3 100644 --- a/site/documentation/references/deployments/index.md +++ b/site/documentation/references/deployments/index.md @@ -64,9 +64,10 @@ curl -i localhost:8181/ #### Logging OPA focuses on providing debugging support through well-defined APIs and -whenever possible, OPA returns detailed, structured error messages to clients. -By default, OPA only emits log messages in response to critical internal events. -When OPA is healthy, the log stream is quiet. +detailed error messages in response to client requests. + +Log verbosity can be increased to provide debugging information about API +requests and responses. The `--v=N` flag controls log verbosity: @@ -78,7 +79,7 @@ The `--v=N` flag controls log verbosity: With response logging enabled, API response metadata is logged: ``` -I0308 20:20:16.107720 1 logging.go:43] 172.17.0.1:57000 GET /v1/policies 200 18 0.821549ms +I0311 18:54:17.110689 1 logging.go:60] rid=1: 172.17.0.1:60944 PUT /v1/policies/test 200 3683 4.601334ms ``` These log messages include: diff --git a/site/documentation/references/rest/index.md b/site/documentation/references/rest/index.md index 018e94a099..2aeadb8efd 100644 --- a/site/documentation/references/rest/index.md +++ b/site/documentation/references/rest/index.md @@ -597,29 +597,13 @@ Content-Type: application/json } ``` -#### Status Codes - -- **200** - no error -- **404** - not found -- **500** - server error - -### Get a Raw Policy - -``` -GET /v1/policies//raw -``` - -Get a raw policy module. - -Returns the raw policy module content that was sent by the client when the policy was created or last updated. - -#### Example Request +#### Example Request For Source ```http -GET /v1/policies/example1/raw HTTP/1.1 +GET /v1/policies/example1?source=true HTTP/1.1 ``` -#### Example Response +#### Example Response For Source ```http HTTP/1.1 200 OK @@ -641,6 +625,11 @@ public_servers[server] { } ``` +#### Query Parameters + +- **source** - If `true` the response will contain the raw source instead of + the AST. + #### Status Codes - **200** - no error