linkerd2/pkg/k8s/policy.go

408 lines
14 KiB
Go

package k8s
import (
"context"
"fmt"
"os"
"strings"
"k8s.io/apimachinery/pkg/labels"
policyv1 "github.com/linkerd/linkerd2/controller/gen/apis/policy/v1alpha1"
serverv1beta3 "github.com/linkerd/linkerd2/controller/gen/apis/server/v1beta3"
serverauthorizationv1beta1 "github.com/linkerd/linkerd2/controller/gen/apis/serverauthorization/v1beta1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
)
// Authorization holds the names of the resources involved in an authorization.
type Authorization struct {
Route string
Server string
ServerAuthorization string
AuthorizationPolicy string
}
// AuthorizationPolicyGVR is the GroupVersionResource for the AuthorizationPolicy resource.
var AuthorizationPolicyGVR = policyv1.SchemeGroupVersion.WithResource("authorizationpolicies")
// HTTPRouteGVR is the GroupVersionResource for the HTTPRoute resource.
var HTTPRouteGVR = policyv1.SchemeGroupVersion.WithResource("httproutes")
// SazGVR is the GroupVersionResource for the ServerAuthorization resource.
var SazGVR = serverauthorizationv1beta1.SchemeGroupVersion.WithResource("serverauthorizations")
// ServerGVR is the GroupVersionResource for the Server resource.
var ServerGVR = serverv1beta3.SchemeGroupVersion.WithResource("servers")
// AuthorizationsForResource returns a list of ServerAuthorizations and
// AuthorizationPolicies which apply to any Server or HttpRoute which select
// pods belonging to the given resource.
func AuthorizationsForResource(ctx context.Context, k8sAPI *KubernetesAPI, namespace string, resource string) ([]Authorization, error) {
pods, err := getPodsForResourceOrKind(ctx, k8sAPI, namespace, resource, "")
if err != nil {
return nil, err
}
results := make([]Authorization, 0)
sazs, err := k8sAPI.L5dCrdClient.ServerauthorizationV1beta1().ServerAuthorizations(namespace).List(ctx, metav1.ListOptions{})
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get serverauthorization resources: %s\n", err)
os.Exit(1)
}
for _, saz := range sazs.Items {
var servers []serverv1beta3.Server
if saz.Spec.Server.Name != "" {
server, err := k8sAPI.L5dCrdClient.ServerV1beta3().Servers(saz.GetNamespace()).Get(ctx, saz.Spec.Server.Name, metav1.GetOptions{})
if err != nil {
fmt.Fprintf(os.Stderr, "ServerAuthorization/%s targets Server/%s but we failed to get it: %s\n", saz.Name, saz.Spec.Server.Name, err)
continue
}
servers = []serverv1beta3.Server{*server}
} else if saz.Spec.Server.Selector != nil {
selector, err := metav1.LabelSelectorAsSelector(saz.Spec.Server.Selector)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to parse Server selector for ServerAuthorization/%s: %s\n", saz.Name, err)
continue
}
serverList, err := k8sAPI.L5dCrdClient.ServerV1beta3().Servers(saz.GetNamespace()).List(ctx, metav1.ListOptions{LabelSelector: selector.String()})
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get Servers for ServerAuthorization/%s: %s\n", saz.Name, err)
continue
}
servers = serverList.Items
}
for _, server := range servers {
if serverIncludesPod(server, pods) {
results = append(results, Authorization{
Route: "",
Server: server.GetName(),
ServerAuthorization: saz.GetName(),
AuthorizationPolicy: "",
})
}
}
}
policies, err := k8sAPI.L5dCrdClient.PolicyV1alpha1().AuthorizationPolicies(namespace).List(ctx, metav1.ListOptions{})
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get AuthorizationPolicy resources: %s\n", err)
os.Exit(1)
}
allServersInNamespace := map[string]*serverv1beta3.ServerList{}
for _, p := range policies.Items {
target := p.Spec.TargetRef
if target.Kind == NamespaceKind && target.Group == K8sCoreAPIGroup {
serverList, ok := allServersInNamespace[p.Namespace]
if !ok {
serverList, err = k8sAPI.L5dCrdClient.ServerV1beta3().Servers(p.Namespace).List(ctx, metav1.ListOptions{})
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get Servers for Namespace/%s: %s\n", p.Namespace, err)
continue
}
allServersInNamespace[p.Namespace] = serverList
}
for _, server := range serverList.Items {
if serverIncludesPod(server, pods) {
results = append(results, Authorization{
Route: "",
Server: server.GetName(),
ServerAuthorization: "",
AuthorizationPolicy: p.GetName(),
})
}
}
} else if target.Kind == ServerKind && target.Group == PolicyAPIGroup {
server, err := k8sAPI.L5dCrdClient.ServerV1beta3().Servers(p.Namespace).Get(ctx, string(target.Name), metav1.GetOptions{})
if err != nil {
fmt.Fprintf(os.Stderr, "AuthorizationPolicy/%s targets Server/%s but we failed to get it: %s\n", p.Name, target.Name, err)
continue
}
if serverIncludesPod(*server, pods) {
results = append(results, Authorization{
Route: "",
Server: server.GetName(),
ServerAuthorization: "",
AuthorizationPolicy: p.GetName(),
})
}
} else if target.Kind == HTTPRouteKind && target.Group == PolicyAPIGroup {
route, err := k8sAPI.L5dCrdClient.PolicyV1alpha1().HTTPRoutes(p.Namespace).Get(ctx, string(target.Name), metav1.GetOptions{})
if err != nil {
fmt.Fprintf(os.Stderr, "AuthorizationPolicy/%s targets HTTPRoute/%s but we failed to get it: %s\n", p.Name, target.Name, err)
continue
}
for _, parent := range route.Spec.ParentRefs {
if parent.Kind != nil && *parent.Kind == ServerKind &&
parent.Group != nil && *parent.Group == PolicyAPIGroup {
server, err := k8sAPI.L5dCrdClient.ServerV1beta3().Servers(p.Namespace).Get(ctx, string(parent.Name), metav1.GetOptions{})
if err != nil {
fmt.Fprintf(os.Stderr, "HTTPRoute/%s belongs to Server/%s but we failed to get it: %s\n", target.Name, parent.Name, err)
continue
}
if serverIncludesPod(*server, pods) {
results = append(results, Authorization{
Route: route.GetName(),
Server: server.GetName(),
ServerAuthorization: "",
AuthorizationPolicy: p.GetName(),
})
}
}
}
}
}
return results, nil
}
// ServersForResource returns a list of Server names of Servers which select pods
// belonging to the given resource.
func ServersForResource(ctx context.Context, k8sAPI *KubernetesAPI, namespace string, resource string, labelSelector string) ([]string, error) {
pods, err := getPodsForResourceOrKind(ctx, k8sAPI, namespace, resource, labelSelector)
if err != nil {
return nil, err
}
results := make([]string, 0)
servers, err := k8sAPI.L5dCrdClient.ServerV1beta3().Servers(namespace).List(ctx, metav1.ListOptions{})
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get serverauthorization resources: %s\n", err)
os.Exit(1)
}
for _, server := range servers.Items {
if serverIncludesPod(server, pods) {
results = append(results, server.GetName())
}
}
return results, nil
}
// ServerAuthorizationsForServer returns a list of ServerAuthorization names of
// ServerAuthorizations which select the given Server.
func ServerAuthorizationsForServer(ctx context.Context, k8sAPI *KubernetesAPI, namespace string, server string) ([]string, error) {
results := make([]string, 0)
sazs, err := k8sAPI.L5dCrdClient.ServerauthorizationV1beta1().ServerAuthorizations(namespace).List(ctx, metav1.ListOptions{})
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get serverauthorization resources: %s\n", err)
os.Exit(1)
}
for _, saz := range sazs.Items {
if saz.Spec.Server.Name != "" {
s, err := k8sAPI.DynamicClient.Resource(ServerGVR).Namespace(saz.GetNamespace()).Get(ctx, saz.Spec.Server.Name, metav1.GetOptions{})
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get server %s: %s\n", saz.Spec.Server.Name, err)
os.Exit(1)
}
if s.GetName() == server {
results = append(results, saz.GetName())
}
} else if saz.Spec.Server.Selector != nil {
selector, err := metav1.LabelSelectorAsSelector(saz.Spec.Server.Selector)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get servers: %s\n", err)
os.Exit(1)
}
serverList, err := k8sAPI.L5dCrdClient.ServerV1beta3().Servers(saz.GetNamespace()).List(ctx, metav1.ListOptions{LabelSelector: selector.String()})
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get servers: %s\n", err)
os.Exit(1)
}
for _, s := range serverList.Items {
if s.GetName() == server {
results = append(results, saz.GetName())
break
}
}
}
}
return results, nil
}
// serverIncludesPod returns true the given server selects any of the given pods
// and that pod uses the server's port.
func serverIncludesPod(server serverv1beta3.Server, pods []corev1.Pod) bool {
if server.Spec.PodSelector == nil {
return false
}
selector, err := metav1.LabelSelectorAsSelector(server.Spec.PodSelector)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to parse PodSelector of Server/%s: %s\n", server.Name, err)
return false
}
for _, pod := range pods {
if selector.Matches(labels.Set(pod.Labels)) {
for _, container := range pod.Spec.Containers {
for _, p := range container.Ports {
if server.Spec.Port.IntVal == p.ContainerPort || server.Spec.Port.StrVal == p.Name {
return true
}
}
}
}
}
return false
}
// getPodsForResourceOrKind is similar to getPodsForResource, but also supports
// querying for all resources of a given kind (i.e. when resource name is unspecified).
func getPodsForResourceOrKind(ctx context.Context, k8sAPI kubernetes.Interface, namespace string, resource string, labelSelector string) ([]corev1.Pod, error) {
elems := strings.Split(resource, "/")
if len(elems) > 2 {
return nil, fmt.Errorf("invalid resource: %s", resource)
}
if len(elems) == 2 {
pods, err := GetPodsFor(ctx, k8sAPI, namespace, resource)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get pods: %s", err)
os.Exit(1)
}
return pods, nil
}
pods := []corev1.Pod{}
typ, err := CanonicalResourceNameFromFriendlyName(elems[0])
if err != nil {
return nil, fmt.Errorf("invalid resource: %s", resource)
}
selector := metav1.ListOptions{
LabelSelector: labelSelector,
}
switch typ {
case Pod:
ps, err := k8sAPI.CoreV1().Pods(namespace).List(ctx, selector)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get pods: %s", err)
os.Exit(1)
}
pods = append(pods, ps.Items...)
case CronJob:
jobs, err := k8sAPI.BatchV1().CronJobs(namespace).List(ctx, selector)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get cronjobs: %s", err)
os.Exit(1)
}
for _, job := range jobs.Items {
ps, err := GetPodsFor(ctx, k8sAPI, namespace, fmt.Sprintf("%s/%s", CronJob, job.Name))
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get pods: %s", err)
os.Exit(1)
}
pods = append(pods, ps...)
}
case DaemonSet:
dss, err := k8sAPI.AppsV1().DaemonSets(namespace).List(ctx, selector)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get demonsets: %s", err)
os.Exit(1)
}
for _, ds := range dss.Items {
ps, err := GetPodsFor(ctx, k8sAPI, namespace, fmt.Sprintf("%s/%s", DaemonSet, ds.Name))
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get pods: %s", err)
os.Exit(1)
}
pods = append(pods, ps...)
}
case Deployment:
deploys, err := k8sAPI.AppsV1().Deployments(namespace).List(ctx, selector)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get deployments: %s", err)
os.Exit(1)
}
for _, deploy := range deploys.Items {
ps, err := GetPodsFor(ctx, k8sAPI, namespace, fmt.Sprintf("%s/%s", Deployment, deploy.Name))
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get pods: %s", err)
os.Exit(1)
}
pods = append(pods, ps...)
}
case Job:
jobs, err := k8sAPI.BatchV1().Jobs(namespace).List(ctx, selector)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get jobs: %s", err)
os.Exit(1)
}
for _, job := range jobs.Items {
ps, err := GetPodsFor(ctx, k8sAPI, namespace, fmt.Sprintf("%s/%s", Job, job.Name))
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get pods: %s", err)
os.Exit(1)
}
pods = append(pods, ps...)
}
case ReplicaSet:
rss, err := k8sAPI.AppsV1().ReplicaSets(namespace).List(ctx, selector)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get replicasets: %s", err)
os.Exit(1)
}
for _, rs := range rss.Items {
ps, err := GetPodsFor(ctx, k8sAPI, namespace, fmt.Sprintf("%s/%s", ReplicaSet, rs.Name))
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get pods: %s", err)
os.Exit(1)
}
pods = append(pods, ps...)
}
case ReplicationController:
rcs, err := k8sAPI.CoreV1().ReplicationControllers(namespace).List(ctx, selector)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get replicationcontrollers: %s", err)
os.Exit(1)
}
for _, rc := range rcs.Items {
ps, err := GetPodsFor(ctx, k8sAPI, namespace, fmt.Sprintf("%s/%s", ReplicationController, rc.Name))
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get pods: %s", err)
os.Exit(1)
}
pods = append(pods, ps...)
}
case StatefulSet:
sss, err := k8sAPI.AppsV1().StatefulSets(namespace).List(ctx, selector)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get statefulsets: %s", err)
os.Exit(1)
}
for _, ss := range sss.Items {
ps, err := GetPodsFor(ctx, k8sAPI, namespace, fmt.Sprintf("%s/%s", StatefulSet, ss.Name))
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get pods: %s", err)
os.Exit(1)
}
pods = append(pods, ps...)
}
default:
return nil, fmt.Errorf("unsupported resource type: %s", typ)
}
return pods, nil
}