kube-apiserver Configuration (v1beta1)

Пакет v1beta1 є версією v1beta1 API.

Типи ресурсів

TracingConfiguration

З’являється в:

TracingConfiguration надає версійні налаштування для клієнтів трасування OpenTelemetry.

ПолеОпис
endpoint
string

Точка доступу колектора, до якого цей компонент буде надсилати трасування. Зʼєднання є незахищеним і наразі не підтримує TLS. Рекомендовано не встановлювати, стандартна точка доступу для otlp grpc — localhost:4317.

samplingRatePerMillion
int32

SamplingRatePerMillion — це кількість зразків для збору на мільйон відрізків. Рекомендовано не встановлювати. Якщо не задано, семплер дотримується частоти дискретизації батьківського діапазону а якщо ні, то ніколи не робить вибірки.

AuthenticationConfiguration

AuthenticationConfiguration надає версійні налаштування для автентифікації.

ПолеОпис
apiVersion
string
apiserver.k8s.io/v1beta1
kind
string
AuthenticationConfiguration
jwt [Обовʼязково]
[]JWTAuthenticator

jwt — це список автентифікаторів для автентифікації користувачів Kubernetes за допомогою токенів, що відповідають стандартам JWT. Автентифікатор спробує розібрати необроблений ID токен, перевірити, чи підписаний він налаштованим видавцем. Публічний ключ для перевірки підпису виявляється з публічної кінцевої точки видавця за допомогою OIDC discovery. Для вхідного токена кожен JWT автентифікатор буде спробуваний у порядку, в якому він зазначений у цьому списку. Однак зверніть увагу, що інші автентифікатори можуть працювати до або після JWT автентифікаторів. Конкретне положення JWT автентифікаторів щодо інших автентифікаторів не визначено і не стабільне між випусками. Оскільки кожен JWT автентифікатор повинен мати унікальний URL видавця, максимум один JWT автентифікатор спробує криптографічно перевірити токен.

Мінімально допустимий JWT payload повинен містити наступні заявки:

{
"iss": "https://issuer.example.com",
"aud": ["audience"],
"exp": 1234567890,
"": "username"
}

anonymous [Обовʼязково]
AnonymousAuthConfig

Якщо присутній --anonymous-auth не повинен бути встановлений

AuthorizationConfiguration

ПолеОпис
apiVersion
string
apiserver.k8s.io/v1beta1
kind
string
AuthorizationConfiguration
authorizers [Обовʼязково]
[]AuthorizerConfiguration

Authorizers — це впорядкований список авторизаторів для авторизації запитів. Це схоже на прапорець — --authorization-modes kube-apiserver. Має бути принаймні один.

EgressSelectorConfiguration

EgressSelectorConfiguration надає версійні налаштування для клієнтів вибору egress.

ПолеОпис
apiVersion
string
apiserver.k8s.io/v1beta1
kind
string
EgressSelectorConfiguration
egressSelections [Обовʼязково]
[]EgressSelection

connectionServices містить список налаштувань клієнтів вибору egress.

TracingConfiguration

TracingConfiguration надає версійні налаштування для клієнтів трасування.

ПолеОпис
apiVersion
string
apiserver.k8s.io/v1beta1
kind
string
TracingConfiguration
TracingConfiguration [Обовʼязково]
TracingConfiguration
(Елементи TracingConfiguration вбудовані в цей тип.)

Вбудуйте структуру конфігурації трасування компонента.

AnonymousAuthCondition

З’являється в:

AnonymousAuthCondition описує стан, за якого анонімні автентифікації мають бути увімкнені.

ПолеОпис
path [Обовʼязково]
string

Шлях для якого увімкнено анонімну автентифікацію.

AnonymousAuthConfig

З’являється в:

AnonymousAuthConfig надає конфігурацію для анонімного автентифікатора.

ПолеОпис
enabled [Обовʼязково]
bool
Опис не надано.
conditions [Обовʼязково]
[]AnonymousAuthCondition

Якщо встановлено, анонімна автентифікація дозволена, тільки якщо запит відповідає одній з умов.

AudienceMatchPolicyType

(Аліас string)

З’являється в:

AudienceMatchPolicyType — це набір допустимих значень для issuer.audienceMatchPolicy

AuthorizerConfiguration

З’являється в:

ПолеОпис
type [Обовʼязково]
string

