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

all: add means to set device posture attributes from node #14163

Merged
merged 1 commit into from
Dec 31, 2024

Conversation

bradfitz
Copy link
Member

Updates tailscale/corp#24690
Updates #4077

@bradfitz bradfitz changed the title all: [experiment] add means to set device posture attributes from node all: add means to set device posture attributes from node Dec 30, 2024
@bradfitz bradfitz marked this pull request as ready for review December 30, 2024 17:14
@bradfitz bradfitz requested a review from a team as a code owner December 30, 2024 17:14
@bradfitz bradfitz requested review from icio and knyar December 30, 2024 17:15
@bradfitz
Copy link
Member Author

@knyar, @icio, whenever you're back to work (or bored), can you review this? See linked corp issue for background.

Copy link
Member

@raggi raggi left a comment

Choose a reason for hiding this comment

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

We should add a locally enforced rate limit and perhaps a 429 pacer too, to avoid issues with users runaway scripts

// for internal testing. See tailscale/corp#24690.
type SetDeviceAttributesRequest struct {
Version CapabilityVersion
NodeKey key.NodePublic
Copy link
Member

Choose a reason for hiding this comment

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

Might be good to say what the semantics are for how it gets used / how authentication works?

Copy link
Member Author

Choose a reason for hiding this comment

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

It's identical to how all the machine requests work (with a Version and then a NodeKey that must match the machine key used for the ts2021 transport) but I can say a bit more.

Copy link
Member Author

Choose a reason for hiding this comment

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

done

@@ -1643,6 +1643,50 @@ func (c *Direct) ReportHealthChange(w *health.Warnable, us *health.UnhealthyStat
res.Body.Close()
}

// SetDeviceAttrs does a synchronous call to the control plane to update
// the node's attributes.
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd suggest s/node's attributes/device posture attributes/g to align with how we're calling these in the docs and minimize potential confusion with nodeAttrs.

Copy link
Member Author

Choose a reason for hiding this comment

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

done

//
// See docs on [tailcfg.SetDeviceAttributesRequest] for background.
func (c *Direct) SetDeviceAttrs(ctx context.Context, attrs tailcfg.AttrUpdate) error {
np, err := c.getNoiseClient()
Copy link
Contributor

Choose a reason for hiding this comment

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

I am curious why we don't call DoNoiseRequest here, which already checks for panicOnUse, and seems to use the same transport.

This comment implies that using getConn directly (like doWithBody does) would attach node key proofs, but I don't think that's actually true? I only see KeyProvingNoiseRoundTripper used in tsnet.

// Client is an HTTP client to talk to the coordination server.
// It automatically makes a new Noise connection as needed.
// It does not support node key proofs. To do that, call
// noiseClient.getConn instead to make a connection.
*http.Client

I am likely missing something, since it's my first time taking a closer look at noise client, but I would like to understand it better.

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, right. That was added for one caller ages ago that needed to do it from afar, but all the existing callers (who used the helpers that automatically JSON-encode the request body) were never updated to use it, because the exported one isn't as helpful.

But then apparently a handful of people have since moved on to using DoNoiseRequest directly, and doing the JSON encoding themselves. But those callers aren't setting the load balancer hint header:

func addLBHeader(req *http.Request, nodeKey key.NodePublic) {
	if !nodeKey.IsZero() {
		req.Header.Add(tailcfg.LBHeader, nodeKey.String())
	}
}

Which makes the load balancer work a bit harder when it's absent.

There's about a 50/50 split between the two styles right now and I don't want to clean them up one way or another in this PR, but I do want to clean it up in its own change later.

I'll keep this as is for now (as it calls addLBHeader) but yeah, I don't like the split.

}

// AttrUpdate is a map of attributes to update.
// Attributes not in the map are left unchanged.
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd suggest linking to https://tailscale.com/s/api-device-posture-attrs (which I am adding in https://github.com/tailscale/tailscale-www/pull/7256) to provide more context on supported keys and values.

Copy link
Member Author

Choose a reason for hiding this comment

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

done

Version CapabilityVersion
NodeKey key.NodePublic

// Update is a map of attributes to update.
Copy link
Contributor

@knyar knyar Dec 31, 2024

Choose a reason for hiding this comment

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

I suspect at some point we might want clients to be able to set an expiry time, like our HTTP API supports: https://tailscale.com/api#tag/devices/POST/device/{deviceId}/attributes/{attributeKey}

Would that be another map of key->time.Time? (similar to the expiries field returned by our get api)

Or should the API be mapping keys to another struct, which would contain value alongside an optional expiry time?

Copy link
Member Author

Choose a reason for hiding this comment

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

How about we keep it any-to-any now (where the value any can be float64/bool/string/nil) and in the future we could add a struct (JSON object) type for other metadata like expiries?

@bradfitz
Copy link
Member Author

We should add a locally enforced rate limit and perhaps a 429 pacer too, to avoid issues with users runaway scripts.

The capver will at least tell us server-side how long like the client will block (30 seconds today) for a response, so we can also rate limit them server side.

If we added some client-side protection, I'd want to do it for all machine requests, not just for this one.

Updates tailscale/corp#24690
Updates #4077

Change-Id: I05fe799beb1d2a71d1ec3ae08744cc68bcadae2a
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
@bradfitz bradfitz merged commit ff09560 into main Dec 31, 2024
45 of 47 checks passed
@bradfitz bradfitz deleted the bradfitz/devattr branch December 31, 2024 20:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants