Files
opensandbox-group--opensandbox/components/egress/credential_vault_handler_test.go
T
wehub-resource-sync e0e362d700
Deploy Docs Pages / build (push) Waiting to run
Deploy Docs Pages / deploy (push) Blocked by required conditions
Real E2E Tests / Real E2E CI (push) Blocked by required conditions
SDK Tests / SDK CI (push) Blocked by required conditions
SDK Tests / changes (push) Successful in 2m29s
Real E2E Tests / changes (push) Successful in 2m29s
Real E2E Tests / Python E2E (docker bridge) (push) Waiting to run
Real E2E Tests / C# E2E (docker bridge) (push) Waiting to run
Real E2E Tests / Go E2E (docker bridge) (push) Waiting to run
Real E2E Tests / Java E2E (docker bridge) (push) Waiting to run
Real E2E Tests / JavaScript E2E (docker bridge) (push) Waiting to run
SDK Tests / Python SDK Tests (sandbox) (push) Waiting to run
SDK Tests / CLI Quality (push) Waiting to run
SDK Tests / CLI Tests (push) Waiting to run
SDK Tests / Python SDK Quality (code-interpreter) (push) Waiting to run
SDK Tests / Python SDK Quality (sandbox) (push) Waiting to run
SDK Tests / Python SDK Tests (code-interpreter) (push) Waiting to run
SDK Tests / JavaScript SDK Quality And Tests (code-interpreter) (push) Waiting to run
SDK Tests / JavaScript SDK Quality And Tests (sandbox) (push) Waiting to run
SDK Tests / Kotlin SDK Quality And Tests (sandbox) (push) Waiting to run
SDK Tests / Kotlin SDK Quality And Tests (code-interpreter) (push) Waiting to run
SDK Tests / C# SDK Quality And Tests (code-interpreter) (push) Waiting to run
SDK Tests / C# SDK Quality And Tests (sandbox) (push) Waiting to run
SDK Tests / Go SDK Quality And Tests (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:39:33 +08:00

308 lines
11 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 (
"context"
"encoding/json"
"io"
"net"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/alibaba/opensandbox/egress/pkg/constants"
"github.com/alibaba/opensandbox/egress/pkg/credentialvault"
"github.com/alibaba/opensandbox/egress/pkg/policy"
"github.com/stretchr/testify/require"
)
func testCredentialVaultPolicy(t *testing.T, raw string) *policy.NetworkPolicy {
t.Helper()
pol, err := policy.ParsePolicy(raw)
require.NoError(t, err)
return pol
}
func testCredentialVaultRequest() credentialvault.CreateRequest {
return credentialvault.CreateRequest{
Credentials: []credentialvault.Credential{
{
Name: "gitlab-token",
Source: json.RawMessage(`{"type":"inline","value":"secret-token"}`),
},
},
Bindings: []credentialvault.Binding{
{
Name: "gitlab-api",
Match: credentialvault.Match{
Hosts: []string{"code.example.com"},
Methods: []string{"GET"},
Paths: []string{"/api/v8/*"},
},
Auth: credentialvault.Auth{
Type: "apiKey",
Name: "PRIVATE-TOKEN",
Credential: "gitlab-token",
},
},
},
}
}
func TestCredentialVaultActiveTCPAlwaysForbidden(t *testing.T) {
store := credentialvault.NewStore(nil, func() bool { return true })
pol := testCredentialVaultPolicy(t, `{"defaultAction":"deny","egress":[{"action":"allow","target":"code.example.com"}]}`)
_, err := store.Create(testCredentialVaultRequest(), pol)
require.NoError(t, err)
srv := &policyServer{
token: "public-egress-token",
credentialVault: store,
}
req := httptest.NewRequest(http.MethodGet, "/credential-vault/_active", nil)
req.RemoteAddr = "127.0.0.1:4321"
req.Header.Set(constants.EgressAuthTokenHeader, "public-egress-token")
w := httptest.NewRecorder()
srv.handleCredentialVaultSubresource(w, req)
require.Equal(t, http.StatusForbidden, w.Result().StatusCode)
require.NotContains(t, w.Body.String(), "secret-token")
}
func TestCredentialVaultActiveUnixSocketReturnsSnapshot(t *testing.T) {
store := credentialvault.NewStore(nil, func() bool { return true })
pol := testCredentialVaultPolicy(t, `{"defaultAction":"deny","egress":[{"action":"allow","target":"code.example.com"}]}`)
_, err := store.Create(testCredentialVaultRequest(), pol)
require.NoError(t, err)
srv := &policyServer{
token: "public-egress-token",
credentialVault: store,
}
tmpDir, err := os.MkdirTemp("/tmp", "opensandbox-active-*")
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, os.RemoveAll(tmpDir))
})
socketPath := filepath.Join(tmpDir, "credential-proxy", "active.sock")
_, cleanup, err := credentialvault.StartActiveSocketServer(srv.handleCredentialVaultActive, socketPath, -1)
require.NoError(t, err)
t.Cleanup(func() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
require.NoError(t, cleanup(ctx))
})
client := &http.Client{
Transport: &http.Transport{
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
var dialer net.Dialer
return dialer.DialContext(ctx, "unix", socketPath)
},
},
}
resp, err := client.Get("http://credential-proxy/credential-vault/_active")
require.NoError(t, err)
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
require.Contains(t, string(body), "secret-token")
require.Contains(t, string(body), "Private-Token")
}
func TestCredentialVaultActiveBindingBlocksEgressPolicyRemoval(t *testing.T) {
initial := testCredentialVaultPolicy(t, `{"defaultAction":"deny","egress":[{"action":"allow","target":"code.example.com"}]}`)
proxy := &stubProxy{updated: initial}
nft := &stubNft{}
store := credentialvault.NewStore(nil, func() bool { return true })
_, err := store.Create(testCredentialVaultRequest(), initial)
require.NoError(t, err)
srv := &policyServer{
proxy: proxy,
nft: nft,
enforcementMode: "dns+nft",
credentialVault: store,
}
req := httptest.NewRequest(http.MethodDelete, "/policy", strings.NewReader(`["code.example.com"]`))
w := httptest.NewRecorder()
srv.handlePolicy(w, req)
require.Equal(t, http.StatusBadRequest, w.Result().StatusCode)
require.Len(t, proxy.updated.Egress, 1)
require.Equal(t, 0, nft.calls)
}
func TestCredentialVaultActiveBindingBlocksPolicyReset(t *testing.T) {
initial := testCredentialVaultPolicy(t, `{"defaultAction":"deny","egress":[{"action":"allow","target":"code.example.com"}]}`)
proxy := &stubProxy{updated: initial}
nft := &stubNft{}
store := credentialvault.NewStore(nil, func() bool { return true })
_, err := store.Create(testCredentialVaultRequest(), initial)
require.NoError(t, err)
srv := &policyServer{
proxy: proxy,
nft: nft,
enforcementMode: "dns+nft",
credentialVault: store,
}
req := httptest.NewRequest(http.MethodPost, "/policy", strings.NewReader(""))
w := httptest.NewRecorder()
srv.handlePolicy(w, req)
require.Equal(t, http.StatusBadRequest, w.Result().StatusCode)
require.Contains(t, w.Body.String(), "credential vault policy validation")
require.Len(t, proxy.updated.Egress, 1)
require.Equal(t, 0, nft.calls)
}
func TestCredentialVaultDeleteRequiresReady(t *testing.T) {
t.Setenv(constants.EnvMitmproxyTransparent, "")
srv := &policyServer{
credentialVault: credentialvault.NewStore(nil, func() bool { return true }),
}
req := httptest.NewRequest(http.MethodDelete, "/credential-vault", nil)
req.RemoteAddr = "127.0.0.1:4321"
w := httptest.NewRecorder()
srv.handleCredentialVault(w, req)
require.Equal(t, http.StatusPreconditionFailed, w.Result().StatusCode)
require.Contains(t, w.Body.String(), "transparent mitmproxy")
}
func TestCredentialVaultWriteRequiresTLSOrLoopback(t *testing.T) {
t.Setenv(constants.EnvMitmproxyTransparent, "true")
t.Setenv(constants.EnvEgressMode, constants.PolicyDnsNft)
initial := testCredentialVaultPolicy(t, `{"defaultAction":"deny","egress":[{"action":"allow","target":"code.example.com"}]}`)
srv := &policyServer{
proxy: &stubProxy{updated: initial},
credentialVault: credentialvault.NewStore(nil, func() bool { return true }),
credentialVaultRequireTLS: true,
}
req := httptest.NewRequest(http.MethodPost, "/credential-vault", strings.NewReader(`{"credentials":[],"bindings":[]}`))
req.RemoteAddr = "198.51.100.10:1234"
w := httptest.NewRecorder()
srv.handleCredentialVault(w, req)
require.Equal(t, http.StatusUpgradeRequired, w.Result().StatusCode)
req = httptest.NewRequest(http.MethodPost, "/credential-vault", strings.NewReader(`{"credentials":[],"bindings":[]}`))
req.RemoteAddr = "127.0.0.1:4321"
w = httptest.NewRecorder()
srv.handleCredentialVault(w, req)
require.Equal(t, http.StatusCreated, w.Result().StatusCode)
req = httptest.NewRequest(http.MethodDelete, "/credential-vault", nil)
req.RemoteAddr = "198.51.100.10:1234"
w = httptest.NewRecorder()
srv.handleCredentialVault(w, req)
require.Equal(t, http.StatusUpgradeRequired, w.Result().StatusCode)
req = httptest.NewRequest(http.MethodDelete, "/credential-vault", nil)
req.RemoteAddr = "127.0.0.1:4321"
w = httptest.NewRecorder()
srv.handleCredentialVault(w, req)
require.Equal(t, http.StatusNoContent, w.Result().StatusCode)
}
func TestCredentialVaultWriteAllowsForwardedProto(t *testing.T) {
t.Setenv(constants.EnvMitmproxyTransparent, "true")
t.Setenv(constants.EnvEgressMode, constants.PolicyDnsNft)
t.Setenv(constants.EnvCredentialVaultTrustedProxyCIDRs, "198.51.100.0/24")
initial := testCredentialVaultPolicy(t, `{"defaultAction":"deny","egress":[{"action":"allow","target":"code.example.com"}]}`)
srv := &policyServer{
proxy: &stubProxy{updated: initial},
credentialVault: credentialvault.NewStore(nil, func() bool { return true }),
credentialVaultRequireTLS: true,
}
req := httptest.NewRequest(http.MethodPost, "/credential-vault", strings.NewReader(`{"credentials":[],"bindings":[]}`))
req.RemoteAddr = "198.51.100.10:1234"
req.Header.Set("X-Forwarded-Proto", "https")
w := httptest.NewRecorder()
srv.handleCredentialVault(w, req)
require.Equal(t, http.StatusCreated, w.Result().StatusCode)
}
func TestCredentialVaultWriteRejectsForwardedProtoFromUntrustedPeer(t *testing.T) {
t.Setenv(constants.EnvMitmproxyTransparent, "true")
t.Setenv(constants.EnvEgressMode, constants.PolicyDnsNft)
t.Setenv(constants.EnvCredentialVaultTrustedProxyCIDRs, "203.0.113.0/24")
initial := testCredentialVaultPolicy(t, `{"defaultAction":"deny","egress":[{"action":"allow","target":"code.example.com"}]}`)
srv := &policyServer{
proxy: &stubProxy{updated: initial},
credentialVault: credentialvault.NewStore(nil, func() bool { return true }),
credentialVaultRequireTLS: true,
}
req := httptest.NewRequest(http.MethodPost, "/credential-vault", strings.NewReader(`{"credentials":[],"bindings":[]}`))
req.RemoteAddr = "198.51.100.10:1234"
req.Header.Set("X-Forwarded-Proto", "https")
w := httptest.NewRecorder()
srv.handleCredentialVault(w, req)
require.Equal(t, http.StatusUpgradeRequired, w.Result().StatusCode)
}
func TestCredentialVaultWriteSkipsTLSCheckByDefault(t *testing.T) {
t.Setenv(constants.EnvMitmproxyTransparent, "true")
t.Setenv(constants.EnvEgressMode, constants.PolicyDnsNft)
initial := testCredentialVaultPolicy(t, `{"defaultAction":"deny","egress":[{"action":"allow","target":"code.example.com"}]}`)
srv := &policyServer{
proxy: &stubProxy{updated: initial},
credentialVault: credentialvault.NewStore(nil, func() bool { return true }),
}
req := httptest.NewRequest(http.MethodPost, "/credential-vault", strings.NewReader(`{"credentials":[],"bindings":[]}`))
req.RemoteAddr = "198.51.100.10:1234"
w := httptest.NewRecorder()
srv.handleCredentialVault(w, req)
require.Equal(t, http.StatusCreated, w.Result().StatusCode)
}
func TestCredentialVaultWriteRejectsDNSOnlyEnforcement(t *testing.T) {
t.Setenv(constants.EnvMitmproxyTransparent, "true")
t.Setenv(constants.EnvEgressMode, constants.PolicyDnsOnly)
initial := testCredentialVaultPolicy(t, `{"defaultAction":"deny","egress":[{"action":"allow","target":"code.example.com"}]}`)
srv := &policyServer{
proxy: &stubProxy{updated: initial},
credentialVault: credentialvault.NewStore(nil, func() bool { return true }),
}
req := httptest.NewRequest(http.MethodPost, "/credential-vault", strings.NewReader(`{"credentials":[],"bindings":[]}`))
req.RemoteAddr = "127.0.0.1:4321"
w := httptest.NewRecorder()
srv.handleCredentialVault(w, req)
require.Equal(t, http.StatusPreconditionFailed, w.Result().StatusCode)
require.Contains(t, w.Body.String(), "dns+nft")
}