Тип відноситься до типу авторизатора "Webhook" підтримується у загальному API сервері Інші API сервери можуть підтримувати додаткові типи авторизаторів такі як Node, RBAC, ABAC і т.д.

name [Обовʼязково]
string

Імʼя, яке використовується для опису webhook Це явно використовується в моніторинговій машинерії для метрик Примітка: Імена повинні бути мітками DNS1123, такими як myauthorizername або піддоменами, такими як myauthorizer.example.domain Обовʼязково, стандартного значення немає

webhook [Обовʼязково]
WebhookConfiguration

Webhook визначає налаштування для Webhook авторизатора Має бути визначено, коли Type=Webhook Не повинно бути визначено, коли Type!=Webhook

ClaimMappings

З’являється в:

ClaimMappings надає налаштування для мапінгу claims.

ПолеОпис
username [Обовʼязково]
PrefixedClaimOrExpression

username представляє опцію для атрибуту username. Значення claim має бути одним рядком. Те ж саме, що й прапори --oidc-username-claim та --oidc-username-prefix. Якщо встановлено username.expression, вираз повинен видавати значення рядка. Якщо username.expression використовує 'claims.email', тоді 'claims.email_verified' повинен використовуватися в username.expression або extra[].valueExpression or claimValidationRules[].expression. Приклад виразу правила валідації claims, який відповідає валідації, що автоматично застосовується, коли username.claim встановлено на 'email', — це 'claims.?email_verified.orValue(true)'.

У підході на основі прапорів, прапорці --oidc-username-claim та --oidc-username-prefix є необовʼязковими. Якщо --oidc-username-claim не встановлено, стандартне значення — "sub". Для конфігурації автентифікації стандартне значення для claim або prefix відсутні. Claim та prefix повинні бути встановлені явно. Для claim, якщо прапорець --oidc-username-claim не було встановлено за допомогою старого підходу, налаштуйте username.claim="sub" у конфігурації автентифікації. Для prefix: (1) --oidc-username-prefix="-", префікс не додавався до імені користувача. Для такої ж поведінки за допомогою конфігурації автентифікації, встановіть username.prefix="" (2) --oidc-username-prefix="" та --oidc-username-claim != "email", префікс був "<значення --oidc-issuer-url>#". Для такої ж поведінки за допомогою конфігурації автентифікації, встановіть username.prefix="#" (3) --oidc-username-prefix="". Для такої ж поведінки за допомогою конфігурації автентифікації, встановіть username.prefix=""

groups
PrefixedClaimOrExpression

groups представляє опцію для атрибуту groups. Значення claim має бути рядком або масивом рядків. Якщо groups.claim встановлено, префікс повинен бути вказаний (і може бути порожнім рядком). Якщо groups.expression встановлено, вираз повинен видавати значення рядка або масиву рядків. "", [], і null значення розглядаються як відсутність мапінгу групи.

uid
ClaimOrExpression

uid представляє опцію для атрибуту uid. Claim повинен бути одним рядковим claim. Якщо uid.expression встановлено, вираз повинен видавати значення рядка.

extra
[]ExtraMapping

extra представляє опцію для атрибуту extra. вираз повинен видавати значення рядка або масиву рядків. Якщо значення порожнє, мапінг extra не буде присутнім.

жорстко закодований extra ключ/значення

  • key: "foo" valueExpression: "'bar'" Це призведе до появи extra атрибуту — foo: ["bar"]

жорстко закодований ключ, значення копіюється з значення claim

  • key: "foo" valueExpression: "claims.some_claim" Це призведе до появи extra атрибуту - foo: [значення some_claim]

жорстко закодований ключ, значення виводиться з значення claim

  • key: "admin" valueExpression: '(has(claims.is_admin) && claims.is_admin) ? "true":""' Це призведе до:
  • якщо claim is_admin присутній та true, extra атрибут — admin: ["true"]
  • якщо claim is_admin присутній та false або claim is_admin не присутній, extra атрибут не буде доданий

ClaimOrExpression

З’являється в:

ClaimOrExpression надає налаштування для одного claim або виразу.

ПолеОпис
claim
string

claim — це JWT claim для використання. Має бути встановлено або claim, або expression. Взаємовиключне з expression.

expression
string

expression представляє вираз, який буде оцінюватися за допомогою CEL.

CEL вирази мають доступ до вмісту claims токену, організованих у CEL змінну:

  • 'claims' — це map з назвами claims до значень claims. Наприклад, змінну з назвою 'sub' можна отримати доступом як 'claims.sub'. Вкладені claims можна отримати доступом за допомогою нотації через крапку, наприклад 'claims.foo.bar'.

Документація з CEL: https://kubernetes.io/docs/reference/using-api/cel/

Взаємовиключне з claim.

ClaimValidationRule

З’являється в:

ClaimValidationRule забезпечує конфігурацію для одного правила валідації заяви.

ПолеОпис
claim
string

claim — це імʼя необхідної заяви. Такий же, як прапорець --oidc-required-claim. Підтримуються тільки ключі заяв у форматі рядка. Взаємно виключає з expression та message.

requiredValue
string

requiredValue — це значення необхідної заяви. Такий же, як прапорець --oidc-required-claim. Підтримуються тільки значення заяв у форматі рядка. Якщо claim встановлено, а requiredValue не встановлено, заява повинна бути присутня з значенням, яке дорівнює порожньому рядку. Взаємновиключнє з expression та message.

expression
string

expression представляє вираз, який буде оцінений CEL. Повинен повернути булеве значення.

Вирази CEL мають доступ до вмісту заявок токена, організованого у змінну CEL:

  • 'claims' — це відображення імен заявок на значення заявок. Наприклад, змінна з імʼям 'sub' може бути доступна як 'claims.sub'. Вкладені заяви можна отримати, використовуючи крапкову нотацію, наприклад 'cl ims.foo.bar'. Повинен повернути true для успішної валідації.

Документація з CEL: https://kubernetes.io/docs/reference/using-api/cel/

Взаємовиключне з claim та requiredValue.

message
string

message налаштовує повідомлення про помилку, яке повертається, коли expression повертає false. message є літералним рядком. Взаємовиключне з claim та requiredValue.

Connection

З’являється в:

Connection надає конфігурацію для одного клієнта вибору egress зʼєднання.

ПолеОпис
proxyProtocol [Обовʼязково]
ProtocolType

Protocol — це протокол, який використовується для зʼєднання клієнта з konnectivity сервером.

transport
Transport

Transport визначає транспортні конфігурації, які ми використовуємо для зʼєднання з konnectivity сервером. Це обовʼязково, якщо ProxyProtocol — це HTTPConnect або GRPC.

EgressSelection

З’являється в:

EgressSelection надає конфігурацію для одного клієнта вибору egress зʼєднання.

ПолеОпис
name [Обовʼязково]
string

name — це назва вибору egress зʼєднання. На даний момент підтримуються значення "controlplane", "master", "etcd" та "cluster". Селекторо egress зʼєднання "master" застарілий і його рекомендується замінити на "controlplane".

connection [Обовʼязково]
Connection

connection — це точна інформація, яка використовується для налаштування вибору вихідного зʼєднання.

ExtraMapping

З’являється в:

ExtraMapping надає конфігурацію для одного додаткового зіставлення.

ПолеОпис
key [Обовʼязково]
string

key — це рядок, який використовується як ключ додаткового атрибуту. key повинен бути шляхом з префіксом домену (наприклад, example.org/foo). Усі символи перед першим "/" повинні бути дійсним субдоменом, як це визначено RFC 1123. Усі символи, що йдуть після першого "/", повинні бути дійсними символами шляху HTTP, як це визначено RFC 3986. key повинен бути написаний малими літерами. Має бути унікальним.

valueExpression [Обовʼязково]
string

valueExpression — це вираз CEL для отримання значення додаткового атрибуту. valueExpression повинен повертати значення рядка або масиву рядків. "", [], та null значення розглядаються як відсутність додаткового зіставлення. Порожні рядкові значення, що містяться у масиві рядків, відфільтровуються.

Вирази CEL мають доступ до вмісту токенів, організованих у змінну CEL:

  • 'claims' — це зіставлення імен вимог до значень вимог. Наприклад, змінна з імʼям 'sub' може бути доступна як 'claims.sub'. Вкладені вимоги можуть бути доступні за допомогою крапкової нотації, наприклад, 'claims.foo.bar'.

Документація з CEL: https://kubernetes.io/docs/reference/using-api/cel/

Issuer

З’являється в:

Issuer надає конфігурацію для специфічних налаштувань зовнішнього постачальника.

discoveryURL повинен відрізнятися від url. Має бути унікальним серед усіх JWT автентифікаторів. Зверніть увагу, що конфігурація вибору вихідного зʼєднання не використовується для цього мережевого зʼєднання.

ПолеОпис
url [Обовʼязково]
string

url вказує на URL видавця у форматі https://url або https://url/path. Це повинно відповідати вимозі "iss" у наданому JWT і видавцю, який повертається discovery. Таке саме значення, як і прапорець --oidc-issuer-url. Інформація про discovery отримується з "{url}/.well-known/openid-configuration", якщо discoveryURL не перевизначений. Вимагається бути унікальним серед усіх JWT автентифікаторів. Зверніть увагу, що конфігурація вибору вихідного зʼєднання не використовується для цього мережевого зʼєднання.

discoveryURL
string

discoveryURL, якщо вказано, перевизначає URL, використовуваний для отримання інформації про discovery, замість використання "{url}/.well-known/openid-configuration". Використовується точне вказане значення, тому "/.well-known/openid-configuration" повинен бути включений у discoveryURL, якщо це необхідно.

Поле "issuer" у отриманій інформації про discovery повинно відповідати полю "issuer.url" в AuthenticationConfiguration і буде використовуватися для перевірки вимоги "iss" у наданому JWT. Це призначено для сценаріїв, коли загальновідомі та точки доступу jwks розміщуються в іншому місці, ніж видавець (наприклад, локально у кластері).

Приклад: URL discovery, який експонується за допомогою сервіса kubernetes 'oidc' у просторі імен 'oidc-namespace' і інформація про discovery доступна за адресою '/.well-known/openid-configuration'. discoveryURL: "https://oidc.oidc-namespace/.well-known/openid-configuration" certificateAuthority використовується для перевірки TLS-зʼєднання, і імʼя хоста на сертифікаті повинно бути налаштовано на 'oidc.oidc-namespace'.

curl https://oidc.oidc-namespace/.well-known/openid-configuration (.discoveryURL поле) { issuer: "https://oidc.example.com" (.url поле) }

certificateAuthority
string

certificateAuthority містить сертифікати уповноважених органів, закодовані в PEM, які використовуються для перевірки зʼєднання під час отримання інформації про discovery. Якщо не встановлено, використовується системний перевіряльник. Таке саме значення, як і вміст файлу, на який посилається прапорець --oidc-ca-file.

audiences [Обовʼязково]
[]string

audiences — це набір прийнятних аудиторій, для яких повинен бути виданий JWT. Щонайменше один з записів повинен відповідати вимозі "aud" у наданих JWT. Таке саме значення, як і прапорець --oidc-client-id (хоча це поле підтримує масив). Має бути непорожнім.

audienceMatchPolicy
AudienceMatchPolicyType

audienceMatchPolicy визначає, як поле "audiences" використовується для співставлення з вимогою "aud" у наданому JWT. Допустимі значення:

  1. "MatchAny", коли вказано кілька аудиторій
  2. порожнє (або не встановлене) або "MatchAny", коли вказано одну аудиторію.
  • MatchAny: вимога "aud" у наданому JWT повинна відповідати щонайменше одному з записів у полі "audiences". Наприклад, якщо "audiences" містить ["foo", "bar"], вимога "aud" у наданому JWT повинна містити або "foo", або "bar" (і може містити обидва).

  • "": Політика співставлення може бути порожньою (або не встановленою), коли у полі "audiences" вказано одну аудиторію. Вимога "aud" у наданому JWT повинна містити одну аудиторію (і може містити інші).

Для більш точного перевірки аудиторій використовуйте claimValidationRules. Приклад: claimValidationRule[].expression: 'sets.equivalent(claims.aud, ["bar", "foo", "baz"])', щоб вимагати точного співпадіння.

JWTAuthenticator

З’являється в:

JWTAuthenticator надає конфігурацію для одного JWT автентифікатора.

ПолеОпис
issuer [Обовʼязково]
Issuer

issuer містить основні параметри підключення постачальника OIDC.

claimValidationRules
[]ClaimValidationRule

claimValidationRules — це правила, які застосовуються для перевірки вимог токенів для автентифікації користувачів.

claimMappings [Обовʼязково]
ClaimMappings

claimMappings вказує вимоги токена, які слід вважати атрибутами користувача.

userValidationRules
[]UserValidationRule

userValidationRules — це правила, які застосовуються до кінцевого користувача перед завершенням автентифікації. Ці правила дозволяють застосовувати інваріанти до вхідних ідентичностей, наприклад, забороняти використання префіксу system:, який зазвичай використовується компонентами Kubernetes. Правила перевірки логічно повʼязані оператором AND і всі повинні повернути true, щоб перевірка пройшла.

PrefixedClaimOrExpression

З’являється в:

PrefixedClaimOrExpression надає конфігурацію для одного префіксованого вимоги або виразу.

ПолеОпис
claim
string

claim — це JWT вимога для використання. Взаємовиключне з expression.

prefix
string

prefix додається до значення вимоги, щоб уникнути конфліктів з існуючими іменами. prefix необхідно встановити, якщо встановлено claim, і він може бути порожнім рядком. Взаємовиключне з expression.

expression
string

expression представляє вираз, який буде оцінюватися за допомогою CEL.

CEL вирази мають доступ до вмісту вимог токену, організованого у змінну CEL:

  • 'claims' є зіставленням імен вимог до значень вимог. Наприклад, змінну з іменем 'sub' можна отримати як 'claims.sub'. Вкладені вимоги можна отримати за допомогою нотації з крапкою, наприклад 'claims.foo.bar'.

Документація з CEL: https://kubernetes.io/docs/reference/using-api/cel/

Взаємовиключне з claim і prefix.

TCPTransport

З’являється в:

TCPTransport надає інформацію для підключення до konnectivity серверу через TCP

ПолеОпис
url [Обовʼязково]
string

URL є місцем розташування konnectivity серверу для підключення. Як приклад, це може бути "https://127.0.0.1:8131"

tlsConfig
TLSConfig

TLSConfig є конфігурацією, необхідною для використання TLS при підключенні до konnectivity серверу

TLSConfig

З’являється в:

TLSConfig надає інформацію для автентифікації для підключення до konnectivity серверу. Використовується тільки з TCPTransport

ПолеОпис
caBundle
string

caBundle — це місцезнаходження файлу CA, який буде використовуватися для визначення довіри до konnectivity серверу. Має бути відсутнім/порожнім, якщо TCPTransport.URL починається з http:// Якщо відсутній, а TCPTransport.URL починається з https://, стандартно використовуються системні корені довіри.

clientKey
string

clientKey — це місцезнаходження файлу клієнтського ключа, який буде використовуватися в mtls рукостисканнях з konnectivity сервером. Має бути відсутнім/порожнім, якщо TCPTransport.URL починається з http:// Має бути налаштований, якщо TCPTransport.URL починається з https://

clientCert
string

clientCert — це місцезнаходження файлу клієнтського сертифіката, який буде використовуватися в mtls рукостисканнях з konnectivity сервером. Має бути відсутнім/порожнім, якщо TCPTransport.URL починається з http:// Має бути налаштований, якщо TCPTransport.URL починається з https://

Transport

З’являється в:

Transport визначає конфігурації транспорту, які ми використовуємо для зʼєднання з konnectivity сервером

ПолеОпис
tcp
TCPTransport

TCP — це конфігурація TCP для звʼязку з konnectivity сервером через TCP. ProxyProtocol GRPC наразі не підтримується з TCP транспортом. Вимагає налаштування хоча б одного з TCP або UDS

uds
UDSTransport

UDS — це конфігурація UDS для звʼязку з konnectivity сервером ерез UDS. Вимагає налаштування хоча б одного з TCP або UDS

UDSTransport

З’являється в:

UDSTransport надає інформацію для підключення до konnectivity серверу через UDS

ПолеОпис
udsName [Обовʼязкове]
string

UDSName — це назва unix domain socket для підключення до konnectivity серверу. Не використовує префікс unix://. (Наприклад: /etc/srv/kubernetes/konnectivity-server/konnectivity-server.socket)

UserValidationRule

З’являється в:

UserValidationRule надає конфігурацію для одного правила валідації інформації про користувача.

ПолеОпис
expression [Обовʼязкове]
string

expression представляє вираз, який буде оцінено за допомогою CEL. Має повернути true, щоб перевірка була успішною.

CEL вирази мають доступ до вмісту UserInfo, організованого в CEL змінну:

Документація з CEL: https://kubernetes.io/docs/reference/using-api/cel/

message
string

message налаштовує повернуте повідомлення про помилку, коли правилоило повертає false. message є літеральним рядком.

WebhookConfiguration

З’являється в:

ПолеОпис
authorizedTTL [Обовʼязкове]
meta/v1.Duration

Тривалість кешування відповідей 'authorized' від webhook авторизатора. Те ж саме, що і встановлення --authorization-webhook-cache-authorized-ttl прапорця. Стандартно: 5m0s

unauthorizedTTL [Обовʼязкове]
meta/v1.Duration

Тривалість кешування відповідей 'unauthorized' від webhook авторизатора. Те ж саме, що і встановлення --authorization-webhook-cache-unauthorized-ttl прапорця. Стандартно: 30s

timeout [Обовʼязкове]
meta/v1.Duration

Тайм-аут для запиту webhook. Максимальне допустиме значення - 30s. Обовʼязкове, стандартного значення немає.

subjectAccessReviewVersion [Обовʼязкове]
string

Версія API authorization.k8s.io SubjectAccessReview, яка буде відправлена та очікувана від webhook. Те ж саме, що і встановлення --authorization-webhook-version прапорця. Допустимі значення: v1beta1, v1. Обовʼязкове, стандартного значення немає

matchConditionSubjectAccessReviewVersion [Обовʼязкове]
string

MatchConditionSubjectAccessReviewVersion визначає версію SubjectAccessReview, для якої оцінюються CEL вирази. Допустимі значення: v1. Обовʼязкове, стандартного значення немає

failurePolicy [Обовʼязкове]
string

Контролює рішення про авторизацію, коли запит webhook не вдається завершити або повертає некоректну відповідь або помилки при оцінці matchConditions. Допустимі значення:

  • NoOpinion: продовжувати до наступних авторизаторів, щоб перевірити, чи дозволяє запит хоча б один з них
  • Deny: відхилити запит без консультацій з наступними авторизаторами. Обовʼязкове, стандартного значення немає.
connectionInfo [Обовʼязкове]
WebhookConnectionInfo

ConnectionInfo визначає, як ми спілкуємося з webhook

matchConditions [Обовʼязкове]
[]WebhookMatchCondition

matchConditions — це список умов, які повинні бути виконані, щоб запит був відправлений на цей webhook. Порожній список matchConditions відповідає всім запитам. Максимум 64 умови відповідності дозволені.

Точна логіка відповідності (у порядку):

  1. Якщо хоча б одна matchCondition оцінюється як FALSE, то webhook пропускається.
  2. Якщо ВСІ matchConditions оцінюються як TRUE, то викликається webhook.
  3. Якщо хоча б одна matchCondition оцінюється як помилка (але жодна не є FALSE):
    • Якщо failurePolicy=Deny, то webhook відхиляє запит
    • Якщо failurePolicy=NoOpinion, то помилка ігнорується, і webhook пропускається

WebhookConnectionInfo

З’являється в:

ПолеОпис
type [Обовʼязкове]
string

Контролює, як webhook має спілкуватися з сервером. Допустимі значення:

  • KubeConfigFile: використовувати файл, вказаний у kubeConfigFile, для знаходження сервера.
  • InClusterConfig: використовувати конфігурацію у кластері для виклику SubjectAccessReview API, розміщеного kube-apiserver. Цей режим не дозволений для kube-apiserver.
kubeConfigFile [Обовʼязкове]
string

Шлях до KubeConfigFile для інформації про зʼєднання. Обовʼязкове, якщо connectionInfo.Type — KubeConfig

WebhookMatchCondition

З’являється в:

ПолеОпис
expression [Обовʼязкове]
string

expression представляє вираз, який буде оцінюватися CEL. Повинен оцінюватися як bool. CEL вирази мають доступ до вмісту SubjectAccessReview у версії v1. Якщо версія, вказана у змінній запиту subjectAccessReviewVersion, є v1beta1, вміст буде конвертовано у версію v1 перед оцінкою виразу CEL.

Документація з CEL: https://kubernetes.io/docs/reference/using-api/cel/

Змінено August 22, 2024 at 6:59 PM PST: upstream sync (b7f2b32b60)