Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
@@ -0,0 +1,79 @@
package opa
import (
"context"
"io"
"os"
"time"
"github.com/open-policy-agent/opa/rego"
"github.com/open-policy-agent/opa/topdown/print"
"github.com/qsfera/server/pkg/log"
"github.com/qsfera/server/services/policies/pkg/config"
"github.com/qsfera/server/services/policies/pkg/engine"
)
// OPA wraps open policy agent makes it possible to ask if an action is granted.
type OPA struct {
printHook print.Hook
policies []string
timeout time.Duration
options []func(r *rego.Rego)
}
// NewOPA returns a ready to use opa engine.
func NewOPA(timeout time.Duration, logger log.Logger, conf config.Engine) (OPA, error) {
var mtReader io.ReadCloser
if conf.Mimes != "" {
var err error
mtReader, err = os.Open(conf.Mimes)
if err != nil {
return OPA{}, err
}
defer mtReader.Close()
}
rfMimetypeExtensions, err := RFMimetypeExtensions(mtReader)
if err != nil {
return OPA{}, err
}
return OPA{
policies: conf.Policies,
timeout: timeout,
printHook: logPrinter{logger: logger},
options: []func(r *rego.Rego){
RFMimetypeDetect,
RFResourceDownload,
rfMimetypeExtensions,
},
}, nil
}
// Evaluate evaluates the opa policies and returns the result.
func (o OPA) Evaluate(ctx context.Context, qs string, env engine.Environment) (bool, error) {
ctx, cancel := context.WithTimeout(ctx, o.timeout)
defer cancel()
q, err := rego.New(
append([]func(r *rego.Rego){
rego.Query(qs),
rego.Load(o.policies, nil),
rego.EnablePrintStatements(true),
rego.PrintHook(o.printHook),
}, o.options...)...,
).PrepareForEval(ctx)
if err != nil {
return false, err
}
result, err := q.Eval(ctx, rego.EvalInput(env))
if err != nil {
return false, err
}
return result.Allowed(), nil
}
@@ -0,0 +1,16 @@
package opa
import (
"github.com/open-policy-agent/opa/topdown/print"
"github.com/qsfera/server/pkg/log"
)
type logPrinter struct {
logger log.Logger
}
func (lp logPrinter) Print(_ print.Context, msg string) error {
lp.logger.Info().Msg(msg)
return nil
}
@@ -0,0 +1,13 @@
package opa_test
import (
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
func TestOpa(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Opa Suite")
}
@@ -0,0 +1,97 @@
package opa
import (
"bufio"
"io"
"mime"
"strings"
"github.com/gabriel-vasile/mimetype"
"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/rego"
"github.com/open-policy-agent/opa/types"
)
// RFMimetypeExtensions extends the rego dictionary with the possibility of mapping mimetypes to file extensions.
// Be careful calling this multiple times with individual readers, the mime store is global,
// which results in one global store which holds all known mimetype mappings at once.
//
// Rego: `qsfera.mimetype.extensions("application/pdf")`
// Result `[.pdf]`
func RFMimetypeExtensions(f io.Reader) (func(*rego.Rego), error) {
if f != nil {
scanner := bufio.NewScanner(f)
for scanner.Scan() {
fields := strings.Fields(scanner.Text())
if len(fields) <= 1 || fields[0][0] == '#' {
continue
}
mimeType := fields[0]
for _, ext := range fields[1:] {
if ext[0] == '#' {
break
}
if err := mime.AddExtensionType("."+ext, mimeType); err != nil {
return nil, err
}
}
}
if err := scanner.Err(); err != nil {
return nil, err
}
}
return rego.Function1(
&rego.Function{
Name: "qsfera.mimetype.extensions",
Decl: types.NewFunction(types.Args(types.S), types.A),
Memoize: true,
Nondeterministic: true,
},
func(_ rego.BuiltinContext, a *ast.Term) (*ast.Term, error) {
var mt string
if err := ast.As(a.Value, &mt); err != nil {
return nil, err
}
detectedExtensions, err := mime.ExtensionsByType(mt)
if err != nil {
return nil, err
}
var mimeTerms []*ast.Term
for _, extension := range detectedExtensions {
mimeTerms = append(mimeTerms, ast.NewTerm(ast.String(extension)))
}
return ast.ArrayTerm(mimeTerms...), nil
},
), nil
}
// RFMimetypeDetect extends the rego dictionary with the possibility to detect mimetypes.
// Be careful, the list of known mimetypes is limited.
//
// Rego: `qsfera.mimetype.extensions(".txt")`
// Result `text/plain`
var RFMimetypeDetect = rego.Function1(
&rego.Function{
Name: "qsfera.mimetype.detect",
Decl: types.NewFunction(types.Args(types.A), types.S),
Memoize: true,
Nondeterministic: true,
},
func(_ rego.BuiltinContext, a *ast.Term) (*ast.Term, error) {
var body []byte
if err := ast.As(a.Value, &body); err != nil {
return nil, err
}
mimetype := mimetype.Detect(body).String()
return ast.StringTerm(strings.Split(mimetype, ";")[0]), nil
},
)
@@ -0,0 +1,57 @@
package opa_test
import (
"context"
"io"
"strings"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/open-policy-agent/opa/rego"
"github.com/qsfera/server/services/policies/pkg/engine/opa"
)
var _ = Describe("opa qsfera mimetype functions", func() {
Describe("qsfera.mimetype.detect", func() {
It("detects the mimetype", func() {
r := rego.New(rego.Query(`qsfera.mimetype.detect("")`), opa.RFMimetypeDetect)
rs, err := r.Eval(context.Background())
Expect(err).ToNot(HaveOccurred())
Expect(rs[0].Expressions[0].String()).To(Equal("text/plain"))
})
})
Describe("qsfera.mimetype.extensions", func() {
DescribeTable("resolves extensions by mimetype",
func(mimetype string, expectations []string, f io.Reader) {
rfMimetypeExtensions, err := opa.RFMimetypeExtensions(f)
Expect(err).ToNot(HaveOccurred())
r := rego.New(rego.Query(`qsfera.mimetype.extensions("`+mimetype+`")`), rfMimetypeExtensions)
rs, err := r.Eval(context.Background())
Expect(err).ToNot(HaveOccurred())
got := rs[0].Expressions[0].String()
if len(expectations) == 0 {
Expect(got).To(Equal("[]"))
}
for i, expectation := range expectations {
if i+1 != len(expectations) {
expectation += " "
}
Expect(string(got[0])).To(Equal("["))
Expect(strings.Contains(got, expectation)).To(BeTrue())
Expect(string(got[len(got)-1])).To(Equal("]"))
}
},
Entry("With default mimetype", "application/pdf", []string{".pdf"}, nil),
Entry("With unknown mimetype", "qsfera/with.custom.mt", []string{}, nil),
Entry("With custom mimetype", "qsfera/with.custom.mt", []string{".with.custom.mt"}, strings.NewReader("qsfera/with.custom.mt with.custom.mt")),
Entry("With multiple custom mimetypes", "qsfera/with.multiple.custom.mt", []string{".with.multiple.custom.1.mt", ".with.multiple.custom.2.mt"}, strings.NewReader("qsfera/with.multiple.custom.mt with.multiple.custom.1.mt with.multiple.custom.2.mt")),
Entry("With custom ignored mimetype", "qsfera/with.multiple.custom.ignored.mt", []string{}, strings.NewReader("#qsfera/with.multiple.custom.ignored.mt with.multiple.custom.ignored.mt")),
)
})
})
@@ -0,0 +1,60 @@
package opa
import (
"bytes"
"fmt"
"net/http"
"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/rego"
"github.com/open-policy-agent/opa/types"
"github.com/opencloud-eu/reva/v2/pkg/rhttp"
)
// RFResourceDownload extends the rego dictionary with the possibility to download qsfera resources.
//
// Rego: `qsfera.resource.download("qsfera/path/0034892347349827")`
// Result: bytes
var RFResourceDownload = rego.Function1(
&rego.Function{
Name: "qsfera.resource.download",
Decl: types.NewFunction(types.Args(types.S), types.A),
Memoize: true,
Nondeterministic: true,
},
func(_ rego.BuiltinContext, a *ast.Term) (*ast.Term, error) {
var url string
if err := ast.As(a.Value, &url); err != nil {
return nil, err
}
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err
}
client := rhttp.GetHTTPClient(rhttp.Insecure(true))
res, err := client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status code from Download %v", res.StatusCode)
}
buf := new(bytes.Buffer)
if _, err := buf.ReadFrom(res.Body); err != nil {
return nil, err
}
v, err := ast.InterfaceToValue(buf.Bytes())
if err != nil {
return nil, err
}
return ast.NewTerm(v), nil
},
)
@@ -0,0 +1,36 @@
package opa_test
import (
"context"
"encoding/base64"
"net/http"
"net/http/httptest"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/open-policy-agent/opa/rego"
"github.com/qsfera/server/services/policies/pkg/engine/opa"
)
var _ = Describe("opa qsfera resource functions", func() {
Describe("qsfera.resource.download", func() {
It("downloads reva resources", func() {
ts := []byte("Lorem Ipsum is simply dummy text of the printing and typesetting")
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write(ts)
}))
defer srv.Close()
r := rego.New(rego.Query(`qsfera.resource.download("`+srv.URL+`")`), opa.RFResourceDownload)
rs, err := r.Eval(context.Background())
Expect(err).ToNot(HaveOccurred())
data, err := base64.StdEncoding.DecodeString(rs[0].Expressions[0].String())
Expect(err).ToNot(HaveOccurred())
Expect(data).To(Equal(ts))
})
})
})