Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More Pod attributes for PDP: SA and labels #665

Merged
merged 10 commits into from
Jul 17, 2024

Conversation

zivnevo
Copy link
Collaborator

@zivnevo zivnevo commented Jul 4, 2024

This PR adds more client-Pod attributes for the PDP to consider. We now have:

  • clusterlink/metadata.clientName - the value of the Pod's label app (if it has one)
  • clusterlink/metadata.clientNamespace - the Pod's namespace
  • clusterlink/metadata.clientServiceAccount - the Pod's service account
  • client/metadata.<key> - the value of the Pod's label <key> (one such attribute for each label)

Open questions:

  • Do we still need clusterlink/metadata.clientName? Users can now simply use client/metadata.app instead
  • How should we handle Pods for which CL has no info (yet)? Deny their request? Provide the PDP with an empty set of attributes?
  • E2E testing issue: how to make sure Pod info was read by CL before the Pod can start sending requests? Currently, the solution is sleep 3. Any better ideas?

podInfo := m.getPodInfoByIP(req.IP)
if podInfo == nil {
m.logger.Infof("Pod has no info: IP=%v.", req.IP)
return clientAttrs // better return an error here?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning an error will simply yield a RST to the client connection.
I think it's better to keep it the way it is (which still allows for allow-all policies to proceed).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit problematic when having policies with negative selectors (using the NotIn and DoesNotExist operators).
E.g., I have a policy to deny all connections, except from those originating from namespace foo.
Connections from IPs for which CL cannot link a Pod, will always be allowed.

apiVersion: clusterlink.net/v1alpha1
kind: AccessPolicy
metadata:
  name: deny-except-from-foo
spec:
  action: deny
  from:
  - workloadSelector:
      matchExpressions:
      - key: clusterlink/metadata.clientNamespace
        operator: NotIn
        value: foo
  to:
  - workloadSelector: {}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right.
I think the right thing to do is to do a non-cached GET from the API-server to get the pod info in this case.
I did not find a way to do this via the given client. This will be the best option.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found this:
kubernetes-sigs/controller-runtime#585 (comment)

So there's a non-cached reader, but it's not available to authz.Manager.
Need to propagate it from cl-controlplane/app/server.go.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently, even this is not good enough. Pod may start running and send requests, even before its IP is updated in etcd. From what I see, some non-cached List() calls return the Pod with its Node IP, but without its Pod IP. It only gets updated later.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How long is this window of a working pod with etcd not yet updated?
If not long, maybe we can stall a bit to wait for the update.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or deny and let the client retry?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the implementation: now if the client has no attributes (no pod info) AND the PDP has at least one policy which depends on client attributes, the request will be denied, and the client will have to retry.
Note that even if the PDP has no attribute-dependent policies, attribute-less requests can still be denied, depending on the policies (e.g., DENY policies take precedence, or no ALLOW policies)
This is enforced both on egress and on ingress.

clientAttrs[ClientSALabel] = podInfo.serviceAccount

if src, ok := podInfo.labels["app"]; ok {
clientAttrs[ServiceNameLabel] = src // deprecated
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does it take to remove the deprecated attributes?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think they are being used currently in some experiments, so better wait a bit.
They will not appear in the documentation.

Namespace: server.Namespace,
Image: "curlimages/curl",
Args: []string{"curl", "-s", "-m", "1", "http://" + server.Name},
})
return strings.TrimSpace(body), err
}

// Sleep allows more time for CL to be notified of the new Pod, so CL can retrieve the Pod's info.
func RunClientInPodWithSleep(cluster *util.KindCluster, server *util.Service) (string, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you need this function. If the non-sleep function fails, you will be covered by the allowRetry=true for the AccessService function.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does the retry work when we run the client in a Pod?
Aren't we just creating Pods again and again?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, my bad.
So maybe add a flag that removes the RestartPolicy: v1.RestartPolicyNever.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, my bad. So maybe add a flag that removes the RestartPolicy: v1.RestartPolicyNever.

This won't work.

How about using
--retry-delay 1 --retry-all-errors
in the curl pod command line?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, this works.

@elevran
Copy link
Collaborator

elevran commented Jul 4, 2024

This PR adds more client-Pod attributes for the PDP to consider. We now have:

  • clusterlink/metadata.clientName - the value of the Pod's label app (if it has one)
  • clusterlink/metadata.clientNamespace - the Pod's namespace
  • clusterlink/metadata.clientServiceAccount - the Pod's service account
  • client/metadata.<key> - the value of the Pod's label <key> (one such attribute for each label)

Is there a need to designate client vs service in attribute names?
clusterlink/metadata.clientNamespace can just be clusterlink/metadata.namespace...

Open questions:

  • Do we still need clusterlink/metadata.clientName? Users can now simply use client/metadata.app instead

IMO, we do not. I think it is up to the user to set the labels they care about and we should not treat app any different than other labels.

  • How should we handle Pods for which CL has no info (yet)? Deny their request? Provide the PDP with an empty set of attributes?

I would propose we deny on lack of sufficient context, to comply with principles of default deny, and let the client retry (e.g., we could consider reset or timeout as the response).
They could have properties that would deny the request so proceeding with insufficient knowledge seems to me as the wrong thing security wise.

  • E2E testing issue: how to make sure Pod info was read by CL before the Pod can start sending requests? Currently, the solution is sleep 3. Any better ideas?

Tests can explicitly confirm the Pod has IP then wait 1s or so?

pkg/controlplane/authz/manager.go Outdated Show resolved Hide resolved
podInfo := m.getPodInfoByIP(req.IP)
if podInfo == nil {
m.logger.Infof("Pod has no info: IP=%v.", req.IP)
return clientAttrs // better return an error here?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or deny and let the client retry?

@zivnevo zivnevo requested review from orozery and elevran July 11, 2024 07:21
@zivnevo
Copy link
Collaborator Author

zivnevo commented Jul 15, 2024

After some rethinking, I now believe it's better for client and service to have distinct attribute prefixes. The main reason is that they don't have the exact same set of attributes (e.g., only clients have a service account, only services have a name). Having distinct prefixes will make it easier to ensure users are not using unavailable attributes in their policies. In addition, distinct prefixes make all attributes share the same pattern - a more consistent scheme.

My current proposal for attribute names (inspired by K8s well-known labels):

Client (Pod):

  • client.clusterlink.net/namespace - pod namespace
  • client.clusterlink.net/service-account - pod service account
  • client.clusterlink.net/labels.<label-key> - pod labels

Service (export):

  • export.clusterlink.net/name - Export name
  • export.clusterlink.net/service-name - the name of the K8s Service behind the Export (not currently implemented)
  • export.clusterlink.net/namespace - Export namespace
  • export.clusterlink.net/labels.<label-key> - Export labels
  • export.clusterlink.net/service-labels.<label-key> - the labels of the k8s Service behind the Export (not currently implemented)

Peer:

  • peer.clusterlink.net/name - Peer name
  • peer.clusterlink.net/labels.<label-key> - Peer labels (not currently implemented, to be set when calling clusterlink deploy peer)

@elevran , @orozery , @kfirtoledo - your thoughts?

@zivnevo zivnevo requested a review from michalmalka as a code owner July 17, 2024 09:58
@zivnevo zivnevo merged commit b13602e into clusterlink-net:main Jul 17, 2024
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Policy engine to consider more workload attributes when deciding on a connection
3 participants