-
Notifications
You must be signed in to change notification settings - Fork 30
Description
Our usage of Application capabilities has developed a bit organically. There are currently two rule schemas sharing the same key in the app map tailscale.com/cap/tsidp. Fortunately, there is a very small number of usages of app cap for tsidp, so backwards compatibility is not a problem. It also makes now the right time to get the design more solid.
This issue proposes a design for tsidp's app cap to that considers current functionality as well as some possible future needs. It is a WIP and written to solicit feedback.
Key Constraints
- tsidp's default access is lowest privilege, fail closed
- each rule grants more access or capabilities
- there is no "deny" grant that reduces access
- use only one struct type for rules:
type AppCapRule struct { ... } - rules are checked until access is granted. First match wins (early return in checking)
- users are expected to manage the app cap grants carefully and know they are cumulative
- tsidp will emit warnings/errors logs to help troubleshoot
default deny, no funnel access, etc ...
Some endpoints are no access by default. For example the dynamic client registration (#16), admin UI (#17)) require an app cap rule for any access. Some endpoints like, /authorize must be accessed over the tailnet. tsidp has a mix of access modes and we want to get the defaults right through a combination of app logic and application capabilities.
Developer experience
Using a single rule type avoids using map[string]any and makes the data easier to work with in Go. As we add support for more use cases managing access should be easy and intuitive.
type AppCapRule struct {
ACLs []ACLRule `json:"acls"`
OAuthExtraClaims: []string `json:"oauth_claims"`
// ...
}
// CheckACL checks for a grant in the app cap rules
func (s *IDPServer) CheckACL(req *http.Request, access, grant string) bool {
rules, err := tailcfg.UnmarshalCapJSON[AppCapRule](who.CapMap, "tailscale.com/cap/tsidp")
// more hand waving ...
}
func (s *IDPServer) handleDynamicClientRegistration(w ..., r ...) {
if !s.CheckACL(r, "dcr.allow") {
s.Info("dcr.allow access denied")
writeAccessError(w)
return
}
}Related issues: