From a74f4cf4d058ae3240ca02e41b53118c07cf8715 Mon Sep 17 00:00:00 2001 From: Manuel Bluhm Date: Fri, 8 Nov 2024 19:40:51 +0400 Subject: [PATCH] Add dbus proxy - update flake - add dbus proxy module, using xdg-dbus-proxy - add socket proxy as agent module - add dbus test across various setups - re-work submodules and parameter parsing - smaller fixes Signed-off-by: Manuel Bluhm --- api/socket/socket.pb.go | 136 +++++ api/socket/socket.proto | 14 + api/socket/socket_grpc.pb.go | 144 ++++++ flake.lock | 41 +- flake.nix | 1 + go.mod | 2 +- internal/cmd/givc-agent/main.go | 271 ++++++---- internal/pkgs/applications/applications.go | 2 + .../pkgs/applications/applications_test.go | 2 + internal/pkgs/grpc/grpcserver.go | 5 +- internal/pkgs/socketproxy/controller.go | 106 ++++ internal/pkgs/socketproxy/transport.go | 241 +++++++++ internal/pkgs/types/types.go | 83 +-- internal/pkgs/utility/tls.go | 12 +- internal/pkgs/utility/utility.go | 3 +- nixos/modules/appvm.nix | 207 +++----- nixos/modules/dbus.nix | 283 +++++++++++ nixos/modules/definitions.nix | 108 ++++ nixos/modules/host.nix | 140 +---- nixos/modules/sysvm.nix | 210 ++++---- nixos/packages/givc-agent.nix | 4 +- nixos/tests/admin.nix | 41 +- nixos/tests/dbus.nix | 478 ++++++++++++++++++ nixos/tests/default.nix | 3 + nixos/tests/netvm.nix | 26 +- 25 files changed, 1984 insertions(+), 579 deletions(-) create mode 100644 api/socket/socket.pb.go create mode 100644 api/socket/socket.proto create mode 100644 api/socket/socket_grpc.pb.go create mode 100644 internal/pkgs/socketproxy/controller.go create mode 100644 internal/pkgs/socketproxy/transport.go create mode 100644 nixos/modules/dbus.nix create mode 100644 nixos/modules/definitions.nix create mode 100644 nixos/tests/dbus.nix diff --git a/api/socket/socket.pb.go b/api/socket/socket.pb.go new file mode 100644 index 0000000..30f673f --- /dev/null +++ b/api/socket/socket.pb.go @@ -0,0 +1,136 @@ +// Copyright 2024 TII (SSRC) and the Ghaf contributors +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.35.1 +// protoc v5.28.2 +// source: socket.proto + +package socketproxy + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type BytePacket struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Data []byte `protobuf:"bytes,1,opt,name=Data,proto3" json:"Data,omitempty"` +} + +func (x *BytePacket) Reset() { + *x = BytePacket{} + mi := &file_socket_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BytePacket) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BytePacket) ProtoMessage() {} + +func (x *BytePacket) ProtoReflect() protoreflect.Message { + mi := &file_socket_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BytePacket.ProtoReflect.Descriptor instead. +func (*BytePacket) Descriptor() ([]byte, []int) { + return file_socket_proto_rawDescGZIP(), []int{0} +} + +func (x *BytePacket) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +var File_socket_proto protoreflect.FileDescriptor + +var file_socket_proto_rawDesc = []byte{ + 0x0a, 0x0c, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, + 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x22, 0x20, 0x0a, 0x0a, 0x42, + 0x79, 0x74, 0x65, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x44, 0x61, 0x74, + 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x44, 0x61, 0x74, 0x61, 0x32, 0x56, 0x0a, + 0x0c, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x46, 0x0a, + 0x0c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x44, 0x61, 0x74, 0x61, 0x12, 0x17, 0x2e, + 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x42, 0x79, 0x74, 0x65, + 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x1a, 0x17, 0x2e, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x70, + 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x42, 0x79, 0x74, 0x65, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x22, + 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x0f, 0x5a, 0x0d, 0x2e, 0x2f, 0x73, 0x6f, 0x63, 0x6b, 0x65, + 0x74, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_socket_proto_rawDescOnce sync.Once + file_socket_proto_rawDescData = file_socket_proto_rawDesc +) + +func file_socket_proto_rawDescGZIP() []byte { + file_socket_proto_rawDescOnce.Do(func() { + file_socket_proto_rawDescData = protoimpl.X.CompressGZIP(file_socket_proto_rawDescData) + }) + return file_socket_proto_rawDescData +} + +var file_socket_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_socket_proto_goTypes = []any{ + (*BytePacket)(nil), // 0: socketproxy.BytePacket +} +var file_socket_proto_depIdxs = []int32{ + 0, // 0: socketproxy.SocketStream.TransferData:input_type -> socketproxy.BytePacket + 0, // 1: socketproxy.SocketStream.TransferData:output_type -> socketproxy.BytePacket + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_socket_proto_init() } +func file_socket_proto_init() { + if File_socket_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_socket_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_socket_proto_goTypes, + DependencyIndexes: file_socket_proto_depIdxs, + MessageInfos: file_socket_proto_msgTypes, + }.Build() + File_socket_proto = out.File + file_socket_proto_rawDesc = nil + file_socket_proto_goTypes = nil + file_socket_proto_depIdxs = nil +} diff --git a/api/socket/socket.proto b/api/socket/socket.proto new file mode 100644 index 0000000..d7ae8c9 --- /dev/null +++ b/api/socket/socket.proto @@ -0,0 +1,14 @@ +// Copyright 2024 TII (SSRC) and the Ghaf contributors +// SPDX-License-Identifier: Apache-2.0 +syntax = "proto3"; +option go_package = "./socketproxy"; +package socketproxy; + +message BytePacket { + bytes Data = 1; +} + +service SocketStream { + rpc TransferData(stream BytePacket) returns (stream BytePacket) {} +} + diff --git a/api/socket/socket_grpc.pb.go b/api/socket/socket_grpc.pb.go new file mode 100644 index 0000000..b656d3c --- /dev/null +++ b/api/socket/socket_grpc.pb.go @@ -0,0 +1,144 @@ +// Copyright 2024 TII (SSRC) and the Ghaf contributors +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.3.0 +// - protoc v5.28.2 +// source: socket.proto + +package socketproxy + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +const ( + SocketStream_TransferData_FullMethodName = "/socketproxy.SocketStream/TransferData" +) + +// SocketStreamClient is the client API for SocketStream service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type SocketStreamClient interface { + TransferData(ctx context.Context, opts ...grpc.CallOption) (SocketStream_TransferDataClient, error) +} + +type socketStreamClient struct { + cc grpc.ClientConnInterface +} + +func NewSocketStreamClient(cc grpc.ClientConnInterface) SocketStreamClient { + return &socketStreamClient{cc} +} + +func (c *socketStreamClient) TransferData(ctx context.Context, opts ...grpc.CallOption) (SocketStream_TransferDataClient, error) { + stream, err := c.cc.NewStream(ctx, &SocketStream_ServiceDesc.Streams[0], SocketStream_TransferData_FullMethodName, opts...) + if err != nil { + return nil, err + } + x := &socketStreamTransferDataClient{stream} + return x, nil +} + +type SocketStream_TransferDataClient interface { + Send(*BytePacket) error + Recv() (*BytePacket, error) + grpc.ClientStream +} + +type socketStreamTransferDataClient struct { + grpc.ClientStream +} + +func (x *socketStreamTransferDataClient) Send(m *BytePacket) error { + return x.ClientStream.SendMsg(m) +} + +func (x *socketStreamTransferDataClient) Recv() (*BytePacket, error) { + m := new(BytePacket) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// SocketStreamServer is the server API for SocketStream service. +// All implementations must embed UnimplementedSocketStreamServer +// for forward compatibility +type SocketStreamServer interface { + TransferData(SocketStream_TransferDataServer) error + mustEmbedUnimplementedSocketStreamServer() +} + +// UnimplementedSocketStreamServer must be embedded to have forward compatible implementations. +type UnimplementedSocketStreamServer struct { +} + +func (UnimplementedSocketStreamServer) TransferData(SocketStream_TransferDataServer) error { + return status.Errorf(codes.Unimplemented, "method TransferData not implemented") +} +func (UnimplementedSocketStreamServer) mustEmbedUnimplementedSocketStreamServer() {} + +// UnsafeSocketStreamServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to SocketStreamServer will +// result in compilation errors. +type UnsafeSocketStreamServer interface { + mustEmbedUnimplementedSocketStreamServer() +} + +func RegisterSocketStreamServer(s grpc.ServiceRegistrar, srv SocketStreamServer) { + s.RegisterService(&SocketStream_ServiceDesc, srv) +} + +func _SocketStream_TransferData_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(SocketStreamServer).TransferData(&socketStreamTransferDataServer{stream}) +} + +type SocketStream_TransferDataServer interface { + Send(*BytePacket) error + Recv() (*BytePacket, error) + grpc.ServerStream +} + +type socketStreamTransferDataServer struct { + grpc.ServerStream +} + +func (x *socketStreamTransferDataServer) Send(m *BytePacket) error { + return x.ServerStream.SendMsg(m) +} + +func (x *socketStreamTransferDataServer) Recv() (*BytePacket, error) { + m := new(BytePacket) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// SocketStream_ServiceDesc is the grpc.ServiceDesc for SocketStream service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var SocketStream_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "socketproxy.SocketStream", + HandlerType: (*SocketStreamServer)(nil), + Methods: []grpc.MethodDesc{}, + Streams: []grpc.StreamDesc{ + { + StreamName: "TransferData", + Handler: _SocketStream_TransferData_Handler, + ServerStreams: true, + ClientStreams: true, + }, + }, + Metadata: "socket.proto", +} diff --git a/flake.lock b/flake.lock index cdf1692..9fdb5fb 100644 --- a/flake.lock +++ b/flake.lock @@ -1,17 +1,12 @@ { "nodes": { "crane": { - "inputs": { - "nixpkgs": [ - "nixpkgs" - ] - }, "locked": { - "lastModified": 1724537630, - "narHash": "sha256-gpqINM71zp3kw5XYwUXa84ZtPnCmLLnByuFoYesT1bY=", + "lastModified": 1730652660, + "narHash": "sha256-+XVYfmVXAiYA0FZT7ijHf555dxCe+AoAT5A6RU+6vSo=", "owner": "ipetkov", "repo": "crane", - "rev": "3e08f4b1fc9aaede5dd511d8f5f4ef27501e49b0", + "rev": "a4ca93905455c07cb7e3aca95d4faf7601cba458", "type": "github" }, "original": { @@ -27,11 +22,11 @@ ] }, "locked": { - "lastModified": 1722113426, - "narHash": "sha256-Yo/3loq572A8Su6aY5GP56knpuKYRvM2a1meP9oJZCw=", + "lastModified": 1728330715, + "narHash": "sha256-xRJ2nPOXb//u1jaBnDP56M7v5ldavjbtR6lfGqSvcKg=", "owner": "numtide", "repo": "devshell", - "rev": "67cce7359e4cd3c45296fb4aaf6a19e2a9c757ae", + "rev": "dd6b80932022cea34a019e2bb32f6fa9e494dfef", "type": "github" }, "original": { @@ -63,11 +58,11 @@ ] }, "locked": { - "lastModified": 1722555600, - "narHash": "sha256-XOQkdLafnb/p9ij77byFQjDf5m5QYl9b2REiVClC+x4=", + "lastModified": 1730504689, + "narHash": "sha256-hgmguH29K2fvs9szpq2r3pz2/8cJd2LPS+b4tfNFCwE=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "8471fe90ad337a8074e957b69ca4d0089218391d", + "rev": "506278e768c2a08bec68eb62932193e341f55c90", "type": "github" }, "original": { @@ -114,11 +109,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1726243404, - "narHash": "sha256-sjiGsMh+1cWXb53Tecsm4skyFNag33GPbVgCdfj3n9I=", + "lastModified": 1730785428, + "narHash": "sha256-Zwl8YgTVJTEum+L+0zVAWvXAGbWAuXHax3KzuejaDyo=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "345c263f2f53a3710abe117f28a5cb86d0ba4059", + "rev": "4aa36568d413aca0ea84a1684d2d46f55dbabad7", "type": "github" }, "original": { @@ -139,11 +134,11 @@ ] }, "locked": { - "lastModified": 1724763886, - "narHash": "sha256-SzBtZs5z+YGM50oyt67R78qLhxG/wG5/SlVRsCF5kRc=", + "lastModified": 1730814269, + "narHash": "sha256-fWPHyhYE6xvMI1eGY3pwBTq85wcy1YXqdzTZF+06nOg=", "owner": "cachix", "repo": "pre-commit-hooks.nix", - "rev": "1cd12de659fab215624c630c37d1c62aa2b7824e", + "rev": "d70155fdc00df4628446352fc58adc640cd705c2", "type": "github" }, "original": { @@ -170,11 +165,11 @@ ] }, "locked": { - "lastModified": 1724338379, - "narHash": "sha256-kKJtaiU5Ou+e/0Qs7SICXF22DLx4V/WhG1P6+k4yeOE=", + "lastModified": 1730321837, + "narHash": "sha256-vK+a09qq19QNu2MlLcvN4qcRctJbqWkX7ahgPZ/+maI=", "owner": "numtide", "repo": "treefmt-nix", - "rev": "070f834771efa715f3e74cd8ab93ecc96fabc951", + "rev": "746901bb8dba96d154b66492a29f5db0693dbfcc", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index ba1af79..e66ec61 100644 --- a/flake.nix +++ b/flake.nix @@ -84,6 +84,7 @@ host = import ./nixos/modules/host.nix { inherit self; }; sysvm = import ./nixos/modules/sysvm.nix { inherit self; }; appvm = import ./nixos/modules/appvm.nix { inherit self; }; + dbus = import ./nixos/modules/dbus.nix { inherit self; }; }; # Overlays diff --git a/go.mod b/go.mod index 60a9a08..158f8f7 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 github.com/shirou/gopsutil v3.21.11+incompatible github.com/sirupsen/logrus v1.9.3 + golang.org/x/net v0.29.0 golang.org/x/sync v0.8.0 google.golang.org/grpc v1.66.2 google.golang.org/protobuf v1.34.2 @@ -22,7 +23,6 @@ require ( github.com/tklauser/go-sysconf v0.3.14 // indirect github.com/tklauser/numcpus v0.8.0 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect - golang.org/x/net v0.29.0 // indirect golang.org/x/sys v0.25.0 // indirect golang.org/x/text v0.18.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect diff --git a/internal/cmd/givc-agent/main.go b/internal/cmd/givc-agent/main.go index 7cb81e5..56977f8 100644 --- a/internal/cmd/givc-agent/main.go +++ b/internal/cmd/givc-agent/main.go @@ -5,21 +5,24 @@ package main import ( "context" "crypto/tls" + "encoding/json" "os" "path/filepath" "strconv" "strings" + "time" - "givc/api/admin" + givc_admin "givc/api/admin" givc_app "givc/internal/pkgs/applications" givc_grpc "givc/internal/pkgs/grpc" - "givc/internal/pkgs/hwidmanager" - "givc/internal/pkgs/localelistener" - "givc/internal/pkgs/serviceclient" - "givc/internal/pkgs/servicemanager" - "givc/internal/pkgs/types" + givc_hwidmanager "givc/internal/pkgs/hwidmanager" + givc_localelistener "givc/internal/pkgs/localelistener" + givc_serviceclient "givc/internal/pkgs/serviceclient" + givc_servicemanager "givc/internal/pkgs/servicemanager" + givc_socketproxy "givc/internal/pkgs/socketproxy" + givc_types "givc/internal/pkgs/types" givc_util "givc/internal/pkgs/utility" - "givc/internal/pkgs/wifimanager" + givc_wifimanager "givc/internal/pkgs/wifimanager" log "github.com/sirupsen/logrus" ) @@ -27,24 +30,22 @@ import ( func main() { var err error + serverStarted := make(chan struct{}) + log.Infof("Running %s", filepath.Base(os.Args[0])) - name := os.Getenv("NAME") - if name == "" { - log.Fatalf("No 'NAME' environment variable present.") - } - address := os.Getenv("ADDR") - if address == "" { - log.Fatalf("No 'ADDR' environment variable present.") - } - port := os.Getenv("PORT") - if port == "" { - log.Fatalf("No 'PORT' environment variable present.") - } - protocol := os.Getenv("PROTO") - if protocol == "" { - log.Fatalf("No 'PROTO' environment variable present.") + // Parse fundamental parameters + var agent givc_types.TransportConfig + jsonTransportConfigString, transportConfigPresent := os.LookupEnv("AGENT") + if jsonTransportConfigString != "" && transportConfigPresent { + err := json.Unmarshal([]byte(jsonTransportConfigString), &agent) + if err != nil { + log.Fatalf("Error parsing application manifests: %s", err) + } + } else { + log.Fatalf("No 'AGENT' environment variable present.") } + debug := os.Getenv("DEBUG") if debug != "true" { log.SetLevel(log.WarnLevel) @@ -61,13 +62,15 @@ func main() { log.Fatalf("No or wrong 'SUBTYPE' environment variable present.") } + // Configure system services/units to be administrated by this agent var services []string servicesString, servicesPresent := os.LookupEnv("SERVICES") if servicesPresent { services = strings.Split(servicesString, " ") } - var applications []types.ApplicationManifest + // Configure applications to be administrated by this agent + var applications []givc_types.ApplicationManifest jsonApplicationString, appPresent := os.LookupEnv("APPLICATIONS") if appPresent && jsonApplicationString != "" { applications, err = givc_app.ParseApplicationManifests(jsonApplicationString) @@ -76,22 +79,19 @@ func main() { } } - adminServerName := os.Getenv("ADMIN_SERVER_NAME") - if adminServerName == "" { - log.Fatalf("A name for the admin server is required in environment variable $ADMIN_SERVER_NAME.") - } - adminServerAddr := os.Getenv("ADMIN_SERVER_ADDR") - if adminServerAddr == "" { - log.Fatalf("An address for the admin server is required in environment variable $ADMIN_SERVER_ADDR.") - } - adminServerPort := os.Getenv("ADMIN_SERVER_PORT") - if adminServerPort == "" { - log.Fatalf("An port address for the admin server is required in environment variable $ADMIN_SERVER_PORT.") - } - adminServerProtocol := os.Getenv("ADMIN_SERVER_PROTO") - if adminServerProtocol == "" { - log.Fatalf("An address for the admin server is required in environment variable $ADMIN_SERVER_PROTO.") + // Set admin server parameters + var admin givc_types.TransportConfig + jsonAdminServerString, adminPresent := os.LookupEnv("ADMIN_SERVER") + if jsonAdminServerString != "" && adminPresent { + err := json.Unmarshal([]byte(jsonAdminServerString), &admin) + if err != nil { + log.Fatalf("Error parsing admin server transport values: %s", err) + } + } else { + log.Fatalf("No 'ADMIN_SERVER' environment variable present.") } + + // Configure optional services wifiEnabled := false wifiService, wifiOption := os.LookupEnv("WIFI") if wifiOption && (wifiService != "false") { @@ -107,100 +107,104 @@ func main() { } hwidEnabled = true } - var tlsConfig *tls.Config - if os.Getenv("TLS") != "false" { - cacert := os.Getenv("CA_CERT") - if cacert == "" { - log.Fatalf("No 'CA_CERT' environment variable present. To turn off TLS set 'TLS' to 'false'.") - } - cert := os.Getenv("HOST_CERT") - if cert == "" { - log.Fatalf("No 'HOST_CERT' environment variable present. To turn off TLS set 'TLS' to 'false'.") + + localeListenerEnabled := false + localeListenerService, localeListenerOption := os.LookupEnv("LOCALE_LISTENER") + if localeListenerOption && (localeListenerService != "false") { + localeListenerEnabled = true + } + + var proxyConfigs []givc_types.ProxyConfig + jsonDbusproxyString, socketProxyOption := os.LookupEnv("SOCKET_PROXY") + if socketProxyOption && jsonDbusproxyString != "" { + err = json.Unmarshal([]byte(jsonDbusproxyString), &proxyConfigs) + if err != nil { + log.Fatalf("error unmarshalling JSON string: %v", err) } - key := os.Getenv("HOST_KEY") - if key == "" { - log.Fatalf("No 'HOST_KEY' environment variable present. To turn off TLS set 'TLS' to 'false'.") + } + + // Configure TLS + var tlsConfigJson givc_types.TlsConfigJson + jsonTlsConfigString, tlsConfigOption := os.LookupEnv("TLS_CONFIG") + if tlsConfigOption && jsonTlsConfigString != "" { + err = json.Unmarshal([]byte(jsonTlsConfigString), &tlsConfigJson) + if err != nil { + log.Fatalf("error unmarshalling JSON string: %v", err) } - // @TODO add path and file checks - tlsConfig = givc_util.TlsServerConfig(cacert, cert, key, true) } - // @TODO add path and file checks - - cfgAdminServer := &types.EndpointConfig{ - Transport: types.TransportConfig{ - Name: adminServerName, - Address: adminServerAddr, - Port: adminServerPort, - Protocol: adminServerProtocol, - }, + + var tlsConfig *tls.Config + if tlsConfigJson.Enable { + tlsConfig = givc_util.TlsServerConfig(tlsConfigJson.CaCertPath, tlsConfigJson.CertPath, tlsConfigJson.KeyPath, true) + } + + // Set admin server configurations + cfgAdminServer := &givc_types.EndpointConfig{ + Transport: admin, TlsConfig: tlsConfig, } // Set agent configurations - cfgAgent := &types.EndpointConfig{ - Transport: types.TransportConfig{ - Name: name, - Address: address, - Port: port, - Protocol: protocol, - }, + cfgAgent := &givc_types.EndpointConfig{ + Transport: agent, TlsConfig: tlsConfig, } - // Add services - agentServiceName := "givc-" + name + ".service" + // Create registeration entry + agentServiceName := "givc-" + agent.Name + ".service" cfgAgent.Services = append(cfgAgent.Services, agentServiceName) if servicesPresent { cfgAgent.Services = append(cfgAgent.Services, services...) } log.Infof("Started with services: %v\n", cfgAgent.Services) - agentEntryRequest := &admin.RegistryRequest{ + agentEntryRequest := &givc_admin.RegistryRequest{ Name: agentServiceName, Type: uint32(agentType), Parent: parentName, - Transport: &admin.TransportConfig{ + Transport: &givc_admin.TransportConfig{ Protocol: cfgAgent.Transport.Protocol, Address: cfgAgent.Transport.Address, Port: cfgAgent.Transport.Port, Name: cfgAgent.Transport.Name, }, - State: &admin.UnitStatus{ + State: &givc_admin.UnitStatus{ Name: agentServiceName, }, } // Register this instance - serverStarted := make(chan struct{}) go func() { // Wait for server to start <-serverStarted - // Register agent - _, err := serviceclient.RegisterRemoteService(cfgAdminServer, agentEntryRequest) - if err != nil { - log.Fatalf("Error register agent: %s", err) + // Register agent with admin server + _, err := givc_serviceclient.RegisterRemoteService(cfgAdminServer, agentEntryRequest) + for err != nil { + log.Warnf("Error register agent: %s", err) + time.Sleep(1 * time.Second) + _, err = givc_serviceclient.RegisterRemoteService(cfgAdminServer, agentEntryRequest) } - // Register services + // Register services with admin server for _, service := range services { if strings.Contains(service, ".service") { - serviceEntryRequest := &admin.RegistryRequest{ + serviceEntryRequest := &givc_admin.RegistryRequest{ Name: service, Parent: agentServiceName, Type: uint32(agentSubType), - Transport: &admin.TransportConfig{ + Transport: &givc_admin.TransportConfig{ Name: cfgAgent.Transport.Name, Protocol: cfgAgent.Transport.Protocol, Address: cfgAgent.Transport.Address, Port: cfgAgent.Transport.Port, }, - State: &admin.UnitStatus{ + State: &givc_admin.UnitStatus{ Name: service, }, } log.Infof("Trying to register service: %s", service) - _, err := serviceclient.RegisterRemoteService(cfgAdminServer, serviceEntryRequest) + _, err := givc_serviceclient.RegisterRemoteService(cfgAdminServer, serviceEntryRequest) if err != nil { log.Warnf("Error registering service: %s", err) } @@ -208,49 +212,112 @@ func main() { } }() - // Create and resgister gRPC services - var grpcServices []types.GrpcServiceRegistration + // Create and register gRPC services + var grpcServices []givc_types.GrpcServiceRegistration // Create systemd control server - systemdControlServer, err := servicemanager.NewSystemdControlServer(cfgAgent.Services, applications) + systemdControlServer, err := givc_servicemanager.NewSystemdControlServer(cfgAgent.Services, applications) if err != nil { - log.Fatalf("Cannot create systemd control server") + log.Fatalf("Cannot create systemd control server: %v", err) } grpcServices = append(grpcServices, systemdControlServer) - localeClientServer, err := localelistener.NewLocaleServer() - if err != nil { - log.Fatalf("Cannot create locale listener server") + if localeListenerEnabled { + // Create locale listener server + localeClientServer, err := givc_localelistener.NewLocaleServer() + if err != nil { + log.Fatalf("Cannot create locale listener server: %v", err) + } + grpcServices = append(grpcServices, localeClientServer) } - grpcServices = append(grpcServices, localeClientServer) + // Create wifi control server (optional) if wifiEnabled { // Create wifi control server - wifiControlServer, err := wifimanager.NewWifiControlServer() + wifiControlServer, err := givc_wifimanager.NewWifiControlServer() if err != nil { - log.Fatalf("Cannot create wifi control server") + log.Fatalf("Cannot create wifi control server: %v", err) } grpcServices = append(grpcServices, wifiControlServer) } + // Create hwid server (optional) if hwidEnabled { - hwidServer, err := hwidmanager.NewHwIdServer(hwidIface) + hwidServer, err := givc_hwidmanager.NewHwIdServer(hwidIface) if err != nil { - log.Fatalf("Cannot create hwid server") + log.Fatalf("Cannot create hwid server: %v", err) } grpcServices = append(grpcServices, hwidServer) } - // Create grpc server + // Create socket proxy server (optional) + for _, proxyConfig := range proxyConfigs { + + // Create socket proxy server for dbus + socketProxyServer, err := givc_socketproxy.NewSocketProxyServer(proxyConfig.Socket, proxyConfig.Server) + if err != nil { + log.Errorf("Cannot create socket proxy server: %v", err) + } + + // Run proxy client + if !proxyConfig.Server { + log.Infof("Configuring socket proxy client: %v", proxyConfig) + + go func(proxyConfig givc_types.ProxyConfig) { + + // Configure client endpoint + socketClient := &givc_types.EndpointConfig{ + Transport: proxyConfig.Transport, + TlsConfig: tlsConfig, + } + + err = socketProxyServer.StreamToRemote(context.Background(), socketClient) + if err != nil { + log.Errorf("Socket client stream exited: %v", err) + } + + }(proxyConfig) + } + + // Run proxy server + if proxyConfig.Server { + log.Infof("Configuring socket proxy server: %v", proxyConfig) + + go func(proxyConfig givc_types.ProxyConfig) { + + // Socket proxy server config + cfgProxyServer := &givc_types.EndpointConfig{ + Transport: givc_types.TransportConfig{ + Name: cfgAgent.Transport.Name, + Address: cfgAgent.Transport.Address, + Port: proxyConfig.Transport.Port, + Protocol: proxyConfig.Transport.Protocol, + }, + TlsConfig: tlsConfig, + } + + var grpcProxyService []givc_types.GrpcServiceRegistration + grpcProxyService = append(grpcProxyService, socketProxyServer) + grpcServer, err := givc_grpc.NewServer(cfgProxyServer, grpcProxyService) + if err != nil { + log.Errorf("Cannot create grpc proxy server config: %v", err) + } + err = grpcServer.ListenAndServe(context.Background(), make(chan struct{})) + if err != nil { + log.Errorf("Grpc socket proxy server failed: %v", err) + } + + }(proxyConfig) + } + } + + // Create and start main grpc server grpcServer, err := givc_grpc.NewServer(cfgAgent, grpcServices) if err != nil { - log.Fatalf("Cannot create grpc server config") + log.Fatalf("Cannot create grpc server config: %v", err) } - - // Start server - ctx := context.Background() - err = grpcServer.ListenAndServe(ctx, serverStarted) + err = grpcServer.ListenAndServe(context.Background(), serverStarted) if err != nil { - log.Fatalf("Grpc server failed: %s", err) + log.Fatalf("Grpc server failed: %v", err) } } diff --git a/internal/pkgs/applications/applications.go b/internal/pkgs/applications/applications.go index 9ee05ea..13b49a2 100644 --- a/internal/pkgs/applications/applications.go +++ b/internal/pkgs/applications/applications.go @@ -1,3 +1,5 @@ +// Copyright 2024 TII (SSRC) and the Ghaf contributors +// SPDX-License-Identifier: Apache-2.0 package applications import ( diff --git a/internal/pkgs/applications/applications_test.go b/internal/pkgs/applications/applications_test.go index f3bffcc..d94d3a0 100644 --- a/internal/pkgs/applications/applications_test.go +++ b/internal/pkgs/applications/applications_test.go @@ -1,3 +1,5 @@ +// Copyright 2024 TII (SSRC) and the Ghaf contributors +// SPDX-License-Identifier: Apache-2.0 package applications import ( diff --git a/internal/pkgs/grpc/grpcserver.go b/internal/pkgs/grpc/grpcserver.go index 9c01f3c..ad63631 100644 --- a/internal/pkgs/grpc/grpcserver.go +++ b/internal/pkgs/grpc/grpcserver.go @@ -101,11 +101,14 @@ func (s *GrpcServer) ListenAndServe(ctx context.Context, started chan struct{}) listener, err = net.Listen(s.config.Transport.Protocol, addr) if err != nil { time.Sleep(LISTENER_WAIT_TIME) - log.WithFields(log.Fields{"addr": addr}).Info("Error binding address for GRPC server, retrying...") + log.WithFields(log.Fields{"addr": addr, "err": err}).Info("Error binding address for GRPC server, retrying...") continue } break } + if listener == nil { + return fmt.Errorf("unable to bind address for GRPC server: %s", addr) + } defer listener.Close() group, ctx := errgroup.WithContext(ctx) diff --git a/internal/pkgs/socketproxy/controller.go b/internal/pkgs/socketproxy/controller.go new file mode 100644 index 0000000..de0144c --- /dev/null +++ b/internal/pkgs/socketproxy/controller.go @@ -0,0 +1,106 @@ +// Copyright 2024 TII (SSRC) and the Ghaf contributors +// SPDX-License-Identifier: Apache-2.0 +package socketproxy + +import ( + "fmt" + "net" + "os" + "time" + + log "github.com/sirupsen/logrus" +) + +type SocketProxyController struct { + runAsServer bool + socket string + listener net.Listener +} + +func NewSocketProxyController(socket string, runAsServer bool) (*SocketProxyController, error) { + + var listener net.Listener + var err error + if !runAsServer { + // Remove socket file if it exists + os.Remove(socket) + + // Listen on unix socket + listener, err = net.Listen("unix", socket) + if err != nil { + log.Errorf("Unable to listen on unix socket: %v", err) + return nil, err + } + } + + // Block until the socket is created + _, err = os.Stat(socket) + for err != nil { + time.Sleep(500 * time.Millisecond) + _, err = os.Stat(socket) + } + + // Change socket owner and permissions to allow any users in group 'users' (gid: 100) + err = os.Chown(socket, -1, 100) + if err != nil { + log.Errorf("Unable to change socket file ownership: %v", err) + } + err = os.Chmod(socket, 0770) + if err != nil { + log.Errorf("Unable to change socket file permissions: %v", err) + } + + return &SocketProxyController{socket: socket, runAsServer: runAsServer, listener: listener}, nil +} + +func (s *SocketProxyController) Dial() (net.Conn, error) { + + // Dial to the unix socket + conn, err := net.Dial("unix", s.socket) + if err != nil { + log.Errorf("unable to dial unix socket: %v", err) + return nil, err + } + return conn, nil +} + +func (s *SocketProxyController) Accept() (net.Conn, error) { + + // Accept new connection + conn, err := s.listener.Accept() + if err != nil { + log.Errorf("unable to accept socket connection: %v", err) + return nil, err + } + return conn, nil +} + +func (s *SocketProxyController) Close() error { + if s.listener != nil { + err := s.listener.Close() + if err != nil { + return err + } + } + return nil +} + +func (s *SocketProxyController) Write(conn net.Conn, data []byte) error { + n, err := conn.Write(data) + if err != nil { + return err + } + if n != len(data) { + return fmt.Errorf("unable to write all data to socket") + } + return nil +} + +func (s *SocketProxyController) Read(conn net.Conn) ([]byte, error) { + buf := make([]byte, 1024) + n, err := conn.Read(buf) + if err != nil { + return nil, err + } + return buf[:n], nil +} diff --git a/internal/pkgs/socketproxy/transport.go b/internal/pkgs/socketproxy/transport.go new file mode 100644 index 0000000..833098f --- /dev/null +++ b/internal/pkgs/socketproxy/transport.go @@ -0,0 +1,241 @@ +// Copyright 2024 TII (SSRC) and the Ghaf contributors +// SPDX-License-Identifier: Apache-2.0 + +/* + SocketProxyServer is a GRPC service that proxies data between a local socket and a remote GRPC server. + + It can run in two modes, server or client: + + 1. As a server: The server waits for a remote client to connect. + Once connected, it dials to a local socket and proxies data between the remote client and the local socket. + + 2. As a client: The client creates a socket listener and waits for a connection to the local socket. + Once a client (application) connects to the socket, it initiates the connection to the server. + + Both client and server run a GRPC server, and the main routine 'StreamToRemote' which must be run in a different go routine + than the GRPC server itself. + + As the socket read() function is blocking, the end of a socket connection must be forwarded to the remote, and locally + closed (here, by closing the socket connection) on both ends. This is done by sending an EOF message to the remote counterpart. + + Both ends unwind and close both socket and GRPC connections when any socket read error occurs, EOF or otherwise. +*/ + +package socketproxy + +import ( + "bytes" + "fmt" + socket_api "givc/api/socket" + "io" + "net" + "time" + + givc_grpc "givc/internal/pkgs/grpc" + givc_types "givc/internal/pkgs/types" + + "golang.org/x/net/context" + "golang.org/x/sync/errgroup" + "google.golang.org/grpc" + + log "github.com/sirupsen/logrus" +) + +type SocketProxyServer struct { + socketController *SocketProxyController + socket_api.UnimplementedSocketStreamServer +} + +type DataStream interface { + Recv() (*socket_api.BytePacket, error) + Send(*socket_api.BytePacket) error + Context() context.Context +} + +func (s *SocketProxyServer) Name() string { + return "Socket Proxy" +} + +func (s *SocketProxyServer) RegisterGrpcService(srv *grpc.Server) { + socket_api.RegisterSocketStreamServer(srv, s) +} + +func NewSocketProxyServer(socket string, runAsServer bool) (*SocketProxyServer, error) { + + // Create a new socket proxy controller + var err error + socketController, err := NewSocketProxyController(socket, runAsServer) + if err != nil { + return nil, err + } + + return &SocketProxyServer{ + socketController: socketController, + }, nil +} + +func (s *SocketProxyServer) Close() error { + return s.socketController.Close() +} + +func (s *SocketProxyServer) StreamToRemote(ctx context.Context, cfg *givc_types.EndpointConfig) error { + + defer s.socketController.Close() + + // Setup and dial GRPC client + grpcClientConn, err := givc_grpc.NewClient(cfg, true) + if err != nil { + return err + } + defer grpcClientConn.Close() + + // Create streaming client + socketStreamClient := socket_api.NewSocketStreamClient(grpcClientConn) + if socketStreamClient == nil { + return fmt.Errorf("failed to create 'NewSocketStreamClient'") + } + + for { + select { + // Return if context is done + case <-ctx.Done(): + return nil + + // Stream data to remote + default: + + // Wait for new connection to socket; blocking + conn, err := s.socketController.Accept() + if err != nil { + return err + } + + // Handle connection + go func(c net.Conn) { + + // Create new GRPC stream + stream, err := socketStreamClient.TransferData(ctx) + for err != nil { + time.Sleep(1 * time.Second) + stream, err = socketStreamClient.TransferData(ctx) + } + + // Stream data between socket and GRPC + err = s.StreamData(stream, c) + if err != nil { + log.Warnf("StreamData exited with: %v", err) + } + + // Close stream connection + if stream != nil { + stream.CloseSend() + } + + }(conn) + } + } + +} + +func (s *SocketProxyServer) TransferData(stream socket_api.SocketStream_TransferDataServer) error { + + if !s.socketController.runAsServer { + return fmt.Errorf("socket proxy runs as client") + } + + // Dial to the unix socket + var err error + conn, err := s.socketController.Dial() + if err != nil { + return err + } + + return s.StreamData(stream, conn) +} + +func (s *SocketProxyServer) StreamData(stream DataStream, conn net.Conn) error { + + group, ctx := errgroup.WithContext(stream.Context()) + + // Routine to read from grpc stream and write to socket + group.Go(func() error { + + for { + select { + case <-ctx.Done(): + return nil + default: + + // Read data from grpc stream + data, err := stream.Recv() + if err != nil { + log.Warnf(">> GRPC failure: %v", err) + return err + } + log.Infof("Recv data: %s", data.GetData()) + + // Check for EOF; and close socket connection + if bytes.Equal(data.GetData(), []byte(io.EOF.Error())) { + if conn != nil { + conn.Close() + } + return fmt.Errorf("EOF received") + } + + // Write data to socket + err = s.socketController.Write(conn, data.GetData()) + if err != nil { + log.Warnf("Error writing to socket: %v", err) + return err + } + } + } + }) + + // Routine to read from socket and write to grpc stream + group.Go(func() error { + + for { + select { + case <-ctx.Done(): + return nil + default: + // Read data from socket + data, err := s.socketController.Read(conn) + if err != nil { + // Forward any read error to terminate stream and socket connections on both ends + log.Infof(">> Socket read error: %v", err) + message := &socket_api.BytePacket{ + Data: []byte(io.EOF.Error()), + } + err = stream.Send(message) + if err != nil { + log.Errorf("failed to send EOF to remote: %v", err) + } + return err + } + + // Send data to grpc stream + message := &socket_api.BytePacket{ + Data: data, + } + err = stream.Send(message) + if err != nil { + return err + } + log.Infof("Sent data: %s", data) + } + } + }) + + if err := group.Wait(); err != nil { + log.Infof("Stream exited with: %s", err) + } + + // Close socket connection + if conn != nil { + conn.Close() + } + + return nil +} diff --git a/internal/pkgs/types/types.go b/internal/pkgs/types/types.go index 976e57f..56cccb7 100644 --- a/internal/pkgs/types/types.go +++ b/internal/pkgs/types/types.go @@ -10,32 +10,6 @@ import ( type UnitType uint32 -const ( - UNIT_TYPE_HOST_MGR UnitType = 0 - UNIT_TYPE_HOST_SVC UnitType = 1 - UNIT_TYPE_HOST_APP UnitType = 2 - - UNIT_TYPE_ADMVM UnitType = 3 - UNIT_TYPE_ADMVM_MGR UnitType = 4 - UNIT_TYPE_ADMVM_SVC UnitType = 5 - UNIT_TYPE_ADMVM_APP UnitType = 6 - - UNIT_TYPE_SYSVM UnitType = 7 - UNIT_TYPE_SYSVM_MGR UnitType = 8 - UNIT_TYPE_SYSVM_SVC UnitType = 9 - UNIT_TYPE_SYSVM_APP UnitType = 10 - - UNIT_TYPE_APPVM UnitType = 11 - UNIT_TYPE_APPVM_MGR UnitType = 12 - UNIT_TYPE_APPVM_SVC UnitType = 13 - UNIT_TYPE_APPVM_APP UnitType = 14 -) - -const ( - APP_ARG_FLAG = "flag" - APP_ARG_URL = "url" -) - type UnitStatus struct { Name string Description string @@ -47,10 +21,10 @@ type UnitStatus struct { } type TransportConfig struct { - Name string - Address string - Port string - Protocol string + Name string `json:"name"` + Address string `json:"addr"` + Port string `json:"port"` + Protocol string `json:"protocol"` } type EndpointConfig struct { @@ -59,6 +33,13 @@ type EndpointConfig struct { TlsConfig *tls.Config } +type ProxyConfig struct { + Transport TransportConfig + Server bool `json:"server"` + Socket string `json:"socket"` + TlsConfig *tls.Config +} + type RegistryEntry struct { Name string Parent string @@ -69,12 +50,48 @@ type RegistryEntry struct { } type ApplicationManifest struct { - Name string `json:"Name"` - Command string `json:"Command"` - Args []string `json:"Args,omitempty"` + Name string `json:"name"` + Command string `json:"command"` + Args []string `json:"args,omitempty"` +} + +type TlsConfigJson struct { + Enable bool `json:"enable"` + CaCertPath string `json:"caCertPath"` + CertPath string `json:"certPath"` + KeyPath string `json:"keyPath"` } type GrpcServiceRegistration interface { Name() string RegisterGrpcService(*grpc.Server) } + +const ( + UNIT_TYPE_HOST_MGR = iota + UNIT_TYPE_HOST_SVC + UNIT_TYPE_HOST_APP + UNIT_TYPE_ADMVM + UNIT_TYPE_ADMVM_MGR + UNIT_TYPE_ADMVM_SVC + UNIT_TYPE_ADMVM_APP + UNIT_TYPE_SYSVM + UNIT_TYPE_SYSVM_MGR + UNIT_TYPE_SYSVM_SVC + UNIT_TYPE_SYSVM_APP + UNIT_TYPE_APPVM + UNIT_TYPE_APPVM_MGR + UNIT_TYPE_APPVM_SVC + UNIT_TYPE_APPVM_APP +) + +const ( + PROXY_NULL uint32 = 0 + PROXY_SERVER_CONNECTED uint32 = 1 + PROXY_CLIENT_CONNECTED uint32 = 2 +) + +const ( + APP_ARG_FLAG = "flag" + APP_ARG_URL = "url" +) diff --git a/internal/pkgs/utility/tls.go b/internal/pkgs/utility/tls.go index 6344766..4f0c40f 100644 --- a/internal/pkgs/utility/tls.go +++ b/internal/pkgs/utility/tls.go @@ -19,15 +19,15 @@ var ( } ) -func TlsServerConfig(CACertFilePath string, CertFilePath string, KeyFilePath string, mutual bool) *tls.Config { +func TlsServerConfig(cacertFilePath string, certFilePath string, keyFilePath string, mutual bool) *tls.Config { // Load TLS certificates and key - serverTLSCert, err := tls.LoadX509KeyPair(filepath.Clean(CertFilePath), filepath.Clean(KeyFilePath)) + serverTLSCert, err := tls.LoadX509KeyPair(filepath.Clean(certFilePath), filepath.Clean(keyFilePath)) if err != nil { log.Fatalf("[TlsServerConfig] Error loading server certificate and key file: %v", err) } certPool := x509.NewCertPool() - caCertPEM, err := os.ReadFile(filepath.Clean(CACertFilePath)) + caCertPEM, err := os.ReadFile(filepath.Clean(cacertFilePath)) if err != nil { log.Fatalf("[TlsServerConfig] Error loading CA certificate: %v", err) } @@ -56,15 +56,15 @@ func TlsServerConfig(CACertFilePath string, CertFilePath string, KeyFilePath str return tlsConfig } -func TlsClientConfig(CACertFilePath string, CertFilePath string, KeyFilePath string, serverName string) *tls.Config { +func TlsClientConfig(cacertFilePath string, certFilePath string, keyFilePath string, serverName string) *tls.Config { // Load TLS certificates and key - clientTLSCert, err := tls.LoadX509KeyPair(CertFilePath, KeyFilePath) + clientTLSCert, err := tls.LoadX509KeyPair(certFilePath, keyFilePath) if err != nil { log.Fatalf("[TlsClientConfig] Error loading client certificate and key file: %v", err) } certPool := x509.NewCertPool() - caCertPEM, err := os.ReadFile(filepath.Clean(CACertFilePath)) + caCertPEM, err := os.ReadFile(filepath.Clean(cacertFilePath)) if err != nil { log.Fatalf("[TlsClientConfig] Error loading CA certificate: %v", err) } diff --git a/internal/pkgs/utility/utility.go b/internal/pkgs/utility/utility.go index e68638e..dcf41a7 100644 --- a/internal/pkgs/utility/utility.go +++ b/internal/pkgs/utility/utility.go @@ -5,12 +5,13 @@ package utility import ( "fmt" "io" - "log" "net" "os" "os/user" "path/filepath" "strings" + + log "github.com/sirupsen/logrus" ) func IsRoot() bool { diff --git a/nixos/modules/appvm.nix b/nixos/modules/appvm.nix index 6097bf9..994f5da 100644 --- a/nixos/modules/appvm.nix +++ b/nixos/modules/appvm.nix @@ -16,58 +16,26 @@ let mkIf types trivial - attrsets + strings + lists + optionalString + optionals + ; + inherit (builtins) toJSON; + inherit (import ./definitions.nix { inherit config lib; }) + transportSubmodule + applicationSubmodule + proxySubmodule + tlsSubmodule ; - - applicationSubmodule = types.submodule { - options = { - name = mkOption { - description = "Name of the application."; - type = types.str; - }; - command = mkOption { - description = "Command to run the application."; - type = types.str; - }; - args = mkOption { - description = '' - List of allowed argument types for the application. Currently implemented argument types: - - 'url': URL provided to the application as string - - 'flag': Flag (boolean) provided to the application as string - ''; - type = types.listOf types.str; - default = [ ]; - }; - }; - }; - in { options.givc.appvm = { enable = mkEnableOption "Enable givc-appvm module."; - name = mkOption { - description = "Host name (without domain)."; - type = types.str; - default = "localhost"; - }; - - addr = mkOption { - description = "IPv4 address."; - type = types.str; - default = "127.0.0.1"; - }; - - port = mkOption { - description = "Port of the agent service. Defaults to '9000'."; - type = types.str; - default = "9000"; - }; - - protocol = mkOption { - description = "Transport protocol, defaults to 'tcp'."; - type = types.str; - default = "tcp"; + agent = mkOption { + description = "Host configuration"; + type = transportSubmodule; }; debug = mkEnableOption "Enable verbose logs for debugging."; @@ -80,88 +48,34 @@ in default = [ { } ]; example = [ { - Name = "app"; - Command = "/bin/bash"; - Args = [ "url" ]; + name = "app"; + command = "/bin/bash"; + args = [ "url" ]; } ]; }; + socketProxy = mkOption { + description = '' + Optional socket proxy module. If not provided, the module will not use a socket proxy. + ''; + type = types.nullOr (types.listOf proxySubmodule); + default = null; + }; + admin = mkOption { description = "Admin server configuration."; - type = - with types; - submodule { - options = { - enable = mkEnableOption "Admin module"; - name = mkOption { - description = "Hostname of admin server"; - type = types.str; - }; - - addr = mkOption { - description = "Address of admin server"; - type = types.str; - }; - - port = mkOption { - description = "Port of admin server"; - type = types.str; - }; - - protocol = mkOption { - description = "Protocol of admin server"; - type = types.str; - }; - }; - }; - default = { - enable = true; - name = "localhost"; - addr = "127.0.0.1"; - port = "9001"; - protocol = "tcp"; - }; + type = transportSubmodule; }; tls = mkOption { - description = '' - TLS options for gRPC connections. It is enabled by default to discourage unprotected connections, - and requires paths to certificates and key being set. To disable it use 'tls.enable = false;'. - ''; - type = types.submodule { - options = { - enable = mkOption { - description = "Enable TLS. Defaults to 'true'."; - type = types.bool; - default = true; - }; - caCertPath = mkOption { - description = "Path to the CA certificate file."; - type = types.str; - default = ""; - }; - certPath = mkOption { - description = "Path to the service certificate file."; - type = types.str; - default = ""; - }; - keyPath = mkOption { - description = "Path to the service key file."; - type = types.str; - default = ""; - }; - }; - }; - default = { - enable = true; - caCertPath = ""; - certPath = ""; - keyPath = ""; - }; + description = "TLS configuration."; + type = tlsSubmodule; }; }; + imports = [ self.nixosModules.dbus ]; + config = mkIf cfg.enable { assertions = [ { @@ -171,7 +85,20 @@ in { assertion = !(cfg.tls.enable && (cfg.tls.caCertPath == "" || cfg.tls.certPath == "" || cfg.tls.keyPath == "")); - message = "The TLS option requires paths' to CA certificate, service certificate, and service key."; + message = '' + The TLS configuration requires paths' to CA certificate, service certificate, and service key. + To disable TLS, set 'tls.enable = false;'. + ''; + } + { + assertion = + cfg.socketProxy == null + || lists.allUnique (map (p: (strings.toInt p.transport.port)) cfg.socketProxy); + message = "SocketProxy: Each socket proxy instance requires a unique port number."; + } + { + assertion = cfg.socketProxy == null || lists.allUnique (map (p: p.socket) cfg.socketProxy); + message = "SocketProxy: Each socket proxy instance requires a unique socket."; } ]; @@ -188,8 +115,9 @@ in }); ''; }; - systemd.user.services."givc-${cfg.name}" = { - description = "GIVC remote service manager for application VMs."; + + systemd.user.services."givc-${cfg.agent.name}" = { + description = "GIVC remote service manager for application VMs"; enable = true; after = [ "sockets.target" ]; wants = [ "sockets.target" ]; @@ -199,35 +127,26 @@ in ExecStart = "${givc-agent}/bin/givc-agent"; Restart = "always"; RestartSec = 1; - PrivateTmp = true; }; - environment = - { - "NAME" = "${cfg.name}"; - "ADDR" = "${cfg.addr}"; - "PORT" = "${cfg.port}"; - "PROTO" = "${cfg.protocol}"; - "DEBUG" = "${trivial.boolToString cfg.debug}"; - "TYPE" = "12"; - "SUBTYPE" = "13"; - "TLS" = "${trivial.boolToString cfg.tls.enable}"; - "PARENT" = "microvm@${cfg.name}.service"; - "APPLICATIONS" = "${builtins.toJSON cfg.applications}"; - "ADMIN_SERVER_NAME" = "${cfg.admin.name}"; - "ADMIN_SERVER_ADDR" = "${cfg.admin.addr}"; - "ADMIN_SERVER_PORT" = "${cfg.admin.port}"; - "ADMIN_SERVER_PROTO" = "${cfg.admin.protocol}"; - } - // attrsets.optionalAttrs cfg.tls.enable { - "CA_CERT" = "${cfg.tls.caCertPath}"; - "HOST_CERT" = "${cfg.tls.certPath}"; - "HOST_KEY" = "${cfg.tls.keyPath}"; - }; + environment = { + "AGENT" = "${toJSON cfg.agent}"; + "DEBUG" = "${trivial.boolToString cfg.debug}"; + "TYPE" = "12"; + "SUBTYPE" = "13"; + "PARENT" = "microvm@${cfg.agent.name}.service"; + "APPLICATIONS" = "${toJSON cfg.applications}"; + "SOCKET_PROXY" = "${optionalString (cfg.socketProxy != null) (toJSON cfg.socketProxy)}"; + "ADMIN_SERVER" = "${toJSON cfg.admin}"; + "TLS_CONFIG" = "${toJSON cfg.tls}"; + }; }; networking.firewall.allowedTCPPorts = let - port = lib.strings.toInt cfg.port; + agentPort = strings.toInt cfg.agent.port; + proxyPorts = optionals (cfg.socketProxy != null) ( + map (p: (strings.toInt p.transport.port)) cfg.socketProxy + ); in - [ port ]; + [ agentPort ] ++ proxyPorts; }; } diff --git a/nixos/modules/dbus.nix b/nixos/modules/dbus.nix new file mode 100644 index 0000000..ab47a35 --- /dev/null +++ b/nixos/modules/dbus.nix @@ -0,0 +1,283 @@ +# Copyright 2024 TII (SSRC) and the Ghaf contributors +# SPDX-License-Identifier: Apache-2.0 +{ self }: +{ + config, + pkgs, + lib, + ... +}: +let + cfg = config.givc.dbusproxy; + inherit (lib) + mkOption + mkEnableOption + mkIf + types + concatStringsSep + concatMapStringsSep + optionalString + optionalAttrs + ; + + # Dbus policy submodule + policySubmodule = types.submodule { + options = { + see = mkOption { + description = '' + SEE policy: + The name/ID is visible in the ListNames reply + The name/ID is visible in the ListActivatableNames reply + You can call GetNameOwner on the name + You can call NameHasOwner on the name + You see NameOwnerChanged signals on the name + You see NameOwnerChanged signals on the ID when the client disconnects + You can call the GetXXX methods on the name/ID to get e.g. the peer pid + You get AccessDenied rather than NameHasNoOwner when sending messages to the name/ID + ''; + type = types.nullOr (types.listOf types.str); + default = null; + }; + talk = mkOption { + description = '' + TALK policy: + You can send any method calls and signals to the name/ID + You will receive broadcast signals from the name/ID (if you have a match rule for them) + You can call StartServiceByName on the name + ''; + type = types.nullOr (types.listOf types.str); + default = null; + }; + own = mkOption { + description = '' + OWN policy: + You are allowed to call RequestName/ReleaseName/ListQueuedOwners on the name + ''; + type = types.nullOr (types.listOf types.str); + default = null; + }; + + call = mkOption { + description = '' + CALL policy: + You can call the specific methods + + From xdg-dbus-proxy manual: + + The RULE in these options determines what interfaces, methods and object paths are allowed. It must be of the form [METHOD][@PATH], where METHOD + can be either '*' or a D-Bus interface, possible with a '.*' suffix, or a fully-qualified method name, and PATH is a D-Bus object path, possible with a '/*' suffix. + ''; + type = types.nullOr (types.listOf types.str); + default = null; + }; + broadcast = mkOption { + description = '' + BROADCAST policy: + You can receive broadcast signals from the name/ID + + From xdg-dbus-proxy manual: + + The RULE in these options determines what interfaces, methods and object paths are allowed. It must be of the form [METHOD][@PATH], where METHOD + can be either '*' or a D-Bus interface, possible with a '.*' suffix, or a fully-qualified method name, and PATH is a D-Bus object path, possible with a '/*' suffix. + ''; + type = types.nullOr (types.listOf types.str); + default = null; + }; + }; + }; + + # Dbus component submodule + dbusSubmodule = types.submodule { + options = { + enable = mkEnableOption "Enable the dbus component"; + user = mkOption { + description = '' + User to run the xdg-dbus-proxy service as. This option must be set to allow a remote user to connect to the bus. + Defaults to `root`. + ''; + type = types.str; + default = "root"; + }; + socket = mkOption { + description = "Socket path used to connect to the bus. Defaults to `/tmp/.dbusproxy.sock`."; + type = types.str; + default = "/tmp/.dbusproxy.sock"; + }; + policy = mkOption { + description = '' + Policy submodule for the dbus proxy. + + Filtering is applied only to outgoing signals and method calls and incoming broadcast signals. All replies (errors or method returns) are allowed once for an outstanding method call, and never otherwise. + If a client ever receives a message from another peer on the bus, the senders unique name is made visible, so the client can track caller lifetimes via NameOwnerChanged signals. If a client calls a method on + or receives a broadcast signal from a name (even if filtered to some subset of paths or interfaces), that names basic policy is considered to be (at least) TALK, from then on. + ''; + type = policySubmodule; + }; + }; + }; + +in +{ + options.givc.dbusproxy = { + enable = mkEnableOption '' + Enables givc-dbusproxy module. This module is a wrapper for the `xdg-dbus-proxy`, and configures systemd services for + the system and/or session bus. The respective service is enabled if a policy for the bus is set. + + Filtering is enabled by default, and the config requires at least one policy value (see/talk/own) to be set. For more + details, please refer to the xdg-dbus-proxy manual (e.g., https://www.systutorials.com/docs/linux/man/1-xdg-dbus-proxy/). + + Policy values are a list of strings, where each string is translated into the respective argument. Multiple instances + of the same value are allowed. + + Example: + ``` + config.givc.dbusproxy.system.policy = { + see = [ "org.freedesktop.NetworkManager.*" "org.freedesktop.Avahi.*" ]; + talk = [ "org.freedesktop.NetworkManager.*" ]; + }; + ``` + In order to create your policies, consider using `busctl` to list the available services and their properties. + ''; + system = mkOption { + description = "Configuration of givc-dbusproxy for system bus."; + type = dbusSubmodule; + default = { }; + }; + session = mkOption { + description = "Configuration of givc-dbusproxy for user session bus."; + type = dbusSubmodule; + default = { }; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = cfg.enable -> (cfg.system.enable || cfg.session.enable); + message = '' + The DBUS proxy module requires at least one of the system or session bus to be enabled. + ''; + } + { + assertion = + cfg.system.enable + -> ( + cfg.system.policy.see != null + || cfg.system.policy.talk != null + || cfg.system.policy.own != null + || cfg.system.policy.call != null + || cfg.system.policy.broadcast != null + ); + message = '' + At least one policy value (see/talk/own/call/broadcast) for the system bus must be set. For more information, please + refer to the xdg-dbus-proxy manual (e.g., https://www.systutorials.com/docs/linux/man/1-xdg-dbus-proxy/). + ''; + } + { + assertion = + cfg.session.enable + -> ( + cfg.session.policy.see != null + || cfg.session.policy.talk != null + || cfg.session.policy.own != null + || cfg.session.policy.call != null + || cfg.session.policy.broadcast != null + ); + message = '' + At least one policy value (see/talk/own/call/broadcast) for the session bus must be set. For more information, please + refer to the xdg-dbus-proxy manual (e.g., https://www.systutorials.com/docs/linux/man/1-xdg-dbus-proxy/). + ''; + } + { + assertion = + cfg.session.enable + -> ( + config.users.users.${cfg.session.user}.isNormalUser + && config.users.users.${cfg.session.user}.uid != null + ); + message = '' + You need to specify a non-system user with UID set to run the session bus proxy. + ''; + } + ]; + + environment.systemPackages = [ + pkgs.xdg-dbus-proxy + ]; + + systemd = + + optionalAttrs cfg.system.enable { + services."givc-dbusproxy-system" = + let + args = concatStringsSep " " [ + "${optionalString (cfg.system.policy.see != null) ( + concatMapStringsSep " " (x: "--see=${x}") cfg.system.policy.see + )}" + "${optionalString (cfg.system.policy.talk != null) ( + concatMapStringsSep " " (x: "--talk=${x}") cfg.system.policy.talk + )}" + "${optionalString (cfg.system.policy.own != null) ( + concatMapStringsSep " " (x: "--own=${x}") cfg.system.policy.own + )}" + "${optionalString (cfg.system.policy.call != null) ( + concatMapStringsSep " " (x: "--call=${x}") cfg.system.policy.call + )}" + "${optionalString (cfg.system.policy.broadcast != null) ( + concatMapStringsSep " " (x: "--broadcast=${x}") cfg.system.policy.broadcast + )}" + ]; + in + { + description = "GIVC local xdg-dbus-proxy system service"; + enable = true; + before = [ "givc-setup.target" ]; + wantedBy = [ "givc-setup.target" ]; + serviceConfig = { + Type = "exec"; + ExecStart = "${pkgs.xdg-dbus-proxy}/bin/xdg-dbus-proxy unix:path=/run/dbus/system_bus_socket ${cfg.system.socket} --filter ${args}"; + Restart = "always"; + RestartSec = 1; + User = cfg.system.user; + }; + }; + } + // optionalAttrs cfg.session.enable { + user.services."givc-dbusproxy-session" = + let + args = concatStringsSep " " [ + "${optionalString (cfg.session.policy.see != null) ( + concatMapStringsSep " " (x: "--see=${x}") cfg.session.policy.see + )}" + "${optionalString (cfg.session.policy.talk != null) ( + concatMapStringsSep " " (x: "--talk=${x}") cfg.session.policy.talk + )}" + "${optionalString (cfg.session.policy.own != null) ( + concatMapStringsSep " " (x: "--own=${x}") cfg.session.policy.own + )}" + "${optionalString (cfg.session.policy.call != null) ( + concatMapStringsSep " " (x: "--call=${x}") cfg.session.policy.call + )}" + "${optionalString (cfg.session.policy.broadcast != null) ( + concatMapStringsSep " " (x: "--broadcast=${x}") cfg.session.policy.broadcast + )}" + ]; + uid = toString config.users.users.${cfg.session.user}.uid; + in + { + description = "GIVC local xdg-dbus-proxy session service"; + enable = true; + after = [ "sockets.target" ]; + wants = [ "sockets.target" ]; + wantedBy = [ "default.target" ]; + unitConfig.ConditionUser = cfg.session.user; + serviceConfig = { + Type = "exec"; + ExecStart = "${pkgs.xdg-dbus-proxy}/bin/xdg-dbus-proxy unix:path=/run/user/${uid}/bus ${cfg.session.socket} --filter ${args}"; + Restart = "always"; + RestartSec = 1; + }; + }; + }; + }; +} diff --git a/nixos/modules/definitions.nix b/nixos/modules/definitions.nix new file mode 100644 index 0000000..83cc3ac --- /dev/null +++ b/nixos/modules/definitions.nix @@ -0,0 +1,108 @@ +{ + config, + lib, +}: +let + inherit (lib) mkOption types; + + transportSubmodule = types.submodule { + options = { + name = mkOption { + description = "Network host and TLS name"; + type = types.str; + default = "localhost"; + }; + + addr = mkOption { + description = "IP Address or socket path"; + type = types.str; + default = "127.0.0.1"; + }; + + port = mkOption { + description = "Port"; + type = types.str; + default = "9000"; + }; + + protocol = mkOption { + description = "Protocol"; + type = types.str; + default = "tcp"; + }; + }; + }; + +in +{ + applicationSubmodule = types.submodule { + options = { + name = mkOption { + description = "Name of the application."; + type = types.str; + }; + command = mkOption { + description = "Command to run the application."; + type = types.str; + }; + args = mkOption { + description = '' + List of allowed argument types for the application. Currently implemented argument types: + - 'url': URL provided to the application as string + - 'flag': Flag (boolean) provided to the application as string + ''; + type = types.listOf types.str; + default = [ ]; + }; + }; + }; + + proxySubmodule = types.submodule { + options = { + transport = mkOption { + description = "Transport Configuration"; + type = transportSubmodule; + }; + socket = mkOption { + description = "Path to the system socket. Defaults to `/tmp/.dbusproxy.sock`."; + type = types.str; + default = "/tmp/.dbusproxy.sock"; + }; + server = mkOption { + description = '' + Whether the module runs as server or client. The server connects to an existing socket, whereas + a client provides a listener socket. Defaults to `config.givc.dbusproxy.enable`. + ''; + type = types.bool; + default = config.givc.dbusproxy.enable; + }; + }; + }; + + tlsSubmodule = types.submodule { + options = { + enable = mkOption { + description = "Enable TLS. Defaults to 'true'."; + type = types.bool; + default = true; + }; + caCertPath = mkOption { + description = "Path to the CA certificate file."; + type = types.str; + default = ""; + }; + certPath = mkOption { + description = "Path to the service certificate file."; + type = types.str; + default = ""; + }; + keyPath = mkOption { + description = "Path to the service key file."; + type = types.str; + default = ""; + }; + }; + }; + + inherit transportSubmodule; +} diff --git a/nixos/modules/host.nix b/nixos/modules/host.nix index 2802d30..1b0d3ce 100644 --- a/nixos/modules/host.nix +++ b/nixos/modules/host.nix @@ -17,35 +17,20 @@ let types concatStringsSep trivial - attrsets + ; + inherit (builtins) toJSON; + inherit (import ./definitions.nix { inherit config lib; }) + transportSubmodule + tlsSubmodule ; in { options.givc.host = { enable = mkEnableOption "Enable givc-host module."; - name = mkOption { - description = "Host name (without domain)."; - type = types.str; - default = "localhost"; - }; - - addr = mkOption { - description = "IPv4 address."; - type = types.str; - default = "127.0.0.1"; - }; - - port = mkOption { - description = "Port of the agent service. Defaults to '9000'."; - type = types.str; - default = "9000"; - }; - - protocol = mkOption { - description = "Transport protocol, defaults to 'tcp'."; - type = types.str; - default = "tcp"; + agent = mkOption { + description = "Host configuration"; + type = transportSubmodule; }; debug = mkEnableOption "Enable verbose logs for debugging."; @@ -67,75 +52,12 @@ in admin = mkOption { description = "Admin server configuration."; - type = - with types; - submodule { - options = { - enable = mkEnableOption "Admin module"; - name = mkOption { - description = "Hostname of admin server"; - type = types.str; - }; - - addr = mkOption { - description = "Address of admin server"; - type = types.str; - }; - - port = mkOption { - description = "Port of admin server"; - type = types.str; - }; - - protocol = mkOption { - description = "Protocol of admin server"; - type = types.str; - }; - }; - }; - default = { - enable = true; - name = "localhost"; - addr = "127.0.0.1"; - port = "9001"; - protocol = "tcp"; - }; + type = transportSubmodule; }; tls = mkOption { - description = '' - TLS options for gRPC connections. It is enabled by default to discourage unprotected connections, - and requires paths to certificates and key being set. To disable it use 'tls.enable = false;'. - ''; - type = - with types; - submodule { - options = { - enable = mkOption { - description = "Enable TLS. Defaults to 'true'."; - type = bool; - default = true; - }; - caCertPath = mkOption { - description = "Path to the CA certificate file."; - type = str; - }; - certPath = mkOption { - description = "Path to the service certificate file."; - type = str; - }; - keyPath = mkOption { - description = "Path to the service key file."; - type = str; - }; - }; - }; - default = { - enable = true; - caCertPath = ""; - certPath = ""; - keyPath = ""; - }; + description = "TLS configuration."; + type = tlsSubmodule; }; }; @@ -148,48 +70,38 @@ in { assertion = !(cfg.tls.enable && (cfg.tls.caCertPath == "" || cfg.tls.certPath == "" || cfg.tls.keyPath == "")); - message = "The TLS option requires paths' to CA certificate, service certificate, and service key."; + message = '' + The TLS configuration requires paths' to CA certificate, service certificate, and service key. + To disable TLS, set 'tls.enable = false;'. + ''; } ]; - systemd.services."givc-${cfg.name}" = { + systemd.services."givc-${cfg.agent.name}" = { description = "GIVC remote service manager for the host."; enable = true; after = [ "network-online.target" ]; wants = [ "network-online.target" ]; wantedBy = [ "multi-user.target" ]; - # @TODO add requirement for admin to run before host module serviceConfig = { Type = "exec"; ExecStart = "${givc-agent}/bin/givc-agent"; Restart = "always"; RestartSec = 1; }; - environment = - { - "NAME" = "${cfg.name}"; - "ADDR" = "${cfg.addr}"; - "PORT" = "${cfg.port}"; - "PROTO" = "${cfg.protocol}"; - "DEBUG" = "${trivial.boolToString cfg.debug}"; - "TYPE" = "0"; - "SUBTYPE" = "1"; - "TLS" = "${trivial.boolToString cfg.tls.enable}"; - "SERVICES" = "${concatStringsSep " " cfg.services}"; - "ADMIN_SERVER_NAME" = "${cfg.admin.name}"; - "ADMIN_SERVER_ADDR" = "${cfg.admin.addr}"; - "ADMIN_SERVER_PORT" = "${cfg.admin.port}"; - "ADMIN_SERVER_PROTO" = "${cfg.admin.protocol}"; - } - // attrsets.optionalAttrs cfg.tls.enable { - "CA_CERT" = "${cfg.tls.caCertPath}"; - "HOST_CERT" = "${cfg.tls.certPath}"; - "HOST_KEY" = "${cfg.tls.keyPath}"; - }; + environment = { + "AGENT" = "${toJSON cfg.agent}"; + "DEBUG" = "${trivial.boolToString cfg.debug}"; + "TYPE" = "0"; + "SUBTYPE" = "1"; + "SERVICES" = "${concatStringsSep " " cfg.services}"; + "ADMIN_SERVER" = "${toJSON cfg.admin}"; + "TLS_CONFIG" = "${toJSON cfg.tls}"; + }; }; networking.firewall.allowedTCPPorts = let - port = lib.strings.toInt cfg.port; + port = lib.strings.toInt cfg.agent.port; in [ port ]; }; diff --git a/nixos/modules/sysvm.nix b/nixos/modules/sysvm.nix index 29319d5..e4e2c30 100644 --- a/nixos/modules/sysvm.nix +++ b/nixos/modules/sysvm.nix @@ -11,19 +11,35 @@ let cfg = config.givc.sysvm; inherit (self.packages.${pkgs.stdenv.hostPlatform.system}) givc-agent; inherit (lib) + mkIf mkOption mkEnableOption - mkIf types - concatStringsSep trivial - attrsets + strings + lists + concatStringsSep + optionalString + optionals + ; + inherit (builtins) toJSON; + inherit (import ./definitions.nix { inherit config lib; }) + transportSubmodule + proxySubmodule + tlsSubmodule ; in { options.givc.sysvm = { enable = mkEnableOption "Enable givc-sysvm module."; + agent = mkOption { + description = '' + Host configuration for the system VM. + ''; + type = transportSubmodule; + }; + services = mkOption { description = '' List of systemd services for the manager to administrate. Expects a space separated list. @@ -36,67 +52,11 @@ in ]; }; - name = mkOption { - description = "Host name (without domain)."; - type = types.str; - default = "localhost"; - }; - - addr = mkOption { - description = "IPv4 address."; - type = types.str; - default = "127.0.0.1"; - }; - - port = mkOption { - description = "Port of the agent service. Defaults to '9000'."; - type = types.str; - default = "9000"; - }; - - protocol = mkOption { - description = "Transport protocol, defaults to 'tcp'."; - type = types.str; - default = "tcp"; - }; - debug = mkEnableOption "Enable verbose logs for debugging."; admin = mkOption { description = "Admin server configuration."; - type = - with types; - submodule { - options = { - enable = mkEnableOption "Admin module"; - name = mkOption { - description = "Hostname of admin server"; - type = types.str; - }; - - addr = mkOption { - description = "Address of admin server"; - type = types.str; - }; - - port = mkOption { - description = "Port of admin server"; - type = types.str; - }; - - protocol = mkOption { - description = "Protocol of admin server"; - type = types.str; - }; - }; - }; - default = { - enable = true; - name = "localhost"; - addr = "127.0.0.1"; - port = "9001"; - protocol = "tcp"; - }; + type = transportSubmodule; }; wifiManager = mkOption { @@ -123,44 +83,32 @@ in default = ""; }; - tls = mkOption { + localeListener = mkOption { description = '' - TLS options for gRPC connections. It is enabled by default to discourage unprotected connections, - and requires paths to certificates and key being set. To disable it use 'tls.enable = false;'. + Locale handler. ''; - type = - with types; - submodule { - options = { - enable = mkOption { - description = "Enable TLS. Defaults to 'true'."; - type = bool; - default = true; - }; - caCertPath = mkOption { - description = "Path to the CA certificate file."; - type = str; - }; - certPath = mkOption { - description = "Path to the service certificate file."; - type = str; - }; - keyPath = mkOption { - description = "Path to the service key file."; - type = str; - }; - }; - }; - default = { - enable = true; - caCertPath = ""; - certPath = ""; - keyPath = ""; - }; + type = types.bool; + default = false; + }; + + socketProxy = mkOption { + description = '' + Socket proxy module. If not provided, the module will not use a socket proxy. + ''; + type = types.nullOr (types.listOf proxySubmodule); + default = null; + }; + + tls = mkOption { + description = "TLS configuration."; + type = tlsSubmodule; }; }; + imports = [ self.nixosModules.dbus ]; + config = mkIf cfg.enable { + assertions = [ { assertion = cfg.services != [ ]; @@ -169,16 +117,37 @@ in { assertion = !(cfg.tls.enable && (cfg.tls.caCertPath == "" || cfg.tls.certPath == "" || cfg.tls.keyPath == "")); - message = "The TLS option requires paths' to CA certificate, service certificate, and service key."; + message = '' + The TLS configuration requires paths' to CA certificate, service certificate, and service key. + To disable TLS, set 'tls.enable = false;'. + ''; + } + { + assertion = + cfg.socketProxy == null + || lists.allUnique (map (p: (strings.toInt p.transport.port)) cfg.socketProxy); + message = "SocketProxy: Each socket proxy instance requires a unique port number."; + } + { + assertion = cfg.socketProxy == null || lists.allUnique (map (p: p.socket) cfg.socketProxy); + message = "SocketProxy: Each socket proxy instance requires a unique socket."; } ]; - systemd.services."givc-${cfg.name}" = { - description = "GIVC remote service manager for system VMs."; + systemd.targets.givc-setup = { enable = true; + description = "Ghaf givc target"; + bindsTo = [ "network-online.target" ]; after = [ "network-online.target" ]; - wants = [ "network-online.target" ]; - wantedBy = [ "multi-user.target" ]; + wantedBy = [ "network-online.target" ]; + }; + + systemd.services."givc-${cfg.agent.name}" = { + description = "GIVC remote service manager for system VMs"; + enable = true; + after = [ "givc-setup.target" ]; + partOf = [ "givc-setup.target" ]; + wantedBy = [ "givc-setup.target" ]; serviceConfig = { Type = "exec"; ExecStart = "${givc-agent}/bin/givc-agent"; @@ -186,36 +155,29 @@ in RestartSec = 1; }; path = [ pkgs.dbus ]; - environment = - { - "NAME" = "${cfg.name}"; - "ADDR" = "${cfg.addr}"; - "PORT" = "${cfg.port}"; - "PROTO" = "${cfg.protocol}"; - "DEBUG" = "${trivial.boolToString cfg.debug}"; - "TYPE" = "8"; - "SUBTYPE" = "9"; - "WIFI" = "${trivial.boolToString cfg.wifiManager}"; - "HWID" = "${trivial.boolToString cfg.hwidService}"; - "HWID_IFACE" = "${cfg.hwidIface}"; - "TLS" = "${trivial.boolToString cfg.tls.enable}"; - "PARENT" = "microvm@${cfg.name}.service"; - "SERVICES" = "${concatStringsSep " " cfg.services}"; - "ADMIN_SERVER_NAME" = "${cfg.admin.name}"; - "ADMIN_SERVER_ADDR" = "${cfg.admin.addr}"; - "ADMIN_SERVER_PORT" = "${cfg.admin.port}"; - "ADMIN_SERVER_PROTO" = "${cfg.admin.protocol}"; - } - // attrsets.optionalAttrs cfg.tls.enable { - "CA_CERT" = "${cfg.tls.caCertPath}"; - "HOST_CERT" = "${cfg.tls.certPath}"; - "HOST_KEY" = "${cfg.tls.keyPath}"; - }; + environment = { + "AGENT" = "${toJSON cfg.agent}"; + "DEBUG" = "${trivial.boolToString cfg.debug}"; + "TYPE" = "8"; + "SUBTYPE" = "9"; + "WIFI" = "${trivial.boolToString cfg.wifiManager}"; + "HWID" = "${trivial.boolToString cfg.hwidService}"; + "HWID_IFACE" = "${cfg.hwidIface}"; + "LOCALE_LISTENER" = "${trivial.boolToString cfg.localeListener}"; + "SOCKET_PROXY" = "${optionalString (cfg.socketProxy != null) (toJSON cfg.socketProxy)}"; + "PARENT" = "microvm@${cfg.agent.name}.service"; + "SERVICES" = "${concatStringsSep " " cfg.services}"; + "ADMIN_SERVER" = "${toJSON cfg.admin}"; + "TLS_CONFIG" = "${toJSON cfg.tls}"; + }; }; networking.firewall.allowedTCPPorts = let - port = lib.strings.toInt cfg.port; + agentPort = strings.toInt cfg.agent.port; + proxyPorts = optionals (cfg.socketProxy != null) ( + map (p: (strings.toInt p.transport.port)) cfg.socketProxy + ); in - [ port ]; + [ agentPort ] ++ proxyPorts; }; } diff --git a/nixos/packages/givc-agent.nix b/nixos/packages/givc-agent.nix index 8285e6e..e3338ab 100644 --- a/nixos/packages/givc-agent.nix +++ b/nixos/packages/givc-agent.nix @@ -3,9 +3,9 @@ { pkgs, src }: pkgs.buildGoModule { pname = "givc-agent"; - version = "0.0.2"; + version = "0.0.3"; inherit src; - vendorHash = "sha256-QXzrdiRtd1eugUyWQQYaBthMNbiRoqiWW1y8MZV0d20="; + vendorHash = "sha256-qF9Amm8A55b8hu0WIVSlxFQqpF+4wFlKhKuUg8k/EiM="; subPackages = [ "internal/pkgs/grpc" "internal/pkgs/servicemanager" diff --git a/nixos/tests/admin.nix b/nixos/tests/admin.nix index 9d91444..8780a9f 100644 --- a/nixos/tests/admin.nix +++ b/nixos/tests/admin.nix @@ -1,3 +1,5 @@ +# Copyright 2024 TII (SSRC) and the Ghaf contributors +# SPDX-License-Identifier: Apache-2.0 { self, lib, @@ -13,7 +15,7 @@ let appvm = "192.168.101.5"; guivm = "192.168.101.3"; }; - adminSettings = { + admin = { name = "admin-vm"; addr = addrs.adminvm; port = "9001"; @@ -45,9 +47,10 @@ in givc.admin = { enable = true; debug = true; - name = "admin-vm"; - addr = addrs.adminvm; - port = "9001"; + inherit (admin) name; + inherit (admin) addr; + inherit (admin) port; + inherit (admin) protocol; tls = mkTls "admin-vm"; }; }; @@ -61,15 +64,13 @@ in ]; givc.host = { enable = true; - name = "ghaf-host"; - addr = addrs.host; - port = "9000"; - admin = { - name = "admin-vm"; - addr = addrs.adminvm; - port = "9001"; - protocol = "tcp"; # go version expect word "tcp" here, but it unused + agent = { + name = "ghaf-host"; + addr = addrs.host; + port = "9000"; + protocol = "tcp"; }; + inherit admin; services = [ "microvm@admin-vm.service" "microvm@foot-vm.service" @@ -150,9 +151,11 @@ in ''; givc.sysvm = { enable = true; - admin = adminSettings; - addr = addrs.guivm; - name = "gui-vm"; + inherit admin; + agent = { + addr = addrs.guivm; + name = "gui-vm"; + }; tls = mkTls "gui-vm"; services = [ "poweroff.target" @@ -201,9 +204,11 @@ in givc.appvm = { enable = true; debug = true; - name = "chromium-vm"; - addr = addrs.appvm; - admin = adminSettings; + agent = { + name = "chromium-vm"; + addr = addrs.appvm; + }; + inherit admin; tls = mkTls "chromium-vm"; applications = [ { diff --git a/nixos/tests/dbus.nix b/nixos/tests/dbus.nix new file mode 100644 index 0000000..444702e --- /dev/null +++ b/nixos/tests/dbus.nix @@ -0,0 +1,478 @@ +# Copyright 2024 TII (SSRC) and the Ghaf contributors +# SPDX-License-Identifier: Apache-2.0 +{ + self, + lib, + inputs, + ... +}: +let + tls = true; + snakeoil = ./snakeoil; + addrs = { + netvm = "192.168.101.1"; + audiovm = "192.168.101.2"; + guivm = "192.168.101.3"; + adminvm = "192.168.101.10"; + appvm = "192.168.101.100"; + }; + admin = { + name = "admin-vm"; + addr = addrs.adminvm; + port = "9001"; + protocol = "tcp"; + }; + mkTls = name: { + enable = tls; + caCertPath = "${snakeoil}/${name}/ca-cert.pem"; + certPath = "${snakeoil}/${name}/${name}-cert.pem"; + keyPath = "${snakeoil}/${name}/${name}-key.pem"; + }; +in +{ + perSystem = _: { + vmTests.tests.dbus = { + module = { + nodes = { + adminvm = + { pkgs, ... }: + { + imports = [ self.nixosModules.admin ]; + + networking.interfaces.eth1.ipv4.addresses = lib.mkOverride 0 [ + { + address = addrs.adminvm; + prefixLength = 24; + } + ]; + environment.systemPackages = [ pkgs.grpcurl ]; + givc.admin = { + enable = true; + inherit (admin) name; + inherit (admin) addr; + inherit (admin) port; + inherit (admin) protocol; + tls = mkTls "admin-vm"; + debug = false; + }; + }; + + guivm = + { pkgs, ... }: + let + inherit (import "${inputs.nixpkgs.outPath}/nixos/tests/ssh-keys.nix" pkgs) + snakeOilPublicKey + ; + in + { + imports = [ + self.nixosModules.sysvm + ]; + + environment.systemPackages = [ + pkgs.networkmanager + ]; + + # Network + networking.interfaces.eth1.ipv4.addresses = lib.mkOverride 0 [ + { + address = addrs.guivm; + prefixLength = 24; + } + ]; + + # Setup users and keys + users.groups.users = { }; + users.users = { + ghaf = { + isNormalUser = true; + group = "users"; + uid = 1000; + openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; + }; + }; + services.getty.autologinUser = "ghaf"; + + # Test users to check access controls work correctly + + # Parameters: + # - name: evil1 + # - isNormalUser: User is a normal user + # - uid: User ID >= 1000 + # - group: 'users', 'networkmanager' + users.users = { + evil1 = { + isNormalUser = true; + uid = 4269; + group = "users"; + openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; + }; + }; + + # Parameters: + # - name: evil2 + # - isSystemUser: User is a system user + # - uid: User ID < 1000 + # - group: 'root', 'networkmanager' + users.users = { + evil2 = { + isSystemUser = true; + uid = 42; + group = "users"; + openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; + }; + }; + + givc.sysvm = { + enable = true; + inherit admin; + agent = { + addr = addrs.guivm; + name = "gui-vm"; + }; + tls = mkTls "gui-vm"; + socketProxy = [ + { + transport = { + name = "net-vm"; + addr = addrs.netvm; + port = "9010"; + protocol = "tcp"; + }; + socket = "/tmp/.dbusproxy_net.sock"; + } + { + transport = { + name = "audio-vm"; + addr = addrs.audiovm; + port = "9011"; + protocol = "tcp"; + }; + socket = "/tmp/.dbusproxy_snd.sock"; + } + { + transport = { + name = "audio-vm"; + addr = addrs.audiovm; + port = "9012"; + protocol = "tcp"; + }; + socket = "/tmp/.dbusproxy_app.sock"; + } + { + transport = { + name = "chromium-vm"; + addr = addrs.appvm; + port = "9013"; + protocol = "tcp"; + }; + socket = "/tmp/.dbusproxy_app2.sock"; + } + ]; + debug = true; + }; + }; + + netvm = + { pkgs, ... }: + let + inherit (import "${inputs.nixpkgs.outPath}/nixos/tests/ssh-keys.nix" pkgs) + snakeOilPublicKey + ; + in + { + imports = [ + self.nixosModules.sysvm + ]; + + # Network + networking.interfaces.eth1.ipv4.addresses = lib.mkOverride 0 [ + { + address = addrs.netvm; + prefixLength = 24; + } + ]; + + # Services + networking.networkmanager.enable = true; + services.avahi.enable = true; + services.upower.enable = true; + + # Setup users and keys + users.groups.users = { }; + users.users = { + ghaf = { + isNormalUser = true; + group = "users"; + uid = 1000; + openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; + }; + }; + + givc.sysvm = { + enable = true; + inherit admin; + agent = { + addr = addrs.netvm; + name = "net-vm"; + }; + tls = mkTls "net-vm"; + socketProxy = [ + { + transport = { + name = "gui-vm"; + addr = addrs.guivm; + port = "9010"; + protocol = "tcp"; + }; + socket = "/tmp/.dbusproxy_net.sock"; + } + ]; + debug = true; + }; + + givc.dbusproxy = { + enable = true; + system = { + enable = true; + user = "ghaf"; + socket = "/tmp/.dbusproxy_net.sock"; + policy = { + talk = [ + "org.freedesktop.NetworkManager.*" + "org.freedesktop.Avahi.*" + ]; + call = [ + "org.freedesktop.UPower=org.freedesktop.UPower.EnumerateDevices" + ]; + }; + }; + }; + }; + + audiovm = + { pkgs, ... }: + let + inherit (import "${inputs.nixpkgs.outPath}/nixos/tests/ssh-keys.nix" pkgs) + snakeOilPublicKey + ; + in + { + imports = [ + self.nixosModules.sysvm + ]; + + # Network + networking.interfaces.eth1.ipv4.addresses = lib.mkOverride 0 [ + { + address = addrs.audiovm; + prefixLength = 24; + } + ]; + + # Service + services.upower.enable = true; + services.playerctld.enable = true; + + # Setup users and keys + users.mutableUsers = false; + users.groups.users = { }; + users.users = { + ghaf = { + isNormalUser = true; + group = "users"; + uid = 1000; + openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; + linger = true; + }; + }; + + givc.sysvm = { + enable = true; + inherit admin; + agent = { + addr = addrs.audiovm; + name = "audio-vm"; + }; + tls = mkTls "audio-vm"; + socketProxy = [ + { + transport = { + name = "gui-vm"; + addr = addrs.guivm; + port = "9011"; + protocol = "tcp"; + }; + socket = "/tmp/.dbusproxy_snd.sock"; + } + { + transport = { + name = "gui-vm"; + addr = addrs.guivm; + port = "9012"; + protocol = "tcp"; + }; + socket = "/tmp/.dbusproxy_app.sock"; + } + ]; + debug = true; + }; + + givc.dbusproxy = { + enable = true; + system = { + enable = true; + user = "ghaf"; + socket = "/tmp/.dbusproxy_snd.sock"; + policy.talk = [ + "org.freedesktop.UPower.*" + ]; + }; + session = { + enable = true; + user = "ghaf"; + socket = "/tmp/.dbusproxy_app.sock"; + policy.talk = [ + "org.mpris.MediaPlayer2.playerctld.*" + ]; + }; + }; + }; + + appvm = + { pkgs, ... }: + let + inherit (import "${inputs.nixpkgs.outPath}/nixos/tests/ssh-keys.nix" pkgs) + snakeOilPublicKey + ; + in + { + imports = [ + self.nixosModules.appvm + ]; + + # Network + networking.interfaces.eth1.ipv4.addresses = lib.mkOverride 0 [ + { + address = addrs.appvm; + prefixLength = 24; + } + ]; + + # Service + services.playerctld.enable = true; + + # Setup users and keys + users.mutableUsers = false; + users.groups.users = { }; + users.users = { + ghaf = { + isNormalUser = true; + group = "users"; + uid = 1000; + openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; + linger = true; + }; + }; + + givc.appvm = { + enable = true; + inherit admin; + agent = { + addr = addrs.appvm; + name = "chromium-vm"; + }; + tls = mkTls "chromium-vm"; + socketProxy = [ + { + transport = { + name = "gui-vm"; + addr = addrs.guivm; + port = "9013"; + protocol = "tcp"; + }; + socket = "/tmp/.dbusproxy_app2.sock"; + } + ]; + applications = [ + { + name = "dummy"; + command = "/bin/bash"; + args = [ ]; + } + ]; + debug = true; + }; + + givc.dbusproxy = { + enable = true; + session = { + enable = true; + user = "ghaf"; + socket = "/tmp/.dbusproxy_app2.sock"; + policy.talk = [ + "org.mpris.MediaPlayer2.playerctld.*" + ]; + }; + }; + }; + }; + + testScript = _: '' + + with subtest("boot_completed"): + adminvm.wait_for_unit("multi-user.target") + audiovm.wait_for_unit("multi-user.target") + netvm.wait_for_unit("multi-user.target") + guivm.wait_for_unit("multi-user.target") + appvm.wait_for_unit("multi-user.target") + + with subtest("success_tests_systembus"): + + # SUCCESS: remote access to netvms NetworkManager service; dbus-send + print(guivm.succeed("sudo -u ghaf dbus-send --bus=unix:path=/tmp/.dbusproxy_net.sock --print-reply --dest=org.freedesktop.NetworkManager /org/freedesktop/NetworkManager org.freedesktop.DBus.Properties.Get string:'org.freedesktop.NetworkManager' string:'ActiveConnections'")) + + # SUCCESS: remote access to netvms NetworkManager service; nmcli + print(guivm.succeed("sudo -u ghaf -- bash -c 'export DBUS_SYSTEM_BUS_ADDRESS=unix:path=/tmp/.dbusproxy_net.sock; nmcli d'")) + + # SUCCESS: access to additional specified netvm service + print(guivm.succeed("sudo -u ghaf dbus-send --bus=unix:path=/tmp/.dbusproxy_net.sock --print-reply --dest=org.freedesktop.Avahi /org/freedesktop/Avahi org.freedesktop.DBus.Introspectable.Introspect")) + + # SUCCESS: 'call' method access to specified netvm service + print(guivm.succeed("sudo -u ghaf dbus-send --bus=unix:path=/tmp/.dbusproxy_net.sock --print-reply --dest=org.freedesktop.UPower /org/freedesktop/UPower org.freedesktop.UPower.EnumerateDevices")) + + # SUCCESS: connection to secondary system vm (audio) + print(guivm.succeed("sudo -u ghaf dbus-send --bus=unix:path=/tmp/.dbusproxy_snd.sock --print-reply --dest=org.freedesktop.UPower /org/freedesktop/UPower org.freedesktop.DBus.Introspectable.Introspect")) + + with subtest("failure_tests_systembus"): + + # FAIL: 'call' access to non-specified netvm service + print(guivm.fail("sudo -u ghaf dbus-send --bus=unix:path=/tmp/.dbusproxy_net.sock --print-reply --dest=org.freedesktop.UPower /org/freedesktop/UPower org.freedesktop.UPower.GetCriticalAction")) + + # FAIL: root user access to netvm service + print(guivm.fail("dbus-send --bus=unix:path=/tmp/.dbusproxy_net.sock --print-reply --dest=org.freedesktop.UPower /org/freedesktop/UPower org.freedesktop.DBus.Introspectable.Introspect")) + + # FAIL: evil1 user access to netvm service + print(guivm.fail("sudo -u evil1 dbus-send --bus=unix:path=/tmp/.dbusproxy_net.sock --print-reply --dest=org.freedesktop.UPower /org/freedesktop/UPower org.freedesktop.UPower.EnumerateDevices")) + + # FAIL: evil2 user access to netvm service + print(guivm.fail("sudo -u evil2 dbus-send --bus=unix:path=/tmp/.dbusproxy_net.sock --print-reply --dest=org.freedesktop.Avahi /org/freedesktop/Avahi org.freedesktop.DBus.Introspectable.Introspect")) + + with subtest("remote_user_to_sesssionbus_access"): + appvm.wait_for_unit("multi-user.target") + + # SUCCESS: ghaf user access to audiovm/appvm session bus + print(guivm.succeed("sudo -u ghaf dbus-send --bus=unix:path=/tmp/.dbusproxy_app.sock --print-reply --dest=org.mpris.MediaPlayer2.playerctld /org/mpris/MediaPlayer2 org.freedesktop.DBus.Introspectable.Introspect")) + print(guivm.succeed("sudo -u ghaf dbus-send --bus=unix:path=/tmp/.dbusproxy_app2.sock --print-reply --dest=org.mpris.MediaPlayer2.playerctld /org/mpris/MediaPlayer2 org.freedesktop.DBus.Introspectable.Introspect")) + + # FAIL: root user access to audiovm/appvm session bus + print(guivm.fail("dbus-send --bus=unix:path=/tmp/.dbusproxy_app.sock --print-reply --dest=org.mpris.MediaPlayer2.playerctld /org/freedesktop/MediaPlayer2 org.freedesktop.DBus.Introspectable.Introspect")) + print(guivm.fail("dbus-send --bus=unix:path=/tmp/.dbusproxy_app2.sock --print-reply --dest=org.mpris.MediaPlayer2.playerctld /org/freedesktop/MediaPlayer2 org.freedesktop.DBus.Introspectable.Introspect")) + + with subtest("test_parallel_access"): + print(guivm.succeed("sudo -u ghaf dbus-send --bus=unix:path=/tmp/.dbusproxy_app.sock --dest=org.mpris.MediaPlayer2.playerctld /org/mpris/MediaPlayer2 org.freedesktop.DBus.Introspectable.Introspect & sudo -u ghaf dbus-send --bus=unix:path=/tmp/.dbusproxy_app.sock --dest=org.mpris.MediaPlayer2.playerctld /org/mpris/MediaPlayer2 org.freedesktop.DBus.Introspectable.Introspect & wait")) + ''; + }; + }; + }; +} diff --git a/nixos/tests/default.nix b/nixos/tests/default.nix index ea600fd..68d88c0 100644 --- a/nixos/tests/default.nix +++ b/nixos/tests/default.nix @@ -1,6 +1,9 @@ +# Copyright 2024 TII (SSRC) and the Ghaf contributors +# SPDX-License-Identifier: Apache-2.0 { imports = [ ./admin.nix ./netvm.nix + ./dbus.nix ]; } diff --git a/nixos/tests/netvm.nix b/nixos/tests/netvm.nix index 9275c93..3ac5862 100644 --- a/nixos/tests/netvm.nix +++ b/nixos/tests/netvm.nix @@ -1,3 +1,5 @@ +# Copyright 2024 TII (SSRC) and the Ghaf contributors +# SPDX-License-Identifier: Apache-2.0 { self, lib, @@ -5,13 +7,13 @@ ... }: let - tls = false; + tls = true; snakeoil = ./snakeoil; addrs = { netvm = "192.168.101.1"; adminvm = "192.168.101.2"; }; - adminSettings = { + admin = { name = "admin-vm"; addr = addrs.adminvm; port = "9001"; @@ -43,9 +45,10 @@ in environment.systemPackages = [ pkgs.grpcurl ]; givc.admin = { enable = true; - name = "admin-vm"; - addr = addrs.adminvm; - port = "9001"; + inherit (admin) name; + inherit (admin) addr; + inherit (admin) port; + inherit (admin) protocol; tls = mkTls "admin-vm"; }; }; @@ -80,6 +83,7 @@ in services.hostapd = { enable = true; radios.wlan0 = { + wifi4.enable = false; wifi6.enable = true; networks = { wlan0 = { @@ -167,9 +171,11 @@ in givc.sysvm = { enable = true; - admin = adminSettings; - addr = addrs.netvm; - name = "net-vm"; + inherit admin; + agent = { + addr = addrs.netvm; + name = "net-vm"; + }; tls = mkTls "net-vm"; wifiManager = true; hwidService = true; @@ -184,10 +190,10 @@ in grpcurl_cmd = "/run/current-system/sw/bin/grpcurl "; grpcurl_args = if tls then - "-cacert ${nodes.netvm.givc.sysvm.tls.caCertPath} -cert ${nodes.netvm.givc.sysvm.tls.certPath} -key ${nodes.netvm.givc.sysvm.tls.keyPath}" + "-cacert ${nodes.adminvm.givc.admin.tls.caCertPath} -cert ${nodes.adminvm.givc.admin.tls.certPath} -key ${nodes.adminvm.givc.admin.tls.keyPath}" else "-plaintext"; - grpcurl_addr = "${nodes.netvm.givc.sysvm.addr}:${nodes.netvm.givc.sysvm.port} "; + grpcurl_addr = "${nodes.netvm.givc.sysvm.agent.addr}:${nodes.netvm.givc.sysvm.agent.port} "; in '' import time