Exposing Services in Kubernetes

Support HackTricks

Hay diferentes formas de exponer servicios en Kubernetes para que tanto los puntos finales internos como los puntos finales externos puedan acceder a ellos. Esta configuración de Kubernetes es bastante crítica, ya que el administrador podría dar acceso a atacantes a servicios a los que no deberían poder acceder.

Enumeración Automática

Antes de comenzar a enumerar las formas en que K8s ofrece exponer servicios al público, sepa que si puede listar namespaces, servicios e ingresses, puede encontrar todo lo expuesto al público con:

kubectl get namespace -o custom-columns='NAME:.metadata.name' | grep -v NAME | while IFS='' read -r ns; do
echo "Namespace: $ns"
kubectl get service -n "$ns"
kubectl get ingress -n "$ns"
echo "=============================================="
echo ""
echo ""
done | grep -v "ClusterIP"
# Remove the last '| grep -v "ClusterIP"' to see also type ClusterIP

ClusterIP

Un servicio ClusterIP es el servicio predeterminado de Kubernetes. Te proporciona un servicio interno en tu clúster al que otras aplicaciones dentro de tu clúster pueden acceder. No hay acceso externo.

Sin embargo, esto se puede acceder utilizando el Proxy de Kubernetes:

kubectl proxy --port=8080

Ahora, puedes navegar a través de la API de Kubernetes para acceder a los servicios utilizando este esquema:

http://localhost:8080/api/v1/proxy/namespaces/<NAMESPACE>/services/<SERVICE-NAME>:<PORT-NAME>/

Por ejemplo, podrías usar la siguiente URL:

http://localhost:8080/api/v1/proxy/namespaces/default/services/my-internal-service:http/

para acceder a este servicio:

apiVersion: v1
kind: Service
metadata:
name: my-internal-service
spec:
selector:
app: my-app
type: ClusterIP
ports:
- name: http
port: 80
targetPort: 80
protocol: TCP

Este método requiere que ejecutes kubectl como un usuario autenticado.

Lista todos los ClusterIPs:

kubectl get services --all-namespaces -o=custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name,TYPE:.spec.type,CLUSTER-IP:.spec.clusterIP,PORT(S):.spec.ports[*].port,TARGETPORT(S):.spec.ports[*].targetPort,SELECTOR:.spec.selector' | grep ClusterIP

NodePort

Cuando se utiliza NodePort, se hace disponible un puerto designado en todos los Nodos (que representan las Máquinas Virtuales). El tráfico dirigido a este puerto específico se rutea sistemáticamente al servicio. Típicamente, este método no se recomienda debido a sus desventajas.

Lista todos los NodePorts:

kubectl get services --all-namespaces -o=custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name,TYPE:.spec.type,CLUSTER-IP:.spec.clusterIP,PORT(S):.spec.ports[*].port,NODEPORT(S):.spec.ports[*].nodePort,TARGETPORT(S):.spec.ports[*].targetPort,SELECTOR:.spec.selector' | grep NodePort

Un ejemplo de especificación de NodePort:

apiVersion: v1
kind: Service
metadata:
name: my-nodeport-service
spec:
selector:
app: my-app
type: NodePort
ports:
- name: http
port: 80
targetPort: 80
nodePort: 30036
protocol: TCP

Si no especificas el nodePort en el yaml (es el puerto que se abrirá) se utilizará un puerto en el rango 30000–32767.

LoadBalancer

Expone el Servicio externamente utilizando el balanceador de carga de un proveedor de nube. En GKE, esto iniciará un Network Load Balancer que te dará una única dirección IP que redirigirá todo el tráfico a tu servicio. En AWS, lanzará un Load Balancer.

Tienes que pagar por un LoadBalancer por cada servicio expuesto, lo que puede ser costoso.

Lista todos los LoadBalancers:

kubectl get services --all-namespaces -o=custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name,TYPE:.spec.type,CLUSTER-IP:.spec.clusterIP,EXTERNAL-IP:.status.loadBalancer.ingress[*],PORT(S):.spec.ports[*].port,NODEPORT(S):.spec.ports[*].nodePort,TARGETPORT(S):.spec.ports[*].targetPort,SELECTOR:.spec.selector' | grep LoadBalancer

IPs Externos

Los IPs externos son expuestos por servicios de tipo Load Balancers y generalmente se utilizan cuando se está utilizando un Load Balancer de un Proveedor de Nube externo.

Para encontrarlos, verifica los load balancers con valores en el campo EXTERNAL-IP.

El tráfico que ingresa al clúster con el IP externo (como IP de destino), en el puerto del Servicio, será enrutado a uno de los endpoints del Servicio. externalIPs no son gestionados por Kubernetes y son responsabilidad del administrador del clúster.

En la especificación del Servicio, externalIPs se pueden especificar junto con cualquiera de los ServiceTypes. En el ejemplo a continuación, "my-service" puede ser accedido por clientes en "80.11.12.10:80" (externalIP:port)

apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- name: http
protocol: TCP
port: 80
targetPort: 9376
externalIPs:
- 80.11.12.10

ExternalName

De la documentación: Los servicios de tipo ExternalName mapean un Servicio a un nombre DNS, no a un selector típico como my-service o cassandra. Especificas estos Servicios con el parámetro spec.externalName.

Esta definición de Servicio, por ejemplo, mapea el Servicio my-service en el espacio de nombres prod a my.database.example.com:

apiVersion: v1
kind: Service
metadata:
name: my-service
namespace: prod
spec:
type: ExternalName
externalName: my.database.example.com

Al buscar el host my-service.prod.svc.cluster.local, el Servicio DNS del clúster devuelve un registro CNAME con el valor my.database.example.com. Acceder a my-service funciona de la misma manera que otros Servicios, pero con la diferencia crucial de que la redirección ocurre a nivel de DNS en lugar de a través de proxy o reenvío.

Lista todos los ExternalNames:

kubectl get services --all-namespaces | grep ExternalName

Ingress

A diferencia de todos los ejemplos anteriores, Ingress NO es un tipo de servicio. En cambio, se sitúa frente a múltiples servicios y actúa como un “enrutador inteligente” o punto de entrada a tu clúster.

Puedes hacer muchas cosas diferentes con un Ingress, y hay muchos tipos de controladores de Ingress que tienen diferentes capacidades.

El controlador de ingress predeterminado de GKE iniciará un HTTP(S) Load Balancer para ti. Esto te permitirá hacer enrutamiento basado en rutas y subdominios a servicios de backend. Por ejemplo, puedes enviar todo en foo.yourdomain.com al servicio foo, y todo bajo la ruta yourdomain.com/bar/ al servicio bar.

El YAML para un objeto Ingress en GKE con un L7 HTTP Load Balancer podría verse así:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: my-ingress
spec:
backend:
serviceName: other
servicePort: 8080
rules:
- host: foo.mydomain.com
http:
paths:
- backend:
serviceName: foo
servicePort: 8080
- host: mydomain.com
http:
paths:
- path: /bar/*
backend:
serviceName: bar
servicePort: 8080

Lista todos los ingresses:

kubectl get ingresses --all-namespaces -o=custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name,RULES:spec.rules[*],STATUS:status'

Aunque en este caso es mejor obtener la información de cada uno uno por uno para leerla mejor:

kubectl get ingresses --all-namespaces -o=yaml

Referencias

Apoya a HackTricks

Last updated