feat: 开发阶段开放局域网访问,移除网络隔离与访问约束
- compose: 去掉 control 网络的 internal 标记(internal 网络上 Docker 会静默丢弃端口发布,导致局域网无法访问) - docker-gateway: 浏览器网络改为普通 bridge,移除 internal+禁 ICC - AGENTS.md: 新增开发阶段原则(仅业务功能、不做访问限制、安全自管),删除输入信任/风控/敏感日志约束 - docs/README: 同步移除 localhost-only 与网络隔离声明 - 端口改为 8082(8080 被 multica-backend-1 占用)
This commit is contained in:
@@ -33,7 +33,6 @@ const (
|
||||
networkRoleLabel = "io.creatorhub.network-role"
|
||||
browserNetworkRole = "browser"
|
||||
controlNetworkName = "creatorhub_control"
|
||||
bridgeICCOption = "com.docker.network.bridge.enable_icc"
|
||||
namePrefix = "creatorhub-browser-"
|
||||
)
|
||||
|
||||
@@ -166,7 +165,7 @@ func run(command *cobra.Command, cfg serviceConfig) error {
|
||||
baseURL: "http://docker/v1.43",
|
||||
client: &http.Client{Transport: transport, Timeout: 30 * time.Second},
|
||||
}
|
||||
if err := docker.ensureInternalNetwork(cfg.network); err != nil {
|
||||
if err := docker.ensureBrowserNetwork(cfg.network); err != nil {
|
||||
return err
|
||||
}
|
||||
logrus.WithFields(logrus.Fields{
|
||||
@@ -416,7 +415,7 @@ func (docker dockerClient) expect(method, path string, payload any, allowed ...i
|
||||
return fmt.Errorf("Docker returned %s: %s", response.Status, strings.TrimSpace(string(message)))
|
||||
}
|
||||
|
||||
func (docker dockerClient) ensureInternalNetwork(name string) error {
|
||||
func (docker dockerClient) ensureBrowserNetwork(name string) error {
|
||||
if err := validateBrowserNetwork(name); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -432,16 +431,14 @@ func (docker dockerClient) ensureInternalNetwork(name string) error {
|
||||
Internal bool `json:"Internal"`
|
||||
Attachable bool `json:"Attachable"`
|
||||
Ingress bool `json:"Ingress"`
|
||||
Options map[string]string `json:"Options"`
|
||||
Labels map[string]string `json:"Labels"`
|
||||
}
|
||||
if err := json.NewDecoder(response.Body).Decode(&network); err != nil {
|
||||
return fmt.Errorf("decode browser network: %w", err)
|
||||
}
|
||||
if network.Name != name || network.Driver != "bridge" || !network.Internal || network.Attachable || network.Ingress ||
|
||||
network.Labels[managedLabel] != "true" || network.Labels[networkRoleLabel] != browserNetworkRole ||
|
||||
len(network.Options) != 1 || network.Options[bridgeICCOption] != "false" {
|
||||
return fmt.Errorf("browser network %q is not an isolated CreatorHub bridge", name)
|
||||
if network.Name != name || network.Driver != "bridge" || network.Internal || network.Attachable || network.Ingress ||
|
||||
network.Labels[managedLabel] != "true" || network.Labels[networkRoleLabel] != browserNetworkRole {
|
||||
return fmt.Errorf("browser network %q is not a CreatorHub bridge", name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -452,8 +449,6 @@ func (docker dockerClient) ensureInternalNetwork(name string) error {
|
||||
"Name": name,
|
||||
"CheckDuplicate": true,
|
||||
"Driver": "bridge",
|
||||
"Internal": true,
|
||||
"Options": map[string]string{bridgeICCOption: "false"},
|
||||
"Labels": map[string]string{
|
||||
managedLabel: "true",
|
||||
networkRoleLabel: browserNetworkRole,
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/gofiber/fiber/v3/middleware/adaptor"
|
||||
@@ -236,13 +230,11 @@ func TestGatewayRefusesUnmanagedContainer(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureInternalNetwork(t *testing.T) {
|
||||
func TestEnsureBrowserNetwork(t *testing.T) {
|
||||
var created struct {
|
||||
Name string `json:"Name"`
|
||||
Driver string `json:"Driver"`
|
||||
Internal bool `json:"Internal"`
|
||||
Options map[string]string `json:"Options"`
|
||||
Labels map[string]string `json:"Labels"`
|
||||
Name string `json:"Name"`
|
||||
Driver string `json:"Driver"`
|
||||
Labels map[string]string `json:"Labels"`
|
||||
}
|
||||
docker := httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
||||
switch request.Method {
|
||||
@@ -258,24 +250,22 @@ func TestEnsureInternalNetwork(t *testing.T) {
|
||||
defer docker.Close()
|
||||
|
||||
client := dockerClient{baseURL: docker.URL, client: docker.Client()}
|
||||
if err := client.ensureInternalNetwork("creatorhub_browser"); err != nil {
|
||||
if err := client.ensureBrowserNetwork("creatorhub_browser"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if created.Name != "creatorhub_browser" || created.Driver != "bridge" || !created.Internal ||
|
||||
created.Options[bridgeICCOption] != "false" || len(created.Options) != 1 ||
|
||||
if created.Name != "creatorhub_browser" || created.Driver != "bridge" ||
|
||||
created.Labels[managedLabel] != "true" || created.Labels[networkRoleLabel] != browserNetworkRole {
|
||||
t.Fatalf("network is not isolated: %#v", created)
|
||||
t.Fatalf("network is not a CreatorHub bridge: %#v", created)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureInternalNetworkRejectsUnsafeExistingNetwork(t *testing.T) {
|
||||
func TestEnsureBrowserNetworkRejectsUnsafeExistingNetwork(t *testing.T) {
|
||||
valid := map[string]any{
|
||||
"Name": "creatorhub_browser",
|
||||
"Driver": "bridge",
|
||||
"Internal": true,
|
||||
"Internal": false,
|
||||
"Attachable": false,
|
||||
"Ingress": false,
|
||||
"Options": map[string]string{bridgeICCOption: "false"},
|
||||
"Labels": map[string]string{
|
||||
managedLabel: "true",
|
||||
networkRoleLabel: browserNetworkRole,
|
||||
@@ -286,14 +276,14 @@ func TestEnsureInternalNetworkRejectsUnsafeExistingNetwork(t *testing.T) {
|
||||
}))
|
||||
defer validDocker.Close()
|
||||
client := dockerClient{baseURL: validDocker.URL, client: validDocker.Client()}
|
||||
if err := client.ensureInternalNetwork("creatorhub_browser"); err != nil {
|
||||
t.Fatalf("expected owned isolated network to be accepted: %v", err)
|
||||
if err := client.ensureBrowserNetwork("creatorhub_browser"); err != nil {
|
||||
t.Fatalf("expected owned bridge network to be accepted: %v", err)
|
||||
}
|
||||
|
||||
tests := map[string]func(map[string]any){
|
||||
"wrong name": func(network map[string]any) { network["Name"] = "other" },
|
||||
"wrong driver": func(network map[string]any) { network["Driver"] = "overlay" },
|
||||
"external": func(network map[string]any) { network["Internal"] = false },
|
||||
"internal": func(network map[string]any) { network["Internal"] = true },
|
||||
"attachable": func(network map[string]any) { network["Attachable"] = true },
|
||||
"ingress": func(network map[string]any) { network["Ingress"] = true },
|
||||
"missing ownership": func(network map[string]any) {
|
||||
@@ -302,11 +292,6 @@ func TestEnsureInternalNetworkRejectsUnsafeExistingNetwork(t *testing.T) {
|
||||
"wrong role": func(network map[string]any) {
|
||||
network["Labels"] = map[string]string{managedLabel: "true", networkRoleLabel: "control"}
|
||||
},
|
||||
"missing ICC": func(network map[string]any) { network["Options"] = map[string]string{} },
|
||||
"enabled ICC": func(network map[string]any) { network["Options"] = map[string]string{bridgeICCOption: "true"} },
|
||||
"extra option": func(network map[string]any) {
|
||||
network["Options"] = map[string]string{bridgeICCOption: "false", "unexpected": "value"}
|
||||
},
|
||||
}
|
||||
|
||||
for name, mutate := range tests {
|
||||
@@ -322,20 +307,20 @@ func TestEnsureInternalNetworkRejectsUnsafeExistingNetwork(t *testing.T) {
|
||||
defer docker.Close()
|
||||
|
||||
client := dockerClient{baseURL: docker.URL, client: docker.Client()}
|
||||
if err := client.ensureInternalNetwork("creatorhub_browser"); err == nil {
|
||||
if err := client.ensureBrowserNetwork("creatorhub_browser"); err == nil {
|
||||
t.Fatal("expected unsafe existing network to be rejected")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureInternalNetworkRejectsControlNetwork(t *testing.T) {
|
||||
func TestEnsureBrowserNetworkRejectsControlNetwork(t *testing.T) {
|
||||
requested := false
|
||||
docker := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { requested = true }))
|
||||
defer docker.Close()
|
||||
|
||||
client := dockerClient{baseURL: docker.URL, client: docker.Client()}
|
||||
if err := client.ensureInternalNetwork(controlNetworkName); err == nil || requested {
|
||||
if err := client.ensureBrowserNetwork(controlNetworkName); err == nil || requested {
|
||||
t.Fatalf("expected control network to be rejected before Docker request, requested=%v err=%v", requested, err)
|
||||
}
|
||||
}
|
||||
@@ -346,46 +331,3 @@ func TestLoadConfigRejectsControlNetwork(t *testing.T) {
|
||||
t.Fatal("expected control network configuration to be rejected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerIntegrationBlocksCrossContainerTraffic(t *testing.T) {
|
||||
if os.Getenv("CREATORHUB_DOCKER_INTEGRATION") != "1" {
|
||||
t.Skip("set CREATORHUB_DOCKER_INTEGRATION=1 to run")
|
||||
}
|
||||
image := "alpine:3.22@sha256:14358309a308569c32bdc37e2e0e9694be33a9d99e68afb0f5ff33cc1f695dce"
|
||||
networkName := fmt.Sprintf("creatorhub_test_%d", time.Now().UnixNano())
|
||||
serverName := networkName + "_server"
|
||||
runDocker := func(args ...string) ([]byte, error) {
|
||||
return exec.Command("docker", args...).CombinedOutput()
|
||||
}
|
||||
if output, err := runDocker("pull", image); err != nil {
|
||||
t.Fatalf("pull integration image: %v: %s", err, output)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if output, err := runDocker("network", "rm", networkName); err != nil {
|
||||
t.Logf("remove test network: %v: %s", err, output)
|
||||
}
|
||||
})
|
||||
t.Cleanup(func() {
|
||||
if output, err := runDocker("rm", "-f", serverName); err != nil {
|
||||
t.Logf("remove test container: %v: %s", err, output)
|
||||
}
|
||||
})
|
||||
|
||||
transport := &http.Transport{DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
|
||||
return (&net.Dialer{}).DialContext(ctx, "unix", "/var/run/docker.sock")
|
||||
}}
|
||||
defer transport.CloseIdleConnections()
|
||||
client := dockerClient{baseURL: "http://docker/v1.43", client: &http.Client{Transport: transport}}
|
||||
if err := client.ensureInternalNetwork(networkName); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if output, err := runDocker("run", "-d", "--name", serverName, "--network", networkName, "--network-alias", "peer", image, "nc", "-lk", "-p", "8080", "-e", "/bin/echo"); err != nil {
|
||||
t.Fatalf("start peer server: %v: %s", err, output)
|
||||
}
|
||||
if output, err := runDocker("exec", serverName, "nc", "-z", "-w", "2", "127.0.0.1", "8080"); err != nil {
|
||||
t.Fatalf("peer server is not ready: %v: %s", err, output)
|
||||
}
|
||||
if output, err := runDocker("run", "--rm", "--network", networkName, image, "nc", "-z", "-w", "2", "peer", "8080"); err == nil {
|
||||
t.Fatalf("cross-container request unexpectedly succeeded: %s", output)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user