Files
higress/plugins/wasm-go/extensions/opa/main.go

135 lines
3.9 KiB
Go

// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"strings"
"time"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
"github.com/tidwall/gjson"
)
func main() {
wrapper.SetCtx(
"opa",
wrapper.ParseConfigBy(parseConfig),
wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders),
wrapper.ProcessRequestBodyBy(onHttpRequestBody),
)
}
type Metadata struct {
Input map[string]interface{} `json:"input"`
}
func parseConfig(json gjson.Result, config *OpaConfig, log wrapper.Log) error {
policy := json.Get("policy").String()
if strings.TrimSpace(policy) == "" {
return errors.New("policy not allow empty")
}
timeout := json.Get("timeout").String()
if strings.TrimSpace(timeout) == "" {
return errors.New("timeout not allow empty")
}
duration, err := time.ParseDuration(timeout)
if err != nil {
return errors.New("timeout parse fail: " + err.Error())
}
var uint32Duration uint32
if duration.Milliseconds() > int64(^uint32(0)) {
} else {
uint32Duration = uint32(duration.Milliseconds())
}
config.timeout = uint32Duration
client, err := Client(json)
if err != nil {
return err
}
config.client = client
config.policy = policy
return nil
}
func onHttpRequestHeaders(ctx wrapper.HttpContext, config OpaConfig, log wrapper.Log) types.Action {
return opaCall(ctx, config, nil, log)
}
func onHttpRequestBody(ctx wrapper.HttpContext, config OpaConfig, body []byte, log wrapper.Log) types.Action {
return opaCall(ctx, config, body, log)
}
func opaCall(ctx wrapper.HttpContext, config OpaConfig, body []byte, log wrapper.Log) types.Action {
request := make(map[string]interface{}, 6)
headers, _ := proxywasm.GetHttpRequestHeaders()
request["method"] = ctx.Method()
request["scheme"] = ctx.Scheme()
request["path"] = ctx.Path()
request["headers"] = headers
if len(body) != 0 {
request["body"] = body
}
parse, _ := url.Parse(ctx.Path())
query, _ := url.ParseQuery(parse.RawQuery)
request["query"] = query
data, _ := json.Marshal(Metadata{Input: map[string]interface{}{"request": request}})
if err := config.client.Post(fmt.Sprintf("/v1/data/%s/allow", config.policy),
[][2]string{{"Content-Type", "application/json"}},
data, rspCall, config.timeout); err != nil {
log.Errorf("client opa fail %v", err)
return types.ActionPause
}
return types.ActionPause
}
func rspCall(statusCode int, _ http.Header, responseBody []byte) {
if statusCode != http.StatusOK {
proxywasm.SendHttpResponseWithDetail(uint32(statusCode), "opa.status_ne_200", nil, []byte("opa state not is 200"), -1)
return
}
var rsp map[string]interface{}
if err := json.Unmarshal(responseBody, &rsp); err != nil {
proxywasm.SendHttpResponseWithDetail(http.StatusInternalServerError, "opa.bad_response_body", nil, []byte(fmt.Sprintf("opa parse rsp fail %+v", err)), -1)
return
}
result, ok := rsp["result"].(bool)
if !ok {
proxywasm.SendHttpResponseWithDetail(http.StatusInternalServerError, "opa.conversion_fail", nil, []byte("rsp type conversion fail"), -1)
return
}
if !result {
proxywasm.SendHttpResponseWithDetail(http.StatusUnauthorized, "opa.server_not_allowed", nil, []byte("opa server not allowed"), -1)
return
}
proxywasm.ResumeHttpRequest()
}