commit 1e7713c8a39fe541a4de107f775765d29ea78e10 Author: Sangbum Kim Date: Tue Jan 2 00:04:24 2024 +0900 added intial commmit diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..ce1b641 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +* +!CMakeLists.txt +!setcap.c +!config.h.in diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..00a6291 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/.vscode +/build diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..b5b0b01 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 3.0) + +if(POLICY CMP0056) + cmake_policy(SET CMP0056 NEW) +endif() +if(POLICY CMP0065) + cmake_policy(SET CMP0065 NEW) +endif() +if(POLICY CMP0066) + cmake_policy(SET CMP0066 NEW) +endif() + +set(CMAKE_FIND_LIBRARY_SUFFIXES "${CMAKE_STATIC_LIBRARY_SUFFIX}") +set(CMAKE_EXE_LINKER_FLAGS "-static") +set(CMAKE_ENABLE_EXPORTS Off) + +include(CheckFunctionExists) + +project(setcap-static VERSION 1.0.0 LANGUAGES C) + +find_path(NAMES sys/capability.h REQUIRED) +find_library(LIBCAP_LIBRARY NAMES "${CMAKE_STATIC_LIBRARY_PREFIX}cap${CMAKE_STATIC_LIBRARY_SUFFIX}" cap) +if(NOT LIBCAP_LIBRARY) + message(FATAL_ERROR "Unable to find libcap") +endif() +add_library(libcap STATIC IMPORTED) +set_target_properties(libcap PROPERTIES IMPORTED_LOCATION ${LIBCAP_LIBRARY}) + +set(CMAKE_REQUIRED_LIBRARIES "${LIBCAP_LIBRARY};${CMAKE_REQUIRED_LIBRARIES}") +check_function_exists(cap_set_nsowner HAVE_CAP_SET_NSOWNER) + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) + +add_executable(setcap-static setcap.c) +target_link_libraries(setcap-static libcap) +target_include_directories(setcap-static PUBLIC ${PROJECT_BINARY_DIR}) diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0defbac --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM harbor.repository.lb.home.dc.internal.amuz.es/infrastructure/alpine-base:3.19-latest AS build +RUN apk add --no-cache cmake make musl-dev gcc libcap-static libcap-dev +WORKDIR /build +COPY . . +RUN \ + cmake -S . -B build -DCMAKE_BUILD_TYPE=MinSizeRel && \ + cmake --build build --config MinSizeRel && \ + strip build/setcap-static + +FROM scratch +COPY --from=build /build/build/setcap-static /setcap-static diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4f7aa48 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Volodymyr Kolesnykov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e164ea4 --- /dev/null +++ b/README.md @@ -0,0 +1,67 @@ +# setcap-static + +[![Build](https://github.com/sjinks/setcap-static/actions/workflows/build.yml/badge.svg)](https://github.com/sjinks/setcap-static/actions/workflows/build.yml) +[![Docker CI/CD](https://github.com/sjinks/setcap-static/actions/workflows/docker.yml/badge.svg)](https://github.com/sjinks/setcap-static/actions/workflows/docker.yml) +[![Language grade: C/C++](https://img.shields.io/lgtm/grade/cpp/g/sjinks/setcap-static.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/sjinks/setcap-static/context:cpp) +![Docker Image Size](https://img.shields.io/docker/image-size/wildwildangel/setcap-static/latest) + +`setcap-static` is a statically linked trimmed down version of [setcap(8)](https://linux.die.net/man/8/setcap). It sets the capabilities of the given filename to the capabilities specified. + +## Why + +KubeSec security guidelines suggest that the running image should be "run as a [non-root user to ensure the least privilege](https://kubesec.io/basics/containers-securitycontext-runasnonroot-true/)." However, if the containerized application needs some `root` privileges (like binding to a port less than 1024) and runs in a `scratch` image, this will not be straightforward. + +The issue is that Docker's `COPY` command does not preserve the extended attributes; therefore, you cannot do something like this: + +```Dockerfile +FROM alpine:3.13 as build + +# ... + +RUN \ + apk add --no-cache libcap \ + && setcap 'cap_net_bind_service=+ep' my-cool-application \ + && apk del --no-cache libcap + +# ... + +FROM scratch +COPY --from=build /path/to/my-cool-application /my-cool-application +``` + +In the target image, `my-cool-application` will not have the capabilities set in the `build` image. Therefore, if you need to grant some capabilities to your application, you have to do it in the target image. You cannot just copy `setcap` from Alpine — because it is a dynamically linked executable (it depends on ld-musl, libcap, libc.musl). + +Here comes `libcap-static`. It is a lightweight version of `libcap`: it can only set the capabilities on a file, it does not support all other options of `libcap`. + +Unlike `libcap`, `libcap-static` has an option to delete itself: this can be handy for `scratch` images if you don't want to leave any other executables than your application visible to the user (or an attacker). If `libcap-static` detects that the first two characters of `argv[0]` are `/!`, it will delete itself after the successful operation. + +For example, + +```Dockerfile +FROM scratch +COPY --from=wildwildangel/setcap-static /setcap-static /!setcap-static +COPY --from=build /build/build/tiny-ssh-honeypot /tiny-ssh-honeypot +RUN ["/!setcap-static", "cap_net_bind_service=+ep", "/tiny-ssh-honeypot"] +``` + +After granting the `CAP_NET_BIND_SERVICE` capability to `tiny-ssh-honeypot`, `libcap-static` will delete itself. + +## Build + +Build dependencies: + * Alpine: cmake, make, libcap-dev, libcap-static + * Ubuntu: cmake, make, libcap-dev + +```bash +cmake -S . -B build -DCMAKE_BUILD_TYPE=MinSizeRel +cmake --build build --config MinSizeRel +``` + +## Usage + +```bash +setcap-static capabilities filename +``` + + * `capabilities` is the list of capabilities in the form supported by [`cap_from_text(3)`](https://linux.die.net/man/3/cap_from_text) (or by `setcap`) + * `filename` is the name of the file to operate on; it must not refer to a symlink. diff --git a/config.h.in b/config.h.in new file mode 100644 index 0000000..2bab62f --- /dev/null +++ b/config.h.in @@ -0,0 +1,6 @@ +#ifndef A654D99F_22CB_438F_A0D3_6659AC64CE80 +#define A654D99F_22CB_438F_A0D3_6659AC64CE80 + +#cmakedefine HAVE_CAP_SET_NSOWNER 1 + +#endif /* A654D99F_22CB_438F_A0D3_6659AC64CE80 */ diff --git a/lgtm.yml b/lgtm.yml new file mode 100644 index 0000000..40a2b1b --- /dev/null +++ b/lgtm.yml @@ -0,0 +1,4 @@ +extraction: + cpp: + prepare: + packages: "libcap-dev" diff --git a/setcap.c b/setcap.c new file mode 100644 index 0000000..424cc9f --- /dev/null +++ b/setcap.c @@ -0,0 +1,66 @@ +#include +#include +#include +#include +#include "config.h" + +int main(int argc, char** argv) +{ + if (argc != 3) { + fprintf(stderr, "Usage: setcap capabilities filename\n"); + return argc == 1 ? EXIT_SUCCESS : EXIT_FAILURE; + } + + cap_t my_caps = cap_get_proc(); + if (my_caps == NULL) { + perror("cap_get_proc"); + return EXIT_FAILURE; + } + + cap_t target_caps = cap_from_text(argv[1]); + if (target_caps == NULL) { + perror("cap_from_text"); + cap_free(my_caps); + return EXIT_FAILURE; + } + +#ifdef HAVE_CAP_SETNSOWNER + if (cap_set_nsowner(target_caps, 0)) { + perror("cap_set_nsowner"); + cap_free(my_caps); + cap_free(target_caps); + return EXIT_FAILURE; + } +#endif + + cap_value_t flag = CAP_SETFCAP; + if (cap_set_flag(my_caps, CAP_EFFECTIVE, 1, &flag, CAP_SET) != 0) { + perror("cap_set_flag(CAP_SETFCAP)"); + cap_free(my_caps); + cap_free(target_caps); + return EXIT_FAILURE; + } + + if (cap_set_proc(my_caps) != 0) { + perror("cap_set_proc"); + cap_free(my_caps); + cap_free(target_caps); + return EXIT_FAILURE; + } + + cap_free(my_caps); + + if (cap_set_file(argv[2], target_caps) != 0) { + perror("cap_set_file"); + cap_free(target_caps); + return EXIT_FAILURE; + } + + cap_free(target_caps); + + if (argv[0][0] == '/' && argv[0][1] == '!') { + unlink(argv[0]); + } + + return EXIT_SUCCESS; +}