Files
wehub-resource-sync e0e362d700
SDK Tests / changes (push) Successful in 2m29s
Real E2E Tests / changes (push) Successful in 2m29s
Deploy Docs Pages / build (push) Has been cancelled
Deploy Docs Pages / deploy (push) Has been cancelled
Real E2E Tests / JavaScript E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Python E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Java E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / C# E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Go E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Real E2E CI (push) Has been cancelled
SDK Tests / SDK CI (push) Has been cancelled
SDK Tests / CLI Tests (push) Has been cancelled
SDK Tests / Python SDK Quality (code-interpreter) (push) Has been cancelled
SDK Tests / Python SDK Quality (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (sandbox) (push) Has been cancelled
SDK Tests / CLI Quality (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Go SDK Quality And Tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:39:33 +08:00

212 lines
5.7 KiB
Go

// Copyright 2026 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"
"io"
"net/http"
"os"
"strconv"
"strings"
"github.com/alibaba/opensandbox/egress/pkg/constants"
"github.com/alibaba/opensandbox/egress/pkg/log"
"github.com/alibaba/opensandbox/egress/pkg/policy"
slogger "github.com/alibaba/opensandbox/internal/logger"
)
const maxPolicyBodyBytes = 1 << 20
func readPolicyRequestBody(r *http.Request) (string, error) {
body, err := io.ReadAll(io.LimitReader(r.Body, maxPolicyBodyBytes))
if err != nil {
return "", err
}
raw := strings.TrimSpace(string(body))
log.Infof("policy API: request body (%s %s): %s", r.Method, r.URL.Path, raw)
return raw, nil
}
func patchMergedPolicy(base *policy.NetworkPolicy, patchRules []policy.EgressRule) (*policy.NetworkPolicy, error) {
if base == nil {
base = policy.DefaultDenyPolicy()
}
baseCopy := *base
baseCopy.Egress = append([]policy.EgressRule(nil), base.Egress...)
merged := mergeEgressRules(baseCopy.Egress, patchRules)
rawMerged, err := json.Marshal(policy.NetworkPolicy{
DefaultAction: baseCopy.DefaultAction,
Egress: merged,
})
if err != nil {
return nil, err
}
return policy.ParsePolicy(string(rawMerged))
}
func mergeEgressRules(base, additions []policy.EgressRule) []policy.EgressRule {
if len(additions) == 0 {
return base
}
out := make([]policy.EgressRule, 0, len(base)+len(additions))
seen := make(map[string]struct{})
// patch rules win on same target; base fills the rest
for _, r := range additions {
key := mergeKey(r)
if _, ok := seen[key]; ok {
continue
}
seen[key] = struct{}{}
out = append(out, r)
}
for _, r := range base {
key := mergeKey(r)
if _, ok := seen[key]; ok {
continue
}
seen[key] = struct{}{}
out = append(out, r)
}
return out
}
// removeRulesByTarget returns a new slice with rules matching targets removed,
// plus the removed rules. Domain targets are matched case-insensitively.
// Targets not found are silently ignored.
func removeRulesByTarget(rules []policy.EgressRule, targets []string) (kept, removed []policy.EgressRule) {
if len(targets) == 0 || len(rules) == 0 {
return rules, nil
}
removeSet := make(map[string]struct{}, len(targets))
for _, t := range targets {
key := strings.ToLower(strings.TrimSpace(t))
if key == "" {
continue
}
removeSet[key] = struct{}{}
}
kept = make([]policy.EgressRule, 0, len(rules))
for _, r := range rules {
if _, ok := removeSet[strings.ToLower(r.Target)]; ok {
removed = append(removed, r)
} else {
kept = append(kept, r)
}
}
return kept, removed
}
// mergeKey: domain targets lowercased for dedupe; IP/CIDR left as-is.
func mergeKey(r policy.EgressRule) string {
if r.Target == "" {
return r.Target
}
return strings.ToLower(r.Target)
}
func maxEgressRulesFromEnv() int {
s := strings.TrimSpace(os.Getenv(constants.EnvMaxEgressRules))
if s == "" {
return constants.DefaultMaxEgressRules
}
n, err := strconv.Atoi(s)
if err != nil || n < 0 {
return constants.DefaultMaxEgressRules
}
if n == 0 {
return 0
}
return n
}
func writeJSON(w http.ResponseWriter, status int, payload any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
_ = json.NewEncoder(w).Encode(payload)
}
func modeFromPolicy(p *policy.NetworkPolicy) string {
if p == nil {
return "deny_all"
}
if p.DefaultAction == policy.ActionAllow && len(p.Egress) == 0 {
return "allow_all"
} else if p.DefaultAction == policy.ActionDeny && len(p.Egress) == 0 {
return "deny_all"
}
return "enforcing"
}
func policyRuleSummary(p *policy.NetworkPolicy) []map[string]string {
if p == nil {
return nil
}
return egressRulesSummary(p.Egress)
}
func egressRulesSummary(egress []policy.EgressRule) []map[string]string {
out := make([]map[string]string, 0, len(egress))
for _, r := range egress {
out = append(out, map[string]string{
"action": r.Action,
"target": r.Target,
})
}
return out
}
func logEgressLoaded(pol *policy.NetworkPolicy) {
if pol == nil {
pol = policy.DefaultDenyPolicy()
}
fields := []slogger.Field{
{Key: "opensandbox.event", Value: "egress.loaded"},
{Key: "egress.default", Value: pol.DefaultAction},
{Key: "rules", Value: policyRuleSummary(pol)},
}
log.Logger.With(fields...).Infof("egress policy loaded")
}
// logEgressUpdated: egress.updated event. rules is only the delta for this request (PATCH: patch list;
// POST/PUT: full body egress; reset: empty), defaultAction is the policy after apply.
func logEgressUpdated(defaultAction string, deltaEgress []policy.EgressRule) {
fields := []slogger.Field{
{Key: "opensandbox.event", Value: "egress.updated"},
{Key: "egress.default", Value: defaultAction},
{Key: "rules", Value: egressRulesSummary(deltaEgress)},
}
log.Logger.With(fields...).Infof("egress policy updated")
}
func logEgressUpdateFailedWarn(msg string) {
fields := []slogger.Field{
{Key: "opensandbox.event", Value: "egress.update_failed"},
{Key: "error", Value: msg},
}
log.Logger.With(fields...).Warnf("egress policy update failed")
}
func logEgressUpdateFailedError(msg string) {
fields := []slogger.Field{
{Key: "opensandbox.event", Value: "egress.update_failed"},
{Key: "error", Value: msg},
}
log.Logger.With(fields...).Errorf("egress policy update failed")
}