Skip to content

Application Capability usage in tsidp (proposal) - WIP #44

@mostlygeek

Description

@mostlygeek

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:

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions