diff --git a/accessors/acl.go b/accessors/acl.go index aafce6fd011..63ddc743777 100644 --- a/accessors/acl.go +++ b/accessors/acl.go @@ -1,39 +1 @@ package accessors - -import ( - "www.velocidex.com/golang/velociraptor/acls" - acl_proto "www.velocidex.com/golang/velociraptor/acls/proto" - config_proto "www.velocidex.com/golang/velociraptor/config/proto" - vql_subsystem "www.velocidex.com/golang/velociraptor/vql" -) - -// Get a new, more restricted ACL manager suitable for remapping -// configuration. NOTE that this remapping manager can not give -// **more** permissions than before, but can only remove permissions -// from the existing token. It is useful when we want to block -// certaain plugins from working because we are emulating a more -// restricted environment. For example when analyzing a dead image on -// Windows we need to prevent wmi() plugin from interrogating the -// analysis host, therefore would typically remove the MACHINE_STATE -// permission. -func GetRemappingACLManager( - existing_manager vql_subsystem.ACLManager, - remap_config []*config_proto.RemappingConfig) (vql_subsystem.ACLManager, error) { - token := &acl_proto.ApiClientACL{} - for _, item := range remap_config { - if item.Type == "permissions" { - for _, perm := range item.Permissions { - allowed, err := existing_manager.CheckAccess( - acls.GetPermission(perm)) - if err == nil && allowed { - err := acls.SetTokenPermission(token, perm) - if err != nil { - return nil, err - } - } - } - } - } - - return &vql_subsystem.ServerACLManager{Token: token}, nil -} diff --git a/accessors/api_test.go b/accessors/api_test.go index 20d327c0176..209275c9982 100644 --- a/accessors/api_test.go +++ b/accessors/api_test.go @@ -11,6 +11,7 @@ import ( "www.velocidex.com/golang/velociraptor/constants" "www.velocidex.com/golang/velociraptor/json" vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" _ "www.velocidex.com/golang/velociraptor/accessors/ntfs" _ "www.velocidex.com/golang/velociraptor/accessors/offset" @@ -128,7 +129,7 @@ var human_string_tests = []human_string_tests_t{ func TestOSPathHumanString(t *testing.T) { scope := vql_subsystem.MakeScope().AppendVars(ordereddict.NewDict(). - Set(vql_subsystem.ACL_MANAGER_VAR, vql_subsystem.NullACLManager{}). + Set(vql_subsystem.ACL_MANAGER_VAR, acl_managers.NullACLManager{}). Set(constants.SCOPE_DEVICE_MANAGER, accessors.GlobalDeviceManager.Copy())) diff --git a/accessors/file/accessor_test.go b/accessors/file/accessor_test.go index b92a5b7caf8..efca85210d3 100644 --- a/accessors/file/accessor_test.go +++ b/accessors/file/accessor_test.go @@ -17,6 +17,7 @@ import ( "www.velocidex.com/golang/velociraptor/config" "www.velocidex.com/golang/velociraptor/glob" vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" _ "www.velocidex.com/golang/velociraptor/accessors/ntfs" ) @@ -47,7 +48,7 @@ func (self *AccessorWindowsTestSuite) TestACL() { // Try again with more premissions. scope = vql_subsystem.MakeScope().AppendVars(ordereddict.NewDict(). - Set(vql_subsystem.ACL_MANAGER_VAR, vql_subsystem.NullACLManager{})) + Set(vql_subsystem.ACL_MANAGER_VAR, acl_managers.NullACLManager{})) scope.SetLogger(log.New(os.Stderr, " ", 0)) accessor, err = accessors.GetAccessor("file", scope) @@ -90,7 +91,7 @@ func (self *AccessorWindowsTestSuite) TestSymlinks() { assert.NoError(self.T(), err) scope := vql_subsystem.MakeScope().AppendVars(ordereddict.NewDict(). - Set(vql_subsystem.ACL_MANAGER_VAR, vql_subsystem.NullACLManager{})) + Set(vql_subsystem.ACL_MANAGER_VAR, acl_managers.NullACLManager{})) scope.SetLogger(log.New(os.Stderr, " ", 0)) accessor, err := accessors.GetAccessor("file", scope) assert.NoError(self.T(), err) diff --git a/accessors/ntfs/mft_test.go b/accessors/ntfs/mft_test.go index 2746891d591..b873a4930d5 100644 --- a/accessors/ntfs/mft_test.go +++ b/accessors/ntfs/mft_test.go @@ -9,12 +9,13 @@ import ( "github.com/Velocidex/ordereddict" "www.velocidex.com/golang/velociraptor/accessors" vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" "www.velocidex.com/golang/velociraptor/vtesting/assert" ) func TestMFTFilesystemAccessor(t *testing.T) { scope := vql_subsystem.MakeScope().AppendVars(ordereddict.NewDict(). - Set(vql_subsystem.ACL_MANAGER_VAR, vql_subsystem.NullACLManager{})) + Set(vql_subsystem.ACL_MANAGER_VAR, acl_managers.NullACLManager{})) scope.SetLogger(log.New(os.Stderr, " ", 0)) abs_path, _ := filepath.Abs("../../artifacts/testdata/files/test.ntfs.dd") diff --git a/accessors/ntfs/ntfs_accessor_test.go b/accessors/ntfs/ntfs_accessor_test.go index 961e4914653..3bbb9d4f84b 100644 --- a/accessors/ntfs/ntfs_accessor_test.go +++ b/accessors/ntfs/ntfs_accessor_test.go @@ -15,6 +15,7 @@ import ( "www.velocidex.com/golang/velociraptor/glob" "www.velocidex.com/golang/velociraptor/json" vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" "www.velocidex.com/golang/velociraptor/vtesting/assert" _ "www.velocidex.com/golang/velociraptor/accessors/file" @@ -23,7 +24,7 @@ import ( func TestNTFSFilesystemAccessor(t *testing.T) { config_obj := config.GetDefaultConfig() scope := vql_subsystem.MakeScope().AppendVars(ordereddict.NewDict(). - Set(vql_subsystem.ACL_MANAGER_VAR, vql_subsystem.NullACLManager{})) + Set(vql_subsystem.ACL_MANAGER_VAR, acl_managers.NullACLManager{})) scope.SetLogger(log.New(os.Stderr, " ", 0)) abs_path, _ := filepath.Abs("../../artifacts/testdata/files/test.ntfs.dd") @@ -77,7 +78,7 @@ func TestNTFSFilesystemAccessorRemapping(t *testing.T) { accessors.MustNewWindowsOSPath(""), root_fs_accessor) scope := vql_subsystem.MakeScope().AppendVars(ordereddict.NewDict(). - Set(vql_subsystem.ACL_MANAGER_VAR, vql_subsystem.NullACLManager{})) + Set(vql_subsystem.ACL_MANAGER_VAR, acl_managers.NullACLManager{})) scope.SetLogger(log.New(os.Stderr, " ", 0)) abs_path, _ := filepath.Abs("../../artifacts/testdata/files/test.ntfs.dd") diff --git a/accessors/raw_registry/raw_registry_test.go b/accessors/raw_registry/raw_registry_test.go index 033c87b9690..09370ed2851 100644 --- a/accessors/raw_registry/raw_registry_test.go +++ b/accessors/raw_registry/raw_registry_test.go @@ -16,6 +16,7 @@ import ( "www.velocidex.com/golang/velociraptor/json" "www.velocidex.com/golang/velociraptor/logging" vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" "www.velocidex.com/golang/vfilter" _ "www.velocidex.com/golang/velociraptor/accessors/file" @@ -69,7 +70,7 @@ func TestAccessorRawReg(t *testing.T) { // Now repeat with proper access scope = vql_subsystem.MakeScope().AppendVars(ordereddict.NewDict(). - Set(vql_subsystem.ACL_MANAGER_VAR, vql_subsystem.NullACLManager{})) + Set(vql_subsystem.ACL_MANAGER_VAR, acl_managers.NullACLManager{})) hits, err := runtest(scope) assert.NoError(t, err) diff --git a/accessors/zip/gzip_test.go b/accessors/zip/gzip_test.go index 0ac77dc1923..e867f96b363 100644 --- a/accessors/zip/gzip_test.go +++ b/accessors/zip/gzip_test.go @@ -10,6 +10,7 @@ import ( "github.com/Velocidex/ordereddict" "www.velocidex.com/golang/velociraptor/accessors" vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" "www.velocidex.com/golang/velociraptor/vtesting/assert" _ "www.velocidex.com/golang/velociraptor/accessors/file" @@ -18,7 +19,7 @@ import ( func TestAccessorGzip(t *testing.T) { scope := vql_subsystem.MakeScope().AppendVars(ordereddict.NewDict(). - Set(vql_subsystem.ACL_MANAGER_VAR, vql_subsystem.NullACLManager{})) + Set(vql_subsystem.ACL_MANAGER_VAR, acl_managers.NullACLManager{})) scope.SetLogger(log.New(os.Stderr, " ", 0)) gzip_accessor, err := accessors.GetAccessor("gzip", scope) @@ -37,7 +38,7 @@ func TestAccessorGzip(t *testing.T) { func TestAccessorBzip2(t *testing.T) { scope := vql_subsystem.MakeScope().AppendVars(ordereddict.NewDict(). - Set(vql_subsystem.ACL_MANAGER_VAR, vql_subsystem.NullACLManager{})) + Set(vql_subsystem.ACL_MANAGER_VAR, acl_managers.NullACLManager{})) scope.SetLogger(log.New(os.Stderr, " ", 0)) gzip_accessor, err := accessors.GetAccessor("bzip2", scope) diff --git a/acls/acls.go b/acls/acls.go index 33c8b20c2af..783ad5aa0d6 100644 --- a/acls/acls.go +++ b/acls/acls.go @@ -100,6 +100,12 @@ const ( // Allowed to manage server configuration. SERVER_ADMIN + // Allowed to manage orgs + ORG_ADMIN + + // Allows the user to specify a different username for the query() plugin + IMPERSONATION + // Allowed to read arbitrary files from the filesystem. FILESYSTEM_READ @@ -147,6 +153,10 @@ func (self ACL_PERMISSION) String() string { return "NOTEBOOK_EDITOR" case SERVER_ADMIN: return "SERVER_ADMIN" + case ORG_ADMIN: + return "ORG_ADMIN" + case IMPERSONATION: + return "IMPERSONATION" case FILESYSTEM_READ: return "FILESYSTEM_READ" case FILESYSTEM_WRITE: @@ -191,6 +201,10 @@ func GetPermission(name string) ACL_PERMISSION { return NOTEBOOK_EDITOR case "SERVER_ADMIN": return SERVER_ADMIN + case "ORG_ADMIN": + return ORG_ADMIN + case "IMPERSONATION": + return IMPERSONATION case "FILESYSTEM_READ": return FILESYSTEM_READ case "FILESYSTEM_WRITE": @@ -356,6 +370,12 @@ func (self ACLManager) CheckAccessWithToken( case SERVER_ADMIN: return token.ServerAdmin, nil + case ORG_ADMIN: + return token.OrgAdmin, nil + + case IMPERSONATION: + return token.Impersonation, nil + case FILESYSTEM_READ: return token.FilesystemRead, nil diff --git a/acls/proto/acl.pb.go b/acls/proto/acl.pb.go index 2d71ccecd6d..58d8bcfcf1d 100644 --- a/acls/proto/acl.pb.go +++ b/acls/proto/acl.pb.go @@ -37,12 +37,17 @@ type ApiClientACL struct { ServerArtifactWriter bool `protobuf:"varint,15,opt,name=server_artifact_writer,json=serverArtifactWriter,proto3" json:"server_artifact_writer,omitempty"` Execve bool `protobuf:"varint,7,opt,name=execve,proto3" json:"execve,omitempty"` NotebookEditor bool `protobuf:"varint,8,opt,name=notebook_editor,json=notebookEditor,proto3" json:"notebook_editor,omitempty"` - ServerAdmin bool `protobuf:"varint,12,opt,name=server_admin,json=serverAdmin,proto3" json:"server_admin,omitempty"` - FilesystemRead bool `protobuf:"varint,13,opt,name=filesystem_read,json=filesystemRead,proto3" json:"filesystem_read,omitempty"` - FilesystemWrite bool `protobuf:"varint,14,opt,name=filesystem_write,json=filesystemWrite,proto3" json:"filesystem_write,omitempty"` - MachineState bool `protobuf:"varint,16,opt,name=machine_state,json=machineState,proto3" json:"machine_state,omitempty"` - PrepareResults bool `protobuf:"varint,17,opt,name=prepare_results,json=prepareResults,proto3" json:"prepare_results,omitempty"` - DatastoreAccess bool `protobuf:"varint,18,opt,name=datastore_access,json=datastoreAccess,proto3" json:"datastore_access,omitempty"` + // Has the ability to add/remove/list orgs. A user with + // server_admin on the root org will also receive org_admin. + OrgAdmin bool `protobuf:"varint,19,opt,name=org_admin,json=orgAdmin,proto3" json:"org_admin,omitempty"` + // Allows a user to run queries as another user. + Impersonation bool `protobuf:"varint,20,opt,name=impersonation,proto3" json:"impersonation,omitempty"` + ServerAdmin bool `protobuf:"varint,12,opt,name=server_admin,json=serverAdmin,proto3" json:"server_admin,omitempty"` + FilesystemRead bool `protobuf:"varint,13,opt,name=filesystem_read,json=filesystemRead,proto3" json:"filesystem_read,omitempty"` + FilesystemWrite bool `protobuf:"varint,14,opt,name=filesystem_write,json=filesystemWrite,proto3" json:"filesystem_write,omitempty"` + MachineState bool `protobuf:"varint,16,opt,name=machine_state,json=machineState,proto3" json:"machine_state,omitempty"` + PrepareResults bool `protobuf:"varint,17,opt,name=prepare_results,json=prepareResults,proto3" json:"prepare_results,omitempty"` + DatastoreAccess bool `protobuf:"varint,18,opt,name=datastore_access,json=datastoreAccess,proto3" json:"datastore_access,omitempty"` // A list of roles in lieu of the permissions above. These will be // interpolated into this ACL object. Roles []string `protobuf:"bytes,9,rep,name=roles,proto3" json:"roles,omitempty"` @@ -157,6 +162,20 @@ func (x *ApiClientACL) GetNotebookEditor() bool { return false } +func (x *ApiClientACL) GetOrgAdmin() bool { + if x != nil { + return x.OrgAdmin + } + return false +} + +func (x *ApiClientACL) GetImpersonation() bool { + if x != nil { + return x.Impersonation + } + return false +} + func (x *ApiClientACL) GetServerAdmin() bool { if x != nil { return x.ServerAdmin @@ -268,7 +287,7 @@ var File_acl_proto protoreflect.FileDescriptor var file_acl_proto_rawDesc = []byte{ 0x0a, 0x09, 0x61, 0x63, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x14, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x65, 0x6d, 0x61, 0x6e, 0x74, - 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x8e, 0x06, 0x0a, 0x0c, 0x41, 0x70, 0x69, + 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xd1, 0x06, 0x0a, 0x0c, 0x41, 0x70, 0x69, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x41, 0x43, 0x4c, 0x12, 0x4b, 0x0a, 0x09, 0x61, 0x6c, 0x6c, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x42, 0x2e, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x28, 0x12, 0x26, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x73, 0x20, 0x61, @@ -301,32 +320,36 @@ var file_acl_proto_rawDesc = []byte{ 0x28, 0x08, 0x52, 0x06, 0x65, 0x78, 0x65, 0x63, 0x76, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x6e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x5f, 0x65, 0x64, 0x69, 0x74, 0x6f, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x6e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x45, 0x64, 0x69, - 0x74, 0x6f, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x61, 0x64, - 0x6d, 0x69, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x12, 0x27, 0x0a, 0x0f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, - 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x72, 0x65, 0x61, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x0e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x61, 0x64, 0x12, - 0x29, 0x0a, 0x10, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x77, 0x72, - 0x69, 0x74, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x66, 0x69, 0x6c, 0x65, 0x73, - 0x79, 0x73, 0x74, 0x65, 0x6d, 0x57, 0x72, 0x69, 0x74, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x61, - 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x10, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x0c, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, - 0x27, 0x0a, 0x0f, 0x70, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x73, 0x18, 0x11, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x70, 0x72, 0x65, 0x70, 0x61, 0x72, - 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x64, 0x61, 0x74, 0x61, - 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x12, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x0f, 0x64, 0x61, 0x74, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x41, 0x63, 0x63, - 0x65, 0x73, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x18, 0x09, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x05, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x22, 0x51, 0x0a, 0x04, 0x52, 0x6f, 0x6c, - 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x41, 0x70, 0x69, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x41, 0x43, 0x4c, 0x52, - 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x32, 0x5a, 0x30, - 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65, 0x6c, 0x6f, 0x63, 0x69, 0x64, 0x65, 0x78, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x67, 0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x76, 0x65, 0x6c, 0x6f, 0x63, 0x69, 0x72, - 0x61, 0x70, 0x74, 0x6f, 0x72, 0x2f, 0x61, 0x63, 0x6c, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x74, 0x6f, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x72, 0x67, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, + 0x18, 0x13, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6f, 0x72, 0x67, 0x41, 0x64, 0x6d, 0x69, 0x6e, + 0x12, 0x24, 0x0a, 0x0d, 0x69, 0x6d, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x14, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x69, 0x6d, 0x70, 0x65, 0x72, 0x73, 0x6f, + 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x73, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x12, 0x27, 0x0a, 0x0f, 0x66, 0x69, 0x6c, + 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x72, 0x65, 0x61, 0x64, 0x18, 0x0d, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x52, 0x65, + 0x61, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, + 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x66, 0x69, + 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x57, 0x72, 0x69, 0x74, 0x65, 0x12, 0x23, 0x0a, + 0x0d, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x10, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x53, 0x74, 0x61, + 0x74, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x5f, 0x72, 0x65, + 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x11, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x70, 0x72, 0x65, + 0x70, 0x61, 0x72, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x64, + 0x61, 0x74, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, + 0x12, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x64, 0x61, 0x74, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, + 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x18, + 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x22, 0x51, 0x0a, 0x04, + 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x0b, 0x70, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x70, 0x69, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x41, + 0x43, 0x4c, 0x52, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x42, + 0x32, 0x5a, 0x30, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65, 0x6c, 0x6f, 0x63, 0x69, 0x64, 0x65, 0x78, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x76, 0x65, 0x6c, 0x6f, + 0x63, 0x69, 0x72, 0x61, 0x70, 0x74, 0x6f, 0x72, 0x2f, 0x61, 0x63, 0x6c, 0x73, 0x2f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/acls/proto/acl.proto b/acls/proto/acl.proto index 823b7ce798e..aaff903bc60 100644 --- a/acls/proto/acl.proto +++ b/acls/proto/acl.proto @@ -28,6 +28,14 @@ message ApiClientACL { bool server_artifact_writer = 15; bool execve = 7; bool notebook_editor = 8; + + // Has the ability to add/remove/list orgs. A user with + // server_admin on the root org will also receive org_admin. + bool org_admin = 19; + + // Allows a user to run queries as another user. + bool impersonation = 20; + bool server_admin = 12; bool filesystem_read = 13; bool filesystem_write = 14; diff --git a/acls/roles.go b/acls/roles.go index 14b12f21521..199d1052b50 100644 --- a/acls/roles.go +++ b/acls/roles.go @@ -10,7 +10,7 @@ import ( func ValidateRole(role string) bool { switch role { - case "administrator", "reader", "analyst", "investigator", "artifact_writer", "api": + case "org_admin", "administrator", "reader", "analyst", "investigator", "artifact_writer", "api": return true } @@ -43,6 +43,10 @@ func SetTokenPermission( token.NotebookEditor = true case "SERVER_ADMIN": token.ServerAdmin = true + case "ORG_ADMIN": + token.OrgAdmin = true + case "IMPERSONATION": + token.Impersonation = true case "FILESYSTEM_READ": token.FilesystemRead = true case "FILESYSTEM_WRITE": @@ -69,11 +73,15 @@ func GetRolePermissions( for _, role := range roles { switch role { + case "org_admin": + result.OrgAdmin = true + // Admins get all query access case "administrator": result.AllQuery = true result.AnyQuery = true result.ReadResults = true + result.Impersonation = true result.LabelClients = true result.CollectClient = true result.CollectServer = true @@ -87,6 +95,12 @@ func GetRolePermissions( result.MachineState = true result.PrepareResults = true + // An administrator for the root org is allowed to + // manipulate orgs. + if config_obj != nil && config_obj.OrgId == "" { + result.OrgAdmin = true + } + // Readers can view results but not edit or // modify anything. case "reader": diff --git a/actions/vql.go b/actions/vql.go index 8d6b6667977..c120c7dc092 100644 --- a/actions/vql.go +++ b/actions/vql.go @@ -38,6 +38,7 @@ import ( "www.velocidex.com/golang/velociraptor/services" "www.velocidex.com/golang/velociraptor/uploads" vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" "www.velocidex.com/golang/vfilter" "www.velocidex.com/golang/vfilter/types" ) @@ -141,7 +142,7 @@ func (self VQLClientAction) StartQuery( // client context. ClientConfig: config_obj.Client, // Disable ACLs on the client. - ACLManager: vql_subsystem.NullACLManager{}, + ACLManager: acl_managers.NullACLManager{}, Env: ordereddict.NewDict(), Uploader: uploader, Repository: repository, diff --git a/api/api.go b/api/api.go index d2566b2be7b..adadade4cdb 100644 --- a/api/api.go +++ b/api/api.go @@ -56,6 +56,7 @@ import ( "www.velocidex.com/golang/velociraptor/server" "www.velocidex.com/golang/velociraptor/services" vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" "www.velocidex.com/golang/vfilter" ) @@ -134,7 +135,7 @@ func (self *ApiServer) GetReport( "User is not allowed to view reports.") } - acl_manager := vql_subsystem.NewServerACLManager(org_config_obj, user_name) + acl_manager := acl_managers.NewServerACLManager(org_config_obj, user_name) manager, err := services.GetRepositoryManager(org_config_obj) if err != nil { @@ -164,7 +165,7 @@ func (self *ApiServer) CollectArtifact( } creator := user_record.Name - var acl_manager vql_subsystem.ACLManager = vql_subsystem.NullACLManager{} + var acl_manager vql_subsystem.ACLManager = acl_managers.NullACLManager{} // Internal calls from the frontend can set the creator. if creator != org_config_obj.Client.PinnedServerName { @@ -175,7 +176,7 @@ func (self *ApiServer) CollectArtifact( permissions = acls.COLLECT_SERVER } - acl_manager = vql_subsystem.NewServerACLManager(org_config_obj, + acl_manager = acl_managers.NewServerACLManager(org_config_obj, creator) perm, err := acl_manager.CheckAccess(permissions) @@ -1039,7 +1040,7 @@ func (self *ApiServer) CreateDownloadFile(ctx context.Context, services.ScopeBuilder{ Config: org_config_obj, Env: env, - ACLManager: vql_subsystem.NewServerACLManager(org_config_obj, user_name), + ACLManager: acl_managers.NewServerACLManager(org_config_obj, user_name), Logger: logging.NewPlainLogger(org_config_obj, &logging.FrontendComponent), }) defer scope.Close() diff --git a/api/hunts.go b/api/hunts.go index fce9cbccfc0..38c9a96cd9a 100644 --- a/api/hunts.go +++ b/api/hunts.go @@ -18,6 +18,7 @@ import ( "www.velocidex.com/golang/velociraptor/services" "www.velocidex.com/golang/velociraptor/services/hunt_dispatcher" vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" ) func (self *ApiServer) GetHuntFlows( @@ -102,7 +103,7 @@ func (self *ApiServer) CreateHunt( in.Creator = user_record.Name in.HuntId = hunt_dispatcher.GetNewHuntId() - acl_manager := vql_subsystem.NewServerACLManager(org_config_obj, in.Creator) + acl_manager := acl_managers.NewServerACLManager(org_config_obj, in.Creator) permissions := acls.COLLECT_CLIENT perm, err := acls.CheckAccess(org_config_obj, in.Creator, permissions) diff --git a/api/proto/api.pb.gw.go b/api/proto/api.pb.gw.go index 63a8851b0dc..54ab774bf72 100644 --- a/api/proto/api.pb.gw.go +++ b/api/proto/api.pb.gw.go @@ -23,7 +23,7 @@ import ( "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" proto_4 "www.velocidex.com/golang/velociraptor/artifacts/proto" - proto_6 "www.velocidex.com/golang/velociraptor/flows/proto" + proto_0 "www.velocidex.com/golang/velociraptor/flows/proto" ) // Suppress "imported and not used" errors @@ -1017,7 +1017,7 @@ func local_request_API_GetTable_0(ctx context.Context, marshaler runtime.Marshal } func request_API_CollectArtifact_0(ctx context.Context, marshaler runtime.Marshaler, client APIClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq proto_6.ArtifactCollectorArgs + var protoReq proto_0.ArtifactCollectorArgs var metadata runtime.ServerMetadata newReader, berr := utilities.IOReaderFactory(req.Body) @@ -1034,7 +1034,7 @@ func request_API_CollectArtifact_0(ctx context.Context, marshaler runtime.Marsha } func local_request_API_CollectArtifact_0(ctx context.Context, marshaler runtime.Marshaler, server APIServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq proto_6.ArtifactCollectorArgs + var protoReq proto_0.ArtifactCollectorArgs var metadata runtime.ServerMetadata newReader, berr := utilities.IOReaderFactory(req.Body) @@ -1435,7 +1435,7 @@ func local_request_API_GetServerMonitoringState_0(ctx context.Context, marshaler } func request_API_SetServerMonitoringState_0(ctx context.Context, marshaler runtime.Marshaler, client APIClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq proto_6.ArtifactCollectorArgs + var protoReq proto_0.ArtifactCollectorArgs var metadata runtime.ServerMetadata newReader, berr := utilities.IOReaderFactory(req.Body) @@ -1452,7 +1452,7 @@ func request_API_SetServerMonitoringState_0(ctx context.Context, marshaler runti } func local_request_API_SetServerMonitoringState_0(ctx context.Context, marshaler runtime.Marshaler, server APIServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq proto_6.ArtifactCollectorArgs + var protoReq proto_0.ArtifactCollectorArgs var metadata runtime.ServerMetadata newReader, berr := utilities.IOReaderFactory(req.Body) @@ -1473,7 +1473,7 @@ var ( ) func request_API_GetClientMonitoringState_0(ctx context.Context, marshaler runtime.Marshaler, client APIClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq proto_6.GetClientMonitoringStateRequest + var protoReq proto_0.GetClientMonitoringStateRequest var metadata runtime.ServerMetadata if err := req.ParseForm(); err != nil { @@ -1489,7 +1489,7 @@ func request_API_GetClientMonitoringState_0(ctx context.Context, marshaler runti } func local_request_API_GetClientMonitoringState_0(ctx context.Context, marshaler runtime.Marshaler, server APIServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq proto_6.GetClientMonitoringStateRequest + var protoReq proto_0.GetClientMonitoringStateRequest var metadata runtime.ServerMetadata if err := req.ParseForm(); err != nil { @@ -1505,7 +1505,7 @@ func local_request_API_GetClientMonitoringState_0(ctx context.Context, marshaler } func request_API_SetClientMonitoringState_0(ctx context.Context, marshaler runtime.Marshaler, client APIClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq proto_6.ClientEventTable + var protoReq proto_0.ClientEventTable var metadata runtime.ServerMetadata newReader, berr := utilities.IOReaderFactory(req.Body) @@ -1522,7 +1522,7 @@ func request_API_SetClientMonitoringState_0(ctx context.Context, marshaler runti } func local_request_API_SetClientMonitoringState_0(ctx context.Context, marshaler runtime.Marshaler, server APIServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq proto_6.ClientEventTable + var protoReq proto_0.ClientEventTable var metadata runtime.ServerMetadata newReader, berr := utilities.IOReaderFactory(req.Body) diff --git a/api/query.go b/api/query.go index 96a7c45c2a0..1cafea415a9 100644 --- a/api/query.go +++ b/api/query.go @@ -36,6 +36,7 @@ import ( "www.velocidex.com/golang/velociraptor/logging" "www.velocidex.com/golang/velociraptor/services" vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" "www.velocidex.com/golang/vfilter" ) @@ -83,7 +84,7 @@ func streamQuery( builder := services.ScopeBuilder{ Config: config_obj, - ACLManager: vql_subsystem.NewServerACLManager(config_obj, peer_name), + ACLManager: acl_managers.NewServerACLManager(config_obj, peer_name), Logger: scope_logger, Repository: repository, Env: ordereddict.NewDict(), diff --git a/api/vql.go b/api/vql.go index d3db3b8a9ab..0d2bb1075fd 100644 --- a/api/vql.go +++ b/api/vql.go @@ -25,7 +25,7 @@ import ( "www.velocidex.com/golang/velociraptor/file_store/csv" "www.velocidex.com/golang/velociraptor/logging" "www.velocidex.com/golang/velociraptor/services" - vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" "www.velocidex.com/golang/vfilter" ) @@ -45,7 +45,7 @@ func RunVQL( scope := manager.BuildScope(services.ScopeBuilder{ Config: config_obj, Env: env, - ACLManager: vql_subsystem.NewServerACLManager(config_obj, principal), + ACLManager: acl_managers.NewServerACLManager(config_obj, principal), Logger: logging.NewPlainLogger(config_obj, &logging.ToolComponent), }) defer scope.Close() diff --git a/artifacts/definitions/Server/Monitor/Health.yaml b/artifacts/definitions/Server/Monitor/Health.yaml index 3ad5060768d..fdca6fc5eb2 100644 --- a/artifacts/definitions/Server/Monitor/Health.yaml +++ b/artifacts/definitions/Server/Monitor/Health.yaml @@ -70,15 +70,8 @@ reports: ## Users {{ define "UserPermissions" }} - LET cleanup(permission) = to_dict(item={ - SELECT * FROM foreach(row=items(item=permission), query={ - SELECT _key, _value FROM scope() - WHERE _value AND NOT _key = "roles" - }) - }) - - SELECT name, cleanup(permission=Permissions) AS Permissions, - join(array=Permissions.roles, sep=", ") AS Roles + SELECT name, effective_policy AS _EffectivePolicy, + join(array=roles, sep=", ") AS Roles FROM gui_users() {{ end }} diff --git a/artifacts/definitions/Server/Orgs/ListOrgs.yaml b/artifacts/definitions/Server/Orgs/ListOrgs.yaml new file mode 100644 index 00000000000..07ede764885 --- /dev/null +++ b/artifacts/definitions/Server/Orgs/ListOrgs.yaml @@ -0,0 +1,28 @@ +name: Server.Orgs.ListOrgs +description: | + This server artifact will list all currently configured orgs on the + server. + + NOTE: This artifact is only available to users with the ORG_ADMIN + permission, normally only given to users with the administrator role + while using the root org (You might need to switch to the root org + in the GUI before collecting this artifact). + +type: SERVER + +parameters: +- name: AlsoDownloadClientConfigs + type: bool + description: When set also downloads client configs from each org + +sources: +- query: | + SELECT * FROM if(condition=AlsoDownloadClientConfigs, + then={ + SELECT *, upload(file=_client_config, + accessor="data", + name=format(format="client.%s.config.yaml", args=OrgId || "RootOrg")) AS ClientConfig + FROM orgs() + }, else={ + SELECT * FROM orgs() + }) diff --git a/artifacts/testdata/server/testcases/orgs.in.yaml b/artifacts/testdata/server/testcases/orgs.in.yaml new file mode 100644 index 00000000000..d6a71ef2799 --- /dev/null +++ b/artifacts/testdata/server/testcases/orgs.in.yaml @@ -0,0 +1,111 @@ +Queries: +# Initially we only have the root org +- SELECT Name, OrgId FROM orgs() ORDER BY OrgId + +# Try to add a user to a nonexistant org +- LET _ <= user_create(user="FailedUser", + roles="reader", password="X", + orgs=["ORGID"]) + +- SELECT * FROM test_read_logs() WHERE Log =~ "Org not found" AND NOT Log =~ "SELECT" + +- SELECT * FROM gui_users(all_orgs=TRUE) ORDER BY name + +# Now create a new org +- LET _ <= org_create(name="MyOrg", org_id="ORGID") + +# Now we can see it +- SELECT Name, OrgId FROM orgs() ORDER BY OrgId + +# Add some users to the orgs +- SELECT user_create(user="ReaderUser", + roles="reader", password="X", + orgs=["root", "ORGID"]), + user_create(user="OrgUser", + roles="administrator", password="X", + orgs=["ORGID"]), + user_create(user="OrgAdmin", + roles="administrator", password="X") + FROM scope() + +- SELECT * FROM gui_users(all_orgs=TRUE) ORDER BY name + +# We are at the root org - gui_users should show only root org users +- SELECT * FROM gui_users() ORDER BY name + +# Run the query from the suborg, query should show only users in that org. +- SELECT * FROM query(query={ + SELECT * FROM gui_users() ORDER BY name + }, org_id="ORGID") + +# Running the orgs() plugin as the reader will fail due to access +# denied. +- SELECT * FROM query(query={ + SELECT * FROM orgs() ORDER BY OrgId + }, runas="ReaderUser") + +- SELECT * FROM test_read_logs() WHERE Log =~ "Permission denied.+ORG_ADMIN" AND NOT Log =~ "SELECT" + +# Running the orgs() plugin as an administrator works but only in the +# root org +- SELECT * FROM query(query={ + SELECT OrgId FROM orgs() ORDER BY OrgId + }, runas="OrgAdmin", org_id="root") + +# In a non-root org an admin user needs to explicitely have the +# org_admin role to do org things. +- SELECT * FROM query(query={ + SELECT OrgId FROM orgs() ORDER BY OrgId + }, runas="OrgUser", org_id="ORGID") + +- SELECT * FROM test_read_logs() WHERE Log =~ "Permission denied.+ORG_ADMIN" AND NOT Log =~ "SELECT" + +# Test incrementally adding a user to an org. Create another org. +- LET _ <= org_create(name="MySecondOrg", org_id="ORGID2") + +# Give OrgUser a reader role in that org. +- LET _ <= user_create(user="OrgUser", + roles="reader", password="X", orgs=["ORGID2"]) + +# OrgUser should be an admin in ORGID and reader in ORGID2 +- SELECT * FROM gui_users(all_orgs=TRUE) WHERE name =~ "OrgUser" ORDER BY name + +# Launching collections on different orgs can be done using a number of ways. +# 1. Use the query() plugin to run the query on another org. +- SELECT * FROM query(query={ + SELECT collect_client(client_id="C.123", artifacts="Generic.Client.Info").request.artifacts + FROM scope() + }, runas="OrgUser", org_id="ORGID") + +- SELECT * FROM query(query={ + SELECT client_id, request.artifacts AS artifacts + FROM flows(client_id="C.123") + }, runas="OrgUser", org_id="ORGID") + +# The flow is not scheduled in the root org or in the second org +- SELECT client_id, request.artifacts AS artifacts + FROM flows(client_id="C.123") +- SELECT * FROM query(query={ + SELECT client_id, request.artifacts AS artifacts + FROM flows(client_id="C.123") + }, runas="OrgUser", org_id="ORGID2") + +# OrgUser is a reader in ORGID2 so can not schedule collections +- SELECT * FROM query(query={ + SELECT collect_client(client_id="C.123", artifacts="Generic.Client.Info").request.artifacts + FROM scope() + }, runas="OrgUser", org_id="ORGID2") + +- SELECT * FROM test_read_logs() WHERE Log =~ "Permission denied.+COLLECT_CLIENT" AND NOT Log =~ "SELECT" + +# Second way to schedule collections is directly by specifying org id +- SELECT * FROM query(query={ + SELECT collect_client(client_id="C.123", + artifacts="Generic.Client.DiskSpace", org_id="ORGID").request.artifacts + FROM scope() + }, runas="OrgUser") + +- SELECT * FROM query(query={ + SELECT client_id, request.artifacts AS artifacts FROM flows(client_id="C.123") + WHERE artifacts =~ "Disk" + }, runas="OrgUser", org_id="ORGID") diff --git a/artifacts/testdata/server/testcases/orgs.out.yaml b/artifacts/testdata/server/testcases/orgs.out.yaml new file mode 100644 index 00000000000..48f69efed78 --- /dev/null +++ b/artifacts/testdata/server/testcases/orgs.out.yaml @@ -0,0 +1,263 @@ +SELECT Name, OrgId FROM orgs() ORDER BY OrgId[ + { + "Name": "\u003croot org\u003e", + "OrgId": "" + } +]LET _ <= user_create(user="FailedUser", roles="reader", password="X", orgs=["ORGID"])[]SELECT * FROM test_read_logs() WHERE Log =~ "Org not found" AND NOT Log =~ "SELECT"[ + { + "Log": "Velociraptor: user_create: Org not found\n" + } +]SELECT * FROM gui_users(all_orgs=TRUE) ORDER BY name[]LET _ <= org_create(name="MyOrg", org_id="ORGID")[]SELECT Name, OrgId FROM orgs() ORDER BY OrgId[ + { + "Name": "\u003croot org\u003e", + "OrgId": "" + }, + { + "Name": "MyOrg", + "OrgId": "ORGID" + } +]SELECT user_create(user="ReaderUser", roles="reader", password="X", orgs=["root", "ORGID"]), user_create(user="OrgUser", roles="administrator", password="X", orgs=["ORGID"]), user_create(user="OrgAdmin", roles="administrator", password="X") FROM scope()[ + { + "user_create(user=\"ReaderUser\", roles=\"reader\", password=\"X\", orgs= [\"root\", \"ORGID\"])": "ReaderUser", + "user_create(user=\"OrgUser\", roles=\"administrator\", password=\"X\", orgs= [\"ORGID\"])": "OrgUser", + "user_create(user=\"OrgAdmin\", roles=\"administrator\", password=\"X\")": "OrgAdmin" + } +]SELECT * FROM gui_users(all_orgs=TRUE) ORDER BY name[ + { + "name": "OrgAdmin", + "org_id": "", + "picture": "", + "email": false, + "roles": [ + "administrator" + ], + "effective_policy": { + "all_query": true, + "any_query": true, + "read_results": true, + "label_clients": true, + "collect_client": true, + "collect_server": true, + "artifact_writer": true, + "server_artifact_writer": true, + "execve": true, + "notebook_editor": true, + "org_admin": true, + "impersonation": true, + "server_admin": true, + "filesystem_read": true, + "filesystem_write": true, + "machine_state": true, + "prepare_results": true + } + }, + { + "name": "OrgUser", + "org_id": "ORGID", + "picture": "", + "email": false, + "roles": [ + "administrator" + ], + "effective_policy": { + "all_query": true, + "any_query": true, + "read_results": true, + "label_clients": true, + "collect_client": true, + "collect_server": true, + "artifact_writer": true, + "server_artifact_writer": true, + "execve": true, + "notebook_editor": true, + "impersonation": true, + "server_admin": true, + "filesystem_read": true, + "filesystem_write": true, + "machine_state": true, + "prepare_results": true + } + }, + { + "name": "ReaderUser", + "org_id": "", + "picture": "", + "email": false, + "roles": [ + "reader" + ], + "effective_policy": { + "read_results": true + } + }, + { + "name": "ReaderUser", + "org_id": "ORGID", + "picture": "", + "email": false, + "roles": [ + "reader" + ], + "effective_policy": { + "read_results": true + } + } +]SELECT * FROM gui_users() ORDER BY name[ + { + "name": "OrgAdmin", + "org_id": "", + "picture": "", + "email": false, + "roles": [ + "administrator" + ], + "effective_policy": { + "all_query": true, + "any_query": true, + "read_results": true, + "label_clients": true, + "collect_client": true, + "collect_server": true, + "artifact_writer": true, + "server_artifact_writer": true, + "execve": true, + "notebook_editor": true, + "org_admin": true, + "impersonation": true, + "server_admin": true, + "filesystem_read": true, + "filesystem_write": true, + "machine_state": true, + "prepare_results": true + } + } +]SELECT * FROM query(query={ SELECT * FROM gui_users() ORDER BY name }, org_id="ORGID")[ + { + "name": "OrgUser", + "org_id": "ORGID", + "picture": "", + "email": false, + "roles": [ + "administrator" + ], + "effective_policy": { + "all_query": true, + "any_query": true, + "read_results": true, + "label_clients": true, + "collect_client": true, + "collect_server": true, + "artifact_writer": true, + "server_artifact_writer": true, + "execve": true, + "notebook_editor": true, + "impersonation": true, + "server_admin": true, + "filesystem_read": true, + "filesystem_write": true, + "machine_state": true, + "prepare_results": true + } + }, + { + "name": "ReaderUser", + "org_id": "ORGID", + "picture": "", + "email": false, + "roles": [ + "reader" + ], + "effective_policy": { + "read_results": true + } + } +]SELECT * FROM query(query={ SELECT * FROM orgs() ORDER BY OrgId }, runas="ReaderUser")[]SELECT * FROM test_read_logs() WHERE Log =~ "Permission denied.+ORG_ADMIN" AND NOT Log =~ "SELECT"[ + { + "Log": "Velociraptor: orgs: Permission denied: [ORG_ADMIN]\n" + } +]SELECT * FROM query(query={ SELECT OrgId FROM orgs() ORDER BY OrgId }, runas="OrgAdmin", org_id="root")[ + { + "OrgId": "" + }, + { + "OrgId": "ORGID" + } +]SELECT * FROM query(query={ SELECT OrgId FROM orgs() ORDER BY OrgId }, runas="OrgUser", org_id="ORGID")[]SELECT * FROM test_read_logs() WHERE Log =~ "Permission denied.+ORG_ADMIN" AND NOT Log =~ "SELECT"[ + { + "Log": "Velociraptor: orgs: Permission denied: [ORG_ADMIN]\n" + } +]LET _ <= org_create(name="MySecondOrg", org_id="ORGID2")[]LET _ <= user_create(user="OrgUser", roles="reader", password="X", orgs=["ORGID2"])[]SELECT * FROM gui_users(all_orgs=TRUE) WHERE name =~ "OrgUser" ORDER BY name[ + { + "name": "OrgUser", + "org_id": "ORGID", + "picture": "", + "email": false, + "roles": [ + "administrator" + ], + "effective_policy": { + "all_query": true, + "any_query": true, + "read_results": true, + "label_clients": true, + "collect_client": true, + "collect_server": true, + "artifact_writer": true, + "server_artifact_writer": true, + "execve": true, + "notebook_editor": true, + "impersonation": true, + "server_admin": true, + "filesystem_read": true, + "filesystem_write": true, + "machine_state": true, + "prepare_results": true + } + }, + { + "name": "OrgUser", + "org_id": "ORGID2", + "picture": "", + "email": false, + "roles": [ + "reader" + ], + "effective_policy": { + "read_results": true + } + } +]SELECT * FROM query(query={ SELECT collect_client(client_id="C.123", artifacts="Generic.Client.Info").request.artifacts FROM scope() }, runas="OrgUser", org_id="ORGID")[ + { + "collect_client(client_id=\"C.123\", artifacts=\"Generic.Client.Info\").request.artifacts": [ + "Generic.Client.Info" + ] + } +]SELECT * FROM query(query={ SELECT client_id, request.artifacts AS artifacts FROM flows(client_id="C.123") }, runas="OrgUser", org_id="ORGID")[ + { + "client_id": "C.123", + "artifacts": [ + "Generic.Client.Info" + ] + } +]SELECT client_id, request.artifacts AS artifacts FROM flows(client_id="C.123")[]SELECT * FROM query(query={ SELECT client_id, request.artifacts AS artifacts FROM flows(client_id="C.123") }, runas="OrgUser", org_id="ORGID2")[]SELECT * FROM query(query={ SELECT collect_client(client_id="C.123", artifacts="Generic.Client.Info").request.artifacts FROM scope() }, runas="OrgUser", org_id="ORGID2")[ + { + "collect_client(client_id=\"C.123\", artifacts=\"Generic.Client.Info\").request.artifacts": null + } +]SELECT * FROM test_read_logs() WHERE Log =~ "Permission denied.+COLLECT_CLIENT" AND NOT Log =~ "SELECT"[ + { + "Log": "Velociraptor: collect_client: Permission denied: [COLLECT_CLIENT]\n" + } +]SELECT * FROM query(query={ SELECT collect_client(client_id="C.123", artifacts="Generic.Client.DiskSpace", org_id="ORGID").request.artifacts FROM scope() }, runas="OrgUser")[ + { + "collect_client(client_id=\"C.123\", artifacts=\"Generic.Client.DiskSpace\", org_id=\"ORGID\").request.artifacts": [ + "Generic.Client.DiskSpace" + ] + } +]SELECT * FROM query(query={ SELECT client_id, request.artifacts AS artifacts FROM flows(client_id="C.123") WHERE artifacts =~ "Disk" }, runas="OrgUser", org_id="ORGID")[ + { + "client_id": "C.123", + "artifacts": [ + "Generic.Client.DiskSpace" + ] + } +] \ No newline at end of file diff --git a/artifacts/testdata/server/testcases/users.in.yaml b/artifacts/testdata/server/testcases/users.in.yaml index f9564a92823..a7419dc50b4 100644 --- a/artifacts/testdata/server/testcases/users.in.yaml +++ b/artifacts/testdata/server/testcases/users.in.yaml @@ -7,7 +7,7 @@ Queries: user="TestUser", password="hunter2", roles=["reader", "investigator"]) FROM scope() - - SELECT name, Permissions.roles FROM gui_users() WHERE name =~ "TestUser" + - SELECT name, roles FROM gui_users() WHERE name =~ "TestUser" # Now delete it - SELECT user_delete(user="TestUser") FROM scope() diff --git a/artifacts/testdata/server/testcases/users.out.yaml b/artifacts/testdata/server/testcases/users.out.yaml index d98ce5ae1b5..75a04bc14f5 100644 --- a/artifacts/testdata/server/testcases/users.out.yaml +++ b/artifacts/testdata/server/testcases/users.out.yaml @@ -2,10 +2,10 @@ SELECT * FROM gui_users() WHERE name =~ "TestUser"[]SELECT user_create( user="Te { "user_create(user=\"TestUser\", password=\"hunter2\", roles= [\"reader\", \"investigator\"])": "TestUser" } -]SELECT name, Permissions.roles FROM gui_users() WHERE name =~ "TestUser"[ +]SELECT name, roles FROM gui_users() WHERE name =~ "TestUser"[ { "name": "TestUser", - "Permissions.roles": [ + "roles": [ "reader", "investigator" ] diff --git a/artifacts/testdata/server/users/mic.db b/artifacts/testdata/server/users/mic.db deleted file mode 100644 index f1cf23a6547..00000000000 --- a/artifacts/testdata/server/users/mic.db +++ /dev/null @@ -1,2 +0,0 @@ - -mic ªã§»­•Œ”‚½5óNü0Öch5¶-TfV+›y&YÓ ‡€ÿΚ±””Š†zœ^ÒÅ…-Xú ìªS>9Û^G \ No newline at end of file diff --git a/bin/analysis_target.go b/bin/analysis_target.go index 2b367aa39ca..05741108e50 100644 --- a/bin/analysis_target.go +++ b/bin/analysis_target.go @@ -11,6 +11,7 @@ import ( config_proto "www.velocidex.com/golang/velociraptor/config/proto" logging "www.velocidex.com/golang/velociraptor/logging" vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" "www.velocidex.com/golang/velociraptor/vql/remapping" ) @@ -56,7 +57,7 @@ func applyAnalysisTarget(config_obj *config_proto.Config) error { // repository manager so we just make it up. scope := vql_subsystem.MakeScope() scope.AppendVars(ordereddict.NewDict(). - Set(vql_subsystem.ACL_MANAGER_VAR, vql_subsystem.NullACLManager{})) + Set(vql_subsystem.ACL_MANAGER_VAR, acl_managers.NullACLManager{})) defer scope.Close() // Apply the remapping on this scope to catch any errors in the diff --git a/bin/artifacts.go b/bin/artifacts.go index 61a38c90f47..764919fe238 100644 --- a/bin/artifacts.go +++ b/bin/artifacts.go @@ -35,7 +35,7 @@ import ( logging "www.velocidex.com/golang/velociraptor/logging" "www.velocidex.com/golang/velociraptor/services" "www.velocidex.com/golang/velociraptor/startup" - vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" ) var ( @@ -218,7 +218,7 @@ func doArtifactCollect() error { scope := manager.BuildScope(services.ScopeBuilder{ Config: config_obj, - ACLManager: vql_subsystem.NullACLManager{}, + ACLManager: acl_managers.NullACLManager{}, Logger: logger, Env: ordereddict.NewDict(). Set("Artifacts", *artifact_command_collect_names). @@ -394,7 +394,7 @@ func doArtifactList() error { } request, err := launcher.CompileCollectorArgs( - sm.Ctx, config_obj, vql_subsystem.NullACLManager{}, repository, + sm.Ctx, config_obj, acl_managers.NullACLManager{}, repository, services.CompilerOptions{ DisablePrecondition: true, }, diff --git a/bin/csv.go b/bin/csv.go index af30385688f..b79350dde74 100644 --- a/bin/csv.go +++ b/bin/csv.go @@ -9,6 +9,7 @@ import ( "www.velocidex.com/golang/velociraptor/services" "www.velocidex.com/golang/velociraptor/startup" vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" "www.velocidex.com/golang/vfilter" ) @@ -39,11 +40,11 @@ func doCSV() error { builder := services.ScopeBuilder{ Config: config_obj, - ACLManager: vql_subsystem.NullACLManager{}, + ACLManager: acl_managers.NullACLManager{}, Logger: log.New(&LogWriter{config_obj}, "", 0), Env: ordereddict.NewDict(). Set(vql_subsystem.ACL_MANAGER_VAR, - vql_subsystem.NewRoleACLManager("administrator")). + acl_managers.NewRoleACLManager("administrator")). Set("Files", *csv_cmd_files), } diff --git a/bin/deaddisk.go b/bin/deaddisk.go index 9472523c185..650731f0cd9 100644 --- a/bin/deaddisk.go +++ b/bin/deaddisk.go @@ -16,6 +16,7 @@ import ( "www.velocidex.com/golang/velociraptor/startup" "www.velocidex.com/golang/velociraptor/utils" vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" "www.velocidex.com/golang/vfilter" ) @@ -56,7 +57,7 @@ func addWindowsDirectory( builder := services.ScopeBuilder{ Config: config_obj, - ACLManager: vql_subsystem.NullACLManager{}, + ACLManager: acl_managers.NullACLManager{}, Logger: log.New(&LogWriter{config_obj}, "", 0), } @@ -164,11 +165,11 @@ func addWindowsHardDisk( builder := services.ScopeBuilder{ Config: config_obj, - ACLManager: vql_subsystem.NullACLManager{}, + ACLManager: acl_managers.NullACLManager{}, Logger: log.New(&LogWriter{config_obj}, "", 0), Env: ordereddict.NewDict(). Set(vql_subsystem.ACL_MANAGER_VAR, - vql_subsystem.NewRoleACLManager("administrator")). + acl_managers.NewRoleACLManager("administrator")). Set("ImagePath", image), } diff --git a/bin/fs.go b/bin/fs.go index f955972a15d..60e74879147 100644 --- a/bin/fs.go +++ b/bin/fs.go @@ -39,6 +39,7 @@ import ( "www.velocidex.com/golang/velociraptor/startup" "www.velocidex.com/golang/velociraptor/uploads" vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" vfilter "www.velocidex.com/golang/vfilter" ) @@ -147,11 +148,11 @@ func doLS(path, accessor string) error { builder := services.ScopeBuilder{ Config: config_obj, - ACLManager: vql_subsystem.NullACLManager{}, + ACLManager: acl_managers.NullACLManager{}, Logger: log.New(&LogWriter{config_obj}, "", 0), Env: ordereddict.NewDict(). Set(vql_subsystem.ACL_MANAGER_VAR, - vql_subsystem.NewRoleACLManager("administrator")). + acl_managers.NewRoleACLManager("administrator")). Set("accessor", accessor). Set("path", path), } @@ -211,7 +212,7 @@ func doRM(path, accessor string) error { builder := services.ScopeBuilder{ Config: config_obj, - ACLManager: vql_subsystem.NewRoleACLManager("administrator"), + ACLManager: acl_managers.NewRoleACLManager("administrator"), Logger: log.New(&LogWriter{config_obj}, "", 0), Env: ordereddict.NewDict(). Set("accessor", accessor). @@ -279,7 +280,7 @@ func doCp(path, accessor string, dump_dir string) error { Env: ordereddict.NewDict(). Set("accessor", accessor). Set("path", path), - ACLManager: vql_subsystem.NewRoleACLManager("administrator"), + ACLManager: acl_managers.NewRoleACLManager("administrator"), } switch output_accessor { diff --git a/bin/golden.go b/bin/golden.go index b5c4e601253..00c3279545d 100644 --- a/bin/golden.go +++ b/bin/golden.go @@ -44,6 +44,7 @@ import ( "www.velocidex.com/golang/velociraptor/services" "www.velocidex.com/golang/velociraptor/startup" vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" "www.velocidex.com/golang/velociraptor/vql/remapping" vfilter "www.velocidex.com/golang/vfilter" ) @@ -174,7 +175,7 @@ func runTest(fixture *testFixture, sm *services.Service, builder := services.ScopeBuilder{ Config: config_obj, - ACLManager: vql_subsystem.NewRoleACLManager("administrator"), + ACLManager: acl_managers.NewRoleACLManager("administrator", "org_admin"), Logger: log.New(log_writer, "Velociraptor: ", 0), Uploader: container, Env: ordereddict.NewDict(). @@ -423,6 +424,8 @@ func (self MemoryLogPlugin) Call( output_chan <- ordereddict.NewDict(). Set("Log", line) } + + log_writer.Clear() } }() diff --git a/bin/orgs.go b/bin/orgs.go index 9dfeb67bfb3..2c29b371822 100644 --- a/bin/orgs.go +++ b/bin/orgs.go @@ -124,7 +124,7 @@ func doOrgCreate() error { return err } - record, err := org_manager.CreateNewOrg(*orgs_create_name) + record, err := org_manager.CreateNewOrg(*orgs_create_name, "") if err != nil { return err } diff --git a/bin/query.go b/bin/query.go index ae21ff96871..4ba2ffd0bcc 100644 --- a/bin/query.go +++ b/bin/query.go @@ -40,6 +40,7 @@ import ( "www.velocidex.com/golang/velociraptor/uploads" "www.velocidex.com/golang/velociraptor/utils" vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" "www.velocidex.com/golang/vfilter" ) @@ -281,13 +282,13 @@ func doQuery() error { builder := services.ScopeBuilder{ Config: config_obj, - ACLManager: vql_subsystem.NullACLManager{}, + ACLManager: acl_managers.NullACLManager{}, Logger: log.New(&LogWriter{config_obj}, "", 0), Env: ordereddict.NewDict(), } if *run_as != "" { - builder.ACLManager = vql_subsystem.NewServerACLManager(config_obj, *run_as) + builder.ACLManager = acl_managers.NewServerACLManager(config_obj, *run_as) } // Configure an uploader if required. diff --git a/bin/unzip.go b/bin/unzip.go index 7bfe61e011d..5c264f68f8e 100644 --- a/bin/unzip.go +++ b/bin/unzip.go @@ -11,7 +11,7 @@ import ( "www.velocidex.com/golang/velociraptor/services" "www.velocidex.com/golang/velociraptor/startup" "www.velocidex.com/golang/velociraptor/uploads" - vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" "www.velocidex.com/golang/vfilter" ) @@ -62,7 +62,7 @@ func doUnzip() error { builder := services.ScopeBuilder{ Config: config_obj, - ACLManager: vql_subsystem.NewRoleACLManager("administrator"), + ACLManager: acl_managers.NewRoleACLManager("administrator"), Logger: log.New(&LogWriter{config_obj}, "", 0), Env: ordereddict.NewDict(). Set("ZipPath", filename). diff --git a/docs/references/vql.yaml b/docs/references/vql.yaml index 9f6f994f71f..13bb29e321a 100644 --- a/docs/references/vql.yaml +++ b/docs/references/vql.yaml @@ -607,6 +607,9 @@ type: bool description: Set the collection as urgent - skips other queues collections on the client. + - name: org_id + type: string + description: If set the collection will be started in the specified org. category: server - name: column_filter description: | @@ -2950,6 +2953,20 @@ type: int64 description: Maximum size of file we load into memory. category: parsers +- name: org_create + description: Creates a new organizaion. + type: Function + args: + - name: name + type: string + description: The name of the org. + required: true + - name: org_id + type: string + description: An ID for the new org (if not set use a random ID). +- name: orgs + description: Retrieve the list of orgs on this server. + type: Plugin - name: parallelize description: | Runs query on result batches in parallel. @@ -3968,6 +3985,13 @@ type: float64 description: If no progress is detected in this many seconds, we terminate the query and output debugging information + - name: org_id + type: string + description: If specified, the query will run in the specified org space (Use + 'root' to refer to the root org) + - name: runas + type: string + description: If specified, the query will run as the specified user - name: rand description: Selects a random number. type: Function @@ -4386,9 +4410,11 @@ args: - name: item type: Any + description: A dict to set required: true - name: field type: string + description: The field to set required: true - name: value type: Any @@ -5250,13 +5276,20 @@ args: - name: user type: string + description: The user to create or update. required: true - name: roles type: string + description: List of roles to give the user. repeated: true required: true - name: password type: string + description: A password to set for the user (If not using SSO this might be needed). + - name: orgs + type: string + description: One or more org IDs to grant access to. + repeated: true category: server - name: user_delete description: Deletes a user from the server. @@ -5264,6 +5297,7 @@ args: - name: user type: string + description: The user to delete. required: true category: server - name: users @@ -5636,3 +5670,4 @@ type: string description: If set use this key to cache the yara rules. category: plugin + diff --git a/file_store/memory/memory.go b/file_store/memory/memory.go index b29c0139b4c..d6e6df02360 100644 --- a/file_store/memory/memory.go +++ b/file_store/memory/memory.go @@ -23,6 +23,13 @@ var ( Test_memory_file_store *MemoryFileStore ) +func ResetMemoryFileStore() { + mu.Lock() + defer mu.Unlock() + + Test_memory_file_store = nil +} + func NewMemoryFileStore(config_obj *config_proto.Config) *MemoryFileStore { mu.Lock() defer mu.Unlock() @@ -393,6 +400,9 @@ func (self *MemoryFileStore) Clear() { self.Data = ordereddict.NewDict() self.Paths = ordereddict.NewDict() + + // Next filestore will be pristine. + ResetMemoryFileStore() } func (self *MemoryFileStore) Close() error { diff --git a/file_store/test_utils/query.go b/file_store/test_utils/query.go index 553fe9f62db..1c2026decf6 100644 --- a/file_store/test_utils/query.go +++ b/file_store/test_utils/query.go @@ -8,7 +8,7 @@ import ( config_proto "www.velocidex.com/golang/velociraptor/config/proto" "www.velocidex.com/golang/velociraptor/logging" "www.velocidex.com/golang/velociraptor/services" - vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" "www.velocidex.com/golang/vfilter" ) @@ -21,7 +21,7 @@ func RunQuery( builder := services.ScopeBuilder{ Config: config_obj, - ACLManager: vql_subsystem.NullACLManager{}, + ACLManager: acl_managers.NullACLManager{}, Logger: logging.NewPlainLogger( config_obj, &logging.FrontendComponent), Env: env, diff --git a/flows/artifacts_test.go b/flows/artifacts_test.go index 04bb4a00a52..279371c405b 100644 --- a/flows/artifacts_test.go +++ b/flows/artifacts_test.go @@ -31,6 +31,7 @@ import ( _ "www.velocidex.com/golang/velociraptor/accessors/file" _ "www.velocidex.com/golang/velociraptor/accessors/ntfs" _ "www.velocidex.com/golang/velociraptor/result_sets/timed" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" _ "www.velocidex.com/golang/velociraptor/vql/filesystem" _ "www.velocidex.com/golang/velociraptor/vql/networking" _ "www.velocidex.com/golang/velociraptor/vql/windows" @@ -108,7 +109,7 @@ func (self *TestSuite) TestGetFlow() { for i := 0; i < 20; i++ { flow_id, err := launcher.ScheduleArtifactCollection( ctx, self.ConfigObj, - vql_subsystem.NullACLManager{}, + acl_managers.NullACLManager{}, repository, request1, nil) assert.NoError(self.T(), err) @@ -116,7 +117,7 @@ func (self *TestSuite) TestGetFlow() { flow_id, err = launcher.ScheduleArtifactCollection( ctx, self.ConfigObj, - vql_subsystem.NullACLManager{}, + acl_managers.NullACLManager{}, repository, request2, nil) assert.NoError(self.T(), err) @@ -170,7 +171,7 @@ func (self *TestSuite) TestRetransmission() { flow_id, err := launcher.ScheduleArtifactCollection( ctx, self.ConfigObj, - vql_subsystem.NullACLManager{}, + acl_managers.NullACLManager{}, repository, request, nil) assert.NoError(self.T(), err) @@ -233,7 +234,7 @@ func (self *TestSuite) TestResourceLimits() { flow_id, err := launcher.ScheduleArtifactCollection( ctx, self.ConfigObj, - vql_subsystem.NullACLManager{}, + acl_managers.NullACLManager{}, repository, request, nil) assert.NoError(self.T(), err) @@ -356,7 +357,7 @@ func (self *TestSuite) TestClientUploaderStoreFile() { } scope := vql_subsystem.MakeScope().AppendVars(ordereddict.NewDict(). - Set(vql_subsystem.ACL_MANAGER_VAR, vql_subsystem.NullACLManager{})) + Set(vql_subsystem.ACL_MANAGER_VAR, acl_managers.NullACLManager{})) uploader.Upload(context.Background(), scope, filename, "ntfs", "", 1000, nilTime, nilTime, nilTime, nilTime, reader) @@ -800,7 +801,7 @@ func (self *TestSuite) TestClientUploaderStoreSparseFile() { } scope := vql_subsystem.MakeScope().AppendVars(ordereddict.NewDict(). - Set(vql_subsystem.ACL_MANAGER_VAR, vql_subsystem.NullACLManager{})) + Set(vql_subsystem.ACL_MANAGER_VAR, acl_managers.NullACLManager{})) uploader.Upload(context.Background(), scope, sparse_filename, "ntfs", "", 1000, nilTime, nilTime, nilTime, nilTime, reader) @@ -914,7 +915,7 @@ func (self *TestSuite) TestClientUploaderStoreSparseFileNTFS() { cmd.CombinedOutput() scope := vql_subsystem.MakeScope().AppendVars(ordereddict.NewDict(). - Set(vql_subsystem.ACL_MANAGER_VAR, vql_subsystem.NullACLManager{})) + Set(vql_subsystem.ACL_MANAGER_VAR, acl_managers.NullACLManager{})) accessor, err := accessors.GetAccessor("ntfs", scope) assert.NoError(self.T(), err) diff --git a/gui/velociraptor/src/components/users/user-label.css b/gui/velociraptor/src/components/users/user-label.css index 96af5b301fa..96bfe910290 100644 --- a/gui/velociraptor/src/components/users/user-label.css +++ b/gui/velociraptor/src/components/users/user-label.css @@ -12,3 +12,9 @@ .timezone-selector { flex: 1 1 auto; } + +.org-label { + padding-left: 1ex; + display: inline; + opacity: 50%; +} diff --git a/gui/velociraptor/src/components/users/user-label.js b/gui/velociraptor/src/components/users/user-label.js index 4d41185fa84..4dbee6f7c53 100644 --- a/gui/velociraptor/src/components/users/user-label.js +++ b/gui/velociraptor/src/components/users/user-label.js @@ -8,6 +8,8 @@ import ButtonGroup from 'react-bootstrap/ButtonGroup'; import Button from 'react-bootstrap/Button'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { withRouter } from "react-router-dom"; + import UserConfig from '../core/user.js'; import Modal from 'react-bootstrap/Modal'; import Form from 'react-bootstrap/Form'; @@ -60,6 +62,20 @@ class UserSettings extends React.PureComponent { }); } + changeOrg = org=>{ + // Force navigation to the + // welcome screen to make sure + // the GUI is reset + this.props.history.push("/welcome"); + this.props.setSetting({ + theme: this.state.theme, + timezone: this.state.timezone, + lang: this.state.lang, + org: org, + default_password: this.state.default_password, + }); + this.props.onClose(); + } render() { return ( { - this.setState({ - org: e.currentTarget.value, - org_changed: true, - }); - }}> + placeholder={T("Select an org")} + onChange={e=>this.changeOrg(e.currentTarget.value)} + > {_.map(this.context.traits.orgs || [], function(x) { return ; })} @@ -229,6 +241,7 @@ class UserSettings extends React.PureComponent { }; } +const UserSettingsWithRouter = withRouter(UserSettings); export default class UserLabel extends React.Component { static contextType = UserConfig; @@ -283,11 +296,25 @@ export default class UserLabel extends React.Component { }); } + orgName() { + let id = this.context.traits && this.context.traits.org; + if (!id || id==="root") { + return <>; + } + let orgs = (this.context.traits && this.context.traits.orgs) || []; + for(let i=0; i{orgs[i].name}; + } + }; + return <>; + } + render() { return ( <> { this.state.showUserSettings && - this.setState({showUserSettings: false})} /> } @@ -305,6 +332,7 @@ export default class UserLabel extends React.Component { src={ this.context.traits.picture} /> } + { this.orgName() } diff --git a/json/protobuf.go b/json/protobuf.go index 62c33f492df..8dd04be8d5d 100644 --- a/json/protobuf.go +++ b/json/protobuf.go @@ -105,6 +105,8 @@ func setOneValue(result *ordereddict.Dict, value protoreflect.Value, result.Set(field_name, descriptorToDict(value.Message())) default: - result.Set(field_name, value.Interface()) + // Skip empty values + v := value.Interface() + result.Set(field_name, v) } } diff --git a/server/server_test.go b/server/server_test.go index 2608c546ab4..1ed57b4e65b 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -30,7 +30,7 @@ import ( "www.velocidex.com/golang/velociraptor/paths/artifacts" "www.velocidex.com/golang/velociraptor/server" "www.velocidex.com/golang/velociraptor/services" - vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" "www.velocidex.com/golang/velociraptor/vtesting/assert" _ "www.velocidex.com/golang/velociraptor/result_sets/simple" @@ -224,7 +224,7 @@ func (self *ServerTestSuite) TestForeman() { hunt_id, err := hunt_dispatcher.CreateHunt( context.Background(), self.ConfigObj, - vql_subsystem.NullACLManager{}, + acl_managers.NullACLManager{}, &api_proto.Hunt{ State: api_proto.Hunt_RUNNING, StartRequest: expected, @@ -451,7 +451,7 @@ func (self *ServerTestSuite) TestScheduleCollection() { flow_id, err := launcher.ScheduleArtifactCollection( context.Background(), self.ConfigObj, - vql_subsystem.NullACLManager{}, + acl_managers.NullACLManager{}, repository, request, nil) @@ -489,7 +489,7 @@ func (self *ServerTestSuite) createArtifactCollection() (string, error) { flow_id, err := launcher.ScheduleArtifactCollection( context.Background(), self.ConfigObj, - vql_subsystem.NullACLManager{}, + acl_managers.NullACLManager{}, repository, &flows_proto.ArtifactCollectorArgs{ ClientId: self.client_id, diff --git a/services/client_monitoring/client_monitoring.go b/services/client_monitoring/client_monitoring.go index b3fcc0dd9ac..1e9c676bdef 100644 --- a/services/client_monitoring/client_monitoring.go +++ b/services/client_monitoring/client_monitoring.go @@ -35,7 +35,7 @@ import ( "www.velocidex.com/golang/velociraptor/paths" "www.velocidex.com/golang/velociraptor/services" "www.velocidex.com/golang/velociraptor/utils" - vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" ) type ClientEventTable struct { @@ -162,7 +162,7 @@ func (self *ClientEventTable) compileArtifactCollectorArgs( } return launcher.CompileCollectorArgs( - ctx, config_obj, vql_subsystem.NullACLManager{}, + ctx, config_obj, acl_managers.NullACLManager{}, repository, services.CompilerOptions{ ObfuscateNames: true, IgnoreMissingArtifacts: true, diff --git a/services/hunt_dispatcher/hunts_test.go b/services/hunt_dispatcher/hunts_test.go index a10e25b4387..63658307b57 100644 --- a/services/hunt_dispatcher/hunts_test.go +++ b/services/hunt_dispatcher/hunts_test.go @@ -11,7 +11,7 @@ import ( flows_proto "www.velocidex.com/golang/velociraptor/flows/proto" "www.velocidex.com/golang/velociraptor/paths" "www.velocidex.com/golang/velociraptor/services" - vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" _ "www.velocidex.com/golang/velociraptor/result_sets/timed" ) @@ -65,7 +65,7 @@ sources: }, } - acl_manager := vql_subsystem.NullACLManager{} + acl_manager := acl_managers.NullACLManager{} hunt_dispatcher, err := services.GetHuntDispatcher(self.ConfigObj) assert.NoError(self.T(), err) diff --git a/services/hunt_manager/hunt_manager.go b/services/hunt_manager/hunt_manager.go index f727bef318e..9b5273f6c52 100644 --- a/services/hunt_manager/hunt_manager.go +++ b/services/hunt_manager/hunt_manager.go @@ -66,7 +66,7 @@ import ( "www.velocidex.com/golang/velociraptor/services" "www.velocidex.com/golang/velociraptor/services/journal" "www.velocidex.com/golang/velociraptor/utils" - vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" "www.velocidex.com/golang/vfilter" ) @@ -725,7 +725,7 @@ func scheduleHuntOnClient( request.Creator = hunt_id flow_id, err := launcher.ScheduleArtifactCollection( - ctx, config_obj, vql_subsystem.NullACLManager{}, + ctx, config_obj, acl_managers.NullACLManager{}, repository, request, nil) if err != nil { return err diff --git a/services/hunt_manager/hunt_manager_test.go b/services/hunt_manager/hunt_manager_test.go index 4e1ed6eda34..05c68e0bf62 100644 --- a/services/hunt_manager/hunt_manager_test.go +++ b/services/hunt_manager/hunt_manager_test.go @@ -19,7 +19,7 @@ import ( "www.velocidex.com/golang/velociraptor/paths" "www.velocidex.com/golang/velociraptor/services" "www.velocidex.com/golang/velociraptor/services/hunt_manager" - vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" "www.velocidex.com/golang/velociraptor/vtesting" _ "www.velocidex.com/golang/velociraptor/result_sets/timed" @@ -583,7 +583,7 @@ func (self *HuntTestSuite) TestHuntClientOSConditionInterrogation() { }, } - acl_manager := vql_subsystem.NullACLManager{} + acl_manager := acl_managers.NullACLManager{} hunt_dispatcher, err := services.GetHuntDispatcher(self.ConfigObj) assert.NoError(t, err) diff --git a/services/indexing/rebuild.go b/services/indexing/rebuild.go index ccc10ffc379..8824c8b3796 100644 --- a/services/indexing/rebuild.go +++ b/services/indexing/rebuild.go @@ -2,6 +2,7 @@ package indexing import ( "context" + "errors" "strings" "time" @@ -33,6 +34,12 @@ func (self *Indexer) LoadIndexFromDatastore( now := time.Now() count := 0 for _, child := range children { + select { + case <-ctx.Done(): + return errors.New("Cancelled") + default: + } + if child.IsDir() { continue } diff --git a/services/interrogation/interrogation.go b/services/interrogation/interrogation.go index 9bf2ec99165..3d6298ee4ce 100644 --- a/services/interrogation/interrogation.go +++ b/services/interrogation/interrogation.go @@ -44,7 +44,7 @@ import ( "www.velocidex.com/golang/velociraptor/result_sets" "www.velocidex.com/golang/velociraptor/services" "www.velocidex.com/golang/velociraptor/services/journal" - vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" ) type EnrollmentService struct { @@ -146,7 +146,7 @@ func (self *EnrollmentService) ProcessEnrollment( } flow_id, err := launcher.ScheduleArtifactCollection( - ctx, config_obj, vql_subsystem.NullACLManager{}, + ctx, config_obj, acl_managers.NullACLManager{}, repository, &flows_proto.ArtifactCollectorArgs{ Creator: "InterrogationService", diff --git a/services/inventory/inventory_test.go b/services/inventory/inventory_test.go index ef385f1162e..ad855d9ab91 100644 --- a/services/inventory/inventory_test.go +++ b/services/inventory/inventory_test.go @@ -19,7 +19,7 @@ import ( "www.velocidex.com/golang/velociraptor/services" "www.velocidex.com/golang/velociraptor/services/inventory" "www.velocidex.com/golang/velociraptor/services/launcher" - vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" _ "www.velocidex.com/golang/velociraptor/result_sets/timed" ) @@ -162,7 +162,7 @@ tools: assert.NoError(self.T(), err) response, err := launcher.CompileCollectorArgs( - ctx, self.ConfigObj, vql_subsystem.NullACLManager{}, repository, + ctx, self.ConfigObj, acl_managers.NullACLManager{}, repository, services.CompilerOptions{}, &flows_proto.ArtifactCollectorArgs{ Artifacts: []string{"TestArtifact"}, @@ -260,7 +260,7 @@ tools: assert.NoError(self.T(), err) response, err := launcher.CompileCollectorArgs( - ctx, self.ConfigObj, vql_subsystem.NullACLManager{}, repository, + ctx, self.ConfigObj, acl_managers.NullACLManager{}, repository, services.CompilerOptions{}, &flows_proto.ArtifactCollectorArgs{ Artifacts: []string{"TestArtifact"}, diff --git a/services/launcher/artifacts_test.go b/services/launcher/artifacts_test.go index b9aee1eff19..4dd65ed5480 100644 --- a/services/launcher/artifacts_test.go +++ b/services/launcher/artifacts_test.go @@ -14,7 +14,7 @@ import ( "www.velocidex.com/golang/velociraptor/responder" "www.velocidex.com/golang/velociraptor/services" "www.velocidex.com/golang/velociraptor/utils" - vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" ) var ( @@ -97,7 +97,7 @@ func (self *ArtifactTestSuite) TestUnknownArtifact() { assert.NoError(self.T(), err) _, err = launcher.CompileCollectorArgs(context.Background(), self.ConfigObj, - vql_subsystem.NullACLManager{}, + acl_managers.NullACLManager{}, self.repository, services.CompilerOptions{}, request) assert.Error(self.T(), err) assert.Contains(self.T(), err.Error(), "Unknown artifact reference") @@ -114,7 +114,7 @@ func (self *ArtifactTestSuite) TestStackOverflow() { launcher, err := services.GetLauncher(self.ConfigObj) assert.NoError(self.T(), err) vql_requests, err := launcher.CompileCollectorArgs(context.Background(), - self.ConfigObj, vql_subsystem.NullACLManager{}, + self.ConfigObj, acl_managers.NullACLManager{}, self.repository, services.CompilerOptions{}, request) assert.NoError(self.T(), err) @@ -142,7 +142,7 @@ func (self *ArtifactTestSuite) TestArtifactDependencies() { assert.NoError(self.T(), err) vql_requests, err := launcher.CompileCollectorArgs(context.Background(), - self.ConfigObj, vql_subsystem.NullACLManager{}, + self.ConfigObj, acl_managers.NullACLManager{}, self.repository, services.CompilerOptions{}, request) assert.NoError(self.T(), err) diff --git a/services/launcher/launcher_test.go b/services/launcher/launcher_test.go index 42705eb211e..4c0607258cb 100644 --- a/services/launcher/launcher_test.go +++ b/services/launcher/launcher_test.go @@ -27,12 +27,12 @@ import ( "www.velocidex.com/golang/velociraptor/paths" "www.velocidex.com/golang/velociraptor/responder" "www.velocidex.com/golang/velociraptor/services" - vql_subsystem "www.velocidex.com/golang/velociraptor/vql" "www.velocidex.com/golang/vfilter" // Load plugins (timestamp, parse_csv) _ "www.velocidex.com/golang/velociraptor/accessors/data" _ "www.velocidex.com/golang/velociraptor/result_sets/timed" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" _ "www.velocidex.com/golang/velociraptor/vql/functions" _ "www.velocidex.com/golang/velociraptor/vql/parsers/csv" _ "www.velocidex.com/golang/velociraptor/vql/protocols" @@ -108,7 +108,7 @@ func (self *LauncherTestSuite) TestCompilingWithTools() { Timeout: 73, } ctx := context.Background() - acl_manager := vql_subsystem.NullACLManager{} + acl_manager := acl_managers.NullACLManager{} // Simulate an error downloading the tool on demand - this // prevents the VQL from being compiled, and therefore @@ -267,7 +267,7 @@ func (self *LauncherTestSuite) TestGetDependentArtifactsWithImports() { Artifacts: []string{"Custom.CallArtifactWithImports"}, } ctx := context.Background() - acl_manager := vql_subsystem.NullACLManager{} + acl_manager := acl_managers.NullACLManager{} // Compile the request. compiled, err := launcher.CompileCollectorArgs(ctx, self.ConfigObj, @@ -311,7 +311,7 @@ sources: Timeout: 73, } ctx := context.Background() - acl_manager := vql_subsystem.NullACLManager{} + acl_manager := acl_managers.NullACLManager{} // Compile the request. launcher, err := services.GetLauncher(self.ConfigObj) @@ -380,7 +380,7 @@ func (self *LauncherTestSuite) TestCompiling() { Timeout: 73, } ctx := context.Background() - acl_manager := vql_subsystem.NullACLManager{} + acl_manager := acl_managers.NullACLManager{} launcher, err := services.GetLauncher(self.ConfigObj) assert.NoError(self.T(), err) @@ -462,7 +462,7 @@ func (self *LauncherTestSuite) TestCompilingMultipleArtifacts() { Timeout: 73, } ctx := context.Background() - acl_manager := vql_subsystem.NullACLManager{} + acl_manager := acl_managers.NullACLManager{} launcher, err := services.GetLauncher(self.ConfigObj) assert.NoError(self.T(), err) @@ -522,7 +522,7 @@ sources: } ctx := context.Background() - acl_manager := vql_subsystem.NullACLManager{} + acl_manager := acl_managers.NullACLManager{} launcher, err := services.GetLauncher(self.ConfigObj) assert.NoError(self.T(), err) @@ -552,7 +552,7 @@ func (self *LauncherTestSuite) TestCompilingObfuscation() { Artifacts: []string{"Test.Artifact"}, } ctx := context.Background() - acl_manager := vql_subsystem.NullACLManager{} + acl_manager := acl_managers.NullACLManager{} launcher, err := services.GetLauncher(self.ConfigObj) assert.NoError(self.T(), err) @@ -586,7 +586,7 @@ sources: } ctx := context.Background() - acl_manager := vql_subsystem.NewServerACLManager(self.ConfigObj, "UserX") + acl_manager := acl_managers.NewServerACLManager(self.ConfigObj, "UserX") // Permission denied - the principal is not allowed to compile this artifact. launcher, err := services.GetLauncher(self.ConfigObj) @@ -604,7 +604,7 @@ sources: assert.NoError(self.T(), err) // Should be fine now. - acl_manager = vql_subsystem.NewServerACLManager(self.ConfigObj, "UserX") + acl_manager = acl_managers.NewServerACLManager(self.ConfigObj, "UserX") compiled, err = launcher.CompileCollectorArgs( ctx, self.ConfigObj, acl_manager, repository, services.CompilerOptions{}, request) @@ -646,7 +646,7 @@ func (self *LauncherTestSuite) TestParameterTypes() { defer cancel() // Compile the artifact request into VQL - acl_manager := vql_subsystem.NullACLManager{} + acl_manager := acl_managers.NullACLManager{} launcher, err := services.GetLauncher(self.ConfigObj) assert.NoError(self.T(), err) @@ -787,7 +787,7 @@ func (self *LauncherTestSuite) TestParameterTypesDeps() { defer cancel() // Compile the artifact request into VQL - acl_manager := vql_subsystem.NullACLManager{} + acl_manager := acl_managers.NullACLManager{} launcher, err := services.GetLauncher(self.ConfigObj) assert.NoError(self.T(), err) @@ -814,7 +814,7 @@ func (self *LauncherTestSuite) TestParameterTypesDepsQuery() { builder := services.ScopeBuilder{ Config: self.ConfigObj, - ACLManager: vql_subsystem.NullACLManager{}, + ACLManager: acl_managers.NullACLManager{}, Repository: repository, Logger: logging.NewPlainLogger( self.ConfigObj, &logging.FrontendComponent), @@ -881,7 +881,7 @@ sources: defer cancel() // Compile the artifact request into VQL - acl_manager := vql_subsystem.NullACLManager{} + acl_manager := acl_managers.NullACLManager{} launcher, err := services.GetLauncher(self.ConfigObj) assert.NoError(self.T(), err) @@ -930,7 +930,7 @@ sources: defer cancel() // Compile the artifact request into VQL - acl_manager := vql_subsystem.NullACLManager{} + acl_manager := acl_managers.NullACLManager{} launcher, err := services.GetLauncher(self.ConfigObj) assert.NoError(self.T(), err) @@ -984,7 +984,7 @@ sources: defer cancel() // Compile the artifact request into VQL - acl_manager := vql_subsystem.NullACLManager{} + acl_manager := acl_managers.NullACLManager{} launcher, err := services.GetLauncher(self.ConfigObj) assert.NoError(self.T(), err) @@ -1093,7 +1093,7 @@ sources: defer cancel() // Compile the artifact request into VQL - acl_manager := vql_subsystem.NullACLManager{} + acl_manager := acl_managers.NullACLManager{} launcher, err := services.GetLauncher(self.ConfigObj) assert.NoError(self.T(), err) diff --git a/services/notebook/calculate.go b/services/notebook/calculate.go index 58aa6db5b66..ef9fec9a56c 100644 --- a/services/notebook/calculate.go +++ b/services/notebook/calculate.go @@ -14,7 +14,7 @@ import ( "www.velocidex.com/golang/velociraptor/paths" "www.velocidex.com/golang/velociraptor/reporting" "www.velocidex.com/golang/velociraptor/services" - vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" ) func (self *NotebookManager) UpdateNotebookCell( @@ -45,7 +45,7 @@ func (self *NotebookManager) UpdateNotebookCell( // Run the actual query independently. query_ctx, query_cancel := context.WithCancel(context.Background()) - acl_manager := vql_subsystem.NewServerACLManager(self.config_obj, user_name) + acl_manager := acl_managers.NewServerACLManager(self.config_obj, user_name) manager, err := services.GetRepositoryManager(self.config_obj) if err != nil { diff --git a/services/notifications/notifications.go b/services/notifications/notifications.go index be1194a013b..199fc8ea739 100644 --- a/services/notifications/notifications.go +++ b/services/notifications/notifications.go @@ -129,7 +129,7 @@ func NewNotificationService( } self.notification_pool = nil }() - defer logger.Info("Exiting notification service for %v!", + defer logger.Info("Exiting notification service for %v!", services.GetOrgName(config_obj)) for { diff --git a/services/orgs.go b/services/orgs.go index 06a89956835..078998dd747 100644 --- a/services/orgs.go +++ b/services/orgs.go @@ -58,7 +58,7 @@ type ServiceContainer interface { type OrgManager interface { GetOrgConfig(org_id string) (*config_proto.Config, error) OrgIdByNonce(nonce string) (string, error) - CreateNewOrg(name string) (*api_proto.OrgRecord, error) + CreateNewOrg(name, id string) (*api_proto.OrgRecord, error) ListOrgs() []*api_proto.OrgRecord GetOrg(org_id string) (*api_proto.OrgRecord, error) diff --git a/services/orgs/orgs.go b/services/orgs/orgs.go index 590f91e1e85..6cc38ecdf25 100644 --- a/services/orgs/orgs.go +++ b/services/orgs/orgs.go @@ -2,6 +2,7 @@ package orgs import ( "context" + "errors" "sync" "time" @@ -50,6 +51,10 @@ func (self *OrgManager) GetOrgConfig(org_id string) (*config_proto.Config, error self.mu.Lock() defer self.mu.Unlock() + if org_id == "root" { + org_id = "" + } + // An empty org id corresponds to the root org. if org_id == "" { return self.config_obj, nil @@ -66,6 +71,10 @@ func (self *OrgManager) GetOrg(org_id string) (*api_proto.OrgRecord, error) { self.mu.Lock() defer self.mu.Unlock() + if org_id == "root" { + org_id = "" + } + result, pres := self.orgs[org_id] if !pres { return nil, services.NotFoundError @@ -90,15 +99,27 @@ func (self *OrgManager) OrgIdByNonce(nonce string) (string, error) { return result, nil } -func (self *OrgManager) CreateNewOrg(name string) ( +func (self *OrgManager) CreateNewOrg(name, id string) ( *api_proto.OrgRecord, error) { + if id == "" { + id = NewOrgId() + } + org_record := &api_proto.OrgRecord{ Name: name, - OrgId: NewOrgId(), + OrgId: id, Nonce: NewNonce(), } + // Check if the org already exists + self.mu.Lock() + _, pres := self.orgs[id] + self.mu.Unlock() + if pres { + return nil, errors.New("Org ID already exists") + } + err := self.startOrg(org_record) if err != nil { return nil, err diff --git a/services/repository/plugin.go b/services/repository/plugin.go index f61eca23d5b..b6d8331fbaa 100644 --- a/services/repository/plugin.go +++ b/services/repository/plugin.go @@ -17,6 +17,7 @@ import ( flows_proto "www.velocidex.com/golang/velociraptor/flows/proto" "www.velocidex.com/golang/velociraptor/services" vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" "www.velocidex.com/golang/vfilter" "www.velocidex.com/golang/vfilter/types" ) @@ -119,7 +120,7 @@ func (self *ArtifactRepositoryPlugin) Call( acl_manager, ok := artifacts.GetACLManager(scope) if !ok { - acl_manager = vql_subsystem.NullACLManager{} + acl_manager = acl_managers.NullACLManager{} } launcher, err := services.GetLauncher(self.config_obj) diff --git a/services/repository/plugin_test.go b/services/repository/plugin_test.go index 18bc72bf475..d263952388c 100644 --- a/services/repository/plugin_test.go +++ b/services/repository/plugin_test.go @@ -32,9 +32,9 @@ import ( "www.velocidex.com/golang/velociraptor/responder" "www.velocidex.com/golang/velociraptor/services" "www.velocidex.com/golang/velociraptor/services/repository" - vql_subsystem "www.velocidex.com/golang/velociraptor/vql" "www.velocidex.com/golang/vfilter" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" _ "www.velocidex.com/golang/velociraptor/vql/common" ) @@ -129,7 +129,7 @@ func (self *PluginTestSuite) TestArtifactPluginWithPrecondition() { builder := services.ScopeBuilder{ Config: self.ConfigObj, - ACLManager: vql_subsystem.NullACLManager{}, + ACLManager: acl_managers.NullACLManager{}, Repository: repository, Logger: logging.NewPlainLogger(self.ConfigObj, &logging.FrontendComponent), Env: ordereddict.NewDict(), @@ -191,7 +191,7 @@ func (self *PluginTestSuite) TestClientPluginMultipleSources() { Artifacts: []string{"Call"}, } - acl_manager := vql_subsystem.NullACLManager{} + acl_manager := acl_managers.NullACLManager{} launcher, err := services.GetLauncher(self.ConfigObj) assert.NoError(self.T(), err) @@ -236,7 +236,7 @@ func (self *PluginTestSuite) TestClientPluginMultipleSourcesAndPrecondtions() { repository := self.LoadArtifacts(precondition_source_definitions) builder := services.ScopeBuilder{ Config: self.ConfigObj, - ACLManager: vql_subsystem.NullACLManager{}, + ACLManager: acl_managers.NullACLManager{}, Repository: repository, Logger: logging.NewPlainLogger( self.ConfigObj, &logging.FrontendComponent), @@ -296,7 +296,7 @@ func (self *PluginTestSuite) TestClientPluginMultipleSourcesAndPrecondtionsEvent repository := self.LoadArtifacts(precondition_source_events_definitions) builder := services.ScopeBuilder{ Config: self.ConfigObj, - ACLManager: vql_subsystem.NullACLManager{}, + ACLManager: acl_managers.NullACLManager{}, Repository: repository, Logger: logging.NewPlainLogger( self.ConfigObj, &logging.FrontendComponent), diff --git a/services/repository/scope.go b/services/repository/scope.go index 1799b7cd5e5..d2f43dd13ed 100644 --- a/services/repository/scope.go +++ b/services/repository/scope.go @@ -11,6 +11,7 @@ import ( "www.velocidex.com/golang/velociraptor/json" "www.velocidex.com/golang/velociraptor/services" vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" "www.velocidex.com/golang/velociraptor/vql/remapping" "www.velocidex.com/golang/velociraptor/vql/sorter" "www.velocidex.com/golang/vfilter" @@ -110,7 +111,7 @@ func _build(self services.ScopeBuilder, from_scratch bool) vfilter.Scope { // Reduce permissions based on the configuration. if self.ACLManager != nil { - new_acl_manager, err := accessors.GetRemappingACLManager( + new_acl_manager, err := acl_managers.GetRemappingACLManager( self.ACLManager, self.Config.Remappings) if err != nil { scope.Log("Applying remapping: %v", err) diff --git a/services/sanity/server_artifacts.go b/services/sanity/server_artifacts.go index 68226790bfe..5e2bcd79f9c 100644 --- a/services/sanity/server_artifacts.go +++ b/services/sanity/server_artifacts.go @@ -11,7 +11,7 @@ import ( "www.velocidex.com/golang/velociraptor/logging" "www.velocidex.com/golang/velociraptor/paths" "www.velocidex.com/golang/velociraptor/services" - vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" ) func maybeStartInitialArtifacts( @@ -64,7 +64,7 @@ func maybeStartInitialArtifacts( } _, err = launcher.ScheduleArtifactCollection(ctx, config_obj, - vql_subsystem.NewRoleACLManager("administrator"), + acl_managers.NewRoleACLManager("administrator"), repository, &flows_proto.ArtifactCollectorArgs{ Creator: principal, diff --git a/services/server_artifacts/server_artifacts.go b/services/server_artifacts/server_artifacts.go index f7a1ea8e9ff..c28a2d0a346 100644 --- a/services/server_artifacts/server_artifacts.go +++ b/services/server_artifacts/server_artifacts.go @@ -30,6 +30,7 @@ import ( "www.velocidex.com/golang/velociraptor/paths" artifact_paths "www.velocidex.com/golang/velociraptor/paths/artifacts" "www.velocidex.com/golang/velociraptor/result_sets" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" "www.velocidex.com/golang/velociraptor/services" "www.velocidex.com/golang/velociraptor/utils" @@ -254,7 +255,7 @@ func (self *ServerArtifactsRunner) runQuery( // Run this query on behalf of the caller so they are // subject to ACL checks - ACLManager: vql_subsystem.NewServerACLManager(self.config_obj, principal), + ACLManager: acl_managers.NewServerACLManager(self.config_obj, principal), Logger: log.New(logger, "", 0), }) defer scope.Close() diff --git a/services/server_artifacts/server_artifacts_test.go b/services/server_artifacts/server_artifacts_test.go index eed44d7aaa8..0fb2f75ade2 100644 --- a/services/server_artifacts/server_artifacts_test.go +++ b/services/server_artifacts/server_artifacts_test.go @@ -17,10 +17,10 @@ import ( "www.velocidex.com/golang/velociraptor/paths" "www.velocidex.com/golang/velociraptor/services" "www.velocidex.com/golang/velociraptor/services/journal" - vql_subsystem "www.velocidex.com/golang/velociraptor/vql" "www.velocidex.com/golang/velociraptor/vtesting" _ "www.velocidex.com/golang/velociraptor/result_sets/timed" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" _ "www.velocidex.com/golang/velociraptor/vql/functions" ) @@ -76,7 +76,7 @@ func (self *ServerArtifactsTestSuite) ScheduleAndWait( launcher, err := services.GetLauncher(self.ConfigObj) assert.NoError(self.T(), err) - acl_manager := vql_subsystem.NewServerACLManager(self.ConfigObj, user) + acl_manager := acl_managers.NewServerACLManager(self.ConfigObj, user) // Schedule a job for the server runner. flow_id, err := launcher.ScheduleArtifactCollection( diff --git a/services/server_monitoring/server_monitoring.go b/services/server_monitoring/server_monitoring.go index cfeadd94833..2ede562a123 100644 --- a/services/server_monitoring/server_monitoring.go +++ b/services/server_monitoring/server_monitoring.go @@ -26,6 +26,7 @@ import ( "www.velocidex.com/golang/velociraptor/services" "www.velocidex.com/golang/velociraptor/utils" vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" "www.velocidex.com/golang/vfilter" ) @@ -89,7 +90,8 @@ func (self *EventTable) _Close() { // Close the old table. if self.cancel != nil { logger := logging.GetLogger(self.config_obj, &logging.FrontendComponent) - logger.Info("Closing Server Monitoring Event table") + logger.Info("Closing Server Monitoring Event table for %v", + services.GetOrgName(self.config_obj)) self.cancel() self.cancel = nil @@ -234,7 +236,7 @@ func (self *EventTable) StartQueries( } // No ACLs enforced on server events. - acl_manager := vql_subsystem.NullACLManager{} + acl_manager := acl_managers.NullACLManager{} // Make a context for all the VQL queries. subctx, cancel := context.WithCancel(self.parent_ctx) @@ -337,7 +339,7 @@ func (self *EventTable) RunQuery( // Run the monitoring queries as the server account. If the // artifact launches other artifacts then it will indicate the // creator was the server. - ACLManager: vql_subsystem.NewServerACLManager( + ACLManager: acl_managers.NewServerACLManager( self.config_obj, self.config_obj.Client.PinnedServerName), Env: ordereddict.NewDict(), @@ -450,7 +452,8 @@ func NewServerMonitoringService( logger := logging.GetLogger( config_obj, &logging.FrontendComponent) - logger.Info("server_monitoring: Starting Server Monitoring Service") + logger.Info("server_monitoring: Starting Server Monitoring Service for %v", + services.GetOrgName(config_obj)) manager := &EventTable{ config_obj: config_obj, diff --git a/services/users.go b/services/users.go index 1ae6096e23f..7fec26ad871 100644 --- a/services/users.go +++ b/services/users.go @@ -19,6 +19,7 @@ package services import ( "context" + "errors" api_proto "www.velocidex.com/golang/velociraptor/api/proto" config_proto "www.velocidex.com/golang/velociraptor/config/proto" @@ -26,14 +27,18 @@ import ( var ( global_user_manager UserManager + + UserNotFoundError = errors.New("User not found") ) type UserManager interface { SetUser(user_record *api_proto.VelociraptorUser) error + GetUser(username string) (*api_proto.VelociraptorUser, error) + ListUsers() ([]*api_proto.VelociraptorUser, error) GetUserFromContext(ctx context.Context) ( *api_proto.VelociraptorUser, *config_proto.Config, error) - GetUser(username string) (*api_proto.VelociraptorUser, error) + GetUserWithHashes(username string) (*api_proto.VelociraptorUser, error) SetUserOptions(username string, options *api_proto.SetGUIOptionsRequest) error diff --git a/services/users/users.go b/services/users/users.go index 416cdf526f3..8e47a918722 100644 --- a/services/users/users.go +++ b/services/users/users.go @@ -201,7 +201,7 @@ func (self UserManager) GetUserWithHashes(username string) ( err = db.GetSubject(self.config_obj, paths.UserPathManager{Name: username}.Path(), user_record) if errors.Is(err, os.ErrNotExist) || user_record.Name == "" { - return nil, errors.New("User not found") + return nil, services.UserNotFoundError } return user_record, err diff --git a/services/vfs_service/utils.go b/services/vfs_service/utils.go index b9f61828a2d..bf41b8e717e 100644 --- a/services/vfs_service/utils.go +++ b/services/vfs_service/utils.go @@ -12,7 +12,7 @@ import ( "www.velocidex.com/golang/velociraptor/logging" "www.velocidex.com/golang/velociraptor/services" "www.velocidex.com/golang/velociraptor/utils" - vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" "www.velocidex.com/golang/vfilter" ) @@ -43,12 +43,12 @@ func watchForFlowCompletion( defer wg.Done() defer cancel() - defer logger.Info("Stopping watch for %v for %v (%v)", + defer logger.Info("Stopping watch for %v for %v (%v)", artifact_name, services.GetOrgName(config_obj), watcher_name) builder := services.ScopeBuilder{ Config: config_obj, - ACLManager: vql_subsystem.NewRoleACLManager("administrator"), + ACLManager: acl_managers.NewRoleACLManager("administrator"), Env: ordereddict.NewDict(). Set("artifact_name", artifact_name), Logger: logging.NewPlainLogger(config_obj, diff --git a/utils/sanitize.go b/utils/sanitize.go index 1ab2bbd6ba9..03549008615 100644 --- a/utils/sanitize.go +++ b/utils/sanitize.go @@ -80,6 +80,10 @@ func SanitizeString(component string) string { result[result_idx+2] = hexTable[c&15] result_idx += 3 } + + if result_idx > len(result)-1 { + break + } } return string(result[:result_idx]) diff --git a/vql/acl_managers/null.go b/vql/acl_managers/null.go new file mode 100644 index 00000000000..5773d64967a --- /dev/null +++ b/vql/acl_managers/null.go @@ -0,0 +1,18 @@ +package acl_managers + +import "www.velocidex.com/golang/velociraptor/acls" + +// NullACLManager is an acl manager which allows everything. This is +// currently used on the client and on the command line where there is +// no clear principal or ACL controls. +type NullACLManager struct{} + +func (self NullACLManager) CheckAccess( + permission ...acls.ACL_PERMISSION) (bool, error) { + return true, nil +} + +func (self NullACLManager) CheckAccessWithArgs( + permission acls.ACL_PERMISSION, args ...string) (bool, error) { + return true, nil +} diff --git a/vql/acl_managers/remapping.go b/vql/acl_managers/remapping.go new file mode 100644 index 00000000000..37eb35b2040 --- /dev/null +++ b/vql/acl_managers/remapping.go @@ -0,0 +1,39 @@ +package acl_managers + +import ( + "www.velocidex.com/golang/velociraptor/acls" + acl_proto "www.velocidex.com/golang/velociraptor/acls/proto" + config_proto "www.velocidex.com/golang/velociraptor/config/proto" + vql_subsystem "www.velocidex.com/golang/velociraptor/vql" +) + +// Get a new, more restricted ACL manager suitable for remapping +// configuration. NOTE that this remapping manager can not give +// **more** permissions than before, but can only remove permissions +// from the existing token. It is useful when we want to block +// certain plugins from working because we are emulating a more +// restricted environment. For example when analyzing a dead image on +// Windows we need to prevent wmi() plugin from interrogating the +// analysis host, therefore would typically remove the MACHINE_STATE +// permission. +func GetRemappingACLManager( + existing_manager vql_subsystem.ACLManager, + remap_config []*config_proto.RemappingConfig) (vql_subsystem.ACLManager, error) { + token := &acl_proto.ApiClientACL{} + for _, item := range remap_config { + if item.Type == "permissions" { + for _, perm := range item.Permissions { + allowed, err := existing_manager.CheckAccess( + acls.GetPermission(perm)) + if err == nil && allowed { + err := acls.SetTokenPermission(token, perm) + if err != nil { + return nil, err + } + } + } + } + } + + return &RoleACLManager{Token: token}, nil +} diff --git a/vql/acl_managers/role.go b/vql/acl_managers/role.go new file mode 100644 index 00000000000..16f735e3e00 --- /dev/null +++ b/vql/acl_managers/role.go @@ -0,0 +1,42 @@ +package acl_managers + +import ( + "www.velocidex.com/golang/velociraptor/acls" + acl_proto "www.velocidex.com/golang/velociraptor/acls/proto" + vql_subsystem "www.velocidex.com/golang/velociraptor/vql" +) + +type RoleACLManager struct { + Token *acl_proto.ApiClientACL +} + +func (self *RoleACLManager) CheckAccess( + permissions ...acls.ACL_PERMISSION) (bool, error) { + for _, permission := range permissions { + ok, err := acls.CheckAccessWithToken(self.Token, permission) + if !ok || err != nil { + return ok, err + } + } + + return true, nil +} + +func (self *RoleACLManager) CheckAccessWithArgs( + permission acls.ACL_PERMISSION, args ...string) (bool, error) { + + return acls.CheckAccessWithToken(self.Token, permission, args...) +} + +// NewRoleACLManager creates an ACL manager with only the assigned +// roles. This is useful for creating limited VQL permissions +// internally. +func NewRoleACLManager(roles ...string) vql_subsystem.ACLManager { + policy := &acl_proto.ApiClientACL{} + + // If we fail just return an empty policy + for _, role := range roles { + _ = acls.GetRolePermissions(nil, []string{role}, policy) + } + return &RoleACLManager{Token: policy} +} diff --git a/vql/acl_managers/server.go b/vql/acl_managers/server.go new file mode 100644 index 00000000000..ac69b8d2fdb --- /dev/null +++ b/vql/acl_managers/server.go @@ -0,0 +1,111 @@ +package acl_managers + +import ( + "sync" + + "www.velocidex.com/golang/velociraptor/acls" + acl_proto "www.velocidex.com/golang/velociraptor/acls/proto" + config_proto "www.velocidex.com/golang/velociraptor/config/proto" + "www.velocidex.com/golang/velociraptor/services" + vql_subsystem "www.velocidex.com/golang/velociraptor/vql" +) + +// ServerACLManager is used when running server side VQL to control +// ACLs on various VQL plugins. +type ServerACLManager struct { + principal string + config_obj *config_proto.Config + + // Cache principal's token for each org_id + mu sync.Mutex + TokenCache map[string]*acl_proto.ApiClientACL +} + +func (self *ServerACLManager) GetPrincipal() string { + return self.principal +} + +// Token must have *ALL* the specified permissions. +func (self *ServerACLManager) CheckAccess( + permissions ...acls.ACL_PERMISSION) (bool, error) { + + policy, err := self.getPolicyInOrg(self.config_obj.OrgId) + if err != nil { + return false, err + } + + for _, permission := range permissions { + ok, err := acls.CheckAccessWithToken(policy, permission) + if !ok || err != nil { + return ok, err + } + } + + return true, nil +} + +func (self *ServerACLManager) getPolicyInOrg(org_id string) (*acl_proto.ApiClientACL, error) { + self.mu.Lock() + policy, pres := self.TokenCache[org_id] + self.mu.Unlock() + if pres && policy != nil { + return policy, nil + } + + org_manager, err := services.GetOrgManager() + if err != nil { + return nil, err + } + + org_config_obj, err := org_manager.GetOrgConfig(org_id) + if err != nil { + return nil, err + } + + policy, err = acls.GetEffectivePolicy(org_config_obj, self.principal) + if err != nil { + return nil, err + } + + self.mu.Lock() + self.TokenCache[org_id] = policy + self.mu.Unlock() + + return policy, nil +} + +func (self *ServerACLManager) CheckAccessInOrg( + org_id string, permissions ...acls.ACL_PERMISSION) (bool, error) { + policy, err := self.getPolicyInOrg(org_id) + if err != nil { + return false, err + } + for _, permission := range permissions { + ok, err := acls.CheckAccessWithToken(policy, permission) + if !ok || err != nil { + return ok, err + } + } + + return true, nil +} + +func (self *ServerACLManager) CheckAccessWithArgs( + permission acls.ACL_PERMISSION, args ...string) (bool, error) { + policy, err := self.getPolicyInOrg(self.config_obj.OrgId) + if err != nil { + return false, err + } + + return acls.CheckAccessWithToken(policy, permission, args...) +} + +func NewServerACLManager( + config_obj *config_proto.Config, + principal string) vql_subsystem.ACLManager { + return &ServerACLManager{ + principal: principal, + config_obj: config_obj, + TokenCache: make(map[string]*acl_proto.ApiClientACL), + } +} diff --git a/vql/acls.go b/vql/acls.go index e4983d6ddf4..f684bd605f0 100644 --- a/vql/acls.go +++ b/vql/acls.go @@ -3,11 +3,7 @@ package vql import ( "fmt" - "github.com/sirupsen/logrus" "www.velocidex.com/golang/velociraptor/acls" - acl_proto "www.velocidex.com/golang/velociraptor/acls/proto" - config_proto "www.velocidex.com/golang/velociraptor/config/proto" - "www.velocidex.com/golang/velociraptor/logging" "www.velocidex.com/golang/vfilter" ) @@ -23,72 +19,12 @@ type ACLManager interface { permission acls.ACL_PERMISSION, args ...string) (bool, error) } -// NullACLManager is an acl manager which allows everything. This is -// currently used on the client and on the command line where there is -// no clear principal or ACL controls. -type NullACLManager struct{} - -func (self NullACLManager) CheckAccess( - permission ...acls.ACL_PERMISSION) (bool, error) { - return true, nil -} - -func (self NullACLManager) CheckAccessWithArgs( - permission acls.ACL_PERMISSION, args ...string) (bool, error) { - return true, nil -} - -// ServerACLManager is used when running server side VQL to control -// ACLs on various VQL plugins. -type ServerACLManager struct { - principal string - Token *acl_proto.ApiClientACL -} - -// Token must have *ALL* the specified permissions. -func (self *ServerACLManager) CheckAccess( - permissions ...acls.ACL_PERMISSION) (bool, error) { - for _, permission := range permissions { - ok, err := acls.CheckAccessWithToken(self.Token, permission) - if !ok || err != nil { - return ok, err - } - } - - return true, nil +type OrgACLManager interface { + CheckAccessInOrg(org_id string, permission ...acls.ACL_PERMISSION) (bool, error) } -func (self *ServerACLManager) CheckAccessWithArgs( - permission acls.ACL_PERMISSION, args ...string) (bool, error) { - return acls.CheckAccessWithToken(self.Token, permission, args...) -} - -// NewRoleACLManager creates an ACL manager with only the assigned -// roles. This is useful for creating limited VQL permissions -// internally. -func NewRoleACLManager(role string) ACLManager { - policy := &acl_proto.ApiClientACL{} - - // If we fail just return an empty policy - _ = acls.GetRolePermissions(nil, []string{role}, policy) - - return &ServerACLManager{Token: policy} -} - -func NewServerACLManager( - config_obj *config_proto.Config, - principal string) ACLManager { - policy, err := acls.GetEffectivePolicy(config_obj, principal) - if err != nil { - logger := logging.GetLogger(config_obj, &logging.FrontendComponent) - logger.WithFields(logrus.Fields{ - "user": principal, - "error": err, - }).Error("Unable to get policy") - policy = &acl_proto.ApiClientACL{} - } - - return &ServerACLManager{principal: principal, Token: policy} +type PrincipalACLManager interface { + GetPrincipal() string } // Check access through the ACL manager in the scope. NOTE: This @@ -116,6 +52,30 @@ func CheckAccess(scope vfilter.Scope, permissions ...acls.ACL_PERMISSION) error return nil } +// A variant of CheckAccess() that can check access in a different org. +func CheckAccessInOrg(scope vfilter.Scope, org_id string, permissions ...acls.ACL_PERMISSION) error { + manager_any, pres := scope.Resolve(ACL_MANAGER_VAR) + if !pres { + return fmt.Errorf("Permission denied: %v", permissions) + } + + manager, ok := manager_any.(OrgACLManager) + if !ok { + return fmt.Errorf("Permission denied: %v", permissions) + } + + if org_id == "root" { + org_id = "" + } + + perm, err := manager.CheckAccessInOrg(org_id, permissions...) + if !perm || err != nil { + return fmt.Errorf("Permission denied: %v", permissions) + } + + return nil +} + func CheckAccessWithArgs(scope vfilter.Scope, permissions acls.ACL_PERMISSION, args ...string) error { manager_any, pres := scope.Resolve(ACL_MANAGER_VAR) @@ -160,10 +120,10 @@ func GetPrincipal(scope vfilter.Scope) string { return "" } - manager, ok := manager_any.(*ServerACLManager) + manager, ok := manager_any.(PrincipalACLManager) if !ok { return "" } - return manager.principal + return manager.GetPrincipal() } diff --git a/vql/functions/functions.go b/vql/functions/functions.go index 2ee6d01bdaf..313431779a5 100644 --- a/vql/functions/functions.go +++ b/vql/functions/functions.go @@ -391,8 +391,8 @@ type Setter interface { } type _SetFunctionArgs struct { - Item vfilter.Any `vfilter:"required,field=item,docs=A dict to set"` - Field string `vfilter:"required,field=field,docs=The field to set"` + Item vfilter.Any `vfilter:"required,field=item,doc=A dict to set"` + Field string `vfilter:"required,field=field,doc=The field to set"` Value vfilter.Any `vfilter:"required,field=value"` } diff --git a/vql/parsers/sqlite_test.go b/vql/parsers/sqlite_test.go index 8c46dbb00aa..48c7cebcfbe 100644 --- a/vql/parsers/sqlite_test.go +++ b/vql/parsers/sqlite_test.go @@ -20,7 +20,7 @@ import ( "www.velocidex.com/golang/velociraptor/file_store/test_utils" "www.velocidex.com/golang/velociraptor/json" "www.velocidex.com/golang/velociraptor/services" - vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" vfilter "www.velocidex.com/golang/vfilter" _ "www.velocidex.com/golang/velociraptor/accessors/file" @@ -64,7 +64,7 @@ func (self *TestSuite) TestSQLite() { builder := services.ScopeBuilder{ Config: self.ConfigObj, - ACLManager: vql_subsystem.NullACLManager{}, + ACLManager: acl_managers.NullACLManager{}, Logger: log.New(log_buffer, "vql: ", 0), Env: ordereddict.NewDict(), } diff --git a/vql/readers/paged_reader_test.go b/vql/readers/paged_reader_test.go index 7da70de0d0a..186a49692d8 100644 --- a/vql/readers/paged_reader_test.go +++ b/vql/readers/paged_reader_test.go @@ -16,6 +16,7 @@ import ( "www.velocidex.com/golang/velociraptor/accessors" "www.velocidex.com/golang/velociraptor/constants" vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" "www.velocidex.com/golang/velociraptor/vtesting" "www.velocidex.com/golang/vfilter" @@ -34,7 +35,7 @@ func (self *TestSuite) SetupTest() { self.scope = vql_subsystem.MakeScope() self.scope.AppendVars(ordereddict.NewDict(). Set(vql_subsystem.CACHE_VAR, vql_subsystem.NewScopeCache()). - Set(vql_subsystem.ACL_MANAGER_VAR, vql_subsystem.NullACLManager{}). + Set(vql_subsystem.ACL_MANAGER_VAR, acl_managers.NullACLManager{}). Set(constants.SCOPE_ROOT, self.scope)) // Make a very small pool diff --git a/vql/remapping/remapping_test.go b/vql/remapping/remapping_test.go index 9ce79cd6df3..b7ee5fb68d2 100644 --- a/vql/remapping/remapping_test.go +++ b/vql/remapping/remapping_test.go @@ -15,12 +15,12 @@ import ( "www.velocidex.com/golang/velociraptor/json" "www.velocidex.com/golang/velociraptor/logging" "www.velocidex.com/golang/velociraptor/services" - vql_subsystem "www.velocidex.com/golang/velociraptor/vql" "www.velocidex.com/golang/vfilter" _ "www.velocidex.com/golang/velociraptor/accessors/file" _ "www.velocidex.com/golang/velociraptor/accessors/raw_registry" _ "www.velocidex.com/golang/velociraptor/result_sets/timed" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" _ "www.velocidex.com/golang/velociraptor/vql/filesystem" _ "www.velocidex.com/golang/velociraptor/vql/protocols" ) @@ -78,7 +78,7 @@ func (self *RemapTestSuite) TestConfigFileRemap() { // Just build a standard scope. builder := services.ScopeBuilder{ Config: self.ConfigObj, - ACLManager: vql_subsystem.NullACLManager{}, + ACLManager: acl_managers.NullACLManager{}, Logger: logging.NewPlainLogger(self.ConfigObj, &logging.FrontendComponent), Env: ordereddict.NewDict(), } @@ -139,7 +139,7 @@ func (self *RemapTestSuite) TestRemapByPlugin() { // Just build a standard scope. builder := services.ScopeBuilder{ Config: self.ConfigObj, - ACLManager: vql_subsystem.NullACLManager{}, + ACLManager: acl_managers.NullACLManager{}, Logger: logging.NewPlainLogger(self.ConfigObj, &logging.FrontendComponent), Env: ordereddict.NewDict(). Set("RemappingConfig", serialized), diff --git a/vql/server/clients/delete_test.go b/vql/server/clients/delete_test.go index 6c5952cb347..448c20e9355 100644 --- a/vql/server/clients/delete_test.go +++ b/vql/server/clients/delete_test.go @@ -24,7 +24,7 @@ import ( "www.velocidex.com/golang/velociraptor/logging" "www.velocidex.com/golang/velociraptor/paths" "www.velocidex.com/golang/velociraptor/services" - vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" "www.velocidex.com/golang/velociraptor/vtesting" _ "www.velocidex.com/golang/velociraptor/result_sets/simple" @@ -134,7 +134,7 @@ func (self *DeleteTestSuite) TestDeleteClient() { manager, _ := services.GetRepositoryManager(self.ConfigObj) builder := services.ScopeBuilder{ Config: self.ConfigObj, - ACLManager: vql_subsystem.NullACLManager{}, + ACLManager: acl_managers.NullACLManager{}, Logger: logging.NewPlainLogger(self.ConfigObj, &logging.FrontendComponent), Env: ordereddict.NewDict(), diff --git a/vql/server/downloads/reporting.go b/vql/server/downloads/reporting.go index e5107d2f475..6d247832ce2 100644 --- a/vql/server/downloads/reporting.go +++ b/vql/server/downloads/reporting.go @@ -16,7 +16,7 @@ import ( "www.velocidex.com/golang/velociraptor/paths" "www.velocidex.com/golang/velociraptor/reporting" "www.velocidex.com/golang/velociraptor/services" - vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" "www.velocidex.com/golang/vfilter" ) @@ -89,7 +89,7 @@ func WriteFlowReport( // generate arbitrary HTML. template_engine, err := reporting.NewHTMLTemplateEngine( config_obj, context.Background(), scope, - vql_subsystem.NullACLManager{}, repository, + acl_managers.NullACLManager{}, repository, definition.Name, false /* sanitize_html */) if err != nil { scope.Log("Error creating report for %v: %v", @@ -116,7 +116,7 @@ func WriteFlowReport( template_engine, err := reporting.NewHTMLTemplateEngine( config_obj, context.Background(), scope, - vql_subsystem.NullACLManager{}, repository, + acl_managers.NullACLManager{}, repository, template, false /* sanitize_html */) if err != nil { return err diff --git a/vql/server/artifacts.go b/vql/server/flows/create.go similarity index 85% rename from vql/server/artifacts.go rename to vql/server/flows/create.go index b9eac3af42a..ab0abc48a6f 100644 --- a/vql/server/artifacts.go +++ b/vql/server/flows/create.go @@ -1,5 +1,3 @@ -// +build server_vql - /* Velociraptor - Hunting Evil Copyright (C) 2019 Velocidex Innovations. @@ -17,7 +15,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package server +package flows import ( "context" @@ -30,6 +28,7 @@ import ( "www.velocidex.com/golang/velociraptor/json" "www.velocidex.com/golang/velociraptor/services" vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" "www.velocidex.com/golang/velociraptor/vql/tools" "www.velocidex.com/golang/vfilter" "www.velocidex.com/golang/vfilter/arg_parser" @@ -47,6 +46,7 @@ type ScheduleCollectionFunctionArg struct { MaxRows uint64 `vfilter:"optional,field=max_rows,doc=Max number of rows to fetch"` MaxBytes uint64 `vfilter:"optional,field=max_bytes,doc=Max number of bytes to upload"` Urgent bool `vfilter:"optional,field=urgent,doc=Set the collection as urgent - skips other queues collections on the client."` + OrgId string `vfilter:"optional,field=org_id,doc=If set the collection will be started in the specified org."` } type ScheduleCollectionFunction struct{} @@ -67,6 +67,12 @@ func (self *ScheduleCollectionFunction) Call(ctx context.Context, return vfilter.Null{} } + config_obj, ok := vql_subsystem.GetServerConfig(scope) + if !ok { + scope.Log("collect_client: Command can only run on the server") + return vfilter.Null{} + } + // Scheduling artifacts on the server requires higher // permissions. var permission acls.ACL_PERMISSION @@ -79,16 +85,33 @@ func (self *ScheduleCollectionFunction) Call(ctx context.Context, return vfilter.Null{} } - err = vql_subsystem.CheckAccess(scope, permission) - if err != nil { - scope.Log("collect_client: %v", err) - return vfilter.Null{} - } + // Which org should this be collected on + if arg.OrgId == "" { + err = vql_subsystem.CheckAccess(scope, permission) + if err != nil { + scope.Log("collect_client: %v", err) + return vfilter.Null{} + } - config_obj, ok := vql_subsystem.GetServerConfig(scope) - if !ok { - scope.Log("collect_client: Command can only run on the server") - return vfilter.Null{} + } else { + err = vql_subsystem.CheckAccessInOrg(scope, arg.OrgId, permission) + if err != nil { + scope.Log("collect_client: %v", err) + return vfilter.Null{} + } + + org_manager, err := services.GetOrgManager() + if err != nil { + scope.Log("collect_client: %v", err) + return vfilter.Null{} + } + + // If an org is specied we use the config obj from the org. + config_obj, err = org_manager.GetOrgConfig(arg.OrgId) + if err != nil { + scope.Log("collect_client: %v", err) + return vfilter.Null{} + } } manager, err := services.GetRepositoryManager(config_obj) @@ -135,7 +158,7 @@ func (self *ScheduleCollectionFunction) Call(ctx context.Context, result := &flows_proto.ArtifactCollectorResponse{Request: request} acl_manager, ok := artifacts.GetACLManager(scope) if !ok { - acl_manager = vql_subsystem.NullACLManager{} + acl_manager = acl_managers.NullACLManager{} } launcher, err := services.GetLauncher(config_obj) diff --git a/vql/server/flows/flow_test.go b/vql/server/flows/flow_test.go index 4f6eb9e762d..d8c45b41235 100644 --- a/vql/server/flows/flow_test.go +++ b/vql/server/flows/flow_test.go @@ -20,7 +20,7 @@ import ( "www.velocidex.com/golang/velociraptor/logging" "www.velocidex.com/golang/velociraptor/paths" "www.velocidex.com/golang/velociraptor/services" - vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" "www.velocidex.com/golang/velociraptor/vql/server/flows" "www.velocidex.com/golang/velociraptor/vtesting" ) @@ -90,7 +90,7 @@ func (self *FilestoreTestSuite) TestEnumerateFlow() { manager, _ := services.GetRepositoryManager(self.ConfigObj) builder := services.ScopeBuilder{ Config: self.ConfigObj, - ACLManager: vql_subsystem.NullACLManager{}, + ACLManager: acl_managers.NullACLManager{}, Logger: logging.NewPlainLogger(self.ConfigObj, &logging.FrontendComponent), Env: ordereddict.NewDict(), diff --git a/vql/server/flows/parallel_test.go b/vql/server/flows/parallel_test.go index 658581c59bc..45e614e03d7 100644 --- a/vql/server/flows/parallel_test.go +++ b/vql/server/flows/parallel_test.go @@ -21,7 +21,7 @@ import ( "www.velocidex.com/golang/velociraptor/result_sets" "www.velocidex.com/golang/velociraptor/services" "www.velocidex.com/golang/velociraptor/utils" - vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" "www.velocidex.com/golang/vfilter" _ "www.velocidex.com/golang/velociraptor/result_sets/simple" @@ -76,7 +76,7 @@ func (self *TestSuite) TestArtifactSource() { ctx := context.Background() builder := services.ScopeBuilder{ Config: self.ConfigObj, - ACLManager: vql_subsystem.NullACLManager{}, + ACLManager: acl_managers.NullACLManager{}, Logger: logging.NewPlainLogger(self.ConfigObj, &logging.FrontendComponent), Env: ordereddict.NewDict(). Set("ClientId", self.client_id). @@ -137,7 +137,7 @@ func (self *TestSuite) TestHuntsSource() { assert.NoError(self.T(), err) hunt_id, err := hunt_dispatcher.CreateHunt(ctx, - self.ConfigObj, vql_subsystem.NullACLManager{}, + self.ConfigObj, acl_managers.NullACLManager{}, &api_proto.Hunt{ StartRequest: &flows_proto.ArtifactCollectorArgs{ Artifacts: []string{"Test.Artifact"}, @@ -160,7 +160,7 @@ func (self *TestSuite) TestHuntsSource() { client_id := fmt.Sprintf("%s_%v", self.client_id, client_number) flow_id, err := launcher.ScheduleArtifactCollection(self.Ctx, - self.ConfigObj, vql_subsystem.NullACLManager{}, + self.ConfigObj, acl_managers.NullACLManager{}, repository, &flows_proto.ArtifactCollectorArgs{ ClientId: client_id, Artifacts: []string{"Test.Artifact"}, @@ -196,7 +196,7 @@ func (self *TestSuite) TestHuntsSource() { builder := services.ScopeBuilder{ Config: self.ConfigObj, - ACLManager: vql_subsystem.NullACLManager{}, + ACLManager: acl_managers.NullACLManager{}, Logger: logging.NewPlainLogger(self.ConfigObj, &logging.FrontendComponent), Env: ordereddict.NewDict().Set("MyHuntId", hunt_id), } diff --git a/vql/server/hunts/create.go b/vql/server/hunts/create.go index f77c6400ec0..abf08f93a40 100644 --- a/vql/server/hunts/create.go +++ b/vql/server/hunts/create.go @@ -30,6 +30,7 @@ import ( "www.velocidex.com/golang/velociraptor/services" "www.velocidex.com/golang/velociraptor/utils" vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" "www.velocidex.com/golang/velociraptor/vql/functions" "www.velocidex.com/golang/velociraptor/vql/tools" "www.velocidex.com/golang/vfilter/arg_parser" @@ -154,7 +155,7 @@ func (self *ScheduleHuntFunction) Call(ctx context.Context, } // Run the hunt in the ACL context of the caller. - acl_manager := vql_subsystem.NewServerACLManager( + acl_manager := acl_managers.NewServerACLManager( config_obj, vql_subsystem.GetPrincipal(scope)) hunt_dispatcher, err := services.GetHuntDispatcher(config_obj) diff --git a/vql/server/monitoring/add_monitoring_test.go b/vql/server/monitoring/add_monitoring_test.go index 150c394f25f..9788aae654d 100644 --- a/vql/server/monitoring/add_monitoring_test.go +++ b/vql/server/monitoring/add_monitoring_test.go @@ -15,6 +15,7 @@ import ( "www.velocidex.com/golang/velociraptor/json" "www.velocidex.com/golang/velociraptor/services" vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" "www.velocidex.com/golang/vfilter" _ "www.velocidex.com/golang/velociraptor/result_sets/timed" @@ -97,7 +98,7 @@ func (self *MonitoringTestSuite) TestAddServerMonitoring() { builder := services.ScopeBuilder{ Config: self.ConfigObj, - ACLManager: vql_subsystem.NullACLManager{}, + ACLManager: acl_managers.NullACLManager{}, Logger: log.New(log_buffer, "vql: ", 0), Env: ordereddict.NewDict(), } diff --git a/vql/server/orgs/create.go b/vql/server/orgs/create.go index 5754a4c59c7..c449c29d0b9 100644 --- a/vql/server/orgs/create.go +++ b/vql/server/orgs/create.go @@ -12,7 +12,8 @@ import ( ) type OrgCreateFunctionArgs struct { - OrgName string `vfilter:"required,field=name,docs=The name of the org."` + OrgName string `vfilter:"required,field=name,doc=The name of the org."` + OrgId string `vfilter:"optional,field=org_id,doc=An ID for the new org (if not set use a random ID)."` } type OrgCreateFunction struct{} @@ -22,7 +23,7 @@ func (self OrgCreateFunction) Call( scope vfilter.Scope, args *ordereddict.Dict) vfilter.Any { - err := vql_subsystem.CheckAccess(scope, acls.SERVER_ADMIN) + err := vql_subsystem.CheckAccess(scope, acls.ORG_ADMIN) if err != nil { scope.Log("org_create: %s", err) return vfilter.Null{} @@ -41,7 +42,7 @@ func (self OrgCreateFunction) Call( return vfilter.Null{} } - org_record, err := org_manager.CreateNewOrg(arg.OrgName) + org_record, err := org_manager.CreateNewOrg(arg.OrgName, arg.OrgId) if err != nil { scope.Log("org_create: %s", err) return vfilter.Null{} diff --git a/vql/server/orgs/orgs.go b/vql/server/orgs/orgs.go new file mode 100644 index 00000000000..83eee875b7b --- /dev/null +++ b/vql/server/orgs/orgs.go @@ -0,0 +1,74 @@ +package orgs + +import ( + "context" + + "github.com/Velocidex/ordereddict" + "github.com/Velocidex/yaml/v2" + "www.velocidex.com/golang/velociraptor/acls" + "www.velocidex.com/golang/velociraptor/services" + vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/vfilter" +) + +type OrgsPlugin struct{} + +func (self OrgsPlugin) Call( + ctx context.Context, + scope vfilter.Scope, + args *ordereddict.Dict) <-chan vfilter.Row { + output_chan := make(chan vfilter.Row) + + go func() { + defer close(output_chan) + + err := vql_subsystem.CheckAccess(scope, acls.ORG_ADMIN) + if err != nil { + scope.Log("orgs: %v", err) + return + } + + org_manager, err := services.GetOrgManager() + if err != nil { + scope.Log("orgs: %v", err) + return + } + + for _, org_record := range org_manager.ListOrgs() { + org_config_obj, err := org_manager.GetOrgConfig(org_record.OrgId) + if err != nil { + continue + } + + serialized, err := yaml.Marshal(org_config_obj) + if err != nil { + continue + } + + row := ordereddict.NewDict(). + Set("Name", org_record.Name). + Set("OrgId", org_record.OrgId). + Set("_client_config", string(serialized)) + + select { + case <-ctx.Done(): + return + case output_chan <- row: + } + } + + }() + + return output_chan +} + +func (self OrgsPlugin) Info(scope vfilter.Scope, type_map *vfilter.TypeMap) *vfilter.PluginInfo { + return &vfilter.PluginInfo{ + Name: "orgs", + Doc: "Retrieve the list of orgs on this server.", + } +} + +func init() { + vql_subsystem.RegisterPlugin(&OrgsPlugin{}) +} diff --git a/vql/server/users/create.go b/vql/server/users/create.go index 0764ecc5072..c0facd5d868 100644 --- a/vql/server/users/create.go +++ b/vql/server/users/create.go @@ -7,6 +7,7 @@ import ( "github.com/Velocidex/ordereddict" "www.velocidex.com/golang/velociraptor/acls" "www.velocidex.com/golang/velociraptor/api/authenticators" + api_proto "www.velocidex.com/golang/velociraptor/api/proto" "www.velocidex.com/golang/velociraptor/services" "www.velocidex.com/golang/velociraptor/services/users" vql_subsystem "www.velocidex.com/golang/velociraptor/vql" @@ -15,10 +16,10 @@ import ( ) type UserCreateFunctionArgs struct { - Username string `vfilter:"required,field=user,docs=The user to create or update."` - Roles []string `vfilter:"required,field=roles,docs=List of roles to give the user."` - Password string `vfilter:"optional,field=password,docs=A password to set for the user (If not using SSO this might be needed)."` - OrgIds []string `vfilter:"optional,field=orgs,docs=One or more org IDs to grant access to."` + Username string `vfilter:"required,field=user,doc=The user to create or update."` + Roles []string `vfilter:"required,field=roles,doc=List of roles to give the user."` + Password string `vfilter:"optional,field=password,doc=A password to set for the user (If not using SSO this might be needed)."` + OrgIds []string `vfilter:"optional,field=orgs,doc=One or more org IDs to grant access to."` } type UserCreateFunction struct{} @@ -34,12 +35,6 @@ func (self UserCreateFunction) Call( return vfilter.Null{} } - config_obj, ok := vql_subsystem.GetServerConfig(scope) - if !ok { - scope.Log("Command can only run on the server") - return vfilter.Null{} - } - arg := &UserCreateFunctionArgs{} err = arg_parser.ExtractArgsWithContext(ctx, scope, args, arg) if err != nil { @@ -47,9 +42,23 @@ func (self UserCreateFunction) Call( return vfilter.Null{} } - // OK - Lets make the user now - user_record, err := users.NewUserRecord(arg.Username) - if err != nil { + config_obj, ok := vql_subsystem.GetServerConfig(scope) + if !ok { + scope.Log("Command can only run on the server") + return vfilter.Null{} + } + + users_manager := services.GetUserManager() + user_record, err := users_manager.GetUser(arg.Username) + if err == services.UserNotFoundError { + // OK - Lets make the user now + user_record, err = users.NewUserRecord(arg.Username) + if err != nil { + scope.Log("user_create: %s", err) + return vfilter.Null{} + } + + } else if err != nil { scope.Log("user_create: %s", err) return vfilter.Null{} } @@ -83,15 +92,58 @@ func (self UserCreateFunction) Call( users.SetPassword(user_record, arg.Password) } - // Grant the roles to the user - err = acls.GrantRoles(config_obj, arg.Username, arg.Roles) - if err != nil { - scope.Log("user_create: %s", err) - return vfilter.Null{} + // Grat the user the roles in all orgs. + org_config_obj := config_obj + + // No OrgIds specified - the user will be created in the root org. + if len(arg.OrgIds) == 0 { + // Grant the roles to the user + err = acls.GrantRoles(config_obj, arg.Username, arg.Roles) + if err != nil { + scope.Log("user_create: %s", err) + return vfilter.Null{} + } + + // OrgIds specified, grant the user an ACL in each org specified. + } else { + org_manager, err := services.GetOrgManager() + if err != nil { + scope.Log("user_create: %v", err) + return vfilter.Null{} + } + + for _, org_id := range arg.OrgIds { + org_config_obj, err = org_manager.GetOrgConfig(org_id) + if err != nil { + scope.Log("user_create: %v", err) + return vfilter.Null{} + } + + // Grant the roles to the user + err = acls.GrantRoles(org_config_obj, arg.Username, arg.Roles) + if err != nil { + scope.Log("user_create: %s", err) + return vfilter.Null{} + } + } + + org_exists := func(org_id string) bool { + for _, org := range user_record.Orgs { + if org.Id == org_id { + return true + } + } + return false + } + for _, org_id := range arg.OrgIds { + if !org_exists(org_id) { + user_record.Orgs = append(user_record.Orgs, + &api_proto.Org{Id: org_id}) + } + } } // Write the user record. - users_manager := services.GetUserManager() err = users_manager.SetUser(user_record) if err != nil { scope.Log("user_create: %s", err) diff --git a/vql/server/users/delete.go b/vql/server/users/delete.go index 40e7e041a58..2e1b744ff10 100644 --- a/vql/server/users/delete.go +++ b/vql/server/users/delete.go @@ -13,7 +13,7 @@ import ( ) type UserDeleteFunctionArgs struct { - Username string `vfilter:"required,field=user,docs=The user to delete."` + Username string `vfilter:"required,field=user,doc=The user to delete."` } type UserDeleteFunction struct{} diff --git a/vql/server/users/users.go b/vql/server/users/users.go index a936363d174..b715eab2260 100644 --- a/vql/server/users/users.go +++ b/vql/server/users/users.go @@ -5,12 +5,19 @@ import ( "github.com/Velocidex/ordereddict" "www.velocidex.com/golang/velociraptor/acls" + acl_proto "www.velocidex.com/golang/velociraptor/acls/proto" "www.velocidex.com/golang/velociraptor/json" "www.velocidex.com/golang/velociraptor/services" + "www.velocidex.com/golang/velociraptor/utils" vql_subsystem "www.velocidex.com/golang/velociraptor/vql" "www.velocidex.com/golang/vfilter" + "www.velocidex.com/golang/vfilter/arg_parser" ) +type UsersPluginArgs struct { + AllOrgs bool `vfilter:"optional,field=all_orgs,doc=If set we enumberate permission for all orgs, otherwise just for this org."` +} + type UsersPlugin struct{} func (self UsersPlugin) Call( @@ -21,9 +28,16 @@ func (self UsersPlugin) Call( go func() { defer close(output_chan) - err := vql_subsystem.CheckAccess(scope, acls.READ_RESULTS) + err := vql_subsystem.CheckAccess(scope, acls.SERVER_ADMIN) + if err != nil { + scope.Log("users: %v", err) + return + } + + arg := &UsersPluginArgs{} + err = arg_parser.ExtractArgsWithContext(ctx, scope, args, arg) if err != nil { - scope.Log("users: %s", err) + scope.Log("users: %v", err) return } @@ -40,16 +54,65 @@ func (self UsersPlugin) Call( return } + org_manager, err := services.GetOrgManager() + if err != nil { + scope.Log("users: %v", err) + return + } + for _, user_details := range user_list { - policy, err := acls.GetPolicy( - config_obj, user_details.Name) - if err == nil { - user_details.Permissions = policy + // org id for all orgs the user belongs to. + var orgs []string + for _, org_record := range user_details.Orgs { + orgs = append(orgs, org_record.Id) + } + + // If not specific org, the user belongs to the root org. + if len(orgs) == 0 { + orgs = append(orgs, "") } - select { - case <-ctx.Done(): - return - case output_chan <- json.ConvertProtoToOrderedDict(user_details): + + // Does the user have access to read orgs? + err := vql_subsystem.CheckAccess(scope, acls.ORG_ADMIN) + if err != nil { + arg.AllOrgs = false + } + + // Only display users that belong to the current org + if !arg.AllOrgs { + if !utils.InString(orgs, config_obj.OrgId) { + continue + } + + orgs = []string{config_obj.OrgId} + } + + for _, org_id := range orgs { + org_config_obj, err := org_manager.GetOrgConfig(org_id) + if err != nil { + continue + } + + details := ordereddict.NewDict(). + Set("name", user_details.Name). + Set("org_id", org_config_obj.OrgId). + Set("picture", user_details.Picture). + Set("email", user_details.VerifiedEmail) + policy, err := acls.GetPolicy(org_config_obj, user_details.Name) + if err == nil { + details.Set("roles", policy.Roles) + } + + effective_policy, err := acls.GetEffectivePolicy(org_config_obj, user_details.Name) + if err == nil { + details.Set("effective_policy", ConvertPolicyToOrderedDict(effective_policy)) + } + + select { + case <-ctx.Done(): + return + case output_chan <- details: + } } } @@ -68,3 +131,37 @@ func (self UsersPlugin) Info(scope vfilter.Scope, type_map *vfilter.TypeMap) *vf func init() { vql_subsystem.RegisterPlugin(&UsersPlugin{}) } + +func ConvertPolicyToOrderedDict( + policy *acl_proto.ApiClientACL) *ordereddict.Dict { + policy_dict := json.ConvertProtoToOrderedDict(policy) + result := ordereddict.NewDict() + for _, k := range policy_dict.Keys() { + v, _ := policy_dict.Get(k) + + switch t := v.(type) { + case bool: + if !t { + continue + } + case string: + if t == "" { + continue + } + + case []string: + if len(t) == 0 { + continue + } + + case []interface{}: + if len(t) == 0 { + continue + } + } + + result.Set(k, v) + } + + return result +} diff --git a/vql/tools/collector.go b/vql/tools/collector.go index 74bb91d6806..c6b1a702765 100644 --- a/vql/tools/collector.go +++ b/vql/tools/collector.go @@ -23,6 +23,7 @@ import ( "www.velocidex.com/golang/velociraptor/services" "www.velocidex.com/golang/velociraptor/utils" vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" "www.velocidex.com/golang/velociraptor/vql/functions" "www.velocidex.com/golang/vfilter" "www.velocidex.com/golang/vfilter/arg_parser" @@ -177,7 +178,7 @@ func (self CollectPlugin) Call( // bypass the ACL manager and get more permissions. acl_manager, ok := artifacts.GetACLManager(scope) if !ok { - acl_manager = vql_subsystem.NullACLManager{} + acl_manager = acl_managers.NullACLManager{} } launcher, err := services.GetLauncher(config_obj) diff --git a/vql/tools/collector_test.go b/vql/tools/collector_test.go index 044d6018a77..65a6105b952 100644 --- a/vql/tools/collector_test.go +++ b/vql/tools/collector_test.go @@ -22,6 +22,7 @@ import ( // Load all needed plugins _ "www.velocidex.com/golang/velociraptor/accessors/data" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" _ "www.velocidex.com/golang/velociraptor/vql/functions" _ "www.velocidex.com/golang/velociraptor/vql/networking" _ "www.velocidex.com/golang/velociraptor/vql/parsers" @@ -137,7 +138,7 @@ func (self *TestSuite) TestSimpleCollection() { launcher, err := services.GetLauncher(self.ConfigObj) assert.NoError(self.T(), err) - acl_manager := vql_subsystem.NullACLManager{} + acl_manager := acl_managers.NullACLManager{} vql_requests, err := launcher.CompileCollectorArgs( context.Background(), self.ConfigObj, acl_manager, repository, services.CompilerOptions{}, request) @@ -164,7 +165,7 @@ func (self *TestSuite) TestCollectionWithArtifacts() { builder := services.ScopeBuilder{ Config: self.ConfigObj, - ACLManager: vql_subsystem.NullACLManager{}, + ACLManager: acl_managers.NullACLManager{}, Logger: logging.NewPlainLogger(self.ConfigObj, &logging.FrontendComponent), Env: ordereddict.NewDict(), } @@ -210,7 +211,7 @@ func (self *TestSuite) TestCollectionWithTypes() { builder := services.ScopeBuilder{ Config: self.ConfigObj, - ACLManager: vql_subsystem.NullACLManager{}, + ACLManager: acl_managers.NullACLManager{}, Logger: logging.NewPlainLogger(self.ConfigObj, &logging.FrontendComponent), Env: ordereddict.NewDict(), } @@ -250,7 +251,7 @@ func (self *TestSuite) TestCollectionWithUpload() { builder := services.ScopeBuilder{ Config: self.ConfigObj, - ACLManager: vql_subsystem.NullACLManager{}, + ACLManager: acl_managers.NullACLManager{}, Logger: logging.NewPlainLogger(self.ConfigObj, &logging.FrontendComponent), Env: ordereddict.NewDict(), } diff --git a/vql/tools/import_test.go b/vql/tools/import_test.go index 5fd3f09d951..4b93f5ba368 100644 --- a/vql/tools/import_test.go +++ b/vql/tools/import_test.go @@ -13,7 +13,7 @@ import ( flows_proto "www.velocidex.com/golang/velociraptor/flows/proto" "www.velocidex.com/golang/velociraptor/logging" "www.velocidex.com/golang/velociraptor/services" - vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" ) func (self *TestSuite) TestImportCollection() { @@ -24,7 +24,7 @@ func (self *TestSuite) TestImportCollection() { builder := services.ScopeBuilder{ Config: self.ConfigObj, - ACLManager: vql_subsystem.NullACLManager{}, + ACLManager: acl_managers.NullACLManager{}, Logger: logging.NewPlainLogger(self.ConfigObj, &logging.FrontendComponent), Env: ordereddict.NewDict(), } diff --git a/vql/tools/process/tracker_test.go b/vql/tools/process/tracker_test.go index 3bf404e436b..f18fb3bc0dd 100644 --- a/vql/tools/process/tracker_test.go +++ b/vql/tools/process/tracker_test.go @@ -14,12 +14,12 @@ import ( "www.velocidex.com/golang/velociraptor/logging" "www.velocidex.com/golang/velociraptor/services" "www.velocidex.com/golang/velociraptor/utils" - vql_subsystem "www.velocidex.com/golang/velociraptor/vql" "www.velocidex.com/golang/velociraptor/vtesting/assert" "www.velocidex.com/golang/vfilter" _ "www.velocidex.com/golang/velociraptor/result_sets/simple" _ "www.velocidex.com/golang/velociraptor/result_sets/timed" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" _ "www.velocidex.com/golang/velociraptor/vql/protocols" ) @@ -191,7 +191,7 @@ func (self *ProcessTrackerTestSuite) TestProcessTracker() { // Just build a standard scope. builder := services.ScopeBuilder{ Config: self.ConfigObj, - ACLManager: vql_subsystem.NullACLManager{}, + ACLManager: acl_managers.NullACLManager{}, Logger: logging.NewPlainLogger(self.ConfigObj, &logging.FrontendComponent), } diff --git a/vql/tools/query.go b/vql/tools/query.go index c4517040e7a..2cbdf8908c0 100644 --- a/vql/tools/query.go +++ b/vql/tools/query.go @@ -5,9 +5,11 @@ import ( "time" "github.com/Velocidex/ordereddict" + "www.velocidex.com/golang/velociraptor/acls" "www.velocidex.com/golang/velociraptor/actions" "www.velocidex.com/golang/velociraptor/services" vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" "www.velocidex.com/golang/vfilter" "www.velocidex.com/golang/vfilter/arg_parser" ) @@ -18,6 +20,8 @@ type QueryPluginArgs struct { CpuLimit float64 `vfilter:"optional,field=cpu_limit,doc=Average CPU usage in percent of a core."` IopsLimit float64 `vfilter:"optional,field=iops_limit,doc=Average IOPs to target."` ProgressTimeout float64 `vfilter:"optional,field=progress_timeout,doc=If no progress is detected in this many seconds, we terminate the query and output debugging information"` + OrgId string `vfilter:"optional,field=org_id,doc=If specified, the query will run in the specified org space (Use 'root' to refer to the root org)"` + Principal string `vfilter:"optional,field=runas,doc=If specified, the query will run as the specified user"` } type QueryPlugin struct{} @@ -42,29 +46,56 @@ func (self QueryPlugin) Call( return } + // If we are not running on the server, we need to get the + // root config from the org manager. + org_manager, err := services.GetOrgManager() + if err != nil { + scope.Log("query: %v", err) + return + } + config_obj, ok := vql_subsystem.GetServerConfig(scope) if !ok { - // If we are not running on the server, we need to get the - // root config from the org manager. - org_manager, err := services.GetOrgManager() + config_obj, err = org_manager.GetOrgConfig("") if err != nil { scope.Log("query: %v", err) return } + } + org_config_obj := config_obj - config_obj, err = org_manager.GetOrgConfig("") + // Build a completely new scope to evaluate the query + // in. + builder := services.ScopeBuilderFromScope(scope) + + // Did the user request running in the specified org? Switch + // orgs if so. + if arg.OrgId != "" { + org_config_obj, err = org_manager.GetOrgConfig(arg.OrgId) if err != nil { scope.Log("query: %v", err) return } + + // The subscoope will switch to the specified org. + builder.Config = org_config_obj } - // Build a completely new scope to evaluate the query - // in. - builder := services.ScopeBuilderFromScope(scope) + if arg.Principal != "" { + // Impersonation is only allowed for administrator users. + err := vql_subsystem.CheckAccess(scope, acls.IMPERSONATION) + if err != nil { + scope.Log("query: Permission required for runas: %v", err) + return + } + + // Run as the specified user. + builder.ACLManager = acl_managers.NewServerACLManager( + org_config_obj, arg.Principal) + } // Make a new scope for each artifact. - manager, err := services.GetRepositoryManager(config_obj) + manager, err := services.GetRepositoryManager(org_config_obj) if err != nil { scope.Log("query: %v", err) return diff --git a/vql/tools/reporting.go b/vql/tools/reporting.go index 4f17607a48f..8534ac712e8 100644 --- a/vql/tools/reporting.go +++ b/vql/tools/reporting.go @@ -11,7 +11,7 @@ import ( config_proto "www.velocidex.com/golang/velociraptor/config/proto" "www.velocidex.com/golang/velociraptor/reporting" "www.velocidex.com/golang/velociraptor/services" - vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/velociraptor/vql/acl_managers" "www.velocidex.com/golang/vfilter" "www.velocidex.com/golang/vfilter/arg_parser" ) @@ -85,7 +85,7 @@ func produceReport( // generate arbitrary HTML. template_engine, err := reporting.NewHTMLTemplateEngine( config_obj, ctx, subscope, - vql_subsystem.NullACLManager{}, repository, + acl_managers.NullACLManager{}, repository, definition.Name, false /* sanitize_html */) if err != nil { return err @@ -110,7 +110,7 @@ func produceReport( template_engine, err := reporting.NewHTMLTemplateEngine( config_obj, ctx, subscope, - vql_subsystem.NullACLManager{}, repository, + acl_managers.NullACLManager{}, repository, template, false /* sanitize_html */) if err != nil { return err