summaryrefslogtreecommitdiffhomepage
path: root/ipn/localapi/syspolicy_api.go
diff options
context:
space:
mode:
Diffstat (limited to 'ipn/localapi/syspolicy_api.go')
-rw-r--r--ipn/localapi/syspolicy_api.go67
1 files changed, 67 insertions, 0 deletions
diff --git a/ipn/localapi/syspolicy_api.go b/ipn/localapi/syspolicy_api.go
new file mode 100644
index 000000000..366045de3
--- /dev/null
+++ b/ipn/localapi/syspolicy_api.go
@@ -0,0 +1,67 @@
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build !ts_omit_syspolicy
+
+package localapi
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "tailscale.com/util/syspolicy/rsop"
+ "tailscale.com/util/syspolicy/setting"
+)
+
+func init() {
+ handler["policy/"] = (*Handler).servePolicy
+}
+
+func (h *Handler) servePolicy(w http.ResponseWriter, r *http.Request) {
+ if !h.PermitRead {
+ http.Error(w, "policy access denied", http.StatusForbidden)
+ return
+ }
+
+ suffix, ok := strings.CutPrefix(r.URL.EscapedPath(), "/localapi/v0/policy/")
+ if !ok {
+ http.Error(w, "misconfigured", http.StatusInternalServerError)
+ return
+ }
+
+ var scope setting.PolicyScope
+ if suffix == "" {
+ scope = setting.DefaultScope()
+ } else if err := scope.UnmarshalText([]byte(suffix)); err != nil {
+ http.Error(w, fmt.Sprintf("%q is not a valid scope", suffix), http.StatusBadRequest)
+ return
+ }
+
+ policy, err := rsop.PolicyFor(scope)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ var effectivePolicy *setting.Snapshot
+ switch r.Method {
+ case "GET":
+ effectivePolicy = policy.Get()
+ case "POST":
+ effectivePolicy, err = policy.Reload()
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ default:
+ http.Error(w, "unsupported method", http.StatusMethodNotAllowed)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ e := json.NewEncoder(w)
+ e.SetIndent("", "\t")
+ e.Encode(effectivePolicy)
+}