Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(#1038) Add scope start command #1049

Merged
merged 56 commits into from
Sep 16, 2022
Merged

Conversation

michalbiesek
Copy link
Contributor

@michalbiesek michalbiesek commented Jul 19, 2022

TODO:

Question:

  • In case of setting custom yml configuration should service be still using the following path /etc/scope/<service_name>/scope.yml or point to a new yml file location

overwrite the file

Next steps:

Interactive process: docker exec -it local/remote login/command run Ensure that our library is loaded

Future Improvements

  • Handle communication layer from fs namespace after scope attach from host perspective?
  • Identify User in Namespace/ Container ID in command like scope ps
Found multiple processes matching that name...
ID	PID    	USER    	SCOPED	COMMAND
1 	846727 	-       	true  	memcached
2 	877968 	-       	true  	/usr/bin/memcached -m 64 -p 11211 -u memcache -l 127.0.0.1 -P /var/run/memcached/memcached.pid
3 	1198535	memcache	false 	/usr/bin/memcached -m 64 -p 11211 -u memcache -l 127.0.0.1 -P /var/run/memcached/memcached.pid

@michalbiesek michalbiesek force-pushed the feature/1038/scope-filter branch 3 times, most recently from 9e54b38 to 9ae1f2b Compare July 20, 2022 08:35
@michalbiesek
Copy link
Contributor Author

michalbiesek commented Jul 20, 2022

Progress for interactive commands and loading the libscope

For single cmd run

docker exec --user foo -e LD_PRELOAD=/usr/local/lib/libscope.so -it ubuntu_test top
docker exec -e LD_PRELOAD=/usr/local/lib/libscope.so alpine_test top
  • The libscope.so is loaded in top

The disadvantage is that the command must be prepared on the host side.
Tested on Ubuntu and Alpine.

For interactive login shell

docker exec -it --user foo ubuntu_test bash -l

Steps required to do it:

  1. Create the script in /etc/profile.d/scope.sh
export LD_PRELOAD=/usr/local/lib/libscope.so

All commands executed in the shell result in preloading the libscope.so excluding the shell itself.
Tested on Ubuntu and Alpine.

For interactive shell

For the bash

docker exec -it --user foo ubuntu_test bash

Append to the ~/.bashrc -> export LD_PRELOAD=/usr/local/lib/libscope.so
With the change above commands executed in the shell result in preloading the libscope.so excluding the shell itself.

For the ash (Alpine)
I did not yet found a viable way to load libscope.so for children process in case of the following command:

docker exec -it --user foo alpine_test ash

The solution present in a single cmd run works.

Credits to @iapaddler for investigation.

@michalbiesek
Copy link
Contributor Author

michalbiesek commented Jul 20, 2022

Progress for namespace

  • explore namespace API to see if we can run scope filter in the container context.

Let's consider the following example:

  • We want to scope the process on the container by focusing on running most of the action on the host machine.
flowchart LR
  subgraph Host 
    subgraph Container A
       id1["Process A"]
    end
    subgraph Container B
       id2["Service B"]
    end
    id3["Process C"]
  end
Loading
flowchart TB
    A[Host] -->|scope filter config.file| C{Check if Process belongs to host namespace}
    C -->|Process is on host namespace| D[Perform scope filter action]
    C -->|Process is not on host namespace| E[Switch namespace]
    E --> D
Loading

scope filter depends on passed configuration, but internally it will call actions like:

  • scope service ?
  • scope attach ?
  • Perform action like described here - My current goal is to focus on switching namespace and perform attach operation

Scope attach with switching namespaces at first glance seems to work with Docker containers

@michalbiesek michalbiesek force-pushed the feature/1038/scope-filter branch 2 times, most recently from 6b467ce to a276757 Compare July 25, 2022 10:41
@michalbiesek
Copy link
Contributor Author

michalbiesek commented Jul 26, 2022

Scope attach in different namespace progress:

  • Attach to docker process Ubuntu
  • Attach to docker process Alpine
  • Passing configuration file from the host
  • Passing configuration parameter from host
  • Add information about namespace switch in log

@michalbiesek
Copy link
Contributor Author

michalbiesek commented Jul 27, 2022

Scope attach in different namespace progress:

  • Attach to LXC container

While attaching functionality to the Docker container works fine - the LXC container is not eager to cooperate.

Several observations are mentioned below:

  • with the current implementation, the attach fails on the namespace side in:

    appscope/src/scope_static.c

    Lines 1100 to 1104 in 431fb7a

    int fd = scope_shm_open(path, O_RDWR|O_CREAT, S_IRUSR|S_IRGRP|S_IROTH);
    if (fd == -1) {
    scope_perror("shm_open() failed");
    return EXIT_FAILURE;
    }

    returning the EOVERFLOW
  • the loader in lxc namespace context has access to the dev/shm path
  • I see that files created from CLI in the context of namespace (/root/.scope/) have different owner than a root
drwxr-xr-x  3 nobody nogroup    5 Jul 27 11:29 .scope
  • In the case of Docker container, the same directory and its content are owned by root.
  • I cannot remove (/root/.scope/) being a root inside the LXC namespace:
root@ubuntuLcTest:~/.scope# ls -la ldscope 
-rwxr-xr-x 1 nobody nogroup 11411456 Jul 27 10:58 ldscope
root@ubuntuLcTest:~/.scope# rm -f ldscope 
rm: cannot remove 'ldscope': Permission denied
  • This can come from a different approach in user and gid mappings

In Docker:

proc/<proc_running_in_docker_namespace_pid_from_host_perspective>/uid_map 
         0          0 4294967295
proc/<proc_running_in_docker_namespace_pid_from_host_perspective>/gid_map 
         0          0 4294967295

In Lxc:

proc/<proc_running_in_lxc_namespace_pid_from_host_perspective>/uid_map 
         0    1000000 1000000000
proc/<proc_running_in_lxc_namespace_pid_from_host_perspective>/gid_map 
         0    1000000 1000000000

@michalbiesek
Copy link
Contributor Author

michalbiesek commented Jul 28, 2022

Scope attach in different namespace progress:

  • LXC seems to work - it required a GID and UID translation during switching namespace to avoid file permissions issue mentioned in the previous comment
  • I have observed timing-related? the issue that sometimes during attach I fail during:

    appscope/src/scope_static.c

    Lines 1071 to 1074 in 431fb7a

    if (scope_readlink("/proc/self/exe", execPath, sizeof(execPath) - 1) == -1) {
    scope_perror("readlink(/proc/self/exe) failed");
    return EXIT_FAILURE;
    }

    Setting SCOPE_EXEC_PATH in cli solves the issue
  • I am not sure if it is worth mentioning somewhere that the --cap-add CAP_SYS_PTRACE must be added in the case of Docker container even if we perform attach as a root from the host
  • The one challenge which I did not resolve yet is the following scenario:
 lxc launch ubuntu:20.04 ubuntuLcTest
 lxc exec ubuntuLcTest bash
 apt update -y && apt install -y redis

After installation redis will be run as a service - but we cannot attach to it - even from the container itself. scope service will work in this case though.
The issue is with stricter systemd setting for the redis service.
E.g. setting

PrivateTmp=true

Prevent us from attach

error: dlopen() failed, library could not be injected

@jrcheli jrcheli changed the base branch from master to release/1.2 July 28, 2022 17:18
@michalbiesek michalbiesek force-pushed the feature/1038/scope-filter branch 2 times, most recently from e356137 to 65db197 Compare July 29, 2022 15:01
@michalbiesek michalbiesek changed the title [WIP] (#1038) Add scope filter command [WIP] (#1038) Add scope start command Aug 23, 2022
@michalbiesek michalbiesek force-pushed the feature/1038/scope-filter branch 6 times, most recently from ec38cdd to 0157253 Compare August 26, 2022 12:38
@michalbiesek michalbiesek force-pushed the feature/1038/scope-filter branch 3 times, most recently from 97a628b to 21e0715 Compare September 2, 2022 08:58
@michalbiesek michalbiesek changed the title [WIP] (#1038) Add scope start command (#1038) Add scope start command Sep 2, 2022
@michalbiesek michalbiesek marked this pull request as ready for review September 2, 2022 09:09
@michalbiesek michalbiesek force-pushed the feature/1038/scope-filter branch 2 times, most recently from 21e897e to 7b81cc0 Compare September 2, 2022 10:05
@michalbiesek michalbiesek linked an issue Sep 2, 2022 that may be closed by this pull request
- add support for passing configuration file from host into the container
- in `ldscope` copy the configuration file from host to container

Closes #1061
cli/run/start.go Outdated Show resolved Hide resolved
cli/run/start.go Outdated Show resolved Hide resolved
cli/run/start.go Outdated

// Validate user has root permissions
if err := util.UserVerifyRootPerm(); err != nil {
log.Fatal().
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

log.Fatal will actually cause an os.Exit(). might be better to stick to log.Error and return err , to maintain our pattern

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I intend to call os. exit here please see that there is no point to run it further if you are not an root.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

then the return err afterwards is not needed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Your suggestion with Log.error instead of Log.fatal is better. With Log.fatal I call Os.Exit with the same value. With Log.error I can distinguish the error with the return value. Long story short changed

cli/run/start.go Outdated Show resolved Hide resolved
cli/run/start.go Outdated Show resolved Hide resolved
cli/run/start.go Outdated Show resolved Hide resolved
- `Verfy` -> `Verify`
- `exit` -> `exist`
- `alllow` -> `allow`
- Add TODO
- Fix formatting
src/ns.c Show resolved Hide resolved
src/ns.c Outdated Show resolved Hide resolved
}
if (setNamespace(hostPid, "mnt") == FALSE) {
goto cleanupMem;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like we need a net NS and probably ipc as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH for my internal testing mnt and pid namespace was sufficent

static int g_debug = 0;

static void
setEnvVariable(char *env, char *value)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have a utility func that sets an env var. utils.c:fullSetenv(). can that be used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not think so: FullSetenv is doing the operation based on g_fn structure.
setEnvVariable (or more generally loaderop module) is doing the operations in the context of our loader (ldscope) where we g_fn is not available.

*/

static int
get_dir(const char *path, char *fres, size_t len) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be a generally avail func in utils.c, like getpath()?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH the code here was moved from scope_static.c with the intention to share some logic between modules e.g. function loaderOpPatchLibrary used in ns.c

See commit d5badd8 for details

src/setup.c Outdated Show resolved Hide resolved
}

char *
loaderOpGetLoader(const char *exe) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are all of these funcs moved? are they used in multiple places?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With scope start: one of the first operations which I perform is copying the libscope.so to the well-known location on the host and on the containers.
For the container part, I use the code presented in the ldscope as a --configure. If the container is a musl-based distro (e.g. Alpine) I need to perform patching of the libscope.so.
Therefore after copying the libscope.so from a host into a container I need to perform a patching operation. I do not perform patching via ldscope --patch. I patch the libscope.so with function loaderOpPatchLibrary.
The function loaderOpPatchLibrary is used in multiple places: in ldscope and in setup.c. The rest of the code is moved here since it is needed for the loaderOpPatchLibrary logic.
I limit the visibility of loaderOpSetLibrary and loaderOpSetLibrary

src/setup.c Outdated Show resolved Hide resolved
@michalbiesek michalbiesek merged commit 2818ca8 into release/1.2 Sep 16, 2022
@michalbiesek michalbiesek deleted the feature/1038/scope-filter branch November 15, 2022 08:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Interpose services based on a filter
3 participants