Skip to content

Commit b8f1829

Browse files
authored
Update Network Permissions At Runtime (#1528)
<!-- This is an auto-generated description by cubic. --> ## Summary by cubic Add a runtime API to update a sandbox’s network permissions without restart. Supports blocking all egress or setting an allowlist; exposed ports remain accessible. - **New Features** - PodService: SandboxUpdateNetworkPermissions (gRPC + HTTP POST /pods/{containerId}/network/update). - Worker: ContainerSandboxUpdateNetworkPermissions applies new iptables/IPv6 rules in place. - Python SDK: sandbox.update_network_permissions(block_network, allow_list). - Updated OpenAPI, protos, gateway, and container client. <sup>Written for commit 8ab7e1f. Summary will update automatically on new commits.</sup> <!-- End of auto-generated description by cubic. -->
1 parent 555800c commit b8f1829

14 files changed

Lines changed: 1492 additions & 657 deletions

File tree

docs/openapi/pod.swagger.json

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,44 @@
535535
]
536536
}
537537
},
538+
"/pods/{containerId}/network/update": {
539+
"post": {
540+
"operationId": "PodService_SandboxUpdateNetworkPermissions",
541+
"responses": {
542+
"200": {
543+
"description": "A successful response.",
544+
"schema": {
545+
"$ref": "#/definitions/podPodSandboxUpdateNetworkPermissionsResponse"
546+
}
547+
},
548+
"default": {
549+
"description": "An unexpected error response.",
550+
"schema": {
551+
"$ref": "#/definitions/rpcStatus"
552+
}
553+
}
554+
},
555+
"parameters": [
556+
{
557+
"name": "containerId",
558+
"in": "path",
559+
"required": true,
560+
"type": "string"
561+
},
562+
{
563+
"name": "body",
564+
"in": "body",
565+
"required": true,
566+
"schema": {
567+
"$ref": "#/definitions/PodServiceSandboxUpdateNetworkPermissionsBody"
568+
}
569+
}
570+
],
571+
"tags": [
572+
"PodService"
573+
]
574+
}
575+
},
538576
"/pods/{containerId}/ports/expose": {
539577
"post": {
540578
"operationId": "PodService_SandboxExposePort",
@@ -916,6 +954,23 @@
916954
}
917955
}
918956
},
957+
"PodServiceSandboxUpdateNetworkPermissionsBody": {
958+
"type": "object",
959+
"properties": {
960+
"stubId": {
961+
"type": "string"
962+
},
963+
"blockNetwork": {
964+
"type": "boolean"
965+
},
966+
"allowList": {
967+
"type": "array",
968+
"items": {
969+
"type": "string"
970+
}
971+
}
972+
}
973+
},
919974
"PodServiceSandboxUpdateTTLBody": {
920975
"type": "object",
921976
"properties": {
@@ -1277,6 +1332,17 @@
12771332
}
12781333
}
12791334
},
1335+
"podPodSandboxUpdateNetworkPermissionsResponse": {
1336+
"type": "object",
1337+
"properties": {
1338+
"ok": {
1339+
"type": "boolean"
1340+
},
1341+
"errorMsg": {
1342+
"type": "string"
1343+
}
1344+
}
1345+
},
12801346
"podPodSandboxUpdateTTLResponse": {
12811347
"type": "object",
12821348
"properties": {

pkg/abstractions/pod/pod.proto

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,13 @@ service PodService {
101101
body : "*"
102102
};
103103
}
104+
rpc SandboxUpdateNetworkPermissions(PodSandboxUpdateNetworkPermissionsRequest)
105+
returns (PodSandboxUpdateNetworkPermissionsResponse) {
106+
option (google.api.http) = {
107+
post : "/pods/{container_id}/network/update"
108+
body : "*"
109+
};
110+
}
104111
rpc SandboxReplaceInFiles(PodSandboxReplaceInFilesRequest)
105112
returns (PodSandboxReplaceInFilesResponse) {
106113
option (google.api.http) = {
@@ -333,6 +340,18 @@ message PodSandboxExposePortResponse {
333340
string error_msg = 3;
334341
}
335342

343+
message PodSandboxUpdateNetworkPermissionsRequest {
344+
string container_id = 1;
345+
string stub_id = 2;
346+
bool block_network = 3;
347+
repeated string allow_list = 4;
348+
}
349+
350+
message PodSandboxUpdateNetworkPermissionsResponse {
351+
bool ok = 1;
352+
string error_msg = 2;
353+
}
354+
336355
message PodSandboxFindInFilesRequest {
337356
string container_id = 1;
338357
string container_path = 2;

pkg/abstractions/pod/sandbox.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,31 @@ func (s *GenericPodService) SandboxExposePort(ctx context.Context, in *pb.PodSan
346346
}, nil
347347
}
348348

349+
func (s *GenericPodService) SandboxUpdateNetworkPermissions(ctx context.Context, in *pb.PodSandboxUpdateNetworkPermissionsRequest) (*pb.PodSandboxUpdateNetworkPermissionsResponse, error) {
350+
authInfo, _ := auth.AuthInfoFromContext(ctx)
351+
352+
client, _, err := s.getClient(ctx, in.ContainerId, authInfo.Token.Key, authInfo.Workspace.ExternalId)
353+
if err != nil {
354+
return &pb.PodSandboxUpdateNetworkPermissionsResponse{
355+
Ok: false,
356+
ErrorMsg: "Failed to connect to sandbox",
357+
}, nil
358+
}
359+
360+
resp, err := client.SandboxUpdateNetworkPermissions(in.ContainerId, in.BlockNetwork, in.AllowList)
361+
if err != nil {
362+
return &pb.PodSandboxUpdateNetworkPermissionsResponse{
363+
Ok: false,
364+
ErrorMsg: "Failed to update network permissions",
365+
}, nil
366+
}
367+
368+
return &pb.PodSandboxUpdateNetworkPermissionsResponse{
369+
Ok: resp.Ok,
370+
ErrorMsg: resp.ErrorMsg,
371+
}, nil
372+
}
373+
349374
func (s *GenericPodService) SandboxStatFile(ctx context.Context, in *pb.PodSandboxStatFileRequest) (*pb.PodSandboxStatFileResponse, error) {
350375
authInfo, _ := auth.AuthInfoFromContext(ctx)
351376

pkg/common/container_client.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,18 @@ func (c *ContainerClient) SandboxExposePort(containerId string, port int32) (*pb
233233
return resp, nil
234234
}
235235

236+
func (c *ContainerClient) SandboxUpdateNetworkPermissions(containerId string, blockNetwork bool, allowList []string) (*pb.ContainerSandboxUpdateNetworkPermissionsResponse, error) {
237+
resp, err := c.client.ContainerSandboxUpdateNetworkPermissions(context.TODO(), &pb.ContainerSandboxUpdateNetworkPermissionsRequest{
238+
ContainerId: containerId,
239+
BlockNetwork: blockNetwork,
240+
AllowList: allowList,
241+
})
242+
if err != nil {
243+
return resp, err
244+
}
245+
return resp, nil
246+
}
247+
236248
func (c *ContainerClient) Kill(containerId string) (*pb.ContainerKillResponse, error) {
237249
resp, err := c.client.ContainerKill(context.TODO(), &pb.ContainerKillRequest{ContainerId: containerId})
238250
if err != nil {

pkg/worker/container_server.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1022,6 +1022,35 @@ func (s *ContainerRuntimeServer) ContainerSandboxExposePort(ctx context.Context,
10221022
return &pb.ContainerSandboxExposePortResponse{Ok: setAddressMapResponse.Ok}, err
10231023
}
10241024

1025+
func (s *ContainerRuntimeServer) ContainerSandboxUpdateNetworkPermissions(ctx context.Context, in *pb.ContainerSandboxUpdateNetworkPermissionsRequest) (*pb.ContainerSandboxUpdateNetworkPermissionsResponse, error) {
1026+
_, exists := s.containerInstances.Get(in.ContainerId)
1027+
if !exists {
1028+
return &pb.ContainerSandboxUpdateNetworkPermissionsResponse{Ok: false, ErrorMsg: "Container not found"}, nil
1029+
}
1030+
1031+
err := s.waitForContainer(ctx, in.ContainerId)
1032+
if err != nil {
1033+
return &pb.ContainerSandboxUpdateNetworkPermissionsResponse{Ok: false, ErrorMsg: err.Error()}, nil
1034+
}
1035+
1036+
// Create a container request with the new network permissions
1037+
request := &types.ContainerRequest{
1038+
BlockNetwork: in.BlockNetwork,
1039+
AllowList: in.AllowList,
1040+
}
1041+
1042+
// Update network permissions via the network manager
1043+
err = s.containerNetworkManager.UpdateNetworkPermissions(in.ContainerId, request)
1044+
if err != nil {
1045+
log.Error().Str("container_id", in.ContainerId).Msgf("failed to update network permissions: %v", err)
1046+
return &pb.ContainerSandboxUpdateNetworkPermissionsResponse{Ok: false, ErrorMsg: err.Error()}, nil
1047+
}
1048+
1049+
log.Info().Str("container_id", in.ContainerId).Msgf("updated network permissions: block_network=%v, allow_list=%v", in.BlockNetwork, in.AllowList)
1050+
1051+
return &pb.ContainerSandboxUpdateNetworkPermissionsResponse{Ok: true}, nil
1052+
}
1053+
10251054
func (s *ContainerRuntimeServer) ContainerSandboxReplaceInFiles(ctx context.Context, in *pb.ContainerSandboxReplaceInFilesRequest) (*pb.ContainerSandboxReplaceInFilesResponse, error) {
10261055
instance, exists := s.containerInstances.Get(in.ContainerId)
10271056
if !exists {

pkg/worker/network.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -806,6 +806,75 @@ func (m *ContainerNetworkManager) ExposePort(containerId string, hostPort, conta
806806
return nil
807807
}
808808

809+
func (m *ContainerNetworkManager) UpdateNetworkPermissions(containerId string, request *types.ContainerRequest) error {
810+
m.mu.Lock()
811+
defer m.mu.Unlock()
812+
813+
info, err := getContainerNetworkInfo(m.ctx, m.workerRepoClient, m.networkPrefix, containerId, m.ipt6 != nil)
814+
if err != nil {
815+
return err
816+
}
817+
818+
// Remove existing restriction rules (search by comment tag)
819+
if err := m.removeNetworkRestrictionRules(info.ContainerIp, m.ipt); err != nil {
820+
return err
821+
}
822+
823+
if m.ipt6 != nil && info.ContainerIpv6 != "" {
824+
if err := m.removeNetworkRestrictionRules(info.ContainerIpv6, m.ipt6); err != nil {
825+
return err
826+
}
827+
}
828+
829+
// Apply new rules
830+
if len(request.AllowList) > 0 {
831+
if err := m.setupAllowList(containerId, request, request.AllowList); err != nil {
832+
return err
833+
}
834+
} else if request.BlockNetwork {
835+
if err := m.setupBlockNetwork(containerId, request); err != nil {
836+
return err
837+
}
838+
}
839+
840+
return nil
841+
}
842+
843+
func (m *ContainerNetworkManager) removeNetworkRestrictionRules(ip string, ipt *iptables.IPTables) error {
844+
// Similar to removeIPTablesRules but only remove DROP/ACCEPT rules
845+
// Preserve DNAT rules (exposed ports) and other infrastructure rules
846+
tables := []string{"filter"}
847+
for _, table := range tables {
848+
chains := []string{"PREROUTING", "FORWARD"}
849+
850+
for _, chain := range chains {
851+
// List rules in the chain
852+
rules, err := ipt.List(table, chain)
853+
if err != nil {
854+
continue
855+
}
856+
857+
for _, rule := range rules {
858+
if strings.Contains(rule, ip) {
859+
if strings.Contains(rule, "DROP") || strings.Contains(rule, "ACCEPT") {
860+
parts := strings.Fields(rule)
861+
862+
// Remove any double quotes
863+
for i, part := range parts {
864+
parts[i] = strings.ReplaceAll(part, `"`, "")
865+
}
866+
867+
if err := ipt.Delete(table, chain, parts[2:]...); err != nil {
868+
return err
869+
}
870+
}
871+
}
872+
}
873+
}
874+
}
875+
return nil
876+
}
877+
809878
// getRandomFreePort chooses a random free port
810879
func getRandomFreePort() (int, error) {
811880
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")

pkg/worker/worker.proto

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ service ContainerService {
4242
returns (ContainerSandboxDeleteDirectoryResponse) {}
4343
rpc ContainerSandboxExposePort(ContainerSandboxExposePortRequest)
4444
returns (ContainerSandboxExposePortResponse) {}
45+
rpc ContainerSandboxUpdateNetworkPermissions(ContainerSandboxUpdateNetworkPermissionsRequest)
46+
returns (ContainerSandboxUpdateNetworkPermissionsResponse) {}
4547
rpc ContainerSandboxReplaceInFiles(ContainerSandboxReplaceInFilesRequest)
4648
returns (ContainerSandboxReplaceInFilesResponse) {}
4749
rpc ContainerSandboxFindInFiles(ContainerSandboxFindInFilesRequest)
@@ -278,3 +280,14 @@ message ContainerSandboxListProcessesResponse {
278280
string error_msg = 2;
279281
repeated types.ProcessInfo processes = 3;
280282
}
283+
284+
message ContainerSandboxUpdateNetworkPermissionsRequest {
285+
string container_id = 1;
286+
bool block_network = 2;
287+
repeated string allow_list = 3;
288+
}
289+
290+
message ContainerSandboxUpdateNetworkPermissionsResponse {
291+
bool ok = 1;
292+
string error_msg = 2;
293+
}

0 commit comments

Comments
 (0)