Seccomp у Kubernetes: 7 рэчаў, пра якія трэба ведаць з самага пачатку

Заўв. перав.: Уяўляем увазе пераклад артыкула старэйшага інжынера па бяспецы прыкладанняў брытанскай кампаніі ASOS.com. З ёй ён пачынае цыкл публікацый, прысвечаных павышэнню бяспекі ў Kubernetes дзякуючы выкарыстанню seccomp. Калі ўвядзенне спадабаецца чытачам, мы рушым услед за аўтарам і працягнем з яго будучымі матэрыяламі па гэтай тэме.

Seccomp у Kubernetes: 7 рэчаў, пра якія трэба ведаць з самага пачатку

Гэты артыкул - першая з серыі публікацый аб тым, як ствараць профілі seccomp у духу SecDevOps, не звяртаючыся да магіі і вядзьмарству. У першай частцы я распавяду аб асновах і ўнутраных дэталях рэалізацыі seccomp у Kubernetes.

Экасістэма Kubernetes прапануе дастатковую разнастайнасць спосабаў па забеспячэнні бяспекі і ізаляцыі кантэйнераў. Артыкул прысвечана Secure Computing Mode, таксама вядомаму як seccomp. Яго сутнасць складаецца ў фільтраванні сістэмных выклікаў, даступных для выканання кантэйнерамі.

Чаму гэта важна? Кантэйнер - гэта ўсяго толькі нейкі працэс, запушчаны на вызначанай машыне. І ён выкарыстоўвае ядро ​​нароўні з іншымі праграмамі. Калі б кантэйнеры маглі выконваць любыя сістэмныя выклікі, вельмі хутка гэтым бы скарысталіся шкоднасныя праграмы, каб абыйсці ізаляцыю кантэйнера і ўздзейнічаць на іншыя прыкладанні: перахапляць інфармацыю, змяняць налады сістэмы і да т.п.

Профілі seccomp вызначаюць, якія сістэмныя выклікі павінны быць дазволеныя ці забароненыя. Асяроддзе выканання кантэйнера актывуе іх падчас яго запуску, каб ядро ​​магло весці кантроль за іх выкананнем. Ужыванне падобных профіляў дазваляе абмежаваць вектар нападу і скараціць страты ў выпадку, калі якая-небудзь праграма ўсярэдзіне кантэйнера (гэта значыць вашы залежнасці, ці іх залежнасці) пачне рабіць тое, што ёй не дазволена.

Разбіраемся з асновамі

Базавы профіль seccomp уключае тры элемента: defaultAction, architectures (Або archMap) І syscalls:

{
    "defaultAction": "SCMP_ACT_ERRNO",
    "architectures": [
        "SCMP_ARCH_X86_64",
        "SCMP_ARCH_X86",
        "SCMP_ARCH_X32"
    ],
    "syscalls": [
        {
            "names": [
                "arch_prctl",
                "sched_yield",
                "futex",
                "write",
                "mmap",
                "exit_group",
                "madvise",
                "rt_sigprocmask",
                "getpid",
                "gettid",
                "tgkill",
                "rt_sigaction",
                "read",
                "getpgrp"
            ],
            "action": "SCMP_ACT_ALLOW"
        }
    ]
}

(medium-basic-seccomp.json)

defaultAction вызначае лёс па змаўчанні любога сістэмнага выкліку, не ўказанага ў раздзеле syscalls. Каб спрасціць задачу, засяродзімся на двух асноўных значэннях, якія будуць выкарыстоўвацца:

  • SCMP_ACT_ERRNO - блакуе выкананне сістэмнага выкліку,
  • SCMP_ACT_ALLOW - дазваляе.

У раздзеле architectures пералічваюцца мэтавыя архітэктуры. Гэта важна, паколькі сам фільтр, які ўжываецца на ўзроўні ядра, залежыць ад ідэнтыфікатараў сістэмных выклікаў, а не ад іх назоваў, прапісаных у профілі. Перад ужываннем асяроддзе выканання кантэйнера супаставіць іх з ідэнтыфікатарамі. Сэнс у тым, што сістэмныя выклікі могуць мець зусім розныя ID у залежнасці ад архітэктуры сістэмы. Напрыклад, сістэмны выклік recvfrom (выкарыстоўваецца для атрымання інфармацыі ад сокета) мае ID = 64 у x64-сістэмах і ID = 517 у x86. Тут вы можаце знайсці спіс усіх сістэмных выклікаў для архітэктур x86-x64.

У секцыі syscalls пералічваюцца ўсе сістэмныя выклікі і паказваецца, што з імі трэба рабіць. Напрыклад, можна стварыць белы спіс, усталяваўшы defaultAction на SCMP_ACT_ERRNO, а выклікам у секцыі syscalls прысвоіць SCMP_ACT_ALLOW. Тым самым вы дазваляеце толькі выклікі, прапісаныя ў раздзеле syscalls, і забараняеце ўсе астатнія. Для чорнага спісу варта памяняць значэнні defaultAction і дзеянні на супрацьлеглыя.

Цяпер варта сказаць пару слоў аб нюансах, якія не гэтак відавочныя. Звярніце ўвагу, што рэкамендацыі ніжэй зыходзяць з таго, што вы разгортваеце лінейку бізнес-прыкладанняў у Kubernetes і вам важна, каб яны працавалі з найменшымі прывілеямі.

1. AllowPrivilegeEscalation=false

В securityContext кантэйнера маецца параметр AllowPrivilegeEscalation. Калі ён усталяваны ў false, кантэйнеры будуць запускацца з усталяваным (on) бітам no_new_priv. Сэнс гэтага параметру відавочны з назвы: ён не дазваляе кантэйнеру запускаць новыя працэсы з прывілеямі, большымі, чым маюцца ў яго самога.

Пабочным эфектам гэтага параметру, усталяванага ў true (значэнне па змаўчанні), з'яўляецца тое, што runtime кантэйнера прымяняе профіль seccomp ў самым пачатку працэсу запуску. Такім чынам, усе сістэмныя выклікі, неабходныя для запуску ўнутраных працэсаў асяроддзя выканання (напрыклад, усталёўка ідэнтыфікатараў карыстача/групы, адкідванне некаторых capabilities), павінны быць дазволеныя ў профілі.

Кантэйнеру, які выконвае банальнае echo hi, спатрэбяцца наступныя дазволы:

{
    "defaultAction": "SCMP_ACT_ERRNO",
    "architectures": [
        "SCMP_ARCH_X86_64",
        "SCMP_ARCH_X86",
        "SCMP_ARCH_X32"
    ],
    "syscalls": [
        {
            "names": [
                "arch_prctl",
                "brk",
                "capget",
                "capset",
                "chdir",
                "close",
                "execve",
                "exit_group",
                "fstat",
                "fstatfs",
                "futex",
                "getdents64",
                "getppid",
                "lstat",
                "mprotect",
                "nanosleep",
                "newfstatat",
                "openat",
                "prctl",
                "read",
                "rt_sigaction",
                "statfs",
                "setgid",
                "setgroups",
                "setuid",
                "stat",
                "uname",
                "write"
            ],
            "action": "SCMP_ACT_ALLOW"
        }
    ]
}

(hi-pod-seccomp.json)

… замест гэтых:

{
    "defaultAction": "SCMP_ACT_ERRNO",
    "architectures": [
        "SCMP_ARCH_X86_64",
        "SCMP_ARCH_X86",
        "SCMP_ARCH_X32"
    ],
    "syscalls": [
        {
            "names": [
                "arch_prctl",
                "brk",
                "close",
                "execve",
                "exit_group",
                "futex",
                "mprotect",
                "nanosleep",
                "stat",
                "write"
            ],
            "action": "SCMP_ACT_ALLOW"
        }
    ]
}

(hi-container-seccomp.json)

Але зноў жа, чаму гэта праблема? Асабіста я пазбягаў бы занясенні ў белы спіс наступных сістэмных выклікаў (калі ў іх няма рэальнай неабходнасці): capset, set_tid_address, setgid, setgroups и setuid. Аднак сапраўдная складанасць у тым, што, дазваляючы працэсы, якія вы абсалютна не кантралюеце, вы прывязваеце профілі да рэалізацыі асяроддзя выканання кантэйнераў. Іншымі словамі, аднойчы вы можаце сутыкнуцца з тым, што пасля абнаўлення runtime-асяроддзя кантэйнера (вамі ці, што верагодней, пастаўшчыком хмарных паслуг) кантэйнеры раптам перастануць запускацца.

Савет №1: Запускайце кантэйнеры з AllowPrivilegeEscaltion=false. Гэта скароціць памер профіляў seccomp і зробіць іх меней адчувальнымі да змены асяроддзя выканання кантэйнера.

2. Заданне профіляў seccomp на ўзроўні кантэйнера

Профіль seccomp можна задаваць на ўзроўні pod'а:

annotations:
  seccomp.security.alpha.kubernetes.io/pod: "localhost/profile.json"

… або на ўзроўні кантэйнера:

annotations:
  container.security.alpha.kubernetes.io/<container-name>: "localhost/profile.json"

Звярніце ўвагу, што прыведзены вышэй сінтаксіс зменіцца, калі Kubernetes seccomp стане GA (гэтая падзея чакаецца ўжо ў наступным рэлізе Kubernetes – 1.18 – заўв. перакл.).

Мала хто ведае, што ў Kubernetes заўжды меўся памылка, з-за якога профілі seccomp ужываліся да pause-кантэйнеру. Асяроддзе выканання часткова кампенсуе дадзены недахоп, аднак гэты кантэйнер нікуды не дзяецца з pod'ов, паколькі выкарыстоўваецца для наладкі іх інфраструктуры.

Праблема ж у тым, што гэты кантэйнер заўсёды запускаецца з AllowPrivilegeEscalation=true, прыводзячы да праблем, агучаных у пункце 1, і змяніць гэта немагчыма.

Ужываючы профілі seccomp на ўзроўні кантэйнера, вы пазбягаеце дадзенай пасткі і можаце стварыць профіль, які будзе "заменчаны" пад пэўны кантэйнер. Так давядзецца рабіць да таго часу, пакуль распрацоўшчыкі не выправяць баг і новая версія (можа быць, 1.18?) стане даступная для ўсіх жадаючых.

Савет №2: Задавайце профілі seccomp на ўзроўні кантэйнера.

У практычным сэнсе гэтае правіла звычайна служыць універсальным адказам на пытанне: «Чаму мой профіль seccomp працуе з docker run, але не працуе пасля разгортвання ў кластары Kubernetes?».

3. Выкарыстоўвайце runtime/default толькі ў крайнім выпадку

У Kubernetes маецца два варыянты ўбудаваных профіляў: runtime/default и docker/default. Абодва рэалізуюцца асяроддзем выканання кантэйнера, а не Kubernetes. Таму яны могуць адрознівацца ў залежнасці ад выкарыстоўванага асяроддзя выканання і яго версіі.

Іншымі словамі, у выніку змены runtime кантэйнер можа атрымаць доступ да іншага набору сістэмных выклікаў, якія ён можа выкарыстоўваць ці не выкарыстоўваць. Большасць асяроддзяў выканання выкарыстоўваюць рэалізацыю Docker. Калі вы жадаеце задзейнічаць гэты профіль, пераканаецеся, што ён вам падыходзіць.

Профіль docker/default лічыцца састарэлым з Kubernetes 1.11, таму пазбягайце яго прымянення.

На маю думку, профіль runtime/default выдатна падыходзіць для тых мэт, для якіх ён ствараўся: абароны карыстачоў ад рызык, злучаных з выкананнем каманды docker run на іх машынах. Аднак калі казаць аб бізнэс-прыкладаннях, якія працуюць у кластарах Kubernetes, я б узяў на сябе смеласць сцвярджаць, што такі профіль занадта адчынены і распрацоўнікі павінны сканцэнтравацца на стварэнні профіляў пад свае прыкладанні (або тыпы прыкладанняў).

