From d12b8058771823a566c11585f9de49eda4cf433b Mon Sep 17 00:00:00 2001 From: Dom Delnano Date: Fri, 10 Jan 2025 11:11:15 -0800 Subject: [PATCH] [cloud] Provide ability to disable executing modified pxl scripts (#2062) Summary: [cloud] Provide ability to disable executing modified pxl scripts Certain security conscious users are hesitant to use Pixie because without RBAC anyone with Pixie UI access can write arbitrary BPF code (bpftrace integration), access or export arbitrary data (modifying pxl scripts, writing export scripts). This change aims to address this concern with a global setting to prevent the ability to execute modified scripts. When an adhoc script is executed, the cloud will hash the contents of the script and check it against the scripts known to the scriptmgr service. If it is contained in the scriptmgr service, the script will be allowed to execute. Note: this does not prevent users from writing new export scripts. Since the query broker can source its scripts from a configmap as of #1326, this is deemed as an appropriate mitigation for cluster admins and I'll follow up with UI support to reflect that a vizier is in "configmap mode". Relevant Issues: N/A Type of change: /kind feature Test Plan: The following checks were performed - [x] New tests verify the scriptmgr and api service changes work - [x] Skaffold'ed to a testing cluster and verified script modification is blocked and unmodified scripts are allowed to run. In addition to this, the code editor in the UI is made read only and shows an explanation
Screenshots ![Screen Shot 2025-01-07 at 8 58 34 AM](https://github.com/user-attachments/assets/26c7cc23-08e2-4064-ab15-6172a2593391) ![Screen Shot 2025-01-07 at 8 58 37 AM](https://github.com/user-attachments/assets/8ddf05be-7f83-4935-af0a-44b424a8dc8a) ![Screen Shot 2025-01-07 at 8 58 59 AM](https://github.com/user-attachments/assets/b0033854-758d-4843-98ca-39120f8f8326)
Changelog Message: Pixie Cloud can now disable executing modified pxl scripts via the `PL_SCRIPT_MODIFICATION_DISABLED` key in the `pl-script-bundle-config` ConfigMap. See reference manifests for more details. --------- Signed-off-by: Dom Del Nano --- k8s/cloud/base/api_deployment.yaml | 7 + k8s/cloud/base/proxy_nginx_config.yaml | 5 +- k8s/cloud/base/script_bundles_config.yaml | 1 + k8s/cloud/dev/script_bundles_config.yaml | 1 + k8s/cloud/prod/script_bundles_config.yaml | 1 + .../public/base/script_bundles_config.yaml | 1 + k8s/cloud/staging/script_bundles_config.yaml | 1 + k8s/cloud/testing/script_bundles_config.yaml | 1 + src/cloud/api/api_server.go | 10 +- src/cloud/api/apienv/scriptmgr_client.go | 4 +- src/cloud/api/ptproxy/BUILD.bazel | 4 + src/cloud/api/ptproxy/vizier_pt_proxy.go | 62 +- src/cloud/api/ptproxy/vizier_pt_proxy_test.go | 163 +++++- src/cloud/scriptmgr/controllers/server.go | 29 +- .../scriptmgr/controllers/server_test.go | 43 ++ .../scriptmgrpb/mock/scriptmgrpb_mock.gen.go | 35 ++ src/cloud/scriptmgr/scriptmgrpb/service.pb.go | 539 ++++++++++++++++-- src/cloud/scriptmgr/scriptmgrpb/service.proto | 11 + src/ui/src/containers/constants.ts | 2 + src/ui/src/containers/editor/editor.tsx | 7 + src/ui/src/flags.js | 4 +- .../data-export-detail.tsx | 22 +- src/ui/webpack.config.js | 1 + 23 files changed, 886 insertions(+), 68 deletions(-) diff --git a/k8s/cloud/base/api_deployment.yaml b/k8s/cloud/base/api_deployment.yaml index babff6943e9..ce9b9039f2b 100644 --- a/k8s/cloud/base/api_deployment.yaml +++ b/k8s/cloud/base/api_deployment.yaml @@ -40,6 +40,8 @@ spec: name: pl-ory-service-config - configMapRef: name: pl-auth-connector-config + - configMapRef: + name: pl-script-bundles-config - configMapRef: name: pl-errors-config optional: true @@ -59,6 +61,11 @@ spec: configMapKeyRef: name: pl-service-config key: PL_VZMGR_SERVICE + - name: PL_SCRIPTMGR_SERVICE + valueFrom: + configMapKeyRef: + name: pl-service-config + key: PL_SCRIPTMGR_SERVICE - name: PL_AUTH_SERVICE valueFrom: configMapKeyRef: diff --git a/k8s/cloud/base/proxy_nginx_config.yaml b/k8s/cloud/base/proxy_nginx_config.yaml index fb4359a3a81..5d3e3b0321f 100644 --- a/k8s/cloud/base/proxy_nginx_config.yaml +++ b/k8s/cloud/base/proxy_nginx_config.yaml @@ -35,6 +35,7 @@ data: sub_filter '__CONFIG_DOMAIN_NAME__' "'${domain_name}'"; sub_filter '__CONFIG_SCRIPT_BUNDLE_URLS__' "'${script_bundle_urls}'"; sub_filter '__CONFIG_SCRIPT_BUNDLE_DEV__' "'${script_bundle_dev}'"; + sub_filter '__CONFIG_SCRIPT_MODIFICATION_DISABLED__' "${script_modification_disabled}"; sub_filter '__SEGMENT_UI_WRITE_KEY__' "'${segment_ui_write_key}'"; sub_filter '__SEGMENT_ANALYTICS_JS_DOMAIN__' "'segment.${domain_name}'"; sub_filter '__CONFIG_LD_CLIENT_ID__' "'${ld_client_id}'"; @@ -134,6 +135,7 @@ data: set_by_lua_block $segment_cli_write_key { return os.getenv("PL_SEGMENT_CLI_WRITE_KEY") } set_by_lua_block $script_bundle_urls { return os.getenv("SCRIPT_BUNDLE_URLS") } set_by_lua_block $script_bundle_dev { return os.getenv("SCRIPT_BUNDLE_DEV") } + set_by_lua_block $script_modification_disabled { return os.getenv("PL_SCRIPT_MODIFICATION_DISABLED") } set_by_lua_block $analytics_enabled { return os.getenv("ANALYTICS_ENABLED") } set_by_lua_block $announcement_enabled { return os.getenv("ANNOUNCEMENT_ENABLED") } set_by_lua_block $announce_widget_url { return os.getenv("ANNOUNCE_WIDGET_URL") } @@ -169,7 +171,8 @@ data: env PL_HYDRA_SERVICE; env PL_KRATOS_SERVICE; env SCRIPT_BUNDLE_URLS; - env SCRIPT_BUNDE_DEV; + env SCRIPT_BUNDLE_DEV; + env PL_SCRIPT_MODIFICATION_DISABLED; env ANALYTICS_ENABLED; env ANNOUNCEMENT_ENABLED; env ANNOUNCE_WIDGET_URL; diff --git a/k8s/cloud/base/script_bundles_config.yaml b/k8s/cloud/base/script_bundles_config.yaml index 24250e588ed..0a7c5c3fd7a 100644 --- a/k8s/cloud/base/script_bundles_config.yaml +++ b/k8s/cloud/base/script_bundles_config.yaml @@ -9,3 +9,4 @@ data: "https://artifacts.px.dev/pxl_scripts/bundle.json" ] SCRIPT_BUNDLE_DEV: "false" + PL_SCRIPT_MODIFICATION_DISABLED: "false" diff --git a/k8s/cloud/dev/script_bundles_config.yaml b/k8s/cloud/dev/script_bundles_config.yaml index 0db2590eb16..619dd866756 100644 --- a/k8s/cloud/dev/script_bundles_config.yaml +++ b/k8s/cloud/dev/script_bundles_config.yaml @@ -10,3 +10,4 @@ data: "https://artifacts.px.dev/pxl_scripts/bundle.json" ] SCRIPT_BUNDLE_DEV: "false" + PL_SCRIPT_MODIFICATION_DISABLED: "false" diff --git a/k8s/cloud/prod/script_bundles_config.yaml b/k8s/cloud/prod/script_bundles_config.yaml index 0db2590eb16..619dd866756 100644 --- a/k8s/cloud/prod/script_bundles_config.yaml +++ b/k8s/cloud/prod/script_bundles_config.yaml @@ -10,3 +10,4 @@ data: "https://artifacts.px.dev/pxl_scripts/bundle.json" ] SCRIPT_BUNDLE_DEV: "false" + PL_SCRIPT_MODIFICATION_DISABLED: "false" diff --git a/k8s/cloud/public/base/script_bundles_config.yaml b/k8s/cloud/public/base/script_bundles_config.yaml index 24250e588ed..0a7c5c3fd7a 100644 --- a/k8s/cloud/public/base/script_bundles_config.yaml +++ b/k8s/cloud/public/base/script_bundles_config.yaml @@ -9,3 +9,4 @@ data: "https://artifacts.px.dev/pxl_scripts/bundle.json" ] SCRIPT_BUNDLE_DEV: "false" + PL_SCRIPT_MODIFICATION_DISABLED: "false" diff --git a/k8s/cloud/staging/script_bundles_config.yaml b/k8s/cloud/staging/script_bundles_config.yaml index 0db2590eb16..619dd866756 100644 --- a/k8s/cloud/staging/script_bundles_config.yaml +++ b/k8s/cloud/staging/script_bundles_config.yaml @@ -10,3 +10,4 @@ data: "https://artifacts.px.dev/pxl_scripts/bundle.json" ] SCRIPT_BUNDLE_DEV: "false" + PL_SCRIPT_MODIFICATION_DISABLED: "false" diff --git a/k8s/cloud/testing/script_bundles_config.yaml b/k8s/cloud/testing/script_bundles_config.yaml index 0db2590eb16..619dd866756 100644 --- a/k8s/cloud/testing/script_bundles_config.yaml +++ b/k8s/cloud/testing/script_bundles_config.yaml @@ -10,3 +10,4 @@ data: "https://artifacts.px.dev/pxl_scripts/bundle.json" ] SCRIPT_BUNDLE_DEV: "false" + PL_SCRIPT_MODIFICATION_DISABLED: "false" diff --git a/src/cloud/api/api_server.go b/src/cloud/api/api_server.go index 07d83085197..0951fa38c62 100644 --- a/src/cloud/api/api_server.go +++ b/src/cloud/api/api_server.go @@ -66,6 +66,7 @@ func init() { pflag.String("auth_connector_name", "", "If any, the name of the auth connector to be used with Pixie") pflag.String("auth_connector_callback_url", "", "If any, the callback URL for the auth connector") + pflag.Bool("script_modification_disabled", false, "If script modification should be disallowed to prevent arbitrary script execution") } func main() { @@ -213,10 +214,6 @@ func main() { authServer := &controllers.AuthServer{AuthClient: ac} cloudpb.RegisterAuthServiceServer(s.GRPCServer(), authServer) - vpt := ptproxy.NewVizierPassThroughProxy(nc, vc) - vizierpb.RegisterVizierServiceServer(s.GRPCServer(), vpt) - vizierpb.RegisterVizierDebugServiceServer(s.GRPCServer(), vpt) - sm, err := apienv.NewScriptMgrServiceClient() if err != nil { log.WithError(err).Fatal("Failed to init scriptmgr client.") @@ -224,6 +221,11 @@ func main() { sms := &controllers.ScriptMgrServer{ScriptMgr: sm} cloudpb.RegisterScriptMgrServer(s.GRPCServer(), sms) + scriptModificationDisabled := viper.GetBool("script_modification_disabled") + vpt := ptproxy.NewVizierPassThroughProxy(nc, vc, sm, scriptModificationDisabled) + vizierpb.RegisterVizierServiceServer(s.GRPCServer(), vpt) + vizierpb.RegisterVizierDebugServiceServer(s.GRPCServer(), vpt) + mdIndexName := viper.GetString("md_index_name") if mdIndexName == "" { log.Fatal("Must specify a name for the elastic index.") diff --git a/src/cloud/api/apienv/scriptmgr_client.go b/src/cloud/api/apienv/scriptmgr_client.go index a372a4437fd..98fa1387697 100644 --- a/src/cloud/api/apienv/scriptmgr_client.go +++ b/src/cloud/api/apienv/scriptmgr_client.go @@ -28,7 +28,7 @@ import ( ) func init() { - pflag.String("scriptmgr_service", "scriptmgr-service.plc.svc.local:52000", "The profile service url (load balancer/list is ok)") + pflag.String("scriptmgr_service", "scriptmgr-service.plc.svc.local:52000", "The scriptmgr service url (load balancer/list is ok)") } // NewScriptMgrServiceClient creates a new scriptmgr RPC client stub. @@ -38,7 +38,7 @@ func NewScriptMgrServiceClient() (scriptmgrpb.ScriptMgrServiceClient, error) { return nil, err } - authChannel, err := grpc.Dial(viper.GetString("scripts_service"), dialOpts...) + authChannel, err := grpc.Dial(viper.GetString("scriptmgr_service"), dialOpts...) if err != nil { return nil, err } diff --git a/src/cloud/api/ptproxy/BUILD.bazel b/src/cloud/api/ptproxy/BUILD.bazel index ceb0ace1752..6c57a001a23 100644 --- a/src/cloud/api/ptproxy/BUILD.bazel +++ b/src/cloud/api/ptproxy/BUILD.bazel @@ -28,16 +28,19 @@ go_library( deps = [ "//src/api/proto/uuidpb:uuid_pl_go_proto", "//src/api/proto/vizierpb:vizier_pl_go_proto", + "//src/cloud/scriptmgr/scriptmgrpb:service_pl_go_proto", "//src/cloud/shared/vzshard", "//src/shared/cvmsgspb:cvmsgs_pl_go_proto", "//src/shared/services/authcontext", "//src/shared/services/jwtpb:jwt_pl_go_proto", + "//src/shared/services/utils", "//src/utils", "@com_github_gofrs_uuid//:uuid", "@com_github_gogo_protobuf//proto", "@com_github_gogo_protobuf//types", "@com_github_nats_io_nats_go//:nats_go", "@com_github_sirupsen_logrus//:logrus", + "@com_github_spf13_viper//:viper", "@org_golang_google_grpc//:grpc", "@org_golang_google_grpc//codes", "@org_golang_google_grpc//metadata", @@ -53,6 +56,7 @@ pl_go_test( ":ptproxy", "//src/api/proto/uuidpb:uuid_pl_go_proto", "//src/api/proto/vizierpb:vizier_pl_go_proto", + "//src/cloud/scriptmgr/scriptmgrpb:service_pl_go_proto", "//src/cloud/shared/vzshard", "//src/shared/cvmsgspb:cvmsgs_pl_go_proto", "//src/shared/services/env", diff --git a/src/cloud/api/ptproxy/vizier_pt_proxy.go b/src/cloud/api/ptproxy/vizier_pt_proxy.go index 59e79e2de66..e55e3d747d2 100644 --- a/src/cloud/api/ptproxy/vizier_pt_proxy.go +++ b/src/cloud/api/ptproxy/vizier_pt_proxy.go @@ -20,15 +20,24 @@ package ptproxy import ( "context" + "crypto/sha256" + "encoding/hex" + "fmt" "github.com/nats-io/nats.go" + "github.com/spf13/viper" "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" "px.dev/pixie/src/api/proto/uuidpb" "px.dev/pixie/src/api/proto/vizierpb" + "px.dev/pixie/src/cloud/scriptmgr/scriptmgrpb" "px.dev/pixie/src/shared/cvmsgspb" "px.dev/pixie/src/shared/services/authcontext" "px.dev/pixie/src/shared/services/jwtpb" + jwtutils "px.dev/pixie/src/shared/services/utils" ) type vzmgrClient interface { @@ -36,16 +45,50 @@ type vzmgrClient interface { GetVizierConnectionInfo(ctx context.Context, in *uuidpb.UUID, opts ...grpc.CallOption) (*cvmsgspb.VizierConnectionInfo, error) } +type scriptmgrClient interface { + CheckScriptExists(ctx context.Context, req *scriptmgrpb.CheckScriptExistsReq, opts ...grpc.CallOption) (*scriptmgrpb.CheckScriptExistsResp, error) +} + // VizierPassThroughProxy implements the VizierAPI and allows proxying the data to the actual // vizier cluster. type VizierPassThroughProxy struct { - nc *nats.Conn - vc vzmgrClient + nc *nats.Conn + vc vzmgrClient + sm scriptmgrClient + scriptModificationDisabled bool +} + +// getServiceCredentials returns JWT credentials for inter-service requests. +func getServiceCredentials(signingKey string) (string, error) { + claims := jwtutils.GenerateJWTForService("cloud api", viper.GetString("domain_name")) + return jwtutils.SignJWTClaims(claims, signingKey) } // NewVizierPassThroughProxy creates a new passthrough proxy. -func NewVizierPassThroughProxy(nc *nats.Conn, vc vzmgrClient) *VizierPassThroughProxy { - return &VizierPassThroughProxy{nc: nc, vc: vc} +func NewVizierPassThroughProxy(nc *nats.Conn, vc vzmgrClient, sm scriptmgrClient, scriptModificationDisabled bool) *VizierPassThroughProxy { + return &VizierPassThroughProxy{nc: nc, vc: vc, sm: sm, scriptModificationDisabled: scriptModificationDisabled} +} + +func (v *VizierPassThroughProxy) isScriptModified(ctx context.Context, script string) (bool, error) { + hash := sha256.New() + hash.Write([]byte(script)) + hashStr := hex.EncodeToString(hash.Sum(nil)) + req := &scriptmgrpb.CheckScriptExistsReq{Sha256Hash: hashStr} + + serviceAuthToken, err := getServiceCredentials(viper.GetString("jwt_signing_key")) + ctx = metadata.AppendToOutgoingContext(ctx, "authorization", + fmt.Sprintf("bearer %s", serviceAuthToken)) + + if err != nil { + return false, err + } + + resp, err := v.sm.CheckScriptExists(ctx, req) + + if err != nil { + return false, err + } + return !resp.Exists, nil } // ExecuteScript is the GRPC stream method. @@ -55,6 +98,17 @@ func (v *VizierPassThroughProxy) ExecuteScript(req *vizierpb.ExecuteScriptReques return err } defer rp.Finish() + if v.scriptModificationDisabled { + modified, err := v.isScriptModified(srv.Context(), req.QueryStr) + if err != nil { + return err + } + + if modified { + return status.Error(codes.InvalidArgument, "Script modification has been disabled") + } + } + vizReq := rp.prepareVizierRequest() vizReq.Msg = &cvmsgspb.C2VAPIStreamRequest_ExecReq{ExecReq: req} if err := rp.sendMessageToVizier(vizReq); err != nil { diff --git a/src/cloud/api/ptproxy/vizier_pt_proxy_test.go b/src/cloud/api/ptproxy/vizier_pt_proxy_test.go index 9d233fe890f..aea177f17ef 100644 --- a/src/cloud/api/ptproxy/vizier_pt_proxy_test.go +++ b/src/cloud/api/ptproxy/vizier_pt_proxy_test.go @@ -44,6 +44,7 @@ import ( "px.dev/pixie/src/api/proto/uuidpb" "px.dev/pixie/src/api/proto/vizierpb" "px.dev/pixie/src/cloud/api/ptproxy" + "px.dev/pixie/src/cloud/scriptmgr/scriptmgrpb" "px.dev/pixie/src/cloud/shared/vzshard" "px.dev/pixie/src/shared/cvmsgspb" "px.dev/pixie/src/shared/services/env" @@ -65,15 +66,15 @@ type testState struct { conn *grpc.ClientConn } -func createTestState(t *testing.T) (*testState, func(t *testing.T)) { +func createTestState(t *testing.T, scriptModificationDisabled bool) (*testState, func(t *testing.T)) { lis := bufconn.Listen(bufSize) env := env.New("withpixie.ai") s := server.CreateGRPCServer(env, &server.GRPCServerOptions{}) nc, natsCleanup := testingutils.MustStartTestNATS(t) - vizierpb.RegisterVizierServiceServer(s, ptproxy.NewVizierPassThroughProxy(nc, &fakeVzMgr{})) - vizierpb.RegisterVizierDebugServiceServer(s, ptproxy.NewVizierPassThroughProxy(nc, &fakeVzMgr{})) + vizierpb.RegisterVizierServiceServer(s, ptproxy.NewVizierPassThroughProxy(nc, &fakeVzMgr{}, &fakeScriptMgr{}, scriptModificationDisabled)) + vizierpb.RegisterVizierDebugServiceServer(s, ptproxy.NewVizierPassThroughProxy(nc, &fakeVzMgr{}, &fakeScriptMgr{}, scriptModificationDisabled)) eg := errgroup.Group{} eg.Go(func() error { return s.Serve(lis) }) @@ -112,7 +113,7 @@ func createDialer(lis *bufconn.Listener) func(ctx context.Context, url string) ( func TestVizierPassThroughProxy_ExecuteScript(t *testing.T) { viper.Set("jwt_signing_key", "the-key") - ts, cleanup := createTestState(t) + ts, cleanup := createTestState(t, false) defer cleanup(t) client := vizierpb.NewVizierServiceClient(ts.conn) @@ -283,10 +284,144 @@ func TestVizierPassThroughProxy_ExecuteScript(t *testing.T) { } } +func TestVizierPassThroughProxy_ExecuteScriptWithScriptModificationEnabled(t *testing.T) { + viper.Set("jwt_signing_key", "the-key") + + ts, cleanup := createTestState(t, true) + defer cleanup(t) + + client := vizierpb.NewVizierServiceClient(ts.conn) + validTestToken := testingutils.GenerateTestJWTToken(t, viper.GetString("jwt_signing_key")) + + testCases := []struct { + name string + + clusterID string + authToken string + pxlString string + respFromVizier []*cvmsgspb.V2CAPIStreamResponse + + expGRPCError error + expGRPCResponses []*vizierpb.ExecuteScriptResponse + }{ + { + name: "Request with modified pxl script", + + clusterID: "00000000-1111-2222-2222-333333333333", + pxlString: "import pxl", + authToken: validTestToken, + expGRPCError: status.Error(codes.InvalidArgument, "Script modification has been disabled"), + expGRPCResponses: nil, + }, + { + name: "Request with not modified pxl script", + + clusterID: "00000000-1111-2222-2222-333333333333", + pxlString: "liveview1 pxl", + authToken: validTestToken, + expGRPCError: nil, + expGRPCResponses: []*vizierpb.ExecuteScriptResponse{ + { + QueryID: "abc", + }, + { + QueryID: "abc", + }, + }, + respFromVizier: []*cvmsgspb.V2CAPIStreamResponse{ + { + Msg: &cvmsgspb.V2CAPIStreamResponse_ExecResp{ExecResp: &vizierpb.ExecuteScriptResponse{QueryID: "abc"}}, + }, + { + Msg: &cvmsgspb.V2CAPIStreamResponse_ExecResp{ExecResp: &vizierpb.ExecuteScriptResponse{QueryID: "abc"}}, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + if len(tc.authToken) > 0 { + ctx = metadata.AppendToOutgoingContext(ctx, "authorization", + fmt.Sprintf("bearer %s", tc.authToken)) + } + + ctx, cancel := context.WithCancel(ctx) + defer cancel() + resp, err := client.ExecuteScript(ctx, + &vizierpb.ExecuteScriptRequest{ClusterID: tc.clusterID, QueryStr: tc.pxlString}) + require.NoError(t, err) + fv := newFakeVizier(t, uuid.FromStringOrNil(tc.clusterID), ts.nc) + fv.Run(t, tc.respFromVizier) + defer fv.Stop() + + grpcDataCh := make(chan *vizierpb.ExecuteScriptResponse) + var gotReadErr error + var eg errgroup.Group + eg.Go(func() error { + defer close(grpcDataCh) + for { + d, err := resp.Recv() + if err != nil && err != io.EOF { + gotReadErr = err + } + if err == io.EOF { + return nil + } + if d == nil { + return nil + } + grpcDataCh <- d + } + }) + + var responses []*vizierpb.ExecuteScriptResponse + eg.Go(func() error { + timeout := time.NewTimer(defaultTimeout) + defer timeout.Stop() + + for { + select { + case <-resp.Context().Done(): + return nil + case <-timeout.C: + return fmt.Errorf("timeout waiting for data on grpc channel") + case msg := <-grpcDataCh: + + if msg == nil { + return nil + } + responses = append(responses, msg) + } + } + }) + + err = eg.Wait() + if err != nil { + t.Fatalf("Got error while streaming grpc: %v", err) + } + if tc.expGRPCError != nil { + if gotReadErr == nil { + t.Fatal("Expected to get GRPC error") + } + assert.Equal(t, status.Code(tc.expGRPCError), status.Code(gotReadErr)) + } + if tc.expGRPCResponses == nil { + if len(responses) != 0 { + t.Fatal("Expected to get no responses") + } + } else { + assert.Equal(t, tc.expGRPCResponses, responses) + } + }) + } +} + func TestVizierPassThroughProxy_HealthCheck(t *testing.T) { viper.Set("jwt_signing_key", "the-key") - ts, cleanup := createTestState(t) + ts, cleanup := createTestState(t, false) defer cleanup(t) client := vizierpb.NewVizierServiceClient(ts.conn) @@ -463,7 +598,7 @@ func TestVizierPassThroughProxy_HealthCheck(t *testing.T) { func TestVizierPassThroughProxy_DebugLog(t *testing.T) { viper.Set("jwt_signing_key", "the-key") - ts, cleanup := createTestState(t) + ts, cleanup := createTestState(t, false) defer cleanup(t) client := vizierpb.NewVizierDebugServiceClient(ts.conn) @@ -582,7 +717,7 @@ func TestVizierPassThroughProxy_DebugLog(t *testing.T) { func TestVizierPassThroughProxy_DebugPods(t *testing.T) { viper.Set("jwt_signing_key", "the-key") - ts, cleanup := createTestState(t) + ts, cleanup := createTestState(t, false) defer cleanup(t) client := vizierpb.NewVizierDebugServiceClient(ts.conn) @@ -719,6 +854,20 @@ func TestVizierPassThroughProxy_DebugPods(t *testing.T) { } } +type fakeScriptMgr struct{} + +func (s *fakeScriptMgr) CheckScriptExists(ctx context.Context, req *scriptmgrpb.CheckScriptExistsReq, opts ...grpc.CallOption) (*scriptmgrpb.CheckScriptExistsResp, error) { + hash := "488f131003f415a61090901c544e0ace731e8a85b12ce0aea770273d656f08e0" // sha256 of "liveview1 pxl" + + scripts := map[string]bool{ + hash: true, + } + _, ok := scripts[req.Sha256Hash] + return &scriptmgrpb.CheckScriptExistsResp{ + Exists: ok, + }, nil +} + type fakeVzMgr struct{} func (v *fakeVzMgr) GetVizierInfo(ctx context.Context, in *uuidpb.UUID, opts ...grpc.CallOption) (*cvmsgspb.VizierInfo, error) { diff --git a/src/cloud/scriptmgr/controllers/server.go b/src/cloud/scriptmgr/controllers/server.go index b867d523021..7c484fd3715 100644 --- a/src/cloud/scriptmgr/controllers/server.go +++ b/src/cloud/scriptmgr/controllers/server.go @@ -20,6 +20,8 @@ package controllers import ( "context" + "crypto/sha256" + "encoding/hex" "fmt" "strings" "time" @@ -51,8 +53,9 @@ type liveViewModel struct { } type scriptStore struct { - Scripts map[uuid.UUID]*scriptModel - LiveViews map[uuid.UUID]*liveViewModel + Scripts map[uuid.UUID]*scriptModel + LiveViews map[uuid.UUID]*liveViewModel + ScriptHashes map[string]bool } // Server implements the GRPC Server for the scriptmgr service. @@ -72,8 +75,9 @@ func NewServer(bundleBucket string, bundlePath string, sc stiface.Client) *Serve bundlePath: bundlePath, sc: sc, store: &scriptStore{ - Scripts: make(map[uuid.UUID]*scriptModel), - LiveViews: make(map[uuid.UUID]*liveViewModel), + Scripts: make(map[uuid.UUID]*scriptModel), + LiveViews: make(map[uuid.UUID]*liveViewModel), + ScriptHashes: make(map[string]bool), }, storeLastUpdate: time.Unix(0, 0), SeedUUID: uuid.Must(uuid.NewV4()), @@ -117,6 +121,13 @@ func (s *Server) addScript(name string, bundleScript *pixieScript, hasLiveView b } } +func (s *Server) addScriptHash(pxl string) { + scriptHash := sha256.New() + scriptHash.Write([]byte(pxl)) + str := hex.EncodeToString(scriptHash.Sum(nil)) + s.store.ScriptHashes[str] = true +} + func (s *Server) updateStore() error { b, err := getBundle(s.sc, s.bundleBucket, s.bundlePath) if err != nil { @@ -132,6 +143,7 @@ func (s *Server) updateStore() error { errorMsgs = append(errorMsgs, fmt.Sprintf("Error in Live View %s: %s", name, err.Error())) } } + s.addScriptHash(bundleScript.Pxl) } if len(errorMsgs) > 0 { @@ -246,3 +258,12 @@ func (s *Server) GetScriptContents(ctx context.Context, req *scriptmgrpb.GetScri Contents: script.pxl, }, nil } + +// CheckScriptExists returns if a script with the given hash exists. +func (s *Server) CheckScriptExists(ctx context.Context, req *scriptmgrpb.CheckScriptExistsReq) (*scriptmgrpb.CheckScriptExistsResp, error) { + hash := req.Sha256Hash + _, ok := s.store.ScriptHashes[hash] + return &scriptmgrpb.CheckScriptExistsResp{ + Exists: ok, + }, nil +} diff --git a/src/cloud/scriptmgr/controllers/server_test.go b/src/cloud/scriptmgr/controllers/server_test.go index 210c0177445..fa5b2adced0 100644 --- a/src/cloud/scriptmgr/controllers/server_test.go +++ b/src/cloud/scriptmgr/controllers/server_test.go @@ -323,3 +323,46 @@ func TestScriptMgr_GetScriptContents(t *testing.T) { }) } } + +func TestScriptMgr_CheckScriptExists(t *testing.T) { + testCases := []struct { + name string + scriptHash string + exists bool + expectErr bool + }{ + { + name: "Script with matching hash should return script.", + scriptHash: "488f131003f415a61090901c544e0ace731e8a85b12ce0aea770273d656f08e0", // hex encoded output of 'liveview1' hash + exists: true, + expectErr: false, + }, + { + name: "Hash not in bundle returns false.", + scriptHash: "not-a-real-script", + exists: false, + expectErr: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + c := mustSetupFakeBucket(t, testBundle) + s := controllers.NewServer(bundleBucket, bundlePath, c) + ctx := context.Background() + req := &scriptmgrpb.CheckScriptExistsReq{ + Sha256Hash: tc.scriptHash, + } + expectedResp := &scriptmgrpb.CheckScriptExistsResp{ + Exists: tc.exists, + } + resp, err := s.CheckScriptExists(ctx, req) + if tc.expectErr { + require.NotNil(t, err) + } else { + require.NoError(t, err) + assert.Equal(t, expectedResp, resp) + } + }) + } +} diff --git a/src/cloud/scriptmgr/scriptmgrpb/mock/scriptmgrpb_mock.gen.go b/src/cloud/scriptmgr/scriptmgrpb/mock/scriptmgrpb_mock.gen.go index f6944936526..4dfa6f6a7b2 100644 --- a/src/cloud/scriptmgr/scriptmgrpb/mock/scriptmgrpb_mock.gen.go +++ b/src/cloud/scriptmgr/scriptmgrpb/mock/scriptmgrpb_mock.gen.go @@ -36,6 +36,26 @@ func (m *MockScriptMgrServiceClient) EXPECT() *MockScriptMgrServiceClientMockRec return m.recorder } +// CheckScriptExists mocks base method. +func (m *MockScriptMgrServiceClient) CheckScriptExists(ctx context.Context, in *scriptmgrpb.CheckScriptExistsReq, opts ...grpc.CallOption) (*scriptmgrpb.CheckScriptExistsResp, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, in} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "CheckScriptExists", varargs...) + ret0, _ := ret[0].(*scriptmgrpb.CheckScriptExistsResp) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CheckScriptExists indicates an expected call of CheckScriptExists. +func (mr *MockScriptMgrServiceClientMockRecorder) CheckScriptExists(ctx, in interface{}, opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, in}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckScriptExists", reflect.TypeOf((*MockScriptMgrServiceClient)(nil).CheckScriptExists), varargs...) +} + // GetLiveViewContents mocks base method. func (m *MockScriptMgrServiceClient) GetLiveViewContents(ctx context.Context, in *scriptmgrpb.GetLiveViewContentsReq, opts ...grpc.CallOption) (*scriptmgrpb.GetLiveViewContentsResp, error) { m.ctrl.T.Helper() @@ -139,6 +159,21 @@ func (m *MockScriptMgrServiceServer) EXPECT() *MockScriptMgrServiceServerMockRec return m.recorder } +// CheckScriptExists mocks base method. +func (m *MockScriptMgrServiceServer) CheckScriptExists(arg0 context.Context, arg1 *scriptmgrpb.CheckScriptExistsReq) (*scriptmgrpb.CheckScriptExistsResp, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CheckScriptExists", arg0, arg1) + ret0, _ := ret[0].(*scriptmgrpb.CheckScriptExistsResp) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CheckScriptExists indicates an expected call of CheckScriptExists. +func (mr *MockScriptMgrServiceServerMockRecorder) CheckScriptExists(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckScriptExists", reflect.TypeOf((*MockScriptMgrServiceServer)(nil).CheckScriptExists), arg0, arg1) +} + // GetLiveViewContents mocks base method. func (m *MockScriptMgrServiceServer) GetLiveViewContents(arg0 context.Context, arg1 *scriptmgrpb.GetLiveViewContentsReq) (*scriptmgrpb.GetLiveViewContentsResp, error) { m.ctrl.T.Helper() diff --git a/src/cloud/scriptmgr/scriptmgrpb/service.pb.go b/src/cloud/scriptmgr/scriptmgrpb/service.pb.go index c4a77f85d41..447d341ddfc 100755 --- a/src/cloud/scriptmgr/scriptmgrpb/service.pb.go +++ b/src/cloud/scriptmgr/scriptmgrpb/service.pb.go @@ -509,6 +509,92 @@ func (m *GetScriptContentsResp) GetContents() string { return "" } +type CheckScriptExistsReq struct { + Sha256Hash string `protobuf:"bytes,1,opt,name=sha256_hash,json=sha256Hash,proto3" json:"sha256_hash,omitempty"` +} + +func (m *CheckScriptExistsReq) Reset() { *m = CheckScriptExistsReq{} } +func (*CheckScriptExistsReq) ProtoMessage() {} +func (*CheckScriptExistsReq) Descriptor() ([]byte, []int) { + return fileDescriptor_e19e341d77057158, []int{10} +} +func (m *CheckScriptExistsReq) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *CheckScriptExistsReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_CheckScriptExistsReq.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *CheckScriptExistsReq) XXX_Merge(src proto.Message) { + xxx_messageInfo_CheckScriptExistsReq.Merge(m, src) +} +func (m *CheckScriptExistsReq) XXX_Size() int { + return m.Size() +} +func (m *CheckScriptExistsReq) XXX_DiscardUnknown() { + xxx_messageInfo_CheckScriptExistsReq.DiscardUnknown(m) +} + +var xxx_messageInfo_CheckScriptExistsReq proto.InternalMessageInfo + +func (m *CheckScriptExistsReq) GetSha256Hash() string { + if m != nil { + return m.Sha256Hash + } + return "" +} + +type CheckScriptExistsResp struct { + Exists bool `protobuf:"varint,1,opt,name=exists,proto3" json:"exists,omitempty"` +} + +func (m *CheckScriptExistsResp) Reset() { *m = CheckScriptExistsResp{} } +func (*CheckScriptExistsResp) ProtoMessage() {} +func (*CheckScriptExistsResp) Descriptor() ([]byte, []int) { + return fileDescriptor_e19e341d77057158, []int{11} +} +func (m *CheckScriptExistsResp) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *CheckScriptExistsResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_CheckScriptExistsResp.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *CheckScriptExistsResp) XXX_Merge(src proto.Message) { + xxx_messageInfo_CheckScriptExistsResp.Merge(m, src) +} +func (m *CheckScriptExistsResp) XXX_Size() int { + return m.Size() +} +func (m *CheckScriptExistsResp) XXX_DiscardUnknown() { + xxx_messageInfo_CheckScriptExistsResp.DiscardUnknown(m) +} + +var xxx_messageInfo_CheckScriptExistsResp proto.InternalMessageInfo + +func (m *CheckScriptExistsResp) GetExists() bool { + if m != nil { + return m.Exists + } + return false +} + func init() { proto.RegisterType((*GetLiveViewsReq)(nil), "px.services.GetLiveViewsReq") proto.RegisterType((*LiveViewMetadata)(nil), "px.services.LiveViewMetadata") @@ -520,6 +606,8 @@ func init() { proto.RegisterType((*GetScriptsResp)(nil), "px.services.GetScriptsResp") proto.RegisterType((*GetScriptContentsReq)(nil), "px.services.GetScriptContentsReq") proto.RegisterType((*GetScriptContentsResp)(nil), "px.services.GetScriptContentsResp") + proto.RegisterType((*CheckScriptExistsReq)(nil), "px.services.CheckScriptExistsReq") + proto.RegisterType((*CheckScriptExistsResp)(nil), "px.services.CheckScriptExistsResp") } func init() { @@ -527,47 +615,52 @@ func init() { } var fileDescriptor_e19e341d77057158 = []byte{ - // 629 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x54, 0x4b, 0x6f, 0xd3, 0x40, - 0x10, 0xce, 0xa6, 0x55, 0x49, 0xc7, 0xe9, 0x6b, 0x79, 0x45, 0x2e, 0xdd, 0xa4, 0x06, 0x89, 0x5c, - 0x6a, 0x4b, 0x41, 0x08, 0x81, 0xb8, 0xb4, 0x04, 0x55, 0x16, 0x20, 0x21, 0x57, 0xed, 0xa1, 0x1c, - 0x82, 0x1f, 0x4b, 0xba, 0x92, 0x53, 0x2f, 0x5e, 0xc7, 0xe4, 0xc8, 0x85, 0x3b, 0x47, 0x7e, 0x02, - 0x3f, 0x85, 0x63, 0x8f, 0x3d, 0x55, 0xd4, 0xe5, 0xc0, 0xb1, 0x3f, 0x01, 0xf9, 0x95, 0x38, 0x69, - 0xda, 0x9c, 0xb8, 0x24, 0xf3, 0xf8, 0xe6, 0x9b, 0xd9, 0xf9, 0x76, 0x0d, 0x5b, 0xc2, 0xb7, 0x35, - 0xdb, 0xf5, 0xfa, 0x8e, 0x26, 0x6c, 0x9f, 0xf1, 0xa0, 0xd7, 0xf5, 0x47, 0x16, 0xb7, 0x34, 0x41, - 0xfd, 0x90, 0xd9, 0x54, 0xe5, 0xbe, 0x17, 0x78, 0x58, 0xe2, 0x03, 0x35, 0x8b, 0x08, 0x79, 0xab, - 0xcb, 0x82, 0xa3, 0xbe, 0xa5, 0xda, 0x5e, 0x4f, 0xeb, 0x7a, 0x5d, 0x4f, 0x4b, 0x30, 0x56, 0xff, - 0x53, 0xe2, 0x25, 0x4e, 0x62, 0xa5, 0xb5, 0x72, 0x3d, 0x6e, 0x65, 0x72, 0x96, 0xc2, 0xb4, 0x7e, - 0x9f, 0x39, 0xdc, 0x4a, 0xfe, 0x32, 0xc0, 0xc6, 0x38, 0x20, 0x64, 0x82, 0x5b, 0xf1, 0x6f, 0x9a, - 0x56, 0xd6, 0x60, 0x65, 0x97, 0x06, 0x6f, 0x59, 0x48, 0x0f, 0x18, 0xfd, 0x22, 0x0c, 0xfa, 0x59, - 0xb1, 0x61, 0x35, 0xf7, 0xdf, 0xd1, 0xc0, 0x74, 0xcc, 0xc0, 0xc4, 0x8f, 0xa1, 0xcc, 0x9c, 0x1a, - 0x6a, 0xa0, 0xa6, 0xd4, 0x5a, 0x51, 0xf9, 0x40, 0x4d, 0x1b, 0xa9, 0xfb, 0xfb, 0x7a, 0x7b, 0x67, - 0x21, 0x3a, 0xab, 0x97, 0xf5, 0xb6, 0x51, 0x66, 0x0e, 0xc6, 0x30, 0xef, 0x50, 0x61, 0xd7, 0xca, - 0x0d, 0xd4, 0x5c, 0x34, 0x12, 0x3b, 0x8e, 0x1d, 0x9b, 0x3d, 0x5a, 0x9b, 0x4b, 0x63, 0xb1, 0xad, - 0xbc, 0x87, 0xd5, 0xf1, 0xbe, 0x82, 0xe3, 0x97, 0x00, 0x2e, 0x0b, 0x69, 0x27, 0x8c, 0x23, 0x35, - 0xd4, 0x98, 0x6b, 0x4a, 0xad, 0x0d, 0xb5, 0xb0, 0x1c, 0x75, 0x72, 0x2e, 0x63, 0xd1, 0xcd, 0x19, - 0x94, 0x0f, 0x70, 0xaf, 0xc0, 0xf8, 0xca, 0x3b, 0x0e, 0xe8, 0x71, 0x10, 0x1f, 0x08, 0x6f, 0x43, - 0x75, 0xc8, 0xdb, 0xb9, 0xfe, 0x18, 0xcb, 0xd1, 0x59, 0x1d, 0xf2, 0x7a, 0xbd, 0x6d, 0x40, 0xce, - 0xad, 0x3b, 0xca, 0x0f, 0x04, 0xf7, 0xa7, 0xb2, 0x0b, 0x8e, 0x9f, 0x43, 0xa5, 0x97, 0xcd, 0x93, - 0x51, 0xcf, 0x18, 0x7a, 0x08, 0xc7, 0x9b, 0x50, 0xe5, 0x03, 0xb7, 0x63, 0x67, 0x74, 0xd9, 0xd6, - 0x24, 0x3e, 0x70, 0xf3, 0x0e, 0xb8, 0x0e, 0x73, 0x21, 0x13, 0xc9, 0xee, 0xa4, 0xd6, 0x52, 0x4c, - 0x9c, 0x48, 0xa8, 0x1e, 0x30, 0x61, 0xc4, 0x19, 0x65, 0x05, 0x96, 0x76, 0x69, 0xb0, 0x97, 0xdc, - 0xae, 0x44, 0xbf, 0x6f, 0x08, 0x96, 0x53, 0xf7, 0xbf, 0xc9, 0x87, 0x15, 0x58, 0x3a, 0x32, 0x45, - 0x67, 0xb8, 0xd6, 0xda, 0x7c, 0x03, 0x35, 0x2b, 0x86, 0x74, 0x64, 0x8a, 0xfc, 0xb8, 0xca, 0x2e, - 0x2c, 0x17, 0x07, 0x13, 0x1c, 0x3f, 0x85, 0x5b, 0xe9, 0x2b, 0xc8, 0xd5, 0x5d, 0x1f, 0x5b, 0xd4, - 0xf8, 0xd0, 0x46, 0x8e, 0x55, 0x0c, 0xb8, 0x33, 0x24, 0x2a, 0xea, 0xfa, 0x02, 0x16, 0x53, 0xc8, - 0x0d, 0xa2, 0x56, 0xa3, 0xb3, 0x7a, 0x25, 0x2d, 0xd5, 0xdb, 0x46, 0x25, 0xc5, 0xeb, 0x8e, 0xe2, - 0xc2, 0xdd, 0x29, 0x9c, 0x82, 0xe3, 0x67, 0x57, 0xd4, 0xbc, 0x71, 0xc8, 0x91, 0x96, 0x32, 0x54, - 0x26, 0x74, 0x1c, 0xfa, 0xad, 0x3f, 0x65, 0x58, 0xcd, 0x0a, 0xbb, 0xfe, 0x5e, 0x4a, 0x85, 0xdf, - 0x40, 0xb5, 0xf8, 0x04, 0xf0, 0x83, 0xb1, 0x3e, 0x13, 0xaf, 0x52, 0xde, 0xb8, 0x21, 0x2b, 0x38, - 0xfe, 0x08, 0xb7, 0xa7, 0xdc, 0x4f, 0xfc, 0xf0, 0xba, 0xaa, 0xc2, 0x1e, 0xe5, 0x47, 0xb3, 0x41, - 0x82, 0xe3, 0xd7, 0x00, 0x23, 0x39, 0xb1, 0x3c, 0x59, 0x33, 0xba, 0x80, 0xf2, 0xfa, 0xb5, 0x39, - 0xc1, 0xf1, 0x21, 0xac, 0x5d, 0x59, 0x3c, 0xde, 0x9c, 0x5e, 0x51, 0x1c, 0x52, 0x99, 0x05, 0x11, - 0x7c, 0x67, 0xfb, 0xe4, 0x9c, 0x94, 0x4e, 0xcf, 0x49, 0xe9, 0xf2, 0x9c, 0xa0, 0xaf, 0x11, 0x41, - 0x3f, 0x23, 0x82, 0x7e, 0x45, 0x04, 0x9d, 0x44, 0x04, 0xfd, 0x8e, 0x08, 0xfa, 0x1b, 0x91, 0xd2, - 0x65, 0x44, 0xd0, 0xf7, 0x0b, 0x52, 0x3a, 0xb9, 0x20, 0xa5, 0xd3, 0x0b, 0x52, 0x3a, 0x94, 0x0a, - 0x5f, 0x66, 0x6b, 0x21, 0xf9, 0x2c, 0x3e, 0xf9, 0x17, 0x00, 0x00, 0xff, 0xff, 0x15, 0xf6, 0xee, - 0xa0, 0xc3, 0x05, 0x00, 0x00, + // 705 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x55, 0xcd, 0x4e, 0xdb, 0x4a, + 0x14, 0xce, 0x24, 0x88, 0x9b, 0x1c, 0x87, 0xbf, 0xb9, 0xc0, 0x8d, 0xc2, 0xc5, 0x09, 0xbe, 0x57, + 0x6a, 0x36, 0xd8, 0x52, 0x2a, 0x5a, 0xb5, 0xea, 0x06, 0x08, 0x4a, 0xa3, 0xb6, 0x52, 0x65, 0x04, + 0x0b, 0xba, 0x48, 0x1d, 0x7b, 0x1a, 0x8f, 0x9a, 0x90, 0x69, 0xc6, 0x49, 0xb3, 0xec, 0xa6, 0xfb, + 0x2e, 0xfb, 0x08, 0x7d, 0x8c, 0x2e, 0xbb, 0x64, 0xc9, 0x0a, 0x15, 0xb3, 0xe9, 0x92, 0x47, 0xa8, + 0x3c, 0x63, 0x27, 0x4e, 0x30, 0xb0, 0xea, 0x06, 0xe6, 0x9c, 0xf9, 0xce, 0x77, 0x7e, 0xbe, 0x33, + 0x0e, 0x6c, 0xf3, 0xbe, 0x6d, 0xd8, 0x9d, 0xde, 0xc0, 0x31, 0xb8, 0xdd, 0xa7, 0xcc, 0xeb, 0xb6, + 0xfb, 0x93, 0x13, 0x6b, 0x19, 0x9c, 0xf4, 0x87, 0xd4, 0x26, 0x3a, 0xeb, 0xf7, 0xbc, 0x1e, 0x56, + 0xd8, 0x48, 0x0f, 0x3d, 0xbc, 0xb8, 0xdd, 0xa6, 0x9e, 0x3b, 0x68, 0xe9, 0x76, 0xaf, 0x6b, 0xb4, + 0x7b, 0xed, 0x9e, 0x21, 0x30, 0xad, 0xc1, 0x3b, 0x61, 0x09, 0x43, 0x9c, 0x64, 0x6c, 0xb1, 0x14, + 0xa4, 0xb2, 0x18, 0x95, 0x30, 0x63, 0x30, 0xa0, 0x0e, 0x6b, 0x89, 0x7f, 0x21, 0x60, 0x73, 0x1a, + 0x30, 0xa4, 0x9c, 0xb5, 0x82, 0xbf, 0xf2, 0x5a, 0x5b, 0x81, 0xa5, 0x3a, 0xf1, 0x5e, 0xd2, 0x21, + 0x39, 0xa6, 0xe4, 0x23, 0x37, 0xc9, 0x07, 0xcd, 0x86, 0xe5, 0xc8, 0x7e, 0x45, 0x3c, 0xcb, 0xb1, + 0x3c, 0x0b, 0x3f, 0x80, 0x34, 0x75, 0x0a, 0xa8, 0x8c, 0x2a, 0x4a, 0x75, 0x49, 0x67, 0x23, 0x5d, + 0x26, 0xd2, 0x8f, 0x8e, 0x1a, 0xb5, 0xbd, 0x79, 0xff, 0xa2, 0x94, 0x6e, 0xd4, 0xcc, 0x34, 0x75, + 0x30, 0x86, 0x39, 0x87, 0x70, 0xbb, 0x90, 0x2e, 0xa3, 0x4a, 0xce, 0x14, 0xe7, 0xc0, 0x77, 0x6a, + 0x75, 0x49, 0x21, 0x23, 0x7d, 0xc1, 0x59, 0x7b, 0x0d, 0xcb, 0xd3, 0x79, 0x39, 0xc3, 0xcf, 0x00, + 0x3a, 0x74, 0x48, 0x9a, 0xc3, 0xc0, 0x53, 0x40, 0xe5, 0x4c, 0x45, 0xa9, 0x6e, 0xea, 0xb1, 0xe1, + 0xe8, 0xb3, 0x75, 0x99, 0xb9, 0x4e, 0xc4, 0xa0, 0xbd, 0x81, 0xf5, 0x18, 0xe3, 0x7e, 0xef, 0xd4, + 0x23, 0xa7, 0x5e, 0xd0, 0x10, 0xde, 0x85, 0xfc, 0x98, 0xb7, 0x79, 0x7b, 0x1b, 0x8b, 0xfe, 0x45, + 0x09, 0xa2, 0xf8, 0x46, 0xcd, 0x84, 0x88, 0xbb, 0xe1, 0x68, 0x5f, 0x11, 0xfc, 0x93, 0xc8, 0xce, + 0x19, 0x7e, 0x02, 0xd9, 0x6e, 0x58, 0x4f, 0x48, 0x7d, 0x4f, 0xd1, 0x63, 0x38, 0xde, 0x82, 0x3c, + 0x1b, 0x75, 0x9a, 0x76, 0x48, 0x17, 0x4e, 0x4d, 0x61, 0xa3, 0x4e, 0x94, 0x01, 0x97, 0x20, 0x33, + 0xa4, 0x5c, 0xcc, 0x4e, 0xa9, 0x2e, 0x04, 0xc4, 0x42, 0x42, 0xfd, 0x98, 0x72, 0x33, 0xb8, 0xd1, + 0x96, 0x60, 0xa1, 0x4e, 0xbc, 0x43, 0xb1, 0x5d, 0x42, 0xbf, 0xcf, 0x08, 0x16, 0xa5, 0xf9, 0xc7, + 0xe4, 0xc3, 0x1a, 0x2c, 0xb8, 0x16, 0x6f, 0x8e, 0xc7, 0x5a, 0x98, 0x2b, 0xa3, 0x4a, 0xd6, 0x54, + 0x5c, 0x8b, 0x47, 0xed, 0x6a, 0x75, 0x58, 0x8c, 0x17, 0xc6, 0x19, 0xde, 0x81, 0xbf, 0xe4, 0x2b, + 0x88, 0xd4, 0xdd, 0x98, 0x1a, 0xd4, 0x74, 0xd1, 0x66, 0x84, 0xd5, 0x4c, 0x58, 0x1d, 0x13, 0xc5, + 0x75, 0x7d, 0x0a, 0x39, 0x09, 0xb9, 0x43, 0xd4, 0xbc, 0x7f, 0x51, 0xca, 0xca, 0xd0, 0x46, 0xcd, + 0xcc, 0x4a, 0x7c, 0xc3, 0xd1, 0x3a, 0xb0, 0x96, 0xc0, 0xc9, 0x19, 0x7e, 0x7c, 0x43, 0xcd, 0x3b, + 0x8b, 0x9c, 0x68, 0x59, 0x84, 0xec, 0x8c, 0x8e, 0x63, 0x5b, 0xab, 0xc3, 0xea, 0xbe, 0x4b, 0xec, + 0xf7, 0x32, 0xf8, 0x60, 0x44, 0xb9, 0xec, 0xc0, 0x00, 0x85, 0xbb, 0x56, 0x75, 0xe7, 0x51, 0xd3, + 0xb5, 0xb8, 0x2b, 0xf2, 0xe5, 0xe4, 0x1e, 0x1e, 0x0a, 0xf7, 0x73, 0x8b, 0xbb, 0x26, 0xf0, 0xf1, + 0x59, 0x33, 0x60, 0x2d, 0x81, 0x88, 0x33, 0xbc, 0x0e, 0xf3, 0x44, 0x58, 0x82, 0x24, 0x6b, 0x86, + 0x56, 0xf5, 0x7b, 0x06, 0x96, 0xc3, 0x92, 0xdb, 0xfd, 0x43, 0xd9, 0x04, 0x7e, 0x01, 0xf9, 0xf8, + 0xe3, 0xc3, 0xff, 0x4e, 0x75, 0x38, 0xf3, 0x3d, 0x28, 0x6e, 0xde, 0x71, 0xcb, 0x19, 0x7e, 0x0b, + 0x7f, 0x27, 0xbc, 0x0c, 0xfc, 0xdf, 0x6d, 0x51, 0x31, 0x05, 0x8b, 0xff, 0xdf, 0x0f, 0xe2, 0x0c, + 0x1f, 0x00, 0x4c, 0x16, 0x09, 0x17, 0x67, 0x63, 0x26, 0xab, 0x5f, 0xdc, 0xb8, 0xf5, 0x8e, 0x33, + 0x7c, 0x02, 0x2b, 0x37, 0x24, 0xc7, 0x5b, 0xc9, 0x11, 0xf1, 0x22, 0xb5, 0xfb, 0x20, 0x92, 0xfb, + 0x86, 0x2e, 0x33, 0xdc, 0x49, 0x0b, 0x30, 0xc3, 0x9d, 0x28, 0xed, 0xde, 0xee, 0xd9, 0xa5, 0x9a, + 0x3a, 0xbf, 0x54, 0x53, 0xd7, 0x97, 0x2a, 0xfa, 0xe4, 0xab, 0xe8, 0x9b, 0xaf, 0xa2, 0x1f, 0xbe, + 0x8a, 0xce, 0x7c, 0x15, 0xfd, 0xf4, 0x55, 0xf4, 0xcb, 0x57, 0x53, 0xd7, 0xbe, 0x8a, 0xbe, 0x5c, + 0xa9, 0xa9, 0xb3, 0x2b, 0x35, 0x75, 0x7e, 0xa5, 0xa6, 0x4e, 0x94, 0xd8, 0xef, 0x4d, 0x6b, 0x5e, + 0x7c, 0xec, 0x1f, 0xfe, 0x0e, 0x00, 0x00, 0xff, 0xff, 0xf4, 0x4f, 0x50, 0xf7, 0x99, 0x06, 0x00, + 0x00, } func (this *GetLiveViewsReq) Equal(that interface{}) bool { @@ -838,6 +931,54 @@ func (this *GetScriptContentsResp) Equal(that interface{}) bool { } return true } +func (this *CheckScriptExistsReq) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*CheckScriptExistsReq) + if !ok { + that2, ok := that.(CheckScriptExistsReq) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if this.Sha256Hash != that1.Sha256Hash { + return false + } + return true +} +func (this *CheckScriptExistsResp) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*CheckScriptExistsResp) + if !ok { + that2, ok := that.(CheckScriptExistsResp) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if this.Exists != that1.Exists { + return false + } + return true +} func (this *GetLiveViewsReq) GoString() string { if this == nil { return "nil" @@ -962,6 +1103,26 @@ func (this *GetScriptContentsResp) GoString() string { s = append(s, "}") return strings.Join(s, "") } +func (this *CheckScriptExistsReq) GoString() string { + if this == nil { + return "nil" + } + s := make([]string, 0, 5) + s = append(s, "&scriptmgrpb.CheckScriptExistsReq{") + s = append(s, "Sha256Hash: "+fmt.Sprintf("%#v", this.Sha256Hash)+",\n") + s = append(s, "}") + return strings.Join(s, "") +} +func (this *CheckScriptExistsResp) GoString() string { + if this == nil { + return "nil" + } + s := make([]string, 0, 5) + s = append(s, "&scriptmgrpb.CheckScriptExistsResp{") + s = append(s, "Exists: "+fmt.Sprintf("%#v", this.Exists)+",\n") + s = append(s, "}") + return strings.Join(s, "") +} func valueToGoStringService(v interface{}, typ string) string { rv := reflect.ValueOf(v) if rv.IsNil() { @@ -987,6 +1148,7 @@ type ScriptMgrServiceClient interface { GetLiveViewContents(ctx context.Context, in *GetLiveViewContentsReq, opts ...grpc.CallOption) (*GetLiveViewContentsResp, error) GetScripts(ctx context.Context, in *GetScriptsReq, opts ...grpc.CallOption) (*GetScriptsResp, error) GetScriptContents(ctx context.Context, in *GetScriptContentsReq, opts ...grpc.CallOption) (*GetScriptContentsResp, error) + CheckScriptExists(ctx context.Context, in *CheckScriptExistsReq, opts ...grpc.CallOption) (*CheckScriptExistsResp, error) } type scriptMgrServiceClient struct { @@ -1033,12 +1195,22 @@ func (c *scriptMgrServiceClient) GetScriptContents(ctx context.Context, in *GetS return out, nil } +func (c *scriptMgrServiceClient) CheckScriptExists(ctx context.Context, in *CheckScriptExistsReq, opts ...grpc.CallOption) (*CheckScriptExistsResp, error) { + out := new(CheckScriptExistsResp) + err := c.cc.Invoke(ctx, "/px.services.ScriptMgrService/CheckScriptExists", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // ScriptMgrServiceServer is the server API for ScriptMgrService service. type ScriptMgrServiceServer interface { GetLiveViews(context.Context, *GetLiveViewsReq) (*GetLiveViewsResp, error) GetLiveViewContents(context.Context, *GetLiveViewContentsReq) (*GetLiveViewContentsResp, error) GetScripts(context.Context, *GetScriptsReq) (*GetScriptsResp, error) GetScriptContents(context.Context, *GetScriptContentsReq) (*GetScriptContentsResp, error) + CheckScriptExists(context.Context, *CheckScriptExistsReq) (*CheckScriptExistsResp, error) } // UnimplementedScriptMgrServiceServer can be embedded to have forward compatible implementations. @@ -1057,6 +1229,9 @@ func (*UnimplementedScriptMgrServiceServer) GetScripts(ctx context.Context, req func (*UnimplementedScriptMgrServiceServer) GetScriptContents(ctx context.Context, req *GetScriptContentsReq) (*GetScriptContentsResp, error) { return nil, status.Errorf(codes.Unimplemented, "method GetScriptContents not implemented") } +func (*UnimplementedScriptMgrServiceServer) CheckScriptExists(ctx context.Context, req *CheckScriptExistsReq) (*CheckScriptExistsResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method CheckScriptExists not implemented") +} func RegisterScriptMgrServiceServer(s *grpc.Server, srv ScriptMgrServiceServer) { s.RegisterService(&_ScriptMgrService_serviceDesc, srv) @@ -1134,6 +1309,24 @@ func _ScriptMgrService_GetScriptContents_Handler(srv interface{}, ctx context.Co return interceptor(ctx, in, info, handler) } +func _ScriptMgrService_CheckScriptExists_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CheckScriptExistsReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ScriptMgrServiceServer).CheckScriptExists(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/px.services.ScriptMgrService/CheckScriptExists", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ScriptMgrServiceServer).CheckScriptExists(ctx, req.(*CheckScriptExistsReq)) + } + return interceptor(ctx, in, info, handler) +} + var _ScriptMgrService_serviceDesc = grpc.ServiceDesc{ ServiceName: "px.services.ScriptMgrService", HandlerType: (*ScriptMgrServiceServer)(nil), @@ -1154,6 +1347,10 @@ var _ScriptMgrService_serviceDesc = grpc.ServiceDesc{ MethodName: "GetScriptContents", Handler: _ScriptMgrService_GetScriptContents_Handler, }, + { + MethodName: "CheckScriptExists", + Handler: _ScriptMgrService_CheckScriptExists_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "src/cloud/scriptmgr/scriptmgrpb/service.proto", @@ -1553,6 +1750,69 @@ func (m *GetScriptContentsResp) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *CheckScriptExistsReq) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *CheckScriptExistsReq) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *CheckScriptExistsReq) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Sha256Hash) > 0 { + i -= len(m.Sha256Hash) + copy(dAtA[i:], m.Sha256Hash) + i = encodeVarintService(dAtA, i, uint64(len(m.Sha256Hash))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *CheckScriptExistsResp) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *CheckScriptExistsResp) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *CheckScriptExistsResp) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Exists { + i-- + if m.Exists { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + func encodeVarintService(dAtA []byte, offset int, v uint64) int { offset -= sovService(v) base := offset @@ -1721,6 +1981,31 @@ func (m *GetScriptContentsResp) Size() (n int) { return n } +func (m *CheckScriptExistsReq) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Sha256Hash) + if l > 0 { + n += 1 + l + sovService(uint64(l)) + } + return n +} + +func (m *CheckScriptExistsResp) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Exists { + n += 2 + } + return n +} + func sovService(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -1843,6 +2128,26 @@ func (this *GetScriptContentsResp) String() string { }, "") return s } +func (this *CheckScriptExistsReq) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&CheckScriptExistsReq{`, + `Sha256Hash:` + fmt.Sprintf("%v", this.Sha256Hash) + `,`, + `}`, + }, "") + return s +} +func (this *CheckScriptExistsResp) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&CheckScriptExistsResp{`, + `Exists:` + fmt.Sprintf("%v", this.Exists) + `,`, + `}`, + }, "") + return s +} func valueToStringService(v interface{}) string { rv := reflect.ValueOf(v) if rv.IsNil() { @@ -2883,6 +3188,158 @@ func (m *GetScriptContentsResp) Unmarshal(dAtA []byte) error { } return nil } +func (m *CheckScriptExistsReq) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowService + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: CheckScriptExistsReq: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CheckScriptExistsReq: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Sha256Hash", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowService + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthService + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthService + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Sha256Hash = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipService(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthService + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *CheckScriptExistsResp) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowService + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: CheckScriptExistsResp: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CheckScriptExistsResp: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Exists", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowService + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Exists = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := skipService(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthService + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipService(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/src/cloud/scriptmgr/scriptmgrpb/service.proto b/src/cloud/scriptmgr/scriptmgrpb/service.proto index 937d41b59f0..59cef599e55 100644 --- a/src/cloud/scriptmgr/scriptmgrpb/service.proto +++ b/src/cloud/scriptmgr/scriptmgrpb/service.proto @@ -36,6 +36,9 @@ service ScriptMgrService { rpc GetScripts(GetScriptsReq) returns (GetScriptsResp); // GetScriptContents returns the pxl string of the script. rpc GetScriptContents(GetScriptContentsReq) returns (GetScriptContentsResp); + // Checks to see if script with hash exists. This is used to check if a script + // has been modified or not. + rpc CheckScriptExists(CheckScriptExistsReq) returns (CheckScriptExistsResp); } // GetLiveViewsReq is the request message for getting a list of all live views. @@ -135,3 +138,11 @@ message GetScriptContentsResp { // string of the pxl for the script. string contents = 2; } + +message CheckScriptExistsReq { + string sha256_hash = 1 [ (gogoproto.customname) = "Sha256Hash" ]; +} + +message CheckScriptExistsResp { + bool exists = 1; +} diff --git a/src/ui/src/containers/constants.ts b/src/ui/src/containers/constants.ts index 81811ae5c6d..d7ee42f53c5 100644 --- a/src/ui/src/containers/constants.ts +++ b/src/ui/src/containers/constants.ts @@ -35,6 +35,7 @@ declare global { LD_CLIENT_ID: string; SCRIPT_BUNDLE_URLS: string; // Actually a string[] in JSON form SCRIPT_BUNDLE_DEV: boolean; + SCRIPT_MODIFICATION_DISABLED: boolean; ANALYTICS_ENABLED: boolean; ANNOUNCEMENT_ENABLED: boolean; ANNOUNCE_WIDGET_URL: string; @@ -60,6 +61,7 @@ export const { SEGMENT_UI_WRITE_KEY } = window.__PIXIE_FLAGS__; export const { LD_CLIENT_ID } = window.__PIXIE_FLAGS__; export const { SCRIPT_BUNDLE_URLS } = window.__PIXIE_FLAGS__; export const { SCRIPT_BUNDLE_DEV } = window.__PIXIE_FLAGS__; +export const { SCRIPT_MODIFICATION_DISABLED } = window.__PIXIE_FLAGS__; export const { ANALYTICS_ENABLED } = window.__PIXIE_FLAGS__; export const { ANNOUNCEMENT_ENABLED } = window.__PIXIE_FLAGS__; export const { ANNOUNCE_WIDGET_URL } = window.__PIXIE_FLAGS__; diff --git a/src/ui/src/containers/editor/editor.tsx b/src/ui/src/containers/editor/editor.tsx index b227ddf198e..66004fd666d 100644 --- a/src/ui/src/containers/editor/editor.tsx +++ b/src/ui/src/containers/editor/editor.tsx @@ -31,6 +31,7 @@ import { } from 'app/components'; import { usePluginList } from 'app/containers/admin/plugins/plugin-gql'; import { SCRATCH_SCRIPT } from 'app/containers/App/scripts-context'; +import { SCRIPT_MODIFICATION_DISABLED } from 'app/containers/constants'; import { getKeyMap } from 'app/containers/live/shortcuts'; import { EditorContext } from 'app/context/editor-context'; import { LayoutContext } from 'app/context/layout-context'; @@ -77,6 +78,8 @@ const useStyles = makeStyles((theme: Theme) => createStyles({ }, }), { name: 'Editor' }); +const editorReadOnlyReason = 'Your cluster admin has disabled script modification'; + const shortcutKeys = Object.values(getKeyMap()).map((keyBinding) => keyBinding.sequence); const VisEditor = React.memo<{ visible: boolean }>(({ visible }) => { @@ -110,6 +113,8 @@ const VisEditor = React.memo<{ visible: boolean }>(({ visible }) => { shortcutKeys={shortcutKeys} language='json' theme={EDITOR_THEME_MAP[theme.palette.mode]} + readOnlyReason={SCRIPT_MODIFICATION_DISABLED ? editorReadOnlyReason : undefined} + isReadOnly={SCRIPT_MODIFICATION_DISABLED} /> ); }); @@ -147,6 +152,8 @@ const PxLEditor = React.memo<{ visible: boolean }>(({ visible }) => { shortcutKeys={shortcutKeys} language='python' theme={EDITOR_THEME_MAP[theme.palette.mode]} + isReadOnly={SCRIPT_MODIFICATION_DISABLED} + readOnlyReason={SCRIPT_MODIFICATION_DISABLED ? editorReadOnlyReason : undefined} /> ); }); diff --git a/src/ui/src/flags.js b/src/ui/src/flags.js index 76997501519..4491a847eea 100644 --- a/src/ui/src/flags.js +++ b/src/ui/src/flags.js @@ -21,7 +21,7 @@ __CONFIG_AUTH_URI__, __CONFIG_AUTH_EMAIL_PASSWORD_CONN__, __CONFIG_DOMAIN_NAME__ __CONFIG_OIDC_HOST__, __CONFIG_OIDC_METADATA_URL__, __CONFIG_OIDC_CLIENT_ID__, __CONFIG_OIDC_ADDITIONAL_SCOPES__, __CONFIG_OIDC_SOCIAL_CONFIG_LOGIN__, __CONFIG_OIDC_SOCIAL_CONFIG_SIGNUP__, __CONFIG_LD_CLIENT_ID__, __CONFIG_SCRIPT_BUNDLE_URLS__, __CONFIG_SCRIPT_BUNDLE_DEV__, -__SEGMENT_UI_WRITE_KEY__, __ANALYTICS_ENABLED__, +__CONFIG_SCRIPT_MODIFICATION_DISABLED__, __SEGMENT_UI_WRITE_KEY__, __ANALYTICS_ENABLED__, __ANNOUNCEMENT_ENABLED__, __ANNOUNCE_WIDGET_URL__, __CONTACT_ENABLED__, __PASSTHROUGH_PROXY_PORT__ */ const OAUTH_PROVIDER = __CONFIG_OAUTH_PROVIDER__; @@ -38,6 +38,7 @@ const DOMAIN_NAME = __CONFIG_DOMAIN_NAME__; const LD_CLIENT_ID = __CONFIG_LD_CLIENT_ID__; const SCRIPT_BUNDLE_URLS = __CONFIG_SCRIPT_BUNDLE_URLS__; const SCRIPT_BUNDLE_DEV = __CONFIG_SCRIPT_BUNDLE_DEV__; +const SCRIPT_MODIFICATION_DISABLED = __CONFIG_SCRIPT_MODIFICATION_DISABLED__; const SEGMENT_UI_WRITE_KEY = __SEGMENT_UI_WRITE_KEY__; const ANALYTICS_ENABLED = __ANALYTICS_ENABLED__; const ANNOUNCEMENT_ENABLED = __ANNOUNCEMENT_ENABLED__; @@ -63,6 +64,7 @@ window.__PIXIE_FLAGS__ = { LD_CLIENT_ID, SCRIPT_BUNDLE_URLS, SCRIPT_BUNDLE_DEV, + SCRIPT_MODIFICATION_DISABLED, SEGMENT_UI_WRITE_KEY, ANALYTICS_ENABLED, ANNOUNCEMENT_ENABLED, diff --git a/src/ui/src/pages/configure-data-export/data-export-detail.tsx b/src/ui/src/pages/configure-data-export/data-export-detail.tsx index 5286889808d..98cf01fd41e 100644 --- a/src/ui/src/pages/configure-data-export/data-export-detail.tsx +++ b/src/ui/src/pages/configure-data-export/data-export-detail.tsx @@ -42,7 +42,8 @@ import { useHistory, useLocation } from 'react-router'; import { CodeEditor, EDITOR_THEME_MAP, StatusCell, StatusGroup, useSnackbar } from 'app/components'; import { usePluginConfig } from 'app/containers/admin/plugins/plugin-gql'; -import { GQLClusterStatus, GQLEditableRetentionScript } from 'app/types/schema'; +import { SCRIPT_MODIFICATION_DISABLED } from 'app/containers/constants'; +import { GQLClusterStatus, GQLDetailedRetentionScript, GQLEditableRetentionScript } from 'app/types/schema'; import { AutoSizerContext, withAutoSizerContext } from 'app/utils/autosizer'; import { allowRetentionScriptName } from 'configurable/data-export'; @@ -121,9 +122,21 @@ interface RetentionScriptEditorProps { initialValue: string; onChange: (value: string) => void; isReadOnly: boolean; + readOnlyReason: string; } + +const getReadOnlyReason = (script: GQLDetailedRetentionScript | null): string | undefined => { + if (script?.isPreset) { + return 'PxL for preset scripts cannot be edited'; + } + if (SCRIPT_MODIFICATION_DISABLED) { + return 'Editing retention scripts is disabled.'; + } + return undefined; +}; + const RetentionScriptEditorInner = React.memo(({ - initialValue, onChange, isReadOnly = false, + initialValue, onChange, isReadOnly = false, readOnlyReason, }) => { const { width, height } = React.useContext(AutoSizerContext); const theme = useTheme(); @@ -152,7 +165,7 @@ const RetentionScriptEditorInner = React.memo(({ language='python' theme={EDITOR_THEME_MAP[theme.palette.mode]} isReadOnly={isReadOnly} - readOnlyReason='PxL for preset scripts cannot be edited' + readOnlyReason={readOnlyReason} /> ); @@ -497,7 +510,8 @@ export const EditDataExportScript = React.memo<{ scriptId: string, isCreate: boo setPendingField('contents', v)} - isReadOnly={script?.isPreset} + isReadOnly={script?.isPreset || SCRIPT_MODIFICATION_DISABLED} + readOnlyReason={getReadOnlyReason(script)} /> diff --git a/src/ui/webpack.config.js b/src/ui/webpack.config.js index c0f575287d8..10b51752779 100644 --- a/src/ui/webpack.config.js +++ b/src/ui/webpack.config.js @@ -302,6 +302,7 @@ module.exports = (env, argv) => { __CONFIG_LD_CLIENT_ID__: JSON.stringify(yamls.ld.data.PL_LD_CLIENT_ID), __CONFIG_SCRIPT_BUNDLE_URLS__: JSON.stringify(yamls.scriptBundle.data.SCRIPT_BUNDLE_URLS), __CONFIG_SCRIPT_BUNDLE_DEV__: JSON.parse(yamls.scriptBundle.data.SCRIPT_BUNDLE_DEV), + __CONFIG_SCRIPT_MODIFICATION_DISABLED__: JSON.parse(yamls.scriptBundle.data.PL_SCRIPT_MODIFICATION_DISABLED), __SEGMENT_ANALYTICS_JS_DOMAIN__: `"segment.${yamls.domain.data.PL_DOMAIN_NAME}"`, }), );