Add support for Direct Workload Identity auth (#348)
This adds a new authentication mode, Direct Workload Identity Federation. This new mode permits authenticating to Google Cloud directly using the GitHub Actions OIDC token instead of proxying through a Google Cloud Service Account.
This commit is contained in:
parent
f105ef0cdb
commit
fe9207673e
@ -8,4 +8,9 @@ module.exports = {
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:prettier/recommended',
|
||||
],
|
||||
|
||||
// We have many situations where we accept and expect arbitrary JSON payloads.
|
||||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
},
|
||||
};
|
||||
|
157
.github/workflows/test.yml
vendored
157
.github/workflows/test.yml
vendored
@ -27,6 +27,10 @@ concurrency:
|
||||
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: 'bash'
|
||||
|
||||
jobs:
|
||||
unit:
|
||||
name: 'unit'
|
||||
@ -48,9 +52,13 @@ jobs:
|
||||
- name: 'npm test'
|
||||
run: 'npm run test'
|
||||
|
||||
credentials_json:
|
||||
|
||||
#
|
||||
# Direct Workload Identity Federation
|
||||
#
|
||||
direct_workload_identity_federation:
|
||||
if: ${{ github.event_name == 'push' || github.repository == github.event.pull_request.head.repo.full_name }}
|
||||
name: 'credentials_json'
|
||||
name: 'direct_workload_identity_federation'
|
||||
runs-on: '${{ matrix.os }}'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@ -60,6 +68,9 @@ jobs:
|
||||
- 'windows-latest'
|
||||
- 'macos-latest'
|
||||
|
||||
permissions:
|
||||
id-token: 'write'
|
||||
|
||||
steps:
|
||||
- uses: 'actions/checkout@v4'
|
||||
|
||||
@ -74,56 +85,33 @@ jobs:
|
||||
name: 'auth-default'
|
||||
uses: './'
|
||||
with:
|
||||
credentials_json: '${{ secrets.SERVICE_ACCOUNT_KEY_JSON }}'
|
||||
project_id: '${{ vars.PROJECT_ID }}'
|
||||
workload_identity_provider: '${{ vars.WIF_PROVIDER_NAME }}'
|
||||
|
||||
- id: 'setup-gcloud'
|
||||
name: 'setup-gcloud'
|
||||
uses: 'google-github-actions/setup-gcloud@main'
|
||||
- uses: 'google-github-actions/setup-gcloud@main'
|
||||
with:
|
||||
version: '>= 363.0.0'
|
||||
|
||||
- id: 'gcloud'
|
||||
name: 'gcloud'
|
||||
shell: 'bash'
|
||||
- name: 'gcloud'
|
||||
run: |-
|
||||
gcloud secrets versions access "latest" --secret "${{ vars.SECRET_NAME }}"
|
||||
|
||||
- id: 'auth-access-token'
|
||||
name: 'auth-access-token'
|
||||
uses: './'
|
||||
with:
|
||||
credentials_json: '${{ secrets.SERVICE_ACCOUNT_KEY_JSON }}'
|
||||
token_format: 'access_token'
|
||||
|
||||
- id: 'access-token'
|
||||
name: 'access-token'
|
||||
shell: 'bash'
|
||||
- id: 'oauth-federated-token'
|
||||
name: 'oauth-federated-token'
|
||||
run: |-
|
||||
curl https://secretmanager.googleapis.com/v1/projects/${{ steps.auth-access-token.outputs.project_id }}/secrets/${{ vars.SECRET_NAME }}/versions/latest:access \
|
||||
curl https://secretmanager.googleapis.com/v1/projects/${{ steps.auth-default.outputs.project_id }}/secrets/${{ vars.SECRET_NAME }}/versions/latest:access \
|
||||
--silent \
|
||||
--show-error \
|
||||
--fail \
|
||||
--header "Authorization: Bearer ${{ steps.auth-access-token.outputs.access_token }}"
|
||||
--header "Authorization: Bearer ${{ steps.auth-default.outputs.auth_token }}"
|
||||
|
||||
- id: 'auth-id-token'
|
||||
name: 'auth-id-token'
|
||||
uses: './'
|
||||
with:
|
||||
credentials_json: '${{ secrets.SERVICE_ACCOUNT_KEY_JSON }}'
|
||||
token_format: 'id_token'
|
||||
id_token_audience: 'https://secretmanager.googleapis.com/'
|
||||
id_token_include_email: true
|
||||
|
||||
- id: 'auth-sa-retries'
|
||||
name: 'auth-sa-retries'
|
||||
uses: './'
|
||||
with:
|
||||
retries: '2'
|
||||
backoff: '200'
|
||||
backoff_limit: '1000'
|
||||
credentials_json: '${{ secrets.SERVICE_ACCOUNT_KEY_JSON }}'
|
||||
|
||||
workload_identity_federation:
|
||||
#
|
||||
# Workload Identity Federation through a Service Account
|
||||
#
|
||||
workload_identity_federation_through_service_account:
|
||||
if: ${{ github.event_name == 'push' || github.repository == github.event.pull_request.head.repo.full_name }}
|
||||
name: 'workload_identity_federation'
|
||||
name: 'workload_identity_federation_through_service_account'
|
||||
runs-on: '${{ matrix.os }}'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@ -153,13 +141,11 @@ jobs:
|
||||
workload_identity_provider: '${{ vars.WIF_PROVIDER_NAME }}'
|
||||
service_account: '${{ vars.SERVICE_ACCOUNT_EMAIL }}'
|
||||
|
||||
- id: 'setup-gcloud'
|
||||
name: 'setup-gcloud'
|
||||
uses: 'google-github-actions/setup-gcloud@main'
|
||||
- uses: 'google-github-actions/setup-gcloud@main'
|
||||
with:
|
||||
version: '>= 363.0.0'
|
||||
|
||||
- id: 'gcloud'
|
||||
name: 'gcloud'
|
||||
shell: 'bash'
|
||||
- name: 'gcloud'
|
||||
run: |-
|
||||
gcloud secrets versions access "latest" --secret "${{ vars.SECRET_NAME }}"
|
||||
|
||||
@ -171,9 +157,74 @@ jobs:
|
||||
service_account: '${{ vars.SERVICE_ACCOUNT_EMAIL }}'
|
||||
token_format: 'access_token'
|
||||
|
||||
- id: 'oauth-token'
|
||||
name: 'oauth-token'
|
||||
run: |-
|
||||
curl https://secretmanager.googleapis.com/v1/projects/${{ steps.auth-access-token.outputs.project_id }}/secrets/${{ vars.SECRET_NAME }}/versions/latest:access \
|
||||
--silent \
|
||||
--show-error \
|
||||
--fail \
|
||||
--header "Authorization: Bearer ${{ steps.auth-access-token.outputs.access_token }}"
|
||||
|
||||
- id: 'id-token'
|
||||
name: 'id-token'
|
||||
uses: './'
|
||||
with:
|
||||
workload_identity_provider: '${{ vars.WIF_PROVIDER_NAME }}'
|
||||
service_account: '${{ vars.SERVICE_ACCOUNT_EMAIL }}'
|
||||
token_format: 'id_token'
|
||||
id_token_audience: 'https://secretmanager.googleapis.com/'
|
||||
id_token_include_email: true
|
||||
|
||||
|
||||
#
|
||||
# Service Account Key JSON
|
||||
#
|
||||
credentials_json:
|
||||
if: ${{ github.event_name == 'push' || github.repository == github.event.pull_request.head.repo.full_name }}
|
||||
name: 'credentials_json'
|
||||
runs-on: '${{ matrix.os }}'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os:
|
||||
- 'ubuntu-latest'
|
||||
- 'windows-latest'
|
||||
- 'macos-latest'
|
||||
|
||||
steps:
|
||||
- uses: 'actions/checkout@v4'
|
||||
|
||||
- uses: 'actions/setup-node@v4'
|
||||
with:
|
||||
node-version: '20.x'
|
||||
|
||||
- name: 'npm build'
|
||||
run: 'npm ci && npm run build'
|
||||
|
||||
- id: 'auth-default'
|
||||
name: 'auth-default'
|
||||
uses: './'
|
||||
with:
|
||||
credentials_json: '${{ secrets.SERVICE_ACCOUNT_KEY_JSON }}'
|
||||
|
||||
- uses: 'google-github-actions/setup-gcloud@main'
|
||||
with:
|
||||
version: '>= 363.0.0'
|
||||
|
||||
- name: 'gcloud'
|
||||
run: |-
|
||||
gcloud secrets versions access "latest" --secret "${{ vars.SECRET_NAME }}"
|
||||
|
||||
- id: 'auth-access-token'
|
||||
name: 'auth-access-token'
|
||||
uses: './'
|
||||
with:
|
||||
credentials_json: '${{ secrets.SERVICE_ACCOUNT_KEY_JSON }}'
|
||||
token_format: 'access_token'
|
||||
|
||||
- id: 'access-token'
|
||||
name: 'access-token'
|
||||
shell: 'bash'
|
||||
run: |-
|
||||
curl https://secretmanager.googleapis.com/v1/projects/${{ steps.auth-access-token.outputs.project_id }}/secrets/${{ vars.SECRET_NAME }}/versions/latest:access \
|
||||
--silent \
|
||||
@ -185,26 +236,26 @@ jobs:
|
||||
name: 'auth-id-token'
|
||||
uses: './'
|
||||
with:
|
||||
workload_identity_provider: '${{ vars.WIF_PROVIDER_NAME }}'
|
||||
service_account: '${{ vars.SERVICE_ACCOUNT_EMAIL }}'
|
||||
credentials_json: '${{ secrets.SERVICE_ACCOUNT_KEY_JSON }}'
|
||||
token_format: 'id_token'
|
||||
id_token_audience: 'https://secretmanager.googleapis.com/'
|
||||
id_token_include_email: true
|
||||
|
||||
- id: 'auth-wif-retries'
|
||||
name: 'auth-wif-retries'
|
||||
- id: 'auth-sa-retries'
|
||||
name: 'auth-sa-retries'
|
||||
uses: './'
|
||||
with:
|
||||
retries: '2'
|
||||
backoff: '200'
|
||||
backoff_limit: '1000'
|
||||
workload_identity_provider: '${{ vars.WIF_PROVIDER_NAME }}'
|
||||
service_account: '${{ vars.SERVICE_ACCOUNT_EMAIL }}'
|
||||
credentials_json: '${{ secrets.SERVICE_ACCOUNT_KEY_JSON }}'
|
||||
|
||||
#
|
||||
# This test ensures that the GOOGLE_APPLICATION_CREDENTIALS environment
|
||||
# variable is shared with the container and that the path of the file is on
|
||||
# the shared filesystem with the container and that the USER for the container
|
||||
# has permissions to read the file.
|
||||
#
|
||||
docker:
|
||||
if: ${{ github.event_name == 'push' || github.repository == github.event.pull_request.head.repo.full_name }}
|
||||
name: 'docker'
|
||||
|
@ -168,14 +168,14 @@ outputs:
|
||||
description: |-
|
||||
Path on the local filesystem where the generated credentials file resides.
|
||||
This is only available if "create_credentials_file" was set to true.
|
||||
auth_token:
|
||||
description: |-
|
||||
The intermediate authentication token, which could be used to call other
|
||||
Google Cloud APIs, depending on how you configured IAM.
|
||||
access_token:
|
||||
description: |-
|
||||
The Google Cloud access token for calling other Google Cloud APIs. This is
|
||||
only available when "token_format" is "access_token".
|
||||
access_token_expiration:
|
||||
description: |-
|
||||
The RFC3339 UTC "Zulu" format timestamp for the access token. This is only
|
||||
available when "token_format" is "access_token".
|
||||
id_token:
|
||||
description: |-
|
||||
The Google Cloud ID token. This is only available when "token_format" is
|
||||
|
6
dist/main/index.js
vendored
6
dist/main/index.js
vendored
File diff suppressed because one or more lines are too long
6
dist/post/index.js
vendored
6
dist/post/index.js
vendored
File diff suppressed because one or more lines are too long
187
docs/EXAMPLES.md
Normal file
187
docs/EXAMPLES.md
Normal file
@ -0,0 +1,187 @@
|
||||
# Examples for Authenticating to Google Cloud from GitHub Actions
|
||||
|
||||
> Consider using the [Markdown TOC][github-markdown-toc] to make browsing these
|
||||
> samples easier.
|
||||
|
||||
These examples assume you have completed all corresponding [Setup
|
||||
Instructions](../README.md#setup).
|
||||
|
||||
## Direct Workload Identity Federation
|
||||
|
||||
This example shows authenticating directly with Workload Identity Federation.
|
||||
Google Cloud Resources must have the Workload Identity Pool as a `principalSet`
|
||||
as an IAM permission.
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
job_id:
|
||||
permissions:
|
||||
contents: 'read'
|
||||
id-token: 'write'
|
||||
|
||||
- id: 'auth'
|
||||
uses: 'google-github-actions/auth@v2'
|
||||
with:
|
||||
project_id: 'my-project'
|
||||
workload_identity_provider: 'projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider'
|
||||
|
||||
# Use 'steps.auth.outputs.auth_token' in subsequent steps as a bearer token.
|
||||
#
|
||||
# - run: |-
|
||||
# curl -H 'Bearer: ${{ steps.auth.outputs.auth_token }}' https://...
|
||||
#
|
||||
```
|
||||
|
||||
## Workload Identity Federation through a Service Account
|
||||
|
||||
This example shows authenticating to Google Cloud by proxying through a Service
|
||||
Account. Future authentication calls will be made with the Service Account's
|
||||
OAuth 2.0 Access token.
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
job_id:
|
||||
permissions:
|
||||
contents: 'read'
|
||||
id-token: 'write'
|
||||
|
||||
- uses: 'google-github-actions/auth@v2'
|
||||
with:
|
||||
workload_identity_provider: 'projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider'
|
||||
service_account: 'my-service-account@my-project.iam.gserviceaccount.com'
|
||||
|
||||
# NOTE: 'steps.auth.outputs.auth_token' will be a federated authentication
|
||||
# token, it does not correspond to the service account. To get a token for
|
||||
# the service account, specify the 'token_format' parameter and use the
|
||||
# 'accesss_token' output.
|
||||
#
|
||||
# - uses: 'google-github-actions/auth@v2'
|
||||
# with:
|
||||
# workload_identity_provider: 'projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider'
|
||||
# service_account: 'my-service-account@my-project.iam.gserviceaccount.com'
|
||||
# token_format: 'access_token'
|
||||
#
|
||||
# - run: |-
|
||||
# curl -H 'Bearer: ${{ steps.auth.outputs.access_token }}' https://...
|
||||
#
|
||||
```
|
||||
|
||||
## Service Account Key JSON
|
||||
|
||||
This example demonstrates authenticating via a Google Cloud Service Account Key
|
||||
JSON. After you [export a Google Cloud Service Account Key][sake], insert the
|
||||
value into a GitHub Secret named 'GOOGLE_CREDENTIALS'.
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
job_id:
|
||||
steps:
|
||||
- uses: 'actions/checkout@v4'
|
||||
|
||||
- uses: 'google-github-actions/auth@v2'
|
||||
with:
|
||||
credentials_json: '${{ secrets.GOOGLE_CREDENTIALS }}'
|
||||
```
|
||||
|
||||
### Configuring gcloud
|
||||
|
||||
This example demonstrates using this GitHub Action to configure authentication
|
||||
for the `gcloud` CLI tool.
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
job_id:
|
||||
permissions:
|
||||
contents: 'read'
|
||||
id-token: 'write'
|
||||
|
||||
steps:
|
||||
- uses: 'actions/checkout@v4'
|
||||
|
||||
- id: 'auth'
|
||||
uses: 'google-github-actions/auth@v2'
|
||||
with:
|
||||
workload_identity_provider: 'projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider'
|
||||
|
||||
- name: 'Set up Cloud SDK'
|
||||
uses: 'google-github-actions/setup-gcloud@v1'
|
||||
```
|
||||
|
||||
### Generating an OAuth 2.0 Access Token
|
||||
|
||||
This example demonstrates using this GitHub Action to generate an OAuth 2.0
|
||||
Access Token for authenticating to Google Cloud.
|
||||
|
||||
> **⚠️ NOTE!** The default lifetime is 1 hour, but you can request up to 12
|
||||
> hours if you set the
|
||||
> [`constraints/iam.allowServiceAccountCredentialLifetimeExtension` organization
|
||||
> policy][orgpolicy-creds-lifetime].
|
||||
|
||||
> **⚠️ NOTE!** If you authenticate via `credentials_json`, the service account
|
||||
> must have `roles/iam.serviceAccountTokenCreator` on itself.
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
job_id:
|
||||
permissions:
|
||||
contents: 'read'
|
||||
id-token: 'write'
|
||||
|
||||
steps:
|
||||
- uses: 'actions/checkout@v4'
|
||||
|
||||
- id: 'auth'
|
||||
uses: 'google-github-actions/auth@v2'
|
||||
with:
|
||||
token_format: 'access_token' # <--
|
||||
workload_identity_provider: 'projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider'
|
||||
service_account: 'my-service-account@my-project.iam.gserviceaccount.com'
|
||||
access_token_lifetime: '300s' # optional, default: '3600s' (1 hour)
|
||||
|
||||
# Example of using the output. The token is usually provided as a Bearer
|
||||
# token.
|
||||
- id: 'access-secret'
|
||||
run: |-
|
||||
curl https://secretmanager.googleapis.com/v1/projects/my-project/secrets/my-secret/versions/1:access \
|
||||
--header "Authorization: Bearer ${{ steps.auth.outputs.access_token }}"
|
||||
```
|
||||
|
||||
### Generating an ID Token (JWT)
|
||||
|
||||
This example demonstrates using this GitHub Action to generate a Google Cloud ID
|
||||
Token for authenticating to Google Cloud. This is commonly used when invoking a
|
||||
Cloud Run service.
|
||||
|
||||
> **⚠️ NOTE!** If you authenticate via `credentials_json`, the service account
|
||||
> must have `roles/iam.serviceAccountTokenCreator` on itself.
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
job_id:
|
||||
permissions:
|
||||
contents: 'read'
|
||||
id-token: 'write'
|
||||
|
||||
steps:
|
||||
- uses: 'actions/checkout@v4'
|
||||
|
||||
- id: 'auth'
|
||||
uses: 'google-github-actions/auth@v2'
|
||||
with:
|
||||
token_format: 'id_token' # <--
|
||||
workload_identity_provider: 'projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider'
|
||||
service_account: 'my-service-account@my-project.iam.gserviceaccount.com'
|
||||
id_token_audience: 'https://myapp-uvehjacqzq.a.run.app' # required, value depends on target
|
||||
id_token_include_email: true
|
||||
|
||||
# Example of using the output. The token is usually provided as a Bearer
|
||||
# token.
|
||||
- id: 'invoke-service'
|
||||
run: |-
|
||||
curl https://myapp-uvehjacqzq.a.run.app \
|
||||
--header "Authorization: Bearer ${{ steps.auth.outputs.id_token }}"
|
||||
```
|
||||
|
||||
[github-markdown-toc]: https://github.blog/changelog/2021-04-13-table-of-contents-support-in-markdown-files/
|
||||
[orgpolicy-creds-lifetime]: https://cloud.google.com/resource-manager/docs/organization-policy/org-policy-constraints
|
||||
[sake]: https://cloud.google.com/iam/docs/creating-managing-service-account-keys
|
@ -2,46 +2,33 @@
|
||||
|
||||
## Permission denied
|
||||
|
||||
When troubleshooting "permission denied" errors from `auth` for Workload
|
||||
Identity, the first step is to ask the `auth` plugin to generate an OAuth access
|
||||
token. Do this by adding `token_format: 'access_token'` to your YAML:
|
||||
|
||||
```yaml
|
||||
- uses: 'google-github-actions/auth@v1'
|
||||
with:
|
||||
# ...
|
||||
token_format: 'access_token'
|
||||
```
|
||||
|
||||
If your workflow _succeeds_ after adding the step to generate an access token,
|
||||
it means Workload Identity Federation is configured correctly and the issue is
|
||||
in subsequent actions. You can remove the `token_format` from your YAML. To
|
||||
further debug:
|
||||
|
||||
1. Enable [GitHub Actions debug logging][debug-logs] and re-run the workflow to
|
||||
see exactly which step is failing. Ensure you are using the latest version
|
||||
of that GitHub Action.
|
||||
of the GitHub Action.
|
||||
|
||||
1. Make sure you use `actions/checkout@v4` **before** the `auth` action in your
|
||||
> **❗️ WARNING!** Enabling debug logging increases the chances of a secret
|
||||
> being accidentially logged. While GitHub Actions will scrub secrets,
|
||||
> please take extra caution when sharing these debug logs in publicly
|
||||
> accessible places like GitHub issues.
|
||||
>
|
||||
> If you do not feel comfortable attaching the debug logs to a GitHub issue,
|
||||
> please create the issue and then email the debug logs to
|
||||
> google-github-actions@google.com, including the GitHub issue number in the
|
||||
> subject line and email body.
|
||||
|
||||
1. Ensure you have waited at least 5 minutes between making changes to the
|
||||
Workload Identity Pool, Workload Identity Provider, or IAM policies. Changes
|
||||
to these resources are eventually consistent. Usually they happen
|
||||
immediately, but sometimes they can take up to 5 minutes to propagate.
|
||||
|
||||
1. Ensure `actions/checkout@v4` is **before** the `auth` action in your
|
||||
workflow.
|
||||
|
||||
1. If the failing action is from `google-github-action/*`, please file an issue
|
||||
in the corresponding repository.
|
||||
|
||||
1. If the failing action is from an external action, please file an issue
|
||||
against that repository. The `auth` action exports Google Application
|
||||
Default Credentials (ADC). Ask the action author to ensure they are
|
||||
processing ADC correctly and using the latest versions of the Google client
|
||||
libraries. Please note that we do not have control over actions outside of
|
||||
`google-github-actions`.
|
||||
|
||||
If your workflow _fails_ after adding the step to generate an access token,
|
||||
it likely means there is a misconfiguration with Workload Identity. Here are
|
||||
some common sources of errors:
|
||||
|
||||
1. Enable [GitHub Actions debug logging][debug-logs] and re-run the workflow to
|
||||
see exactly which step is failing. Ensure you are using the latest version
|
||||
of that GitHub Action.
|
||||
```yaml
|
||||
steps:
|
||||
- uses: 'actions/checkout@v4'
|
||||
- uses: 'google-github-actions/auth@v2'
|
||||
```
|
||||
|
||||
1. Ensure the value for `workload_identity_provider` is the full _Provider_
|
||||
name, **not** the _Pool_ name:
|
||||
@ -55,8 +42,13 @@ some common sources of errors:
|
||||
**number**. Workload Identity Federation does not accept Google Cloud
|
||||
Project IDs.
|
||||
|
||||
1. Ensure that you have the correct `permissions:` for the job in your workflow, per
|
||||
the [usage](../README.md#usage) docs, i.e.
|
||||
```diff
|
||||
- projects/my-project/locations/global/workloadIdentityPools/my-pool/providers/my-provider
|
||||
+ projects/1234567890/locations/global/workloadIdentityPools/my-pool/providers/
|
||||
```
|
||||
|
||||
1. Ensure that you have the correct `permissions:` for the job in your
|
||||
workflow, per the [usage](../README.md#usage) docs:
|
||||
|
||||
```yaml
|
||||
permissions:
|
||||
@ -67,43 +59,47 @@ some common sources of errors:
|
||||
1. Ensure you have created an **Attribute Mapping** for any **Attribute
|
||||
Conditions** or **Service Account Impersonation** principals. You cannot
|
||||
create an Attribute Condition unless you map that value from the incoming
|
||||
GitHub OIDC token. You cannot grant permissions to impersonate a Service
|
||||
Account on an attribute unless you map that value from the incoming GitHub
|
||||
OIDC token.
|
||||
GitHub OIDC token. You cannot grant permissions on an attribute unless you
|
||||
map that value from the incoming GitHub OIDC token.
|
||||
|
||||
You can use the [GitHub Actions OIDC Debugger][oidc-debugger] to print the
|
||||
list of token claims and compare them to your Attribute Mappings and
|
||||
Attribute Conditions.
|
||||
> **📝 TIP!** Use the [GitHub Actions OIDC Debugger][oidc-debugger] to print
|
||||
> the list of token claims and compare them to your Attribute Mappings and
|
||||
> Attribute Conditions.
|
||||
|
||||
1. Ensure you have the correct casing and capitalization. GitHub does not
|
||||
distinguish between "foobar" and "FooBar", but Google Cloud does. Ensure any
|
||||
**Attribute Conditions** use the correct capitalization.
|
||||
1. Ensure you have the correct character casing and capitalization. GitHub does
|
||||
not distinguish between "foobar" and "FooBar", but Google Cloud does. Ensure
|
||||
any **Attribute Conditions** use the correct capitalization. The
|
||||
capitalization must match what is in the GitHub Actions OIDC token.
|
||||
|
||||
1. Check the specific error message that is returned.
|
||||
|
||||
- If the error message includes "failed to generate Google Cloud federated
|
||||
- If the error message includes "Failed to generate Google Cloud federated
|
||||
token", it means admission into the Workload Identity Pool failed. Check
|
||||
your [**Attribute Conditions**][attribute-conditions].
|
||||
|
||||
- If the error message inclues "failed to generate Google Cloud access
|
||||
token", it means Service Account Impersonation failed. Check your
|
||||
- If the error message inclues "Failed to generate OAuth 2.0 Access
|
||||
Token", it means Service Account Impersonation failed. Check your
|
||||
[**Service Account Impersonation**][sa-impersonation] settings and
|
||||
ensure the principalSet is correct.
|
||||
|
||||
1. Enable `Admin Read`, `Data Read`, and `Data Write` [Audit Logging][cal] for
|
||||
Identity and Access Management (IAM) in your Google Cloud project.
|
||||
|
||||
**Warning!** This will increase log volume which may increase costs. To keep
|
||||
costs low, you can disable this audit logging after you have debugged the
|
||||
issue.
|
||||
> **❗️ WARNING!** This will increase log volume which may increase costs.
|
||||
> You can disable this audit logging after you have debugged the issue.
|
||||
|
||||
Try to authenticate again, and then explore the logs for your Workload
|
||||
Identity Provider and Workload Identity Pool. Sometimes these error messages
|
||||
are helpful in identifying the root cause.
|
||||
|
||||
1. Ensure you have waited at least 5 minutes between making changes to the
|
||||
Workload Identity Pool and Workload Identity Provider. Changes to these
|
||||
resources are eventually consistent.
|
||||
1. If failures are coming from a different GitHub Action step, please file an
|
||||
issue against that repository. The `auth` action exports Google Application
|
||||
Default Credentials (ADC). Ask the action author to ensure they are
|
||||
processing ADC correctly and using the latest versions of the Google client
|
||||
libraries.
|
||||
|
||||
> **⚠️ NOTE!** We do not have control over GitHub Actions outside of the
|
||||
> `google-github-actions` GitHub organization.
|
||||
|
||||
|
||||
## Subject exceeds the 127 byte limit
|
||||
@ -234,6 +230,33 @@ tool like `jq`:
|
||||
cat credentials.json | jq -r tostring
|
||||
```
|
||||
|
||||
## Organizational Policy Constraints
|
||||
|
||||
**⚠️ NOTE!** Your Google Cloud organization administrator controls these
|
||||
policies. You must work with your internal IT department to resolve OrgPolicy
|
||||
violations and constraints.
|
||||
|
||||
### Workload Identity Providers
|
||||
|
||||
Your organization may restrict which external identity providers are permitted
|
||||
on your Google Cloud account. To enable GitHub Actions as a Workload Identity
|
||||
Pool and Provider, add the `https://token.actions.githubusercontent.com` to the
|
||||
allowed `iam.workloadIdentityPoolProviders` Org Policy constraint.
|
||||
|
||||
```shell
|
||||
gcloud resource-manager org-policies allow "constraints/iam.workloadIdentityPoolProviders" \
|
||||
https://token.actions.githubusercontent.com
|
||||
```
|
||||
|
||||
### Service Account Key Export
|
||||
|
||||
Your organization may restrict exporting Service Account Keys. To enable Service
|
||||
Account Key export, set the `iam.disableServiceAccountCreation` to false.
|
||||
|
||||
```shell
|
||||
gcloud resource-manager org-policies disable-enforce "constraints/iam.disableServiceAccountCreation"
|
||||
```
|
||||
|
||||
|
||||
[attribute-conditions]: https://cloud.google.com/iam/docs/workload-identity-federation#conditions
|
||||
[sa-impersonation]: https://cloud.google.com/iam/docs/workload-identity-federation#impersonation
|
||||
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 34 KiB |
111
docs/google-github-actions-auth-service-account-key-export.svg
Normal file
111
docs/google-github-actions-auth-service-account-key-export.svg
Normal file
@ -0,0 +1,111 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 771 365">
|
||||
<style>
|
||||
path {
|
||||
fill: #333;
|
||||
stroke: #333;
|
||||
stroke-width: 0.5;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
path {
|
||||
fill: #ccc;
|
||||
stroke: #ccc;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<path d="M2.9 13.3V6.4h3.8v1h-3v6z"/>
|
||||
<path d="M2.9 26.3v-14h.8v14z"/>
|
||||
<path d="M2.9 39.3v-14h.8v14z"/>
|
||||
<path d="M2.9 52.3v-14h.8v14z"/>
|
||||
<path d="M2.9 65.3v-14h.8v14z"/>
|
||||
<path d="M2.9 78.3v-14h.8v14z"/>
|
||||
<path d="M2.9 91.3v-14h.8v14z"/>
|
||||
<path d="M2.9 104.3v-14h.8v14z"/>
|
||||
<path d="M2.9 117.3v-14h.8v14z"/>
|
||||
<path d="M2.9 130.3v-14h.8v14z"/>
|
||||
<path d="M2.9 143.3v-14h.8v14z"/>
|
||||
<path d="M2.9 156.3v-14h.8v14z"/>
|
||||
<path d="M2.9 169.3v-14h.8v14z"/>
|
||||
<path d="M2.9 182.3v-14h.8v14z"/>
|
||||
<path d="M2.9 195.3v-14h.8v14z"/>
|
||||
<path d="M2.9 208.3v-14h.8v14z"/>
|
||||
<path d="M2.9 221.3v-14h.8v14z"/>
|
||||
<path d="M2.9 234.3v-14h.8v14z"/>
|
||||
<path d="M2.9 247.3v-14h.8v14z"/>
|
||||
<path d="M2.9 260.3v-14h.8v14z"/>
|
||||
<path d="M2.9 273.3v-14h.8v14z"/>
|
||||
<path d="M2.9 286.3v-14h.8v14z"/>
|
||||
<path d="M2.9 299.3v-14h.8v14z"/>
|
||||
<path d="M2.9 312.3v-14h.8v14z"/>
|
||||
<path d="M2.9 325.3v-14h.8v14z"/>
|
||||
<path d="M2.9 338.3v-14h.8v14z"/>
|
||||
<path d="M2.9 351.3v-14h.8v14z"/>
|
||||
<path d="M2.9 358.3v-8h.8v7.1h3v1zm4 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h3v-7.1h.8v8zm38 0v-8h.8v7.1h3v1zm4 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h3v-7.1h.8v8zm38 0v-8h.8v7.1h3v1zm4 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h3v-7.1h.8v8zM20 347.3l-1 .6q-.6.3-1.2.3-1.6 0-2.4-1.1-.9-1.1-.9-3.1t1-3q.8-1.2 2.3-1.2l1 .2.9.4v1.1l-1-.6q-.4-.2-.9-.2-1 0-1.6.8-.5.9-.5 2.5 0 1.7.5 2.5t1.6.8l.6-.1q.3 0 .5-.3v-2.1h-1.2v-1H20zm4.3-4.6q-.7 0-1.1.6-.4.5-.4 1.7 0 1.1.4 1.7.4.6 1.1.6.8 0 1.2-.6.3-.6.3-1.7 0-1.2-.3-1.7-.4-.6-1.2-.6zm0-.9q1.3 0 2 .9.6.8.6 2.3 0 1.5-.7 2.3-.6.9-1.9.9-1.2 0-1.9-.9-.7-.8-.7-2.3 0-1.5.7-2.3.7-.9 2-.9zm7 .9q-.7 0-1.1.6-.4.5-.4 1.7 0 1.1.4 1.7.4.6 1.1.6.8 0 1.2-.6.3-.6.3-1.7 0-1.2-.3-1.7-.4-.6-1.2-.6zm0-.9q1.3 0 2 .9.6.8.6 2.3 0 1.5-.7 2.3-.6.9-1.9.9-1.2 0-1.9-.9-.7-.8-.7-2.3 0-1.5.7-2.3.7-.9 2-.9zm8.3 3.2q0-1.2-.4-1.7-.3-.6-1-.6t-1.1.6q-.4.5-.4 1.6 0 1.2.4 1.7.4.6 1.1.6.7 0 1-.6.4-.5.4-1.7zm1 2.6q0 1.4-.6 2-.7.8-2 .8l-.8-.1-.9-.2v-1l1 .4h.8q.8 0 1.1-.4.4-.4.4-1.3v-.8q-.2.5-.6.8-.4.2-1 .2-1 0-1.7-.8-.6-.8-.6-2.3 0-1.4.6-2.2.6-.9 1.7-.9.6 0 1 .3.4.2.6.7v-.8h1zm5.3-1.8q0 .7.2 1 .3.4.7.4H48v.8h-1.3q-.9 0-1.3-.6-.5-.5-.5-1.6v-5.4h-1.6v-.8h2.6zm9.1-1.3v.5h-4.3q0 1.2.5 1.8.5.5 1.5.5l1-.1 1-.5v1l-1 .3-1 .2q-1.5 0-2.2-.9-.8-.8-.8-2.3 0-1.4.7-2.3.8-.9 2.1-.9 1.1 0 1.8.8t.7 1.9zm-1-.3q0-.6-.4-1-.4-.5-1.1-.5-.8 0-1.2.4-.5.5-.5 1.1zm14.8 3.5-.9.3q-.4.2-1 .2-1.5 0-2.3-1.1-.9-1.1-.9-3.1t.9-3q.9-1.2 2.4-1.2l1 .1.8.4v1l-.9-.4q-.4-.2-1-.2-1 0-1.5.8T65 344q0 1.6.5 2.5.5.8 1.6.8l1-.2.8-.5zm5.1-1.9q0 .7.2 1 .3.4.7.4H76v.8h-1.3q-.9 0-1.3-.6-.5-.5-.5-1.6v-5.4h-1.6v-.8h2.6zm6.4-3.1q-.7 0-1.1.6-.4.5-.4 1.7 0 1.1.4 1.7.4.6 1.1.6.8 0 1.2-.6.3-.6.3-1.7 0-1.2-.3-1.7-.4-.6-1.2-.6zm0-.9q1.3 0 2 .9.6.8.6 2.3 0 1.5-.7 2.3-.6.9-1.9.9-1.2 0-1.9-.9-.7-.8-.7-2.3 0-1.5.7-2.3.7-.9 2-.9zm4.7 3.9V342h1v3.7q0 .8.3 1.2.3.4 1 .4.6 0 1-.5t.4-1.4V342h1v6h-1v-.9q-.3.5-.8.8-.4.3-1 .3-1 0-1.4-.6-.5-.7-.5-1.9zm10.6-2.9v-3.2h1v8.4h-1v-.8q-.2.5-.6.7-.5.3-1 .3-1 0-1.7-.9-.6-.8-.6-2.3 0-1.5.6-2.3.6-.9 1.7-.9.5 0 1 .3.4.2.6.7zm-2.9 2.2q0 1.1.4 1.7.3.6 1 .6.8 0 1.1-.6.4-.6.4-1.7t-.4-1.7q-.3-.6-1-.6-.8 0-1.1.6-.4.5-.4 1.7z"/>
|
||||
<path d="M233.9 351.3v-14h.8v14zm35 0v-14h.8v14zm17.1-4-1 .6q-.6.3-1.2.3-1.6 0-2.4-1.1-.9-1.1-.9-3.1t1-3q.8-1.2 2.3-1.2l1 .2.9.4v1.1l-1-.6q-.4-.2-.9-.2-1 0-1.6.8-.5.9-.5 2.5 0 1.7.5 2.5t1.6.8l.6-.1q.3 0 .5-.3v-2.1h-1.2v-1h2.2zm2.7-5.3h2.5v5.2h2v.8h-5v-.8h2v-4.4h-1.5zm1.5-2.3h1v1.2h-1zm7.1.6v1.7h2.2v.8h-2.2v3.2q0 .7.3 1 .2.2.8.2h1.1v.8h-1.2q-1 0-1.5-.4-.5-.5-.5-1.6v-3.2h-1.6v-.8h1.6v-1.7zm4.4-.3h1.1v3h3v-3h1v8h-1v-4h-3v4h-1zm7.3 5.7V342h1v3.7q0 .8.3 1.2.3.4 1 .4.6 0 1-.5t.4-1.4V342h1v6h-1v-.9q-.3.5-.8.8-.4.3-1 .3-1 0-1.4-.6-.5-.7-.5-1.9zm11-.7q0-1.2-.4-1.7-.4-.6-1.1-.6-.7 0-1.1.6-.4.6-.4 1.7t.4 1.7q.4.6 1 .6.8 0 1.2-.6.3-.6.3-1.7zm-3-2.2q.3-.5.7-.7.4-.3 1-.3 1 0 1.6.9.7.8.7 2.3 0 1.5-.7 2.3-.6.9-1.7.9-.5 0-1-.3l-.6-.7v.8h-1v-8.4h1zm182.9 8.5v-14h.8v14zm35 0v-14h.8v14zm17.1-4-1 .6q-.6.3-1.2.3-1.6 0-2.4-1.1-.9-1.1-.9-3.1t1-3q.8-1.2 2.3-1.2l1 .2.9.4v1.1l-1-.6q-.4-.2-.9-.2-1 0-1.6.8-.5.9-.5 2.5 0 1.7.5 2.5t1.6.8l.6-.1q.3 0 .5-.3v-2.1h-1.2v-1h2.2zm4.3-4.6q-.7 0-1.1.6-.4.5-.4 1.7 0 1.1.4 1.7.4.6 1.1.6.8 0 1.2-.6.3-.6.3-1.7 0-1.2-.3-1.7-.4-.6-1.2-.6zm0-.9q1.3 0 2 .9.6.8.6 2.3 0 1.5-.7 2.3-.6.9-1.9.9-1.2 0-1.9-.9-.7-.8-.7-2.3 0-1.5.7-2.3.7-.9 2-.9zm7 .9q-.7 0-1.1.6-.4.5-.4 1.7 0 1.1.4 1.7.4.6 1.1.6.8 0 1.2-.6.3-.6.3-1.7 0-1.2-.3-1.7-.4-.6-1.2-.6zm0-.9q1.3 0 2 .9.6.8.6 2.3 0 1.5-.7 2.3-.6.9-1.9.9-1.2 0-1.9-.9-.7-.8-.7-2.3 0-1.5.7-2.3.7-.9 2-.9zm8.3 3.2q0-1.2-.4-1.7-.3-.6-1-.6t-1.1.6q-.4.5-.4 1.6 0 1.2.4 1.7.4.6 1.1.6.7 0 1-.6.4-.5.4-1.7zm1 2.6q0 1.4-.6 2-.7.8-2 .8l-.8-.1-.9-.2v-1l1 .4h.8q.8 0 1.1-.4.4-.4.4-1.3v-.8q-.2.5-.6.8-.4.2-1 .2-1 0-1.7-.8-.6-.8-.6-2.3 0-1.4.6-2.2.6-.9 1.7-.9.6 0 1 .3.4.2.6.7v-.8h1zm5.3-1.8q0 .7.2 1 .3.4.7.4h1.2v.8h-1.3q-.9 0-1.3-.6-.5-.5-.5-1.6v-5.4h-1.6v-.8h2.6zm9.1-1.3v.5h-4.3q0 1.2.5 1.8.5.5 1.5.5l1-.1 1-.5v1l-1 .3-1 .2q-1.5 0-2.2-.9-.8-.8-.8-2.3 0-1.4.7-2.3.8-.9 2.1-.9 1.1 0 1.8.8t.7 1.9zm-1-.3q0-.6-.4-1-.4-.5-1.1-.5-.8 0-1.2.4-.5.5-.5 1.1zm14.8 3.5-.9.3q-.4.2-1 .2-1.5 0-2.3-1.1-.9-1.1-.9-3.1t.9-3q.9-1.2 2.4-1.2l1 .1.8.4v1l-.9-.4q-.4-.2-1-.2-1 0-1.5.8t-.5 2.5q0 1.6.5 2.5.5.8 1.6.8l1-.2.8-.5zm5.1-1.9q0 .7.2 1 .3.4.7.4h1.2v.8h-1.3q-.9 0-1.3-.6-.5-.5-.5-1.6v-5.4h-1.6v-.8h2.6zm6.4-3.1q-.7 0-1.1.6-.4.5-.4 1.7 0 1.1.4 1.7.4.6 1.1.6.8 0 1.2-.6.3-.6.3-1.7 0-1.2-.3-1.7-.4-.6-1.2-.6zm0-.9q1.3 0 2 .9.6.8.6 2.3 0 1.5-.7 2.3-.6.9-1.9.9-1.2 0-1.9-.9-.7-.8-.7-2.3 0-1.5.7-2.3.7-.9 2-.9zm4.7 3.9V342h1v3.7q0 .8.3 1.2.3.4 1 .4.6 0 1-.5t.4-1.4V342h1v6h-1v-.9q-.3.5-.8.8-.4.3-1 .3-1 0-1.4-.6-.5-.7-.5-1.9zm10.6-2.9v-3.2h1v8.4h-1v-.8q-.2.5-.6.7-.5.3-1 .3-1 0-1.7-.9-.6-.8-.6-2.3 0-1.5.6-2.3.6-.9 1.7-.9.5 0 1 .3.4.2.6.7zm-2.9 2.2q0 1.1.4 1.7.3.6 1 .6.8 0 1.1-.6.4-.6.4-1.7t-.4-1.7q-.3-.6-1-.6-.8 0-1.1.6-.4.5-.4 1.7zm141.2 6.3v-14h.8v14zm-735-312v-6.9h3.8v1h-3v6z"/>
|
||||
<path d="M30.9 52.3v-14h.8v14z"/>
|
||||
<path d="M30.9 65.3v-14h.8v14z"/>
|
||||
<path d="M30.9 78.3v-14h.8v14z"/>
|
||||
<path d="M30.9 91.3v-14h.8v14z"/>
|
||||
<path d="M30.9 104.3v-14h.8v14z"/>
|
||||
<path d="M30.9 117.3v-14h.8v14z"/>
|
||||
<path d="M30.9 130.3v-14h.8v14z"/>
|
||||
<path d="M30.9 137.3v-8h.8v7.1h3v1zm0 84v-6.9h3.8v1h-3v6z"/>
|
||||
<path d="M30.9 234.3v-14h.8v14z"/>
|
||||
<path d="M30.9 247.3v-14h.8v14z"/>
|
||||
<path d="M30.9 260.3v-14h.8v14z"/>
|
||||
<path d="M30.9 273.3v-14h.8v14z"/>
|
||||
<path d="M30.9 286.3v-14h.8v14z"/>
|
||||
<path d="M30.9 299.3v-14h.8v14z"/>
|
||||
<path d="M30.9 312.3v-14h.8v14z"/>
|
||||
<path d="M30.9 319.3v-8h.8v7.1h3v1zm203 19v-14h.8v14zm35 0v-14h.8v14zm231 0v-14h.8v14zm35 0v-14h.8v14zm231 0v-14h.8v14zm-731-19v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h3v-7.1h.8v8z"/>
|
||||
<path d="M233.9 325.3v-14h.8v14zm35 0v-14h.8v14zm28-6v-8h.8v7.1h3v1zm4 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h3v-7.1h.8v8zm31 6v-14h.8v14zm35 0v-14h.8v14zm28-6v-8h.8v7.1h3v1zm4 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h3v-7.1h.8v8zm31 6v-14h.8v14zm-711.5-63v1l-1-.4-1-.2q-.7 0-1.2.4-.4.3-.4 1 0 .5.3.7.3.3 1 .5l.6.1q1.2.3 1.7.9.5.5.5 1.5 0 1.2-.7 1.8-.7.6-2 .6L51 270q-.6 0-1.2-.3v-1.2l1.2.6 1.1.2q.8 0 1.3-.4t.5-1q0-.6-.4-1-.3-.3-1-.4l-.6-.2q-1.1-.2-1.6-.7-.6-.5-.6-1.4 0-1 .8-1.7.7-.7 1.9-.7l1 .1 1 .4zm151.5 24v-14h.8v14z"/>
|
||||
<path d="M205.9 299.3v-14h.8v14z"/>
|
||||
<path d="M205.9 312.3v-14h.8v14zm28-26v-14h.8v14z"/>
|
||||
<path d="M233.9 299.3v-14h.8v14z"/>
|
||||
<path d="M233.9 312.3v-14h.8v14zm35-26v-14h.8v14z"/>
|
||||
<path d="M268.9 299.3v-14h.8v14z"/>
|
||||
<path d="M268.9 312.3v-14h.8v14zm28-26v-14h.8v14z"/>
|
||||
<path d="M296.9 299.3v-14h.8v14z"/>
|
||||
<path d="M296.9 312.3v-14h.8v14zm175-26v-14h.8v14z"/>
|
||||
<path d="M471.9 299.3v-14h.8v14z"/>
|
||||
<path d="M471.9 312.3v-14h.8v14zm28-26v-14h.8v14z"/>
|
||||
<path d="M499.9 299.3v-14h.8v14z"/>
|
||||
<path d="M499.9 312.3v-14h.8v14zm14-19v-8h.8v7.1h3v1zm21 19v-14h.8v14zm28 0v-14h.8v14zm175 0v-14h.8v14zm28 0v-14h.8v14zm-241-19v-.9h6.8v1z"/>
|
||||
<path d="M534.9 299.3v-14h.8v14zm4-6v-.9h6.8v1zm14.1 3.1V290l6.6 3.2zm9.9 2.9v-14h.8v14zm76.1-5.5h2v2.2h-2zm7 0h2v2.2h-2zm7 0h2v2.2h-2zm84.9 5.5v-14h.8v14zm28 0v-14h.8v14z"/>
|
||||
<path d="M534.9 286.3v-14h.8v14zm28 0v-14h.8v14zm175 0v-14h.8v14zm28 0v-14h.8v14zM62 266.5v.5h-4.3q0 1.2.5 1.8.5.5 1.5.5l1-.1 1-.5v1l-1 .3-1 .2q-1.5 0-2.2-.9-.8-.8-.8-2.3 0-1.4.7-2.3.8-.9 2.1-.9 1.1 0 1.8.8t.7 1.9zm-1-.3q0-.6-.4-1-.4-.5-1.1-.5-.8 0-1.2.4-.5.5-.5 1.1zm8.2-1-.6-.3-.8-.1q-.9 0-1.4.5-.5.6-.5 1.7v3h-1v-6h1v1.2q.3-.7.8-1 .5-.4 1.2-.4l.7.1.6.3zm1.3-1.2h1l1.8 5 1.8-5h1l-2.2 6h-1.2zm8.2 0h2.5v5.2h2v.8h-5v-.8h2v-4.4h-1.5zm1.5-2.3h1v1.2h-1zm9.5 8-.8.3q-.4.2-.9.2-1.4 0-2.2-.9-.8-.8-.8-2.3 0-1.5.8-2.3.8-.9 2.2-.9.5 0 .9.2.4 0 .8.3v1l-.8-.5-.9-.1q-1 0-1.4.6-.5.6-.5 1.7t.5 1.7q.5.6 1.4.6l1-.1.7-.5zm7.3-3.2v.5h-4.3q0 1.2.5 1.8.5.5 1.5.5l1-.1 1-.5v1l-1 .3-1 .2q-1.5 0-2.2-.9-.8-.8-.8-2.3 0-1.4.7-2.3.8-.9 2.1-.9 1.1 0 1.8.8t.7 1.9zm-1-.3q0-.6-.4-1-.4-.5-1.1-.5-.8 0-1.2.4-.5.5-.5 1.1zm12.3-3.2-1.1 4h2.3zm-.6-1h1.3l2.4 8h-1.1l-.6-2H107l-.6 2h-1.1zm10 7.7-.8.3q-.4.2-.9.2-1.4 0-2.2-.9-.8-.8-.8-2.3 0-1.5.8-2.3.8-.9 2.2-.9.5 0 .9.2.4 0 .8.3v1l-.8-.5-.9-.1q-1 0-1.4.6-.5.6-.5 1.7t.5 1.7q.5.6 1.4.6l1-.1.7-.5zm7 0-.8.3q-.4.2-.9.2-1.4 0-2.2-.9-.8-.8-.8-2.3 0-1.5.8-2.3.8-.9 2.2-.9.5 0 .9.2.4 0 .8.3v1l-.8-.5-.9-.1q-1 0-1.4.6-.5.6-.5 1.7t.5 1.7q.5.6 1.4.6l1-.1.7-.5zm4.6-5q-.7 0-1.1.6-.4.5-.4 1.7 0 1.1.4 1.7.4.6 1.1.6.8 0 1.2-.6.3-.6.3-1.7 0-1.2-.3-1.7-.4-.6-1.2-.6zm0-.9q1.3 0 2 .9.6.8.6 2.3 0 1.5-.7 2.3-.6.9-1.9.9-1.2 0-1.9-.9-.7-.8-.7-2.3 0-1.5.7-2.3.7-.9 2-.9zm4.7 3.9V264h1v3.7q0 .8.3 1.2.3.4 1 .4.6 0 1-.5t.4-1.4V264h1v6h-1v-.9q-.3.5-.8.8-.4.3-1 .3-1 0-1.4-.6-.5-.7-.5-1.9zm11.6-1.4v3.7h-1v-3.7q0-.8-.2-1.2-.3-.4-1-.4-.6 0-1 .5t-.4 1.4v3.4h-1v-6h1v.9q.3-.5.7-.8.5-.3 1.1-.3 1 0 1.4.6.4.6.4 1.9zm4.7-4v1.7h2.2v.8h-2.2v3.2q0 .7.3 1 .2.2.8.2h1.1v.8h-1.2q-1 0-1.5-.4-.5-.5-.5-1.6v-3.2h-1.6v-.8h1.6v-1.7zm11.4-.3h1.1v3.5l3.4-3.5h1.3l-3.1 3.3 3.2 4.7h-1.3l-2.6-4-.9.9v3.1h-1zm12.3 4.5v.5h-4.3q0 1.2.5 1.8.5.5 1.5.5l1-.1 1-.5v1l-1 .3-1 .2q-1.5 0-2.2-.9-.8-.8-.8-2.3 0-1.4.7-2.3.8-.9 2.1-.9 1.1 0 1.8.8t.7 1.9zm-1-.3q0-.6-.4-1-.4-.5-1.1-.5-.8 0-1.2.4-.5.5-.5 1.1zm6.6 1.8-.6 1.7-.7 1.8q-.3.4-.7.6-.3.2-.8.2h-.8v-.8h.6q.4 0 .7-.3l.6-1.3-2.3-6h1l1.8 4.8 1.7-4.7h1zm26.3 5.3v-14h.8v14zm4-6v-.9h6.8v1zm14 0v-.9h6.8v1zm10 6v-14h.8v14zm4-6v-.9h6.8v1zm14 0v-.9h6.8v1zm17.8 0v6h-.8v-6h-3v-.9h3v-7.1h.8v7.1h3v1zm10.2 0v-.9h6.8v1zm7.1 3.1V264l6.6 3.2zm9.9 2.9v-14h.8v14zm45.1-4-1 .6q-.6.3-1.2.3-1.6 0-2.4-1.1-.9-1.1-.9-3.1t1-3q.8-1.2 2.3-1.2l1 .2.9.4v1.1l-1-.6q-.4-.2-.9-.2-1 0-1.6.8-.5.9-.5 2.5 0 1.7.5 2.5t1.6.8l.6-.1q.3 0 .5-.3v-2.1h-1.2v-1h2.2zm2.7-5.3h2.5v5.2h2v.8h-5v-.8h2v-4.4h-1.5zm1.5-2.3h1v1.2h-1zm7.1.6v1.7h2.2v.8h-2.2v3.2q0 .7.3 1 .2.2.8.2h1.1v.8h-1.2q-1 0-1.5-.4-.5-.5-.5-1.6v-3.2h-1.6v-.8h1.6v-1.7zm4.4-.3h1.1v3h3v-3h1v8h-1v-4h-3v4h-1zm7.3 5.7V264h1v3.7q0 .8.3 1.2.3.4 1 .4.6 0 1-.5t.4-1.4V264h1v6h-1v-.9q-.3.5-.8.8-.4.3-1 .3-1 0-1.4-.6-.5-.7-.5-1.9zm11-.7q0-1.2-.4-1.7-.4-.6-1.1-.6-.7 0-1.1.6-.4.6-.4 1.7t.4 1.7q.4.6 1 .6.8 0 1.2-.6.3-.6.3-1.7zm-3-2.2q.3-.5.7-.7.4-.3 1-.3 1 0 1.6.9.7.8.7 2.3 0 1.5-.7 2.3-.6.9-1.7.9-.5 0-1-.3l-.6-.7v.8h-1v-8.4h1zm17.4-2.5v1l-1-.4-1-.2q-.7 0-1.2.4-.4.3-.4 1 0 .5.3.7.3.3 1 .5l.6.1q1.2.3 1.7.9.5.5.5 1.5 0 1.2-.7 1.8-.7.6-2 .6l-1.2-.2q-.6 0-1.2-.3v-1.2l1.2.6 1.1.2q.8 0 1.3-.4t.5-1q0-.6-.4-1-.3-.3-1-.4l-.6-.2q-1.1-.2-1.6-.7-.6-.5-.6-1.4 0-1 .8-1.7.7-.7 1.9-.7l1 .1 1 .4zm7.6 4.2v.5h-4.3q0 1.2.5 1.8.5.5 1.5.5l1-.1 1-.5v1l-1 .3-1 .2q-1.5 0-2.2-.9-.8-.8-.8-2.3 0-1.4.7-2.3.8-.9 2.1-.9 1.1 0 1.8.8t.7 1.9zm-1-.3q0-.6-.4-1-.4-.5-1.1-.5-.8 0-1.2.4-.5.5-.5 1.1zm7.7 3.5-.8.3q-.4.2-.9.2-1.4 0-2.2-.9-.8-.8-.8-2.3 0-1.5.8-2.3.8-.9 2.2-.9.5 0 .9.2.4 0 .8.3v1l-.8-.5-.9-.1q-1 0-1.4.6-.5.6-.5 1.7t.5 1.7q.5.6 1.4.6l1-.1.7-.5zm7.5-4.5-.6-.3-.8-.1q-.9 0-1.4.5-.5.6-.5 1.7v3h-1v-6h1v1.2q.3-.7.8-1 .5-.4 1.2-.4l.7.1.6.3zm6.8 1.3v.5h-4.3q0 1.2.5 1.8.5.5 1.5.5l1-.1 1-.5v1l-1 .3-1 .2q-1.5 0-2.2-.9-.8-.8-.8-2.3 0-1.4.7-2.3.8-.9 2.1-.9 1.1 0 1.8.8t.7 1.9zm-1-.3q0-.6-.4-1-.4-.5-1.1-.5-.8 0-1.2.4-.5.5-.5 1.1zm5.3-3.9v1.7h2.2v.8h-2.2v3.2q0 .7.3 1 .2.2.8.2h1.1v.8h-1.2q-1 0-1.5-.4-.5-.5-.5-1.6v-3.2h-1.6v-.8h1.6v-1.7zm8.9 1.9v1l-.8-.4-.9-.1q-.7 0-1 .2-.3.2-.3.6 0 .5.2.6l1.2.4.4.1q.8.2 1.2.6.3.4.3 1 0 1-.6 1.5t-1.8.5l-.9-.1-1-.3v-1l1 .4 1 .1q.6 0 1-.2.3-.3.3-.8 0-.7-1.3-1h-.4q-.9-.2-1.2-.6-.4-.4-.4-1 0-1 .6-1.4.5-.5 1.6-.5l1 .1.8.3zm39.7 9.1v-14h.8v14zm28 0v-14h.8v14zm14 0v-14h.8v14z"/>
|
||||
<path d="M534.9 273.3v-14h.8v14zm28 0v-6.9h3.8v1h-3v6zm4-6v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm10 6v-6h-3v-.9h3.8v7zm28 0v-14h.8v14zm-560-39v-14h.8v14z"/>
|
||||
<path d="M205.9 247.3v-14h.8v14z"/>
|
||||
<path d="M205.9 260.3v-14h.8v14zm28-26v-14h.8v14z"/>
|
||||
<path d="M233.9 247.3v-14h.8v14z"/>
|
||||
<path d="M233.9 260.3v-14h.8v14zm35-26v-14h.8v14z"/>
|
||||
<path d="M268.9 247.3v-14h.8v14z"/>
|
||||
<path d="M268.9 260.3v-14h.8v14zm28-26v-14h.8v14z"/>
|
||||
<path d="M296.9 247.3v-14h.8v14z"/>
|
||||
<path d="M296.9 260.3v-14h.8v14zm175-26v-14h.8v14z"/>
|
||||
<path d="M471.9 247.3v-14h.8v14z"/>
|
||||
<path d="M471.9 260.3v-14h.8v14zm28-26v-14h.8v14z"/>
|
||||
<path d="M499.9 247.3v-14h.8v14z"/>
|
||||
<path d="M499.9 260.3v-14h.8v14zm14-13v-14h.8v14zm21 13v-14h.8v14zm231 0v-14h.8v14z"/>
|
||||
<path d="M534.9 247.3v-14h.8v14zm28-6v-8h.8v7.1h3v1zm4 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h3v-7.1h.8v8zm31 6v-14h.8v14z"/>
|
||||
<path d="M534.9 234.3v-14h.8v14zm28 0v-14h.8v14zm175 0v-14h.8v14zm28 0v-14h.8v14zm-731-19v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm10 6v-6h-3v-.9h3.8v7zm28 0v-14h.8v14zm35 0v-14h.8v14zm28 0v-6.9h3.8v1h-3v6zm4-6v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm10 6v-6h-3v-.9h3.8v7zm28 0v-14h.8v14zm14 0v-14h.8v7.1h3v1h-3v6zm11-6v-.9h6.8v1z"/>
|
||||
<path d="M534.9 221.3v-14h.8v14zm4-6v-.9h6.8v1zm14.1 3.1V212l6.6 3.2zm9.9 2.9v-14h.8v14zm23.9-3.6-.9.3q-.4.2-1 .2-1.5 0-2.3-1.1-.9-1.1-.9-3.1t.9-3q.9-1.2 2.4-1.2l1 .1.8.4v1l-.9-.4q-.4-.2-1-.2-1 0-1.5.8t-.5 2.5q0 1.6.5 2.5.5.8 1.6.8l1-.2.8-.5zm5.1-1.9q0 .7.2 1 .3.4.7.4h1.2v.8h-1.3q-.9 0-1.3-.6-.5-.5-.5-1.6v-5.4h-1.6v-.8h2.6zm6.4-3.1q-.7 0-1.1.6-.4.5-.4 1.7 0 1.1.4 1.7.4.6 1.1.6.8 0 1.2-.6.3-.6.3-1.7 0-1.2-.3-1.7-.4-.6-1.2-.6zm0-.9q1.3 0 2 .9.6.8.6 2.3 0 1.5-.7 2.3-.6.9-1.9.9-1.2 0-1.9-.9-.7-.8-.7-2.3 0-1.5.7-2.3.7-.9 2-.9zm4.7 3.9V212h1v3.7q0 .8.3 1.2.3.4 1 .4.6 0 1-.5t.4-1.4V212h1v6h-1v-.9q-.3.5-.8.8-.4.3-1 .3-1 0-1.4-.6-.5-.7-.5-1.9zm10.6-2.9v-3.2h1v8.4h-1v-.8q-.2.5-.6.7-.5.3-1 .3-1 0-1.7-.9-.6-.8-.6-2.3 0-1.5.6-2.3.6-.9 1.7-.9.5 0 1 .3.4.2.6.7zm-2.9 2.2q0 1.1.4 1.7.3.6 1 .6.8 0 1.1-.6.4-.6.4-1.7t-.4-1.7q-.3-.6-1-.6-.8 0-1.1.6-.4.5-.4 1.7zm17.7-4.7v1l-1-.4-1-.2q-.7 0-1.2.4-.4.3-.4 1 0 .5.3.7.3.3 1 .5l.6.1q1.2.3 1.7.9.5.5.5 1.5 0 1.2-.7 1.8-.7.6-2 .6l-1.2-.2q-.6 0-1.2-.3v-1.2l1.2.6 1.1.2q.8 0 1.3-.4t.5-1q0-.6-.4-1-.3-.3-1-.4l-.6-.2q-1.1-.2-1.6-.7-.6-.5-.6-1.4 0-1 .8-1.7.7-.7 1.9-.7l1 .1 1 .4zm4.9 0v1.7h2.2v.8h-2.2v3.2q0 .7.3 1 .2.2.8.2h1.1v.8h-1.2q-1 0-1.5-.4-.5-.5-.5-1.6v-3.2h-1.6v-.8h1.6v-1.7zm7 2.4q-.7 0-1.1.6-.4.5-.4 1.7 0 1.1.4 1.7.4.6 1.1.6.8 0 1.2-.6.3-.6.3-1.7 0-1.2-.3-1.7-.4-.6-1.2-.6zm0-.9q1.3 0 2 .9.6.8.6 2.3 0 1.5-.7 2.3-.6.9-1.9.9-1.2 0-1.9-.9-.7-.8-.7-2.3 0-1.5.7-2.3.7-.9 2-.9zm9.9 1.4-.6-.3-.8-.1q-.9 0-1.4.5-.5.6-.5 1.7v3h-1v-6h1v1.2q.3-.7.8-1 .5-.4 1.2-.4l.7.1.6.3zm4.6 1.8h-.4q-.8 0-1.3.3-.4.3-.4.9 0 .5.3.8.4.3 1 .3.8 0 1.2-.5.5-.6.5-1.6v-.2zm1.9-.4v3.4h-1v-.9q-.3.5-.8.8-.5.3-1.2.3-.9 0-1.4-.6-.6-.5-.6-1.4 0-1 .7-1.5t2-.5h1.3v-.2q0-.7-.4-1-.3-.3-1.1-.3l-1 .1-1 .4v-1l1-.3h1q.7 0 1.3.2.5.2.8.6l.3.7v1.2zm5.9.4q0-1.2-.4-1.7-.3-.6-1-.6t-1.1.6q-.4.5-.4 1.6 0 1.2.4 1.7.4.6 1.1.6.7 0 1-.6.4-.5.4-1.7zm1 2.6q0 1.4-.6 2-.7.8-2 .8l-.8-.1-.9-.2v-1l1 .4h.8q.8 0 1.1-.4.4-.4.4-1.3v-.8q-.2.5-.6.8-.4.2-1 .2-1 0-1.7-.8-.6-.8-.6-2.3 0-1.4.6-2.2.6-.9 1.7-.9.6 0 1 .3.4.2.6.7v-.8h1zm7.4-3.1v.5h-4.3q0 1.2.5 1.8.5.5 1.5.5l1-.1 1-.5v1l-1 .3-1 .2q-1.5 0-2.2-.9-.8-.8-.8-2.3 0-1.4.7-2.3.8-.9 2.1-.9 1.1 0 1.8.8t.7 1.9zm-1-.3q0-.6-.4-1-.4-.5-1.1-.5-.8 0-1.2.4-.5.5-.5 1.1zm11-.2v3.1h1.3q1 0 1.3-.3.4-.4.4-1.1 0-1-.4-1.4-.4-.3-1.3-.3zm0-3.1v2.2h1.2q.8 0 1.2-.3.3-.3.3-.8 0-.6-.3-.9-.4-.2-1.2-.2zm-1.1-1h2.4q1.2 0 1.9.6.6.5.6 1.5 0 .5-.3 1-.4.4-1 .5.7.1 1.2.7.4.5.4 1.6 0 1-.7 1.6-.7.6-2.1.6h-2.4zm7.1 5.8V212h1v3.7q0 .8.3 1.2.3.4 1 .4.6 0 1-.5t.4-1.4V212h1v6h-1v-.9q-.3.5-.8.8-.4.3-1 .3-1 0-1.4-.6-.5-.7-.5-1.9zm11.7 2-.8.3q-.4.2-.9.2-1.4 0-2.2-.9-.8-.8-.8-2.3 0-1.5.8-2.3.8-.9 2.2-.9.5 0 .9.2.4 0 .8.3v1l-.8-.5-.9-.1q-1 0-1.4.6-.5.6-.5 1.7t.5 1.7q.5.6 1.4.6l1-.1.7-.5zm2.6-8.1h1v4.9l2.6-2.5h1.2l-2.4 2.3 2.8 3.7h-1.3l-2.2-3.1-.7.7v2.4h-1zm11.7 4.9v.5h-4.3q0 1.2.5 1.8.5.5 1.5.5l1-.1 1-.5v1l-1 .3-1 .2q-1.5 0-2.2-.9-.8-.8-.8-2.3 0-1.4.7-2.3.8-.9 2.1-.9 1.1 0 1.8.8t.7 1.9zm-1-.3q0-.6-.4-1-.4-.5-1.1-.5-.8 0-1.2.4-.5.5-.5 1.1zm5.3-3.9v1.7h2.2v.8h-2.2v3.2q0 .7.3 1 .2.2.8.2h1.1v.8h-1.2q-1 0-1.5-.4-.5-.5-.5-1.6v-3.2h-1.6v-.8h1.6v-1.7zm20.6 11v-14h.8v14zm28 0v-14h.8v14zm-644-65v-14h.8v14zm0 26v-14h.8v14zm0 26v-14h.8v14zm112 0v-14h.8v14zm35 0v-14h.8v14zm119.4-2.9L385 199h6.6zm111.6 2.9v-14h.8v14z"/>
|
||||
<path d="M534.9 208.3v-14h.8v14zm28 0v-14h.8v14zm175 0v-14h.8v14zm28 0v-14h.8v14zm-532-13v-14h.8v14zm35 0v-14h.8v14zm119 0v-14h.8v14zm112 0v-14h.8v14zm14 0v-14h.8v14z"/>
|
||||
<path d="M534.9 195.3v-14h.8v14zm28 0v-6.9h3.8v1h-3v6zm4-6v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm10 6v-6h-3v-.9h3.8v7zm28 0v-14h.8v14zm-532-13v-14h.8v14zm35 0v-14h.8v14zm117.6-4.3h1.6v-6l-2 1v-1l2-1h1.1v7h1.7v1h-4.4zm113.4 4.3v-14h.8v14z"/>
|
||||
<path d="M534.9 182.3v-14h.8v14zm231 0v-14h.8v14zm-532-13v-14h.8v14zm35 0v-14h.8v14zm119 0v-14h.8v14zm112 0v-14h.8v14zm14 0v-14h.8v14z"/>
|
||||
<path d="M534.9 169.3v-14h.8v14zm28-6v-8h.8v7.1h3v1zm4 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h3v-7.1h.8v8zm31 6v-14h.8v14zm-532-13v-14h.8v14zm35 0v-14h.8v14zm116.1-2.9 3.3-6.5 3.3 6.5zm114.9 2.9v-14h.8v14z"/>
|
||||
<path d="M534.9 156.3v-14h.8v14zm28 0v-14h.8v14zm175 0v-14h.8v14zm28 0v-14h.8v14zm-731-19v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h3v-7.1h.8v8zm31 6v-14h.8v14zm35 0v-14h.8v14zm28-6v-8h.8v7.1h3v1zm4 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h3v-7.1h.8v8zm31 6v-14h.8v14zm14 0v-14h.8v7.1h3v1h-3v6zm11-6v-.9h6.8v1z"/>
|
||||
<path d="M534.9 143.3v-14h.8v14zm4-6v-.9h6.8v1zm14.1 3.1V134l6.6 3.2zm9.9 2.9v-14h.8v14zm30.9-3.6-.9.3q-.4.2-1 .2-1.5 0-2.3-1.1-.9-1.1-.9-3.1t.9-3q.9-1.2 2.4-1.2l1 .1.8.4v1l-.9-.4q-.4-.2-1-.2-1 0-1.5.8t-.5 2.5q0 1.6.5 2.5.5.8 1.6.8l1-.2.8-.5zm5.1-1.9q0 .7.2 1 .3.4.7.4h1.2v.8h-1.3q-.9 0-1.3-.6-.5-.5-.5-1.6v-5.4h-1.6v-.8h2.6zm6.4-3.1q-.7 0-1.1.6-.4.5-.4 1.7 0 1.1.4 1.7.4.6 1.1.6.8 0 1.2-.6.3-.6.3-1.7 0-1.2-.3-1.7-.4-.6-1.2-.6zm0-.9q1.3 0 2 .9.6.8.6 2.3 0 1.5-.7 2.3-.6.9-1.9.9-1.2 0-1.9-.9-.7-.8-.7-2.3 0-1.5.7-2.3.7-.9 2-.9zm4.7 3.9V134h1v3.7q0 .8.3 1.2.3.4 1 .4.6 0 1-.5t.4-1.4V134h1v6h-1v-.9q-.3.5-.8.8-.4.3-1 .3-1 0-1.4-.6-.5-.7-.5-1.9zm10.6-2.9v-3.2h1v8.4h-1v-.8q-.2.5-.6.7-.5.3-1 .3-1 0-1.7-.9-.6-.8-.6-2.3 0-1.5.6-2.3.6-.9 1.7-.9.5 0 1 .3.4.2.6.7zm-2.9 2.2q0 1.1.4 1.7.3.6 1 .6.8 0 1.1-.6.4-.6.4-1.7t-.4-1.7q-.3-.6-1-.6-.8 0-1.1.6-.4.5-.4 1.7zm16.3-.8q.5.1.8.4.3.3.7 1.2l1.1 2.2h-1.1l-1-2q-.4-.9-.7-1.1-.4-.3-.9-.3h-1v3.4h-1.1v-8h2.2q1.3 0 2 .6t.7 1.7q0 .8-.4 1.3t-1.2.6zm-2-3.2v2.8h1.1q.8 0 1.2-.3.4-.4.4-1.1 0-.7-.4-1-.4-.4-1.2-.4zm6 4.7V134h1v3.7q0 .8.3 1.2.3.4 1 .4.6 0 1-.5t.4-1.4V134h1v6h-1v-.9q-.3.5-.8.8-.4.3-1 .3-1 0-1.4-.6-.5-.7-.5-1.9zm11.6-1.4v3.7h-1v-3.7q0-.8-.2-1.2-.3-.4-1-.4-.6 0-1 .5t-.4 1.4v3.4h-1v-6h1v.9q.3-.5.8-.8.4-.3 1-.3 1 0 1.4.6.4.6.4 1.9zm13.8-4v1l-1-.4-1-.2q-.7 0-1.2.4-.4.3-.4 1 0 .5.3.7.3.3 1 .5l.6.1q1.2.3 1.7.9.5.5.5 1.5 0 1.2-.7 1.8-.7.6-2 .6l-1.2-.2q-.6 0-1.2-.3v-1.2l1.2.6 1.1.2q.8 0 1.3-.4t.5-1q0-.6-.4-1-.3-.3-1-.4l-.6-.2q-1.1-.2-1.6-.7-.6-.5-.6-1.4 0-1 .8-1.7.7-.7 1.9-.7l1 .1 1 .4zm7.6 4.2v.5h-4.3q0 1.2.5 1.8.5.5 1.5.5l1-.1 1-.5v1l-1 .3-1 .2q-1.5 0-2.2-.9-.8-.8-.8-2.3 0-1.4.7-2.3.8-.9 2.1-.9 1.1 0 1.8.8t.7 1.9zm-1-.3q0-.6-.4-1-.4-.5-1.1-.5-.8 0-1.2.4-.5.5-.5 1.1zm8.2-1-.6-.3-.8-.1q-.9 0-1.4.5-.5.6-.5 1.7v3h-1v-6h1v1.2q.3-.7.8-1 .5-.4 1.2-.4l.7.1.6.3zm1.3-1.2h1l1.8 5 1.8-5h1l-2.2 6h-1.2zm8.2 0h2.5v5.2h2v.8h-5v-.8h2v-4.4h-1.5zm1.5-2.3h1v1.2h-1zm9.5 8-.8.3q-.4.2-.9.2-1.4 0-2.2-.9-.8-.8-.8-2.3 0-1.5.8-2.3.8-.9 2.2-.9.5 0 .9.2.4 0 .8.3v1l-.8-.5-.9-.1q-1 0-1.4.6-.5.6-.5 1.7t.5 1.7q.5.6 1.4.6l1-.1.7-.5zm7.3-3.2v.5h-4.3q0 1.2.5 1.8.5.5 1.5.5l1-.1 1-.5v1l-1 .3-1 .2q-1.5 0-2.2-.9-.8-.8-.8-2.3 0-1.4.7-2.3.8-.9 2.1-.9 1.1 0 1.8.8t.7 1.9zm-1-.3q0-.6-.4-1-.4-.5-1.1-.5-.8 0-1.2.4-.5.5-.5 1.1zm32.9 7.1v-14h.8v14zm28 0v-14h.8v14zm-697.5-63v1l-1-.4-1-.2q-.7 0-1.2.4-.4.3-.4 1 0 .5.3.7.3.3 1 .5l.6.1q1.2.3 1.7.9.5.5.5 1.5 0 1.2-.7 1.8-.7.6-2 .6L65 88q-.6 0-1.2-.3v-1.2l1.2.6 1.1.2q.8 0 1.3-.4t.5-1q0-.6-.4-1-.3-.3-1-.4l-.6-.2q-1.1-.2-1.6-.7-.6-.5-.6-1.4 0-1 .8-1.7.7-.7 1.9-.7l1 .1 1 .4zm137.5 24v-14h.8v14z"/>
|
||||
<path d="M205.9 117.3v-14h.8v14z"/>
|
||||
<path d="M205.9 130.3v-14h.8v14zm4-19v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm10 19v-14h.8v14zm35 0v-14h.8v14zm28 0v-14h.8v14zm175 0v-14h.8v14zm28 0v-14h.8v14zm35 0v-14h.8v14zm28 0v-14h.8v14zm175 0v-14h.8v14zm28 0v-14h.8v14z"/>
|
||||
<path d="M234.7 111.3v6h-.8v-6h-3v-.9h3v-7.1h.8v7.1h3v1zm3.2 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm11.3-1.6q.8.2 1.2.8.4.5.4 1.3 0 1-.7 1.7-.8.7-2.1.7l-1.1-.1-1.2-.3v-1.1l1.1.4 1 .1q1 0 1.4-.4.5-.4.5-1.1 0-.7-.5-1.1-.4-.4-1.3-.4h-.8v-1h.8q.8 0 1.2-.3.4-.3.4-.9 0-.6-.4-1-.3-.3-1-.3l-1 .2q-.6 0-1.1.3v-1l1.1-.3h1q1.1 0 1.8.5.7.6.7 1.6 0 .6-.4 1-.3.5-1 .7zm2.7 1.6v-.9h6.8v1zm7 0v-.9h6.8v1zm7.1 3.1V108l6.6 3.2zm9.9 2.9v-14h.8v14zm175 0v-14h.8v14zm28 0v-14h.8v14zm14 0v-14h.8v14zm21 0v-14h.8v14zm28 0v-6.9h3.8v1h-3v6zm4-6v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm10 6v-6h-3v-.9h3.8v7zm28 0v-14h.8v14z"/>
|
||||
<path d="M233.9 104.3v-14h.8v14zm35 0v-14h.8v14zm28 0v-14h.8v14zm175 0v-14h.8v14zm28 0v-14h.8v14zm35 0v-14h.8v14zm231 0v-14h.8v14zM76 84.5v.5h-4.3q0 1.2.5 1.8.5.5 1.5.5l1-.1 1-.5v1l-1 .3-1 .2q-1.5 0-2.2-.9-.8-.8-.8-2.3 0-1.4.7-2.3.8-.9 2.1-.9 1.1 0 1.8.8t.7 1.9zm-1-.3q0-.6-.4-1-.4-.5-1.1-.5-.8 0-1.2.4-.5.5-.5 1.1zm8.2-1-.6-.3-.8-.1q-.9 0-1.4.5-.5.6-.5 1.7v3h-1v-6h1v1.2q.3-.7.8-1 .5-.4 1.2-.4l.7.1.6.3zm1.3-1.2h1l1.8 5 1.8-5h1l-2.2 6h-1.2zm8.2 0h2.5v5.2h2v.8h-5v-.8h2v-4.4h-1.5zm1.5-2.3h1v1.2h-1zm9.5 8-.8.3q-.4.2-.9.2-1.4 0-2.2-.9-.8-.8-.8-2.3 0-1.5.8-2.3.8-.9 2.2-.9.5 0 .9.2.4 0 .8.3v1l-.8-.5-.9-.1q-1 0-1.4.6-.5.6-.5 1.7t.5 1.7q.5.6 1.4.6l1-.1.7-.5zm7.3-3.2v.5h-4.3q0 1.2.5 1.8.5.5 1.5.5l1-.1 1-.5v1l-1 .3-1 .2q-1.5 0-2.2-.9-.8-.8-.8-2.3 0-1.4.7-2.3.8-.9 2.1-.9 1.1 0 1.8.8t.7 1.9zm-1-.3q0-.6-.4-1-.4-.5-1.1-.5-.8 0-1.2.4-.5.5-.5 1.1zm12.3-3.2-1.1 4h2.3zm-.6-1h1.3l2.4 8h-1.1l-.6-2H121l-.6 2h-1.1zm10 7.7-.8.3q-.4.2-.9.2-1.4 0-2.2-.9-.8-.8-.8-2.3 0-1.5.8-2.3.8-.9 2.2-.9.5 0 .9.2.4 0 .8.3v1l-.8-.5-.9-.1q-1 0-1.4.6-.5.6-.5 1.7t.5 1.7q.5.6 1.4.6l1-.1.7-.5zm7 0-.8.3q-.4.2-.9.2-1.4 0-2.2-.9-.8-.8-.8-2.3 0-1.5.8-2.3.8-.9 2.2-.9.5 0 .9.2.4 0 .8.3v1l-.8-.5-.9-.1q-1 0-1.4.6-.5.6-.5 1.7t.5 1.7q.5.6 1.4.6l1-.1.7-.5zm4.6-5q-.7 0-1.1.6-.4.5-.4 1.7 0 1.1.4 1.7.4.6 1.1.6.8 0 1.2-.6.3-.6.3-1.7 0-1.2-.3-1.7-.4-.6-1.2-.6zm0-.9q1.3 0 2 .9.6.8.6 2.3 0 1.5-.7 2.3-.6.9-1.9.9-1.2 0-1.9-.9-.7-.8-.7-2.3 0-1.5.7-2.3.7-.9 2-.9zm4.7 3.9V82h1v3.7q0 .8.3 1.2.3.4 1 .4.6 0 1-.5t.4-1.4V82h1v6h-1v-.9q-.3.5-.8.8-.4.3-1 .3-1 0-1.4-.6-.5-.7-.5-1.9zm11.6-1.4V88h-1v-3.7q0-.8-.2-1.2-.3-.4-1-.4-.6 0-1 .5t-.4 1.4V88h-1v-6h1v.9q.3-.5.7-.8.5-.3 1.1-.3 1 0 1.4.6.4.6.4 1.9zm4.7-4V82h2.2v.8h-2.2V86q0 .7.3 1 .2.2.8.2h1.1v.8h-1.2q-1 0-1.5-.4-.5-.5-.5-1.6v-3.2h-1.6V82h1.6v-1.7zm41.6 11v-14h.8v14z"/>
|
||||
<path d="M233.9 91.3v-14h.8v14zm35 0v-14h.8v14zm28 0v-14h.8v14zM332.3 81l-1.1 4h2.3zm-.6-1h1.3l2.4 8h-1.1l-.6-2H331l-.6 2h-1.1zm10 7.7-.8.3q-.4.2-.9.2-1.4 0-2.2-.9-.8-.8-.8-2.3 0-1.5.8-2.3.8-.9 2.2-.9.5 0 .9.2.4 0 .8.3v1l-.8-.5-.9-.1q-1 0-1.4.6-.5.6-.5 1.7t.5 1.7q.5.6 1.4.6l1-.1.7-.5zm4.6-7.4V82h2.2v.8h-2.2V86q0 .7.3 1 .2.2.8.2h1.1v.8h-1.2q-1 0-1.5-.4-.5-.5-.5-1.6v-3.2h-1.6V82h1.6v-1.7zm5.4 1.7h2.5v5.2h2v.8h-5v-.8h2v-4.4h-1.5zm1.5-2.3h1v1.2h-1zm7.1 3q-.7 0-1.1.6-.4.5-.4 1.7 0 1.1.4 1.7.4.6 1.1.6.8 0 1.2-.6.3-.6.3-1.7 0-1.2-.3-1.7-.4-.6-1.2-.6zm0-.9q1.3 0 2 .9.6.8.6 2.3 0 1.5-.7 2.3-.6.9-1.9.9-1.2 0-1.9-.9-.7-.8-.7-2.3 0-1.5.7-2.3.7-.9 2-.9zm9.3 2.5V88h-1v-3.7q0-.8-.2-1.2-.3-.4-1-.4-.6 0-1 .5t-.4 1.4V88h-1v-6h1v.9q.3-.5.7-.8.5-.3 1.1-.3 1 0 1.4.6.4.6.4 1.9zm6.6-2.1v1l-.8-.4-.9-.1q-.7 0-1 .2-.3.2-.3.6 0 .5.2.6l1.2.4.4.1q.8.2 1.2.6.3.4.3 1 0 1-.6 1.5t-1.8.5l-.9-.1-1-.3v-1l1 .4 1 .1q.6 0 1-.2.3-.3.3-.8 0-.7-1.3-1h-.4q-.9-.2-1.2-.6-.4-.4-.4-1 0-1 .6-1.4.5-.5 1.6-.5l1 .1.8.3zM385 80h1l.8 6.5 1-4.3h1l1 4.3.8-6.5h1l-1.2 8h-1l-1-4.8-1.2 4.8h-1zm10.3 2.7q-.7 0-1.1.6-.4.5-.4 1.7 0 1.1.4 1.7.4.6 1.1.6.8 0 1.2-.6.3-.6.3-1.7 0-1.2-.3-1.7-.4-.6-1.2-.6zm0-.9q1.3 0 2 .9.6.8.6 2.3 0 1.5-.7 2.3-.6.9-1.9.9-1.2 0-1.9-.9-.7-.8-.7-2.3 0-1.5.7-2.3.7-.9 2-.9zm9.9 1.4-.6-.3-.8-.1q-.9 0-1.4.5-.5.6-.5 1.7v3h-1v-6h1v1.2q.3-.7.8-1 .5-.4 1.2-.4l.7.1.6.3zm2.1-3.6h1v4.9l2.6-2.5h1.2l-2.4 2.3 2.8 3.7h-1.3l-2.2-3.1-.7.7V88h-1zm11.4 0v.9h-1.1q-.5 0-.8.2-.2.2-.2.8v.5h2.1v.8h-2V88h-1v-5.2H414V82h1.7v-.4q0-1 .4-1.5.5-.5 1.4-.5zm5.2 6.2q0 .7.2 1 .3.4.7.4h1.2v.8h-1.3q-.9 0-1.3-.6-.5-.5-.5-1.6v-5.4h-1.6v-.8h2.6zm6.4-3.1q-.7 0-1.1.6-.4.5-.4 1.7 0 1.1.4 1.7.4.6 1.1.6.8 0 1.2-.6.3-.6.3-1.7 0-1.2-.3-1.7-.4-.6-1.2-.6zm0-.9q1.3 0 2 .9.6.8.6 2.3 0 1.5-.7 2.3-.6.9-1.9.9-1.2 0-1.9-.9-.7-.8-.7-2.3 0-1.5.7-2.3.7-.9 2-.9zm3.7.2h1l1 4.8.9-3h.8l.9 3 1-4.8h1l-1.4 6h-1l-.9-3.3-1 3.3h-.9zm37.9 9.3v-14h.8v14zm11-6v-.9h6.8v1zM501 81l-2.6 4.2h2.5zm-.2-1h1.2v5.2h1.1v.9h-1V88h-1.2v-2h-3.4v-1zm13.1 11.3v-6h-3v-.9h3v-7.1h.8v14zm21 0v-14h.8v14zm28-6v-8h.8v7.1h3v1zm4 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h3v-7.1h.8v8zm31 6v-14h.8v14zm-560-39v-14h.8v14z"/>
|
||||
<path d="M205.9 65.3v-14h.8v14z"/>
|
||||
<path d="M205.9 78.3v-14h.8v14zm4.1-19.2 6.6-3.2v6.5zm23.9 19.2v-14h.8v14zm35 0v-14h.8v14zm28 0v-14h.8v14zm175 0v-14h.8v14zm28 0v-14h.8v14zm35 0v-14h.8v14zm28 0v-14h.8v14zm175 0v-14h.8v14zm28 0v-14h.8v14zm-549-19v-.9h6.8v1zm7 0v-.9h6.8v1zM233 61h3.7v1h-4.9v-1l1.8-1.8 1-1.1.7-1 .2-1q0-.6-.4-1t-1-.4l-1 .2-1.2.6v-1.1l1-.4 1.1-.2q1.2 0 2 .7.6.6.6 1.6l-.2 1q-.2.5-.8 1.2l-.9 1L233 61zm4.9-1.7v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1z"/>
|
||||
<path d="M269.7 59.3v6h-.8v-6h-3v-.9h3v-7.1h.8v7.1h3v1zm3.2 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm10 6v-14h.8v14zm175 0v-14h.8v14zm28 0v-14h.8v14zm14 0v-6.9h3.8v1h-3v6zm11-6v-.9h6.8v1zm10 6v-14h.8v14zm4-6v-.9h6.8v1zm14.1 3.1V56l6.6 3.2zm9.9 2.9v-14h.8v14zm35.4-4.3 1.9-7h1.1l-2.3 8h-1.3l-2.4-8h1.1zm5.4-5h2.5v5.2h2v.8h-5v-.8h2v-4.4h-1.5zm1.5-2.3h1v1.2h-1zm10 3.5-.6-.3-.8-.1q-.9 0-1.4.5-.5.6-.5 1.7v3h-1v-6h1v1.2q.3-.7.8-1 .5-.4 1.2-.4l.7.1.6.3zm4.1-2.9V56h2.2v.8h-2.2V60q0 .7.3 1 .2.2.8.2h1.1v.8h-1.2q-1 0-1.5-.4-.5-.5-.5-1.6v-3.2h-1.6V56h1.6v-1.7zm4.7 5.4V56h1v3.7q0 .8.3 1.2.3.4 1 .4.6 0 1-.5t.4-1.4V56h1v6h-1v-.9q-.3.5-.8.8-.4.3-1 .3-1 0-1.4-.6-.5-.7-.5-1.9zm9.8-.7h-.4q-.8 0-1.3.3-.4.3-.4.9 0 .5.3.8.4.3 1 .3.8 0 1.2-.5.5-.6.5-1.6V59zm1.9-.4V62h-1v-.9q-.3.5-.8.8-.5.3-1.2.3-.9 0-1.4-.6-.6-.5-.6-1.4 0-1 .7-1.5t2-.5h1.3V58q0-.7-.4-1-.3-.3-1.1-.3l-1 .1-1 .4v-1l1-.3h1q.7 0 1.3.2.5.2.8.6l.3.7v1.2zm5.2 1.2q0 .7.2 1 .3.4.7.4h1.2v.8h-1.3q-.9 0-1.3-.6-.5-.5-.5-1.6v-5.4h-1.6v-.8h2.6zm10.3-5.8h1.4l1.4 4 1.4-4h1.5v8h-1v-7l-1.5 4.1h-.8l-1.4-4.2V62h-1zm10.6 5h-.4q-.8 0-1.3.3-.4.3-.4.9 0 .5.3.8.4.3 1 .3.8 0 1.2-.5.5-.6.5-1.6V59zm1.9-.4V62h-1v-.9q-.3.5-.8.8-.5.3-1.2.3-.9 0-1.4-.6-.6-.5-.6-1.4 0-1 .7-1.5t2-.5h1.3V58q0-.7-.4-1-.3-.3-1.1-.3l-1 .1-1 .4v-1l1-.3h1q.7 0 1.3.2.5.2.8.6l.3.7v1.2zm7 3.1-.8.3q-.4.2-.9.2-1.4 0-2.2-.9-.8-.8-.8-2.3 0-1.5.8-2.3.8-.9 2.2-.9.5 0 .9.2.4 0 .8.3v1l-.8-.5-.9-.1q-1 0-1.4.6-.5.6-.5 1.7t.5 1.7q.5.6 1.4.6l1-.1.7-.5zm6.9-3.4V62h-1v-3.7q0-.8-.2-1.2-.3-.4-1-.4-.6 0-1 .5t-.4 1.4V62h-1v-8.4h1V57q.3-.5.8-.8.4-.3 1-.3 1 0 1.4.6.4.6.4 1.9zm3.1-2.3h2.5v5.2h2v.8h-5v-.8h2v-4.4h-1.5zm1.5-2.3h1v1.2h-1zm9.4 4.6V62h-1v-3.7q0-.8-.2-1.2-.3-.4-1-.4-.6 0-1 .5t-.4 1.4V62h-1v-6h1v.9q.3-.5.8-.8.4-.3 1-.3 1 0 1.4.6.4.6.4 1.9zm7.4.2v.5h-4.3q0 1.2.5 1.8.5.5 1.5.5l1-.1 1-.5v1l-1 .3-1 .2q-1.5 0-2.2-.9-.8-.8-.8-2.3 0-1.4.7-2.3.8-.9 2.1-.9 1.1 0 1.8.8t.7 1.9zm-1-.3q0-.6-.4-1-.4-.5-1.1-.5-.8 0-1.2.4-.5.5-.5 1.1zm39.9 7.1v-14h.8v14zm28 0v-14h.8v14zm-532-13v-14h.8v14z"/>
|
||||
<path d="M268.9 52.3v-14h.8v14zm28 0v-14h.8v14zm175 0v-14h.8v14zm28 0v-14h.8v14zm35 0v-14h.8v14zm28 0v-14h.8v14zm175 0v-14h.8v14zm28 0v-14h.8v14zm-731-19v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm10 6v-6h-3v-.9h3.8v7zm28 0v-14h.8v14z"/>
|
||||
<path d="M268.9 39.3v-14h.8v14zm28 0v-6.9h3.8v1h-3v6zm4-6v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm10 6v-6h-3v-.9h3.8v7zm28 0v-14h.8v14zm35 0v-14h.8v14zm28 0v-6.9h3.8v1h-3v6zm4-6v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm10 6v-6h-3v-.9h3.8v7zm28 0v-14h.8v14zm-532-13v-14h.8v14z"/>
|
||||
<path d="M268.9 26.3v-14h.8v14zm231 0v-14h.8v14zm35 0v-14h.8v14zm231 0v-14h.8v14zm-759-19v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm10 6v-6h-3v-.9h3.8v7z"/>
|
||||
<path d="M268.9 13.3V6.4h3.8v1h-3v6zm4-6v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm10 6v-6h-3v-.9h3.8v7zm35 0V6.4h3.8v1h-3v6zm4-6v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm10 6v-6h-3v-.9h3.8v7z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 36 KiB |
@ -0,0 +1,241 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 806 430">
|
||||
<style>
|
||||
path {
|
||||
fill: #333;
|
||||
stroke: #333;
|
||||
stroke-width: 0.5;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
path {
|
||||
fill: #ccc;
|
||||
stroke: #ccc;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<path d="M1.3 171H6v.9H2.3v2.1h3.3v1H2.3v4h-1z"/>
|
||||
<path d="M37.9 195.3v-14h.8v14z"/>
|
||||
<path d="M37.9 208.3v-14h.8v14z"/>
|
||||
<path d="M37.9 221.3v-14h.8v14z"/>
|
||||
<path d="M37.9 234.3v-14h.8v14z"/>
|
||||
<path d="M37.9 247.3v-14h.8v14z"/>
|
||||
<path d="M37.9 260.3v-14h.8v14z"/>
|
||||
<path d="M37.9 273.3v-14h.8v14z"/>
|
||||
<path d="M37.9 286.3v-14h.8v14z"/>
|
||||
<path d="M37.9 299.3v-14h.8v14z"/>
|
||||
<path d="M37.9 312.3v-14h.8v14z"/>
|
||||
<path d="M37.9 325.3v-14h.8v14z"/>
|
||||
<path d="M37.9 338.3v-14h.8v14z"/>
|
||||
<path d="M37.9 351.3v-14h.8v14z"/>
|
||||
<path d="M37.9 364.3v-14h.8v14z"/>
|
||||
<path d="M37.9 377.3v-14h.8v14z"/>
|
||||
<path d="M37.9 390.3v-14h.8v14z"/>
|
||||
<path d="M37.9 403.3v-14h.8v14z"/>
|
||||
<path d="M37.9 416.3v-14h.8v14z"/>
|
||||
<path d="M37.9 423.3v-8h.8v7.1h3v1zm4 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h3v-7.1h.8v8z"/>
|
||||
<path d="m55 412.3-1 .6q-.6.3-1.2.3-1.6 0-2.4-1.1-.9-1.1-.9-3.1t1-3q.8-1.2 2.3-1.2l1 .2.9.4v1.1l-1-.6q-.4-.2-.9-.2-1 0-1.6.8-.5.9-.5 2.5 0 1.7.5 2.5t1.6.8l.6-.1q.3 0 .5-.3v-2.1h-1.2v-1H55zm4.3-4.6q-.7 0-1.1.6-.4.5-.4 1.7 0 1.1.4 1.7.4.6 1.1.6.8 0 1.2-.6.3-.6.3-1.7 0-1.2-.3-1.7-.4-.6-1.2-.6zm0-.9q1.3 0 2 .9.6.8.6 2.3 0 1.5-.7 2.3-.6.9-1.9.9-1.2 0-1.9-.9-.7-.8-.7-2.3 0-1.5.7-2.3.7-.9 2-.9zm7 .9q-.7 0-1.1.6-.4.5-.4 1.7 0 1.1.4 1.7.4.6 1.1.6.8 0 1.2-.6.3-.6.3-1.7 0-1.2-.3-1.7-.4-.6-1.2-.6zm0-.9q1.3 0 2 .9.6.8.6 2.3 0 1.5-.7 2.3-.6.9-1.9.9-1.2 0-1.9-.9-.7-.8-.7-2.3 0-1.5.7-2.3.7-.9 2-.9zm8.3 3.2q0-1.2-.4-1.7-.3-.6-1-.6t-1.1.6q-.4.5-.4 1.6 0 1.2.4 1.7.4.6 1.1.6.7 0 1-.6.4-.5.4-1.7zm1 2.6q0 1.4-.6 2-.7.8-2 .8l-.8-.1-.9-.2v-1l1 .4h.8q.8 0 1.1-.4.4-.4.4-1.3v-.8q-.2.5-.6.8-.4.2-1 .2-1 0-1.7-.8-.6-.8-.6-2.3 0-1.4.6-2.2.6-.9 1.7-.9.6 0 1 .3.4.2.6.7v-.8h1zm5.3-1.8q0 .7.2 1 .3.4.7.4H83v.8h-1.3q-.9 0-1.3-.6-.5-.5-.5-1.6v-5.4h-1.6v-.8h2.6zm9.1-1.3v.5h-4.3q0 1.2.5 1.8.5.5 1.5.5l1-.1 1-.5v1l-1 .3-1 .2q-1.5 0-2.2-.9-.8-.8-.8-2.3 0-1.4.7-2.3.8-.9 2.1-.9 1.1 0 1.8.8t.7 1.9zm-1-.3q0-.6-.4-1-.4-.5-1.1-.5-.8 0-1.2.4-.5.5-.5 1.1zm14.8 3.5-.9.3q-.4.2-1 .2-1.5 0-2.3-1.1-.9-1.1-.9-3.1t.9-3q.9-1.2 2.4-1.2l1 .1.8.4v1l-.9-.4q-.4-.2-1-.2-1 0-1.5.8t-.5 2.5q0 1.6.5 2.5.5.8 1.6.8l1-.2.8-.5zm5.1-1.9q0 .7.2 1 .3.4.7.4h1.2v.8h-1.3q-.9 0-1.3-.6-.5-.5-.5-1.6v-5.4h-1.6v-.8h2.6zm6.4-3.1q-.7 0-1.1.6-.4.5-.4 1.7 0 1.1.4 1.7.4.6 1.1.6.8 0 1.2-.6.3-.6.3-1.7 0-1.2-.3-1.7-.4-.6-1.2-.6zm0-.9q1.3 0 2 .9.6.8.6 2.3 0 1.5-.7 2.3-.6.9-1.9.9-1.2 0-1.9-.9-.7-.8-.7-2.3 0-1.5.7-2.3.7-.9 2-.9zm4.7 3.9V407h1v3.7q0 .8.3 1.2.3.4 1 .4.6 0 1-.5t.4-1.4V407h1v6h-1v-.9q-.3.5-.8.8-.4.3-1 .3-1 0-1.4-.6-.5-.7-.5-1.9zm10.6-2.9v-3.2h1v8.4h-1v-.8q-.2.5-.6.7-.5.3-1 .3-1 0-1.7-.9-.6-.8-.6-2.3 0-1.5.6-2.3.6-.9 1.7-.9.5 0 1 .3.4.2.6.7zm-2.9 2.2q0 1.1.4 1.7.3.6 1 .6.8 0 1.1-.6.4-.6.4-1.7t-.4-1.7q-.3-.6-1-.6-.8 0-1.1.6-.4.5-.4 1.7z"/>
|
||||
<path d="M268.9 416.3v-14h.8v14z"/>
|
||||
<path d="M65.9 221.3v-6.9h3.8v1h-3v6z"/>
|
||||
<path d="M65.9 234.3v-14h.8v14z"/>
|
||||
<path d="M65.9 247.3v-14h.8v14z"/>
|
||||
<path d="M65.9 260.3v-14h.8v14z"/>
|
||||
<path d="M65.9 267.3v-8h.8v7.1h3v1zm0 71v-6.9h3.8v1h-3v6z"/>
|
||||
<path d="M65.9 351.3v-14h.8v14z"/>
|
||||
<path d="M65.9 364.3v-14h.8v14z"/>
|
||||
<path d="M65.9 377.3v-14h.8v14z"/>
|
||||
<path d="M65.9 384.3v-8h.8v7.1h3v1z"/>
|
||||
<path d="M268.9 403.3v-14h.8v14z"/>
|
||||
<path d="M69.9 384.3v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h3v-7.1h.8v8z"/>
|
||||
<path d="M268.9 390.3v-14h.8v14z"/>
|
||||
<path d="M293.9 384.3v-.9h6.8v1zm14 0v-.9h6.8v1zm14.1 3.1V381l6.6 3.2zm18.9-4.4q0-1.8-.4-2.5-.4-.8-1.2-.8-.8 0-1.2.8-.3.7-.3 2.5t.3 2.5q.4.8 1.2.8.8 0 1.2-.8.4-.7.4-2.5zm1 0q0 2-.6 3.1-.6 1-2 1t-2-1q-.7-1-.7-3.1t.7-3.1q.7-1 2-1 1.4 0 2 1 .7 1 .7 3.1zm4.4-3-1.1 4h2.3zm-.6-1h1.3l2.4 8h-1.1l-.6-2H345l-.6 2h-1.1zm5.3 5.7V381h1v3.7q0 .8.3 1.2.3.4 1 .4.6 0 1-.5t.4-1.4V381h1v6h-1v-.9q-.3.5-.8.8-.4.3-1 .3-1 0-1.4-.6-.5-.7-.5-1.9zm9.3-5.4v1.7h2.2v.8h-2.2v3.2q0 .7.3 1 .2.2.8.2h1.1v.8h-1.2q-1 0-1.5-.4-.5-.5-.5-1.6v-3.2h-1.6v-.8h1.6v-1.7zm9.3 4v3.7h-1v-3.7q0-.8-.2-1.2-.3-.4-1-.4-.6 0-1 .5t-.4 1.4v3.4h-1v-8.4h1v3.3q.3-.5.7-.8.5-.3 1.1-.3 1 0 1.4.6.4.6.4 1.9zM380 386h3.7v1h-4.9v-1l1.8-1.8 1-1.1.7-1 .2-1q0-.6-.4-1t-1-.4l-1 .2-1.2.6v-1.1l1-.4 1.1-.2q1.2 0 2 .7.6.6.6 1.6l-.2 1q-.2.5-.8 1.2l-.9 1-1.7 1.8zm7-1.2h2v2.2h-2zm8.3-5.9q1.3 0 2 1 .6 1 .6 3.1 0 2-.6 3.1-.7 1-2 1t-2-1q-.6-1-.6-3 0-2.1.7-3.2.6-1 2-1zm0 7.4q.8 0 1.1-.8.4-.8.4-2.5v-1.6l-2.6 4q.4.9 1.1.9zm0-6.6q-.7 0-1.1.8-.4.8-.4 2.5l.1 1.4 2.5-4q-.4-.7-1-.7zm14 .3-1.1 4h2.3zm-.6-1h1.3l2.4 8h-1.1l-.6-2H408l-.6 2h-1.1zm10 7.7-.8.3q-.4.2-.9.2-1.4 0-2.2-.9-.8-.8-.8-2.3 0-1.5.8-2.3.8-.9 2.2-.9.5 0 .9.2.4 0 .8.3v1l-.8-.5-.9-.1q-1 0-1.4.6-.5.6-.5 1.7t.5 1.7q.5.6 1.4.6l1-.1.7-.5zm7 0-.8.3q-.4.2-.9.2-1.4 0-2.2-.9-.8-.8-.8-2.3 0-1.5.8-2.3.8-.9 2.2-.9.5 0 .9.2.4 0 .8.3v1l-.8-.5-.9-.1q-1 0-1.4.6-.5.6-.5 1.7t.5 1.7q.5.6 1.4.6l1-.1.7-.5zm7.3-3.2v.5h-4.3q0 1.2.5 1.8.5.5 1.5.5l1-.1 1-.5v1l-1 .3-1 .2q-1.5 0-2.2-.9-.8-.8-.8-2.3 0-1.4.7-2.3.8-.9 2.1-.9 1.1 0 1.8.8t.7 1.9zm-1-.3q0-.6-.4-1-.4-.5-1.1-.5-.8 0-1.2.4-.5.5-.5 1.1zm7.2-2v1l-.8-.4-.9-.1q-.7 0-1 .2-.3.2-.3.6 0 .5.2.6l1.2.4.4.1q.8.2 1.2.6.3.4.3 1 0 1-.6 1.5t-1.8.5l-.9-.1-1-.3v-1l1 .4 1 .1q.6 0 1-.2.3-.3.3-.8 0-.7-1.3-1h-.4q-.9-.2-1.2-.6-.4-.4-.4-1 0-1 .6-1.4.5-.5 1.6-.5l1 .1.8.3zm7 0v1l-.8-.4-.9-.1q-.7 0-1 .2-.3.2-.3.6 0 .5.2.6l1.2.4.4.1q.8.2 1.2.6.3.4.3 1 0 1-.6 1.5t-1.8.5l-.9-.1-1-.3v-1l1 .4 1 .1q.6 0 1-.2.3-.3.3-.8 0-.7-1.3-1h-.4q-.9-.2-1.2-.6-.4-.4-.4-1 0-1 .6-1.4.5-.5 1.6-.5l1 .1.8.3zm9.1-2.2h6v.9H459v7.1h-1.1v-7.1h-2.5zm10 2.7q-.7 0-1.1.6-.4.5-.4 1.7 0 1.1.4 1.7.4.6 1.1.6.8 0 1.2-.6.3-.6.3-1.7 0-1.2-.3-1.7-.4-.6-1.2-.6zm0-.9q1.3 0 2 .9.6.8.6 2.3 0 1.5-.7 2.3-.6.9-1.9.9-1.2 0-1.9-.9-.7-.8-.7-2.3 0-1.5.7-2.3.7-.9 2-.9zm5-2.2h1v4.9l2.6-2.5h1.2l-2.4 2.3 2.8 3.7h-1.3l-2.2-3.1-.7.7v2.4h-1zm11.7 4.9v.5h-4.3q0 1.2.5 1.8.5.5 1.5.5l1-.1 1-.5v1l-1 .3-1 .2q-1.5 0-2.2-.9-.8-.8-.8-2.3 0-1.4.7-2.3.8-.9 2.1-.9 1.1 0 1.8.8t.7 1.9zm-1-.3q0-.6-.4-1-.4-.5-1.1-.5-.8 0-1.2.4-.5.5-.5 1.1zm7.6.1v3.7h-1v-3.7q0-.8-.2-1.2-.3-.4-1-.4-.6 0-1 .5t-.4 1.4v3.4h-1v-6h1v.9q.3-.5.7-.8.5-.3 1.1-.3 1 0 1.4.6.4.6.4 1.9zm-385.2-30v1l-1-.4-1-.2q-.7 0-1.2.4-.4.3-.4 1 0 .5.3.7.3.3 1 .5l.6.1q1.2.3 1.7.9.5.5.5 1.5 0 1.2-.7 1.8-.7.6-2 .6l-1.2-.2q-.6 0-1.2-.3v-1.2l1.2.6 1.1.2q.8 0 1.3-.4t.5-1q0-.6-.4-1-.3-.3-1-.4l-.6-.2q-1.1-.2-1.6-.7-.6-.5-.6-1.4 0-1 .8-1.7.7-.7 1.9-.7l1 .1 1 .4zm137.5 24v-14h.8v14z"/>
|
||||
<path d="M268.9 377.3v-14h.8v14z"/>
|
||||
<path d="M289.9 377.3v-14h.8v14zM111 357.5v.5h-4.3q0 1.2.5 1.8.5.5 1.5.5l1-.1 1-.5v1l-1 .3-1 .2q-1.5 0-2.2-.9-.8-.8-.8-2.3 0-1.4.7-2.3.8-.9 2.1-.9 1.1 0 1.8.8t.7 1.9zm-1-.3q0-.6-.4-1-.4-.5-1.1-.5-.8 0-1.2.4-.5.5-.5 1.1zm8.2-1-.6-.3-.8-.1q-.9 0-1.4.5-.5.6-.5 1.7v3h-1v-6h1v1.2q.3-.7.8-1 .5-.4 1.2-.4l.7.1.6.3zm1.3-1.2h1l1.8 5 1.8-5h1l-2.2 6h-1.2zm8.2 0h2.5v5.2h2v.8h-5v-.8h2v-4.4h-1.5zm1.5-2.3h1v1.2h-1zm9.5 8-.8.3q-.4.2-.9.2-1.4 0-2.2-.9-.8-.8-.8-2.3 0-1.5.8-2.3.8-.9 2.2-.9.5 0 .9.2.4 0 .8.3v1l-.8-.5-.9-.1q-1 0-1.4.6-.5.6-.5 1.7t.5 1.7q.5.6 1.4.6l1-.1.7-.5zm7.3-3.2v.5h-4.3q0 1.2.5 1.8.5.5 1.5.5l1-.1 1-.5v1l-1 .3-1 .2q-1.5 0-2.2-.9-.8-.8-.8-2.3 0-1.4.7-2.3.8-.9 2.1-.9 1.1 0 1.8.8t.7 1.9zm-1-.3q0-.6-.4-1-.4-.5-1.1-.5-.8 0-1.2.4-.5.5-.5 1.1zm12.3-3.2-1.1 4h2.3zm-.6-1h1.3l2.4 8h-1.1l-.6-2H156l-.6 2h-1.1zm10 7.7-.8.3q-.4.2-.9.2-1.4 0-2.2-.9-.8-.8-.8-2.3 0-1.5.8-2.3.8-.9 2.2-.9.5 0 .9.2.4 0 .8.3v1l-.8-.5-.9-.1q-1 0-1.4.6-.5.6-.5 1.7t.5 1.7q.5.6 1.4.6l1-.1.7-.5zm7 0-.8.3q-.4.2-.9.2-1.4 0-2.2-.9-.8-.8-.8-2.3 0-1.5.8-2.3.8-.9 2.2-.9.5 0 .9.2.4 0 .8.3v1l-.8-.5-.9-.1q-1 0-1.4.6-.5.6-.5 1.7t.5 1.7q.5.6 1.4.6l1-.1.7-.5zm4.6-5q-.7 0-1.1.6-.4.5-.4 1.7 0 1.1.4 1.7.4.6 1.1.6.8 0 1.2-.6.3-.6.3-1.7 0-1.2-.3-1.7-.4-.6-1.2-.6zm0-.9q1.3 0 2 .9.6.8.6 2.3 0 1.5-.7 2.3-.6.9-1.9.9-1.2 0-1.9-.9-.7-.8-.7-2.3 0-1.5.7-2.3.7-.9 2-.9zm4.7 3.9V355h1v3.7q0 .8.3 1.2.3.4 1 .4.6 0 1-.5t.4-1.4V355h1v6h-1v-.9q-.3.5-.8.8-.4.3-1 .3-1 0-1.4-.6-.5-.7-.5-1.9zm11.6-1.4v3.7h-1v-3.7q0-.8-.2-1.2-.3-.4-1-.4-.6 0-1 .5t-.4 1.4v3.4h-1v-6h1v.9q.3-.5.7-.8.5-.3 1.1-.3 1 0 1.4.6.4.6.4 1.9zm4.7-4v1.7h2.2v.8h-2.2v3.2q0 .7.3 1 .2.2.8.2h1.1v.8h-1.2q-1 0-1.5-.4-.5-.5-.5-1.6v-3.2h-1.6v-.8h1.6v-1.7zm41.6 11v-14h.8v14z"/>
|
||||
<path d="M268.9 364.3v-14h.8v14zm35-6v-8h.8v7.1h3v1zm4 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h3v-7.1h.8v8zm38 0v-8h.8v7.1h3v1zm4 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h3v-7.1h.8v8z"/>
|
||||
<path d="M240.9 351.3v-14h.8v14z"/>
|
||||
<path d="M268.9 351.3v-14h.8v14z"/>
|
||||
<path d="M289.9 351.3v-14h.8v14z"/>
|
||||
<path d="M303.9 351.3v-14h.8v14z"/>
|
||||
<path d="m321 347.3-1 .6q-.6.3-1.2.3-1.6 0-2.4-1.1-.9-1.1-.9-3.1t1-3q.8-1.2 2.3-1.2l1 .2.9.4v1.1l-1-.6q-.4-.2-.9-.2-1 0-1.6.8-.5.9-.5 2.5 0 1.7.5 2.5t1.6.8l.6-.1q.3 0 .5-.3v-2.1h-1.2v-1h2.2zm2.7-5.3h2.5v5.2h2v.8h-5v-.8h2v-4.4h-1.5zm1.5-2.3h1v1.2h-1zm7.1.6v1.7h2.2v.8h-2.2v3.2q0 .7.3 1 .2.2.8.2h1.1v.8h-1.2q-1 0-1.5-.4-.5-.5-.5-1.6v-3.2h-1.6v-.8h1.6v-1.7zm4.4-.3h1.1v3h3v-3h1v8h-1v-4h-3v4h-1zm7.3 5.7V342h1v3.7q0 .8.3 1.2.3.4 1 .4.6 0 1-.5t.4-1.4V342h1v6h-1v-.9q-.3.5-.8.8-.4.3-1 .3-1 0-1.4-.6-.5-.7-.5-1.9zm11-.7q0-1.2-.4-1.7-.4-.6-1.1-.6-.7 0-1.1.6-.4.6-.4 1.7t.4 1.7q.4.6 1 .6.8 0 1.2-.6.3-.6.3-1.7zm-3-2.2q.3-.5.7-.7.4-.3 1-.3 1 0 1.6.9.7.8.7 2.3 0 1.5-.7 2.3-.6.9-1.7.9-.5 0-1-.3l-.6-.7v.8h-1v-8.4h1z"/>
|
||||
<path d="M534.9 351.3v-14h.8v14zm35 0v-14h.8v14z"/>
|
||||
<path d="m587 347.3-1 .6q-.6.3-1.2.3-1.6 0-2.4-1.1-.9-1.1-.9-3.1t1-3q.8-1.2 2.3-1.2l1 .2.9.4v1.1l-1-.6q-.4-.2-.9-.2-1 0-1.6.8-.5.9-.5 2.5 0 1.7.5 2.5t1.6.8l.6-.1q.3 0 .5-.3v-2.1h-1.2v-1h2.2zm4.3-4.6q-.7 0-1.1.6-.4.5-.4 1.7 0 1.1.4 1.7.4.6 1.1.6.8 0 1.2-.6.3-.6.3-1.7 0-1.2-.3-1.7-.4-.6-1.2-.6zm0-.9q1.3 0 2 .9.6.8.6 2.3 0 1.5-.7 2.3-.6.9-1.9.9-1.2 0-1.9-.9-.7-.8-.7-2.3 0-1.5.7-2.3.7-.9 2-.9zm7 .9q-.7 0-1.1.6-.4.5-.4 1.7 0 1.1.4 1.7.4.6 1.1.6.8 0 1.2-.6.3-.6.3-1.7 0-1.2-.3-1.7-.4-.6-1.2-.6zm0-.9q1.3 0 2 .9.6.8.6 2.3 0 1.5-.7 2.3-.6.9-1.9.9-1.2 0-1.9-.9-.7-.8-.7-2.3 0-1.5.7-2.3.7-.9 2-.9zm8.3 3.2q0-1.2-.4-1.7-.3-.6-1-.6t-1.1.6q-.4.5-.4 1.6 0 1.2.4 1.7.4.6 1.1.6.7 0 1-.6.4-.5.4-1.7zm1 2.6q0 1.4-.6 2-.7.8-2 .8l-.8-.1-.9-.2v-1l1 .4h.8q.8 0 1.1-.4.4-.4.4-1.3v-.8q-.2.5-.6.8-.4.2-1 .2-1 0-1.7-.8-.6-.8-.6-2.3 0-1.4.6-2.2.6-.9 1.7-.9.6 0 1 .3.4.2.6.7v-.8h1zm5.3-1.8q0 .7.2 1 .3.4.7.4h1.2v.8h-1.3q-.9 0-1.3-.6-.5-.5-.5-1.6v-5.4h-1.6v-.8h2.6zm9.1-1.3v.5h-4.3q0 1.2.5 1.8.5.5 1.5.5l1-.1 1-.5v1l-1 .3-1 .2q-1.5 0-2.2-.9-.8-.8-.8-2.3 0-1.4.7-2.3.8-.9 2.1-.9 1.1 0 1.8.8t.7 1.9zm-1-.3q0-.6-.4-1-.4-.5-1.1-.5-.8 0-1.2.4-.5.5-.5 1.1zm14.8 3.5-.9.3q-.4.2-1 .2-1.5 0-2.3-1.1-.9-1.1-.9-3.1t.9-3q.9-1.2 2.4-1.2l1 .1.8.4v1l-.9-.4q-.4-.2-1-.2-1 0-1.5.8t-.5 2.5q0 1.6.5 2.5.5.8 1.6.8l1-.2.8-.5zm5.1-1.9q0 .7.2 1 .3.4.7.4h1.2v.8h-1.3q-.9 0-1.3-.6-.5-.5-.5-1.6v-5.4h-1.6v-.8h2.6zm6.4-3.1q-.7 0-1.1.6-.4.5-.4 1.7 0 1.1.4 1.7.4.6 1.1.6.8 0 1.2-.6.3-.6.3-1.7 0-1.2-.3-1.7-.4-.6-1.2-.6zm0-.9q1.3 0 2 .9.6.8.6 2.3 0 1.5-.7 2.3-.6.9-1.9.9-1.2 0-1.9-.9-.7-.8-.7-2.3 0-1.5.7-2.3.7-.9 2-.9zm4.7 3.9V342h1v3.7q0 .8.3 1.2.3.4 1 .4.6 0 1-.5t.4-1.4V342h1v6h-1v-.9q-.3.5-.8.8-.4.3-1 .3-1 0-1.4-.6-.5-.7-.5-1.9zm10.6-2.9v-3.2h1v8.4h-1v-.8q-.2.5-.6.7-.5.3-1 .3-1 0-1.7-.9-.6-.8-.6-2.3 0-1.5.6-2.3.6-.9 1.7-.9.5 0 1 .3.4.2.6.7zm-2.9 2.2q0 1.1.4 1.7.3.6 1 .6.8 0 1.1-.6.4-.6.4-1.7t-.4-1.7q-.3-.6-1-.6-.8 0-1.1.6-.4.5-.4 1.7z"/>
|
||||
<path d="M800.9 351.3v-14h.8v14z"/>
|
||||
<path d="M69.9 332.3v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm10 6v-6h-3v-.9h3.8v7z"/>
|
||||
<path d="M268.9 338.3v-14h.8v14zm35 0v-14h.8v14zm231 0v-14h.8v14zm35 0v-14h.8v14zm231 0v-14h.8v14z"/>
|
||||
<path d="M156.9 286.3v-14h.8v14zm1.1 2.7-2.6 4.2h2.5zm-.2-1h1.2v5.2h1.1v.9h-1v1.9h-1.2v-2h-3.4v-1zm-.9 24.3v-14h.8v14zm.4 10.1L154 316h6.6z"/>
|
||||
<path d="M268.9 325.3v-14h.8v14z"/>
|
||||
<path d="M289.9 325.3v-14h.8v14z"/>
|
||||
<path d="M303.9 325.3v-14h.8v14z"/>
|
||||
<path d="M331.9 319.3v-8h.8v7.1h3v1zm4 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h3v-7.1h.8v8z"/>
|
||||
<path d="M534.9 325.3v-14h.8v14zm35 0v-14h.8v14z"/>
|
||||
<path d="M597.9 319.3v-8h.8v7.1h3v1zm4 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h3v-7.1h.8v8z"/>
|
||||
<path d="M800.9 325.3v-14h.8v14zm-532-13v-14h.8v14zm35 0v-14h.8v14z"/>
|
||||
<path d="M331.9 312.3v-14h.8v14zm175 0v-14h.8v14z"/>
|
||||
<path d="M534.9 312.3v-14h.8v14zm35 0v-14h.8v14z"/>
|
||||
<path d="M597.9 312.3v-14h.8v14zm175 0v-14h.8v14z"/>
|
||||
<path d="M800.9 312.3v-14h.8v14zm-532-13v-14h.8v14z"/>
|
||||
<path d="M289.9 299.3v-14h.8v14z"/>
|
||||
<path d="M303.9 299.3v-14h.8v14z"/>
|
||||
<path d="M331.9 299.3v-14h.8v14zm175 0v-14h.8v14z"/>
|
||||
<path d="M534.9 299.3v-14h.8v14z"/>
|
||||
<path d="M548.9 293.3v-8h.8v7.1h3v1zm11 0v-.9h6.8v1z"/>
|
||||
<path d="M569.9 299.3v-14h.8v14z"/>
|
||||
<path d="M573.9 293.3v-.9h6.8v1zm14.1 3.1V290l6.6 3.2zm9.9 2.9v-14h.8v14zm76.1-5.5h2v2.2h-2zm7 0h2v2.2h-2zm7 0h2v2.2h-2zm84.9 5.5v-14h.8v14z"/>
|
||||
<path d="M800.9 299.3v-14h.8v14zm-532-13v-14h.8v14zm35 0v-14h.8v14z"/>
|
||||
<path d="M331.9 286.3v-14h.8v14zm175 0v-14h.8v14z"/>
|
||||
<path d="M534.9 286.3v-14h.8v14zm35 0v-14h.8v14z"/>
|
||||
<path d="M597.9 286.3v-14h.8v14zm175 0v-14h.8v14z"/>
|
||||
<path d="M800.9 286.3v-14h.8v14z"/>
|
||||
<path d="M69.9 267.3v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h3v-7.1h.8v8z"/>
|
||||
<path d="M268.9 273.3v-14h.8v14z"/>
|
||||
<path d="M289.9 273.3v-14h.8v14z"/>
|
||||
<path d="M303.9 273.3v-14h.8v14z"/>
|
||||
<path d="M331.9 273.3v-14h.8v14zm51-7.3q0-1.8-.4-2.5-.4-.8-1.2-.8-.8 0-1.2.8-.3.7-.3 2.5t.3 2.5q.4.8 1.2.8.8 0 1.2-.8.4-.7.4-2.5zm1 0q0 2-.6 3.1-.6 1-2 1t-2-1q-.7-1-.7-3.1t.7-3.1q.7-1 2-1 1.4 0 2 1 .7 1 .7 3.1zm2.1-4h4.5v.9H389v6.2h1.6v.9h-4.4v-1h1.7v-6H386zm8.3 7.1q1.4 0 2-.7.5-.6.5-2.4 0-1.8-.5-2.5-.6-.6-2-.6h-.5v6.2zm0-7.1q1.9 0 2.8 1 .8 1 .8 3t-.8 3q-.9 1-2.7 1h-1.7v-8zm10.5 7.7-.9.3q-.4.2-1 .2-1.5 0-2.3-1.1-.9-1.1-.9-3.1t.9-3q.9-1.2 2.4-1.2l1 .1.8.4v1l-.9-.4q-.4-.2-1-.2-1 0-1.5.8t-.5 2.5q0 1.6.5 2.5.5.8 1.6.8l1-.2.8-.5zm13.6-7.4v1l-1-.4-1-.2q-.7 0-1.2.4-.4.3-.4 1 0 .5.3.7.3.3 1 .5l.6.1q1.2.3 1.7.9.5.5.5 1.5 0 1.2-.7 1.8-.7.6-2 .6l-1.2-.2q-.6 0-1.2-.3v-1.2l1.2.6 1.1.2q.8 0 1.3-.4t.5-1q0-.6-.4-1-.3-.3-1-.4l-.6-.2q-1.1-.2-1.6-.7-.6-.5-.6-1.4 0-1 .8-1.7.7-.7 1.9-.7l1 .1 1 .4zm7.6 4.2v.5h-4.3q0 1.2.5 1.8.5.5 1.5.5l1-.1 1-.5v1l-1 .3-1 .2q-1.5 0-2.2-.9-.8-.8-.8-2.3 0-1.4.7-2.3.8-.9 2.1-.9 1.1 0 1.8.8t.7 1.9zm-1-.3q0-.6-.4-1-.4-.5-1.1-.5-.8 0-1.2.4-.5.5-.5 1.1zm8.2-1-.6-.3-.8-.1q-.9 0-1.4.5-.5.6-.5 1.7v3h-1v-6h1v1.2q.3-.7.8-1 .5-.4 1.2-.4l.7.1.6.3zm1.3-1.2h1l1.8 5 1.8-5h1l-2.2 6h-1.2zm8.2 0h2.5v5.2h2v.8h-5v-.8h2v-4.4h-1.5zm1.5-2.3h1v1.2h-1zm9.5 8-.8.3q-.4.2-.9.2-1.4 0-2.2-.9-.8-.8-.8-2.3 0-1.5.8-2.3.8-.9 2.2-.9.5 0 .9.2.4 0 .8.3v1l-.8-.5-.9-.1q-1 0-1.4.6-.5.6-.5 1.7t.5 1.7q.5.6 1.4.6l1-.1.7-.5zm7.3-3.2v.5h-4.3q0 1.2.5 1.8.5.5 1.5.5l1-.1 1-.5v1l-1 .3-1 .2q-1.5 0-2.2-.9-.8-.8-.8-2.3 0-1.4.7-2.3.8-.9 2.1-.9 1.1 0 1.8.8t.7 1.9zm-1-.3q0-.6-.4-1-.4-.5-1.1-.5-.8 0-1.2.4-.5.5-.5 1.1zm46.9 7.1v-14h.8v14z"/>
|
||||
<path d="M534.9 273.3v-14h.8v14z"/>
|
||||
<path d="M548.9 273.3v-14h.8v14z"/>
|
||||
<path d="M569.9 273.3v-14h.8v14z"/>
|
||||
<path d="M597.9 273.3v-6.9h3.8v1h-3v6zm4-6v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm10 6v-6h-3v-.9h3.8v7z"/>
|
||||
<path d="M800.9 273.3v-14h.8v14z"/>
|
||||
<path d="M85 236h4.5v.9H88v6.2h1.6v.9h-4.4v-1h1.7v-6H85zm155.9 24.3v-14h.8v14z"/>
|
||||
<path d="M268.9 260.3v-14h.8v14zm35 0v-14h.8v14z"/>
|
||||
<path d="M331.9 260.3v-14h.8v14zm175 0v-14h.8v14z"/>
|
||||
<path d="M534.9 260.3v-14h.8v14zm35 0v-14h.8v14zm231 0v-14h.8v14z"/>
|
||||
<path d="m94.3 237-1.1 4h2.3zm-.6-1H95l2.4 8h-1.1l-.6-2H93l-.6 2h-1.1zm4.5 0h1.4l1.4 4 1.4-4h1.5v8h-1v-7l-1.5 4.1h-.8l-1.4-4.2v7.1h-1zm19.6 7.7-.9.3q-.4.2-1 .2-1.5 0-2.3-1.1-.9-1.1-.9-3.1t.9-3q.9-1.2 2.4-1.2l1 .1.8.4v1l-.9-.4q-.4-.2-1-.2-1 0-1.5.8t-.5 2.5q0 1.6.5 2.5.5.8 1.6.8l1-.2.8-.5zm7.4-4.5-.6-.3-.8-.1q-.9 0-1.4.5-.5.6-.5 1.7v3h-1v-6h1v1.2q.3-.7.8-1 .5-.4 1.2-.4l.7.1.6.3zm6.8 1.3v.5h-4.3q0 1.2.5 1.8.5.5 1.5.5l1-.1 1-.5v1l-1 .3-1 .2q-1.5 0-2.2-.9-.8-.8-.8-2.3 0-1.4.7-2.3.8-.9 2.1-.9 1.1 0 1.8.8t.7 1.9zm-1-.3q0-.6-.4-1-.4-.5-1.1-.5-.8 0-1.2.4-.5.5-.5 1.1zm6.6-1.4v-3.2h1v8.4h-1v-.8q-.2.5-.6.7-.5.3-1 .3-1 0-1.7-.9-.6-.8-.6-2.3 0-1.5.6-2.3.6-.9 1.7-.9.5 0 1 .3.4.2.6.7zm-2.9 2.2q0 1.1.4 1.7.3.6 1 .6.8 0 1.1-.6.4-.6.4-1.7t-.4-1.7q-.3-.6-1-.6-.8 0-1.1.6-.4.5-.4 1.7zm11.3-.5v.5h-4.3q0 1.2.5 1.8.5.5 1.5.5l1-.1 1-.5v1l-1 .3-1 .2q-1.5 0-2.2-.9-.8-.8-.8-2.3 0-1.4.7-2.3.8-.9 2.1-.9 1.1 0 1.8.8t.7 1.9zm-1-.3q0-.6-.4-1-.4-.5-1.1-.5-.8 0-1.2.4-.5.5-.5 1.1zm7.6.1v3.7h-1v-3.7q0-.8-.2-1.2-.3-.4-1-.4-.6 0-1 .5t-.4 1.4v3.4h-1v-6h1v.9q.3-.5.7-.8.5-.3 1.1-.3 1 0 1.4.6.4.6.4 1.9zm4.7-4v1.7h2.2v.8h-2.2v3.2q0 .7.3 1 .2.2.8.2h1.1v.8h-1.2q-1 0-1.5-.4-.5-.5-.5-1.6v-3.2h-1.6v-.8h1.6v-1.7zm5.4 1.7h2.5v5.2h2v.8h-5v-.8h2v-4.4h-1.5zm1.5-2.3h1v1.2h-1zm7.6 5.3h-.4q-.8 0-1.3.3-.4.3-.4.9 0 .5.3.8.4.3 1 .3.8 0 1.2-.5.5-.6.5-1.6v-.2zm1.9-.4v3.4h-1v-.9q-.3.5-.8.8-.5.3-1.2.3-.9 0-1.4-.6-.6-.5-.6-1.4 0-1 .7-1.5t2-.5h1.3v-.2q0-.7-.4-1-.3-.3-1.1-.3l-1 .1-1 .4v-1l1-.3h1q.7 0 1.3.2.5.2.8.6l.3.7v1.2zm5.2 1.2q0 .7.2 1 .3.4.7.4h1.2v.8h-1.3q-.9 0-1.3-.6-.5-.5-.5-1.6v-5.4h-1.6v-.8h2.6zm8.3-3.6v1l-.8-.4-.9-.1q-.7 0-1 .2-.3.2-.3.6 0 .5.2.6l1.2.4.4.1q.8.2 1.2.6.3.4.3 1 0 1-.6 1.5t-1.8.5l-.9-.1-1-.3v-1l1 .4 1 .1q.6 0 1-.2.3-.3.3-.8 0-.7-1.3-1h-.4q-.9-.2-1.2-.6-.4-.4-.4-1 0-1 .6-1.4.5-.5 1.6-.5l1 .1.8.3zm12.1-1.2-1.1 4h2.3zm-.6-1h1.3l2.4 8h-1.1l-.6-2H198l-.6 2h-1.1zm6.4.9v3h1.3q.8 0 1.2-.4.4-.4.4-1.1 0-.7-.4-1.1-.4-.4-1.2-.4zm-1-1h2.3q1.3 0 2 .7.7.6.7 1.8 0 1.2-.7 1.8-.7.6-2 .6h-1.3v3.2h-1zm6.9.1h4.5v.9H214v6.2h1.6v.9h-4.4v-1h1.7v-6H211zm29.9 11.3v-14h.8v14z"/>
|
||||
<path d="M268.9 247.3v-14h.8v14z"/>
|
||||
<path d="M289.9 247.3v-14h.8v14z"/>
|
||||
<path d="M303.9 247.3v-14h.8v14z"/>
|
||||
<path d="M331.9 247.3v-14h.8v14zm175 0v-14h.8v14z"/>
|
||||
<path d="M534.9 247.3v-14h.8v14z"/>
|
||||
<path d="M548.9 247.3v-14h.8v14z"/>
|
||||
<path d="M569.9 247.3v-14h.8v14z"/>
|
||||
<path d="M597.9 241.3v-8h.8v7.1h3v1zm4 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h3v-7.1h.8v8z"/>
|
||||
<path d="M800.9 247.3v-14h.8v14z"/>
|
||||
<path d="M240.9 234.3v-14h.8v14z"/>
|
||||
<path d="M268.9 234.3v-14h.8v14zm35 0v-14h.8v14z"/>
|
||||
<path d="M331.9 234.3v-14h.8v14zm175 0v-14h.8v14z"/>
|
||||
<path d="M534.9 234.3v-14h.8v14zm35 0v-14h.8v14z"/>
|
||||
<path d="M597.9 234.3v-14h.8v14zm175 0v-14h.8v14z"/>
|
||||
<path d="M800.9 234.3v-14h.8v14z"/>
|
||||
<path d="M69.9 215.3v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm10 6v-6h-3v-.9h3.8v7z"/>
|
||||
<path d="M268.9 221.3v-14h.8v14z"/>
|
||||
<path d="M289.9 221.3v-14h.8v14z"/>
|
||||
<path d="M303.9 221.3v-14h.8v14z"/>
|
||||
<path d="M331.9 221.3v-6.9h3.8v1h-3v6zm4-6v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm10 6v-6h-3v-.9h3.8v7z"/>
|
||||
<path d="M534.9 221.3v-14h.8v14z"/>
|
||||
<path d="M548.9 221.3v-14h.8v7.1h3v1h-3v6zm11-6v-.9h6.8v1z"/>
|
||||
<path d="M569.9 221.3v-14h.8v14z"/>
|
||||
<path d="M573.9 215.3v-.9h6.8v1zm14.1 3.1V212l6.6 3.2zm9.9 2.9v-14h.8v14zm23.9-3.6-.9.3q-.4.2-1 .2-1.5 0-2.3-1.1-.9-1.1-.9-3.1t.9-3q.9-1.2 2.4-1.2l1 .1.8.4v1l-.9-.4q-.4-.2-1-.2-1 0-1.5.8t-.5 2.5q0 1.6.5 2.5.5.8 1.6.8l1-.2.8-.5zm5.1-1.9q0 .7.2 1 .3.4.7.4h1.2v.8h-1.3q-.9 0-1.3-.6-.5-.5-.5-1.6v-5.4h-1.6v-.8h2.6zm6.4-3.1q-.7 0-1.1.6-.4.5-.4 1.7 0 1.1.4 1.7.4.6 1.1.6.8 0 1.2-.6.3-.6.3-1.7 0-1.2-.3-1.7-.4-.6-1.2-.6zm0-.9q1.3 0 2 .9.6.8.6 2.3 0 1.5-.7 2.3-.6.9-1.9.9-1.2 0-1.9-.9-.7-.8-.7-2.3 0-1.5.7-2.3.7-.9 2-.9zm4.7 3.9V212h1v3.7q0 .8.3 1.2.3.4 1 .4.6 0 1-.5t.4-1.4V212h1v6h-1v-.9q-.3.5-.8.8-.4.3-1 .3-1 0-1.4-.6-.5-.7-.5-1.9zm10.6-2.9v-3.2h1v8.4h-1v-.8q-.2.5-.6.7-.5.3-1 .3-1 0-1.7-.9-.6-.8-.6-2.3 0-1.5.6-2.3.6-.9 1.7-.9.5 0 1 .3.4.2.6.7zm-2.9 2.2q0 1.1.4 1.7.3.6 1 .6.8 0 1.1-.6.4-.6.4-1.7t-.4-1.7q-.3-.6-1-.6-.8 0-1.1.6-.4.5-.4 1.7zm17.7-4.7v1l-1-.4-1-.2q-.7 0-1.2.4-.4.3-.4 1 0 .5.3.7.3.3 1 .5l.6.1q1.2.3 1.7.9.5.5.5 1.5 0 1.2-.7 1.8-.7.6-2 .6l-1.2-.2q-.6 0-1.2-.3v-1.2l1.2.6 1.1.2q.8 0 1.3-.4t.5-1q0-.6-.4-1-.3-.3-1-.4l-.6-.2q-1.1-.2-1.6-.7-.6-.5-.6-1.4 0-1 .8-1.7.7-.7 1.9-.7l1 .1 1 .4zm4.9 0v1.7h2.2v.8h-2.2v3.2q0 .7.3 1 .2.2.8.2h1.1v.8h-1.2q-1 0-1.5-.4-.5-.5-.5-1.6v-3.2h-1.6v-.8h1.6v-1.7zm7 2.4q-.7 0-1.1.6-.4.5-.4 1.7 0 1.1.4 1.7.4.6 1.1.6.8 0 1.2-.6.3-.6.3-1.7 0-1.2-.3-1.7-.4-.6-1.2-.6zm0-.9q1.3 0 2 .9.6.8.6 2.3 0 1.5-.7 2.3-.6.9-1.9.9-1.2 0-1.9-.9-.7-.8-.7-2.3 0-1.5.7-2.3.7-.9 2-.9zm9.9 1.4-.6-.3-.8-.1q-.9 0-1.4.5-.5.6-.5 1.7v3h-1v-6h1v1.2q.3-.7.8-1 .5-.4 1.2-.4l.7.1.6.3zm4.6 1.8h-.4q-.8 0-1.3.3-.4.3-.4.9 0 .5.3.8.4.3 1 .3.8 0 1.2-.5.5-.6.5-1.6v-.2zm1.9-.4v3.4h-1v-.9q-.3.5-.8.8-.5.3-1.2.3-.9 0-1.4-.6-.6-.5-.6-1.4 0-1 .7-1.5t2-.5h1.3v-.2q0-.7-.4-1-.3-.3-1.1-.3l-1 .1-1 .4v-1l1-.3h1q.7 0 1.3.2.5.2.8.6l.3.7v1.2zm5.9.4q0-1.2-.4-1.7-.3-.6-1-.6t-1.1.6q-.4.5-.4 1.6 0 1.2.4 1.7.4.6 1.1.6.7 0 1-.6.4-.5.4-1.7zm1 2.6q0 1.4-.6 2-.7.8-2 .8l-.8-.1-.9-.2v-1l1 .4h.8q.8 0 1.1-.4.4-.4.4-1.3v-.8q-.2.5-.6.8-.4.2-1 .2-1 0-1.7-.8-.6-.8-.6-2.3 0-1.4.6-2.2.6-.9 1.7-.9.6 0 1 .3.4.2.6.7v-.8h1zm7.4-3.1v.5h-4.3q0 1.2.5 1.8.5.5 1.5.5l1-.1 1-.5v1l-1 .3-1 .2q-1.5 0-2.2-.9-.8-.8-.8-2.3 0-1.4.7-2.3.8-.9 2.1-.9 1.1 0 1.8.8t.7 1.9zm-1-.3q0-.6-.4-1-.4-.5-1.1-.5-.8 0-1.2.4-.5.5-.5 1.1zm11-.2v3.1h1.3q1 0 1.3-.3.4-.4.4-1.1 0-1-.4-1.4-.4-.3-1.3-.3zm0-3.1v2.2h1.2q.8 0 1.2-.3.3-.3.3-.8 0-.6-.3-.9-.4-.2-1.2-.2zm-1.1-1h2.4q1.2 0 1.9.6.6.5.6 1.5 0 .5-.3 1-.4.4-1 .5.7.1 1.2.7.4.5.4 1.6 0 1-.7 1.6-.7.6-2.1.6h-2.4zm7.1 5.8V212h1v3.7q0 .8.3 1.2.3.4 1 .4.6 0 1-.5t.4-1.4V212h1v6h-1v-.9q-.3.5-.8.8-.4.3-1 .3-1 0-1.4-.6-.5-.7-.5-1.9zm11.7 2-.8.3q-.4.2-.9.2-1.4 0-2.2-.9-.8-.8-.8-2.3 0-1.5.8-2.3.8-.9 2.2-.9.5 0 .9.2.4 0 .8.3v1l-.8-.5-.9-.1q-1 0-1.4.6-.5.6-.5 1.7t.5 1.7q.5.6 1.4.6l1-.1.7-.5zm2.6-8.1h1v4.9l2.6-2.5h1.2l-2.4 2.3 2.8 3.7h-1.3l-2.2-3.1-.7.7v2.4h-1zm11.7 4.9v.5h-4.3q0 1.2.5 1.8.5.5 1.5.5l1-.1 1-.5v1l-1 .3-1 .2q-1.5 0-2.2-.9-.8-.8-.8-2.3 0-1.4.7-2.3.8-.9 2.1-.9 1.1 0 1.8.8t.7 1.9zm-1-.3q0-.6-.4-1-.4-.5-1.1-.5-.8 0-1.2.4-.5.5-.5 1.1zm5.3-3.9v1.7h2.2v.8h-2.2v3.2q0 .7.3 1 .2.2.8.2h1.1v.8h-1.2q-1 0-1.5-.4-.5-.5-.5-1.6v-3.2h-1.6v-.8h1.6v-1.7zm20.6 11v-14h.8v14z"/>
|
||||
<path d="M800.9 221.3v-14h.8v14z"/>
|
||||
<path d="M156.9 195.3v-14h.8v14zm.4 10.1L154 199h6.6z"/>
|
||||
<path d="M268.9 208.3v-14h.8v14zm35 0v-14h.8v14z"/>
|
||||
<path d="M423.3 205.4 420 199h6.6z"/>
|
||||
<path d="M534.9 208.3v-14h.8v14zm35 0v-14h.8v14z"/>
|
||||
<path d="M597.9 208.3v-14h.8v14zm175 0v-14h.8v14z"/>
|
||||
<path d="M800.9 208.3v-14h.8v14zm-532-13v-14h.8v14z"/>
|
||||
<path d="M289.9 195.3v-14h.8v14z"/>
|
||||
<path d="M303.9 195.3v-14h.8v14z"/>
|
||||
<path d="M422.9 195.3v-14h.8v14z"/>
|
||||
<path d="M534.9 195.3v-14h.8v14z"/>
|
||||
<path d="M548.9 195.3v-14h.8v14z"/>
|
||||
<path d="M569.9 195.3v-14h.8v14z"/>
|
||||
<path d="M597.9 195.3v-6.9h3.8v1h-3v6zm4-6v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm10 6v-6h-3v-.9h3.8v7z"/>
|
||||
<path d="M800.9 195.3v-14h.8v14z"/>
|
||||
<path d="M13 175.5v.5H8.7q0 1.2.5 1.8.5.5 1.5.5l1-.1 1-.5v1l-1 .3-1 .2q-1.5 0-2.2-.9-.8-.8-.8-2.3 0-1.4.7-2.3.8-.9 2.1-.9 1.1 0 1.8.8t.7 1.9zm-1-.3q0-.6-.4-1-.4-.5-1.1-.5-.8 0-1.2.4-.5.5-.5 1.1zm6.6-1.4v-3.2h1v8.4h-1v-.8q-.2.5-.6.7-.5.3-1 .3-1 0-1.7-.9-.6-.8-.6-2.3 0-1.5.6-2.3.6-.9 1.7-.9.5 0 1 .3.4.2.6.7zm-2.9 2.2q0 1.1.4 1.7.3.6 1 .6.8 0 1.1-.6.4-.6.4-1.7t-.4-1.7q-.3-.6-1-.6-.8 0-1.1.6-.4.5-.4 1.7zm11.3-.5v.5h-4.3q0 1.2.5 1.8.5.5 1.5.5l1-.1 1-.5v1l-1 .3-1 .2q-1.5 0-2.2-.9-.8-.8-.8-2.3 0-1.4.7-2.3.8-.9 2.1-.9 1.1 0 1.8.8t.7 1.9zm-1-.3q0-.6-.4-1-.4-.5-1.1-.5-.8 0-1.2.4-.5.5-.5 1.1zm8.2-1-.6-.3-.8-.1q-.9 0-1.4.5-.5.6-.5 1.7v3h-1v-6h1v1.2q.3-.7.8-1 .5-.4 1.2-.4l.7.1.6.3zm4.6 1.8h-.4q-.8 0-1.3.3-.4.3-.4.9 0 .5.3.8.4.3 1 .3.8 0 1.2-.5.5-.6.5-1.6v-.2zm1.9-.4v3.4h-1v-.9q-.3.5-.8.8-.5.3-1.2.3-.9 0-1.4-.6-.6-.5-.6-1.4 0-1 .7-1.5t2-.5h1.3v-.2q0-.7-.4-1-.3-.3-1.1-.3l-1 .1-1 .4v-1l1-.3h1q.7 0 1.3.2.5.2.8.6l.3.7v1.2zm4.6-4.3v1.7h2.2v.8h-2.2v3.2q0 .7.3 1 .2.2.8.2h1.1v.8h-1.2q-1 0-1.5-.4-.5-.5-.5-1.6v-3.2h-1.6v-.8h1.6v-1.7zm9.7 4.2v.5h-4.3q0 1.2.5 1.8.5.5 1.5.5l1-.1 1-.5v1l-1 .3-1 .2q-1.5 0-2.2-.9-.8-.8-.8-2.3 0-1.4.7-2.3.8-.9 2.1-.9 1.1 0 1.8.8t.7 1.9zm-1-.3q0-.6-.4-1-.4-.5-1.1-.5-.8 0-1.2.4-.5.5-.5 1.1zm6.6-1.4v-3.2h1v8.4h-1v-.8q-.2.5-.6.7-.5.3-1 .3-1 0-1.7-.9-.6-.8-.6-2.3 0-1.5.6-2.3.6-.9 1.7-.9.5 0 1 .3.4.2.6.7zm-2.9 2.2q0 1.1.4 1.7.3.6 1 .6.8 0 1.1-.6.4-.6.4-1.7t-.4-1.7q-.3-.6-1-.6-.8 0-1.1.6-.4.5-.4 1.7zm12.6-5h6v.9H74v7.1h-1.1v-7.1h-2.5zm10 2.7q-.7 0-1.1.6-.4.5-.4 1.7 0 1.1.4 1.7.4.6 1.1.6.8 0 1.2-.6.3-.6.3-1.7 0-1.2-.3-1.7-.4-.6-1.2-.6zm0-.9q1.3 0 2 .9.6.8.6 2.3 0 1.5-.7 2.3-.6.9-1.9.9-1.2 0-1.9-.9-.7-.8-.7-2.3 0-1.5.7-2.3.7-.9 2-.9zm5-2.2h1v4.9l2.6-2.5H90l-2.4 2.3 2.8 3.7h-1.3l-2.2-3.1-.7.7v2.4h-1zm11.7 4.9v.5h-4.3q0 1.2.5 1.8.5.5 1.5.5l1-.1 1-.5v1l-1 .3-1 .2q-1.5 0-2.2-.9-.8-.8-.8-2.3 0-1.4.7-2.3.8-.9 2.1-.9 1.1 0 1.8.8t.7 1.9zm-1-.3q0-.6-.4-1-.4-.5-1.1-.5-.8 0-1.2.4-.5.5-.5 1.1zm7.6.1v3.7h-1v-3.7q0-.8-.2-1.2-.3-.4-1-.4-.6 0-1 .5t-.4 1.4v3.4h-1v-6h1v.9q.3-.5.8-.8.4-.3 1-.3 1 0 1.4.6.4.6.4 1.9zm8.4.8 6.6-3.2v6.5zm6.9.2v-.9h6.8v1zm14 0v-.9h6.8v1zm25.3-1.6q.8.2 1.2.8.4.5.4 1.3 0 1-.7 1.7-.8.7-2.1.7l-1.1-.1-1.2-.3v-1.1l1.1.4 1 .1q1 0 1.4-.4.5-.4.5-1.1 0-.7-.5-1.1-.4-.4-1.3-.4h-.8v-1h.8q.8 0 1.2-.3.4-.3.4-.9 0-.6-.4-1-.3-.3-1-.3l-1 .2q-.6 0-1.1.3v-1l1.1-.3h1q1.1 0 1.8.5.7.6.7 1.6 0 .6-.4 1-.3.5-1 .7z"/>
|
||||
<path d="M268.9 182.3v-14h.8v14zm35 0v-14h.8v14z"/>
|
||||
<path d="M421.5 178h1.6v-6l-2 1v-1l2-1h1.1v7h1.7v1h-4.4z"/>
|
||||
<path d="M534.9 182.3v-14h.8v14zm35 0v-14h.8v14zm231 0v-14h.8v14zm-763-169V6.4h3.8v1h-3v6z"/>
|
||||
<path d="M37.9 26.3v-14h.8v14z"/>
|
||||
<path d="M37.9 39.3v-14h.8v14z"/>
|
||||
<path d="M37.9 52.3v-14h.8v14z"/>
|
||||
<path d="M37.9 65.3v-14h.8v14z"/>
|
||||
<path d="M37.9 78.3v-14h.8v14z"/>
|
||||
<path d="M37.9 91.3v-14h.8v14z"/>
|
||||
<path d="M37.9 104.3v-14h.8v14z"/>
|
||||
<path d="M37.9 117.3v-14h.8v14z"/>
|
||||
<path d="M37.9 130.3v-14h.8v14z"/>
|
||||
<path d="M37.9 143.3v-14h.8v14z"/>
|
||||
<path d="M37.9 156.3v-14h.8v14z"/>
|
||||
<path d="M37.9 169.3v-14h.8v14z"/>
|
||||
<path d="M65.9 39.3v-6.9h3.8v1h-3v6z"/>
|
||||
<path d="M65.9 52.3v-14h.8v14z"/>
|
||||
<path d="M65.9 65.3v-14h.8v14z"/>
|
||||
<path d="M65.9 78.3v-14h.8v14z"/>
|
||||
<path d="M65.9 91.3v-14h.8v14z"/>
|
||||
<path d="M65.9 104.3v-14h.8v14z"/>
|
||||
<path d="M65.9 117.3v-14h.8v14z"/>
|
||||
<path d="M65.9 130.3v-14h.8v14z"/>
|
||||
<path d="M65.9 137.3v-8h.8v7.1h3v1zm88.1 16.1 3.3-6.5 3.3 6.5zm2.9 15.9v-14h.8v14z"/>
|
||||
<path d="M268.9 169.3v-14h.8v14z"/>
|
||||
<path d="M289.9 169.3v-14h.8v14z"/>
|
||||
<path d="M303.9 169.3v-14h.8v14z"/>
|
||||
<path d="M422.9 169.3v-14h.8v14z"/>
|
||||
<path d="M534.9 169.3v-14h.8v14z"/>
|
||||
<path d="M548.9 169.3v-14h.8v14z"/>
|
||||
<path d="M569.9 169.3v-14h.8v14z"/>
|
||||
<path d="M597.9 163.3v-8h.8v7.1h3v1zm4 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h3v-7.1h.8v8z"/>
|
||||
<path d="M800.9 169.3v-14h.8v14zm-532-13v-14h.8v14zm35 0v-14h.8v14z"/>
|
||||
<path d="m420 153.4 3.3-6.5 3.3 6.5z"/>
|
||||
<path d="M534.9 156.3v-14h.8v14zm35 0v-14h.8v14z"/>
|
||||
<path d="M597.9 156.3v-14h.8v14zm175 0v-14h.8v14z"/>
|
||||
<path d="M800.9 156.3v-14h.8v14z"/>
|
||||
<path d="M69.9 137.3v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h3v-7.1h.8v8z"/>
|
||||
<path d="M268.9 143.3v-14h.8v14z"/>
|
||||
<path d="M289.9 143.3v-14h.8v14z"/>
|
||||
<path d="M303.9 143.3v-14h.8v14z"/>
|
||||
<path d="M331.9 137.3v-8h.8v7.1h3v1zm4 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h3v-7.1h.8v8z"/>
|
||||
<path d="M534.9 143.3v-14h.8v14z"/>
|
||||
<path d="M548.9 143.3v-14h.8v7.1h3v1h-3v6zm11-6v-.9h6.8v1z"/>
|
||||
<path d="M569.9 143.3v-14h.8v14z"/>
|
||||
<path d="M573.9 137.3v-.9h6.8v1zm14.1 3.1V134l6.6 3.2zm9.9 2.9v-14h.8v14zm30.9-3.6-.9.3q-.4.2-1 .2-1.5 0-2.3-1.1-.9-1.1-.9-3.1t.9-3q.9-1.2 2.4-1.2l1 .1.8.4v1l-.9-.4q-.4-.2-1-.2-1 0-1.5.8t-.5 2.5q0 1.6.5 2.5.5.8 1.6.8l1-.2.8-.5zm5.1-1.9q0 .7.2 1 .3.4.7.4h1.2v.8h-1.3q-.9 0-1.3-.6-.5-.5-.5-1.6v-5.4h-1.6v-.8h2.6zm6.4-3.1q-.7 0-1.1.6-.4.5-.4 1.7 0 1.1.4 1.7.4.6 1.1.6.8 0 1.2-.6.3-.6.3-1.7 0-1.2-.3-1.7-.4-.6-1.2-.6zm0-.9q1.3 0 2 .9.6.8.6 2.3 0 1.5-.7 2.3-.6.9-1.9.9-1.2 0-1.9-.9-.7-.8-.7-2.3 0-1.5.7-2.3.7-.9 2-.9zm4.7 3.9V134h1v3.7q0 .8.3 1.2.3.4 1 .4.6 0 1-.5t.4-1.4V134h1v6h-1v-.9q-.3.5-.8.8-.4.3-1 .3-1 0-1.4-.6-.5-.7-.5-1.9zm10.6-2.9v-3.2h1v8.4h-1v-.8q-.2.5-.6.7-.5.3-1 .3-1 0-1.7-.9-.6-.8-.6-2.3 0-1.5.6-2.3.6-.9 1.7-.9.5 0 1 .3.4.2.6.7zm-2.9 2.2q0 1.1.4 1.7.3.6 1 .6.8 0 1.1-.6.4-.6.4-1.7t-.4-1.7q-.3-.6-1-.6-.8 0-1.1.6-.4.5-.4 1.7zm16.3-.8q.5.1.8.4.3.3.7 1.2l1.1 2.2h-1.1l-1-2q-.4-.9-.7-1.1-.4-.3-.9-.3h-1v3.4h-1.1v-8h2.2q1.3 0 2 .6t.7 1.7q0 .8-.4 1.3t-1.2.6zm-2-3.2v2.8h1.1q.8 0 1.2-.3.4-.4.4-1.1 0-.7-.4-1-.4-.4-1.2-.4zm6 4.7V134h1v3.7q0 .8.3 1.2.3.4 1 .4.6 0 1-.5t.4-1.4V134h1v6h-1v-.9q-.3.5-.8.8-.4.3-1 .3-1 0-1.4-.6-.5-.7-.5-1.9zm11.6-1.4v3.7h-1v-3.7q0-.8-.2-1.2-.3-.4-1-.4-.6 0-1 .5t-.4 1.4v3.4h-1v-6h1v.9q.3-.5.8-.8.4-.3 1-.3 1 0 1.4.6.4.6.4 1.9zm13.8-4v1l-1-.4-1-.2q-.7 0-1.2.4-.4.3-.4 1 0 .5.3.7.3.3 1 .5l.6.1q1.2.3 1.7.9.5.5.5 1.5 0 1.2-.7 1.8-.7.6-2 .6l-1.2-.2q-.6 0-1.2-.3v-1.2l1.2.6 1.1.2q.8 0 1.3-.4t.5-1q0-.6-.4-1-.3-.3-1-.4l-.6-.2q-1.1-.2-1.6-.7-.6-.5-.6-1.4 0-1 .8-1.7.7-.7 1.9-.7l1 .1 1 .4zm7.6 4.2v.5h-4.3q0 1.2.5 1.8.5.5 1.5.5l1-.1 1-.5v1l-1 .3-1 .2q-1.5 0-2.2-.9-.8-.8-.8-2.3 0-1.4.7-2.3.8-.9 2.1-.9 1.1 0 1.8.8t.7 1.9zm-1-.3q0-.6-.4-1-.4-.5-1.1-.5-.8 0-1.2.4-.5.5-.5 1.1zm8.2-1-.6-.3-.8-.1q-.9 0-1.4.5-.5.6-.5 1.7v3h-1v-6h1v1.2q.3-.7.8-1 .5-.4 1.2-.4l.7.1.6.3zm1.3-1.2h1l1.8 5 1.8-5h1l-2.2 6h-1.2zm8.2 0h2.5v5.2h2v.8h-5v-.8h2v-4.4h-1.5zm1.5-2.3h1v1.2h-1zm9.5 8-.8.3q-.4.2-.9.2-1.4 0-2.2-.9-.8-.8-.8-2.3 0-1.5.8-2.3.8-.9 2.2-.9.5 0 .9.2.4 0 .8.3v1l-.8-.5-.9-.1q-1 0-1.4.6-.5.6-.5 1.7t.5 1.7q.5.6 1.4.6l1-.1.7-.5zm7.3-3.2v.5h-4.3q0 1.2.5 1.8.5.5 1.5.5l1-.1 1-.5v1l-1 .3-1 .2q-1.5 0-2.2-.9-.8-.8-.8-2.3 0-1.4.7-2.3.8-.9 2.1-.9 1.1 0 1.8.8t.7 1.9zm-1-.3q0-.6-.4-1-.4-.5-1.1-.5-.8 0-1.2.4-.5.5-.5 1.1zm32.9 7.1v-14h.8v14z"/>
|
||||
<path d="M800.9 143.3v-14h.8v14z"/>
|
||||
<path d="M82.4 80.3v1l-1-.4-1-.2q-.7 0-1.2.4-.4.3-.4 1 0 .5.3.7.3.3 1 .5l.6.1q1.2.3 1.7.9.5.5.5 1.5 0 1.2-.7 1.8-.7.6-2 .6L79 88q-.6 0-1.2-.3v-1.2l1.2.6 1.1.2q.8 0 1.3-.4t.5-1q0-.6-.4-1-.3-.3-1-.4l-.6-.2q-1.1-.2-1.6-.7-.6-.5-.6-1.4 0-1 .8-1.7.7-.7 1.9-.7l1 .1 1 .4zm158.5 24v-14h.8v14z"/>
|
||||
<path d="M240.9 117.3v-14h.8v14z"/>
|
||||
<path d="M240.9 130.3v-14h.8v14zm4-19v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1z"/>
|
||||
<path d="M268.9 130.3v-14h.8v14zm35 0v-14h.8v14z"/>
|
||||
<path d="M331.9 130.3v-14h.8v14zm175 0v-14h.8v14z"/>
|
||||
<path d="M534.9 130.3v-14h.8v14zm35 0v-14h.8v14z"/>
|
||||
<path d="M597.9 130.3v-14h.8v14zm175 0v-14h.8v14z"/>
|
||||
<path d="M800.9 130.3v-14h.8v14z"/>
|
||||
<path d="M269.7 111.3v6h-.8v-6h-3v-.9h3v-7.1h.8v7.1h3v1zm3.2 0v-.9h6.8v1zm7 0v-.9h6.8v1zm10 6v-6h-3v-.9h6.8v1h-3v6zm4-6v-.9h6.8v1zm8.2-5.3h4v.9h-3v2l.5-.2h.4q1.3 0 2 .7.7.8.7 2 0 1.3-.7 2-.8.8-2.1.8l-1.2-.1-1-.3v-1l1 .3 1 .1q1 0 1.5-.4.5-.5.5-1.4 0-.8-.6-1.3-.5-.5-1.3-.5l-.9.1-.8.3zm5.8 5.3v-.9h6.8v1zm7 0v-.9h6.8v1zm7.1 3.1V108l6.6 3.2zm9.9 2.9v-14h.8v14zm175 0v-14h.8v14z"/>
|
||||
<path d="M534.9 117.3v-14h.8v14z"/>
|
||||
<path d="M548.9 117.3v-14h.8v14z"/>
|
||||
<path d="M569.9 117.3v-14h.8v14z"/>
|
||||
<path d="M597.9 117.3v-6.9h3.8v1h-3v6zm4-6v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm10 6v-6h-3v-.9h3.8v7z"/>
|
||||
<path d="M800.9 117.3v-14h.8v14zm-532-13v-14h.8v14zm35 0v-14h.8v14z"/>
|
||||
<path d="M331.9 104.3v-14h.8v14zm175 0v-14h.8v14z"/>
|
||||
<path d="M534.9 104.3v-14h.8v14zm35 0v-14h.8v14zm231 0v-14h.8v14z"/>
|
||||
<path d="M90 84.5v.5h-4.3q0 1.2.5 1.8.5.5 1.5.5l1-.1 1-.5v1l-1 .3-1 .2q-1.5 0-2.2-.9-.8-.8-.8-2.3 0-1.4.7-2.3.8-.9 2.1-.9 1.1 0 1.8.8t.7 1.9zm-1-.3q0-.6-.4-1-.4-.5-1.1-.5-.8 0-1.2.4-.5.5-.5 1.1zm7.7 3.5-.8.3q-.4.2-.9.2-1.4 0-2.2-.9-.8-.8-.8-2.3 0-1.5.8-2.3.8-.9 2.2-.9.5 0 .9.2.4 0 .8.3v1l-.8-.5-.9-.1q-1 0-1.4.6-.5.6-.5 1.7t.5 1.7q.5.6 1.4.6l1-.1.7-.5zm2.3-2V82h1v3.7q0 .8.3 1.2.3.4 1 .4.6 0 1-.5t.4-1.4V82h1v6h-1v-.9q-.3.5-.8.8-.4.3-1 .3-1 0-1.4-.6-.5-.7-.5-1.9zm12.2-2.5-.6-.3-.8-.1q-.9 0-1.4.5-.5.6-.5 1.7v3h-1v-6h1v1.2q.3-.7.8-1 .5-.4 1.2-.4l.7.1.6.3zm2.5-1.2h2.5v5.2h2v.8h-5v-.8h2v-4.4h-1.5zm1.5-2.3h1v1.2h-1zm7.1.6V82h2.2v.8h-2.2V86q0 .7.3 1 .2.2.8.2h1.1v.8h-1.2q-1 0-1.5-.4-.5-.5-.5-1.6v-3.2h-1.6V82h1.6v-1.7zm8.3 5.7-.6 1.7-.7 1.8q-.3.4-.7.6-.3.2-.8.2h-.8v-.8h.6q.4 0 .7-.3l.6-1.3-2.3-6h1l1.8 4.8L131 82h1zm9.7-6h6v.9H144V88h-1.1v-7.1h-2.5zm10 2.7q-.7 0-1.1.6-.4.5-.4 1.7 0 1.1.4 1.7.4.6 1.1.6.8 0 1.2-.6.3-.6.3-1.7 0-1.2-.3-1.7-.4-.6-1.2-.6zm0-.9q1.3 0 2 .9.6.8.6 2.3 0 1.5-.7 2.3-.6.9-1.9.9-1.2 0-1.9-.9-.7-.8-.7-2.3 0-1.5.7-2.3.7-.9 2-.9zm5-2.2h1v4.9l2.6-2.5h1.2l-2.4 2.3 2.8 3.7h-1.3l-2.2-3.1-.7.7V88h-1zm11.7 4.9v.5h-4.3q0 1.2.5 1.8.5.5 1.5.5l1-.1 1-.5v1l-1 .3-1 .2q-1.5 0-2.2-.9-.8-.8-.8-2.3 0-1.4.7-2.3.8-.9 2.1-.9 1.1 0 1.8.8t.7 1.9zm-1-.3q0-.6-.4-1-.4-.5-1.1-.5-.8 0-1.2.4-.5.5-.5 1.1zm7.6.1V88h-1v-3.7q0-.8-.2-1.2-.3-.4-1-.4-.6 0-1 .5t-.4 1.4V88h-1v-6h1v.9q.3-.5.7-.8.5-.3 1.1-.3 1 0 1.4.6.4.6.4 1.9zm13.8-4v1l-1-.4-1-.2q-.7 0-1.2.4-.4.3-.4 1 0 .5.3.7.3.3 1 .5l.6.1q1.2.3 1.7.9.5.5.5 1.5 0 1.2-.7 1.8-.7.6-2 .6L184 88q-.6 0-1.2-.3v-1.2l1.2.6 1.1.2q.8 0 1.3-.4t.5-1q0-.6-.4-1-.3-.3-1-.4l-.6-.2q-1.1-.2-1.6-.7-.6-.5-.6-1.4 0-1 .8-1.7.7-.7 1.9-.7l1 .1 1 .4zm7.6 4.2v.5h-4.3q0 1.2.5 1.8.5.5 1.5.5l1-.1 1-.5v1l-1 .3-1 .2q-1.5 0-2.2-.9-.8-.8-.8-2.3 0-1.4.7-2.3.8-.9 2.1-.9 1.1 0 1.8.8t.7 1.9zm-1-.3q0-.6-.4-1-.4-.5-1.1-.5-.8 0-1.2.4-.5.5-.5 1.1zm8.2-1-.6-.3-.8-.1q-.9 0-1.4.5-.5.6-.5 1.7v3h-1v-6h1v1.2q.3-.7.8-1 .5-.4 1.2-.4l.7.1.6.3zm1.3-1.2h1l1.8 5 1.8-5h1l-2.2 6h-1.2zm8.2 0h2.5v5.2h2v.8h-5v-.8h2v-4.4h-1.5zm1.5-2.3h1v1.2h-1zm9.5 8-.8.3q-.4.2-.9.2-1.4 0-2.2-.9-.8-.8-.8-2.3 0-1.5.8-2.3.8-.9 2.2-.9.5 0 .9.2.4 0 .8.3v1l-.8-.5-.9-.1q-1 0-1.4.6-.5.6-.5 1.7t.5 1.7q.5.6 1.4.6l1-.1.7-.5zm7.3-3.2v.5h-4.3q0 1.2.5 1.8.5.5 1.5.5l1-.1 1-.5v1l-1 .3-1 .2q-1.5 0-2.2-.9-.8-.8-.8-2.3 0-1.4.7-2.3.8-.9 2.1-.9 1.1 0 1.8.8t.7 1.9zm-1-.3q0-.6-.4-1-.4-.5-1.1-.5-.8 0-1.2.4-.5.5-.5 1.1zm11.9 7.1v-14h.8v14z"/>
|
||||
<path d="M268.9 91.3v-14h.8v14zm35 0v-14h.8v14z"/>
|
||||
<path d="M331.9 91.3v-14h.8v14zM367.3 81l-1.1 4h2.3zm-.6-1h1.3l2.4 8h-1.1l-.6-2H366l-.6 2h-1.1zm10 7.7-.8.3q-.4.2-.9.2-1.4 0-2.2-.9-.8-.8-.8-2.3 0-1.5.8-2.3.8-.9 2.2-.9.5 0 .9.2.4 0 .8.3v1l-.8-.5-.9-.1q-1 0-1.4.6-.5.6-.5 1.7t.5 1.7q.5.6 1.4.6l1-.1.7-.5zm4.6-7.4V82h2.2v.8h-2.2V86q0 .7.3 1 .2.2.8.2h1.1v.8h-1.2q-1 0-1.5-.4-.5-.5-.5-1.6v-3.2h-1.6V82h1.6v-1.7zm5.4 1.7h2.5v5.2h2v.8h-5v-.8h2v-4.4h-1.5zm1.5-2.3h1v1.2h-1zm7.1 3q-.7 0-1.1.6-.4.5-.4 1.7 0 1.1.4 1.7.4.6 1.1.6.8 0 1.2-.6.3-.6.3-1.7 0-1.2-.3-1.7-.4-.6-1.2-.6zm0-.9q1.3 0 2 .9.6.8.6 2.3 0 1.5-.7 2.3-.6.9-1.9.9-1.2 0-1.9-.9-.7-.8-.7-2.3 0-1.5.7-2.3.7-.9 2-.9zm9.3 2.5V88h-1v-3.7q0-.8-.2-1.2-.3-.4-1-.4-.6 0-1 .5t-.4 1.4V88h-1v-6h1v.9q.3-.5.7-.8.5-.3 1.1-.3 1 0 1.4.6.4.6.4 1.9zm6.6-2.1v1l-.8-.4-.9-.1q-.7 0-1 .2-.3.2-.3.6 0 .5.2.6l1.2.4.4.1q.8.2 1.2.6.3.4.3 1 0 1-.6 1.5t-1.8.5l-.9-.1-1-.3v-1l1 .4 1 .1q.6 0 1-.2.3-.3.3-.8 0-.7-1.3-1h-.4q-.9-.2-1.2-.6-.4-.4-.4-1 0-1 .6-1.4.5-.5 1.6-.5l1 .1.8.3zM420 80h1l.8 6.5 1-4.3h1l1 4.3.8-6.5h1l-1.2 8h-1l-1-4.8-1.2 4.8h-1zm10.3 2.7q-.7 0-1.1.6-.4.5-.4 1.7 0 1.1.4 1.7.4.6 1.1.6.8 0 1.2-.6.3-.6.3-1.7 0-1.2-.3-1.7-.4-.6-1.2-.6zm0-.9q1.3 0 2 .9.6.8.6 2.3 0 1.5-.7 2.3-.6.9-1.9.9-1.2 0-1.9-.9-.7-.8-.7-2.3 0-1.5.7-2.3.7-.9 2-.9zm9.9 1.4-.6-.3-.8-.1q-.9 0-1.4.5-.5.6-.5 1.7v3h-1v-6h1v1.2q.3-.7.8-1 .5-.4 1.2-.4l.7.1.6.3zm2.1-3.6h1v4.9l2.6-2.5h1.2l-2.4 2.3 2.8 3.7h-1.3l-2.2-3.1-.7.7V88h-1zm11.4 0v.9h-1.1q-.5 0-.8.2-.2.2-.2.8v.5h2.1v.8h-2V88h-1v-5.2H449V82h1.7v-.4q0-1 .4-1.5.5-.5 1.4-.5zm5.2 6.2q0 .7.2 1 .3.4.7.4h1.2v.8h-1.3q-.9 0-1.3-.6-.5-.5-.5-1.6v-5.4h-1.6v-.8h2.6zm6.4-3.1q-.7 0-1.1.6-.4.5-.4 1.7 0 1.1.4 1.7.4.6 1.1.6.8 0 1.2-.6.3-.6.3-1.7 0-1.2-.3-1.7-.4-.6-1.2-.6zm0-.9q1.3 0 2 .9.6.8.6 2.3 0 1.5-.7 2.3-.6.9-1.9.9-1.2 0-1.9-.9-.7-.8-.7-2.3 0-1.5.7-2.3.7-.9 2-.9zm3.7.2h1l1 4.8.9-3h.8l.9 3 1-4.8h1l-1.4 6h-1l-.9-3.3-1 3.3h-.9zm37.9 9.3v-14h.8v14zm11-6v-.9h6.8v1zm19.4-5.1v1l-.7-.3-.8-.2q-1 0-1.6.8-.5.8-.5 2.3.3-.5.7-.8.5-.3 1-.3 1.2 0 1.9.7.6.7.6 2t-.6 2q-.7.8-1.9.8-1.4 0-2-1-.7-1-.7-3.2 0-2 .8-3.1.8-1 2.3-1h.8l.7.3zm-2 3.3q-.6 0-1 .5t-.4 1.4q0 1 .4 1.4.4.5 1 .5.8 0 1.2-.5.3-.4.3-1.4 0-1-.3-1.4-.4-.5-1.1-.5zm13.6 7.8v-6h-3v-.9h3v-7.1h.8v14z"/>
|
||||
<path d="M569.9 91.3v-14h.8v14z"/>
|
||||
<path d="M597.9 85.3v-8h.8v7.1h3v1zm4 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h3v-7.1h.8v8z"/>
|
||||
<path d="M800.9 91.3v-14h.8v14z"/>
|
||||
<path d="M240.9 52.3v-14h.8v14z"/>
|
||||
<path d="M240.9 65.3v-14h.8v14z"/>
|
||||
<path d="M240.9 78.3v-14h.8v14zm4.1-19.2 6.6-3.2v6.5z"/>
|
||||
<path d="M268.9 78.3v-14h.8v14zm35 0v-14h.8v14z"/>
|
||||
<path d="M331.9 78.3v-14h.8v14zm175 0v-14h.8v14z"/>
|
||||
<path d="M534.9 78.3v-14h.8v14zm35 0v-14h.8v14z"/>
|
||||
<path d="M597.9 78.3v-14h.8v14zm175 0v-14h.8v14z"/>
|
||||
<path d="M800.9 78.3v-14h.8v14z"/>
|
||||
<path d="M251.9 59.3v-.9h6.8v1zm7 0v-.9h6.8v1zM268 61h3.7v1h-4.9v-1l1.8-1.8 1-1.1.7-1 .2-1q0-.6-.4-1t-1-.4l-1 .2-1.2.6v-1.1l1-.4 1.1-.2q1.2 0 2 .7.6.6.6 1.6l-.2 1q-.2.5-.8 1.2l-.9 1L268 61zm4.9-1.7v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm10.8 0v6h-.8v-6h-3v-.9h3v-7.1h.8v7.1h3v1zm3.2 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm10 6v-14h.8v14zm175 0v-14h.8v14z"/>
|
||||
<path d="M534.9 65.3v-14h.8v14z"/>
|
||||
<path d="M548.9 65.3v-6.9h3.8v1h-3v6zm11-6v-.9h6.8v1z"/>
|
||||
<path d="M569.9 65.3v-14h.8v14z"/>
|
||||
<path d="M573.9 59.3v-.9h6.8v1zm14.1 3.1V56l6.6 3.2zm9.9 2.9v-14h.8v14zm35.4-4.3 1.9-7h1.1l-2.3 8h-1.3l-2.4-8h1.1zm5.4-5h2.5v5.2h2v.8h-5v-.8h2v-4.4h-1.5zm1.5-2.3h1v1.2h-1zm10 3.5-.6-.3-.8-.1q-.9 0-1.4.5-.5.6-.5 1.7v3h-1v-6h1v1.2q.3-.7.8-1 .5-.4 1.2-.4l.7.1.6.3zm4.1-2.9V56h2.2v.8h-2.2V60q0 .7.3 1 .2.2.8.2h1.1v.8h-1.2q-1 0-1.5-.4-.5-.5-.5-1.6v-3.2h-1.6V56h1.6v-1.7zm4.7 5.4V56h1v3.7q0 .8.3 1.2.3.4 1 .4.6 0 1-.5t.4-1.4V56h1v6h-1v-.9q-.3.5-.8.8-.4.3-1 .3-1 0-1.4-.6-.5-.7-.5-1.9zm9.8-.7h-.4q-.8 0-1.3.3-.4.3-.4.9 0 .5.3.8.4.3 1 .3.8 0 1.2-.5.5-.6.5-1.6V59zm1.9-.4V62h-1v-.9q-.3.5-.8.8-.5.3-1.2.3-.9 0-1.4-.6-.6-.5-.6-1.4 0-1 .7-1.5t2-.5h1.3V58q0-.7-.4-1-.3-.3-1.1-.3l-1 .1-1 .4v-1l1-.3h1q.7 0 1.3.2.5.2.8.6l.3.7v1.2zm5.2 1.2q0 .7.2 1 .3.4.7.4h1.2v.8h-1.3q-.9 0-1.3-.6-.5-.5-.5-1.6v-5.4h-1.6v-.8h2.6zm10.3-5.8h1.4l1.4 4 1.4-4h1.5v8h-1v-7l-1.5 4.1h-.8l-1.4-4.2V62h-1zm10.6 5h-.4q-.8 0-1.3.3-.4.3-.4.9 0 .5.3.8.4.3 1 .3.8 0 1.2-.5.5-.6.5-1.6V59zm1.9-.4V62h-1v-.9q-.3.5-.8.8-.5.3-1.2.3-.9 0-1.4-.6-.6-.5-.6-1.4 0-1 .7-1.5t2-.5h1.3V58q0-.7-.4-1-.3-.3-1.1-.3l-1 .1-1 .4v-1l1-.3h1q.7 0 1.3.2.5.2.8.6l.3.7v1.2zm7 3.1-.8.3q-.4.2-.9.2-1.4 0-2.2-.9-.8-.8-.8-2.3 0-1.5.8-2.3.8-.9 2.2-.9.5 0 .9.2.4 0 .8.3v1l-.8-.5-.9-.1q-1 0-1.4.6-.5.6-.5 1.7t.5 1.7q.5.6 1.4.6l1-.1.7-.5zm6.9-3.4V62h-1v-3.7q0-.8-.2-1.2-.3-.4-1-.4-.6 0-1 .5t-.4 1.4V62h-1v-8.4h1V57q.3-.5.8-.8.4-.3 1-.3 1 0 1.4.6.4.6.4 1.9zm3.1-2.3h2.5v5.2h2v.8h-5v-.8h2v-4.4h-1.5zm1.5-2.3h1v1.2h-1zm9.4 4.6V62h-1v-3.7q0-.8-.2-1.2-.3-.4-1-.4-.6 0-1 .5t-.4 1.4V62h-1v-6h1v.9q.3-.5.8-.8.4-.3 1-.3 1 0 1.4.6.4.6.4 1.9zm7.4.2v.5h-4.3q0 1.2.5 1.8.5.5 1.5.5l1-.1 1-.5v1l-1 .3-1 .2q-1.5 0-2.2-.9-.8-.8-.8-2.3 0-1.4.7-2.3.8-.9 2.1-.9 1.1 0 1.8.8t.7 1.9zm-1-.3q0-.6-.4-1-.4-.5-1.1-.5-.8 0-1.2.4-.5.5-.5 1.1zm39.9 7.1v-14h.8v14z"/>
|
||||
<path d="M800.9 65.3v-14h.8v14zm-532-13v-14h.8v14zm35 0v-14h.8v14z"/>
|
||||
<path d="M331.9 52.3v-14h.8v14zm175 0v-14h.8v14z"/>
|
||||
<path d="M534.9 52.3v-14h.8v14zm35 0v-14h.8v14z"/>
|
||||
<path d="M597.9 52.3v-14h.8v14zm175 0v-14h.8v14z"/>
|
||||
<path d="M800.9 52.3v-14h.8v14z"/>
|
||||
<path d="M69.9 33.3v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm10 6v-6h-3v-.9h3.8v7z"/>
|
||||
<path d="M268.9 39.3v-14h.8v14zm35 0v-14h.8v14z"/>
|
||||
<path d="M331.9 39.3v-6.9h3.8v1h-3v6zm4-6v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm10 6v-6h-3v-.9h3.8v7z"/>
|
||||
<path d="M534.9 39.3v-14h.8v14zm35 0v-14h.8v14z"/>
|
||||
<path d="M597.9 39.3v-6.9h3.8v1h-3v6zm4-6v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm10 6v-6h-3v-.9h3.8v7z"/>
|
||||
<g>
|
||||
<path d="M800.9 39.3v-14h.8v14zm-532-13v-14h.8v14zm35 0v-14h.8v14zm231 0v-14h.8v14zm35 0v-14h.8v14z"/>
|
||||
<path d="M800.9 26.3v-14h.8v14zm-759-19v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm10 6v-6h-3v-.9h3.8v7zm35 0V6.4h3.8v1h-3v6zm4-6v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm10 6v-6h-3v-.9h3.8v7zm35 0V6.4h3.8v1h-3v6zm4-6v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1zm7 0v-.9h6.8v1z"/>
|
||||
<path d="M800.9 13.3v-6h-3v-.9h3.8v7z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 46 KiB |
144
package-lock.json
generated
144
package-lock.json
generated
@ -14,16 +14,16 @@
|
||||
"@google-github-actions/actions-utils": "^0.4.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.9.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"@types/node": "^20.10.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.12.0",
|
||||
"@typescript-eslint/parser": "^6.12.0",
|
||||
"@vercel/ncc": "^0.38.1",
|
||||
"eslint": "^8.53.0",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-prettier": "^5.0.1",
|
||||
"prettier": "^3.1.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.2.2"
|
||||
"typescript": "^5.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@aashutoshrathi/word-wrap": {
|
||||
@ -113,9 +113,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/js": {
|
||||
"version": "8.53.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz",
|
||||
"integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==",
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz",
|
||||
"integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
@ -281,31 +281,31 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz",
|
||||
"integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==",
|
||||
"version": "20.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.0.tgz",
|
||||
"integrity": "sha512-D0WfRmU9TQ8I9PFx9Yc+EBHw+vSpIub4IDvQivcp26PtPrdMGAq5SDcpXEo/epqa/DXotVpekHiLNTg3iaKXBQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/semver": {
|
||||
"version": "7.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.5.tgz",
|
||||
"integrity": "sha512-+d+WYC1BxJ6yVOgUgzK8gWvp5qF8ssV5r4nsDcZWKRWcDQLQ619tvWAxJQYGgBrO1MnLJC7a5GtiYsAoQ47dJg==",
|
||||
"version": "7.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz",
|
||||
"integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.11.0.tgz",
|
||||
"integrity": "sha512-uXnpZDc4VRjY4iuypDBKzW1rz9T5YBBK0snMn8MaTSNd2kMlj50LnLBABELjJiOL5YHk7ZD8hbSpI9ubzqYI0w==",
|
||||
"version": "6.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.12.0.tgz",
|
||||
"integrity": "sha512-XOpZ3IyJUIV1b15M7HVOpgQxPPF7lGXgsfcEIu3yDxFPaf/xZKt7s9QO/pbk7vpWQyVulpJbu4E5LwpZiQo4kA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.5.1",
|
||||
"@typescript-eslint/scope-manager": "6.11.0",
|
||||
"@typescript-eslint/type-utils": "6.11.0",
|
||||
"@typescript-eslint/utils": "6.11.0",
|
||||
"@typescript-eslint/visitor-keys": "6.11.0",
|
||||
"@typescript-eslint/scope-manager": "6.12.0",
|
||||
"@typescript-eslint/type-utils": "6.12.0",
|
||||
"@typescript-eslint/utils": "6.12.0",
|
||||
"@typescript-eslint/visitor-keys": "6.12.0",
|
||||
"debug": "^4.3.4",
|
||||
"graphemer": "^1.4.0",
|
||||
"ignore": "^5.2.4",
|
||||
@ -331,15 +331,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.11.0.tgz",
|
||||
"integrity": "sha512-+whEdjk+d5do5nxfxx73oanLL9ghKO3EwM9kBCkUtWMRwWuPaFv9ScuqlYfQ6pAD6ZiJhky7TZ2ZYhrMsfMxVQ==",
|
||||
"version": "6.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.12.0.tgz",
|
||||
"integrity": "sha512-s8/jNFPKPNRmXEnNXfuo1gemBdVmpQsK1pcu+QIvuNJuhFzGrpD7WjOcvDc/+uEdfzSYpNu7U/+MmbScjoQ6vg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "6.11.0",
|
||||
"@typescript-eslint/types": "6.11.0",
|
||||
"@typescript-eslint/typescript-estree": "6.11.0",
|
||||
"@typescript-eslint/visitor-keys": "6.11.0",
|
||||
"@typescript-eslint/scope-manager": "6.12.0",
|
||||
"@typescript-eslint/types": "6.12.0",
|
||||
"@typescript-eslint/typescript-estree": "6.12.0",
|
||||
"@typescript-eslint/visitor-keys": "6.12.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@ -359,13 +359,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.11.0.tgz",
|
||||
"integrity": "sha512-0A8KoVvIURG4uhxAdjSaxy8RdRE//HztaZdG8KiHLP8WOXSk0vlF7Pvogv+vlJA5Rnjj/wDcFENvDaHb+gKd1A==",
|
||||
"version": "6.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.12.0.tgz",
|
||||
"integrity": "sha512-5gUvjg+XdSj8pcetdL9eXJzQNTl3RD7LgUiYTl8Aabdi8hFkaGSYnaS6BLc0BGNaDH+tVzVwmKtWvu0jLgWVbw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "6.11.0",
|
||||
"@typescript-eslint/visitor-keys": "6.11.0"
|
||||
"@typescript-eslint/types": "6.12.0",
|
||||
"@typescript-eslint/visitor-keys": "6.12.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^16.0.0 || >=18.0.0"
|
||||
@ -376,13 +376,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.11.0.tgz",
|
||||
"integrity": "sha512-nA4IOXwZtqBjIoYrJcYxLRO+F9ri+leVGoJcMW1uqr4r1Hq7vW5cyWrA43lFbpRvQ9XgNrnfLpIkO3i1emDBIA==",
|
||||
"version": "6.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.12.0.tgz",
|
||||
"integrity": "sha512-WWmRXxhm1X8Wlquj+MhsAG4dU/Blvf1xDgGaYCzfvStP2NwPQh6KBvCDbiOEvaE0filhranjIlK/2fSTVwtBng==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/typescript-estree": "6.11.0",
|
||||
"@typescript-eslint/utils": "6.11.0",
|
||||
"@typescript-eslint/typescript-estree": "6.12.0",
|
||||
"@typescript-eslint/utils": "6.12.0",
|
||||
"debug": "^4.3.4",
|
||||
"ts-api-utils": "^1.0.1"
|
||||
},
|
||||
@ -403,9 +403,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.11.0.tgz",
|
||||
"integrity": "sha512-ZbEzuD4DwEJxwPqhv3QULlRj8KYTAnNsXxmfuUXFCxZmO6CF2gM/y+ugBSAQhrqaJL3M+oe4owdWunaHM6beqA==",
|
||||
"version": "6.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.12.0.tgz",
|
||||
"integrity": "sha512-MA16p/+WxM5JG/F3RTpRIcuOghWO30//VEOvzubM8zuOOBYXsP+IfjoCXXiIfy2Ta8FRh9+IO9QLlaFQUU+10Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^16.0.0 || >=18.0.0"
|
||||
@ -416,13 +416,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.11.0.tgz",
|
||||
"integrity": "sha512-Aezzv1o2tWJwvZhedzvD5Yv7+Lpu1by/U1LZ5gLc4tCx8jUmuSCMioPFRjliN/6SJIvY6HpTtJIWubKuYYYesQ==",
|
||||
"version": "6.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.12.0.tgz",
|
||||
"integrity": "sha512-vw9E2P9+3UUWzhgjyyVczLWxZ3GuQNT7QpnIY3o5OMeLO/c8oHljGc8ZpryBMIyympiAAaKgw9e5Hl9dCWFOYw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "6.11.0",
|
||||
"@typescript-eslint/visitor-keys": "6.11.0",
|
||||
"@typescript-eslint/types": "6.12.0",
|
||||
"@typescript-eslint/visitor-keys": "6.12.0",
|
||||
"debug": "^4.3.4",
|
||||
"globby": "^11.1.0",
|
||||
"is-glob": "^4.0.3",
|
||||
@ -443,17 +443,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.11.0.tgz",
|
||||
"integrity": "sha512-p23ibf68fxoZy605dc0dQAEoUsoiNoP3MD9WQGiHLDuTSOuqoTsa4oAy+h3KDkTcxbbfOtUjb9h3Ta0gT4ug2g==",
|
||||
"version": "6.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.12.0.tgz",
|
||||
"integrity": "sha512-LywPm8h3tGEbgfyjYnu3dauZ0U7R60m+miXgKcZS8c7QALO9uWJdvNoP+duKTk2XMWc7/Q3d/QiCuLN9X6SWyQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.4.0",
|
||||
"@types/json-schema": "^7.0.12",
|
||||
"@types/semver": "^7.5.0",
|
||||
"@typescript-eslint/scope-manager": "6.11.0",
|
||||
"@typescript-eslint/types": "6.11.0",
|
||||
"@typescript-eslint/typescript-estree": "6.11.0",
|
||||
"@typescript-eslint/scope-manager": "6.12.0",
|
||||
"@typescript-eslint/types": "6.12.0",
|
||||
"@typescript-eslint/typescript-estree": "6.12.0",
|
||||
"semver": "^7.5.4"
|
||||
},
|
||||
"engines": {
|
||||
@ -468,12 +468,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.11.0.tgz",
|
||||
"integrity": "sha512-+SUN/W7WjBr05uRxPggJPSzyB8zUpaYo2hByKasWbqr3PM8AXfZt8UHdNpBS1v9SA62qnSSMF3380SwDqqprgQ==",
|
||||
"version": "6.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.12.0.tgz",
|
||||
"integrity": "sha512-rg3BizTZHF1k3ipn8gfrzDXXSFKyOEB5zxYXInQ6z0hUvmQlhaZQzK+YmHmNViMA9HzW5Q9+bPPt90bU6GQwyw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "6.11.0",
|
||||
"@typescript-eslint/types": "6.12.0",
|
||||
"eslint-visitor-keys": "^3.4.1"
|
||||
},
|
||||
"engines": {
|
||||
@ -597,9 +597,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/big-integer": {
|
||||
"version": "1.6.51",
|
||||
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz",
|
||||
"integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==",
|
||||
"version": "1.6.52",
|
||||
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz",
|
||||
"integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
@ -838,15 +838,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "8.53.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz",
|
||||
"integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==",
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz",
|
||||
"integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.6.1",
|
||||
"@eslint/eslintrc": "^2.1.3",
|
||||
"@eslint/js": "8.53.0",
|
||||
"@eslint/js": "8.54.0",
|
||||
"@humanwhocodes/config-array": "^0.11.13",
|
||||
"@humanwhocodes/module-importer": "^1.0.1",
|
||||
"@nodelib/fs.walk": "^1.2.8",
|
||||
@ -1274,9 +1274,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ignore": {
|
||||
"version": "5.2.4",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
|
||||
"integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==",
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz",
|
||||
"integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
@ -2242,9 +2242,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
|
||||
"integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz",
|
||||
"integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
@ -2255,9 +2255,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "5.27.2",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-5.27.2.tgz",
|
||||
"integrity": "sha512-iS857PdOEy/y3wlM3yRp+6SNQQ6xU0mmZcwRSriqk+et/cwWAtwmIGf6WkoDN2EK/AMdCO/dfXzIwi+rFMrjjQ==",
|
||||
"version": "5.28.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-5.28.0.tgz",
|
||||
"integrity": "sha512-gM12DkXhlAc5+/TPe60iy9P6ETgVfqTuRJ6aQ4w8RYu0MqKuXhaq3/b86GfzDQnNA3NUO6aUNdvevrKH59D0Nw==",
|
||||
"dependencies": {
|
||||
"@fastify/busboy": "^2.0.0"
|
||||
},
|
||||
|
10
package.json
10
package.json
@ -28,15 +28,15 @@
|
||||
"@google-github-actions/actions-utils": "^0.4.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.9.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"@types/node": "^20.10.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.12.0",
|
||||
"@typescript-eslint/parser": "^6.12.0",
|
||||
"@vercel/ncc": "^0.38.1",
|
||||
"eslint": "^8.53.0",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-prettier": "^5.0.1",
|
||||
"prettier": "^3.1.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.2.2"
|
||||
"typescript": "^5.3.2"
|
||||
}
|
||||
}
|
||||
|
269
src/base.ts
269
src/base.ts
@ -12,157 +12,210 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { HttpClient } from '@actions/http-client';
|
||||
import { URLSearchParams } from 'url';
|
||||
import {
|
||||
GoogleAccessTokenParameters,
|
||||
GoogleAccessTokenResponse,
|
||||
GoogleIDTokenParameters,
|
||||
GoogleIDTokenResponse,
|
||||
} from './client/auth_client';
|
||||
|
||||
// Do not listen to the linter - this can NOT be rewritten as an ES6 import statement.
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { version: appVersion } = require('../package.json');
|
||||
import { HttpClient } from '@actions/http-client';
|
||||
|
||||
// userAgent is the default user agent.
|
||||
const userAgent = `google-github-actions:auth/${appVersion}`;
|
||||
import { Logger } from './logger';
|
||||
import { expandEndpoint, userAgent } from './utils';
|
||||
|
||||
/**
|
||||
* BaseClient is the default HTTP client for interacting with the IAM
|
||||
* credentials API.
|
||||
* GenerateAccessTokenParameters are the inputs to the generateAccessToken call.
|
||||
*/
|
||||
export class BaseClient {
|
||||
/**
|
||||
* client is the HTTP client.
|
||||
*/
|
||||
protected readonly client: HttpClient;
|
||||
export interface GenerateAccessTokenParameters {
|
||||
readonly serviceAccount: string;
|
||||
readonly delegates?: string[];
|
||||
readonly scopes?: string[];
|
||||
readonly lifetime?: number;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.client = new HttpClient(userAgent);
|
||||
}
|
||||
/**
|
||||
* GenerateIDTokenParameters are the inputs to the generateIDToken call.
|
||||
*/
|
||||
export interface GenerateIDTokenParameters {
|
||||
readonly serviceAccount: string;
|
||||
readonly audience: string;
|
||||
readonly delegates?: string[];
|
||||
readonly includeEmail?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* googleIDToken generates a Google Cloud ID token for the provided
|
||||
* service account email or unique id.
|
||||
*/
|
||||
async googleIDToken(
|
||||
token: string,
|
||||
{ serviceAccount, audience, delegates, includeEmail }: GoogleIDTokenParameters,
|
||||
): Promise<GoogleIDTokenResponse> {
|
||||
const pth = `https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${serviceAccount}:generateIdToken`;
|
||||
/**
|
||||
* IAMCredentialsClientParameters are the inputs to the IAM client.
|
||||
*/
|
||||
export interface IAMCredentialsClientParameters {
|
||||
readonly authToken: string;
|
||||
}
|
||||
|
||||
const data = {
|
||||
delegates: delegates,
|
||||
audience: audience,
|
||||
includeEmail: includeEmail,
|
||||
};
|
||||
/**
|
||||
* IAMCredentialsClient is a thin HTTP client around the Google Cloud IAM
|
||||
* Credentials API.
|
||||
*/
|
||||
export class IAMCredentialsClient {
|
||||
readonly #logger: Logger;
|
||||
readonly #httpClient: HttpClient;
|
||||
readonly #authToken: string;
|
||||
|
||||
const headers = {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
readonly #universe: string = 'googleapis.com';
|
||||
readonly #endpoints = {
|
||||
iamcredentials: 'https://iamcredentials.{universe}/v1',
|
||||
oauth2: 'https://oauth2.{universe}',
|
||||
};
|
||||
|
||||
try {
|
||||
const resp = await this.client.request('POST', pth, JSON.stringify(data), headers);
|
||||
const body = await resp.readBody();
|
||||
const statusCode = resp.message.statusCode || 500;
|
||||
if (statusCode >= 400) {
|
||||
throw new Error(`(${statusCode}) ${body}`);
|
||||
}
|
||||
const parsed = JSON.parse(body);
|
||||
return {
|
||||
token: parsed['token'],
|
||||
};
|
||||
} catch (err) {
|
||||
throw new Error(`failed to generate Google Cloud ID token for ${serviceAccount}: ${err}`);
|
||||
constructor(logger: Logger, opts: IAMCredentialsClientParameters) {
|
||||
this.#logger = logger.withNamespace(this.constructor.name);
|
||||
this.#httpClient = new HttpClient(userAgent);
|
||||
|
||||
this.#authToken = opts.authToken;
|
||||
|
||||
const endpoints = this.#endpoints;
|
||||
for (const key of Object.keys(this.#endpoints) as Array<keyof typeof endpoints>) {
|
||||
this.#endpoints[key] = expandEndpoint(this.#endpoints[key], this.#universe);
|
||||
}
|
||||
this.#logger.debug(`Computed endpoints`, this.#endpoints);
|
||||
}
|
||||
|
||||
/**
|
||||
* googleAccessToken generates a Google Cloud access token for the provided
|
||||
* service account email or unique id.
|
||||
* generateAccessToken generates a new OAuth 2.0 Access Token for a service
|
||||
* account.
|
||||
*/
|
||||
async googleAccessToken(
|
||||
token: string,
|
||||
{ serviceAccount, delegates, scopes, lifetime }: GoogleAccessTokenParameters,
|
||||
): Promise<GoogleAccessTokenResponse> {
|
||||
const pth = `https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${serviceAccount}:generateAccessToken`;
|
||||
async generateAccessToken({
|
||||
serviceAccount,
|
||||
delegates,
|
||||
scopes,
|
||||
lifetime,
|
||||
}: GenerateAccessTokenParameters): Promise<string> {
|
||||
const pth = `${this.#endpoints.iamcredentials}/projects/-/serviceAccounts/${serviceAccount}:generateAccessToken`;
|
||||
|
||||
const data: Record<string, string | Array<string>> = {};
|
||||
const headers = { Authorization: `Bearer ${this.#authToken}` };
|
||||
|
||||
const body: Record<string, string | Array<string>> = {};
|
||||
if (delegates && delegates.length > 0) {
|
||||
data.delegates = delegates;
|
||||
body.delegates = delegates;
|
||||
}
|
||||
if (scopes && scopes.length > 0) {
|
||||
// Not a typo, the API expects the field to be "scope" (singular).
|
||||
data.scope = scopes;
|
||||
body.scope = scopes;
|
||||
}
|
||||
if (lifetime && lifetime > 0) {
|
||||
data.lifetime = `${lifetime}s`;
|
||||
body.lifetime = `${lifetime}s`;
|
||||
}
|
||||
|
||||
const headers = {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
this.#logger.withNamespace('generateAccessToken').debug({
|
||||
method: `POST`,
|
||||
path: pth,
|
||||
headers: headers,
|
||||
body: body,
|
||||
});
|
||||
|
||||
try {
|
||||
const resp = await this.client.request('POST', pth, JSON.stringify(data), headers);
|
||||
const body = await resp.readBody();
|
||||
const statusCode = resp.message.statusCode || 500;
|
||||
if (statusCode >= 400) {
|
||||
throw new Error(`(${statusCode}) ${body}`);
|
||||
const resp = await this.#httpClient.postJson<{ accessToken: string }>(pth, body, headers);
|
||||
const statusCode = resp.statusCode || 500;
|
||||
if (statusCode < 200 || statusCode > 299) {
|
||||
throw new Error(`Failed to call ${pth}: HTTP ${statusCode}: ${resp.result || '[no body]'}`);
|
||||
}
|
||||
const parsed = JSON.parse(body);
|
||||
return {
|
||||
accessToken: parsed['accessToken'],
|
||||
expiration: parsed['expireTime'],
|
||||
};
|
||||
|
||||
const result = resp.result;
|
||||
if (!result) {
|
||||
throw new Error(`Successfully called ${pth}, but the result was empty`);
|
||||
}
|
||||
return result.accessToken;
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to generate Google Cloud access token for ${serviceAccount}: ${err}`);
|
||||
throw new Error(
|
||||
`Failed to generate Google Cloud OAuth 2.0 Access Token for ${serviceAccount}: ${err}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* googleOAuthToken generates a Google Cloud OAuth token using the legacy
|
||||
* OAuth endpoints.
|
||||
*
|
||||
* @param assertion A signed JWT.
|
||||
*/
|
||||
async googleOAuthToken(assertion: string): Promise<GoogleAccessTokenResponse> {
|
||||
const pth = `https://oauth2.googleapis.com/token`;
|
||||
async generateDomainWideDelegationAccessToken(assertion: string): Promise<string> {
|
||||
const pth = `${this.#endpoints.oauth2}/token`;
|
||||
|
||||
const headers = {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
};
|
||||
|
||||
const data = new URLSearchParams();
|
||||
data.append('grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer');
|
||||
data.append('assertion', assertion);
|
||||
const body = new URLSearchParams();
|
||||
body.append('grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer');
|
||||
body.append('assertion', assertion);
|
||||
|
||||
this.#logger.withNamespace('generateDomainWideDelegationAccessToken').debug({
|
||||
method: `POST`,
|
||||
path: pth,
|
||||
headers: headers,
|
||||
body: body,
|
||||
});
|
||||
|
||||
try {
|
||||
const resp = await this.client.request('POST', pth, data.toString(), headers);
|
||||
const body = await resp.readBody();
|
||||
const resp = await this.#httpClient.post(pth, body.toString(), headers);
|
||||
const respBody = await resp.readBody();
|
||||
const statusCode = resp.message.statusCode || 500;
|
||||
if (statusCode >= 400) {
|
||||
throw new Error(`(${statusCode}) ${body}`);
|
||||
if (statusCode < 200 || statusCode > 299) {
|
||||
throw new Error(`Failed to call ${pth}: HTTP ${statusCode}: ${respBody || '[no body]'}`);
|
||||
}
|
||||
const parsed = JSON.parse(body);
|
||||
|
||||
// Normalize the expiration to be a timestamp like the iamcredentials API.
|
||||
// This API returns the number of seconds until expiration, so convert
|
||||
// that into a date.
|
||||
const expiration = new Date(new Date().getTime() + parsed['expires_in'] * 10000);
|
||||
|
||||
return {
|
||||
accessToken: parsed['access_token'],
|
||||
expiration: expiration.toISOString(),
|
||||
};
|
||||
const parsed = JSON.parse(respBody) as { accessToken: string };
|
||||
return parsed.accessToken;
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to generate Google Cloud OAuth token: ${err}`);
|
||||
throw new Error(
|
||||
`Failed to generate Google Cloud Domain Wide Delegation OAuth 2.0 Access Token: ${err}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* generateIDToken generates a new OpenID Connect ID token for a service
|
||||
* account.
|
||||
*/
|
||||
async generateIDToken({
|
||||
serviceAccount,
|
||||
audience,
|
||||
delegates,
|
||||
includeEmail,
|
||||
}: GenerateIDTokenParameters): Promise<string> {
|
||||
const pth = `${this.#endpoints.iamcredentials}/projects/-/serviceAccounts/${serviceAccount}:generateIdToken`;
|
||||
|
||||
const headers = { Authorization: `Bearer ${this.#authToken}` };
|
||||
|
||||
const body: Record<string, string | string[] | boolean> = {
|
||||
audience: audience,
|
||||
includeEmail: includeEmail ? true : false,
|
||||
};
|
||||
if (delegates && delegates.length > 0) {
|
||||
body.delegates = delegates;
|
||||
}
|
||||
|
||||
this.#logger.withNamespace('generateIDToken').debug({
|
||||
method: `POST`,
|
||||
path: pth,
|
||||
headers: headers,
|
||||
body: body,
|
||||
});
|
||||
|
||||
try {
|
||||
const resp = await this.#httpClient.postJson<{ token: string }>(pth, body, headers);
|
||||
const statusCode = resp.statusCode || 500;
|
||||
if (statusCode < 200 || statusCode > 299) {
|
||||
throw new Error(`Failed to call ${pth}: HTTP ${statusCode}: ${resp.result || '[no body]'}`);
|
||||
}
|
||||
|
||||
const result = resp.result;
|
||||
if (!result) {
|
||||
throw new Error(`Successfully called ${pth}, but the result was empty`);
|
||||
}
|
||||
return result.token;
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`Failed to generate Google Cloud OpenID Connect ID token for ${serviceAccount}: ${err}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { AuthClient } from './client/auth_client';
|
||||
export {
|
||||
ServiceAccountKeyClientParameters,
|
||||
ServiceAccountKeyClient,
|
||||
} from './client/credentials_json_client';
|
||||
export {
|
||||
WorkloadIdentityFederationClientParameters,
|
||||
WorkloadIdentityFederationClient,
|
||||
} from './client/workload_identity_client';
|
||||
|
@ -13,83 +13,24 @@
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* Defines the main interface for all clients that generate credentials.
|
||||
* Client is the default HTTP client for interacting with the IAM credentials
|
||||
* API.
|
||||
*/
|
||||
export interface AuthClient {
|
||||
getAuthToken(): Promise<string>;
|
||||
signJWT(unsignedJWT: string, delegates?: Array<string>): Promise<string>;
|
||||
getProjectID(): Promise<string>;
|
||||
getServiceAccount(): Promise<string>;
|
||||
createCredentialsFile(outputDir: string): Promise<string>;
|
||||
/**
|
||||
* getToken() gets or generates the best token for the auth client.
|
||||
*/
|
||||
getToken(): Promise<string>;
|
||||
|
||||
/**
|
||||
* Provided by BaseClient.
|
||||
* createCredentialsFile creates a credential file (for use with gcloud and
|
||||
* other Google Cloud tools) that instructs the tool how to perform identity
|
||||
* federation.
|
||||
*/
|
||||
googleIDToken(token: string, params: GoogleIDTokenParameters): Promise<GoogleIDTokenResponse>;
|
||||
googleAccessToken(
|
||||
token: string,
|
||||
params: GoogleAccessTokenParameters,
|
||||
): Promise<GoogleAccessTokenResponse>;
|
||||
googleOAuthToken(assertion: string): Promise<GoogleAccessTokenResponse>;
|
||||
}
|
||||
createCredentialsFile(outputPath: string): Promise<string>;
|
||||
|
||||
/**
|
||||
* GoogleAccessTokenParameters are the parameters to generate a Google Cloud
|
||||
* access token as described in:
|
||||
*
|
||||
* https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/generateAccessToken
|
||||
*
|
||||
* @param serviceAccount Optional email address or unique identifier of the
|
||||
* service account.
|
||||
* @param delegates Optional sequence of service accounts in the delegation
|
||||
* chain.
|
||||
* @param lifetime Optional validity period as a number representing the number
|
||||
* of seconds.
|
||||
*/
|
||||
export interface GoogleAccessTokenParameters {
|
||||
serviceAccount?: string;
|
||||
delegates?: Array<string>;
|
||||
scopes?: Array<string>;
|
||||
lifetime?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* GoogleAccessTokenResponse is the response from generating an access token.
|
||||
*
|
||||
* @param accessToken OAuth 2.0 access token.
|
||||
* @param expiration A timestamp in RFC3339 UTC "Zulu" format when the token
|
||||
* expires.
|
||||
*/
|
||||
export interface GoogleAccessTokenResponse {
|
||||
accessToken: string;
|
||||
expiration: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* GoogleIDTokenParameters are the parameters to generate a Google Cloud
|
||||
* ID token as described in:
|
||||
*
|
||||
* https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/generateIdToken
|
||||
*
|
||||
* @param serviceAccount Email address or unique identifier of the service
|
||||
* account.
|
||||
* @param audience The audience for the token.
|
||||
* @param delegates Optional sequence of service accounts in the delegation
|
||||
* chain.
|
||||
*/
|
||||
export interface GoogleIDTokenParameters {
|
||||
serviceAccount?: string;
|
||||
audience: string;
|
||||
delegates?: Array<string>;
|
||||
includeEmail?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* GoogleIDTokenResponse is the response from generating an ID token.
|
||||
*
|
||||
* @param token ID token.
|
||||
* expires.
|
||||
*/
|
||||
export interface GoogleIDTokenResponse {
|
||||
token: string;
|
||||
/**
|
||||
* signJWT signs a JWT using the auth provider.
|
||||
*/
|
||||
signJWT(claims: any): Promise<string>;
|
||||
}
|
||||
|
@ -13,6 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
import { createSign } from 'crypto';
|
||||
|
||||
import {
|
||||
isServiceAccountKey,
|
||||
parseCredential,
|
||||
@ -22,123 +23,112 @@ import {
|
||||
} from '@google-github-actions/actions-utils';
|
||||
|
||||
import { AuthClient } from './auth_client';
|
||||
import { BaseClient } from '../base';
|
||||
import { expandEndpoint } from '../utils';
|
||||
import { Logger } from '../logger';
|
||||
|
||||
/**
|
||||
* Available options to create the CredentialsJSONClient.
|
||||
*
|
||||
* @param projectID User-supplied value for project ID. If not provided, the
|
||||
* project ID is extracted from the credentials JSON.
|
||||
* @param credentialsJSON Raw JSON credentials blob.
|
||||
* ServiceAccountKeyClientParameters is used as input to the
|
||||
* ServiceAccountKeyClient.
|
||||
*/
|
||||
interface CredentialsJSONClientOptions {
|
||||
projectID?: string;
|
||||
credentialsJSON: string;
|
||||
export interface ServiceAccountKeyClientParameters {
|
||||
readonly serviceAccountKey: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* CredentialsJSONClient is a client that accepts a service account key JSON
|
||||
* credential.
|
||||
* ServiceAccountKeyClient is an authentication client that expects a Service
|
||||
* Account Key JSON file.
|
||||
*/
|
||||
export class CredentialsJSONClient extends BaseClient implements AuthClient {
|
||||
readonly #projectID: string;
|
||||
readonly #credentials: ServiceAccountKey;
|
||||
export class ServiceAccountKeyClient implements AuthClient {
|
||||
readonly #logger: Logger;
|
||||
readonly #serviceAccountKey: ServiceAccountKey;
|
||||
|
||||
constructor(opts: CredentialsJSONClientOptions) {
|
||||
super();
|
||||
readonly #universe: string = 'googleapis.com';
|
||||
readonly #endpoints = {
|
||||
iamcredentials: 'https://iamcredentials.{universe}/v1',
|
||||
};
|
||||
readonly #audience: string;
|
||||
|
||||
const credentials = parseCredential(opts.credentialsJSON);
|
||||
if (!isServiceAccountKey(credentials)) {
|
||||
throw new Error(`Provided credential is not a valid service account key JSON`);
|
||||
constructor(logger: Logger, opts: ServiceAccountKeyClientParameters) {
|
||||
this.#logger = logger.withNamespace(this.constructor.name);
|
||||
|
||||
const serviceAccountKey = parseCredential(opts.serviceAccountKey);
|
||||
if (!isServiceAccountKey(serviceAccountKey)) {
|
||||
throw new Error(`Provided credential is not a valid Google Service Account Key JSON`);
|
||||
}
|
||||
this.#credentials = credentials;
|
||||
this.#serviceAccountKey = serviceAccountKey;
|
||||
|
||||
this.#projectID = opts.projectID || this.#credentials.project_id;
|
||||
const endpoints = this.#endpoints;
|
||||
for (const key of Object.keys(this.#endpoints) as Array<keyof typeof endpoints>) {
|
||||
this.#endpoints[key] = expandEndpoint(this.#endpoints[key], this.#universe);
|
||||
}
|
||||
this.#logger.debug(`Computed endpoints`, this.#endpoints);
|
||||
|
||||
this.#audience = new URL(this.#endpoints.iamcredentials).origin + `/`;
|
||||
this.#logger.debug(`Computed audience`, this.#audience);
|
||||
}
|
||||
|
||||
/**
|
||||
* getAuthToken generates a token capable of calling the iamcredentials API.
|
||||
* getToken generates a self-signed JWT that, by default, is capable of
|
||||
* calling the iamcredentials API to mint OAuth 2.0 Access Tokens and ID
|
||||
* Tokens. However, users can theoretically override the audience value and
|
||||
* use the JWT to call other endpoints without calling iamcredentials.
|
||||
*/
|
||||
async getAuthToken(): Promise<string> {
|
||||
const header = {
|
||||
alg: 'RS256',
|
||||
typ: 'JWT',
|
||||
kid: this.#credentials.private_key_id,
|
||||
};
|
||||
|
||||
const now = Math.floor(new Date().getTime() / 1000);
|
||||
|
||||
const body = {
|
||||
iss: this.#credentials.client_email,
|
||||
sub: this.#credentials.client_email,
|
||||
aud: 'https://iamcredentials.googleapis.com/',
|
||||
iat: now,
|
||||
exp: now + 3599,
|
||||
};
|
||||
|
||||
const message = toBase64(JSON.stringify(header)) + '.' + toBase64(JSON.stringify(body));
|
||||
|
||||
async getToken(): Promise<string> {
|
||||
try {
|
||||
const signer = createSign('RSA-SHA256');
|
||||
signer.write(message);
|
||||
signer.end();
|
||||
const now = Math.floor(new Date().getTime() / 1000);
|
||||
|
||||
const signature = signer.sign(this.#credentials.private_key);
|
||||
return message + '.' + toBase64(signature);
|
||||
const claims = {
|
||||
iss: this.#serviceAccountKey.client_email,
|
||||
sub: this.#serviceAccountKey.client_email,
|
||||
aud: this.#audience,
|
||||
iat: now,
|
||||
exp: now + 3599,
|
||||
};
|
||||
|
||||
this.#logger.withNamespace('getToken').debug({
|
||||
claims: claims,
|
||||
});
|
||||
|
||||
return await this.signJWT(claims);
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to sign auth token using ${await this.getServiceAccount()}: ${err}`);
|
||||
throw new Error(
|
||||
`Failed to sign auth token using ${this.#serviceAccountKey.client_email}: ${err}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* signJWT signs the given JWT with the private key.
|
||||
*
|
||||
* @param unsignedJWT The JWT to sign.
|
||||
* signJWT signs a JWT using the Service Account's private key.
|
||||
*/
|
||||
async signJWT(unsignedJWT: string): Promise<string> {
|
||||
async signJWT(claims: any): Promise<string> {
|
||||
const header = {
|
||||
alg: 'RS256',
|
||||
typ: 'JWT',
|
||||
kid: this.#credentials.private_key_id,
|
||||
alg: `RS256`,
|
||||
typ: `JWT`,
|
||||
kid: this.#serviceAccountKey.private_key_id,
|
||||
};
|
||||
|
||||
const message = toBase64(JSON.stringify(header)) + '.' + toBase64(unsignedJWT);
|
||||
const message = toBase64(JSON.stringify(header)) + `.` + toBase64(JSON.stringify(claims));
|
||||
|
||||
try {
|
||||
const signer = createSign('RSA-SHA256');
|
||||
signer.write(message);
|
||||
signer.end();
|
||||
this.#logger.withNamespace('signJWT').debug({
|
||||
header: header,
|
||||
claims: claims,
|
||||
message: message,
|
||||
});
|
||||
|
||||
const signature = signer.sign(this.#credentials.private_key);
|
||||
const jwt = message + '.' + toBase64(signature);
|
||||
return jwt;
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to sign JWT using ${await this.getServiceAccount()}: ${err}`);
|
||||
}
|
||||
const signer = createSign(`RSA-SHA256`);
|
||||
signer.write(message);
|
||||
signer.end();
|
||||
|
||||
const signature = signer.sign(this.#serviceAccountKey.private_key);
|
||||
return message + '.' + toBase64(signature);
|
||||
}
|
||||
|
||||
/**
|
||||
* getProjectID returns the project ID. If an override was given, the override
|
||||
* is returned. Otherwise, this will be the project ID that was extracted from
|
||||
* the service account key JSON.
|
||||
*/
|
||||
async getProjectID(): Promise<string> {
|
||||
return this.#projectID;
|
||||
}
|
||||
|
||||
/**
|
||||
* getServiceAccount returns the service account email for the authentication,
|
||||
* extracted from the Service Account Key JSON.
|
||||
*/
|
||||
async getServiceAccount(): Promise<string> {
|
||||
return this.#credentials.client_email;
|
||||
}
|
||||
|
||||
/**
|
||||
* createCredentialsFile creates a Google Cloud credentials file that can be
|
||||
* set as GOOGLE_APPLICATION_CREDENTIALS for gcloud and client libraries.
|
||||
* createCredentialsFile writes the Service Account Key JSON back to disk at
|
||||
* the specified outputPath.
|
||||
*/
|
||||
async createCredentialsFile(outputPath: string): Promise<string> {
|
||||
return await writeSecureFile(outputPath, JSON.stringify(this.#credentials));
|
||||
this.#logger.withNamespace('createCredentialsFile').debug({ outputPath: outputPath });
|
||||
return await writeSecureFile(outputPath, JSON.stringify(this.#serviceAccountKey));
|
||||
}
|
||||
}
|
||||
|
@ -12,211 +12,208 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { URL } from 'url';
|
||||
import { HttpClient } from '@actions/http-client';
|
||||
|
||||
import { writeSecureFile } from '@google-github-actions/actions-utils';
|
||||
|
||||
import { AuthClient } from './auth_client';
|
||||
import { BaseClient } from '../base';
|
||||
import { expandEndpoint, userAgent } from '../utils';
|
||||
import { Logger } from '../logger';
|
||||
|
||||
/**
|
||||
* Available options to create the WorkloadIdentityClient.
|
||||
*
|
||||
* @param projectID User-supplied value for project ID. If not provided, the
|
||||
* project ID is extracted from the service account email.
|
||||
* @param providerID Full path (including project, location, etc) to the Google
|
||||
* Cloud Workload Identity Provider.
|
||||
* @param serviceAccount Email address or unique identifier of the service
|
||||
* account to impersonate
|
||||
* @param token GitHub OIDC token to use for exchanging with Workload Identity
|
||||
* Federation.
|
||||
* @param audience The value for the audience parameter in the generated GitHub
|
||||
* Actions OIDC token, defaults to the value of workload_identity_provider
|
||||
* WorkloadIdentityFederationClientParameters is used as input to the
|
||||
* WorkloadIdentityFederationClient.
|
||||
*/
|
||||
interface WorkloadIdentityClientOptions {
|
||||
projectID?: string;
|
||||
providerID: string;
|
||||
serviceAccount: string;
|
||||
token: string;
|
||||
audience: string;
|
||||
|
||||
oidcTokenRequestURL: string;
|
||||
oidcTokenRequestToken: string;
|
||||
export interface WorkloadIdentityFederationClientParameters {
|
||||
readonly githubOIDCToken: string;
|
||||
readonly githubOIDCTokenRequestURL: string;
|
||||
readonly githubOIDCTokenRequestToken: string;
|
||||
readonly githubOIDCTokenAudience: string;
|
||||
readonly workloadIdentityProviderName: string;
|
||||
readonly audience?: string;
|
||||
readonly serviceAccount?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* WorkloadIdentityClient is a client that uses the GitHub Actions runtime to
|
||||
* authentication via Workload Identity.
|
||||
* WorkloadIdentityFederationClient is an authentication client that configures
|
||||
* a Workload Identity authentication scheme.
|
||||
*/
|
||||
export class WorkloadIdentityClient extends BaseClient implements AuthClient {
|
||||
readonly #projectID: string;
|
||||
readonly #providerID: string;
|
||||
readonly #serviceAccount: string;
|
||||
readonly #token: string;
|
||||
export class WorkloadIdentityFederationClient implements AuthClient {
|
||||
readonly #logger: Logger;
|
||||
readonly #httpClient: HttpClient;
|
||||
|
||||
readonly #githubOIDCToken: string;
|
||||
readonly #githubOIDCTokenRequestURL: string;
|
||||
readonly #githubOIDCTokenRequestToken: string;
|
||||
readonly #githubOIDCTokenAudience: string;
|
||||
readonly #workloadIdentityProviderName: string;
|
||||
readonly #serviceAccount?: string;
|
||||
|
||||
#cachedToken?: string;
|
||||
#cachedAt?: number;
|
||||
|
||||
readonly #universe: string = 'googleapis.com';
|
||||
readonly #endpoints = {
|
||||
iam: 'https://iam.{universe}/v1',
|
||||
iamcredentials: 'https://iamcredentials.{universe}/v1',
|
||||
sts: 'https://sts.{universe}/v1',
|
||||
www: 'https://www.{universe}',
|
||||
};
|
||||
readonly #audience: string;
|
||||
|
||||
readonly #oidcTokenRequestURL: string;
|
||||
readonly #oidcTokenRequestToken: string;
|
||||
constructor(logger: Logger, opts: WorkloadIdentityFederationClientParameters) {
|
||||
this.#logger = logger.withNamespace(this.constructor.name);
|
||||
this.#httpClient = new HttpClient(userAgent);
|
||||
|
||||
constructor(opts: WorkloadIdentityClientOptions) {
|
||||
super();
|
||||
|
||||
this.#providerID = opts.providerID;
|
||||
this.#githubOIDCToken = opts.githubOIDCToken;
|
||||
this.#githubOIDCTokenRequestURL = opts.githubOIDCTokenRequestURL;
|
||||
this.#githubOIDCTokenRequestToken = opts.githubOIDCTokenRequestToken;
|
||||
this.#githubOIDCTokenAudience = opts.githubOIDCTokenAudience;
|
||||
this.#workloadIdentityProviderName = opts.workloadIdentityProviderName;
|
||||
this.#serviceAccount = opts.serviceAccount;
|
||||
this.#token = opts.token;
|
||||
this.#audience = opts.audience;
|
||||
|
||||
this.#oidcTokenRequestURL = opts.oidcTokenRequestURL;
|
||||
this.#oidcTokenRequestToken = opts.oidcTokenRequestToken;
|
||||
const endpoints = this.#endpoints;
|
||||
for (const key of Object.keys(this.#endpoints) as Array<keyof typeof endpoints>) {
|
||||
this.#endpoints[key] = expandEndpoint(this.#endpoints[key], this.#universe);
|
||||
}
|
||||
this.#logger.debug(`Computed endpoints`, this.#endpoints);
|
||||
|
||||
this.#projectID =
|
||||
opts.projectID || this.extractProjectIDFromServiceAccountEmail(this.#serviceAccount);
|
||||
const iamHost = new URL(this.#endpoints.iam).host;
|
||||
this.#audience = `//${iamHost}/${this.#workloadIdentityProviderName}`;
|
||||
this.#logger.debug(`Computed audience`, this.#audience);
|
||||
}
|
||||
|
||||
/**
|
||||
* extractProjectIDFromServiceAccountEmail extracts the project ID from the
|
||||
* service account email address.
|
||||
* getToken gets a Google Cloud Federated Token that can call other Google
|
||||
* Cloud APIs directly or impersonate an existing Service Account. Direct
|
||||
* Workload Identity Federation will use the Federated Token directly.
|
||||
* Workload Identity Federation through a Service Account will use
|
||||
* impersonation.
|
||||
*/
|
||||
extractProjectIDFromServiceAccountEmail(str: string): string {
|
||||
if (!str) {
|
||||
return '';
|
||||
async getToken(): Promise<string> {
|
||||
const now = new Date().getTime();
|
||||
if (this.#cachedToken && this.#cachedAt && now - this.#cachedAt > 60_000) {
|
||||
this.#logger.debug(`Using cached token`);
|
||||
return this.#cachedToken;
|
||||
}
|
||||
|
||||
const [, dn] = str.split('@', 2);
|
||||
if (!str.endsWith('.iam.gserviceaccount.com')) {
|
||||
throw new Error(
|
||||
`Service account email ${str} is not of the form ` +
|
||||
`"[name]@[project].iam.gserviceaccount.com. You must manually ` +
|
||||
`specify the "project_id" parameter in your GitHub Actions workflow.`,
|
||||
);
|
||||
}
|
||||
const pth = `${this.#endpoints.sts}/token`;
|
||||
|
||||
const [project] = dn.split('.', 2);
|
||||
return project;
|
||||
}
|
||||
|
||||
/**
|
||||
* getAuthToken generates a Google Cloud federated token using the provided
|
||||
* OIDC token and Workload Identity Provider.
|
||||
*/
|
||||
async getAuthToken(): Promise<string> {
|
||||
const pth = `https://sts.googleapis.com/v1/token`;
|
||||
|
||||
const data = {
|
||||
audience: '//iam.googleapis.com/' + this.#providerID,
|
||||
grantType: 'urn:ietf:params:oauth:grant-type:token-exchange',
|
||||
requestedTokenType: 'urn:ietf:params:oauth:token-type:access_token',
|
||||
scope: 'https://www.googleapis.com/auth/cloud-platform',
|
||||
subjectTokenType: 'urn:ietf:params:oauth:token-type:jwt',
|
||||
subjectToken: this.#token,
|
||||
const body = {
|
||||
audience: this.#audience,
|
||||
grantType: `urn:ietf:params:oauth:grant-type:token-exchange`,
|
||||
requestedTokenType: `urn:ietf:params:oauth:token-type:access_token`,
|
||||
scope: `${this.#endpoints.www}/auth/cloud-platform`,
|
||||
subjectTokenType: `urn:ietf:params:oauth:token-type:jwt`,
|
||||
subjectToken: this.#githubOIDCToken,
|
||||
};
|
||||
|
||||
const headers = {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
this.#logger.withNamespace('getToken').debug({
|
||||
method: `POST`,
|
||||
path: pth,
|
||||
body: body,
|
||||
});
|
||||
|
||||
try {
|
||||
const resp = await this.client.request('POST', pth, JSON.stringify(data), headers);
|
||||
const body = await resp.readBody();
|
||||
const statusCode = resp.message.statusCode || 500;
|
||||
if (statusCode >= 400) {
|
||||
throw new Error(`(${statusCode}) ${body}`);
|
||||
const resp = await this.#httpClient.postJson<{ access_token: string }>(pth, body);
|
||||
const statusCode = resp.statusCode || 500;
|
||||
if (statusCode < 200 || statusCode > 299) {
|
||||
throw new Error(`Failed to call ${pth}: HTTP ${statusCode}: ${resp.result || '[no body]'}`);
|
||||
}
|
||||
const parsed = JSON.parse(body);
|
||||
return parsed['access_token'];
|
||||
|
||||
const result = resp.result;
|
||||
if (!result) {
|
||||
throw new Error(`Successfully called ${pth}, but the result was empty`);
|
||||
}
|
||||
|
||||
this.#cachedToken = result.access_token;
|
||||
this.#cachedAt = now;
|
||||
return result.access_token;
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`Failed to generate Google Cloud federated token for ${this.#providerID}: ${err}`,
|
||||
`Failed to generate Google Cloud federated token for ${this.#audience}: ${err}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* signJWT signs the given JWT using the IAM credentials endpoint.
|
||||
*
|
||||
* @param unsignedJWT The JWT to sign.
|
||||
* @param delegates List of service account email address to use for
|
||||
* impersonation in the delegation chain to sign the JWT.
|
||||
* signJWT signs a JWT using the Service Account's private key.
|
||||
*/
|
||||
async signJWT(unsignedJWT: string, delegates?: Array<string>): Promise<string> {
|
||||
const serviceAccount = await this.getServiceAccount();
|
||||
const federatedToken = await this.getAuthToken();
|
||||
|
||||
const pth = `https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${serviceAccount}:signJwt`;
|
||||
|
||||
const data: Record<string, string | Array<string>> = {
|
||||
payload: unsignedJWT,
|
||||
};
|
||||
if (delegates && delegates.length > 0) {
|
||||
data.delegates = delegates;
|
||||
async signJWT(claims: any): Promise<string> {
|
||||
if (!this.#serviceAccount) {
|
||||
throw new Error(`Cannot sign JWTs without specifying a service account`);
|
||||
}
|
||||
|
||||
const pth = `${this.#endpoints.iamcredentials}/projects/-/serviceAccounts/${this.#serviceAccount}:signJwt`;
|
||||
|
||||
const headers = {
|
||||
'Accept': 'application/json',
|
||||
'Authorization': `Bearer ${federatedToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${this.getToken()}`,
|
||||
};
|
||||
|
||||
const body = {
|
||||
payload: claims,
|
||||
};
|
||||
|
||||
this.#logger.withNamespace('signJWT').debug({
|
||||
method: `POST`,
|
||||
path: pth,
|
||||
headers: headers,
|
||||
body: body,
|
||||
});
|
||||
|
||||
try {
|
||||
const resp = await this.client.request('POST', pth, JSON.stringify(data), headers);
|
||||
const body = await resp.readBody();
|
||||
const statusCode = resp.message.statusCode || 500;
|
||||
if (statusCode >= 400) {
|
||||
throw new Error(`(${statusCode}) ${body}`);
|
||||
const resp = await this.#httpClient.postJson<{ signedJwt: string }>(pth, body, headers);
|
||||
const statusCode = resp.statusCode || 500;
|
||||
if (statusCode < 200 || statusCode > 299) {
|
||||
throw new Error(`Failed to call ${pth}: HTTP ${statusCode}: ${resp.result || '[no body]'}`);
|
||||
}
|
||||
const parsed = JSON.parse(body);
|
||||
return parsed['signedJwt'];
|
||||
|
||||
const result = resp.result;
|
||||
if (!result) {
|
||||
throw new Error(`Successfully called ${pth}, but the result was empty`);
|
||||
}
|
||||
return result.signedJwt;
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to sign JWT using ${serviceAccount}: ${err}`);
|
||||
throw new Error(`Failed to sign JWT using ${this.#serviceAccount}: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* getProjectID returns the project ID. If an override was given, the override
|
||||
* is returned. Otherwise, this will be the project ID that was extracted from
|
||||
* the service account key JSON.
|
||||
*/
|
||||
async getProjectID(): Promise<string> {
|
||||
return this.#projectID;
|
||||
}
|
||||
|
||||
/**
|
||||
* getServiceAccount returns the service account email for the authentication,
|
||||
* extracted from the input parameter.
|
||||
*/
|
||||
async getServiceAccount(): Promise<string> {
|
||||
return this.#serviceAccount;
|
||||
}
|
||||
|
||||
/**
|
||||
* createCredentialsFile creates a Google Cloud credentials file that can be
|
||||
* set as GOOGLE_APPLICATION_CREDENTIALS for gcloud and client libraries.
|
||||
* createCredentialsFile writes a Workload Identity Federation credential file
|
||||
* to disk at the specific outputPath.
|
||||
*/
|
||||
async createCredentialsFile(outputPath: string): Promise<string> {
|
||||
const requestURL = new URL(this.#oidcTokenRequestURL);
|
||||
const requestURL = new URL(this.#githubOIDCTokenRequestURL);
|
||||
|
||||
// Append the audience value to the request.
|
||||
const params = requestURL.searchParams;
|
||||
params.set('audience', this.#audience);
|
||||
params.set('audience', this.#githubOIDCTokenAudience);
|
||||
requestURL.search = params.toString();
|
||||
const data = {
|
||||
type: 'external_account',
|
||||
audience: `//iam.googleapis.com/${this.#providerID}`,
|
||||
subject_token_type: 'urn:ietf:params:oauth:token-type:jwt',
|
||||
token_url: 'https://sts.googleapis.com/v1/token',
|
||||
service_account_impersonation_url: `https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${this.#serviceAccount}:generateAccessToken`,
|
||||
|
||||
const data: Record<string, any> = {
|
||||
type: `external_account`,
|
||||
audience: this.#audience,
|
||||
subject_token_type: `urn:ietf:params:oauth:token-type:jwt`,
|
||||
token_url: `${this.#endpoints.sts}/token`,
|
||||
credential_source: {
|
||||
url: requestURL,
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.#oidcTokenRequestToken}`,
|
||||
Authorization: `Bearer ${this.#githubOIDCTokenRequestToken}`,
|
||||
},
|
||||
format: {
|
||||
type: 'json',
|
||||
subject_token_field_name: 'value',
|
||||
type: `json`,
|
||||
subject_token_field_name: `value`,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Only request impersonation if a service account was given, otherwise use
|
||||
// the WIF identity directly.
|
||||
if (this.#serviceAccount) {
|
||||
data.service_account_impersonation_url = `${this.#endpoints.iamcredentials}/projects/-/serviceAccounts/${this.#serviceAccount}:generateAccessToken`;
|
||||
}
|
||||
|
||||
this.#logger.withNamespace('createCredentialsFile').debug({ outputPath: outputPath });
|
||||
return await writeSecureFile(outputPath, JSON.stringify(data));
|
||||
}
|
||||
}
|
||||
|
122
src/logger.ts
Normal file
122
src/logger.ts
Normal file
@ -0,0 +1,122 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import {
|
||||
AnnotationProperties,
|
||||
debug as logDebug,
|
||||
error as logError,
|
||||
info as logInfo,
|
||||
notice as logNotice,
|
||||
warning as logWarning,
|
||||
} from '@actions/core';
|
||||
|
||||
/**
|
||||
* LoggerFunction is the type signature of a log function for the GitHub Actions
|
||||
* SDK.
|
||||
*/
|
||||
type LoggerFunction = (message: string, properties?: AnnotationProperties) => void;
|
||||
|
||||
/**
|
||||
* Logger is a class that handles namespaced logging.
|
||||
*/
|
||||
export class Logger {
|
||||
readonly #namespace?: string;
|
||||
|
||||
constructor(namespace?: string) {
|
||||
this.#namespace = namespace;
|
||||
}
|
||||
|
||||
withNamespace(namespace: string): Logger {
|
||||
const { constructor } = Object.getPrototypeOf(this);
|
||||
if (this.#namespace) {
|
||||
return new constructor(`${this.#namespace}.${namespace}`);
|
||||
}
|
||||
return new constructor(namespace);
|
||||
}
|
||||
|
||||
debug(...args: any[]) {
|
||||
this.logMessage(logDebug, ...args);
|
||||
}
|
||||
|
||||
error(...args: any[]) {
|
||||
this.logMessage(logError, ...args);
|
||||
}
|
||||
|
||||
info(...args: any[]) {
|
||||
this.logMessage(logInfo, ...args);
|
||||
}
|
||||
|
||||
notice(...args: any[]) {
|
||||
this.logMessage(logNotice, ...args);
|
||||
}
|
||||
|
||||
warning(...args: any[]) {
|
||||
this.logMessage(logWarning, ...args);
|
||||
}
|
||||
|
||||
protected logMessage(loggerFn: LoggerFunction, ...args: object[]) {
|
||||
if (!args || args.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let message = '';
|
||||
if (this.#namespace) {
|
||||
message += this.#namespace + ': ';
|
||||
}
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const obj = args[i];
|
||||
|
||||
if (typeof obj === 'undefined' || obj === undefined || obj === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeof obj === 'string' || obj instanceof String) {
|
||||
message += obj;
|
||||
} else {
|
||||
message += JSON.stringify(obj, null, 2);
|
||||
}
|
||||
|
||||
if (i < args.length - 1) {
|
||||
message += ', ';
|
||||
}
|
||||
}
|
||||
|
||||
loggerFn(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* NullLogger is a logger that doesn't actually emit any output.
|
||||
*/
|
||||
export class NullLogger extends Logger {
|
||||
debug(...args: any[]) {
|
||||
this.logMessage(() => {}, ...args);
|
||||
}
|
||||
|
||||
error(...args: any[]) {
|
||||
this.logMessage(() => {}, ...args);
|
||||
}
|
||||
|
||||
info(...args: any[]) {
|
||||
this.logMessage(() => {}, ...args);
|
||||
}
|
||||
|
||||
notice(...args: any[]) {
|
||||
this.logMessage(() => {}, ...args);
|
||||
}
|
||||
|
||||
warning(...args: any[]) {
|
||||
this.logMessage(() => {}, ...args);
|
||||
}
|
||||
}
|
202
src/main.ts
202
src/main.ts
@ -15,16 +15,13 @@
|
||||
import { join as pathjoin } from 'path';
|
||||
|
||||
import {
|
||||
debug as logDebug,
|
||||
exportVariable,
|
||||
getBooleanInput,
|
||||
getIDToken,
|
||||
getInput,
|
||||
info as logInfo,
|
||||
setFailed,
|
||||
setOutput,
|
||||
setSecret,
|
||||
warning as logWarning,
|
||||
} from '@actions/core';
|
||||
import {
|
||||
errorMessage,
|
||||
@ -37,10 +34,19 @@ import {
|
||||
withRetries,
|
||||
} from '@google-github-actions/actions-utils';
|
||||
|
||||
import { WorkloadIdentityClient } from './client/workload_identity_client';
|
||||
import { CredentialsJSONClient } from './client/credentials_json_client';
|
||||
import { AuthClient } from './client/auth_client';
|
||||
import { buildDomainWideDelegationJWT, generateCredentialsFilename } from './utils';
|
||||
import {
|
||||
AuthClient,
|
||||
IAMCredentialsClient,
|
||||
ServiceAccountKeyClient,
|
||||
WorkloadIdentityFederationClient,
|
||||
} from './base';
|
||||
import { Logger } from './logger';
|
||||
import {
|
||||
buildDomainWideDelegationJWT,
|
||||
computeProjectID,
|
||||
computeServiceAccountEmail,
|
||||
generateCredentialsFilename,
|
||||
} from './utils';
|
||||
|
||||
const secretsWarning =
|
||||
`If you are specifying input values via GitHub secrets, ensure the secret ` +
|
||||
@ -57,9 +63,11 @@ const oidcWarning =
|
||||
* Executes the main action.
|
||||
*/
|
||||
async function run(): Promise<void> {
|
||||
const logger = new Logger();
|
||||
|
||||
// Warn if pinned to HEAD
|
||||
if (isPinnedToHead()) {
|
||||
logWarning(pinnedToHeadWarning('v1'));
|
||||
logger.warning(pinnedToHeadWarning('v2'));
|
||||
}
|
||||
|
||||
const retries = Number(getInput('retries'));
|
||||
@ -69,7 +77,7 @@ async function run(): Promise<void> {
|
||||
const backoffLimit = Number(getInput('backoff_limit')) || undefined;
|
||||
|
||||
try {
|
||||
const mainWithRetries = withRetries(main, {
|
||||
const mainWithRetries = withRetries(async () => main(logger), {
|
||||
retries: retries,
|
||||
backoff: backoff,
|
||||
backoffLimit: backoffLimit,
|
||||
@ -85,17 +93,25 @@ async function run(): Promise<void> {
|
||||
/**
|
||||
* Main wraps the main action logic into a function to be used as a parameter to the withRetries function.
|
||||
*/
|
||||
async function main() {
|
||||
async function main(logger: Logger) {
|
||||
// Load configuration.
|
||||
const projectID = getInput('project_id');
|
||||
const workloadIdentityProvider = getInput('workload_identity_provider');
|
||||
const serviceAccount = getInput('service_account');
|
||||
const audience = getInput('audience') || `https://iam.googleapis.com/${workloadIdentityProvider}`;
|
||||
const credentialsJSON = getInput('credentials_json');
|
||||
const createCredentialsFile = getBooleanInput('create_credentials_file');
|
||||
const exportEnvironmentVariables = getBooleanInput('export_environment_variables');
|
||||
const tokenFormat = getInput('token_format');
|
||||
const delegates = parseCSV(getInput('delegates'));
|
||||
const projectID = computeProjectID(
|
||||
getInput(`project_id`),
|
||||
getInput(`service_account`),
|
||||
getInput(`credentials_json`),
|
||||
);
|
||||
const workloadIdentityProvider = getInput(`workload_identity_provider`);
|
||||
const serviceAccount = computeServiceAccountEmail(
|
||||
getInput(`service_account`),
|
||||
getInput('credentials_json'),
|
||||
);
|
||||
const oidcTokenAudience =
|
||||
getInput(`audience`) || `https://iam.googleapis.com/${workloadIdentityProvider}`;
|
||||
const credentialsJSON = getInput(`credentials_json`);
|
||||
const createCredentialsFile = getBooleanInput(`create_credentials_file`);
|
||||
const exportEnvironmentVariables = getBooleanInput(`export_environment_variables`);
|
||||
const tokenFormat = getInput(`token_format`);
|
||||
const delegates = parseCSV(getInput(`delegates`));
|
||||
|
||||
// Ensure exactly one of workload_identity_provider and credentials_json was
|
||||
// provided.
|
||||
@ -107,19 +123,10 @@ async function main() {
|
||||
);
|
||||
}
|
||||
|
||||
// Ensure a service_account was provided if using WIF.
|
||||
if (workloadIdentityProvider && !serviceAccount) {
|
||||
throw new Error(
|
||||
'The GitHub Action workflow must specify a "service_account" to ' +
|
||||
'impersonate when using "workload_identity_provider"! ' +
|
||||
secretsWarning,
|
||||
);
|
||||
}
|
||||
|
||||
// Instantiate the correct client based on the provided input parameters.
|
||||
let client: AuthClient;
|
||||
if (workloadIdentityProvider) {
|
||||
logDebug(`Using workload identity provider "${workloadIdentityProvider}"`);
|
||||
logger.debug(`Using workload identity provider "${workloadIdentityProvider}"`);
|
||||
|
||||
// If we're going to do the OIDC dance, we need to make sure these values
|
||||
// are set. If they aren't, core.getIDToken() will fail and so will
|
||||
@ -130,21 +137,19 @@ async function main() {
|
||||
throw new Error(oidcWarning);
|
||||
}
|
||||
|
||||
const token = await getIDToken(audience);
|
||||
client = new WorkloadIdentityClient({
|
||||
projectID: projectID,
|
||||
providerID: workloadIdentityProvider,
|
||||
const oidcToken = await getIDToken(oidcTokenAudience);
|
||||
client = new WorkloadIdentityFederationClient(logger, {
|
||||
githubOIDCToken: oidcToken,
|
||||
githubOIDCTokenRequestURL: oidcTokenRequestURL,
|
||||
githubOIDCTokenRequestToken: oidcTokenRequestToken,
|
||||
githubOIDCTokenAudience: oidcTokenAudience,
|
||||
workloadIdentityProviderName: workloadIdentityProvider,
|
||||
serviceAccount: serviceAccount,
|
||||
token: token,
|
||||
audience: audience,
|
||||
oidcTokenRequestToken: oidcTokenRequestToken,
|
||||
oidcTokenRequestURL: oidcTokenRequestURL,
|
||||
});
|
||||
} else {
|
||||
logDebug(`Using credentials JSON`);
|
||||
client = new CredentialsJSONClient({
|
||||
projectID: projectID,
|
||||
credentialsJSON: credentialsJSON,
|
||||
logger.debug(`Using credentials JSON`);
|
||||
client = new ServiceAccountKeyClient(logger, {
|
||||
serviceAccountKey: credentialsJSON,
|
||||
});
|
||||
}
|
||||
|
||||
@ -153,7 +158,7 @@ async function main() {
|
||||
// fails, which means continue-on-error actions will still have the file
|
||||
// available.
|
||||
if (createCredentialsFile) {
|
||||
logDebug(`Creating credentials file`);
|
||||
logger.debug(`Creating credentials file`);
|
||||
|
||||
// Note: We explicitly and intentionally export to GITHUB_WORKSPACE
|
||||
// instead of RUNNER_TEMP, because RUNNER_TEMP is not shared with
|
||||
@ -180,7 +185,7 @@ async function main() {
|
||||
// repository.
|
||||
const githubWorkspaceIsEmpty = await isEmptyDir(githubWorkspace);
|
||||
if (githubWorkspaceIsEmpty) {
|
||||
logWarning(
|
||||
logger.warning(
|
||||
`The "create_credentials_file" option is true, but the current ` +
|
||||
`GitHub workspace is empty. Did you forget to use ` +
|
||||
`"actions/checkout" before this step? If you do not intend to ` +
|
||||
@ -193,7 +198,7 @@ async function main() {
|
||||
const outputFile = generateCredentialsFilename();
|
||||
const outputPath = pathjoin(githubWorkspace, outputFile);
|
||||
const credentialsPath = await client.createCredentialsFile(outputPath);
|
||||
logInfo(`Created credentials file at "${credentialsPath}"`);
|
||||
logger.info(`Created credentials file at "${credentialsPath}"`);
|
||||
|
||||
// Output to be available to future steps.
|
||||
setOutput('credentials_file_path', credentialsPath);
|
||||
@ -202,29 +207,48 @@ async function main() {
|
||||
// CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE is picked up by gcloud to
|
||||
// use a specific credential file (subject to change and equivalent to
|
||||
// auth/credential_file_override).
|
||||
exportVariableAndWarn('CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE', credentialsPath);
|
||||
exportVariable('CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE', credentialsPath);
|
||||
|
||||
// GOOGLE_APPLICATION_CREDENTIALS is used by Application Default
|
||||
// Credentials in all GCP client libraries.
|
||||
exportVariableAndWarn('GOOGLE_APPLICATION_CREDENTIALS', credentialsPath);
|
||||
exportVariable('GOOGLE_APPLICATION_CREDENTIALS', credentialsPath);
|
||||
|
||||
// GOOGLE_GHA_CREDS_PATH is used by other Google GitHub Actions.
|
||||
exportVariableAndWarn('GOOGLE_GHA_CREDS_PATH', credentialsPath);
|
||||
exportVariable('GOOGLE_GHA_CREDS_PATH', credentialsPath);
|
||||
}
|
||||
}
|
||||
|
||||
// Set the project ID environment variables to the computed values.
|
||||
const computedProjectID = await client.getProjectID();
|
||||
setOutput('project_id', computedProjectID);
|
||||
if (!projectID) {
|
||||
logger.warning(
|
||||
`Unable to compute project ID from inputs, skipping export. Please ` +
|
||||
`specify the "project_id" input directly.`,
|
||||
);
|
||||
} else {
|
||||
setOutput('project_id', projectID);
|
||||
|
||||
if (exportEnvironmentVariables) {
|
||||
exportVariableAndWarn('CLOUDSDK_CORE_PROJECT', computedProjectID);
|
||||
exportVariableAndWarn('CLOUDSDK_PROJECT', computedProjectID);
|
||||
exportVariableAndWarn('GCLOUD_PROJECT', computedProjectID);
|
||||
exportVariableAndWarn('GCP_PROJECT', computedProjectID);
|
||||
exportVariableAndWarn('GOOGLE_CLOUD_PROJECT', computedProjectID);
|
||||
if (exportEnvironmentVariables) {
|
||||
exportVariable('CLOUDSDK_CORE_PROJECT', projectID);
|
||||
exportVariable('CLOUDSDK_PROJECT', projectID);
|
||||
exportVariable('GCLOUD_PROJECT', projectID);
|
||||
exportVariable('GCP_PROJECT', projectID);
|
||||
exportVariable('GOOGLE_CLOUD_PROJECT', projectID);
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to generate a token. This will ensure the action correctly errors
|
||||
// if the credentials are misconfigured. This is also required so the value
|
||||
// can be set as an output for future authentication calls.
|
||||
const authToken = await client.getToken();
|
||||
logger.debug(`Successfully generated auth token`);
|
||||
setSecret(authToken);
|
||||
setOutput('auth_token', authToken);
|
||||
|
||||
// Create the credential client, we might not use it, but it's basically free.
|
||||
const iamCredentialsClient = new IAMCredentialsClient(logger, {
|
||||
authToken: authToken,
|
||||
});
|
||||
|
||||
switch (tokenFormat) {
|
||||
case '': {
|
||||
break;
|
||||
@ -233,20 +257,28 @@ async function main() {
|
||||
break;
|
||||
}
|
||||
case 'access_token': {
|
||||
logDebug(`Creating access token`);
|
||||
logger.debug(`Creating access token`);
|
||||
|
||||
const accessTokenLifetime = parseDuration(getInput('access_token_lifetime'));
|
||||
const accessTokenScopes = parseCSV(getInput('access_token_scopes'));
|
||||
const accessTokenSubject = getInput('access_token_subject');
|
||||
const serviceAccount = await client.getServiceAccount();
|
||||
|
||||
// Ensure a service_account was provided if using WIF.
|
||||
if (!serviceAccount) {
|
||||
throw new Error(
|
||||
'The GitHub Action workflow must specify a "service_account" to ' +
|
||||
'use when generating an OAuth 2.0 Access Token. ' +
|
||||
secretsWarning,
|
||||
);
|
||||
}
|
||||
|
||||
// If a subject was provided, use the traditional OAuth 2.0 flow to
|
||||
// perform Domain-Wide Delegation. Otherwise, use the modern IAM
|
||||
// Credentials endpoints.
|
||||
let accessToken, expiration;
|
||||
let accessToken;
|
||||
if (accessTokenSubject) {
|
||||
if (accessTokenLifetime > 3600) {
|
||||
logInfo(
|
||||
logger.info(
|
||||
`An access token subject was specified, triggering Domain-Wide ` +
|
||||
`Delegation flow. This flow does not support specifying an ` +
|
||||
`access token lifetime of greater than 1 hour.`,
|
||||
@ -259,39 +291,45 @@ async function main() {
|
||||
accessTokenScopes,
|
||||
accessTokenLifetime,
|
||||
);
|
||||
const signedJWT = await client.signJWT(unsignedJWT, delegates);
|
||||
({ accessToken, expiration } = await client.googleOAuthToken(signedJWT));
|
||||
const signedJWT = await client.signJWT(unsignedJWT);
|
||||
|
||||
accessToken = await iamCredentialsClient.generateDomainWideDelegationAccessToken(signedJWT);
|
||||
} else {
|
||||
const authToken = await client.getAuthToken();
|
||||
({ accessToken, expiration } = await client.googleAccessToken(authToken, {
|
||||
accessToken = await iamCredentialsClient.generateAccessToken({
|
||||
serviceAccount,
|
||||
delegates,
|
||||
scopes: accessTokenScopes,
|
||||
lifetime: accessTokenLifetime,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
setSecret(accessToken);
|
||||
setOutput('access_token', accessToken);
|
||||
setOutput('access_token_expiration', expiration);
|
||||
break;
|
||||
}
|
||||
case 'id_token': {
|
||||
logDebug(`Creating id token`);
|
||||
logger.debug(`Creating id token`);
|
||||
|
||||
const idTokenAudience = getInput('id_token_audience', { required: true });
|
||||
const idTokenIncludeEmail = getBooleanInput('id_token_include_email');
|
||||
const serviceAccount = await client.getServiceAccount();
|
||||
|
||||
const authToken = await client.getAuthToken();
|
||||
const { token } = await client.googleIDToken(authToken, {
|
||||
// Ensure a service_account was provided if using WIF.
|
||||
if (!serviceAccount) {
|
||||
throw new Error(
|
||||
'The GitHub Action workflow must specify a "service_account" to ' +
|
||||
'use when generating an OAuth 2.0 Access Token. ' +
|
||||
secretsWarning,
|
||||
);
|
||||
}
|
||||
|
||||
const idToken = await iamCredentialsClient.generateIDToken({
|
||||
serviceAccount,
|
||||
audience: idTokenAudience,
|
||||
delegates,
|
||||
includeEmail: idTokenIncludeEmail,
|
||||
});
|
||||
setSecret(token);
|
||||
setOutput('id_token', token);
|
||||
setSecret(idToken);
|
||||
setOutput('id_token', idToken);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
@ -300,26 +338,4 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* exportVariableAndWarn exports the given key as an environment variable set to
|
||||
* the provided value. If a value already exists, it is overwritten and an
|
||||
* warning is emitted.
|
||||
*
|
||||
* @param key Environment variable key.
|
||||
* @param value Environment variable value.
|
||||
*/
|
||||
function exportVariableAndWarn(key: string, value: string) {
|
||||
const existing = process.env[key];
|
||||
if (existing && existing !== value) {
|
||||
logWarning(
|
||||
`Overwriting existing environment variable ${key}:
|
||||
- ${JSON.stringify(existing)}
|
||||
+ ${JSON.stringify(value)}
|
||||
`.trim(),
|
||||
);
|
||||
}
|
||||
|
||||
exportVariable(key, value);
|
||||
}
|
||||
|
||||
run();
|
||||
|
63
src/post.ts
63
src/post.ts
@ -12,43 +12,52 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { getBooleanInput, setFailed, info as logInfo } from '@actions/core';
|
||||
import { getBooleanInput, setFailed } from '@actions/core';
|
||||
|
||||
import { errorMessage, forceRemove } from '@google-github-actions/actions-utils';
|
||||
|
||||
import { Logger } from './logger';
|
||||
|
||||
/**
|
||||
* Executes the post action, documented inline.
|
||||
*/
|
||||
export async function run(): Promise<void> {
|
||||
export async function run() {
|
||||
const logger = new Logger();
|
||||
|
||||
try {
|
||||
const createCredentials = getBooleanInput('create_credentials_file');
|
||||
if (!createCredentials) {
|
||||
logInfo(`Skipping credential cleanup - "create_credentials_file" is false.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const cleanupCredentials = getBooleanInput('cleanup_credentials');
|
||||
if (!cleanupCredentials) {
|
||||
logInfo(`Skipping credential cleanup - "cleanup_credentials" is false.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Look up the credentials path, if one exists. Note that we only check the
|
||||
// environment variable set by our action, since we don't want to
|
||||
// accidentially clean up if someone set GOOGLE_APPLICATION_CREDENTIALS or
|
||||
// another environment variable manually.
|
||||
const credentialsPath = process.env['GOOGLE_GHA_CREDS_PATH'];
|
||||
if (!credentialsPath) {
|
||||
logInfo(`Skipping credential cleanup - $GOOGLE_GHA_CREDS_PATH is not set.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the file.
|
||||
await forceRemove(credentialsPath);
|
||||
logInfo(`Removed exported credentials at "${credentialsPath}".`);
|
||||
main(logger);
|
||||
} catch (err) {
|
||||
const msg = errorMessage(err);
|
||||
setFailed(`google-github-actions/auth post failed with: ${msg}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function main(logger: Logger) {
|
||||
const createCredentials = getBooleanInput('create_credentials_file');
|
||||
if (!createCredentials) {
|
||||
logger.info(`Skipping credential cleanup - "create_credentials_file" is false.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const cleanupCredentials = getBooleanInput('cleanup_credentials');
|
||||
if (!cleanupCredentials) {
|
||||
logger.info(`Skipping credential cleanup - "cleanup_credentials" is false.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Look up the credentials path, if one exists. Note that we only check the
|
||||
// environment variable set by our action, since we don't want to
|
||||
// accidentially clean up if someone set GOOGLE_APPLICATION_CREDENTIALS or
|
||||
// another environment variable manually.
|
||||
const credentialsPath = process.env['GOOGLE_GHA_CREDS_PATH'];
|
||||
if (!credentialsPath) {
|
||||
logger.info(`Skipping credential cleanup - $GOOGLE_GHA_CREDS_PATH is not set.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the file.
|
||||
await forceRemove(credentialsPath);
|
||||
logger.info(`Removed exported credentials at "${credentialsPath}".`);
|
||||
}
|
||||
|
||||
run();
|
||||
|
93
src/utils.ts
93
src/utils.ts
@ -12,7 +12,18 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { randomFilename } from '@google-github-actions/actions-utils';
|
||||
import {
|
||||
isServiceAccountKey,
|
||||
parseCredential,
|
||||
randomFilename,
|
||||
} from '@google-github-actions/actions-utils';
|
||||
|
||||
// Do not listen to the linter - this can NOT be rewritten as an ES6 import statement.
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
export const { version: appVersion } = require('../package.json');
|
||||
|
||||
// userAgent is the default user agent.
|
||||
export const userAgent = `google-github-actions:auth/${appVersion}`;
|
||||
|
||||
/**
|
||||
* buildDomainWideDelegationJWT constructs an _unsigned_ JWT to be used for a
|
||||
@ -50,6 +61,86 @@ export function buildDomainWideDelegationJWT(
|
||||
return JSON.stringify(body);
|
||||
}
|
||||
|
||||
/**
|
||||
* computeProjectID attempts to compute the best project ID from the given
|
||||
* inputs.
|
||||
*/
|
||||
export function computeProjectID(
|
||||
projectID?: string,
|
||||
serviceAccount?: string,
|
||||
serviceAccountKeyJSON?: string,
|
||||
): string | undefined {
|
||||
if (projectID) {
|
||||
return projectID;
|
||||
}
|
||||
|
||||
// sa-name@<project-id>.iam.gserviceaccount.com
|
||||
const fromEmail = projectIDFromServiceAccountEmail(serviceAccount);
|
||||
if (fromEmail) {
|
||||
return fromEmail;
|
||||
}
|
||||
|
||||
// Extract from the key
|
||||
if (serviceAccountKeyJSON) {
|
||||
const credential = parseCredential(serviceAccountKeyJSON);
|
||||
if (isServiceAccountKey(credential) && credential.project_id) {
|
||||
return credential.project_id;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* getServiceAccountEmail extracts the service account email from the given
|
||||
* fields.
|
||||
*/
|
||||
export function computeServiceAccountEmail(
|
||||
serviceAccountEmail?: string,
|
||||
serviceAccountKeyJSON?: string,
|
||||
): string | undefined {
|
||||
if (serviceAccountEmail) {
|
||||
return serviceAccountEmail;
|
||||
}
|
||||
|
||||
if (serviceAccountKeyJSON) {
|
||||
const credential = parseCredential(serviceAccountKeyJSON);
|
||||
if (isServiceAccountKey(credential) && credential.client_email) {
|
||||
return credential.client_email;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* projectIDFromServiceAccountEmail attempts to extract the project ID from the
|
||||
* service account email.
|
||||
*/
|
||||
export function projectIDFromServiceAccountEmail(serviceAccount?: string): string | null {
|
||||
if (!serviceAccount) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const emailParts = serviceAccount.split('@');
|
||||
if (emailParts.length !== 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const addressParts = emailParts[1].split('.');
|
||||
if (addressParts.length < 2) {
|
||||
return null;
|
||||
}
|
||||
return addressParts[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* expandEndpoint expands the input url relative to the universe.
|
||||
*/
|
||||
export function expandEndpoint(input: string, universe: string): string {
|
||||
return (input || '').replace(/{universe}/g, universe).replace(/\/+$/, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* generateCredentialsFilename creates a predictable filename under which
|
||||
* credentials are written. This string is the filename, not the filepath. It must match the format:
|
||||
|
@ -21,7 +21,8 @@ import { tmpdir } from 'os';
|
||||
|
||||
import { randomFilename } from '@google-github-actions/actions-utils';
|
||||
|
||||
import { CredentialsJSONClient } from '../../src/client/credentials_json_client';
|
||||
import { NullLogger } from '../../src/logger';
|
||||
import { ServiceAccountKeyClient } from '../../src/client/credentials_json_client';
|
||||
|
||||
// Yes, this is a real private key. No, it's not valid for authenticating
|
||||
// Google Cloud.
|
||||
@ -40,40 +41,40 @@ const credentialsJSON = `
|
||||
}
|
||||
`;
|
||||
|
||||
describe('CredentialsJSONClient', () => {
|
||||
describe('ServiceAccountKeyClient', () => {
|
||||
describe('#parseServiceAccountKeyJSON', () => {
|
||||
it('throws exception on invalid json', async () => {
|
||||
assert.rejects(async () => {
|
||||
new CredentialsJSONClient({
|
||||
credentialsJSON: 'invalid json',
|
||||
new ServiceAccountKeyClient(new NullLogger(), {
|
||||
serviceAccountKey: 'invalid json',
|
||||
});
|
||||
}, SyntaxError);
|
||||
});
|
||||
|
||||
it('handles base64', async () => {
|
||||
assert.rejects(async () => {
|
||||
new CredentialsJSONClient({
|
||||
credentialsJSON: 'base64',
|
||||
new ServiceAccountKeyClient(new NullLogger(), {
|
||||
serviceAccountKey: 'base64',
|
||||
});
|
||||
}, SyntaxError);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getAuthToken', () => {
|
||||
it('signs a jwt', async () => {
|
||||
const client = new CredentialsJSONClient({
|
||||
credentialsJSON: credentialsJSON,
|
||||
describe('#getToken', () => {
|
||||
it('gets a token', async () => {
|
||||
const client = new ServiceAccountKeyClient(new NullLogger(), {
|
||||
serviceAccountKey: credentialsJSON,
|
||||
});
|
||||
|
||||
const token = await client.getAuthToken();
|
||||
const token = await client.getToken();
|
||||
assert.ok(token);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#signJWT', () => {
|
||||
it('signs a jwt', async () => {
|
||||
const client = new CredentialsJSONClient({
|
||||
credentialsJSON: credentialsJSON,
|
||||
const client = new ServiceAccountKeyClient(new NullLogger(), {
|
||||
serviceAccountKey: credentialsJSON,
|
||||
});
|
||||
|
||||
const token = await client.signJWT('thisismy.jwt');
|
||||
@ -81,43 +82,11 @@ describe('CredentialsJSONClient', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getProjectID', () => {
|
||||
it('extracts project ID from the json', async () => {
|
||||
const client = new CredentialsJSONClient({
|
||||
credentialsJSON: credentialsJSON,
|
||||
});
|
||||
|
||||
const result = await client.getProjectID();
|
||||
assert.deepStrictEqual(result, 'my-project');
|
||||
});
|
||||
|
||||
it('prefers the override if given', async () => {
|
||||
const client = new CredentialsJSONClient({
|
||||
projectID: 'my-other-project',
|
||||
credentialsJSON: credentialsJSON,
|
||||
});
|
||||
|
||||
const result = await client.getProjectID();
|
||||
assert.deepStrictEqual(result, 'my-other-project');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getServiceAccount', () => {
|
||||
it('extracts service account from the json', async () => {
|
||||
const client = new CredentialsJSONClient({
|
||||
credentialsJSON: credentialsJSON,
|
||||
});
|
||||
|
||||
const result = await client.getServiceAccount();
|
||||
assert.deepStrictEqual(result, 'my-service-account@my-project.iam.gserviceaccount.com');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#createCredentialsFile', () => {
|
||||
it('writes the file', async () => {
|
||||
const outputFile = pathjoin(tmpdir(), randomFilename());
|
||||
const client = new CredentialsJSONClient({
|
||||
credentialsJSON: credentialsJSON,
|
||||
const client = new ServiceAccountKeyClient(new NullLogger(), {
|
||||
serviceAccountKey: credentialsJSON,
|
||||
});
|
||||
|
||||
const exp = JSON.parse(credentialsJSON);
|
||||
|
@ -21,80 +21,54 @@ import { readFileSync } from 'fs';
|
||||
|
||||
import { randomFilename } from '@google-github-actions/actions-utils';
|
||||
|
||||
import { WorkloadIdentityClient } from '../../src/client/workload_identity_client';
|
||||
|
||||
describe('WorkloadIdentityClient', () => {
|
||||
describe('#getProjectID', () => {
|
||||
it('extracts project ID from the service account email', async () => {
|
||||
const client = new WorkloadIdentityClient({
|
||||
providerID: 'my-provider',
|
||||
token: 'my-token',
|
||||
serviceAccount: 'my-service@my-project.iam.gserviceaccount.com',
|
||||
audience: 'my-aud',
|
||||
oidcTokenRequestURL: 'https://example.com/',
|
||||
oidcTokenRequestToken: 'token',
|
||||
});
|
||||
|
||||
const result = await client.getProjectID();
|
||||
assert.deepStrictEqual(result, 'my-project');
|
||||
});
|
||||
|
||||
it('prefers the override if given', async () => {
|
||||
const client = new WorkloadIdentityClient({
|
||||
projectID: 'my-other-project',
|
||||
providerID: 'my-provider',
|
||||
token: 'my-token',
|
||||
serviceAccount: 'my-service@my-project.iam.gserviceaccount.com',
|
||||
audience: 'my-aud',
|
||||
oidcTokenRequestURL: 'https://example.com/',
|
||||
oidcTokenRequestToken: 'token',
|
||||
});
|
||||
|
||||
const result = await client.getProjectID();
|
||||
assert.deepStrictEqual(result, 'my-other-project');
|
||||
});
|
||||
|
||||
it('throws an error when extraction fails', async () => {
|
||||
assert.rejects(async () => {
|
||||
return new WorkloadIdentityClient({
|
||||
providerID: 'my-provider',
|
||||
token: 'my-token',
|
||||
serviceAccount: 'my-service@developers.google.com',
|
||||
audience: 'my-aud',
|
||||
oidcTokenRequestURL: 'https://example.com/',
|
||||
oidcTokenRequestToken: 'token',
|
||||
});
|
||||
}, Error);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getServiceAccount', () => {
|
||||
it('returns the provided value', async () => {
|
||||
const client = new WorkloadIdentityClient({
|
||||
projectID: 'my-project',
|
||||
providerID: 'my-provider',
|
||||
serviceAccount: 'my-service@my-project.iam.gserviceaccount.com',
|
||||
token: 'my-token',
|
||||
audience: 'my-aud',
|
||||
oidcTokenRequestURL: 'https://example.com/',
|
||||
oidcTokenRequestToken: 'token',
|
||||
});
|
||||
const result = await client.getServiceAccount();
|
||||
assert.deepStrictEqual(result, 'my-service@my-project.iam.gserviceaccount.com');
|
||||
});
|
||||
});
|
||||
import { NullLogger } from '../../src/logger';
|
||||
import { WorkloadIdentityFederationClient } from '../../src/client/workload_identity_client';
|
||||
|
||||
describe('WorkloadIdentityFederationClient', () => {
|
||||
describe('#createCredentialsFile', () => {
|
||||
it('writes the file', async () => {
|
||||
const outputFile = pathjoin(tmpdir(), randomFilename());
|
||||
const client = new WorkloadIdentityClient({
|
||||
projectID: 'my-project',
|
||||
providerID: 'my-provider',
|
||||
const client = new WorkloadIdentityFederationClient(new NullLogger(), {
|
||||
githubOIDCToken: 'my-token',
|
||||
githubOIDCTokenRequestURL: 'https://example.com/',
|
||||
githubOIDCTokenRequestToken: 'token',
|
||||
githubOIDCTokenAudience: 'my-aud',
|
||||
workloadIdentityProviderName: 'my-provider',
|
||||
});
|
||||
|
||||
const exp = {
|
||||
audience: '//iam.googleapis.com/my-provider',
|
||||
credential_source: {
|
||||
format: {
|
||||
subject_token_field_name: 'value',
|
||||
type: 'json',
|
||||
},
|
||||
headers: {
|
||||
Authorization: 'Bearer token',
|
||||
},
|
||||
url: 'https://example.com/?audience=my-aud',
|
||||
},
|
||||
subject_token_type: 'urn:ietf:params:oauth:token-type:jwt',
|
||||
token_url: 'https://sts.googleapis.com/v1/token',
|
||||
type: 'external_account',
|
||||
};
|
||||
|
||||
const pth = await client.createCredentialsFile(outputFile);
|
||||
const data = readFileSync(pth);
|
||||
const got = JSON.parse(data.toString('utf8'));
|
||||
|
||||
assert.deepStrictEqual(got, exp);
|
||||
});
|
||||
|
||||
it('writes the file with impersonation', async () => {
|
||||
const outputFile = pathjoin(tmpdir(), randomFilename());
|
||||
const client = new WorkloadIdentityFederationClient(new NullLogger(), {
|
||||
githubOIDCToken: 'my-token',
|
||||
githubOIDCTokenRequestURL: 'https://example.com/',
|
||||
githubOIDCTokenRequestToken: 'token',
|
||||
githubOIDCTokenAudience: 'my-aud',
|
||||
workloadIdentityProviderName: 'my-provider',
|
||||
serviceAccount: 'my-service@my-project.iam.gserviceaccount.com',
|
||||
token: 'my-token',
|
||||
audience: 'my-aud',
|
||||
oidcTokenRequestURL: 'https://example.com/',
|
||||
oidcTokenRequestToken: 'token',
|
||||
});
|
||||
|
||||
const exp = {
|
||||
|
@ -15,10 +15,17 @@
|
||||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert';
|
||||
|
||||
import { buildDomainWideDelegationJWT, generateCredentialsFilename } from '../src/utils';
|
||||
import {
|
||||
buildDomainWideDelegationJWT,
|
||||
computeProjectID,
|
||||
computeServiceAccountEmail,
|
||||
expandEndpoint,
|
||||
generateCredentialsFilename,
|
||||
projectIDFromServiceAccountEmail,
|
||||
} from '../src/utils';
|
||||
|
||||
describe('Utils', () => {
|
||||
describe('#buildDomainWideDelegationJWT', () => {
|
||||
describe('Utils', async () => {
|
||||
describe('#buildDomainWideDelegationJWT', async () => {
|
||||
const cases = [
|
||||
{
|
||||
name: 'default',
|
||||
@ -57,7 +64,152 @@ describe('Utils', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#generateCredentialsFilename', () => {
|
||||
describe('#computeProjectID', async () => {
|
||||
const cases = [
|
||||
{
|
||||
name: 'directly given',
|
||||
projectID: 'my-project',
|
||||
exp: 'my-project',
|
||||
},
|
||||
{
|
||||
name: 'from service account email',
|
||||
serviceAccountEmail: 'my-account@my-project.iam.gserviceaccount.com',
|
||||
exp: 'my-project',
|
||||
},
|
||||
{
|
||||
name: 'from json credential',
|
||||
serviceAccountKeyJSON: '{"type":"service_account", "project_id": "my-project"}',
|
||||
exp: 'my-project',
|
||||
},
|
||||
{
|
||||
name: 'from json credential invalid',
|
||||
serviceAccountKeyJSON: '{"nope": "foo@bar.com"}',
|
||||
exp: undefined,
|
||||
},
|
||||
];
|
||||
|
||||
cases.forEach(async (tc) => {
|
||||
it(tc.name, async () => {
|
||||
const result = computeProjectID(
|
||||
tc.projectID,
|
||||
tc.serviceAccountEmail,
|
||||
tc.serviceAccountKeyJSON,
|
||||
);
|
||||
assert.deepStrictEqual(result, tc.exp);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#computeServiceAccountEmail', async () => {
|
||||
const cases = [
|
||||
{
|
||||
name: 'directly given',
|
||||
serviceAccountEmail: 'foo@bar.com',
|
||||
exp: 'foo@bar.com',
|
||||
},
|
||||
{
|
||||
name: 'from json credential',
|
||||
serviceAccountKeyJSON: '{"type":"service_account", "client_email": "foo@bar.com"}',
|
||||
exp: 'foo@bar.com',
|
||||
},
|
||||
{
|
||||
name: 'invalid json credential',
|
||||
serviceAccountKeyJSON: '{"nope": "foo@bar.com"}',
|
||||
exp: undefined,
|
||||
},
|
||||
{
|
||||
name: 'nothing',
|
||||
exp: undefined,
|
||||
},
|
||||
];
|
||||
|
||||
cases.forEach(async (tc) => {
|
||||
it(tc.name, async () => {
|
||||
const result = computeServiceAccountEmail(tc.serviceAccountEmail, tc.serviceAccountKeyJSON);
|
||||
assert.deepStrictEqual(result, tc.exp);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#projectIDFromServiceAccountEmail', async () => {
|
||||
const cases = [
|
||||
{
|
||||
name: 'empty',
|
||||
input: '',
|
||||
exp: null,
|
||||
},
|
||||
{
|
||||
name: 'not an email',
|
||||
input: 'not a service account',
|
||||
exp: null,
|
||||
},
|
||||
{
|
||||
name: 'invalid email',
|
||||
input: 'foo@abc',
|
||||
exp: null,
|
||||
},
|
||||
{
|
||||
name: 'returns project',
|
||||
input: 'test-sa@my-project.iam.gserviceaccount.com',
|
||||
exp: 'my-project',
|
||||
},
|
||||
];
|
||||
|
||||
cases.forEach(async (tc) => {
|
||||
it(tc.name, async () => {
|
||||
const result = projectIDFromServiceAccountEmail(tc.input);
|
||||
assert.deepStrictEqual(result, tc.exp);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#expandEndpoint', async () => {
|
||||
const cases = [
|
||||
{
|
||||
name: 'empty',
|
||||
endpoint: '',
|
||||
universe: '',
|
||||
exp: '',
|
||||
},
|
||||
{
|
||||
name: 'no match',
|
||||
endpoint: 'https://www.googleapis.com',
|
||||
universe: 'foobar',
|
||||
exp: 'https://www.googleapis.com',
|
||||
},
|
||||
{
|
||||
name: 'removes trailing slash',
|
||||
endpoint: 'https://www.googleapis.com/',
|
||||
exp: 'https://www.googleapis.com',
|
||||
},
|
||||
{
|
||||
name: 'removes trailing slashes',
|
||||
endpoint: 'https://www.googleapis.com/////',
|
||||
exp: 'https://www.googleapis.com',
|
||||
},
|
||||
{
|
||||
name: 'replaces {universe}',
|
||||
endpoint: 'https://www.{universe}',
|
||||
universe: 'foo.bar',
|
||||
exp: 'https://www.foo.bar',
|
||||
},
|
||||
{
|
||||
name: 'replaces multiple {universe}',
|
||||
endpoint: 'https://www.{universe}.{universe}',
|
||||
universe: 'foo.bar',
|
||||
exp: 'https://www.foo.bar.foo.bar',
|
||||
},
|
||||
];
|
||||
|
||||
cases.forEach(async (tc) => {
|
||||
it(tc.name, async () => {
|
||||
const result = expandEndpoint(tc.endpoint, tc.universe || '');
|
||||
assert.deepStrictEqual(result, tc.exp);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#generateCredentialsFilename', async () => {
|
||||
it('returns a string matching the regex', () => {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const filename = generateCredentialsFilename();
|
||||
|
Loading…
Reference in New Issue
Block a user