Савет №3: Стварайце профілі seccomp пад канкрэтныя прыкладання. Калі гэта немагчыма, займіцеся профілямі пад віды прыкладанняў, напрыклад, стварыце пашыраны профіль, які ўключыць у сябе ўсе вэб-API прыкладання на Golang. Толькі ў якасці крайняга сродку выкарыстоўвайце runtime/default.

У будучых публікацыях я раскажу, як ствараць профілі seccomp у духу SecDevOps, аўтаматызаваць і тэставаць іх у пайплайнах. Іншымі словамі, у вас не застанецца апраўданняў, каб не пераходзіць на профілі пад канкрэтныя прыкладанні.

4. Unconfined - гэта НЕ варыянт

З першага аўдыту бяспекі Kubernetes высветлілася, што па змаўчанні seccomp адключаны. Гэта азначае, што калі вы не задасце PodSecurityPolicy, якая ўключыць яго ў кластары, усе pod'ы, для якіх не вызначаны профіль seccomp, будуць працаваць у рэжыме seccomp=unconfined.

Праца ў такім рэжыме азначае, што губляецца цэлы пласт ізаляцыі, які забяспечвае абарону кластара. Падобны падыход не рэкамендуецца спецыялістамі па бяспецы.

Савет №4: Ніводны кантэйнер у кластары не павінен працаваць у рэжыме seccomp=unconfined, Асабліва ў production-асяроддзях.

5. "Рэжым аўдыту"

Гэты момант не ўнікальны для Kubernetes, але ўсё ж трапляе ў катэгорыю "пра што варта ведаць яшчэ да пачатку".

Так павялося, што стварэнне профіляў seccomp заўсёды было няпростым заняткам і ў значнай ступені засноўвалася на метадзе спроб і памылак. Справа ў тым, што ў карыстачоў проста няма магчымасці праверыць іх у production-асяроддзях, не рызыкуючы «выпусціць» прыкладанне.

Пасля з'яўлення ядра Linux 4.14 з'явілася магчымасць запускаць часткі профіля ў рэжыме аўдыту, запісваючы ў syslog інфармацыю аб усіх сістэмных выкліках, але не блакуючы іх. Актываваць гэты рэжым можна з дапамогай параметру SCMT_ACT_LOG:

SCMP_ACT_LOG: seccomp не будзе ўплываць на працу струменя, які робіць сістэмны выклік, калі ён не падпадае пад якое-небудзь правіла з фільтра, аднак інфармацыя аб сістэмным выкліку будзе занесеная ў часопіс.

Вось тыпавая стратэгія выкарыстання гэтай магчымасці:

  1. Дазволіць сістэмныя выклікі, якія неабходныя.
  2. Заблакаваць сістэмы выклікі, аб якіх вядома, што яны не спатрэбяцца.
  3. Інфармацыю аб усіх астатніх выкліках запісваць у часопіс.

Спрошчаны прыклад выглядае наступным чынам:

{
    "defaultAction": "SCMP_ACT_LOG",
    "architectures": [
        "SCMP_ARCH_X86_64",
        "SCMP_ARCH_X86",
        "SCMP_ARCH_X32"
    ],
    "syscalls": [
        {
            "names": [
                "arch_prctl",
                "sched_yield",
                "futex",
                "write",
                "mmap",
                "exit_group",
                "madvise",
                "rt_sigprocmask",
                "getpid",
                "gettid",
                "tgkill",
                "rt_sigaction",
                "read",
                "getpgrp"
            ],
            "action": "SCMP_ACT_ALLOW"
        },
        {
            "names": [
                "add_key",
                "keyctl",
                "ptrace"
            ],
            "action": "SCMP_ACT_ERRNO"
        }
    ]
}

(medium-mixed-seccomp.json)

Але памятайце, што неабходна заблакаваць усе выклікі, пра якія вядома, што яны не будуць скарыстаны, і якія патэнцыйна здольныя нашкодзіць кластару. Добрай асновай для складання спісу з'яўляецца афіцыйная дакументацыя Docker. У ёй падрабязна тлумачыцца, якія сістэмныя выклікі заблакаваныя ў профілі па змаўчанні і чаму.

Зрэшты, ёсць адзін падвох. Хаця SCMT_ACT_LOG падтрымліваецца ядром Linux з канца 2017 гады, у экасістэму Kubernetes ён увайшоў толькі параўнальна нядаўна. Таму для выкарыстання гэтага метаду спатрэбяцца ядро ​​Linux 4.14 і runC версіі не ніжэй версія 1.0.0-rc9.

Савет №5: Профіль рэжыму аўдыту для тэставання ў production можна стварыць, камбінуючы чорны і белы спісы, а ўсе выключэнні запісваць у часопіс.

6. Выкарыстоўвайце белыя спісы

Фарміраванне белых спісаў патрабуе дадатковых намаганняў, паколькі даводзіцца ідэнтыфікаваць кожны выклік, які можа спатрэбіцца з дадаткам, аднак гэты падыход ладна павышае бяспеку:

Настойліва рэкамендуецца выкарыстоўваць падыход на аснове белых спісаў, паколькі ён прасцейшы і надзейнейшы. Чорны спіс неабходна будзе абнаўляць кожны раз пры даданні патэнцыйна небяспечнага сістэмнага выкліку (ці небяспечнага сцяга/опцыі, калі яны знаходзяцца ў чорным спісе). Акрамя таго, часта можна змяніць уяўленне параметра, не змяняючы яго сутнасць і тым самым абыйсці абмежаванні чорнага спісу.

Для прыкладанняў на мове Go я распрацаваў спецыяльны інструмент, які суправаджае прыкладанне і збірае ўсе выклікі, учыненыя падчас выканання. Напрыклад, для наступнага прыкладання:

package main

import "fmt"

func main() {
	fmt.Println("test")
}

… запусцім gosystract так:

go install https://github.com/pjbgf/gosystract
gosystract --template='{{- range . }}{{printf ""%s",n" .Name}}{{- end}}' application-path

… і атрымаем наступны вынік:

"sched_yield",
"futex",
"write",
"mmap",
"exit_group",
"madvise",
"rt_sigprocmask",
"getpid",
"gettid",
"tgkill",
"rt_sigaction",
"read",
"getpgrp",
"arch_prctl",

Пакуль гэта толькі прыклад - падрабязнасці аб інструментары будуць далей.

Савет №6: Дазваляйце толькі тыя выклікі, якія вам сапраўды неабходныя, і блакуйце ўсе астатнія.

7. Закладзяце правільныя асновы (ці рыхтуйцеся да непрадбачаных паводзін)

Ядро будзе сачыць за выкананнем профіля незалежна ад таго, што вы ў ім прапісалі. Нават калі гэта не зусім тое, чаго хацелася б. Напрыклад, калі заблакаваць доступ да выклікаў накшталт exit або exit_group, кантэйнер не зможа правільна завяршыць працу і нават простая каманда тыпу echo hi падвесіць егаб на нявызначаны тэрмін. У выніку вы атрымаеце высокую загрузку CPU у кластары:

Seccomp у Kubernetes: 7 рэчаў, пра якія трэба ведаць з самага пачатку

У такіх выпадках на выручку можа прыйсці ўтыліта. strace - Яна пакажа, у чым можа заключацца праблема:

Seccomp у Kubernetes: 7 рэчаў, пра якія трэба ведаць з самага пачатку
sudo strace -c -p 9331

Пераканайцеся, што профілі змяшчаюць усе сістэмныя выклікі, патрэбныя з дадаткам падчас працы.

Савет №7: Уважліва ставіцеся да дробязяў і правярайце, што ўсе неабходныя сістэмныя выклікі ўключаны ў белы спіс.

На гэтым першая частка цыклу артыкулаў аб выкарыстанні seccomp у Kubernetes у духу SecDevOps падыходзіць да канца. У наступных частках мы пагаворым аб тым, чаму гэта важна і як аўтаматызаваць працэс.

PS ад перакладчыка

Чытайце таксама ў нашым блогу:

Крыніца: habr.com

Дадаць каментар