1"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[148],{148:function(e,t,s){s.d(t,{default:function(){return a}});var n=s(85893),i=s(10741);function c(e){let t={a:"a",code:"code",h2:"h2",li:"li",p:"p",pre:"pre",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,i.a)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsxs)(t.p,{children:["Node attributes and grant app capabilities both attach additional capabilities to devices through the ",(0,n.jsx)(t.a,{href:"/docs/features/tailnet-policy-file",children:"tailnet policy file"}),". They differ in what the capability attaches to: a node attribute attaches to a device, and a grant app capability attaches to a connection between two devices."]}),"\n",(0,n.jsxs)(t.p,{children:["This topic explains that difference and how to decide where a capability belongs. It does not list every available attribute or capability. For those, refer to the ",(0,n.jsx)(t.a,{href:"/docs/reference/syntax/policy-file#nodeattrs",children:"node attributes reference"})," and the ",(0,n.jsx)(t.a,{href:"/docs/features/access-control/grants/grants-app-capabilities",children:"application capabilities explanation"}),"."]}),"\n",(0,n.jsx)(t.h2,{id:"node-attributes",children:"Node attributes"}),"\n",(0,n.jsxs)(t.p,{children:["The ",(0,n.jsx)(t.a,{href:"/docs/reference/syntax/policy-file#nodeattrs",children:(0,n.jsx)(t.code,{children:"nodeAttrs"})})," section of the tailnet policy file applies attributes to devices based on identity. Each entry names a ",(0,n.jsx)(t.code,{children:"target"})," (the devices the attributes apply to, selected by tag, user, group, or ",(0,n.jsx)(t.code,{children:"*"}),") and the attributes to apply. A node attribute has no source and no destination, so the device carries the attribute at all times."]}),"\n",(0,n.jsx)(t.pre,{children:(0,n.jsx)(t.code,{className:"language-json",children:'"nodeAttrs": [\n {\n "target": ["autogroup:member"],\n "attr": ["funnel"],\n },\n],\n'})}),"\n",(0,n.jsxs)(t.p,{children:["This grants every member's device the ability to use ",(0,n.jsx)(t.a,{href:"/docs/features/tailscale-funnel",children:"Tailscale Funnel"}),". Funnel is a property of the device: the device can either expose a service to the internet or it cannot, regardless of which other device is involved."]}),"\n",(0,n.jsx)(t.h2,{id:"grant-app-capabilities",children:"Grant app capabilities"}),"\n",(0,n.jsxs)(t.p,{children:["A ",(0,n.jsx)(t.a,{href:"/docs/features/access-control/grants",children:"grant"})," defines which source devices (",(0,n.jsx)(t.code,{children:"src"}),") can reach which destination devices (",(0,n.jsx)(t.code,{children:"dst"}),"). The ",(0,n.jsxs)(t.a,{href:"/docs/features/access-control/grants/grants-app-capabilities",children:[(0,n.jsx)(t.code,{children:"app"})," field"]})," on a grant attaches application capabilities to that connection, so the capability applies only when a device in ",(0,n.jsx)(t.code,{children:"src"})," accesses a device in ",(0,n.jsx)(t.code,{children:"dst"}),"."]}),"\n",(0,n.jsx)(t.pre,{children:(0,n.jsx)(t.code,{className:"language-json",children:'"grants": [\n {\n "src": ["tag:us-east-vpc"],\n "dst": ["tag:us-east-relays"],\n "app": {\n "tailscale.com/cap/relay": [],\n },\n },\n],\n'})}),"\n",(0,n.jsxs)(t.p,{children:["This lets devices tagged ",(0,n.jsx)(t.code,{children:"tag:us-east-vpc"})," use the devices tagged ",(0,n.jsx)(t.code,{children:"tag:us-east-relays"})," as ",(0,n.jsx)(t.a,{href:"/docs/features/peer-relay",children:"peer relays"}),". The capability describes a relationship between two sets of devices: changing the ",(0,n.jsx)(t.code,{children:"src"})," or the ",(0,n.jsx)(t.code,{children:"dst"})," changes which devices can relay through which."]}),"\n",(0,n.jsx)(t.h2,{id:"application-capabilities-in-both-sections",children:"Application capabilities in both sections"}),"\n",(0,n.jsxs)(t.p,{children:["The ",(0,n.jsx)(t.code,{children:"app"})," map is not unique to grants. Node attributes carry device properties in two forms: the ",(0,n.jsx)(t.code,{children:"attr"})," flags shown above, such as ",(0,n.jsx)(t.code,{children:"funnel"}),", and a structured ",(0,n.jsx)(t.code,{children:"app"})," field. Grants also accept an ",(0,n.jsx)(t.code,{children:"app"})," field. The same application-capability format (",(0,n.jsx)(t.code,{children:"<domainName>/<capabilityName>"}
1)," mapped to an array of configuration objects) can appear in either ",(0,n.jsx)(t.code,{children:"nodeAttrs"})," or ",(0,n.jsx)(t.code,{children:"grants"}),"."]}),"\n",(0,n.jsxs)(t.p,{children:["Whether a capability uses the ",(0,n.jsx)(t.code,{children:"app"})," map does not determine where it belongs; placement does. For example, you configure an ",(0,n.jsx)(t.a,{href:"/docs/features/app-connectors",children:"app connector"})," with the ",(0,n.jsx)(t.code,{children:"tailscale.com/app-connectors"})," capability in ",(0,n.jsx)(t.code,{children:"nodeAttrs.app"}),":"]}),"\n",(0,n.jsx)(t.pre,{children:(0,n.jsx)(t.code,{className:"language-json",children:'"nodeAttrs": [\n {\n "target": ["tag:example-connector"],\n "app": {\n "tailscale.com/app-connectors": [\n {\n "name": "example-app",\n "connectors": ["tag:example-connector"],\n "domains": ["example.com"],\n },\n ],\n },\n },\n],\n'})}),"\n",(0,n.jsxs)(t.p,{children:["An app connector reaches ",(0,n.jsx)(t.code,{children:"example.com"})," on behalf of the tailnet regardless of which device sends traffic to it, so the capability describes the connector device and lives in ",(0,n.jsx)(t.code,{children:"nodeAttrs.app"}),". Peer relay usage describes a relationship between devices, so it lives in ",(0,n.jsx)(t.code,{children:"grants[].app"}),". The ",(0,n.jsx)(t.code,{children:"app"})," map has the same shape in both sections; its placement records whether the capability is a device property or a connection property."]}),"\n",(0,n.jsx)(t.h2,{id:"choosing-between-them",children:"Choosing between them"}),"\n",(0,n.jsx)(t.p,{children:"Use a node attribute when the capability is true of the device regardless of what it connects to. Use a grant app capability when the capability applies only along a specific source-to-destination path."}),"\n",(0,n.jsxs)(t.table,{children:[(0,n.jsx)(t.thead,{children:(0,n.jsxs)(t.tr,{children:[(0,n.jsx)(t.th,{children:"Aspect"}),(0,n.jsx)(t.th,{children:"Node attribute"}),(0,n.jsx)(t.th,{children:"Grant app capability"})]})}),(0,n.jsxs)(t.tbody,{children:[(0,n.jsxs)(t.tr,{children:[(0,n.jsx)(t.td,{children:"Attaches to"}),(0,n.jsx)(t.td,{children:"A device"}),(0,n.jsx)(t.td,{children:"A connection between two devices"})]}),(0,n.jsxs)(t.tr,{children:[(0,n.jsx)(t.td,{children:"Policy file section"}),(0,n.jsx)(t.td,{children:(0,n.jsx)(t.code,{children:"nodeAttrs"})}),(0,n.jsxs)(t.td,{children:[(0,n.jsx)(t.code,{children:"grants"})," (the ",(0,n.jsx)(t.code,{children:"app"})," field)"]})]}),(0,n.jsxs)(t.tr,{children:[(0,n.jsx)(t.td,{children:"Selected by"}),(0,n.jsxs)(t.td,{children:["Device identity (",(0,n.jsx)(t.code,{children:"target"}),": tag, user, group, or ",(0,n.jsx)(t.code,{children:"*"}),")"]}),(0,n.jsxs)(t.td,{children:["A ",(0,n.jsx)(t.code,{children:"src"})," â ",(0,n.jsx)(t.code,{children:"dst"})," relationship"]})]}),(0,n.jsxs)(t.tr,{children:[(0,n.jsx)(t.td,{children:"Conditional filtering"}),(0,n.jsxs)(t.td,{children:["None, selected by ",(0,n.jsx)(t.code,{children:"target"})," identity alone"]}),(0,n.jsxs)(t.td,{children:["Scope by ",(0,n.jsx)(t.a,{href:"/docs/features/device-posture",children:"device posture"})," (",(0,n.jsx)(t.code,{children:"srcPosture"}),") or route through specific devices (",(0,n.jsx)(t.a,{href:"/docs/features/access-control/grants/grants-via",children:(0,n.jsx)(t.code,{children:"via"})}),")"]})]}),(0,n.jsxs)(t.tr,{children:[(0,n.jsx)(t.td,{children:"Scope"}),(0,n.jsx)(t.td,{children:"Applies wherever the device connects"}),(0,n.jsxs)(t.td,{children:["Applies only when a ",(0,n.jsx)(t.code,{children:"src"})," device reaches a ",(0,n.jsx)(t.code,{children:"dst"})," device"]})]}),(0,n.jsxs)(t.tr,{children:[(0,n.jsx)(t.td,{children:"Examples"}),(0,n.jsx)(t.td,{children:"Funnel, app connectors"}),(0,n.jsx)(t.td,{children:"Peer relay usage"})]})]})]}),"\n",(0,n.jsx)(t.p,{children:"Placement determines scope, so putting a capability in the wrong section changes which devices have it and when:"}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsxs)(t.li,{children:["A connection capability written as a node attribute loses its boundary. The attribute applies by identity alone, with no ",(0,n.jsx)(t.code,{children:"src"})," or ",(0,n.jsx)(t.code,{children:"dst"}),", so the device carries the capability toward every destination it reaches rather than only the path you intended."]}),"\n",(0,n.jsxs)(t.li,{children:["A device property written as a grant app capability gains a boundary it should not have. The grant takes effect only when a listed ",(0,n.jsx)(t.code,{children:"src"})," reaches a listed ",(0,n.jsx)(t.code,{children:"dst"}),", so the capability is absent on every other connection, even though the property is meant to be true of the device everywhere."]}),"\n"]}),"\n",(0,n.jsx)(t.h2,{id:"related",children:"Related"}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.a,{href:"/docs/reference/syntax/policy-file#nodeattrs",children:"Node attributes reference"}),": the full ",(0,n.jsx)(t.code,{children:"nodeAttrs"})," syntax and the available attributes."]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.a,{href:"/docs/features/access-control/grants/grants-app-capabilities",children:"Application capabilities"}),": how the ",(0,n.jsx)(t.code,{children:"app"})," map works and the built-in Tailscale capabilities."]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.a,{href:"/docs/features/access-control/grants",children:"Grants"}),": the access control syntax that scopes capabilities to ",(0,n.jsx)(t.code,{children:"src"})," â ",(0,n.jsx)(t.code,{children:"dst"})," connections."]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.a,{href:"/docs/reference/grants-vs-acls",children:"Grants vs. ACLs"}),": a related comparison of Tailscale's two access control methods."]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.a,{href:"/docs/features/oauth-apps/device-provisioning",children:"Device provisioning with OAuth apps"}),": how custom node attributes are automatically assigned to devices at provision time."]}),"\n"]})]})}function a(e={}){let{wrapper:t}={...(0,i.a)(),...e.components};return t?(0,n.jsx)(t,{...e,children:(0,n.jsx)(c,{...e})}):c(e)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.