Secretos
Todos los sistemas de red modernos necesitan tratar con la información delicada, como el nombre de usuario, contraseñas, claves SSH, etc., en la plataforma. Lo mismo se aplica a los pods en un entorno Kubernetes. Sin embargo, exponer esta información en las especificaciones de Pod como CLEARTEXT puede introducir problemas de seguridad y se necesita una herramienta o método para resolver el – problema al menos para evitar las credenciales de texto sin cifrar lo máximo posible.
El objeto Secrets Kubernetes se ha diseñado específicamente para – codificar todos los datos confidenciales y exponerlos a los pods de una manera controlada.
La definición oficial de los secretos de Kubernetes es:
"Un secreto es un objeto que contiene una pequeña cantidad de datos confidenciales, como una contraseña, un testigo o una clave. En cualquier otro caso, dicha información podría colocarse en una especificación Pod o en una imagen; colocarlo en un objeto secreto permite controlar mejor cómo se utiliza y reduce el riesgo de exposición accidental. "
Los usuarios pueden crear secretos y el sistema también crea secretos. Para usar un secreto, una Pod debe hacer referencia al secreto.
Hay muchos tipos diferentes de secretos, cada uno de ellos sirve a un caso de uso específico, y también hay muchos métodos para crear un secreto y muchas maneras distintas de hacer referencia a él en un conjunto Pod. Una explicación completa de los secretos queda fuera del ámbito de este libro, por lo que debe consultar la documentación oficial para obtener todos los detalles y realizar un seguimiento de todos los cambios actualizados.
En este caso’, se examinarán algunos tipos de secretos comúnmente utilizados. También aprenderá varios métodos para crear un secreto y cómo hacer referencia a él en sus pods. Una vez que llegue al final de la sección, debe comprender las ventajas principales de un objeto de secretos Kubernetes y cómo puede contribuir a mejorar la seguridad de su sistema.
Comencemos’con unos cuantos términos secretos:
Opaco: Este tipo de secreto puede contener pares de clave/valor arbitrarios, por lo que se trata como datos no estructurados desde la perspectiva de Kubernetes’ . Todos los demás tipos de secretos tienen contenido constante.
Kubernetes.io/Dockerconfigjson: Este tipo de secreto se utiliza para autenticar con un registro de contenedor privado (por ejemplo, un servidor Juniper) para extraer su propia imagen privada.
TLS: Un secreto TLS contiene una clave privada y un certificado de TLS. Se utiliza para asegurar una entrada. Verá un ejemplo de un ingreso con un secreto TLS en el capítulo 4.
Kubernetes.io/service-account-token: Cuando los procesos que se ejecutan en contenedores de un conjunto Pod tienen acceso al servidor API, deben ser autenticados como una cuenta concreta (por ejemplo, de forma predeterminada, la cuenta). Una cuenta asociada con un conjunto Pod se denomina cuenta de servicio. Kubernetes.io/service-account-token tipo de secreto contiene información acerca de la cuenta de servicio de Kubernetes. En este’libro, logramos no haber elaborado este tipo de secreto y cuenta de servicio.
Secreto opaco: El secreto del tipo opaco representa datos – arbitrarios del usuario que normalmente deseará colocar algún tipo de datos confidenciales en secreto, por ejemplo, nombre de usuario, contraseña, NIP de seguridad, etc., casi todo lo que cree es sensible y desea llevar a la caja Pod.
Definir secreto opaco
Primero, para que nuestros datos confidenciales parezcan menos confidenciales, permita’que los pueda codificar con la herramienta Base64:
$ echo -n 'username1' | base64 dXNlcm5hbWUx $ echo -n 'password1' | base64 cGFzc3dvcmQx
A continuación, coloque la versión codificada de los datos en un archivo YAML de definición de secreto:
apiVersion: v1 kind: Secret metadata: name: secret-opaque type: Opaque data: username: dXNlcm5hbWUx password: cGFzc3dvcmQx
De manera alternativa, puede definir el mismo secreto directamente desde kubectl CLI, con la opción--from-literal:
kubectl create secret generic secret-opaque \ --from-literal=username='username1' \ --from-literal=password='password1'
En cualquier caso, se generará un secreto:
$ kubectl get secrets NAME TYPE DATA AGE secret-opaque Opaque 2 8s $ kubectl get secrets secret-opaque -o yaml apiVersion: v1 data: password: cGFzc3dvcmQx username: dXNlcm5hbWUx kind: Secret metadata: annotations: kubectl.kubernetes.io/last-applied-configuration: | {"apiVersion":"v1","data":{"password":"cGFzc3dvcmQx","username":"dXNlcm5hbWUx"},"kind":"Secret","metadata":{"annotations":{},"name" :"secret-opaque","namespace":"ns-user-1"},"type":"Opaque"} creationTimestamp: 2019-08-22T22:51:18Z name: secret-opaque namespace: ns-user-1 resourceVersion: "885702" selfLink: /api/v1/namespaces/ns-user-1/secrets/secret-opaque uid: 5a78d9d4-c52f-11e9-90a3-0050569e6cfc type: Opaque
Consultar secreto opaco
A continuación, tendrá que utilizar el secreto en una caja Pod-y la información de usuario contenida en el secreto se transferirá al conjunto Pod. Como se mencionó anteriormente, existen varias maneras de hacer referencia al secreto opaco en una caja Pod y, en consecuencia, el resultado será diferente.
Normalmente, la información de usuario procedente de un secreto puede aparecer en un contenedor de una de estas formas:
Archivos
Variables de entorno
Ahora,’veamos cómo se utiliza el secreto para generar variables de entorno en un contenedor:
#pod-webserver-do-secret.yaml apiVersion: v1 kind: Pod metadata: name: contrail-webserver-secret labels: app: webserver spec: containers: - name: contrail-webserver-secret image: contrailk8sdayone/contrail-webserver #envFrom: #- secretRef: # name: test-secret env: - name: SECRET_USERNAME valueFrom: secretKeyRef: name: secret-opaque key: username - name: SECRET_PASSWORD valueFrom: secretKeyRef: name: secret-opaque key: password
Generar el conjunto Pod y el contenedor desde este archivo YAML:
$ kubectl apply -f pod/pod-webserver-do-secret.yaml pod/contrail-webserver-secret created
Log en el contenedor y verifica las variables de entorno generadas:
$ kubectl exec -it contrail-webserver-secret -- printenv | grep SECRET SECRET_USERNAME=username1 SECRET_PASSWORD=password1
Los datos confidenciales originales codificados con base64 se encuentran ahora en el contenedor!
Dockerconfigjson secreto
El secreto dockerconfigjson, como lo indica el nombre, transporta la información de credenciales de cuenta de Dock que normalmente está almacenada en el archivo. Docker/config. JSON. La imagen en un conjunto pod de Kubernetes puede señalar a un registro de contenedor privado. En ese caso, Kubernetes necesita autenticarlo con ese registro para poder extraer la imagen. El tipo de secreto de dockerconfigjson está diseñado para este propósito.
Datos de credenciales de Docker
El método más directo para crear un tipo de kubernetes.io/dockerconfigjson secreto es proporcionar información de inicio de sesión directamente con el comando kubectl y dejar que genere el secreto:
$ kubectl create secret docker-registry secret-jnpr1 \ --docker-server=hub.juniper.net \ --docker-username=JNPR-FieldUser213 \ --docker-password=CLJd2kpMsVc9zrAuTFPn secret/secret-jnpr created
Compruebe la creación de la clave secreta:
$ kubectl get secrets NAME TYPE DATA AGE secret-jnpr kubernetes.io/dockerconfigjson 1 6s #<--- default-token-hkkzr kubernetes.io/service-account-token 3 62d
Solo la primera línea de la salida es el secreto que acaba de crear. La segunda línea es un kubernetes.io/service-account-token tipo de secreto que el sistema Kubernetes crea automáticamente cuando la configuración del contrail está en funcionamiento.
Ahora, examine los detalles del secreto:
$ kubectl get secrets secret-jnpr -o yaml apiVersion: v1 data: .dockerconfigjson: eyJhdXRocyI6eyJodWIuanVuaXBlci5uZXQvc2...<snipped>... kind: Secret metadata: creationTimestamp: 2019-08-14T05:58:48Z name: secret-jnpr namespace: ns-user-1 resourceVersion: "870370" selfLink: /api/v1/namespaces/ns-user-1/secrets/secret-jnpr uid: 9561cdc3-be58-11e9-9367-0050569e6cfc type: kubernetes.io/dockerconfigjson
No es sorprendente que’no vea ninguna información confidencial en forma de texto no cifrado. Hay una parte de datos del resultado en la que puede ver una cadena muy larga como el valor de la clave: dockerconfigjson. Parece que su aspecto se ha transformado a partir de los datos originales, pero al menos no incluye – información confidencial ya más después de que todo el propósito del uso de un secreto sea mejorar la seguridad del sistema.
Sin embargo, la transformación se realiza mediante codificación, no cifrado, por lo que todavía hay una forma de recuperar manualmente la información confidencial original: solo hay que canalizar el valor de Key. dockerconfigjson a la herramienta Base64, y volver a ver la información original sobre el nombre de usuario y la contraseña:
$ echo "eyJhdXRocyI6eyJodWIuanVua..." | base64 -d | python -mjson.tool { "auths": { "hub.juniper.net": { "auth": "Sj5QUi1GaWVsZFVzZXIyMTM6Q0xKZDJqcE1zVmM5enJBdVRGUG4=", "password": "CLJd2kpMsVc9zrAuTFPn", "username": "JNPR-FieldUser213" } } }
Algunas de las características más destacadas de este resultado son:
La herramienta Python-mjson. se usa para dar formato a los datos JSON descodificados antes de mostrarlos en el terminal.
Hay un par clave-valor de autenticación. Es el testigo generado basándose en la información de autenticación que dio (nombre de usuario y contraseña).
Más adelante, cuando esté equipado con este secreto, un conjunto Pod utilizará este token en lugar del nombre de usuario y la contraseña para autenticarse en el hub.juniper.net del registro privado del acoplador con el fin de extraer una imagen del acoplador.
Esta’es otra forma de descodificar los datos directamente del objeto secreto:
$ kubectl get secret secret-jnpr1 \ --output="jsonpath={.data.\.dockerconfigjson}" \ | base64 --decode | python -mjson.tool { "auths": { "hub.juniper.net/security": { "auth": "Sj5QUi1GaWVsZFVzZXIyMTM6Q0xKZDJqcE1zVmM5enJBdVRGUG4=", "password": "CLJd2kpMsVc9zrAuTFPn", "username": "JNPR-FieldUser213" } } }
El servicio --output=xxxx
opción filtra la kubectl obtener salida, por lo que solo se muestra el valor de. dockerconfigjson en datos. A continuación, el valor se canaliza en Base64 con la opción--Decode (alias de-d) para obtenerlo descodificado.
Un secreto del registro-acoplador creado manualmente, como esto, solo funcionará con un registro privado único. Para admitir varios registros de contenedor privado, puede crear un secreto a partir del archivo de credenciales del acoplador.
Archivo de credenciales de Docker (~/. Docker/config. JSON)
Como el nombre de la clave. dockerconfigjson en el secreto que creamos indica, cumple una función similar a la del archivo de configuración del acoplador: . Docker/config. JSON. En realidad, puede generar el secreto directamente desde el archivo de configuración del acoplador.
Para generar la información de credenciales del acoplador, compruebe primero el archivo de configuración del acoplador:
$ cat .docker/config.json { ...... "auths": {}, ...... }
No’hay nada aquí. Según el uso de la configuración, es posible que vea resultados diferentes, pero la cuestión es que este archivo de configuración de Docker se actualizará automáticamente cada vez que inicie sesión en un nuevo registro:
$ cat mydockerpass.txt | \ docker login hub.juniper.net \ --username JNPR-FieldUser213 \ --password-stdin Login Succeeded
El archivo mydockerpass. txt es la contraseña de inicio de sesión del nombre de usuario JNPR-FieldUser213. Guardar la contraseña en un archivo y, a continuación, canalizarla al comando de inicio de sesión del acoplador con la opción--Password-stdin tiene la ventaja de que no expone la contraseña CLEARTEXT en el historial del shell.
Si desea que pueda escribir la contraseña directamente, recibirá una advertencia descriptiva de que esto no es seguro.
$ docker login hub.juniper.net --username XXXXXXXXXXXXXXX --password XXXXXXXXXXXXXX WARNING! Using --password via the CLI is insecure. Use --password-stdin. Login Succeeded
Ahora, la información de credenciales de Docker se genera en el config.json
archiva
$ cat .docker/config.json { ...... "auths": { #<--- "hub.juniper.net": { "auth": "Sj5QUi1GaWVsZFVzZXIyMTM6Q0xKZDJqcE1zVmM5enJBdVRGUG4=" } }, ...... }
El proceso de inicio de sesión crea o actualiza una config.json
archivo que contiene el token de autorización. ’Crear un secreto a partir de la .docker/config.json
archiva
$ kubectl create secret generic secret-jnpr2 \ --from-file=.dockerconfigjson=/root/.docker/config.json \ --type=kubernetes.io/dockerconfigjson secret/secret-jnpr2 created $ kubectl get secrets NAME TYPE DATA AGE secret-jnpr2 kubernetes.io/dockerconfigjson 1 8s #<--- default-token- hkkzr kubernetes.io/service-account-token 3 63d secret-jnpr kubernetes.io/dockerconfigjson 1 26m $ kubectl get secrets secret-jnpr2 -o yaml apiVersion: v1 data: .dockerconfigjson: ewoJImF1dGhzIjoIlNrNVFVaTFHYVdWc1pGVnpaWEl5TVRNNlEweEtaREpxY0UxelZtTTVlbkpCZ FZSR1VHND0iCgkJfQoJfSwKCSJIdHRwSGVhZGVycyI6IHsKCQkiVXNlci1BZ2VudCI6ICJEb2NrZXItQ2xpZW50LzE4LjAzLjE tY2UgKGxpbnV4KSIKCX0sCgkiZGV0YWNoS2V5cyI6ICJjdHJsLUAiCn0= kind: Secret metadata: creationTimestamp: 2019-08-15T07:35:25Z name: csrx-secret-dr2 namespace: ns-user-1 resourceVersion: "878490" selfLink: /api/v1/namespaces/ns-user-1/secrets/secret-jnpr2 uid: 3efc3bd8-bf2f-11e9-bb2a-0050569e6cfc type: kubernetes.io/dockerconfigjson $ kubectl get secret secret-jnpr2 --output="jsonpath={.data.\.dockerconfigjson}" | base64 --decode { ...... "auths": { "hub.juniper.net": { "auth": "Sj5QUi1GaWVsZFVzZXIyMTM6Q0xKZDJqcE1zVmM5enJBdVRGUG4=" } }, ...... }
Archivo YAML
También puede crear un secreto directamente desde un archivo YAML de la misma manera que crea otros objetos como servicio o ingreso.
Para codificar manualmente el contenido de la .docker/config.json
archiva
$ cat .docker/config.json | base64 ewoJImF1dGhzIjogewoJCSJodWIuanVuaXBlci5uZXQiOiB7CgkJCSJhdXRoIjogIlNrNVFVaTFH YVdWc1pGVnpaWEl5TVRNNlEweEtaREpxY0UxelZtTTVlbkpCZFZSR1VHND0iCgkJfQoJfSwKCSJI dHRwSGVhZGVycyI6IHsKCQkiVXNlci1BZ2VudCI6ICJEb2NrZXItQ2xpZW50LzE4LjAzLjEtY2Ug KGxpbnV4KSIKCX0sCgkiZGV0YWNoS2V5cyI6ICJjdUAiCn0=
A continuación, ponga el valor codificado en Base64 de la .docker/config.json
archivar como datos en la parte inferior del archivo YAML:
#secret-jnpr.yaml apiVersion: v1 kind: Secret type: kubernetes.io/dockerconfigjson metadata: name: secret-jnpr3 namespace: ns-user-1 data: .dockerconfigjson: ewoJImF1dGhzIjogewoJCSJodW...... $ kubectl apply -f secret-jnpr.yaml secret/secret-jnpr3 created $ kubectl get secrets NAME TYPE DATA AGE default-token-hkkzr kubernetes.io/service-account-token 3 64d secret-jnpr1 kubernetes.io/dockerconfigjson 1 9s secret-jnpr2 kubernetes.io/dockerconfigjson 1 6m12s secret-jnpr3 kubernetes.io/dockerconfigjson 1 78s
Tenga en cuenta que en Base64 se trata la codificación, en lugar del – cifrado, se considera lo mismo que el texto sin formato. Por lo tanto, compartir este archivo pone en peligro el secreto.
Consulte Secret en Pod
Después de crear un secreto, se puede hacer referencia a él mediante un conjunto Pod/RC o implementación para poder extraer una imagen del registro privado. Existen muchas formas de hacer referencia a los secretos. En esta sección se examinará el uso de imagePullSecrets bajo Pod spec para referirse al secreto.
Una imagePullSecret es una forma de pasar un secreto que contiene una contraseña del registro de imágenes del acoplador (u otra) a kubelet para que pueda extraer una imagen privada en nombre de su conjunto Pod.
Crear un conjunto Pod que extraiga el contenedor de cSRX Juniper del repositorio privado:
apiVersion: v1 kind: Pod metadata: name: csrx-jnpr labels: app: csrx annotations: k8s.v1.cni.cncf.io/networks: '[ { "name": "vn-left-1" }, { "name": "vn-right-1" } ]' spec: containers: #- name: csrx # image: csrx - name: csrx image: hub.juniper.net/security/csrx:18.1R1.9 ports: - containerPort: 22 #imagePullPolicy: Never imagePullPolicy: IfNotPresent stdin: true tty: true securityContext: privileged: true imagePullSecrets: - name: secret-jnpr
Ahora, genere la caja Pod:
$ kubectl apply -f csrx/csrx-with-secret.yaml pod/csrx-jnpr created
El cSRX está activo y está en funcionamiento:
$ kubectl get pod NAME READY STATUS RESTARTS AGE csrx-jnpr 1/1 Running 0 20h
Y en segundo plano, el conjunto Pod se autentica a sí mismo en el registro privado, extrae la imagen e inicia el contenedor cSRX:
$ kubectl describe pod csrx ...... Events: 19h Normal Scheduled Pod Successfully assigned ns-user-1/csrx to cent333 19h Normal Pulling Pod pulling image "hub.juniper.net/security/csrx:18.1R1.9" 19h Normal Pulled Pod Successfully pulled image "hub.juniper.net/security/csrx:18.1R1.9" 19h Normal Created Pod Created container 19h Normal Started Pod Started container
Como vimos a partir de nuestra prueba, los objetos secretos se crean independientemente de los pods, y la inspección de las especificaciones del objeto no proporciona la información confidencial directamente en la pantalla.
Los secretos no se escriben en el disco, sino que se almacenan en un sistema de archivos tmpfs, únicamente en los nodos que los necesitan. Además, los secretos se eliminan cuando se elimina la caja Pod que depende de ellos.
En la mayoría de las distribuciones de Kubernetes nativas, la comunicación entre los usuarios y el servidor de la API está protegida por SSL/TLS. Por lo tanto, los secretos transmitidos a través de estos canales están protegidos adecuadamente.
Cualquier Pod especificado no tiene acceso a los secretos utilizados por otro Pod, lo que facilita la encapsulación de datos importantes en distintos pods. Cada contenedor de un conjunto Pod debe solicitar un volumen de secreto en su volumeMounts para que sea visible dentro del contenedor. Esta característica puede usarse para construir particiones de seguridad en el nivel Pod.