This commit is contained in:
Carlos Polop
2026-07-09 11:11:50 +02:00
parent b055d05cef
commit b295855824
12 changed files with 249 additions and 19 deletions
@@ -78,7 +78,7 @@ users:
### From AWS to Kubernetes ### From AWS to Kubernetes
The **creator** of the **EKS cluster** is **ALWAYS** going to be able to get into the kubernetes cluster part of the group **`system:masters`** (k8s admin). At the time of this writing there is **no direct way** to find **who created** the cluster (you can check CloudTrail). And the is **no way** to **remove** that **privilege**. Historically, the **creator** of an **EKS cluster** received hidden Kubernetes admin access that was not visible in `aws-auth`. In current EKS clusters, this depends on the cluster access configuration. `bootstrapClusterCreatorAdminPermissions` controls whether the creator is added as a cluster-admin access entry during creation, and EKS access entries make that admin path visible and revocable through the EKS API. Older clusters or clusters that still rely on `aws-auth` might still have legacy creator behavior, so confirm the `accessConfig`, list access entries, and review CloudTrail instead of assuming the creator always has unremovable `system:masters`.
#### Abusing configmap #### Abusing configmap
@@ -93,7 +93,7 @@ Check also[ **this awesome**](https://blog.lightspin.io/exploiting-eks-authentic
#### Abusing Access Entries #### Abusing Access Entries
AWS implementes an additional way to grant IAM users access to the Kubernetes cluster through access entries. If you have the `eks:CreateAccessEntry` and `eks:AssociateAccessPolicy` permissions, you may also be able to assign a Kubernetes administrator role to either your user or a specific rol. AWS implements an additional way to grant IAM users access to the Kubernetes cluster through access entries. If you have the `eks:CreateAccessEntry` and `eks:AssociateAccessPolicy` permissions, you may also be able to assign a Kubernetes administrator role to either your user or a specific role.
First, **create an access entry for your user or role**: First, **create an access entry for your user or role**:
@@ -182,4 +182,3 @@ So, if an **attacker compromises a cluster using fargate** and **removes all the
@@ -88,10 +88,26 @@ kubectl get serviceaccounts -A -o yaml | grep -n 'iam.gke.io' -B 5 -A 8
kubectl get pods -A -o custom-columns='NS:.metadata.namespace,NAME:.metadata.name,SA:.spec.serviceAccountName,NODE:.spec.nodeName' kubectl get pods -A -o custom-columns='NS:.metadata.namespace,NAME:.metadata.name,SA:.spec.serviceAccountName,NODE:.spec.nodeName'
``` ```
If a service account has the annotation `iam.gke.io/gcp-service-account`, review the IAM service account policy for `roles/iam.workloadIdentityUser` grants to Kubernetes service account principals. Also check IAM allow policies for direct workload identity principals or broad principal sets. If a service account has the annotation `iam.gke.io/gcp-service-account`, review the IAM service account policy for `roles/iam.workloadIdentityUser` grants to Kubernetes service account principals. Also check IAM allow policies for direct workload identity principals or broad `principalSet://` grants, such as namespace-wide or cluster-wide workload access. The annotation `iam.gke.io/credential-quota-project` only moves IAM Service Account Credentials API quota to another project; the workload principal still needs `serviceusage.services.use` on that quota project and separate IAM access to the target resource.
Metadata access depends on cluster mode, node pool configuration, and workload settings. Do not assume every pod can steal the node service account. In Workload Identity-enabled environments, ordinary pods should use the GKE metadata server to obtain the workload identity intended for their Kubernetes service account. Node compromise, `hostNetwork` pods in some Standard configurations, and legacy node metadata exposure can still change the blast radius, so verify the actual node pool metadata mode, node service account, OAuth scopes, and pod placement. Metadata access depends on cluster mode, node pool configuration, and workload settings. Do not assume every pod can steal the node service account. In Workload Identity-enabled environments, ordinary pods should use the GKE metadata server to obtain the workload identity intended for their Kubernetes service account. Node compromise, `hostNetwork` pods in some Standard configurations, and legacy node metadata exposure can still change the blast radius, so verify the actual node pool metadata mode, node service account, OAuth scopes, and pod placement.
If a Workload Identity-enabled pod cannot get a token, also check NetworkPolicy egress before assuming the IAM binding is wrong. GKE Standard clusters using NetworkPolicy must allow the metadata-server path required by the cluster version and dataplane, and Dataplane V2 uses the `169.254.169.254` path for metadata-server access.
### Autopilot privileged workload allowlists
GKE Autopilot blocks most privileged workloads by default, but approved exceptions can exist. Review privileged admission settings, `AllowlistSynchronizer` objects, and installed `WorkloadAllowlist` objects before assuming a privileged pod is impossible:
```bash
gcloud container clusters describe <cluster> --region <region> \
--format='yaml(autopilot,privilegedAdmissionConfig,clusterPolicyConfig)'
kubectl get allowlistsynchronizers.auto.gke.io -A -o yaml
kubectl get workloadallowlists.auto.gke.io -A -o yaml
```
Allowlist paths can be GKE-owned (`gke://...`) or customer-owned Cloud Storage paths (`gs://...`). Wildcards and broad bucket paths increase the blast radius because future allowlist files under that path might become valid for the cluster. When a `WorkloadAllowlist` is installed, compare its exemptions and matching criteria to the pod spec, especially image digests, host namespaces, writable hostPath mounts, host ports, Linux capabilities, and whether `autopilot.gke.io/no-connect` prevents `exec` access to the privileged workload.
### TLS Boostrap Privilege Escalation ### TLS Boostrap Privilege Escalation
Initially this privilege escalation technique allowed to **privesc inside the GKE cluster** effectively allowing an attacker to **fully compromise it**. Initially this privilege escalation technique allowed to **privesc inside the GKE cluster** effectively allowing an attacker to **fully compromise it**.
@@ -120,5 +136,3 @@ curl -v -k http://10.124.200.1:10255/pods
Even if the API **doesn't allow to modify resources**, it could be possible to find **sensitive information** in the response. The endpoint /pods was found using [**Kiterunner**](https://github.com/assetnote/kiterunner). Even if the API **doesn't allow to modify resources**, it could be possible to find **sensitive information** in the response. The endpoint /pods was found using [**Kiterunner**](https://github.com/assetnote/kiterunner).
{{#include ../../../banners/hacktricks-training.md}} {{#include ../../../banners/hacktricks-training.md}}
@@ -185,6 +185,12 @@ If you managed to **escape from the container** there are some interesting thing
- `/etc/kubernetes/manifests/etcd.yaml` - **etcd Configuration** - `/etc/kubernetes/manifests/etcd.yaml` - **etcd Configuration**
- `/etc/kubernetes/pki` - **Kubernetes Key** - `/etc/kubernetes/pki` - **Kubernetes Key**
### Image Pull and Registry Credentials
After node access, also review how the node pulls private images. Useful evidence includes runtime image metadata (`crictl images`), Pod or ServiceAccount `imagePullSecrets`, containerd registry configuration such as `/etc/containerd/config.toml` and `/etc/containerd/certs.d`, and kubelet image credential provider flags such as `--image-credential-provider-config` and `--image-credential-provider-bin-dir`.
Do not assume that a cached private image means you have reusable registry credentials. It might only prove that the image exists on this node. However, static runtime registry credentials, Docker config JSON pull secrets, or a credential provider that can mint short-lived pull credentials can expose private registry access. Recent Kubernetes versions also support service-account-token based kubelet credential providers for image pulls, so check whether the provider is using Pod-bound service account tokens and which audience it requests before reporting the impact.
### Find node kubeconfig ### Find node kubeconfig
If you cannot find the kubeconfig file in one of the previously commented paths, **check the argument `--kubeconfig` of the kubelet process**: If you cannot find the kubeconfig file in one of the previously commented paths, **check the argument `--kubeconfig` of the kubelet process**:
@@ -455,4 +461,3 @@ Off-Menu +
{{#include ../../banners/hacktricks-training.md}} {{#include ../../banners/hacktricks-training.md}}
@@ -102,6 +102,7 @@ kubectl get services --all-namespaces \
-o custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name,TYPE:.spec.type,ETP:.spec.externalTrafficPolicy,ITP:.spec.internalTrafficPolicy,AFFINITY:.spec.sessionAffinity,DIST:.spec.trafficDistribution,NODEPORTS:.spec.ports[*].nodePort' -o custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name,TYPE:.spec.type,ETP:.spec.externalTrafficPolicy,ITP:.spec.internalTrafficPolicy,AFFINITY:.spec.sessionAffinity,DIST:.spec.trafficDistribution,NODEPORTS:.spec.ports[*].nodePort'
``` ```
- NodePorts are normally exposed on node addresses, but kube-proxy can restrict the address ranges with `--nodeport-addresses` or `nodePortAddresses` in its configuration. Check the active kube-proxy or CNI service-proxy replacement configuration before assuming the NodePort is reachable on every node IP.
- `externalTrafficPolicy: Local` preserves the original client source IP for NodePort/LoadBalancer traffic and avoids forwarding to endpoints on other nodes. A node without a local ready endpoint may drop the traffic even if the Service has endpoints elsewhere. - `externalTrafficPolicy: Local` preserves the original client source IP for NodePort/LoadBalancer traffic and avoids forwarding to endpoints on other nodes. A node without a local ready endpoint may drop the traffic even if the Service has endpoints elsewhere.
- `externalTrafficPolicy: Cluster` is the default and can forward through any node, but backend logs may see node IPs instead of the real external client IP. - `externalTrafficPolicy: Cluster` is the default and can forward through any node, but backend logs may see node IPs instead of the real external client IP.
- `internalTrafficPolicy: Local` limits in-cluster Service traffic to endpoints local to the source node. This is locality routing, not an authorization boundary. - `internalTrafficPolicy: Local` limits in-cluster Service traffic to endpoints local to the source node. This is locality routing, not an authorization boundary.
@@ -169,6 +170,8 @@ spec:
When looking up the host `my-service.prod.svc.cluster.local`, the cluster DNS Service returns a `CNAME` record with the value `my.database.example.com`. Accessing `my-service` works in the same way as other Services but with the crucial difference that **redirection happens at the DNS level** rather than via proxying or forwarding. When looking up the host `my-service.prod.svc.cluster.local`, the cluster DNS Service returns a `CNAME` record with the value `my.database.example.com`. Accessing `my-service` works in the same way as other Services but with the crucial difference that **redirection happens at the DNS level** rather than via proxying or forwarding.
Security review note: if an Ingress controller, Gateway implementation, service mesh, or application accepts an ExternalName Service as a backend, the controller may resolve and reach the external name from its own network position. That can expose internal-only services through public routing infrastructure when users can create both the route object and the ExternalName Service. Review the specific controller implementation and version, ExternalName support flags or allowlists, route status, and the exact target domain before treating this as safe. For example, Skipper patched a Kubernetes ExternalName SSRF issue in v0.24.0 by disabling ExternalName backends by default and documenting an allowlist option.
List all ExternalNames: List all ExternalNames:
```bash ```bash
@@ -256,21 +259,30 @@ List Gateway API exposure objects:
kubectl get gatewayclasses kubectl get gatewayclasses
kubectl get gateways --all-namespaces kubectl get gateways --all-namespaces
kubectl get httproutes --all-namespaces kubectl get httproutes --all-namespaces
kubectl get grpcroutes,tlsroutes,tcproutes,udproutes --all-namespaces
kubectl get referencegrants --all-namespaces
kubectl get backendtlspolicies --all-namespaces
kubectl get gateway -n <namespace> <gateway-name> -o yaml kubectl get gateway -n <namespace> <gateway-name> -o yaml
kubectl get httproute -n <namespace> <route-name> -o yaml kubectl get httproute -n <namespace> <route-name> -o yaml
``` ```
Check Gateway listeners, allowed route namespaces, Route `parentRefs`, hostnames, filters, backend references, and status conditions such as whether the route was accepted. A Route that is accepted by a shared Gateway can expose a backend even when no legacy Ingress object exists. Check Gateway listeners, allowed route namespaces, Route `parentRefs`, hostnames or SNI matches, filters, backend references, and status conditions such as `Accepted`, `ResolvedRefs`, and `Programmed`. A Route that is accepted by a shared Gateway can expose a backend even when no legacy Ingress object exists.
Do not check only HTTPRoute. GRPCRoute, TLSRoute, TCPRoute, and UDPRoute can expose non-HTTP services such as admin ports, brokers, databases, service-mesh gateways, or pass-through TLS backends. Also review `ReferenceGrant` objects for cross-namespace backend or certificate references and `BackendTLSPolicy` for the TLS identity the Gateway uses when connecting to backend Services. Backend TLS policy is not proof of public reachability by itself, but it is useful evidence when a programmed Gateway route reaches a ready Service with weak, shared, or wrong backend identity validation.
### References ### References
- [https://medium.com/google-cloud/kubernetes-nodeport-vs-loadbalancer-vs-ingress-when-should-i-use-what-922f010849e0](https://medium.com/google-cloud/kubernetes-nodeport-vs-loadbalancer-vs-ingress-when-should-i-use-what-922f010849e0) - [https://medium.com/google-cloud/kubernetes-nodeport-vs-loadbalancer-vs-ingress-when-should-i-use-what-922f010849e0](https://medium.com/google-cloud/kubernetes-nodeport-vs-loadbalancer-vs-ingress-when-should-i-use-what-922f010849e0)
- [https://kubernetes.io/docs/concepts/services-networking/service/](https://kubernetes.io/docs/concepts/services-networking/service/) - [https://kubernetes.io/docs/concepts/services-networking/service/](https://kubernetes.io/docs/concepts/services-networking/service/)
- [https://kubernetes.io/docs/reference/command-line-tools-reference/kube-proxy/](https://kubernetes.io/docs/reference/command-line-tools-reference/kube-proxy/)
- [https://kubernetes.io/blog/2026/05/14/kubernetes-v1-36-deprecation-and-removal-of-service-externalips/](https://kubernetes.io/blog/2026/05/14/kubernetes-v1-36-deprecation-and-removal-of-service-externalips/) - [https://kubernetes.io/blog/2026/05/14/kubernetes-v1-36-deprecation-and-removal-of-service-externalips/](https://kubernetes.io/blog/2026/05/14/kubernetes-v1-36-deprecation-and-removal-of-service-externalips/)
- [https://kubernetes.io/docs/concepts/services-networking/service-traffic-policy/](https://kubernetes.io/docs/concepts/services-networking/service-traffic-policy/) - [https://kubernetes.io/docs/concepts/services-networking/service-traffic-policy/](https://kubernetes.io/docs/concepts/services-networking/service-traffic-policy/)
- [https://kubernetes.io/docs/tutorials/services/source-ip/](https://kubernetes.io/docs/tutorials/services/source-ip/) - [https://kubernetes.io/docs/tutorials/services/source-ip/](https://kubernetes.io/docs/tutorials/services/source-ip/)
- [https://kubernetes.io/docs/concepts/services-networking/topology-aware-routing/](https://kubernetes.io/docs/concepts/services-networking/topology-aware-routing/) - [https://kubernetes.io/docs/concepts/services-networking/topology-aware-routing/](https://kubernetes.io/docs/concepts/services-networking/topology-aware-routing/)
- [https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/](https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/) - [https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/](https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/)
- [https://gateway-api.sigs.k8s.io/](https://gateway-api.sigs.k8s.io/) - [https://gateway-api.sigs.k8s.io/](https://gateway-api.sigs.k8s.io/)
- [https://kubernetes.io/blog/2025/11/06/gateway-api-v1-4/](https://kubernetes.io/blog/2025/11/06/gateway-api-v1-4/)
- [https://gateway-api.sigs.k8s.io/api-types/backendtlspolicy/](https://gateway-api.sigs.k8s.io/api-types/backendtlspolicy/)
- [https://github.com/zalando/skipper/security/advisories/GHSA-mxxc-p822-2hx9](https://github.com/zalando/skipper/security/advisories/GHSA-mxxc-p822-2hx9)
{{#include ../../banners/hacktricks-training.md}} {{#include ../../banners/hacktricks-training.md}}
@@ -209,6 +209,36 @@ kubectl get pods -A -o custom-columns='NS:.metadata.namespace,NAME:.metadata.nam
- `metadata.finalizers` and `metadata.deletionTimestamp` explain resources stuck in deletion and can reveal cleanup controllers or persistence/disruption tricks. - `metadata.finalizers` and `metadata.deletionTimestamp` explain resources stuck in deletion and can reveal cleanup controllers or persistence/disruption tricks.
- `status`, Events, and conditions can reveal node placement, pod IPs, image IDs, failure messages, scheduling issues, admission denials, and controller progress. They are useful clues, but audit logs are still required to prove who performed an action. - `status`, Events, and conditions can reveal node placement, pod IPs, image IDs, failure messages, scheduling issues, admission denials, and controller progress. They are useful clues, but audit logs are still required to prove who performed an action.
### Dynamic Resource Allocation and device evidence
If the cluster uses GPUs, NICs, FPGAs, or other specialized hardware, check whether Kubernetes Dynamic Resource Allocation (DRA) is present. DRA uses `resource.k8s.io` objects such as `DeviceClass`, `ResourceSlice`, `ResourceClaim`, and `ResourceClaimTemplate` to describe available devices and claim them for Pods. These objects can reveal which nodes can access valuable hardware, which driver manages it, and which workload has an allocation.
```bash
kubectl api-resources --api-group=resource.k8s.io
kubectl get deviceclasses.resource.k8s.io 2>/dev/null
kubectl get resourceslices.resource.k8s.io 2>/dev/null
kubectl get resourceclaims.resource.k8s.io -A 2>/dev/null
kubectl get resourceclaimtemplates.resource.k8s.io -A 2>/dev/null
kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"/"}{.metadata.name}{" claims="}{.spec.resourceClaims}{" node="}{.spec.nodeName}{"\n"}{end}'
kubectl get daemonsets,pods -A -o wide | grep -Ei 'dra|device|gpu|nvidia|amd|intel|sriov|fpga'
```
During review, restrict writes to cluster-scoped `DeviceClass` and `ResourceSlice` objects to admins and DRA drivers, and keep `ResourceClaim` / `ResourceClaimTemplate` rights scoped to the namespaces that need them. Driver permissions to update `ResourceClaim` status should be explicit and narrow. On nodes, the kubelet PodResources API is commonly exposed through `/var/lib/kubelet/pod-resources/kubelet.sock`; monitoring DaemonSets may mount that directory to inspect assigned devices, so review those Pods like other privileged node agents.
### ClusterTrustBundle and add-on certificate trust
Recent clusters may expose `ClusterTrustBundle` objects in the `certificates.k8s.io` API group. They are cluster-scoped X.509 trust anchor bundles that Pods can mount through projected volumes. Broad read access is expected, but write access is sensitive because changing trusted roots can affect webhooks, aggregated APIs, service meshes, and applications that consume cluster-distributed CA material.
```bash
kubectl api-resources --api-group=certificates.k8s.io | grep -i clustertrustbundle
kubectl get clustertrustbundles.certificates.k8s.io 2>/dev/null
kubectl get clustertrustbundle <name> -o yaml 2>/dev/null
kubectl get pods -A -o yaml | grep -n -E 'clusterTrustBundle|trustBundle|caBundle'
kubectl get apiservices -o jsonpath='{range .items[*]}{.metadata.name}{" insecure="}{.spec.insecureSkipTLSVerify}{" service="}{.spec.service.namespace}{"/"}{.spec.service.name}{"\n"}{end}'
```
During review, record `signerName`, bundle fingerprints, writer identities, projected-volume consumers, and any trust-distribution controller such as cert-manager trust-manager. Treat `APIService` objects with `insecureSkipTLSVerify: true`, stale `caBundle` values, or broad permissions to patch APIService/webhook trust fields as certificate-trust findings rather than ordinary object inventory.
### Get Current Privileges ### Get Current Privileges
{{#tabs }} {{#tabs }}
@@ -111,6 +111,24 @@ This fact implies that, by default, **every pod running in the same node** is go
> [!WARNING] > [!WARNING]
> Therefore, it's possible to perform A**RP Spoofing attacks between pods in the same node.** > Therefore, it's possible to perform A**RP Spoofing attacks between pods in the same node.**
### NetworkPolicy and admin policy layers
Kubernetes `NetworkPolicy` is a pod traffic control at L3/L4, but it is enforced by the CNI plugin and not by the API server itself. A cluster can store NetworkPolicy objects while still allowing traffic if the active CNI does not implement them, so always validate with a controlled allowed source and a blocked negative-control source.
Do not stop at `kubectl get networkpolicy -A`. Clusters using Cilium, Calico, OVN-Kubernetes, Antrea, or managed-provider dataplanes may also have policy APIs such as `CiliumNetworkPolicy`, `CiliumClusterwideNetworkPolicy`, Calico `GlobalNetworkPolicy`, `AdminNetworkPolicy`, or `BaselineAdminNetworkPolicy`. These can add explicit deny, tier/order, cluster scope, L7/DNS rules, or admin guardrails that ordinary additive Kubernetes NetworkPolicy semantics do not explain.
Useful first checks:
```bash
kubectl api-resources | grep -Ei 'networkpolicy|adminnetworkpolicy|cilium|calico'
kubectl get networkpolicy -A
kubectl get cnp,ccnp -A 2>/dev/null
kubectl get globalnetworkpolicy -A 2>/dev/null
kubectl get adminnetworkpolicy,baselineadminnetworkpolicy -A 2>/dev/null
```
For bypass analysis, check whether the intended block is avoided through an allowed proxy, DNS or egress gateway, `hostNetwork` pod, node-local path, broad namespace or pod label selector, or a higher-precedence admin/global policy. Report the source pod labels, namespace labels, destination Service or EndpointSlice, CNI/policy implementation, deciding policy rule, and traffic proof.
### DNS ### DNS
In kubernetes environments you will usually find 1 (or more) **DNS services running** usually in the kube-system namespace: In kubernetes environments you will usually find 1 (or more) **DNS services running** usually in the kube-system namespace:
@@ -334,4 +352,3 @@ It will install agents in the selected pods and gather their traffic information
{{#include ../../banners/hacktricks-training.md}} {{#include ../../banners/hacktricks-training.md}}
@@ -266,6 +266,55 @@ aws sts assume-role-with-web-identity --role-arn arn:aws:iam::123456789098:role/
../aws-security/aws-basic-information/aws-federation-abuse.md ../aws-security/aws-basic-information/aws-federation-abuse.md
{{#endref}} {{#endref}}
### EKS Pod Identity
EKS Pod Identity is the newer AWS-managed way to associate an IAM role with a Kubernetes service account without relying on each workload to call STS with an IRSA web identity token. The cluster runs the EKS Pod Identity Agent on nodes, the EKS API stores pod identity associations, and AWS SDKs in selected pods obtain credentials through the container credentials provider path exposed by the agent.
From Kubernetes, the interesting evidence is still the service account and pod relationship, but the runtime signals are different from IRSA. Look for AWS container credential environment variables in pods rather than only `AWS_WEB_IDENTITY_TOKEN_FILE`:
```bash
kubectl get pods -A -o yaml | grep -nE 'AWS_CONTAINER_CREDENTIALS|AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE|AWS_ROLE_ARN|AWS_WEB_IDENTITY_TOKEN_FILE'
kubectl get serviceaccounts -A -o yaml | grep -nE 'eks.amazonaws.com|role-arn'
kubectl get ds -A | grep -i 'pod.identity\|eks-pod-identity'
```
From AWS, enumerate the associations and then map them back to Kubernetes namespaces and service accounts:
```bash
aws eks list-pod-identity-associations --cluster-name <cluster>
aws eks describe-pod-identity-association \
--cluster-name <cluster> \
--association-id <association-id>
```
Inside an associated pod, the main runtime indicators are the container credentials provider variables injected by EKS:
```bash
env | grep -E '^AWS_CONTAINER_(CREDENTIALS_FULL_URI|AUTHORIZATION_TOKEN_FILE)='
ls -l /var/run/secrets/pods.eks.amazonaws.com/serviceaccount/ 2>/dev/null
aws sts get-caller-identity
```
The local credential endpoint is usually `http://169.254.170.23/v1/credentials` and the authorization token is a projected service account token for the `pods.eks.amazonaws.com` audience. Remember that AWS SDK credential-provider order still applies: if static environment credentials or shared credential files are configured earlier in the chain, the pod may use those instead of the Pod Identity association.
Pod Identity roles normally trust the `pods.eks.amazonaws.com` service principal for `sts:AssumeRole` and `sts:TagSession`. Review trust-policy conditions on request tags such as `kubernetes-namespace`, `kubernetes-service-account`, and cluster tags, because broad conditions can make a reusable role available to too many service accounts. Pod Identity also adds session tags to the temporary credentials, and those tags can drive ABAC policies such as resource access based on `${aws:PrincipalTag/kubernetes-namespace}` or `${aws:PrincipalTag/kubernetes-service-account}`.
For cross-account access, a Pod Identity association can use a same-account role that chains into a target role in another account. In that case, review both layers: the EKS association role and the target role trust/policy. The Pod Identity session tags are transitive across the role chain, so they are useful evidence for proving which cluster namespace and service account accessed the remote account.
> [!WARNING]
> If you can create or modify pods that use a service account with an EKS Pod Identity association, test whether that pod receives useful AWS permissions. If you are defending, alert on new pod identity associations, unexpected service account use, and AWS API calls from roles that should only be used by specific workloads.
### EKS governance guardrails
When reviewing EKS from the AWS side, remember that IAM and AWS Organizations guardrails can deny unsafe cluster configuration even when a principal has broad-looking EKS permissions. Recent EKS condition keys cover cluster settings such as public or private endpoint access, Kubernetes version, secrets-encryption KMS keys, deletion protection, control-plane scaling tier, and zonal shift configuration. These keys can be used in IAM policies or Service Control Policies to enforce account-wide cluster baselines.
This matters for both attack impact and triage. If a principal can call `eks:UpdateClusterConfig` but an SCP denies enabling a public endpoint through `eks:endpointPublicAccess`, report the attempted risky action and the guardrail that blocked it instead of claiming public API exposure. For defenders, alert on denied EKS configuration changes as well as successful changes, because denied attempts can reveal compromised automation, stale admin roles, or reconnaissance before a pivot to a less protected account.
Useful references:
- [Amazon EKS IAM condition keys](https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazonelastickubernetesservice.html)
- [AWS Organizations service control policies](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_scps.html)
### Find Pods a SAs with IAM Roles in the Cluster ### Find Pods a SAs with IAM Roles in the Cluster
This is a script to easily **iterate over the all the pods and sas** definitions **looking** for that **annotation**: This is a script to easily **iterate over the all the pods and sas** definitions **looking** for that **annotation**:
@@ -378,6 +427,14 @@ metadata:
azure.workload.identity/use: "true" azure.workload.identity/use: "true"
``` ```
Newer AKS environments may use **AKS Identity Bindings** (preview) to scale Workload ID across many clusters or service accounts without creating one federated identity credential per subject. In that model, a user-assigned managed identity is bound to the AKS cluster, workloads opt in with `azure.workload.identity/use-identity-binding: "true"`, and Kubernetes RBAC grants `use-managed-identity` on `cid.wi.aks.azure.com` resources named after managed identity client IDs. A broad `ClusterRoleBinding` here can expose the same Azure identity to more namespaces than expected, even if direct federated identity credential subjects look narrow.
```bash
az aks identity-binding list -g <resource-group> --cluster-name <cluster> -o yaml
kubectl get clusterrole,clusterrolebinding -o yaml | grep -n 'cid.wi.aks.azure.com\|use-managed-identity' -B 8 -A 12
kubectl get pods -A -o yaml | grep -n 'azure.workload.identity/use-identity-binding' -B 8 -A 12
```
If the cluster still uses the deprecated Microsoft Entra pod-managed identity model, look for the old CRDs and NMI/MIC components instead of the Workload ID annotations: If the cluster still uses the deprecated Microsoft Entra pod-managed identity model, look for the old CRDs and NMI/MIC components instead of the Workload ID annotations:
```bash ```bash
@@ -388,14 +445,31 @@ kubectl get ds -A | grep -Ei 'nmi|mic|aad-pod-identity'
AKS nodes are Azure VM scale set instances, so node or host-level access can expose Azure Instance Metadata Service at `169.254.169.254`. Do not assume an ordinary pod should receive node managed identity credentials: verify workload identity settings, legacy pod identity/NMI behavior, hostNetwork usage, network controls, and node access first. If a node identity has broad Azure permissions, node compromise can become an Azure pivot even when application Workload ID is correctly scoped. AKS nodes are Azure VM scale set instances, so node or host-level access can expose Azure Instance Metadata Service at `169.254.169.254`. Do not assume an ordinary pod should receive node managed identity credentials: verify workload identity settings, legacy pod identity/NMI behavior, hostNetwork usage, network controls, and node access first. If a node identity has broad Azure permissions, node compromise can become an Azure pivot even when application Workload ID is correctly scoped.
AKS Automatic and Node Auto-Provisioning (NAP) change the node-side evidence you should collect. AKS Automatic preconfigures several production defaults, including Workload ID/OIDC support, managed node pools, node resource group lockdown, and managed upgrade behavior. NAP is the managed Karpenter-based provisioning mode and uses Kubernetes resources such as `NodePool`, `AKSNodeClass`, and `NodeClaim` to decide which nodes are created for pending workloads. Review who can modify those resources, high-impact scheduling controls, privileged pods, and broad tolerations; also check whether node resource group lockdown blocked direct VMSS/load balancer edits and forced changes back through Kubernetes or AKS APIs.
```bash
az aks show -g <resource-group> -n <cluster> \
--query '{sku:sku,nodeProvisioningProfile:nodeProvisioningProfile,autoUpgradeProfile:autoUpgradeProfile,nodeResourceGroup:nodeResourceGroup,securityProfile:securityProfile}' \
-o yaml
kubectl get crd | grep -Ei 'nodepool|aksnodeclass|nodeclaim|karpenter'
kubectl get nodepools,aksnodeclasses,nodeclaims -A -o yaml 2>/dev/null
```
## References ## References
- [https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity) - [https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity)
- [https://medium.com/zeotap-customer-intelligence-unleashed/gke-workload-identity-a-secure-way-for-gke-applications-to-access-gcp-services-f880f4e74e8c](https://medium.com/zeotap-customer-intelligence-unleashed/gke-workload-identity-a-secure-way-for-gke-applications-to-access-gcp-services-f880f4e74e8c) - [https://medium.com/zeotap-customer-intelligence-unleashed/gke-workload-identity-a-secure-way-for-gke-applications-to-access-gcp-services-f880f4e74e8c](https://medium.com/zeotap-customer-intelligence-unleashed/gke-workload-identity-a-secure-way-for-gke-applications-to-access-gcp-services-f880f4e74e8c)
- [https://blogs.halodoc.io/iam-roles-for-service-accounts-2/](https://blogs.halodoc.io/iam-roles-for-service-accounts-2/) - [https://blogs.halodoc.io/iam-roles-for-service-accounts-2/](https://blogs.halodoc.io/iam-roles-for-service-accounts-2/)
- [https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html](https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html)
- [https://docs.aws.amazon.com/eks/latest/userguide/pod-id-how-it-works.html](https://docs.aws.amazon.com/eks/latest/userguide/pod-id-how-it-works.html)
- [https://learn.microsoft.com/en-us/azure/aks/concepts-identity](https://learn.microsoft.com/en-us/azure/aks/concepts-identity) - [https://learn.microsoft.com/en-us/azure/aks/concepts-identity](https://learn.microsoft.com/en-us/azure/aks/concepts-identity)
- [https://learn.microsoft.com/en-us/azure/aks/workload-identity-overview](https://learn.microsoft.com/en-us/azure/aks/workload-identity-overview) - [https://learn.microsoft.com/en-us/azure/aks/workload-identity-overview](https://learn.microsoft.com/en-us/azure/aks/workload-identity-overview)
- [https://learn.microsoft.com/en-us/azure/aks/identity-bindings-concepts](https://learn.microsoft.com/en-us/azure/aks/identity-bindings-concepts)
- [https://learn.microsoft.com/en-us/azure/aks/identity-bindings](https://learn.microsoft.com/en-us/azure/aks/identity-bindings)
- [https://learn.microsoft.com/en-us/azure/aks/entra-id-authorization](https://learn.microsoft.com/en-us/azure/aks/entra-id-authorization) - [https://learn.microsoft.com/en-us/azure/aks/entra-id-authorization](https://learn.microsoft.com/en-us/azure/aks/entra-id-authorization)
- [https://learn.microsoft.com/en-us/azure/aks/intro-aks-automatic](https://learn.microsoft.com/en-us/azure/aks/intro-aks-automatic)
- [https://learn.microsoft.com/en-us/azure/aks/node-auto-provisioning](https://learn.microsoft.com/en-us/azure/aks/node-auto-provisioning)
- [https://learn.microsoft.com/en-us/azure/aks/node-resource-group-lockdown](https://learn.microsoft.com/en-us/azure/aks/node-resource-group-lockdown)
{{#include ../../banners/hacktricks-training.md}} {{#include ../../banners/hacktricks-training.md}}
@@ -26,6 +26,12 @@ From **Kubernetes** 1.6 onwards, **RBAC** policies are **enabled by default**. B
kube-apiserver --authorization-mode=Example,RBAC --other-options --more-options kube-apiserver --authorization-mode=Example,RBAC --other-options --more-options
``` ```
Modern clusters can also configure the API server authorizer chain with `--authorization-config`, which points to an `AuthorizationConfiguration` file. This file can define ordered authorizers, multiple webhook authorizers, webhook timeouts, `failurePolicy`, cache settings, and CEL `matchConditions` that decide which requests are sent to a webhook. During a security review, do not stop at `--authorization-mode` if `--authorization-config` is present: read the referenced file and check whether a webhook can fail open with `NoOpinion`, whether match conditions skip sensitive resources, and whether all API server replicas use equivalent authorization configuration.
Also check authentication configuration when reviewing anonymous API exposure. `--authentication-config` can scope the anonymous authenticator to specific paths such as `/livez`, `/readyz`, and `/healthz`. Anonymous health endpoint access is not the same as anonymous access to Kubernetes resources; the dangerous condition is an RBAC or authorizer path that lets `system:anonymous` or `system:unauthenticated` read or modify real API objects.
Finally, treat membership in `system:masters` as cluster-admin-equivalent. Users or certificates in this group have unrestricted API access that bypasses normal RBAC and webhook authorization restrictions, so identity mappings that add this group can be more important than ordinary RoleBinding output.
## Templates ## Templates
In the template of a **Role** or a **ClusterRole** you will need to indicate the **name of the role**, the **namespace** (in roles) and then the **apiGroups**, **resources** and **verbs** of the role: In the template of a **Role** or a **ClusterRole** you will need to indicate the **name of the role**, the **namespace** (in roles) and then the **apiGroups**, **resources** and **verbs** of the role:
@@ -55,6 +61,8 @@ Kubernetes sometimes checks authorization for additional permissions using speci
- [Authentication](https://kubernetes.io/docs/reference/access-authn-authz/authentication/) - [Authentication](https://kubernetes.io/docs/reference/access-authn-authz/authentication/)
- `impersonate` verb on `users`, `groups`, and `serviceaccounts` in the core API group, and the `userextras` in the `authentication.k8s.io` API group. - `impersonate` verb on `users`, `groups`, and `serviceaccounts` in the core API group, and the `userextras` in the `authentication.k8s.io` API group.
Kubernetes v1.36 also includes **constrained impersonation** as a beta feature. Instead of only granting the legacy all-or-nothing `impersonate` verb, clusters can grant mode-specific verbs such as `impersonate:user-info`, `impersonate:serviceaccount`, `impersonate:arbitrary-node`, or `impersonate:associated-node`, plus action-specific verbs such as `impersonate-on:user-info:list` on the target resource. Review both halves: the identity the subject can impersonate and the actions it can perform while impersonating. Legacy `impersonate` rules can still allow broader access, so do not assume constrained-looking verbs are enforced unless the API server version and access-review evidence confirm it.
> [!WARNING] > [!WARNING]
> You can find **all the verbs that each resource support** executing `kubectl api-resources --sort-by name -o wide` > You can find **all the verbs that each resource support** executing `kubectl api-resources --sort-by name -o wide`
@@ -160,6 +168,7 @@ kubectl auth can-i create serviceaccounts/token -n default
kubectl auth can-i impersonate users kubectl auth can-i impersonate users
kubectl auth can-i bind clusterroles.rbac.authorization.k8s.io kubectl auth can-i bind clusterroles.rbac.authorization.k8s.io
kubectl auth can-i escalate clusterroles.rbac.authorization.k8s.io kubectl auth can-i escalate clusterroles.rbac.authorization.k8s.io
kubectl auth can-i impersonate-on:user-info:list pods -n default
``` ```
## **Enumerating RBAC** ## **Enumerating RBAC**
@@ -193,4 +202,3 @@ abusing-roles-clusterroles-in-kubernetes/
{{#endref}} {{#endref}}
{{#include ../../banners/hacktricks-training.md}} {{#include ../../banners/hacktricks-training.md}}
@@ -82,6 +82,29 @@ Fields to inspect:
- `clientConfig`: Review whether the webhook points to an in-cluster Service or external URL, and inspect the backing workload and service account. - `clientConfig`: Review whether the webhook points to an in-cluster Service or external URL, and inspect the backing workload and service account.
- `reinvocationPolicy`: Mutating webhooks may be reinvoked when later mutation changes the object. - `reinvocationPolicy`: Mutating webhooks may be reinvoked when later mutation changes the object.
### Native CEL admission policies
Modern clusters can also enforce admission logic with native policy objects in `admissionregistration.k8s.io`, not only with webhook configurations. `ValidatingAdmissionPolicy` is an in-process CEL-based alternative to validating webhooks and is only active when a `ValidatingAdmissionPolicyBinding` selects it. `MutatingAdmissionPolicy` is stable in Kubernetes v1.36 and is activated by `MutatingAdmissionPolicyBinding` for CEL-generated mutations.
Enumerate them with:
```bash
kubectl api-resources --api-group=admissionregistration.k8s.io -o wide
kubectl get validatingadmissionpolicies,validatingadmissionpolicybindings
kubectl get mutatingadmissionpolicies,mutatingadmissionpolicybindings 2>/dev/null || true
kubectl get validatingadmissionpolicy <name> -o yaml
kubectl get validatingadmissionpolicybinding <name> -o yaml
```
Security checks:
- A policy without a binding does not enforce anything.
- `validationActions` on the binding decides whether validation failures are denied, warned, audited, or only recorded.
- `failurePolicy: Ignore` lets CEL evaluation errors or misconfiguration fail open.
- `matchConstraints`, `matchConditions`, `namespaceSelector`, and `objectSelector` can exclude sensitive requests.
- `paramKind` and `paramRef` can make ConfigMaps or CRD-backed parameter objects part of the policy boundary; check who can modify those parameter objects.
- Writes to policies, bindings, and parameter resources should be treated like privileged admission-control changes.
### Abusing Kyverno and Gatekeeper VWC ### Abusing Kyverno and Gatekeeper VWC
As we can see all operators installed have at least one ValidatingWebHookConfiguration(VWC). As we can see all operators installed have at least one ValidatingWebHookConfiguration(VWC).
@@ -145,6 +168,8 @@ abusing-roles-clusterroles-in-kubernetes/
- [https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/) - [https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/)
- [https://kubernetes.io/docs/concepts/cluster-administration/admission-webhooks-good-practices/](https://kubernetes.io/docs/concepts/cluster-administration/admission-webhooks-good-practices/) - [https://kubernetes.io/docs/concepts/cluster-administration/admission-webhooks-good-practices/](https://kubernetes.io/docs/concepts/cluster-administration/admission-webhooks-good-practices/)
- [https://kubernetes.io/docs/reference/access-authn-authz/validating-admission-policy/](https://kubernetes.io/docs/reference/access-authn-authz/validating-admission-policy/) - [https://kubernetes.io/docs/reference/access-authn-authz/validating-admission-policy/](https://kubernetes.io/docs/reference/access-authn-authz/validating-admission-policy/)
- [https://kubernetes.io/docs/reference/access-authn-authz/mutating-admission-policy/](https://kubernetes.io/docs/reference/access-authn-authz/mutating-admission-policy/)
- [https://kubernetes.io/docs/reference/using-api/cel/](https://kubernetes.io/docs/reference/using-api/cel/)
@@ -165,10 +165,22 @@ Do not treat service mesh as a replacement for Kubernetes RBAC or NetworkPolicie
### Kube-apiserver Anonymous Access ### Kube-apiserver Anonymous Access
Anonymous access to **kube-apiserver API endpoints is not allowed**. But you could check some endpoints: Anonymous access to **kube-apiserver resource APIs should not be allowed**. Health endpoints such as `/livez`, `/readyz`, and `/healthz` may be intentionally reachable, especially when the API server uses `AuthenticationConfiguration` to scope anonymous requests to specific paths. Treat health or version responses as reachability evidence; the critical issue is a `200` response for real resource APIs such as namespaces, Secrets, Pods, RBAC objects, metrics, logs, or proxy subresources without valid credentials.
![Kubernetes API server anonymous access output listing exposed API paths](https://www.cyberark.com/wp-content/uploads/2019/09/Kube-Pen-2-fig-5.png) ![Kubernetes API server anonymous access output listing exposed API paths](https://www.cyberark.com/wp-content/uploads/2019/09/Kube-Pen-2-fig-5.png)
Useful checks:
```bash
APISERVER='https://<api-server>:6443'
curl -sk -o /dev/null -w 'livez=%{http_code}\n' "$APISERVER/livez"
curl -sk -o /dev/null -w 'readyz=%{http_code}\n' "$APISERVER/readyz"
curl -sk -o /dev/null -w 'namespaces=%{http_code}\n' "$APISERVER/api/v1/namespaces"
curl -sk -o /dev/null -w 'clusterroles=%{http_code}\n' "$APISERVER/apis/rbac.authorization.k8s.io/v1/clusterroles"
```
If resource APIs return `403`, the API server may have classified the request as `system:anonymous` but authorization blocked it. If resource APIs return `200` without credentials, look for RoleBindings or ClusterRoleBindings to `system:anonymous` or `system:unauthenticated`, permissive authorizer-chain configuration, or a front-door authentication mistake.
### **Checking for ETCD Anonymous Access** ### **Checking for ETCD Anonymous Access**
The ETCD stores the cluster secrets, configuration files and more **sensitive data**. By **default**, the ETCD **cannot** be accessed **anonymously**, but it always good to check. The ETCD stores the cluster secrets, configuration files and more **sensitive data**. By **default**, the ETCD **cannot** be accessed **anonymously**, but it always good to check.
@@ -89,11 +89,25 @@ The kubelet authorizes API requests using the same [request attributes](https://
| /metrics/\* | nodes | metrics | | /metrics/\* | nodes | metrics |
| /logs/\* | nodes | log | | /logs/\* | nodes | log |
| /spec/\* | nodes | spec | | /spec/\* | nodes | spec |
| /checkpoint/\* | nodes | checkpoint |
| _all others_ | nodes | proxy | | _all others_ | nodes | proxy |
In modern clusters, fine-grained kubelet authorization is enabled by default. Kubernetes v1.36 made this stable: kubelet first checks more specific subresources for paths such as `/pods`, `/runningPods`, `/healthz`, and `/configz` before falling back to `nodes/proxy` for backward compatibility.
| Kubelet API | preferred subresource | fallback |
| ----------- | --------------------- | -------- |
| /pods | nodes/pods | nodes/proxy |
| /runningPods/ | nodes/pods | nodes/proxy |
| /healthz | nodes/healthz | nodes/proxy |
| /configz | nodes/configz | nodes/proxy |
Use these narrower subresources for monitoring and diagnostics when possible. Avoid granting broad `nodes/proxy` for ordinary metrics, stats, health, pod-listing, or config review because `nodes/proxy` still covers higher-impact kubelet APIs.
> [!NOTE] > [!NOTE]
> WebSocket-based `/exec`, `/run`, `/attach`, and `/portforward` fall into the default **proxy** subresource and are authorized using the initial HTTP **GET** handshake. A principal with only `nodes/proxy` **GET** can still exec containers if it connects directly to `https://<node_ip>:10250` over WebSockets. See the [nodes/proxy GET -> Kubelet /exec verb confusion abuse](../abusing-roles-clusterroles-in-kubernetes/README.md#nodesproxy-get---kubelet-exec-via-websocket-verb-confusion) for details. > WebSocket-based `/exec`, `/run`, `/attach`, and `/portforward` fall into the default **proxy** subresource and are authorized using the initial HTTP **GET** handshake. A principal with only `nodes/proxy` **GET** can still exec containers if it connects directly to `https://<node_ip>:10250` over WebSockets. See the [nodes/proxy GET -> Kubelet /exec verb confusion abuse](../abusing-roles-clusterroles-in-kubernetes/README.md#nodesproxy-get---kubelet-exec-via-websocket-verb-confusion) for details.
The kubelet Checkpoint API (`POST /checkpoint/<namespace>/<pod>/<container>`) is another sensitive kubelet surface. Kubernetes v1.30 made container checkpointing beta and enabled by default, but a request still depends on kubelet authorization and runtime support such as CRI-O or containerd with checkpoint/CRIU capability. Successful checkpoints are written below the kubelet root directory, by default `/var/lib/kubelet/checkpoints`, and can contain process memory with tokens, keys, or application secrets. Restrict `nodes/checkpoint`, disable the old read-only port, limit direct kubelet network reachability, and monitor or clean checkpoint archives if the feature is intentionally used.
For example, the following request tried to access the pods info of kubelet without permission: For example, the following request tried to access the pods info of kubelet without permission:
```bash ```bash
@@ -108,9 +122,9 @@ Forbidden (user=system:node:ip-172-31-28-172.ec2.internal, verb=get, resource=no
## References ## References
- [https://kubernetes.io/docs/reference/access-authn-authz/kubelet-authn-authz/](https://kubernetes.io/docs/reference/access-authn-authz/kubelet-authn-authz/) - [https://kubernetes.io/docs/reference/access-authn-authz/kubelet-authn-authz/](https://kubernetes.io/docs/reference/access-authn-authz/kubelet-authn-authz/)
- [https://kubernetes.io/docs/reference/node/kubelet-checkpoint-api/](https://kubernetes.io/docs/reference/node/kubelet-checkpoint-api/)
- [nodes/proxy GET -> kubelet exec via WebSocket bypass](https://grahamhelton.com/blog/nodes-proxy-rce) - [nodes/proxy GET -> kubelet exec via WebSocket bypass](https://grahamhelton.com/blog/nodes-proxy-rce)
{{#include ../../../banners/hacktricks-training.md}} {{#include ../../../banners/hacktricks-training.md}}
+26 -6
View File
@@ -1286,31 +1286,37 @@ html:not(.sidebar-resizing) .sidebar {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 14px; gap: clamp(6px, 2vw, 14px);
max-width: 100%;
} }
.footer-socials a, .footer-socials a,
.footer-socials a:visited { .footer-socials a:visited {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: 34px; flex: 0 1 auto;
height: 34px; width: clamp(28px, 8vw, 34px);
height: clamp(28px, 8vw, 34px);
box-sizing: border-box;
border-radius: 9999px; border-radius: 9999px;
color: var(--icons); color: var(--icons);
text-decoration: none; text-decoration: none;
border: 1px solid var(--table-border-color); border: 1px solid var(--table-border-color);
background: color-mix(in srgb, var(--bg) 88%, transparent); background: color-mix(in srgb, var(--bg) 88%, transparent);
overflow: hidden;
} }
.footer-socials a:hover { .footer-socials a:hover {
color: var(--icons-hover); color: var(--icons-hover);
background: var(--theme-hover); background: var(--theme-hover);
} }
.footer-socials i { .footer-socials i {
font-size: 17px; font-size: clamp(14px, 4vw, 17px);
line-height: 1;
} }
.footer-socials a svg { .footer-socials a svg {
width: 16px; display: block;
height: 16px; width: clamp(14px, 4vw, 16px);
height: clamp(14px, 4vw, 16px);
fill: currentColor; fill: currentColor;
vertical-align: middle; vertical-align: middle;
} }
@@ -1330,11 +1336,25 @@ html:not(.sidebar-resizing) .sidebar {
left: 10px; left: 10px;
right: 10px; right: 10px;
bottom: 12px; bottom: 12px;
justify-content: space-between;
gap: clamp(4px, 1.8vw, 10px);
padding-top: 10px; padding-top: 10px;
border-top: 1px solid var(--table-border-color); border-top: 1px solid var(--table-border-color);
background: var(--bg); background: var(--bg);
z-index: 2; z-index: 2;
} }
.sidebar-socials a,
.sidebar-socials a:visited {
width: clamp(24px, 9vw, 34px);
height: clamp(24px, 9vw, 34px);
}
.sidebar-socials i {
font-size: clamp(12px, 4vw, 16px);
}
.sidebar-socials a svg {
width: clamp(12px, 4vw, 15px);
height: clamp(12px, 4vw, 15px);
}
} }
@media only screen and (min-width:800px) { @media only screen and (min-width:800px) {
.page > .footer { .page > .footer {