Hi,
I have a scenario where a normal user process needs to call docker stats command, which is an elevated (root) call. The idea is to write a simple helper binary that adds the appropriate caps to the inheritable and ambient sets (and then set the caps on the binary from a setup script that runs as root...). However, this approach does not work since you can't connect to the docker daemon as a normal user, for good reason... I even set CAP_SYS_ADMIN as a test (obviously using this cap for a helper binary is not part of the plan, for obvious reasons...) and it did not work. It fails with
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.40/version: dial unix /var/run/docker.sock: connect: permission denied
Is this possible to pull off with capabilities?
Here is the sample helper binary code:
#include <stdio.h>
#include <unistd.h>
#include <sys/capability.h>
#include <sys/prctl.h>
/*
P'(ambient) = (file is privileged) ? 0 : P(ambient)
P'(permitted) = (P(inheritable) & F(inheritable)) |
(F(permitted) & P(bounding)) | P'(ambient)
P'(effective) = F(effective) ? P'(permitted) : P'(ambient)
*/
static void set_ambient_caps(int *newcaps, int num_elem)
{
int i;
for(i=0; i<num_elem; i++)
{
if(prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, newcaps, 0, 0)){
printf("Fail!\n");
return;
}
printf("Success!\n");
}
}
int main()
{
cap_value_t newcaps[2] = {CAP_NET_ADMIN, CAP_SYS_ADMIN};
// Add capabilities in the Inheritable set.
cap_t caps = cap_get_proc();
printf("Capabilities: %s\n", cap_to_text(caps, NULL));
cap_set_flag(caps, CAP_INHERITABLE, 2, newcaps, CAP_SET);
cap_set_proc(caps);
printf("Capabilities: %s\n", cap_to_text(caps, NULL));
cap_free(caps);
// Set ambient capabilities.
set_ambient_caps(newcaps, sizeof(newcaps)/sizeof(newcaps[0]));
char *param[] = {"docker", "stats", "--no-stream", NULL};
execv("/usr/bin/docker", param);
}
The setup script is
sudo setcap CAP_NET_ADMIN,CAP_SYS_ADMIN+ep ./elevated_docker
Adding the normal user to docker group is a security hole since this would mean any process running as this user could do stuff like kill container instances, run commands in containers, etc... The helper binary can only do one thing: execv docker stats --no-stream. Besides adding caps to the appropriate sets, this is all it does, so an attacker running as the normal user in this case could not gain access to other docker facilities. Again, I do not want to use the group approach because that is just as bad as running the calling process as root (with respect to what it does with docker containers), which we do not want to do...
Thanks for any suggestions,
Charles
I have a scenario where a normal user process needs to call docker stats command, which is an elevated (root) call. The idea is to write a simple helper binary that adds the appropriate caps to the inheritable and ambient sets (and then set the caps on the binary from a setup script that runs as root...). However, this approach does not work since you can't connect to the docker daemon as a normal user, for good reason... I even set CAP_SYS_ADMIN as a test (obviously using this cap for a helper binary is not part of the plan, for obvious reasons...) and it did not work. It fails with
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.40/version: dial unix /var/run/docker.sock: connect: permission denied
Is this possible to pull off with capabilities?
Here is the sample helper binary code:
#include <stdio.h>
#include <unistd.h>
#include <sys/capability.h>
#include <sys/prctl.h>
/*
P'(ambient) = (file is privileged) ? 0 : P(ambient)
P'(permitted) = (P(inheritable) & F(inheritable)) |
(F(permitted) & P(bounding)) | P'(ambient)
P'(effective) = F(effective) ? P'(permitted) : P'(ambient)
*/
static void set_ambient_caps(int *newcaps, int num_elem)
{
int i;
for(i=0; i<num_elem; i++)
{
if(prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, newcaps, 0, 0)){
printf("Fail!\n");
return;
}
printf("Success!\n");
}
}
int main()
{
cap_value_t newcaps[2] = {CAP_NET_ADMIN, CAP_SYS_ADMIN};
// Add capabilities in the Inheritable set.
cap_t caps = cap_get_proc();
printf("Capabilities: %s\n", cap_to_text(caps, NULL));
cap_set_flag(caps, CAP_INHERITABLE, 2, newcaps, CAP_SET);
cap_set_proc(caps);
printf("Capabilities: %s\n", cap_to_text(caps, NULL));
cap_free(caps);
// Set ambient capabilities.
set_ambient_caps(newcaps, sizeof(newcaps)/sizeof(newcaps[0]));
char *param[] = {"docker", "stats", "--no-stream", NULL};
execv("/usr/bin/docker", param);
}
The setup script is
sudo setcap CAP_NET_ADMIN,CAP_SYS_ADMIN+ep ./elevated_docker
Adding the normal user to docker group is a security hole since this would mean any process running as this user could do stuff like kill container instances, run commands in containers, etc... The helper binary can only do one thing: execv docker stats --no-stream. Besides adding caps to the appropriate sets, this is all it does, so an attacker running as the normal user in this case could not gain access to other docker facilities. Again, I do not want to use the group approach because that is just as bad as running the calling process as root (with respect to what it does with docker containers), which we do not want to do...
Thanks for any suggestions,
Charles