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-nine 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    "GROUP.UPDATE.USER_ROLE",
185    "INVITE.ACCEPT.FEATURE", "INVITE.ACCEPT.NODE_SHARE", "INVITE.ACCEPT.TAILNET_INVITE",
186    "INVITE.CREATE.FEATURE", "INVITE.CREATE.NODE_SHARE", "INVITE.CREATE.TAILNET_INVITE",
187    "INVITE.DELETE.NODE_SHARE", "INVITE.DELETE.TAILNET_INVITE", "INVITE.RESEND.NODE_SHARE",
188    "INVITE.RESEND.TAILNET_INVITE", "NODE.APPROVE", "NODE.CREATE", "NODE.CREATE.ATTRIBUTES",
189    "NODE.DELETE", "NODE.DELETE.ATTRIBUTES", "NODE.DISABLE.KEY_EXPIRY",
190    "NODE.DISCONNECT_NODE.CLIENT_LOG", "NODE.ENABLE.KEY_EXPIRY",
191    "NODE.EXPIRED.KEY_EXPIRY_TIME", "NODE.LOGIN", "NODE.LOGOUT", "NODE.REVOKE",
192    "NODE.UPDATE.ACL_TAGS", "NODE.UPDATE.ALLOWED_IPS", "NODE.UPDATE.ATTRIBUTES",
193    "NODE.UPDATE.AUTO_APPROVED_ROUTES", "NODE.UPDATE.EXIT_NODE",
194    "NODE.UPDATE.KEY_EXPIRY_TIME", "NODE.UPDATE.MACHINE_NAME",
195    "NODE.UPDATE.POSTURE_IDENTITY", "NODE.UPDATE.TKA", "SHARE.CREATE", "SHARE.DELETE",
196    "SHARE.UPDATE", "TAILNET.ACCEPT.FEATURE", "TAILNET.CREATE",
197    "TAILNET.CREATE.LOGSTREAM_ENDPOINT", "TAILNET.CREATE.POSTURE_INTEGRATION",
198    "TAILNET.CREATE.TKA", "TAILNET.DELETE.LOGSTREAM_ENDPOINT",
199    "TAILNET.DELETE.POSTURE_INTEGRATION", "TAILNET.DELETE.TKA",
200    "TAILNET.DISABLE.COLLECT_POSTURE_IDENTITY", "TAILNET.DISABLE.COLLECT_SERVICES",
201    "TAILNET.DISABLE.FILE_SHARING", "TAILNET.DISABLE.GEOSTEERING", "TAILNET.DISABLE.HTTPS",
202    "TAILNET.DISABLE.LOG_EXIT_FLOWS", "TAILNET.DISABLE.MACHINE_APPROVAL_NEEDED",
203    "TAILNET.DISABLE.MAGIC_DNS", "TAILNET.DISABLE.MULLVAD_VPN",
204    "TAILNET.DISABLE.NETWORK_FLOW_LOGGING", "TAILNET.DISABLE.SCIM", "TAILNET.DISABLE.TKA",
205    "TAILNET.DISABLE.USER_APPROVAL_REQUIRED", "TAILNET.ENABLE.COLLECT_POSTURE_IDENTITY",
206    "TAILNET.ENABLE.COLLECT_SERVICES", "TAILNET.ENABLE.FILE_SHARING",
207    "TAILNET.ENABLE.GEOSTEERING", "TAILNET.ENABLE.HTTPS", "TAILNET.ENABLE.LOG_EXIT_FLOWS",
208    "TAILNET.ENABLE.MACHINE_APPROVAL_NEEDED", "TAILNET.ENABLE.MAGIC_DNS",
209    "TAILNET.ENABLE.MULLVAD_VPN", "TAILNET.ENABLE.NETWORK_FLOW_LOGGING",
210    "TAILNET.ENABLE.SCIM", "TAILNET.ENABLE.TKA", "TAILNET.ENABLE.USER_APPROVAL_REQUIRED",
211    "TAILNET.JOIN", "TAILNET.JOIN_WAITLIST.FEATURE", "TAILNET.LEAVE",
212    "TAILNET.UPDATE.ACCOUNT_EMAIL", "TAILNET.UPDATE.ACL", "TAILNET.UPDATE.DNS_CONFIG",
213    "TAILNET.UPDATE.LOGSTREAM_ENDPOINT", "TAILNET.UPDATE.MAX_KEY_DURATION",
214    "TAILNET.UPDATE.POSTURE_INTEGRATION", "TAILNET.UPDATE.SECURITY_EMAIL",
215    "TAILNET.UPDATE.SUPPORT_EMAIL", "TAILNET.UPDATE.TCD", "TAILNET.UPDATE.TKA",
216    "TAILNET.VERIFY.ACCOUNT_EMAIL", "TAILNET.VERIFY.SECURITY_EMAIL",
217    "TAILNET.VERIFY.SUPPORT_EMAIL", "USER.APPROVE", "USER.CREATE", "USER.DELETE",
218    "USER.INVITE", "USER.PUSH_USER.ATTRIBUTES", "USER.RESEND.TAILNET_INVITE",
219    "USER.RESTORE", "USER.RESTORE_GLOBAL", "USER.SUSPEND", "USER.SUSPEND_GLOBAL",
220    "USER.UPDATE.USER_ROLE", "WEBHOOK_ENDPOINT.CREATE", "WEBHOOK_ENDPOINT.DELETE",
221    "WEBHOOK_ENDPOINT.UPDATE.SECRET", "WEBHOOK_ENDPOINT.UPDATE.SUBSCRIBED_EVENTS",
222    "WEB_INTERFACE.LOGIN", "WEB_INTERFACE.LOGOUT", "PAM_CONNECTOR.CREATE",
223    "PAM_CONNECTOR.CREATE.ACCESS_TOKEN", "PAM_CONNECTOR.DELETE",
224    "PAM_CONNECTOR.DISABLE.ACCESS_TOKEN", "PAM_CONNECTOR.UPDATE", "PAM_SERVICE.CREATE",
225    "PAM_SERVICE.DELETE", "PAM_SERVICE.UPDATE", "PAM_SERVICE_ACCOUNT.CREATE",
226    "PAM_SERVICE_ACCOUNT.CREATE.ACCESS_TOKEN", "PAM_SERVICE_ACCOUNT.DELETE",
227    "PAM_SERVICE_ACCOUNT.UPDATE", "PAM_SETTINGS.CREATE.CUSTOM_DOMAIN",
228    "PAM_SETTINGS.CREATE.NOTIFICATION", "PAM_SETTINGS.CREATE.RECORDING_STORAGE",
229    "PAM_SETTINGS.DELETE.CUSTOM_DOMAIN", "PAM_SETTINGS.DELETE.NOTIFICATION",
230    "PAM_SETTINGS.DELETE.RECORDING_STORAGE", "PAM_SETTINGS.UPDATE",
231    "PAM_SETTINGS.UPDATE.CUSTOM_DOMAIN", "PAM_SETTINGS.UPDATE.NOTIFICATION",
232    "PAM_SETTINGS.UPDATE.SETUP_WIZARD",
233];
234
235pub const KNOWN_VALUES: &[KnownValues] = &[
236    ("LogType", LOG_TYPES),
237    (
238        "LogstreamEndpointConfiguration.destinationType",
239        DESTINATION_TYPES,
240    ),
241    (
242        "LogstreamEndpointConfiguration.compressionFormat",
243        COMPRESSION_FORMATS,
244    ),
245    (
246        "LogstreamEndpointConfiguration.s3AuthenticationType",
247        S3_AUTHENTICATION_TYPES,
248    ),
249    ("ConfigurationAuditLog.type", AUDIT_LOG_TYPES),
250    ("ConfigurationAuditLog.origin", AUDIT_ORIGINS),
251    ("ConfigurationAuditLog.actor.type", AUDIT_ACTOR_TYPES),
252    ("ConfigurationAuditLog.target.type", AUDIT_TARGET_TYPES),
253    (
254        "ConfigurationAuditLog.target.property",
255        AUDIT_TARGET_PROPERTIES,
256    ),
257    ("ConfigurationAuditLog.action", AUDIT_ACTIONS),
258    ("ConnectionCounts.proto", FLOW_PROTOCOLS),
259    ("?event[]", AUDIT_EVENTS),
260];
261
262model! {
263    /// One configuration change, as the audit log records it.
264    ConfigurationAuditLog {
265        event_time: "eventTime" => String,
266        /// One of [`AUDIT_LOG_TYPES`].
267        log_type: "type" => String,
268        /// Set where the rate limiter held the record back, naming the time it
269        /// was enqueued rather than the time it was written.
270        deferred_at: "deferredAt" => String,
271        /// Shared by every event that came out of one operation.
272        event_group_id: "eventGroupID" => String,
273        /// One of [`AUDIT_ORIGINS`].
274        origin: "origin" => String,
275        actor: "actor" => AuditActor,
276        target: "target" => AuditTarget,
277        /// One of [`AUDIT_ACTIONS`].
278        action: "action" => String,
279        /// `target.property` before the change; of whatever shape that
280        /// property has.
281        old: "old" => Value,
282        /// `target.property` after the change.
283        new: "new" => Value,
284        /// A reason, where the caller gave one.
285        action_details: "actionDetails" => String,
286        /// Present where the change failed, and readable by the person who
287        /// attempted it.
288        error: "error" => String,
289    }
290
291    /// Who or what made the change.
292    AuditActor as "ConfigurationAuditLog.actor" {
293        /// A user ID or a node ID, depending on `type`.
294        id: "id" => String,
295        /// One of [`AUDIT_ACTOR_TYPES`].
296        actor_type: "type" => String,
297        /// As it was at the time, not as it is now.
298        login_name: "loginName" => String,
299        display_name: "displayName" => String,
300        tags: "tags" => Vec<String>,
301    }
302
303    /// What the change was made to.
304    AuditTarget as "ConfigurationAuditLog.target" {
305        id: "id" => String,
306        /// As it was at the time.
307        name: "name" => String,
308        /// One of [`AUDIT_TARGET_TYPES`].
309        target_type: "type" => String,
310        /// Only meaningful where `type` is `NODE`.
311        is_ephemeral: "isEphemeral" => bool,
312        /// One of [`AUDIT_TARGET_PROPERTIES`]; what `old` and `new` hold.
313        property: "property" => String,
314    }
315
316    /// Traffic between two addresses over one protocol.
317    ConnectionCounts {
318        /// One of [`FLOW_PROTOCOLS`], or the protocol number for anything else.
319        proto: "proto" => String,
320        /// `addr:port`.
321        src: "src" => String,
322        /// `addr:port`.
323        dst: "dst" => String,
324        tx_pkts: "txPkts" => i64,
325        tx_bytes: "txBytes" => i64,
326        rx_pkts: "rxPkts" => i64,
327        rx_bytes: "rxBytes" => i64,
328    }
329
330    /// One node's traffic over one interval, by the path it took.
331    NetworkFlowLog {
332        logged: "logged" => String,
333        node_id: "nodeId" => String,
334        start: "start" => String,
335        end: "end" => String,
336        /// Tailscale address to Tailscale address.
337        virtual_traffic: "virtualTraffic" => Vec<ConnectionCounts>,
338        /// Through a subnet router.
339        subnet_traffic: "subnetTraffic" => Vec<ConnectionCounts>,
340        /// Through an exit node.
341        exit_traffic: "exitTraffic" => Vec<ConnectionCounts>,
342        /// The underlying transport, which is what the other three ride on.
343        physical_traffic: "physicalTraffic" => Vec<ConnectionCounts>,
344    }
345
346    /// Where a log stream goes and how it authenticates.
347    ///
348    /// Most of this is conditional on `destinationType`: the `s3*` fields
349    /// apply to S3, the `gcs*` fields to GCS, and `url`, `user` and `token` to
350    /// the vendors.
351    LogstreamEndpointConfiguration {
352        /// One of [`LOG_TYPES`].
353        log_type: "logType" => String,
354        /// One of [`DESTINATION_TYPES`].
355        destination_type: "destinationType" => String,
356        /// Often empty for S3, where the official endpoint is used.
357        url: "url" => String,
358        user: "user" => String,
359        /// A wait between uploads. Logs that do not fit in one upload are sent
360        /// in several regardless.
361        upload_period_minutes: "uploadPeriodMinutes" => i64,
362        /// One of [`COMPRESSION_FORMATS`], defaulting to `none`.
363        compression_format: "compressionFormat" => String,
364        token: "token" => Secret,
365        s3_bucket: "s3Bucket" => String,
366        s3_region: "s3Region" => String,
367        s3_key_prefix: "s3KeyPrefix" => String,
368        /// One of [`S3_AUTHENTICATION_TYPES`].
369        s3_authentication_type: "s3AuthenticationType" => String,
370        s3_access_key_id: "s3AccessKeyId" => String,
371        s3_secret_access_key: "s3SecretAccessKey" => Secret,
372        /// The role Tailscale assumes under `rolearn` authentication.
373        s3_role_arn: "s3RoleArn" => String,
374        /// What Tailscale presents to AWS under `rolearn` authentication; see
375        /// [`AwsExternalId`].
376        s3_external_id: "s3ExternalId" => String,
377        gcs_bucket: "gcsBucket" => String,
378        gcs_key_prefix: "gcsKeyPrefix" => String,
379        gcs_scopes: "gcsScopes" => Vec<String>,
380        /// Workload identity credentials, as GCS's own JSON document.
381        gcs_credentials: "gcsCredentials" => Secret,
382    }
383
384    /// How a log stream proves to AWS that it is this tailnet.
385    ///
386    /// The pair goes in the AWS role's trust policy, which is what makes
387    /// `rolearn` authentication work without a stored access key.
388    AwsExternalId {
389        external_id: "externalId" => String,
390        tailscale_aws_account_id: "tailscaleAwsAccountId" => String,
391    }
392
393    /// Whether the endpoint is being reached, and how well.
394    LogstreamEndpointPublishingStatus {
395        last_activity: "lastActivity" => String,
396        last_error: "lastError" => String,
397        max_body_size: "maxBodySize" => i64,
398        num_bytes_sent: "numBytesSent" => i64,
399        num_entries_sent: "numEntriesSent" => i64,
400        num_spoofed_entries: "numSpoofedEntries" => i64,
401        num_total_requests: "numTotalRequests" => i64,
402        num_failed_requests: "numFailedRequests" => i64,
403        rate_bytes_sent: "rateBytesSent" => f64,
404        rate_entries_sent: "rateEntriesSent" => f64,
405        rate_total_requests: "rateTotalRequests" => f64,
406        rate_failed_requests: "rateFailedRequests" => f64,
407    }
408
409    // -----------------------------------------------------------------------
410    // The shapes the routes carry.
411    // -----------------------------------------------------------------------
412
413    /// A page of the configuration audit log.
414    ///
415    /// `version` and `tailnet` come back beside the records, which is why this
416    /// is not simply a list.
417    AuditLogPage as "GET /tailnet/{tailnet}/logging/configuration 200" {
418        version: "version" => String,
419        tailnet: "tailnet" => String,
420        logs: "logs" => Vec<ConfigurationAuditLog>,
421    }
422
423    /// A page of the network flow log.
424    NetworkFlowLogPage as "GET /tailnet/{tailnet}/logging/network 200" {
425        logs: "logs" => Vec<NetworkFlowLog>,
426    }
427
428    /// What asking for an AWS external identifier sends.
429    AwsExternalIdRequest as "POST /tailnet/{tailnet}/aws-external-id body" {
430        /// Whether the identifier may be used for more than one role.
431        reusable: "reusable" => bool,
432    }
433
434    /// What checking an AWS trust policy sends.
435    AwsTrustPolicyRequest
436        as "POST /tailnet/{tailnet}/aws-external-id/{id}/validate-aws-trust-policy body" {
437        /// The role Tailscale should be able to assume.
438        role_arn: "roleArn" => String,
439    }
440
441    /// What a failed trust-policy check answers with.
442    ///
443    /// The one place in the description where a failure has a shape of its own
444    /// rather than the shared `Error`: the check answers 422 with what is
445    /// wrong with the policy.
446    AwsTrustPolicyFailure
447        as "POST /tailnet/{tailnet}/aws-external-id/{id}/validate-aws-trust-policy 422" {
448        message: "message" => String,
449    }
450}