Skip to main content

tailscale_rest/models/
logging.rs

1//! Audit logs, network flow logs, and streaming either of them somewhere else.
2
3use serde_json::Value;
4
5use crate::Secret;
6use crate::model;
7use crate::models::KnownValues;
8
9/// The two log streams a tailnet produces.
10pub const LOG_TYPES: &[&str] = &["configuration", "network"];
11
12/// The systems logs can be streamed to.
13pub const DESTINATION_TYPES: &[&str] = &[
14    "splunk",
15    "elastic",
16    "panther",
17    "cribl",
18    "crowdstrike",
19    "datadog",
20    "axiom",
21    "s3",
22];
23
24/// How a log stream is compressed. `none` is the default.
25pub const COMPRESSION_FORMATS: &[&str] = &["zstd", "gzip", "none"];
26
27/// How Tailscale authenticates to S3. `rolearn` is the recommended one.
28pub const S3_AUTHENTICATION_TYPES: &[&str] = &["accesskey", "rolearn"];
29
30/// What kind of log an audit record is. One member today, which is the whole
31/// reason it is a string here (Q60).
32pub const AUDIT_LOG_TYPES: &[&str] = &["CONFIG"];
33
34/// What set a configuration change in motion.
35pub const AUDIT_ORIGINS: &[&str] = &[
36    "ADMIN_CONSOLE",
37    "CONFIG_API",
38    "CONTROL",
39    "IDENTITY_PROVIDER",
40    "NODE",
41    "SUPPORT_REQUEST",
42    "STRIPE",
43    "SECURITY_NOTIFICATION",
44    "LEGAL_NOTIFICATION",
45    "BORDER0_API",
46];
47
48/// What kind of thing acted.
49pub const AUDIT_ACTOR_TYPES: &[&str] = &[
50    "USER",
51    "NODE",
52    "AUTOMATED_WORKER",
53    "OAUTH_CLIENT",
54    "SCIM",
55    "MULLVAD",
56    "LOGSTREAM",
57    "SECRET_SCANNER",
58    "PAM_CONNECTOR",
59    "PAM_SERVICE_ACCOUNT",
60];
61
62/// What kind of thing was acted on.
63pub const AUDIT_TARGET_TYPES: &[&str] = &[
64    "TAILNET",
65    "USER",
66    "GROUP",
67    "NODE",
68    "API_KEY",
69    "INVITE",
70    "SHARE",
71    "BILLING",
72    "ADMIN_CONSOLE",
73    "WEB_INTERFACE",
74    "WEBHOOK_ENDPOINT",
75    "FAILED_REQUEST",
76];
77
78/// Which property of the target changed. The longest of these lists and the
79/// one most likely to grow, since every new setting adds a member.
80pub const AUDIT_TARGET_PROPERTIES: &[&str] = &[
81    "ACL",
82    "ACL_TAGS",
83    "ACCOUNT_EMAIL",
84    "ADDRESS",
85    "ALLOWED_IPS",
86    "AUTO_APPROVED_ROUTES",
87    "ATTRIBUTES",
88    "BILLING_OWNER",
89    "COLLECT_SERVICES",
90    "COLLECT_POSTURE_IDENTITY",
91    "MULLVAD_VPN",
92    "DNS_CONFIG",
93    "EMAIL",
94    "EXIT_NODE",
95    "FEATURE",
96    "FILE_SHARING",
97    "HTTPS",
98    "KEY_EXPIRY_TIME",
99    "KEY_EXPIRY",
100    "LOG_EXIT_FLOWS",
101    "LOGSTREAM_ENDPOINT",
102    "MAGIC_DNS",
103    "MACHINE_AUTH_NEEDED",
104    "MACHINE_APPROVAL_NEEDED",
105    "USER_APPROVAL_REQUIRED",
106    "MACHINE_NAME",
107    "MAX_KEY_DURATION",
108    "NETWORK_FLOW_LOGGING",
109    "GEOSTEERING",
110    "NODE_SHARE",
111    "TAILNET_INVITE",
112    "PAYMENT_INFO",
113    "POSTURE_IDENTITY",
114    "POSTURE_INTEGRATION",
115    "USER_ROLE",
116    "SCIM",
117    "SECURITY_EMAIL",
118    "STRIPE_CUSTOMER_ID",
119    "SUBSCRIPTION",
120    "SUBSCRIBED_EVENTS",
121    "SUPPORT_EMAIL",
122    "SECRET",
123    "TCD",
124    "TKA",
125    "AUTH_PROVIDER",
126];
127
128/// What was attempted against the target.
129pub const AUDIT_ACTIONS: &[&str] = &[
130    "LOGIN",
131    "LOGOUT",
132    "CREATE",
133    "UPDATE",
134    "DELETE",
135    "CANCEL",
136    "REVOKE",
137    "APPROVE",
138    "SUSPEND",
139    "RESTORE",
140    "ENABLE",
141    "DISABLE",
142    "ACCEPT",
143    "EXPIRED",
144    "PUSH_USER",
145    "PUSH_GROUP",
146    "VERIFY",
147    "JOIN_WAITLIST",
148    "INVITE",
149    "JOIN",
150    "LEAVE",
151    "RESEND",
152    "MIGRATE_AUTH_PROVIDER",
153];
154
155/// The IP protocols a flow log names. A flow over anything else is reported by
156/// number, so this list is a spelling aid rather than a set of possibilities.
157pub const FLOW_PROTOCOLS: &[&str] = &[
158    "ah",
159    "dccp",
160    "egp",
161    "esp",
162    "gre",
163    "icmp",
164    "igmp",
165    "igp",
166    "ipv4",
167    "ipv6-icmp",
168    "sctp",
169    "tcp",
170    "udp",
171];
172
173/// Every event the audit log can be filtered by.
174///
175/// A hundred and thirty-eight of them, and the list that a  parameter
176/// quotes. Kept whole rather than summarised because the caller has to spell one
177/// exactly, and a truncated list is worse than none.
178pub const AUDIT_EVENTS: &[&str] = &[
179    "ADMIN_CONSOLE.LOGIN", "ADMIN_CONSOLE.LOGOUT", "API_KEY.CREATE", "API_KEY.EXPIRED",
180    "API_KEY.REVOKE", "BILLING.CANCEL.SUBSCRIPTION", "BILLING.CREATE.SUBSCRIPTION",
181    "BILLING.UPDATE.ADDRESS", "BILLING.UPDATE.BILLING_OWNER", "BILLING.UPDATE.EMAIL",
182    "BILLING.UPDATE.PAYMENT_INFO", "BILLING.UPDATE.STRIPE_CUSTOMER_ID",
183    "BILLING.UPDATE.SUBSCRIPTION", "FAILED_REQUEST.UPDATE", "GROUP.PUSH_GROUP.ATTRIBUTES",
184    "INVITE.ACCEPT.FEATURE", "INVITE.ACCEPT.NODE_SHARE", "INVITE.ACCEPT.TAILNET_INVITE",
185    "INVITE.CREATE.FEATURE", "INVITE.CREATE.NODE_SHARE", "INVITE.CREATE.TAILNET_INVITE",
186    "INVITE.DELETE.NODE_SHARE", "INVITE.DELETE.TAILNET_INVITE", "INVITE.RESEND.NODE_SHARE",
187    "INVITE.RESEND.TAILNET_INVITE", "NODE.APPROVE", "NODE.CREATE", "NODE.CREATE.ATTRIBUTES",
188    "NODE.DELETE", "NODE.DELETE.ATTRIBUTES", "NODE.DISABLE.KEY_EXPIRY",
189    "NODE.DISCONNECT_NODE.CLIENT_LOG", "NODE.ENABLE.KEY_EXPIRY",
190    "NODE.EXPIRED.KEY_EXPIRY_TIME", "NODE.LOGIN", "NODE.LOGOUT", "NODE.REVOKE",
191    "NODE.UPDATE.ACL_TAGS", "NODE.UPDATE.ALLOWED_IPS", "NODE.UPDATE.ATTRIBUTES",
192    "NODE.UPDATE.AUTO_APPROVED_ROUTES", "NODE.UPDATE.EXIT_NODE",
193    "NODE.UPDATE.KEY_EXPIRY_TIME", "NODE.UPDATE.MACHINE_NAME",
194    "NODE.UPDATE.POSTURE_IDENTITY", "NODE.UPDATE.TKA", "SHARE.CREATE", "SHARE.DELETE",
195    "SHARE.UPDATE", "TAILNET.ACCEPT.FEATURE", "TAILNET.CREATE",
196    "TAILNET.CREATE.LOGSTREAM_ENDPOINT", "TAILNET.CREATE.POSTURE_INTEGRATION",
197    "TAILNET.CREATE.TKA", "TAILNET.DELETE.LOGSTREAM_ENDPOINT",
198    "TAILNET.DELETE.POSTURE_INTEGRATION", "TAILNET.DELETE.TKA",
199    "TAILNET.DISABLE.COLLECT_POSTURE_IDENTITY", "TAILNET.DISABLE.COLLECT_SERVICES",
200    "TAILNET.DISABLE.FILE_SHARING", "TAILNET.DISABLE.GEOSTEERING", "TAILNET.DISABLE.HTTPS",
201    "TAILNET.DISABLE.LOG_EXIT_FLOWS", "TAILNET.DISABLE.MACHINE_APPROVAL_NEEDED",
202    "TAILNET.DISABLE.MAGIC_DNS", "TAILNET.DISABLE.MULLVAD_VPN",
203    "TAILNET.DISABLE.NETWORK_FLOW_LOGGING", "TAILNET.DISABLE.SCIM", "TAILNET.DISABLE.TKA",
204    "TAILNET.DISABLE.USER_APPROVAL_REQUIRED", "TAILNET.ENABLE.COLLECT_POSTURE_IDENTITY",
205    "TAILNET.ENABLE.COLLECT_SERVICES", "TAILNET.ENABLE.FILE_SHARING",
206    "TAILNET.ENABLE.GEOSTEERING", "TAILNET.ENABLE.HTTPS", "TAILNET.ENABLE.LOG_EXIT_FLOWS",
207    "TAILNET.ENABLE.MACHINE_APPROVAL_NEEDED", "TAILNET.ENABLE.MAGIC_DNS",
208    "TAILNET.ENABLE.MULLVAD_VPN", "TAILNET.ENABLE.NETWORK_FLOW_LOGGING",
209    "TAILNET.ENABLE.SCIM", "TAILNET.ENABLE.TKA", "TAILNET.ENABLE.USER_APPROVAL_REQUIRED",
210    "TAILNET.JOIN", "TAILNET.JOIN_WAITLIST.FEATURE", "TAILNET.LEAVE",
211    "TAILNET.UPDATE.ACCOUNT_EMAIL", "TAILNET.UPDATE.ACL", "TAILNET.UPDATE.DNS_CONFIG",
212    "TAILNET.UPDATE.LOGSTREAM_ENDPOINT", "TAILNET.UPDATE.MAX_KEY_DURATION",
213    "TAILNET.UPDATE.POSTURE_INTEGRATION", "TAILNET.UPDATE.SECURITY_EMAIL",
214    "TAILNET.UPDATE.SUPPORT_EMAIL", "TAILNET.UPDATE.TCD", "TAILNET.UPDATE.TKA",
215    "TAILNET.VERIFY.ACCOUNT_EMAIL", "TAILNET.VERIFY.SECURITY_EMAIL",
216    "TAILNET.VERIFY.SUPPORT_EMAIL", "USER.APPROVE", "USER.CREATE", "USER.DELETE",
217    "USER.INVITE", "USER.PUSH_USER.ATTRIBUTES", "USER.RESEND.TAILNET_INVITE",
218    "USER.RESTORE", "USER.RESTORE_GLOBAL", "USER.SUSPEND", "USER.SUSPEND_GLOBAL",
219    "USER.UPDATE.USER_ROLE", "WEBHOOK_ENDPOINT.CREATE", "WEBHOOK_ENDPOINT.DELETE",
220    "WEBHOOK_ENDPOINT.UPDATE.SECRET", "WEBHOOK_ENDPOINT.UPDATE.SUBSCRIBED_EVENTS",
221    "WEB_INTERFACE.LOGIN", "WEB_INTERFACE.LOGOUT", "PAM_CONNECTOR.CREATE",
222    "PAM_CONNECTOR.CREATE.ACCESS_TOKEN", "PAM_CONNECTOR.DELETE",
223    "PAM_CONNECTOR.DISABLE.ACCESS_TOKEN", "PAM_CONNECTOR.UPDATE", "PAM_SERVICE.CREATE",
224    "PAM_SERVICE.DELETE", "PAM_SERVICE.UPDATE", "PAM_SERVICE_ACCOUNT.CREATE",
225    "PAM_SERVICE_ACCOUNT.CREATE.ACCESS_TOKEN", "PAM_SERVICE_ACCOUNT.DELETE",
226    "PAM_SERVICE_ACCOUNT.UPDATE", "PAM_SETTINGS.CREATE.CUSTOM_DOMAIN",
227    "PAM_SETTINGS.CREATE.NOTIFICATION", "PAM_SETTINGS.CREATE.RECORDING_STORAGE",
228    "PAM_SETTINGS.DELETE.CUSTOM_DOMAIN", "PAM_SETTINGS.DELETE.NOTIFICATION",
229    "PAM_SETTINGS.DELETE.RECORDING_STORAGE", "PAM_SETTINGS.UPDATE",
230    "PAM_SETTINGS.UPDATE.CUSTOM_DOMAIN", "PAM_SETTINGS.UPDATE.NOTIFICATION",
231    "PAM_SETTINGS.UPDATE.SETUP_WIZARD",
232];
233
234pub const KNOWN_VALUES: &[KnownValues] = &[
235    ("LogType", LOG_TYPES),
236    (
237        "LogstreamEndpointConfiguration.destinationType",
238        DESTINATION_TYPES,
239    ),
240    (
241        "LogstreamEndpointConfiguration.compressionFormat",
242        COMPRESSION_FORMATS,
243    ),
244    (
245        "LogstreamEndpointConfiguration.s3AuthenticationType",
246        S3_AUTHENTICATION_TYPES,
247    ),
248    ("ConfigurationAuditLog.type", AUDIT_LOG_TYPES),
249    ("ConfigurationAuditLog.origin", AUDIT_ORIGINS),
250    ("ConfigurationAuditLog.actor.type", AUDIT_ACTOR_TYPES),
251    ("ConfigurationAuditLog.target.type", AUDIT_TARGET_TYPES),
252    (
253        "ConfigurationAuditLog.target.property",
254        AUDIT_TARGET_PROPERTIES,
255    ),
256    ("ConfigurationAuditLog.action", AUDIT_ACTIONS),
257    ("ConnectionCounts.proto", FLOW_PROTOCOLS),
258    ("?event[]", AUDIT_EVENTS),
259];
260
261model! {
262    /// One configuration change, as the audit log records it.
263    ConfigurationAuditLog {
264        event_time: "eventTime" => String,
265        /// One of [`AUDIT_LOG_TYPES`].
266        log_type: "type" => String,
267        /// Set where the rate limiter held the record back, naming the time it
268        /// was enqueued rather than the time it was written.
269        deferred_at: "deferredAt" => String,
270        /// Shared by every event that came out of one operation.
271        event_group_id: "eventGroupID" => String,
272        /// One of [`AUDIT_ORIGINS`].
273        origin: "origin" => String,
274        actor: "actor" => AuditActor,
275        target: "target" => AuditTarget,
276        /// One of [`AUDIT_ACTIONS`].
277        action: "action" => String,
278        /// `target.property` before the change; of whatever shape that
279        /// property has.
280        old: "old" => Value,
281        /// `target.property` after the change.
282        new: "new" => Value,
283        /// A reason, where the caller gave one.
284        action_details: "actionDetails" => String,
285        /// Present where the change failed, and readable by the person who
286        /// attempted it.
287        error: "error" => String,
288    }
289
290    /// Who or what made the change.
291    AuditActor as "ConfigurationAuditLog.actor" {
292        /// A user ID or a node ID, depending on `type`.
293        id: "id" => String,
294        /// One of [`AUDIT_ACTOR_TYPES`].
295        actor_type: "type" => String,
296        /// As it was at the time, not as it is now.
297        login_name: "loginName" => String,
298        display_name: "displayName" => String,
299        tags: "tags" => Vec<String>,
300    }
301
302    /// What the change was made to.
303    AuditTarget as "ConfigurationAuditLog.target" {
304        id: "id" => String,
305        /// As it was at the time.
306        name: "name" => String,
307        /// One of [`AUDIT_TARGET_TYPES`].
308        target_type: "type" => String,
309        /// Only meaningful where `type` is `NODE`.
310        is_ephemeral: "isEphemeral" => bool,
311        /// One of [`AUDIT_TARGET_PROPERTIES`]; what `old` and `new` hold.
312        property: "property" => String,
313    }
314
315    /// Traffic between two addresses over one protocol.
316    ConnectionCounts {
317        /// One of [`FLOW_PROTOCOLS`], or the protocol number for anything else.
318        proto: "proto" => String,
319        /// `addr:port`.
320        src: "src" => String,
321        /// `addr:port`.
322        dst: "dst" => String,
323        tx_pkts: "txPkts" => i64,
324        tx_bytes: "txBytes" => i64,
325        rx_pkts: "rxPkts" => i64,
326        rx_bytes: "rxBytes" => i64,
327    }
328
329    /// One node's traffic over one interval, by the path it took.
330    NetworkFlowLog {
331        logged: "logged" => String,
332        node_id: "nodeId" => String,
333        start: "start" => String,
334        end: "end" => String,
335        /// Tailscale address to Tailscale address.
336        virtual_traffic: "virtualTraffic" => Vec<ConnectionCounts>,
337        /// Through a subnet router.
338        subnet_traffic: "subnetTraffic" => Vec<ConnectionCounts>,
339        /// Through an exit node.
340        exit_traffic: "exitTraffic" => Vec<ConnectionCounts>,
341        /// The underlying transport, which is what the other three ride on.
342        physical_traffic: "physicalTraffic" => Vec<ConnectionCounts>,
343    }
344
345    /// Where a log stream goes and how it authenticates.
346    ///
347    /// Most of this is conditional on `destinationType`: the `s3*` fields
348    /// apply to S3, the `gcs*` fields to GCS, and `url`, `user` and `token` to
349    /// the vendors.
350    LogstreamEndpointConfiguration {
351        /// One of [`LOG_TYPES`].
352        log_type: "logType" => String,
353        /// One of [`DESTINATION_TYPES`].
354        destination_type: "destinationType" => String,
355        /// Often empty for S3, where the official endpoint is used.
356        url: "url" => String,
357        user: "user" => String,
358        /// A wait between uploads. Logs that do not fit in one upload are sent
359        /// in several regardless.
360        upload_period_minutes: "uploadPeriodMinutes" => i64,
361        /// One of [`COMPRESSION_FORMATS`], defaulting to `none`.
362        compression_format: "compressionFormat" => String,
363        token: "token" => Secret,
364        s3_bucket: "s3Bucket" => String,
365        s3_region: "s3Region" => String,
366        s3_key_prefix: "s3KeyPrefix" => String,
367        /// One of [`S3_AUTHENTICATION_TYPES`].
368        s3_authentication_type: "s3AuthenticationType" => String,
369        s3_access_key_id: "s3AccessKeyId" => String,
370        s3_secret_access_key: "s3SecretAccessKey" => Secret,
371        /// The role Tailscale assumes under `rolearn` authentication.
372        s3_role_arn: "s3RoleArn" => String,
373        /// What Tailscale presents to AWS under `rolearn` authentication; see
374        /// [`AwsExternalId`].
375        s3_external_id: "s3ExternalId" => String,
376        gcs_bucket: "gcsBucket" => String,
377        gcs_key_prefix: "gcsKeyPrefix" => String,
378        gcs_scopes: "gcsScopes" => Vec<String>,
379        /// Workload identity credentials, as GCS's own JSON document.
380        gcs_credentials: "gcsCredentials" => Secret,
381    }
382
383    /// How a log stream proves to AWS that it is this tailnet.
384    ///
385    /// The pair goes in the AWS role's trust policy, which is what makes
386    /// `rolearn` authentication work without a stored access key.
387    AwsExternalId {
388        external_id: "externalId" => String,
389        tailscale_aws_account_id: "tailscaleAwsAccountId" => String,
390    }
391
392    /// Whether the endpoint is being reached, and how well.
393    LogstreamEndpointPublishingStatus {
394        last_activity: "lastActivity" => String,
395        last_error: "lastError" => String,
396        max_body_size: "maxBodySize" => i64,
397        num_bytes_sent: "numBytesSent" => i64,
398        num_entries_sent: "numEntriesSent" => i64,
399        num_spoofed_entries: "numSpoofedEntries" => i64,
400        num_total_requests: "numTotalRequests" => i64,
401        num_failed_requests: "numFailedRequests" => i64,
402        rate_bytes_sent: "rateBytesSent" => f64,
403        rate_entries_sent: "rateEntriesSent" => f64,
404        rate_total_requests: "rateTotalRequests" => f64,
405        rate_failed_requests: "rateFailedRequests" => f64,
406    }
407
408    // -----------------------------------------------------------------------
409    // The shapes the routes carry.
410    // -----------------------------------------------------------------------
411
412    /// A page of the configuration audit log.
413    ///
414    /// `version` and `tailnet` come back beside the records, which is why this
415    /// is not simply a list.
416    AuditLogPage as "GET /tailnet/{tailnet}/logging/configuration 200" {
417        version: "version" => String,
418        tailnet: "tailnet" => String,
419        logs: "logs" => Vec<ConfigurationAuditLog>,
420    }
421
422    /// A page of the network flow log.
423    NetworkFlowLogPage as "GET /tailnet/{tailnet}/logging/network 200" {
424        logs: "logs" => Vec<NetworkFlowLog>,
425    }
426
427    /// What asking for an AWS external identifier sends.
428    AwsExternalIdRequest as "POST /tailnet/{tailnet}/aws-external-id body" {
429        /// Whether the identifier may be used for more than one role.
430        reusable: "reusable" => bool,
431    }
432
433    /// What checking an AWS trust policy sends.
434    AwsTrustPolicyRequest
435        as "POST /tailnet/{tailnet}/aws-external-id/{id}/validate-aws-trust-policy body" {
436        /// The role Tailscale should be able to assume.
437        role_arn: "roleArn" => String,
438    }
439
440    /// What a failed trust-policy check answers with.
441    ///
442    /// The one place in the description where a failure has a shape of its own
443    /// rather than the shared `Error`: the check answers 422 with what is
444    /// wrong with the policy.
445    AwsTrustPolicyFailure
446        as "POST /tailnet/{tailnet}/aws-external-id/{id}/validate-aws-trust-policy 422" {
447        message: "message" => String,
448    }
449}