이전 글 요약

  • 설계 미스로 원하던 구조의 실행기가 아니게 됨.
  • 어디서 미스난지 파악하고 설계 개선

현재 고민

서버에서 Python을 실행시키려면 제약 조건이 많아진다. 신뢰할 수 없는 코드에 대해서도 실행가능해야하는데 다음과 같은 고려 사항을 떠올렸다.

  • 네트워크 격리
  • CPU, 시간, 메모리와 같은 자원 제한
  • syscall 필터
  • 파일 시스템 제한

현재의 선택지

seccomp

seccomp는 리눅스에서 sandbox 기반으로 시스템콜을 허용 및 차단하여 공격의 가능성을 막는 리눅스 보안 메커니즘이다.

Nsjail

네임스페이스, 리소스 제한 및 seccomp-bpf 시스템 호출 필터를 사용하는 Linux 프로세스 격리 도구이다.
내가 원하던 네트워크 격리, syscall, 자원 제한, 파일시스템 제한 등 많은 기능이 있었다.

Nsjail로 결정

다른 사람들이 어떻게 했는지 구글링하다가 같은 생각을 한 사람을 발견했다. 글1 글2 정도 유용하게 쓰였고, 글2가 많은 도움을 주었다. 구글에서도 untrust python code를 실행시키기 위해 nsjail을 썼다고 한다. 그냥 자기들이 만든거 쓴거같은데
결론은 OnPyRunner에 Nsjail을 써서 격리환경을 구성할 것 같다.

테스트 주도 개발 도입

내가 nsjail 코드를 작성하고 코드를 돌렸을 때, 내가 원했던 조건들이 적용되었는지 하나하나씩 일일이 체크하는건 어렵다. 테스트케이스를 만들어서 코드가 주어질때 stdout, stderr를 예측하도록 하기 위해 테스트 주도 개발을 하려고 한다.
runCode라는 함수에 대해서 jest를 사용해서 unit test를 진행해야겠다.

Test Case 만들어보기

먼저 runCode를 통해 Hello, World! 출력을 확인하는 테스트 코드를 작성해보았다.

test("stdout에 Hello, World!가 출력된다", () => {
    const result = runCode({
        code: "print('Hello, World!')",
        input: "",
    });

    expect(result.stdout.trim()).toBe("Hello, World!");
});

이제 아래 코드를 nsjail을 사용해서 python이 실행되도록 만들면 된다.

function runCode({code, input}) {
    return {
        stdout: '',
        stderr: '',
        exitCode: 0,
    };
}

js에서 nsjail 실행시키기

node에서 nsjail을 실행시키려면 child_process의 spawn을 사용하면 된다. 이 때, arg와 config 파일 설정을 통해 nsjail을 커스텀할 수 있다.

삽질하면서 알게 된 것

  • spawn은 비동기함수라서 Jest에서 실행시키면 항상 undefined를 받으므로 spawnSync를 사용해야한다.
  • 실행하다가 “spawnSync /usr/local/bin/nsjail ENOENT"라는 에러가 떴는데, 이것은 node.js가 nsjail 경로를 찾지 못해서였는데, 알고보니 ubuntu용 node가 없고, window node로 실행되어서 window node가 해당 경로로 가게 되어 nsjail을 찾지 못하게 되는 것이였다. 그래서 바로 nvm을 깔아 주었다.

수많은 에러끝에 성공해냄. alt text

참고 자료