Seccomp en Kubernetes: 7 cousas que debes saber desde o principio

Nota. transl.: Presentámosche á túa atención a tradución dun artigo dun enxeñeiro senior de seguridade de aplicacións da empresa británica ASOS.com. Con ela comeza unha serie de publicacións dedicadas a mellorar a seguridade en Kubernetes mediante o uso de seccomp. Se aos lectores lles gusta a introdución, seguiremos ao autor e seguiremos cos seus futuros materiais sobre este tema.

Seccomp en Kubernetes: 7 cousas que debes saber desde o principio

Este artigo é o primeiro dunha serie de publicacións sobre como crear perfís seccomp no espírito de SecDevOps, sen recorrer á maxia e á bruxería. Na parte XNUMX, cubrirei os conceptos básicos e os detalles internos da implementación de seccomp en Kubernetes.

O ecosistema de Kubernetes ofrece unha gran variedade de formas de protexer e illar os contedores. O artigo trata sobre o modo informático seguro, tamén coñecido como seccomp. A súa esencia é filtrar as chamadas do sistema dispoñibles para a súa execución por contedores.

Por que é importante? Un contedor é só un proceso que se executa nunha máquina específica. E usa o núcleo igual que outras aplicacións. Se os contedores puidesen realizar chamadas ao sistema, moi pronto o malware aproveitaría isto para evitar o illamento dos contedores e afectar a outras aplicacións: interceptar información, cambiar a configuración do sistema, etc.

Os perfís seccomp definen que chamadas ao sistema se deben permitir ou desactivar. O tempo de execución do contedor actívaos cando se inicia para que o núcleo poida supervisar a súa execución. Usar tales perfís permíteche limitar o vector de ataque e reducir os danos se algún programa dentro do contedor (é dicir, as túas dependencias ou as súas dependencias) comeza a facer algo que non está permitido.

Chegando ao básico

O perfil básico de seccomp inclúe tres elementos: defaultAction, architectures (Ou archMap) E 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"
        }
    ]
}

(medio-básico-seccomp.json)

defaultAction determina o destino predeterminado de calquera chamada ao sistema non especificada na sección syscalls. Para facilitar as cousas, centrámonos nos dous valores principais que se utilizarán:

  • SCMP_ACT_ERRNO — bloquea a execución dunha chamada ao sistema,
  • SCMP_ACT_ALLOW - permite.

Na sección architectures listanse as arquitecturas de destino. Isto é importante porque o propio filtro, aplicado a nivel do núcleo, depende dos identificadores de chamadas do sistema, e non dos seus nomes especificados no perfil. O tempo de execución do contedor relacionaraos cos identificadores antes de utilizalos. A idea é que as chamadas ao sistema poden ter ID completamente diferentes dependendo da arquitectura do sistema. Por exemplo, chamada ao sistema recvfrom (usado para recibir información do socket) ten ID = 64 en sistemas x64 e ID = 517 en x86. Aquí podes atopar unha lista de todas as chamadas de sistema para arquitecturas x86-x64.

Na sección syscalls enumera todas as chamadas do sistema e especifica que facer con elas. Por exemplo, pode crear unha lista branca configurando defaultAction en SCMP_ACT_ERRNO, e chamadas na sección syscalls asignar SCMP_ACT_ALLOW. Así, só permite chamadas especificadas na sección syscalls, e prohibir todos os demais. Para a lista negra debes cambiar os valores defaultAction e accións ao contrario.

Agora deberíamos dicir algunhas palabras sobre matices que non son tan obvios. Teña en conta que as recomendacións que aparecen a continuación supoñen que está a implementar unha liña de aplicacións empresariais en Kubernetes e quere que se executen coa menor cantidade de privilexios posibles.

1. AllowPrivilegeEscalation=falso

В securityContext o recipiente ten un parámetro AllowPrivilegeEscalation. Se está instalado en false, os contedores comezarán por (on) pouco no_new_priv. O significado deste parámetro é obvio polo nome: impide que o contedor inicie novos procesos con máis privilexios dos que ten.

Un efecto secundario de que esta opción se configure como true (predeterminado) é que o tempo de execución do contedor aplica o perfil seccomp ao comezo do proceso de inicio. Así, todas as chamadas de sistema necesarias para executar procesos internos de execución (por exemplo, establecer ID de usuario/grupo, eliminar certas capacidades) deben estar habilitadas no perfil.

A un recipiente que fai cousas triviais echo hi, serán necesarios os seguintes permisos:

{
    "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)

...en lugar destes:

{
    "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)

Pero de novo, por que é un problema? Persoalmente, evitaría poñer en lista branca as seguintes chamadas do sistema (a non ser que haxa unha necesidade real delas): capset, set_tid_address, setgid, setgroups и setuid. Non obstante, o verdadeiro desafío é que ao permitir procesos sobre os que non tes absolutamente ningún control, estás vinculando os perfís á implementación do tempo de execución do contedor. Noutras palabras, un día podes descubrir que despois de actualizar o ambiente de execución do contedor (xa sexa por ti ou, máis probablemente, polo provedor de servizos na nube), os contedores deixan de funcionar de súpeto.

Consello # 1: Executar contedores con AllowPrivilegeEscaltion=false. Isto reducirá o tamaño dos perfís seccomp e fará que sexan menos sensibles aos cambios no ambiente de execución do contedor.

2. Establecer perfís seccomp a nivel de contedores

O perfil seccomp pódese configurar no nivel de pod:

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

...ou a nivel de contedores:

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

Ten en conta que a sintaxe anterior cambiará cando Kubernetes seccomp converterase en GA (Este evento espérase na próxima versión de Kubernetes - 1.18 - aprox. transl.).

Poucas persoas saben que Kubernetes sempre tivo erroo que provocou que se aplicasen perfís seccomp contedor de pausa. O ambiente de execución compensa parcialmente esta deficiencia, pero este contedor non desaparece dos pods, xa que se utiliza para configurar a súa infraestrutura.

O problema é que este recipiente sempre comeza por AllowPrivilegeEscalation=true, dando lugar aos problemas expostos no parágrafo 1, e isto non pode ser modificado.

Ao usar perfís seccomp a nivel de contenedor, evitas esta trampa e podes crear un perfil adaptado a un contenedor específico. Isto terá que facerse ata que os desenvolvedores corrixan o erro e a nova versión (quizais 1.18?) estea dispoñible para todos.

Consello # 2: Establece perfís seccomp a nivel de contedores.

Nun sentido práctico, esta regra adoita servir como resposta universal á pregunta: "Por que funciona o meu perfil seccomp con docker runpero non funciona despois da implantación nun clúster de Kubernetes?

3. Use o tempo de execución/predeterminado só como último recurso

Kubernetes ten dúas opcións para os perfís integrados: runtime/default и docker/default. Ambos están implementados polo tempo de execución do contenedor, non por Kubernetes. Polo tanto, poden diferir segundo o ambiente de execución utilizado e a súa versión.

Noutras palabras, como resultado do cambio de tempo de execución, o contedor pode ter acceso a un conxunto diferente de chamadas ao sistema, que pode utilizar ou non. A maioría dos tempos de execución úsanse Implementación de Docker. Se queres usar este perfil, asegúrate de que é axeitado para ti.

Perfil docker/default quedou en desuso desde Kubernetes 1.11, así que evite usalo.

Na miña opinión, perfil runtime/default perfectamente axeitado para o propósito para o que foi creado: protexer aos usuarios dos riscos asociados á execución dun comando docker run nos seus coches. Non obstante, cando se trata de aplicacións empresariais que se executan en clústeres de Kubernetes, atreveríame a argumentar que tal perfil é demasiado aberto e que os desenvolvedores deberían centrarse en crear perfís para as súas aplicacións (ou tipos de aplicacións).

Consello # 3: Crea perfís seccomp para aplicacións específicas. Se isto non é posible, cree perfís para tipos de aplicación, por exemplo, cree un perfil avanzado que inclúa todas as API web da aplicación Golang. Use o tempo de execución/predeterminado só como último recurso.

En próximas publicacións, explicarei como crear perfís seccomp inspirados en SecDevOps, automatizalos e probalos en canalizacións. Noutras palabras, non terás escusa para non actualizar a perfís específicos da aplicación.

4. Non confinado NON é unha opción.

De primeira auditoría de seguridade de Kubernetes resultou que por defecto seccomp desactivado. Isto significa que se non configura PodSecurityPolicy, que o habilitará no clúster, funcionarán todos os pods para os que non estea definido o perfil seccomp seccomp=unconfined.

Operar neste modo significa que se perde toda unha capa de illamento que protexe o clúster. Este enfoque non é recomendado polos expertos en seguridade.

Consello # 4: Non se debería executar ningún contenedor do clúster seccomp=unconfined, especialmente en ambientes de produción.

5. "Modo de auditoría"

Este punto non é exclusivo de Kubernetes, pero aínda cae na categoría de "cousas que debes saber antes de comezar".

Como ocorre, a creación de perfís seccomp sempre foi un reto e depende en gran medida da proba e erro. O caso é que os usuarios simplemente non teñen a oportunidade de probalos en contornos de produción sen arriscarse a "soltar" a aplicación.

Despois do lanzamento do kernel Linux 4.14, fíxose posible executar partes dun perfil en modo auditoría, gravando información sobre todas as chamadas do sistema en syslog, pero sen bloquealas. Podes activar este modo usando o parámetro SCMT_ACT_LOG:

SCMP_ACT_LOG: seccomp non afectará ao fío que fai a chamada ao sistema se non coincide con ningunha regra do filtro, pero rexistrarase a información sobre a chamada ao sistema.

Aquí tes unha estratexia típica para usar esta función:

  1. Permitir chamadas ao sistema que sexan necesarias.
  2. Bloquear chamadas do sistema que sabe que non serán útiles.
  3. Grava información sobre todas as outras chamadas no rexistro.

Un exemplo simplificado é o seguinte:

{
    "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"
        }
    ]
}

(medio-mixto-seccomp.json)

Pero lembra que cómpre bloquear todas as chamadas que sabes que non se utilizarán e que poden danar o clúster. Unha boa base para elaborar unha lista é a oficial Documentación de Docker. Explica en detalle cales son as chamadas do sistema bloqueadas no perfil predeterminado e por que.

Non obstante, hai unha trampa. Aínda que SCMT_ACT_LOG soportado polo núcleo de Linux desde finais de 2017, entrou no ecosistema de Kubernetes só hai relativamente pouco tempo. Polo tanto, para usar este método necesitará un núcleo Linux 4.14 e unha versión runC non inferior v1.0.0-rc9.

Consello # 5: Pódese crear un perfil de modo de auditoría para probar en produción combinando listas brancas e negras, e pódense rexistrar todas as excepcións.

6. Usa listas brancas

A inclusión na lista branca require un esforzo adicional porque tes que identificar todas as chamadas que poida necesitar a aplicación, pero este enfoque mellora moito a seguridade:

É moi recomendable utilizar o enfoque da lista branca xa que é máis sinxelo e fiable. A lista negra terá que actualizarse sempre que se engada unha chamada ao sistema potencialmente perigosa (ou unha bandeira/opción perigosa se está na lista negra). Ademais, moitas veces é posible cambiar a representación dun parámetro sen cambiar a súa esencia e, así, evitar as restricións da lista negra.

Para as aplicacións Go, desenvolvín unha ferramenta especial que acompaña á aplicación e recolle todas as chamadas realizadas durante a execución. Por exemplo, para a seguinte aplicación:

package main

import "fmt"

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

... imos lanzar gosystract así:

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

... e obtemos o seguinte resultado:

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

Polo momento, este é só un exemplo; seguirán máis detalles sobre as ferramentas.

Consello # 6: Permite só aquelas chamadas que realmente necesitas e bloquea todas as demais.

7. Pon as bases correctas (ou prepárate para un comportamento inesperado)

O núcleo aplicará o perfil independentemente do que escribas nel. Aínda que non sexa exactamente o que querías. Por exemplo, se bloqueas o acceso a chamadas como exit ou exit_group, o contedor non poderá apagarse correctamente e mesmo un simple comando como echo hi colgarlleo por tempo indefinido. Como resultado, obterás un alto uso da CPU no clúster:

Seccomp en Kubernetes: 7 cousas que debes saber desde o principio

Nestes casos, unha utilidade pode acudir ao rescate strace - mostrará cal pode ser o problema:

Seccomp en Kubernetes: 7 cousas que debes saber desde o principio
sudo strace -c -p 9331

Asegúrese de que os perfís conteñan todas as chamadas ao sistema que a aplicación precisa no tempo de execución.

Consello # 7: Preste atención aos detalles e asegúrese de que todas as chamadas do sistema necesarias estean na lista branca.

Conclúe así a primeira parte dunha serie de artigos sobre o uso de seccomp en Kubernetes no espírito de SecDevOps. Nas seguintes partes imos falar sobre por que isto é importante e como automatizar o proceso.

PS do tradutor

Lea tamén no noso blog:

Fonte: www.habr.com

Engadir un comentario