diff --git a/.drone.yml b/.drone.yml index eb044e125850d..726d0cf74e2d6 100644 --- a/.drone.yml +++ b/.drone.yml @@ -39,7 +39,7 @@ name: push-build-linux-amd64 environment: BUILDBOX_VERSION: teleport13 GID: "1000" - RUNTIME: go1.20.2 + RUNTIME: go1.20.3 UID: "1000" trigger: event: @@ -61,6 +61,7 @@ clone: steps: - name: Check out code image: docker:git + pull: if-not-exists commands: - mkdir -pv "/go/src/github.com/gravitational/teleport" - cd "/go/src/github.com/gravitational/teleport" @@ -79,13 +80,23 @@ steps: from_secret: GITHUB_PRIVATE_KEY - name: Wait for docker image: docker + pull: if-not-exists commands: - timeout 30s /bin/sh -c 'while [ ! -S /var/run/docker.sock ]; do sleep 1; done' + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin + environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: Build artifacts image: docker + pull: if-not-exists commands: - apk add --no-cache make - chown -R $UID:$GID /go @@ -103,6 +114,8 @@ steps: volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: Send Slack notification image: plugins/slack settings: @@ -130,6 +143,10 @@ services: volumes: - name: dockersock temp: {} +- name: dockerconfig + temp: {} +image_pull_secrets: +- DOCKERHUB_CREDENTIALS --- ################################################ @@ -144,7 +161,7 @@ name: push-build-linux-386 environment: BUILDBOX_VERSION: teleport13 GID: "1000" - RUNTIME: go1.20.2 + RUNTIME: go1.20.3 UID: "1000" trigger: event: @@ -166,6 +183,7 @@ clone: steps: - name: Check out code image: docker:git + pull: if-not-exists commands: - mkdir -pv "/go/src/github.com/gravitational/teleport" - cd "/go/src/github.com/gravitational/teleport" @@ -184,13 +202,23 @@ steps: from_secret: GITHUB_PRIVATE_KEY - name: Wait for docker image: docker + pull: if-not-exists commands: - timeout 30s /bin/sh -c 'while [ ! -S /var/run/docker.sock ]; do sleep 1; done' + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin + environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: Build artifacts image: docker + pull: if-not-exists commands: - apk add --no-cache make - chown -R $UID:$GID /go @@ -206,6 +234,8 @@ steps: volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: Send Slack notification image: plugins/slack settings: @@ -233,6 +263,10 @@ services: volumes: - name: dockersock temp: {} +- name: dockerconfig + temp: {} +image_pull_secrets: +- DOCKERHUB_CREDENTIALS --- ################################################ @@ -247,7 +281,7 @@ name: push-build-linux-amd64-fips environment: BUILDBOX_VERSION: teleport13 GID: "1000" - RUNTIME: go1.20.2 + RUNTIME: go1.20.3 UID: "1000" trigger: event: @@ -269,6 +303,7 @@ clone: steps: - name: Check out code image: docker:git + pull: if-not-exists commands: - mkdir -pv "/go/src/github.com/gravitational/teleport" - cd "/go/src/github.com/gravitational/teleport" @@ -289,13 +324,23 @@ steps: from_secret: GITHUB_PRIVATE_KEY - name: Wait for docker image: docker + pull: if-not-exists commands: - timeout 30s /bin/sh -c 'while [ ! -S /var/run/docker.sock ]; do sleep 1; done' + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin + environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: Build artifacts image: docker + pull: if-not-exists commands: - apk add --no-cache make - chown -R $UID:$GID /go @@ -313,6 +358,8 @@ steps: volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: Send Slack notification image: plugins/slack settings: @@ -340,6 +387,10 @@ services: volumes: - name: dockersock temp: {} +- name: dockerconfig + temp: {} +image_pull_secrets: +- DOCKERHUB_CREDENTIALS --- ################################################ @@ -354,7 +405,7 @@ name: push-build-windows-amd64 environment: BUILDBOX_VERSION: teleport13 GID: "1000" - RUNTIME: go1.20.2 + RUNTIME: go1.20.3 UID: "1000" trigger: event: @@ -376,6 +427,7 @@ clone: steps: - name: Check out code image: docker:git + pull: if-not-exists commands: - mkdir -pv "/go/src/github.com/gravitational/teleport" - cd "/go/src/github.com/gravitational/teleport" @@ -394,13 +446,23 @@ steps: from_secret: GITHUB_PRIVATE_KEY - name: Wait for docker image: docker + pull: if-not-exists commands: - timeout 30s /bin/sh -c 'while [ ! -S /var/run/docker.sock ]; do sleep 1; done' + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin + environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: Build artifacts image: docker + pull: if-not-exists commands: - apk add --no-cache make - chown -R $UID:$GID /go @@ -416,6 +478,8 @@ steps: volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: Send Slack notification image: plugins/slack settings: @@ -443,6 +507,10 @@ services: volumes: - name: dockersock temp: {} +- name: dockerconfig + temp: {} +image_pull_secrets: +- DOCKERHUB_CREDENTIALS --- ################################################ @@ -512,7 +580,7 @@ steps: - tar -C /tmp/build-$DRONE_BUILD_NUMBER-$DRONE_BUILD_CREATED/toolchains -xzf $RUNTIME.darwin-amd64.tar.gz - rm -rf $RUNTIME.darwin-amd64.tar.gz environment: - RUNTIME: go1.20.2 + RUNTIME: go1.20.3 - name: Install Rust Toolchain commands: - set -u @@ -818,13 +886,23 @@ steps: && exit 1)' - name: Wait for docker image: docker + pull: if-not-exists commands: - timeout 30s /bin/sh -c 'while [ ! -S /var/run/docker.sock ]; do sleep 1; done' + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin + environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: Assume AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -896,13 +974,17 @@ services: - name: dockersock path: /var/run volumes: -- name: dockersock - temp: {} - name: tmpfs temp: medium: memory - name: awsconfig temp: {} +- name: dockersock + temp: {} +- name: dockerconfig + temp: {} +image_pull_secrets: +- DOCKERHUB_CREDENTIALS --- ################################################ @@ -1122,7 +1204,7 @@ name: push-build-linux-arm environment: BUILDBOX_VERSION: teleport13 GID: "1000" - RUNTIME: go1.20.2 + RUNTIME: go1.20.3 UID: "1000" trigger: event: @@ -1144,6 +1226,7 @@ clone: steps: - name: Check out code image: docker:git + pull: if-not-exists commands: - mkdir -pv "/go/src/github.com/gravitational/teleport" - cd "/go/src/github.com/gravitational/teleport" @@ -1162,13 +1245,23 @@ steps: from_secret: GITHUB_PRIVATE_KEY - name: Wait for docker image: docker + pull: if-not-exists commands: - timeout 30s /bin/sh -c 'while [ ! -S /var/run/docker.sock ]; do sleep 1; done' + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin + environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: Build artifacts image: docker + pull: if-not-exists commands: - apk add --no-cache make - chown -R $UID:$GID /go @@ -1184,6 +1277,8 @@ steps: volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: Send Slack notification image: plugins/slack settings: @@ -1211,6 +1306,10 @@ services: volumes: - name: dockersock temp: {} +- name: dockerconfig + temp: {} +image_pull_secrets: +- DOCKERHUB_CREDENTIALS --- ################################################ @@ -1242,6 +1341,7 @@ clone: steps: - name: Check out code image: docker:git + pull: if-not-exists commands: - mkdir -pv "/go/src/github.com/gravitational/teleport" - cd "/go/src/github.com/gravitational/teleport" @@ -1260,6 +1360,7 @@ steps: from_secret: GITHUB_PRIVATE_KEY - name: Delegate build to GitHub image: golang:1.18-alpine + pull: if-not-exists commands: - cd "/go/src/github.com/gravitational/teleport/build.assets/tooling" - 'go run ./cmd/gh-trigger-workflow -owner ${DRONE_REPO_OWNER} -repo teleport.e @@ -1286,6 +1387,8 @@ steps: when: status: - failure +image_pull_secrets: +- DOCKERHUB_CREDENTIALS --- kind: pipeline @@ -1535,6 +1638,7 @@ clone: steps: - name: Check out code image: alpine/git + pull: if-not-exists commands: - mkdir -p /go/src/github.com/gravitational/teleport - cd /go/src/github.com/gravitational/teleport @@ -1637,7 +1741,7 @@ type: kubernetes name: build-linux-amd64-centos7 environment: BUILDBOX_VERSION: teleport13 - RUNTIME: go1.20.2 + RUNTIME: go1.20.3 trigger: event: include: @@ -1657,6 +1761,7 @@ depends_on: steps: - name: Check out code image: docker:git + pull: if-not-exists commands: - mkdir -p /go/src/github.com/gravitational/teleport - cd /go/src/github.com/gravitational/teleport @@ -1680,13 +1785,23 @@ steps: from_secret: GITHUB_PRIVATE_KEY - name: Wait for docker image: docker + pull: if-not-exists commands: - timeout 30s /bin/sh -c 'while [ ! -S /var/run/docker.sock ]; do sleep 1; done' + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin + environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: Build artifacts image: docker + pull: if-not-exists commands: - apk add --no-cache make - chown -R $UID:$GID /go @@ -1702,8 +1817,11 @@ steps: volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: Copy artifacts image: docker + pull: if-not-exists commands: - cd /go/src/github.com/gravitational/teleport - find . -maxdepth 1 -iname "teleport*.tar.gz" -print -exec cp {} /go/artifacts @@ -1717,6 +1835,7 @@ steps: done && ls -l - name: Assume AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -1741,6 +1860,7 @@ steps: path: /root/.aws - name: Upload to S3 image: amazon/aws-cli + pull: if-not-exists commands: - cd /go/artifacts/ - aws s3 sync . s3://$AWS_S3_BUCKET/teleport/tag/${DRONE_TAG##v} @@ -1753,6 +1873,7 @@ steps: path: /root/.aws - name: Register artifacts image: docker + pull: if-not-exists commands: - WORKSPACE_DIR=$${WORKSPACE_DIR:-/} - VERSION=$(cat "$WORKSPACE_DIR/go/.version.txt") @@ -1812,6 +1933,10 @@ volumes: temp: {} - name: dockersock temp: {} +- name: dockerconfig + temp: {} +image_pull_secrets: +- DOCKERHUB_CREDENTIALS --- ################################################ @@ -1825,7 +1950,7 @@ type: kubernetes name: build-linux-amd64-centos7-fips environment: BUILDBOX_VERSION: teleport13 - RUNTIME: go1.20.2 + RUNTIME: go1.20.3 trigger: event: include: @@ -1845,6 +1970,7 @@ depends_on: steps: - name: Check out code image: docker:git + pull: if-not-exists commands: - mkdir -p /go/src/github.com/gravitational/teleport - cd /go/src/github.com/gravitational/teleport @@ -1868,13 +1994,23 @@ steps: from_secret: GITHUB_PRIVATE_KEY - name: Wait for docker image: docker + pull: if-not-exists commands: - timeout 30s /bin/sh -c 'while [ ! -S /var/run/docker.sock ]; do sleep 1; done' + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin + environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: Build artifacts image: docker + pull: if-not-exists commands: - apk add --no-cache make - chown -R $UID:$GID /go @@ -1892,8 +2028,11 @@ steps: volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: Copy artifacts image: docker + pull: if-not-exists commands: - cd /go/src/github.com/gravitational/teleport - find e/ -maxdepth 1 -iname "teleport*.tar.gz" -print -exec cp {} /go/artifacts @@ -1904,6 +2043,7 @@ steps: done && ls -l - name: Assume AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -1928,6 +2068,7 @@ steps: path: /root/.aws - name: Upload to S3 image: amazon/aws-cli + pull: if-not-exists commands: - cd /go/artifacts/ - aws s3 sync . s3://$AWS_S3_BUCKET/teleport/tag/${DRONE_TAG##v} @@ -1940,6 +2081,7 @@ steps: path: /root/.aws - name: Register artifacts image: docker + pull: if-not-exists commands: - WORKSPACE_DIR=$${WORKSPACE_DIR:-/} - VERSION=$(cat "$WORKSPACE_DIR/go/.version.txt") @@ -1999,6 +2141,10 @@ volumes: temp: {} - name: dockersock temp: {} +- name: dockerconfig + temp: {} +image_pull_secrets: +- DOCKERHUB_CREDENTIALS --- ################################################ @@ -2012,7 +2158,7 @@ type: kubernetes name: build-linux-amd64 environment: BUILDBOX_VERSION: teleport13 - RUNTIME: go1.20.2 + RUNTIME: go1.20.3 trigger: event: include: @@ -2032,6 +2178,7 @@ depends_on: steps: - name: Check out code image: docker:git + pull: if-not-exists commands: - mkdir -p /go/src/github.com/gravitational/teleport - cd /go/src/github.com/gravitational/teleport @@ -2055,13 +2202,23 @@ steps: from_secret: GITHUB_PRIVATE_KEY - name: Wait for docker image: docker + pull: if-not-exists commands: - timeout 30s /bin/sh -c 'while [ ! -S /var/run/docker.sock ]; do sleep 1; done' + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin + environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: Build artifacts image: docker + pull: if-not-exists commands: - apk add --no-cache make - chown -R $UID:$GID /go @@ -2079,8 +2236,11 @@ steps: volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: Copy artifacts image: docker + pull: if-not-exists commands: - cd /go/src/github.com/gravitational/teleport - find . -maxdepth 1 -iname "teleport*.tar.gz" -print -exec cp {} /go/artifacts @@ -2098,6 +2258,7 @@ steps: done && ls -l - name: Assume AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -2122,6 +2283,7 @@ steps: path: /root/.aws - name: Upload to S3 image: amazon/aws-cli + pull: if-not-exists commands: - cd /go/artifacts/ - aws s3 sync . s3://$AWS_S3_BUCKET/teleport/tag/${DRONE_TAG##v} @@ -2134,6 +2296,7 @@ steps: path: /root/.aws - name: Register artifacts image: docker + pull: if-not-exists commands: - WORKSPACE_DIR=$${WORKSPACE_DIR:-/} - VERSION=$(cat "$WORKSPACE_DIR/go/.version.txt") @@ -2193,6 +2356,10 @@ volumes: temp: {} - name: dockersock temp: {} +- name: dockerconfig + temp: {} +image_pull_secrets: +- DOCKERHUB_CREDENTIALS --- ################################################ @@ -2206,7 +2373,7 @@ type: kubernetes name: build-linux-amd64-fips environment: BUILDBOX_VERSION: teleport13 - RUNTIME: go1.20.2 + RUNTIME: go1.20.3 trigger: event: include: @@ -2226,6 +2393,7 @@ depends_on: steps: - name: Check out code image: docker:git + pull: if-not-exists commands: - mkdir -p /go/src/github.com/gravitational/teleport - cd /go/src/github.com/gravitational/teleport @@ -2249,13 +2417,23 @@ steps: from_secret: GITHUB_PRIVATE_KEY - name: Wait for docker image: docker + pull: if-not-exists commands: - timeout 30s /bin/sh -c 'while [ ! -S /var/run/docker.sock ]; do sleep 1; done' + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin + environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: Build artifacts image: docker + pull: if-not-exists commands: - apk add --no-cache make - chown -R $UID:$GID /go @@ -2273,8 +2451,11 @@ steps: volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: Copy artifacts image: docker + pull: if-not-exists commands: - cd /go/src/github.com/gravitational/teleport - find e/ -maxdepth 1 -iname "teleport*.tar.gz" -print -exec cp {} /go/artifacts @@ -2283,6 +2464,7 @@ steps: done && ls -l - name: Assume AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -2307,6 +2489,7 @@ steps: path: /root/.aws - name: Upload to S3 image: amazon/aws-cli + pull: if-not-exists commands: - cd /go/artifacts/ - aws s3 sync . s3://$AWS_S3_BUCKET/teleport/tag/${DRONE_TAG##v} @@ -2319,6 +2502,7 @@ steps: path: /root/.aws - name: Register artifacts image: docker + pull: if-not-exists commands: - WORKSPACE_DIR=$${WORKSPACE_DIR:-/} - VERSION=$(cat "$WORKSPACE_DIR/go/.version.txt") @@ -2378,6 +2562,10 @@ volumes: temp: {} - name: dockersock temp: {} +- name: dockerconfig + temp: {} +image_pull_secrets: +- DOCKERHUB_CREDENTIALS --- ################################################ @@ -2432,13 +2620,23 @@ steps: from_secret: GITHUB_PRIVATE_KEY - name: Wait for docker image: docker + pull: if-not-exists commands: - timeout 30s /bin/sh -c 'while [ ! -S /var/run/docker.sock ]; do sleep 1; done' + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin + environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: Assume Download AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -2480,6 +2678,7 @@ steps: path: /root/.aws - name: Assume Build AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -2527,6 +2726,8 @@ steps: volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: awsconfig path: /root/.aws - name: tmpfs @@ -2541,6 +2742,7 @@ steps: \; - name: Assume Upload AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -2565,6 +2767,7 @@ steps: path: /root/.aws - name: Upload to S3 image: amazon/aws-cli + pull: if-not-exists commands: - cd /go/artifacts/ - aws s3 sync . s3://$AWS_S3_BUCKET/teleport/tag/${DRONE_TAG##v} @@ -2634,13 +2837,17 @@ services: - name: dockersock path: /var/run volumes: +- name: awsconfig + temp: {} - name: dockersock temp: {} -- name: awsconfig +- name: dockerconfig temp: {} - name: tmpfs temp: medium: memory +image_pull_secrets: +- DOCKERHUB_CREDENTIALS --- ################################################ @@ -2695,13 +2902,23 @@ steps: from_secret: GITHUB_PRIVATE_KEY - name: Wait for docker image: docker + pull: if-not-exists commands: - timeout 30s /bin/sh -c 'while [ ! -S /var/run/docker.sock ]; do sleep 1; done' + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin + environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: Assume Download AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -2741,6 +2958,7 @@ steps: path: /root/.aws - name: Assume Build AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -2789,6 +3007,8 @@ steps: volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: awsconfig path: /root/.aws - name: tmpfs @@ -2801,6 +3021,7 @@ steps: \; - name: Assume Upload AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -2825,6 +3046,7 @@ steps: path: /root/.aws - name: Upload to S3 image: amazon/aws-cli + pull: if-not-exists commands: - cd /go/artifacts/ - aws s3 sync . s3://$AWS_S3_BUCKET/teleport/tag/${DRONE_TAG##v} @@ -2894,13 +3116,17 @@ services: - name: dockersock path: /var/run volumes: +- name: awsconfig + temp: {} - name: dockersock temp: {} -- name: awsconfig +- name: dockerconfig temp: {} - name: tmpfs temp: medium: memory +image_pull_secrets: +- DOCKERHUB_CREDENTIALS --- ################################################ @@ -2955,13 +3181,23 @@ steps: from_secret: GITHUB_PRIVATE_KEY - name: Wait for docker image: docker + pull: if-not-exists commands: - timeout 30s /bin/sh -c 'while [ ! -S /var/run/docker.sock ]; do sleep 1; done' + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin + environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: Assume Download AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -3003,6 +3239,7 @@ steps: path: /root/.aws - name: Assume Build AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -3043,6 +3280,8 @@ steps: volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: awsconfig path: /root/.aws - name: Copy artifacts @@ -3055,6 +3294,7 @@ steps: \; - name: Assume Upload AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -3079,6 +3319,7 @@ steps: path: /root/.aws - name: Upload to S3 image: amazon/aws-cli + pull: if-not-exists commands: - cd /go/artifacts/ - aws s3 sync . s3://$AWS_S3_BUCKET/teleport/tag/${DRONE_TAG##v} @@ -3146,10 +3387,14 @@ services: - name: dockersock path: /var/run volumes: +- name: awsconfig + temp: {} - name: dockersock temp: {} -- name: awsconfig +- name: dockerconfig temp: {} +image_pull_secrets: +- DOCKERHUB_CREDENTIALS --- ################################################ @@ -3204,13 +3449,23 @@ steps: from_secret: GITHUB_PRIVATE_KEY - name: Wait for docker image: docker + pull: if-not-exists commands: - timeout 30s /bin/sh -c 'while [ ! -S /var/run/docker.sock ]; do sleep 1; done' + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin + environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: Assume Download AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -3250,6 +3505,7 @@ steps: path: /root/.aws - name: Assume Build AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -3291,6 +3547,8 @@ steps: volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: awsconfig path: /root/.aws - name: Copy artifacts @@ -3301,6 +3559,7 @@ steps: \; - name: Assume Upload AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -3325,6 +3584,7 @@ steps: path: /root/.aws - name: Upload to S3 image: amazon/aws-cli + pull: if-not-exists commands: - cd /go/artifacts/ - aws s3 sync . s3://$AWS_S3_BUCKET/teleport/tag/${DRONE_TAG##v} @@ -3392,10 +3652,14 @@ services: - name: dockersock path: /var/run volumes: +- name: awsconfig + temp: {} - name: dockersock temp: {} -- name: awsconfig +- name: dockerconfig temp: {} +image_pull_secrets: +- DOCKERHUB_CREDENTIALS --- ################################################ @@ -3409,7 +3673,7 @@ type: kubernetes name: build-linux-386 environment: BUILDBOX_VERSION: teleport13 - RUNTIME: go1.20.2 + RUNTIME: go1.20.3 trigger: event: include: @@ -3429,6 +3693,7 @@ depends_on: steps: - name: Check out code image: docker:git + pull: if-not-exists commands: - mkdir -p /go/src/github.com/gravitational/teleport - cd /go/src/github.com/gravitational/teleport @@ -3452,13 +3717,23 @@ steps: from_secret: GITHUB_PRIVATE_KEY - name: Wait for docker image: docker + pull: if-not-exists commands: - timeout 30s /bin/sh -c 'while [ ! -S /var/run/docker.sock ]; do sleep 1; done' + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin + environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: Build artifacts image: docker + pull: if-not-exists commands: - apk add --no-cache make - chown -R $UID:$GID /go @@ -3474,8 +3749,11 @@ steps: volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: Copy artifacts image: docker + pull: if-not-exists commands: - cd /go/src/github.com/gravitational/teleport - find . -maxdepth 1 -iname "teleport*.tar.gz" -print -exec cp {} /go/artifacts @@ -3486,6 +3764,7 @@ steps: done && ls -l - name: Assume AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -3510,6 +3789,7 @@ steps: path: /root/.aws - name: Upload to S3 image: amazon/aws-cli + pull: if-not-exists commands: - cd /go/artifacts/ - aws s3 sync . s3://$AWS_S3_BUCKET/teleport/tag/${DRONE_TAG##v} @@ -3522,6 +3802,7 @@ steps: path: /root/.aws - name: Register artifacts image: docker + pull: if-not-exists commands: - WORKSPACE_DIR=$${WORKSPACE_DIR:-/} - VERSION=$(cat "$WORKSPACE_DIR/go/.version.txt") @@ -3581,6 +3862,10 @@ volumes: temp: {} - name: dockersock temp: {} +- name: dockerconfig + temp: {} +image_pull_secrets: +- DOCKERHUB_CREDENTIALS --- ################################################ @@ -3635,13 +3920,23 @@ steps: from_secret: GITHUB_PRIVATE_KEY - name: Wait for docker image: docker + pull: if-not-exists commands: - timeout 30s /bin/sh -c 'while [ ! -S /var/run/docker.sock ]; do sleep 1; done' + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin + environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: Assume Download AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -3683,6 +3978,7 @@ steps: path: /root/.aws - name: Assume Build AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -3730,6 +4026,8 @@ steps: volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: awsconfig path: /root/.aws - name: tmpfs @@ -3744,6 +4042,7 @@ steps: \; - name: Assume Upload AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -3768,6 +4067,7 @@ steps: path: /root/.aws - name: Upload to S3 image: amazon/aws-cli + pull: if-not-exists commands: - cd /go/artifacts/ - aws s3 sync . s3://$AWS_S3_BUCKET/teleport/tag/${DRONE_TAG##v} @@ -3837,13 +4137,17 @@ services: - name: dockersock path: /var/run volumes: +- name: awsconfig + temp: {} - name: dockersock temp: {} -- name: awsconfig +- name: dockerconfig temp: {} - name: tmpfs temp: medium: memory +image_pull_secrets: +- DOCKERHUB_CREDENTIALS --- ################################################ @@ -3898,13 +4202,23 @@ steps: from_secret: GITHUB_PRIVATE_KEY - name: Wait for docker image: docker + pull: if-not-exists commands: - timeout 30s /bin/sh -c 'while [ ! -S /var/run/docker.sock ]; do sleep 1; done' + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin + environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: Assume Download AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -3946,6 +4260,7 @@ steps: path: /root/.aws - name: Assume Build AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -3986,6 +4301,8 @@ steps: volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: awsconfig path: /root/.aws - name: Copy artifacts @@ -3998,6 +4315,7 @@ steps: \; - name: Assume Upload AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -4022,6 +4340,7 @@ steps: path: /root/.aws - name: Upload to S3 image: amazon/aws-cli + pull: if-not-exists commands: - cd /go/artifacts/ - aws s3 sync . s3://$AWS_S3_BUCKET/teleport/tag/${DRONE_TAG##v} @@ -4089,10 +4408,14 @@ services: - name: dockersock path: /var/run volumes: +- name: awsconfig + temp: {} - name: dockersock temp: {} -- name: awsconfig +- name: dockerconfig temp: {} +image_pull_secrets: +- DOCKERHUB_CREDENTIALS --- ################################################ @@ -4164,7 +4487,7 @@ steps: - tar -C /tmp/build-$DRONE_BUILD_NUMBER-$DRONE_BUILD_CREATED/toolchains -xzf $RUNTIME.darwin-amd64.tar.gz - rm -rf $RUNTIME.darwin-amd64.tar.gz environment: - RUNTIME: go1.20.2 + RUNTIME: go1.20.3 - name: Install Rust Toolchain commands: - set -u @@ -4756,7 +5079,7 @@ type: kubernetes name: build-linux-arm environment: BUILDBOX_VERSION: teleport13 - RUNTIME: go1.20.2 + RUNTIME: go1.20.3 trigger: event: include: @@ -4776,6 +5099,7 @@ depends_on: steps: - name: Check out code image: docker:git + pull: if-not-exists commands: - mkdir -p /go/src/github.com/gravitational/teleport - cd /go/src/github.com/gravitational/teleport @@ -4799,13 +5123,23 @@ steps: from_secret: GITHUB_PRIVATE_KEY - name: Wait for docker image: docker + pull: if-not-exists commands: - timeout 30s /bin/sh -c 'while [ ! -S /var/run/docker.sock ]; do sleep 1; done' + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin + environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: Build artifacts image: docker + pull: if-not-exists commands: - apk add --no-cache make - chown -R $UID:$GID /go @@ -4821,8 +5155,11 @@ steps: volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: Copy artifacts image: docker + pull: if-not-exists commands: - cd /go/src/github.com/gravitational/teleport - find . -maxdepth 1 -iname "teleport*.tar.gz" -print -exec cp {} /go/artifacts @@ -4833,6 +5170,7 @@ steps: done && ls -l - name: Assume AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -4857,6 +5195,7 @@ steps: path: /root/.aws - name: Upload to S3 image: amazon/aws-cli + pull: if-not-exists commands: - cd /go/artifacts/ - aws s3 sync . s3://$AWS_S3_BUCKET/teleport/tag/${DRONE_TAG##v} @@ -4869,6 +5208,7 @@ steps: path: /root/.aws - name: Register artifacts image: docker + pull: if-not-exists commands: - WORKSPACE_DIR=$${WORKSPACE_DIR:-/} - VERSION=$(cat "$WORKSPACE_DIR/go/.version.txt") @@ -4928,6 +5268,10 @@ volumes: temp: {} - name: dockersock temp: {} +- name: dockerconfig + temp: {} +image_pull_secrets: +- DOCKERHUB_CREDENTIALS --- ################################################ @@ -4958,6 +5302,7 @@ depends_on: steps: - name: Check out code image: docker:git + pull: if-not-exists commands: - mkdir -pv "/go/src/github.com/gravitational/teleport" - cd "/go/src/github.com/gravitational/teleport" @@ -4976,6 +5321,7 @@ steps: from_secret: GITHUB_PRIVATE_KEY - name: Delegate build to GitHub image: golang:1.18-alpine + pull: if-not-exists commands: - cd "/go/src/github.com/gravitational/teleport/build.assets/tooling" - 'go run ./cmd/gh-trigger-workflow -owner ${DRONE_REPO_OWNER} -repo teleport.e @@ -4985,6 +5331,8 @@ steps: environment: GHA_APP_KEY: from_secret: GITHUB_WORKFLOW_APP_PRIVATE_KEY +image_pull_secrets: +- DOCKERHUB_CREDENTIALS --- ################################################ @@ -5288,13 +5636,23 @@ steps: from_secret: GITHUB_PRIVATE_KEY - name: Wait for docker image: docker + pull: if-not-exists commands: - timeout 30s /bin/sh -c 'while [ ! -S /var/run/docker.sock ]; do sleep 1; done' + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin + environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: Assume Download AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -5336,6 +5694,7 @@ steps: path: /root/.aws - name: Assume Build AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -5376,6 +5735,8 @@ steps: volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: awsconfig path: /root/.aws - name: Copy artifacts @@ -5388,6 +5749,7 @@ steps: \; - name: Assume Upload AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -5412,6 +5774,7 @@ steps: path: /root/.aws - name: Upload to S3 image: amazon/aws-cli + pull: if-not-exists commands: - cd /go/artifacts/ - aws s3 sync . s3://$AWS_S3_BUCKET/teleport/tag/${DRONE_TAG##v} @@ -5479,10 +5842,14 @@ services: - name: dockersock path: /var/run volumes: +- name: awsconfig + temp: {} - name: dockersock temp: {} -- name: awsconfig +- name: dockerconfig temp: {} +image_pull_secrets: +- DOCKERHUB_CREDENTIALS --- ################################################ @@ -5800,13 +6167,23 @@ steps: from_secret: GITHUB_PRIVATE_KEY - name: Wait for docker image: docker + pull: if-not-exists commands: - timeout 30s /bin/sh -c 'while [ ! -S /var/run/docker.sock ]; do sleep 1; done' + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin + environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: Assume Download AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -5848,6 +6225,7 @@ steps: path: /root/.aws - name: Assume Build AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -5895,6 +6273,8 @@ steps: volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: awsconfig path: /root/.aws - name: tmpfs @@ -5909,6 +6289,7 @@ steps: \; - name: Assume Upload AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -5933,6 +6314,7 @@ steps: path: /root/.aws - name: Upload to S3 image: amazon/aws-cli + pull: if-not-exists commands: - cd /go/artifacts/ - aws s3 sync . s3://$AWS_S3_BUCKET/teleport/tag/${DRONE_TAG##v} @@ -6002,13 +6384,17 @@ services: - name: dockersock path: /var/run volumes: +- name: awsconfig + temp: {} - name: dockersock temp: {} -- name: awsconfig +- name: dockerconfig temp: {} - name: tmpfs temp: medium: memory +image_pull_secrets: +- DOCKERHUB_CREDENTIALS --- ################################################ @@ -6022,7 +6408,7 @@ type: kubernetes name: build-windows-amd64 environment: BUILDBOX_VERSION: teleport13 - RUNTIME: go1.20.2 + RUNTIME: go1.20.3 trigger: event: include: @@ -6042,6 +6428,7 @@ depends_on: steps: - name: Check out code image: docker:git + pull: if-not-exists commands: - mkdir -p /go/src/github.com/gravitational/teleport - cd /go/src/github.com/gravitational/teleport @@ -6065,13 +6452,23 @@ steps: from_secret: GITHUB_PRIVATE_KEY - name: Wait for docker image: docker + pull: if-not-exists commands: - timeout 30s /bin/sh -c 'while [ ! -S /var/run/docker.sock ]; do sleep 1; done' + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin + environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: Build artifacts image: docker + pull: if-not-exists commands: - apk add --no-cache make - chown -R $UID:$GID /go @@ -6091,8 +6488,11 @@ steps: volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: Copy artifacts image: docker + pull: if-not-exists commands: - cd /go/src/github.com/gravitational/teleport - find . -maxdepth 1 -iname "teleport*.zip" -print -exec cp {} /go/artifacts \; @@ -6102,6 +6502,7 @@ steps: done && ls -l - name: Assume AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -6126,6 +6527,7 @@ steps: path: /root/.aws - name: Upload to S3 image: amazon/aws-cli + pull: if-not-exists commands: - cd /go/artifacts/ - aws s3 sync . s3://$AWS_S3_BUCKET/teleport/tag/${DRONE_TAG##v} @@ -6138,6 +6540,7 @@ steps: path: /root/.aws - name: Register artifacts image: docker + pull: if-not-exists commands: - WORKSPACE_DIR=$${WORKSPACE_DIR:-/} - VERSION=$(cat "$WORKSPACE_DIR/go/.version.txt") @@ -6197,6 +6600,10 @@ volumes: temp: {} - name: dockersock temp: {} +- name: dockerconfig + temp: {} +image_pull_secrets: +- DOCKERHUB_CREDENTIALS --- kind: pipeline @@ -6585,13 +6992,23 @@ steps: - git checkout ${DRONE_COMMIT} - name: Wait for docker image: docker + pull: if-not-exists commands: - timeout 30s /bin/sh -c 'while [ ! -S /var/run/docker.sock ]; do sleep 1; done' + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin + environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: Configure Staging AWS Profile image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -6616,6 +7033,7 @@ steps: path: /root/.aws - name: Configure Production AWS Profile image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -6640,6 +7058,7 @@ steps: path: /root/.aws - name: Build and push buildbox image: docker + pull: if-not-exists commands: - apk add --no-cache make aws-cli - chown -R $UID:$GID /go @@ -6653,12 +7072,15 @@ steps: login -u="AWS" --password-stdin public.ecr.aws - docker push public.ecr.aws/gravitational/teleport-buildbox:$BUILDBOX_VERSION volumes: - - name: dockersock - path: /var/run - name: awsconfig path: /root/.aws + - name: dockersock + path: /var/run + - name: dockerconfig + path: /root/.docker - name: Build and push buildbox-fips image: docker + pull: if-not-exists commands: - apk add --no-cache make aws-cli - chown -R $UID:$GID /go @@ -6673,12 +7095,15 @@ steps: login -u="AWS" --password-stdin public.ecr.aws - docker push public.ecr.aws/gravitational/teleport-buildbox-fips:$BUILDBOX_VERSION volumes: - - name: dockersock - path: /var/run - name: awsconfig path: /root/.aws + - name: dockersock + path: /var/run + - name: dockerconfig + path: /root/.docker - name: Build and push buildbox-arm image: docker + pull: if-not-exists commands: - apk add --no-cache make aws-cli - chown -R $UID:$GID /go @@ -6693,12 +7118,15 @@ steps: login -u="AWS" --password-stdin public.ecr.aws - docker push public.ecr.aws/gravitational/teleport-buildbox-arm:$BUILDBOX_VERSION volumes: - - name: dockersock - path: /var/run - name: awsconfig path: /root/.aws + - name: dockersock + path: /var/run + - name: dockerconfig + path: /root/.docker - name: Build and push buildbox-centos7 image: docker + pull: if-not-exists commands: - apk add --no-cache make aws-cli - chown -R $UID:$GID /go @@ -6713,12 +7141,15 @@ steps: login -u="AWS" --password-stdin public.ecr.aws - docker push public.ecr.aws/gravitational/teleport-buildbox-centos7:$BUILDBOX_VERSION volumes: - - name: dockersock - path: /var/run - name: awsconfig path: /root/.aws + - name: dockersock + path: /var/run + - name: dockerconfig + path: /root/.docker - name: Build and push buildbox-centos7-fips image: docker + pull: if-not-exists commands: - apk add --no-cache make aws-cli - chown -R $UID:$GID /go @@ -6733,10 +7164,12 @@ steps: login -u="AWS" --password-stdin public.ecr.aws - docker push public.ecr.aws/gravitational/teleport-buildbox-centos7-fips:$BUILDBOX_VERSION volumes: - - name: dockersock - path: /var/run - name: awsconfig path: /root/.aws + - name: dockersock + path: /var/run + - name: dockerconfig + path: /root/.docker services: - name: Start Docker image: docker:dind @@ -6745,10 +7178,14 @@ services: - name: dockersock path: /var/run volumes: +- name: awsconfig + temp: {} - name: dockersock temp: {} -- name: awsconfig +- name: dockerconfig temp: {} +image_pull_secrets: +- DOCKERHUB_CREDENTIALS --- ################################################ @@ -6777,6 +7214,8 @@ steps: image: alpine:latest commands: - echo "This command, step, and pipeline never runs" +image_pull_secrets: +- DOCKERHUB_CREDENTIALS --- ################################################ @@ -6806,11 +7245,13 @@ clone: steps: - name: Verify build is tagged image: alpine:latest + pull: if-not-exists commands: - '[ -n ${DRONE_TAG} ] || (echo ''DRONE_TAG is not set. Is the commit tagged?'' && exit 1)' - name: Check out code image: alpine/git:latest + pull: if-not-exists commands: - mkdir -pv "/go/src/github.com/gravitational/teleport" - cd "/go/src/github.com/gravitational/teleport" @@ -6820,6 +7261,7 @@ steps: - git checkout -qf "${DRONE_TAG}" - name: Assume Download AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -6867,6 +7309,7 @@ steps: - Check out code - name: Assume Upload AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -6955,6 +7398,8 @@ volumes: medium: memory - name: awsconfig temp: {} +image_pull_secrets: +- DOCKERHUB_CREDENTIALS --- ################################################ @@ -6983,6 +7428,8 @@ steps: image: alpine:latest commands: - echo "This command, step, and pipeline never runs" +image_pull_secrets: +- DOCKERHUB_CREDENTIALS --- ################################################ @@ -7012,11 +7459,13 @@ clone: steps: - name: Verify build is tagged image: alpine:latest + pull: if-not-exists commands: - '[ -n ${DRONE_TAG} ] || (echo ''DRONE_TAG is not set. Is the commit tagged?'' && exit 1)' - name: Check out code image: alpine/git:latest + pull: if-not-exists commands: - mkdir -pv "/go/src/github.com/gravitational/teleport" - cd "/go/src/github.com/gravitational/teleport" @@ -7026,6 +7475,7 @@ steps: - git checkout -qf "${DRONE_TAG}" - name: Assume Download AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -7073,6 +7523,7 @@ steps: - Check out code - name: Assume Upload AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -7162,6 +7613,8 @@ volumes: medium: memory - name: awsconfig temp: {} +image_pull_secrets: +- DOCKERHUB_CREDENTIALS --- kind: pipeline @@ -7723,6 +8176,7 @@ clone: steps: - name: Check out code image: docker:git + pull: if-not-exists commands: - mkdir -pv "/go/src/github.com/gravitational/teleport" - cd "/go/src/github.com/gravitational/teleport" @@ -7741,6 +8195,7 @@ steps: from_secret: GITHUB_PRIVATE_KEY - name: Delegate build to GitHub image: golang:1.18-alpine + pull: if-not-exists commands: - cd "/go/src/github.com/gravitational/teleport/build.assets/tooling" - 'go run ./cmd/gh-trigger-workflow -owner ${DRONE_REPO_OWNER} -repo teleport.e @@ -7749,6 +8204,8 @@ steps: environment: GHA_APP_KEY: from_secret: GITHUB_WORKFLOW_APP_PRIVATE_KEY +image_pull_secrets: +- DOCKERHUB_CREDENTIALS --- ################################################ @@ -8014,19 +8471,30 @@ depends_on: steps: - name: Wait for docker image: docker + pull: if-not-exists commands: - timeout 30s /bin/sh -c 'while [ ! -S /var/run/docker.sock ]; do sleep 1; done' + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin + environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: Wait for docker registry image: alpine + pull: if-not-exists commands: - apk add curl - timeout 30s /bin/sh -c 'while [ "$(curl -s -o /dev/null -w %{http_code} http://drone-docker-registry:5000/)" != "200" ]; do sleep 1; done' - name: Check out code image: alpine/git:latest + pull: if-not-exists commands: - mkdir -pv "/go/src/github.com/gravitational/teleport" - cd "/go/src/github.com/gravitational/teleport" @@ -8042,6 +8510,7 @@ steps: - echo $(cat "/go/var/full-version") - name: Assume ECR - staging AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -8066,6 +8535,7 @@ steps: path: /root/.aws - name: Assume ECR - authenticated-pull AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -8092,6 +8562,7 @@ steps: - Assume ECR - staging AWS Role - name: Assume S3 Download AWS Role for teleport image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -8182,6 +8653,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-v13-amd64-builder" --target "teleport" --platform "linux/amd64" --tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-amd64 --file "/go/build/Dockerfile-teleport" --build-arg DEB_PATH=teleport_$(cat "/go/var/full-version")_amd64.deb @@ -8192,6 +8664,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -8243,6 +8719,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-v13-arm-builder" --target "teleport" --platform "linux/arm" --tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-arm --file "/go/build/Dockerfile-teleport" --build-arg DEB_PATH=teleport_$(cat "/go/var/full-version")_arm.deb @@ -8253,6 +8730,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -8304,6 +8785,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-v13-arm64-builder" --target "teleport" --platform "linux/arm64" --tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-arm64 --file "/go/build/Dockerfile-teleport" --build-arg DEB_PATH=teleport_$(cat "/go/var/full-version")_arm64.deb @@ -8314,6 +8796,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -8330,6 +8816,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport:$(cat "/go/var/full-version")-amd64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-amd64 @@ -8339,6 +8826,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -8353,6 +8844,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport:$(cat "/go/var/full-version")-arm > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-arm @@ -8362,6 +8854,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -8377,6 +8873,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport:$(cat "/go/var/full-version")-arm64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-arm64 @@ -8386,6 +8883,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -8399,6 +8900,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport:$(cat "/go/var/full-version") > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker manifest create 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport:$(cat @@ -8410,6 +8912,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -8421,6 +8927,7 @@ steps: - Tag and push image "teleport:v13-arm64" to ECR - staging - name: Assume S3 Download AWS Role for teleport-ent image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -8511,6 +9018,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-ent-v13-amd64-builder" --target "teleport" --platform "linux/amd64" --tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-amd64 --file "/go/build/Dockerfile-teleport-ent" --build-arg @@ -8521,6 +9029,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -8572,6 +9084,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-ent-v13-arm-builder" --target "teleport" --platform "linux/arm" --tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-arm --file "/go/build/Dockerfile-teleport-ent" --build-arg DEB_PATH=teleport-ent_$(cat @@ -8582,6 +9095,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -8633,6 +9150,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-ent-v13-arm64-builder" --target "teleport" --platform "linux/arm64" --tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-arm64 --file "/go/build/Dockerfile-teleport-ent" --build-arg @@ -8643,6 +9161,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -8659,6 +9181,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/full-version")-amd64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-amd64 @@ -8668,6 +9191,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -8683,6 +9210,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/full-version")-arm > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-arm @@ -8692,6 +9220,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -8707,6 +9239,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/full-version")-arm64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-arm64 @@ -8716,6 +9249,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -8729,6 +9266,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/full-version") > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker manifest create 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat @@ -8740,6 +9278,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -8751,6 +9293,7 @@ steps: - Tag and push image "teleport-ent:v13-arm64" to ECR - staging - name: Assume S3 Download AWS Role for teleport-ent-fips image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -8842,6 +9385,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-ent-v13-fips-amd64-builder" --target "teleport-fips" --platform "linux/amd64" --tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-fips-amd64 --file "/go/build/Dockerfile-teleport-ent-fips" @@ -8853,6 +9397,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -8869,6 +9417,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/full-version")-fips-amd64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-fips-amd64 @@ -8878,6 +9427,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -8891,6 +9444,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/full-version")-fips > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker manifest create 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat @@ -8900,6 +9454,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -8920,6 +9478,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-operator-v13-amd64-builder" --platform "linux/amd64" --tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-amd64 --file "/go/src/github.com/gravitational/teleport/integrations/operator/Dockerfile" @@ -8931,6 +9490,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -8957,6 +9520,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-operator-v13-arm-builder" --platform "linux/arm" --tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-arm --file "/go/src/github.com/gravitational/teleport/integrations/operator/Dockerfile" @@ -8968,6 +9532,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -8994,6 +9562,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-operator-v13-arm64-builder" --platform "linux/arm64" --tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-arm64 --file "/go/src/github.com/gravitational/teleport/integrations/operator/Dockerfile" @@ -9005,6 +9574,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -9026,6 +9599,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-operator:$(cat "/go/var/full-version")-amd64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-amd64 @@ -9035,6 +9609,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -9050,6 +9628,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-operator:$(cat "/go/var/full-version")-arm > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-arm @@ -9059,6 +9638,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -9074,6 +9657,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-operator:$(cat "/go/var/full-version")-arm64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-arm64 @@ -9083,6 +9667,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -9096,6 +9684,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-operator:$(cat "/go/var/full-version") > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker manifest create 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-operator:$(cat @@ -9107,6 +9696,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -9132,6 +9725,10 @@ volumes: temp: {} - name: dockersock temp: {} +- name: dockerconfig + temp: {} +image_pull_secrets: +- DOCKERHUB_CREDENTIALS --- ################################################ @@ -9164,6 +9761,7 @@ depends_on: steps: - name: Check out code image: docker:git + pull: if-not-exists commands: - mkdir -pv "/go/src/github.com/gravitational/teleport" - cd "/go/src/github.com/gravitational/teleport" @@ -9182,6 +9780,7 @@ steps: from_secret: GITHUB_PRIVATE_KEY - name: Delegate build to GitHub image: golang:1.18-alpine + pull: if-not-exists commands: - cd "/go/src/github.com/gravitational/teleport/build.assets/tooling" - 'go run ./cmd/gh-trigger-workflow -owner ${DRONE_REPO_OWNER} -repo teleport.e @@ -9190,6 +9789,8 @@ steps: environment: GHA_APP_KEY: from_secret: GITHUB_WORKFLOW_APP_PRIVATE_KEY +image_pull_secrets: +- DOCKERHUB_CREDENTIALS --- ################################################ @@ -9221,6 +9822,7 @@ clone: steps: - name: Verify build is tagged image: alpine:latest + pull: if-not-exists commands: - '[ -n ${DRONE_TAG} ] || (echo ''DRONE_TAG is not set. Is the commit tagged?'' && exit 1)' @@ -9242,16 +9844,26 @@ steps: '; echo 'a prerelease' - name: Wait for docker image: docker + pull: if-not-exists commands: - timeout 30s /bin/sh -c 'while [ ! -S /var/run/docker.sock ]; do sleep 1; done' + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin + environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker depends_on: - Verify build is tagged - Record if tag ($DRONE_TAG) is prerelease - name: Wait for docker registry image: alpine + pull: if-not-exists commands: - apk add curl - timeout 30s /bin/sh -c 'while [ "$(curl -s -o /dev/null -w %{http_code} http://drone-docker-registry:5000/)" @@ -9261,6 +9873,7 @@ steps: - Record if tag ($DRONE_TAG) is prerelease - name: Check out code image: alpine/git:latest + pull: if-not-exists commands: - mkdir -pv "/go/src/github.com/gravitational/teleport" - cd "/go/src/github.com/gravitational/teleport" @@ -9288,6 +9901,7 @@ steps: - Record if tag ($DRONE_TAG) is prerelease - name: Assume ECR - staging AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -9315,6 +9929,7 @@ steps: - Record if tag ($DRONE_TAG) is prerelease - name: Assume ECR - production AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -9347,6 +9962,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker pull 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport:$(cat "/go/var/full-version")-amd64 - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" @@ -9355,6 +9971,10 @@ steps: - docker push drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-amd64 environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -9375,6 +9995,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker pull 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport:$(cat "/go/var/full-version")-arm - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" @@ -9383,6 +10004,10 @@ steps: - docker push drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-arm environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -9403,6 +10028,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker pull 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport:$(cat "/go/var/full-version")-arm64 - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" @@ -9411,6 +10037,10 @@ steps: - docker push drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-arm64 environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -9431,6 +10061,7 @@ steps: - docker pull --platform "linux/amd64" drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-amd64 - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport:$(cat "/go/var/full-version")-amd64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-amd64 quay.io/gravitational/teleport:$(cat "/go/var/full-version")-amd64 @@ -9443,6 +10074,10 @@ steps: - docker push quay.io/gravitational/teleport:$(cat "/go/var/minor-version")-amd64 - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -9459,6 +10094,7 @@ steps: commands: - docker pull --platform "linux/arm" drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-arm - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport:$(cat "/go/var/full-version")-arm > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-arm quay.io/gravitational/teleport:$(cat "/go/var/full-version")-arm @@ -9471,6 +10107,10 @@ steps: - docker push quay.io/gravitational/teleport:$(cat "/go/var/minor-version")-arm - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -9488,6 +10128,7 @@ steps: - docker pull --platform "linux/arm64" drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-arm64 - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport:$(cat "/go/var/full-version")-arm64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-arm64 quay.io/gravitational/teleport:$(cat "/go/var/full-version")-arm64 @@ -9500,6 +10141,10 @@ steps: - docker push quay.io/gravitational/teleport:$(cat "/go/var/minor-version")-arm64 - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -9519,6 +10164,7 @@ steps: ] && echo "skipping" || echo "continuing" - '[ -f /go/vars/release-is-prerelease ] && exit 0' - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create quay.io/gravitational/teleport:$(cat "/go/var/major-version") --amend quay.io/gravitational/teleport:$(cat "/go/var/major-version")-amd64 --amend quay.io/gravitational/teleport:$(cat "/go/var/major-version")-arm --amend quay.io/gravitational/teleport:$(cat @@ -9526,6 +10172,10 @@ steps: - docker manifest push quay.io/gravitational/teleport:$(cat "/go/var/major-version") - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -9547,6 +10197,7 @@ steps: ] && echo "skipping" || echo "continuing" - '[ -f /go/vars/release-is-prerelease ] && exit 0' - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create quay.io/gravitational/teleport:$(cat "/go/var/minor-version") --amend quay.io/gravitational/teleport:$(cat "/go/var/minor-version")-amd64 --amend quay.io/gravitational/teleport:$(cat "/go/var/minor-version")-arm --amend quay.io/gravitational/teleport:$(cat @@ -9554,6 +10205,10 @@ steps: - docker manifest push quay.io/gravitational/teleport:$(cat "/go/var/minor-version") - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -9571,6 +10226,7 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport:$(cat "/go/var/full-version") > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker manifest create quay.io/gravitational/teleport:$(cat "/go/var/full-version") --amend quay.io/gravitational/teleport:$(cat @@ -9579,6 +10235,10 @@ steps: docker manifest push quay.io/gravitational/teleport:$(cat "/go/var/full-version")) - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -9600,6 +10260,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport:$(cat "/go/var/full-version")-amd64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-amd64 public.ecr.aws/gravitational/teleport:$(cat "/go/var/full-version")-amd64 @@ -9613,6 +10274,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -9627,6 +10292,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport:$(cat "/go/var/full-version")-arm > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-arm public.ecr.aws/gravitational/teleport:$(cat "/go/var/full-version")-arm @@ -9640,6 +10306,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -9655,6 +10325,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport:$(cat "/go/var/full-version")-arm64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-arm64 public.ecr.aws/gravitational/teleport:$(cat "/go/var/full-version")-arm64 @@ -9668,6 +10339,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -9685,6 +10360,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create public.ecr.aws/gravitational/teleport:$(cat "/go/var/major-version") --amend public.ecr.aws/gravitational/teleport:$(cat "/go/var/major-version")-amd64 --amend public.ecr.aws/gravitational/teleport:$(cat "/go/var/major-version")-arm @@ -9693,6 +10369,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -9712,6 +10392,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create public.ecr.aws/gravitational/teleport:$(cat "/go/var/minor-version") --amend public.ecr.aws/gravitational/teleport:$(cat "/go/var/minor-version")-amd64 --amend public.ecr.aws/gravitational/teleport:$(cat "/go/var/minor-version")-arm @@ -9720,6 +10401,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -9735,6 +10420,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport:$(cat "/go/var/full-version") > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker manifest create public.ecr.aws/gravitational/teleport:$(cat "/go/var/full-version") --amend @@ -9745,6 +10431,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -9760,6 +10450,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker pull 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/full-version")-amd64 - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" @@ -9768,6 +10459,10 @@ steps: - docker push drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-amd64 environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -9788,6 +10483,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker pull 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/full-version")-arm - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" @@ -9796,6 +10492,10 @@ steps: - docker push drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-arm environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -9816,6 +10516,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker pull 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/full-version")-arm64 - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" @@ -9824,6 +10525,10 @@ steps: - docker push drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-arm64 environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -9844,6 +10549,7 @@ steps: - docker pull --platform "linux/amd64" drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-amd64 - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-amd64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-amd64 quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-amd64 @@ -9856,6 +10562,10 @@ steps: - docker push quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version")-amd64 - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -9873,6 +10583,7 @@ steps: - docker pull --platform "linux/arm" drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-arm - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-arm > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-arm quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-arm @@ -9885,6 +10596,10 @@ steps: - docker push quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version")-arm - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -9902,6 +10617,7 @@ steps: - docker pull --platform "linux/arm64" drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-arm64 - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-arm64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-arm64 quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-arm64 @@ -9914,6 +10630,10 @@ steps: - docker push quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version")-arm64 - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -9933,6 +10653,7 @@ steps: ] && echo "skipping" || echo "continuing" - '[ -f /go/vars/release-is-prerelease ] && exit 0' - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create quay.io/gravitational/teleport-ent:$(cat "/go/var/major-version") --amend quay.io/gravitational/teleport-ent:$(cat "/go/var/major-version")-amd64 --amend quay.io/gravitational/teleport-ent:$(cat "/go/var/major-version")-arm @@ -9940,6 +10661,10 @@ steps: - docker manifest push quay.io/gravitational/teleport-ent:$(cat "/go/var/major-version") - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -9961,6 +10686,7 @@ steps: ] && echo "skipping" || echo "continuing" - '[ -f /go/vars/release-is-prerelease ] && exit 0' - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version") --amend quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version")-amd64 --amend quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version")-arm @@ -9968,6 +10694,10 @@ steps: - docker manifest push quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version") - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -9985,6 +10715,7 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version") > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker manifest create quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version") --amend @@ -9994,6 +10725,10 @@ steps: "/go/var/full-version")) - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -10015,6 +10750,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/full-version")-amd64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-amd64 public.ecr.aws/gravitational/teleport-ent:$(cat @@ -10029,6 +10765,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -10044,6 +10784,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/full-version")-arm > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-arm public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/full-version")-arm @@ -10057,6 +10798,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -10072,6 +10817,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/full-version")-arm64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-arm64 public.ecr.aws/gravitational/teleport-ent:$(cat @@ -10086,6 +10832,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -10103,6 +10853,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/major-version") --amend public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/major-version")-amd64 --amend public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/major-version")-arm @@ -10111,6 +10862,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -10130,6 +10885,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/minor-version") --amend public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/minor-version")-amd64 --amend public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/minor-version")-arm @@ -10138,6 +10894,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -10153,6 +10913,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/full-version") > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker manifest create public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/full-version") @@ -10163,6 +10924,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -10178,6 +10943,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker pull 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/full-version")-fips-amd64 - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" @@ -10187,6 +10953,10 @@ steps: - docker push drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-fips-amd64 environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -10207,6 +10977,7 @@ steps: - docker pull --platform "linux/amd64" drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-fips-amd64 - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-fips-amd64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-fips-amd64 quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-fips-amd64 @@ -10219,6 +10990,10 @@ steps: - docker push quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version")-fips-amd64 - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -10238,11 +11013,16 @@ steps: ] && echo "skipping" || echo "continuing" - '[ -f /go/vars/release-is-prerelease ] && exit 0' - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create quay.io/gravitational/teleport-ent:$(cat "/go/var/major-version")-fips --amend quay.io/gravitational/teleport-ent:$(cat "/go/var/major-version")-fips-amd64 - docker manifest push quay.io/gravitational/teleport-ent:$(cat "/go/var/major-version")-fips - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -10262,11 +11042,16 @@ steps: ] && echo "skipping" || echo "continuing" - '[ -f /go/vars/release-is-prerelease ] && exit 0' - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version")-fips --amend quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version")-fips-amd64 - docker manifest push quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version")-fips - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -10282,6 +11067,7 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-fips > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker manifest create quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-fips --amend @@ -10289,6 +11075,10 @@ steps: docker manifest push quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-fips) - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -10308,6 +11098,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/full-version")-fips-amd64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-fips-amd64 public.ecr.aws/gravitational/teleport-ent:$(cat @@ -10322,6 +11113,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -10339,12 +11134,17 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/major-version")-fips --amend public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/major-version")-fips-amd64 - docker manifest push public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/major-version")-fips - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -10362,12 +11162,17 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/minor-version")-fips --amend public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/minor-version")-fips-amd64 - docker manifest push public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/minor-version")-fips - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -10381,6 +11186,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/full-version")-fips > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker manifest create public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/full-version")-fips @@ -10389,6 +11195,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -10402,6 +11212,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker pull 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-operator:$(cat "/go/var/full-version")-amd64 - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" @@ -10411,6 +11222,10 @@ steps: - docker push drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-amd64 environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -10431,6 +11246,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker pull 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-operator:$(cat "/go/var/full-version")-arm - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" @@ -10440,6 +11256,10 @@ steps: - docker push drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-arm environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -10460,6 +11280,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker pull 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-operator:$(cat "/go/var/full-version")-arm64 - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" @@ -10469,6 +11290,10 @@ steps: - docker push drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-arm64 environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -10489,6 +11314,7 @@ steps: - docker pull --platform "linux/amd64" drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-amd64 - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-operator:$(cat "/go/var/full-version")-amd64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-amd64 quay.io/gravitational/teleport-operator:$(cat "/go/var/full-version")-amd64 @@ -10501,6 +11327,10 @@ steps: - docker push quay.io/gravitational/teleport-operator:$(cat "/go/var/minor-version")-amd64 - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -10518,6 +11348,7 @@ steps: - docker pull --platform "linux/arm" drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-arm - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-operator:$(cat "/go/var/full-version")-arm > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-arm quay.io/gravitational/teleport-operator:$(cat "/go/var/full-version")-arm @@ -10530,6 +11361,10 @@ steps: - docker push quay.io/gravitational/teleport-operator:$(cat "/go/var/minor-version")-arm - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -10547,6 +11382,7 @@ steps: - docker pull --platform "linux/arm64" drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-arm64 - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-operator:$(cat "/go/var/full-version")-arm64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-arm64 quay.io/gravitational/teleport-operator:$(cat "/go/var/full-version")-arm64 @@ -10559,6 +11395,10 @@ steps: - docker push quay.io/gravitational/teleport-operator:$(cat "/go/var/minor-version")-arm64 - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -10578,6 +11418,7 @@ steps: ] && echo "skipping" || echo "continuing" - '[ -f /go/vars/release-is-prerelease ] && exit 0' - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create quay.io/gravitational/teleport-operator:$(cat "/go/var/major-version") --amend quay.io/gravitational/teleport-operator:$(cat "/go/var/major-version")-amd64 --amend quay.io/gravitational/teleport-operator:$(cat "/go/var/major-version")-arm @@ -10585,6 +11426,10 @@ steps: - docker manifest push quay.io/gravitational/teleport-operator:$(cat "/go/var/major-version") - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -10606,6 +11451,7 @@ steps: ] && echo "skipping" || echo "continuing" - '[ -f /go/vars/release-is-prerelease ] && exit 0' - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create quay.io/gravitational/teleport-operator:$(cat "/go/var/minor-version") --amend quay.io/gravitational/teleport-operator:$(cat "/go/var/minor-version")-amd64 --amend quay.io/gravitational/teleport-operator:$(cat "/go/var/minor-version")-arm @@ -10613,6 +11459,10 @@ steps: - docker manifest push quay.io/gravitational/teleport-operator:$(cat "/go/var/minor-version") - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -10630,6 +11480,7 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-operator:$(cat "/go/var/full-version") > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker manifest create quay.io/gravitational/teleport-operator:$(cat "/go/var/full-version") --amend @@ -10639,6 +11490,10 @@ steps: docker manifest push quay.io/gravitational/teleport-operator:$(cat "/go/var/full-version")) - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -10660,6 +11515,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/full-version")-amd64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-amd64 public.ecr.aws/gravitational/teleport-operator:$(cat @@ -10674,6 +11530,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -10689,6 +11549,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/full-version")-arm > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-arm public.ecr.aws/gravitational/teleport-operator:$(cat @@ -10703,6 +11564,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -10718,6 +11583,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/full-version")-arm64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-arm64 public.ecr.aws/gravitational/teleport-operator:$(cat @@ -10732,6 +11598,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -10749,6 +11619,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/major-version") --amend public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/major-version")-amd64 --amend public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/major-version")-arm @@ -10757,6 +11628,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -10776,6 +11651,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/minor-version") --amend public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/minor-version")-amd64 --amend public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/minor-version")-arm @@ -10784,6 +11660,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -10799,6 +11679,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/full-version") > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker manifest create public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/full-version") @@ -10809,6 +11690,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -10834,6 +11719,10 @@ volumes: temp: {} - name: dockersock temp: {} +- name: dockerconfig + temp: {} +image_pull_secrets: +- DOCKERHUB_CREDENTIALS --- ################################################ @@ -10875,15 +11764,25 @@ steps: "v12" - name: Wait for docker image: docker + pull: if-not-exists commands: - timeout 30s /bin/sh -c 'while [ ! -S /var/run/docker.sock ]; do sleep 1; done' + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin + environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker depends_on: - Find the latest available semver for v12 - name: Wait for docker registry image: alpine + pull: if-not-exists commands: - apk add curl - timeout 30s /bin/sh -c 'while [ "$(curl -s -o /dev/null -w %{http_code} http://drone-docker-registry:5000/)" @@ -10892,6 +11791,7 @@ steps: - Find the latest available semver for v12 - name: Check out code image: alpine/git:latest + pull: if-not-exists commands: - mkdir -pv "/go/src/github.com/gravitational/teleport" - cd "/go/src/github.com/gravitational/teleport" @@ -10918,6 +11818,7 @@ steps: - Find the latest available semver for v12 - name: Assume ECR - staging AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -10944,6 +11845,7 @@ steps: - Find the latest available semver for v12 - name: Assume ECR - authenticated-pull AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -10971,6 +11873,7 @@ steps: - Find the latest available semver for v12 - name: Assume ECR - production AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -10998,6 +11901,7 @@ steps: - Find the latest available semver for v12 - name: Assume S3 Download AWS Role for teleport image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -11092,6 +11996,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-v12-amd64-builder" --target "teleport" --platform "linux/amd64" --tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-amd64 --file "/go/build/Dockerfile-teleport" --build-arg DEB_PATH=teleport_$(cat "/go/var/full-version")_amd64.deb @@ -11102,6 +12007,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -11153,6 +12062,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-v12-arm-builder" --target "teleport" --platform "linux/arm" --tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-arm --file "/go/build/Dockerfile-teleport" --build-arg DEB_PATH=teleport_$(cat "/go/var/full-version")_arm.deb @@ -11163,6 +12073,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -11214,6 +12128,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-v12-arm64-builder" --target "teleport" --platform "linux/arm64" --tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-arm64 --file "/go/build/Dockerfile-teleport" --build-arg DEB_PATH=teleport_$(cat "/go/var/full-version")_arm64.deb @@ -11224,6 +12139,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -11240,6 +12159,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport:$(cat "/go/var/full-version")-$TIMESTAMP-amd64 > /dev/null 2>&1 && echo 'Found existing @@ -11262,6 +12182,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -11276,6 +12200,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport:$(cat "/go/var/full-version")-$TIMESTAMP-arm > /dev/null 2>&1 && echo 'Found existing @@ -11298,6 +12223,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -11313,6 +12242,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport:$(cat "/go/var/full-version")-$TIMESTAMP-arm64 > /dev/null 2>&1 && echo 'Found existing @@ -11335,6 +12265,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -11348,6 +12282,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport:$(cat "/go/var/major-version")-$TIMESTAMP > /dev/null 2>&1 && echo 'Found existing image, @@ -11360,6 +12295,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -11375,6 +12314,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport:$(cat "/go/var/minor-version")-$TIMESTAMP > /dev/null 2>&1 && echo 'Found existing image, @@ -11387,6 +12327,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -11402,6 +12346,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport:$(cat "/go/var/full-version")-$TIMESTAMP > /dev/null 2>&1 && echo 'Found existing image, @@ -11414,6 +12359,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -11429,6 +12378,7 @@ steps: - docker pull --platform "linux/amd64" drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-amd64 - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport:$(cat "/go/var/full-version")-amd64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-amd64 quay.io/gravitational/teleport:$(cat "/go/var/full-version")-amd64 @@ -11441,6 +12391,10 @@ steps: - docker push quay.io/gravitational/teleport:$(cat "/go/var/minor-version")-amd64 - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -11457,6 +12411,7 @@ steps: commands: - docker pull --platform "linux/arm" drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-arm - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport:$(cat "/go/var/full-version")-arm > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-arm quay.io/gravitational/teleport:$(cat "/go/var/full-version")-arm @@ -11469,6 +12424,10 @@ steps: - docker push quay.io/gravitational/teleport:$(cat "/go/var/minor-version")-arm - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -11486,6 +12445,7 @@ steps: - docker pull --platform "linux/arm64" drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-arm64 - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport:$(cat "/go/var/full-version")-arm64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-arm64 quay.io/gravitational/teleport:$(cat "/go/var/full-version")-arm64 @@ -11498,6 +12458,10 @@ steps: - docker push quay.io/gravitational/teleport:$(cat "/go/var/minor-version")-arm64 - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -11513,6 +12477,7 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create quay.io/gravitational/teleport:$(cat "/go/var/major-version") --amend quay.io/gravitational/teleport:$(cat "/go/var/major-version")-amd64 --amend quay.io/gravitational/teleport:$(cat "/go/var/major-version")-arm --amend quay.io/gravitational/teleport:$(cat @@ -11520,6 +12485,10 @@ steps: - docker manifest push quay.io/gravitational/teleport:$(cat "/go/var/major-version") - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -11537,6 +12506,7 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create quay.io/gravitational/teleport:$(cat "/go/var/minor-version") --amend quay.io/gravitational/teleport:$(cat "/go/var/minor-version")-amd64 --amend quay.io/gravitational/teleport:$(cat "/go/var/minor-version")-arm --amend quay.io/gravitational/teleport:$(cat @@ -11544,6 +12514,10 @@ steps: - docker manifest push quay.io/gravitational/teleport:$(cat "/go/var/minor-version") - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -11561,6 +12535,7 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport:$(cat "/go/var/full-version") > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker manifest create quay.io/gravitational/teleport:$(cat "/go/var/full-version") --amend quay.io/gravitational/teleport:$(cat @@ -11569,6 +12544,10 @@ steps: docker manifest push quay.io/gravitational/teleport:$(cat "/go/var/full-version")) - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -11590,6 +12569,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport:$(cat "/go/var/full-version")-amd64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-amd64 public.ecr.aws/gravitational/teleport:$(cat "/go/var/full-version")-amd64 @@ -11603,6 +12583,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -11617,6 +12601,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport:$(cat "/go/var/full-version")-arm > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-arm public.ecr.aws/gravitational/teleport:$(cat "/go/var/full-version")-arm @@ -11630,6 +12615,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -11645,6 +12634,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport:$(cat "/go/var/full-version")-arm64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-arm64 public.ecr.aws/gravitational/teleport:$(cat "/go/var/full-version")-arm64 @@ -11658,6 +12648,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -11671,6 +12665,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create public.ecr.aws/gravitational/teleport:$(cat "/go/var/major-version") --amend public.ecr.aws/gravitational/teleport:$(cat "/go/var/major-version")-amd64 --amend public.ecr.aws/gravitational/teleport:$(cat "/go/var/major-version")-arm @@ -11679,6 +12674,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -11694,6 +12693,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create public.ecr.aws/gravitational/teleport:$(cat "/go/var/minor-version") --amend public.ecr.aws/gravitational/teleport:$(cat "/go/var/minor-version")-amd64 --amend public.ecr.aws/gravitational/teleport:$(cat "/go/var/minor-version")-arm @@ -11702,6 +12702,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -11717,6 +12721,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport:$(cat "/go/var/full-version") > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker manifest create public.ecr.aws/gravitational/teleport:$(cat "/go/var/full-version") --amend @@ -11727,6 +12732,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -11738,6 +12747,7 @@ steps: - Tag and push image "teleport:v12-arm64" to ECR - production - name: Assume S3 Download AWS Role for teleport-ent image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -11832,6 +12842,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-ent-v12-amd64-builder" --target "teleport" --platform "linux/amd64" --tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-amd64 --file "/go/build/Dockerfile-teleport-ent" --build-arg @@ -11842,6 +12853,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -11893,6 +12908,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-ent-v12-arm-builder" --target "teleport" --platform "linux/arm" --tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-arm --file "/go/build/Dockerfile-teleport-ent" --build-arg DEB_PATH=teleport-ent_$(cat @@ -11903,6 +12919,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -11954,6 +12974,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-ent-v12-arm64-builder" --target "teleport" --platform "linux/arm64" --tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-arm64 --file "/go/build/Dockerfile-teleport-ent" --build-arg @@ -11964,6 +12985,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -11980,6 +13005,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/full-version")-$TIMESTAMP-amd64 > /dev/null 2>&1 && echo 'Found existing @@ -12002,6 +13028,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -12017,6 +13047,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/full-version")-$TIMESTAMP-arm > /dev/null 2>&1 && echo 'Found existing @@ -12039,6 +13070,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -12054,6 +13089,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/full-version")-$TIMESTAMP-arm64 > /dev/null 2>&1 && echo 'Found existing @@ -12076,6 +13112,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -12089,6 +13129,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/major-version")-$TIMESTAMP > /dev/null 2>&1 && echo 'Found existing image, @@ -12101,6 +13142,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -12116,6 +13161,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/minor-version")-$TIMESTAMP > /dev/null 2>&1 && echo 'Found existing image, @@ -12128,6 +13174,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -12143,6 +13193,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/full-version")-$TIMESTAMP > /dev/null 2>&1 && echo 'Found existing image, @@ -12155,6 +13206,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -12170,6 +13225,7 @@ steps: - docker pull --platform "linux/amd64" drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-amd64 - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-amd64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-amd64 quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-amd64 @@ -12182,6 +13238,10 @@ steps: - docker push quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version")-amd64 - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -12199,6 +13259,7 @@ steps: - docker pull --platform "linux/arm" drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-arm - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-arm > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-arm quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-arm @@ -12211,6 +13272,10 @@ steps: - docker push quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version")-arm - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -12228,6 +13293,7 @@ steps: - docker pull --platform "linux/arm64" drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-arm64 - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-arm64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-arm64 quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-arm64 @@ -12240,6 +13306,10 @@ steps: - docker push quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version")-arm64 - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -12255,6 +13325,7 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create quay.io/gravitational/teleport-ent:$(cat "/go/var/major-version") --amend quay.io/gravitational/teleport-ent:$(cat "/go/var/major-version")-amd64 --amend quay.io/gravitational/teleport-ent:$(cat "/go/var/major-version")-arm @@ -12262,6 +13333,10 @@ steps: - docker manifest push quay.io/gravitational/teleport-ent:$(cat "/go/var/major-version") - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -12279,6 +13354,7 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version") --amend quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version")-amd64 --amend quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version")-arm @@ -12286,6 +13362,10 @@ steps: - docker manifest push quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version") - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -12303,6 +13383,7 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version") > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker manifest create quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version") --amend @@ -12312,6 +13393,10 @@ steps: "/go/var/full-version")) - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -12333,6 +13418,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/full-version")-amd64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-amd64 public.ecr.aws/gravitational/teleport-ent:$(cat @@ -12347,6 +13433,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -12362,6 +13452,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/full-version")-arm > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-arm public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/full-version")-arm @@ -12375,6 +13466,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -12390,6 +13485,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/full-version")-arm64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-arm64 public.ecr.aws/gravitational/teleport-ent:$(cat @@ -12404,6 +13500,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -12417,6 +13517,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/major-version") --amend public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/major-version")-amd64 --amend public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/major-version")-arm @@ -12425,6 +13526,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -12440,6 +13545,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/minor-version") --amend public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/minor-version")-amd64 --amend public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/minor-version")-arm @@ -12448,6 +13554,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -12463,6 +13573,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/full-version") > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker manifest create public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/full-version") @@ -12473,6 +13584,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -12484,6 +13599,7 @@ steps: - Tag and push image "teleport-ent:v12-arm64" to ECR - production - name: Assume S3 Download AWS Role for teleport-ent-fips image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -12579,6 +13695,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-ent-v12-fips-amd64-builder" --target "teleport-fips" --platform "linux/amd64" --tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-fips-amd64 --file "/go/build/Dockerfile-teleport-ent-fips" @@ -12590,6 +13707,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -12606,6 +13727,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/full-version")-$TIMESTAMP-fips-amd64 > /dev/null 2>&1 && echo 'Found @@ -12628,6 +13750,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -12641,6 +13767,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/major-version")-$TIMESTAMP-fips > /dev/null 2>&1 && echo 'Found existing @@ -12651,6 +13778,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -12664,6 +13795,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/minor-version")-$TIMESTAMP-fips > /dev/null 2>&1 && echo 'Found existing @@ -12674,6 +13806,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -12687,6 +13823,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/full-version")-$TIMESTAMP-fips > /dev/null 2>&1 && echo 'Found existing @@ -12697,6 +13834,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -12710,6 +13851,7 @@ steps: - docker pull --platform "linux/amd64" drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-fips-amd64 - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-fips-amd64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-fips-amd64 quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-fips-amd64 @@ -12722,6 +13864,10 @@ steps: - docker push quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version")-fips-amd64 - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -12737,11 +13883,16 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create quay.io/gravitational/teleport-ent:$(cat "/go/var/major-version")-fips --amend quay.io/gravitational/teleport-ent:$(cat "/go/var/major-version")-fips-amd64 - docker manifest push quay.io/gravitational/teleport-ent:$(cat "/go/var/major-version")-fips - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -12757,11 +13908,16 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version")-fips --amend quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version")-fips-amd64 - docker manifest push quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version")-fips - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -12777,6 +13933,7 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-fips > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker manifest create quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-fips --amend @@ -12784,6 +13941,10 @@ steps: docker manifest push quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-fips) - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -12803,6 +13964,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/full-version")-fips-amd64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-fips-amd64 public.ecr.aws/gravitational/teleport-ent:$(cat @@ -12817,6 +13979,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -12830,12 +13996,17 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/major-version")-fips --amend public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/major-version")-fips-amd64 - docker manifest push public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/major-version")-fips - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -12849,12 +14020,17 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/minor-version")-fips --amend public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/minor-version")-fips-amd64 - docker manifest push public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/minor-version")-fips - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -12868,6 +14044,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/full-version")-fips > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker manifest create public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/full-version")-fips @@ -12876,6 +14053,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -12896,6 +14077,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-operator-v12-amd64-builder" --platform "linux/amd64" --tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-amd64 --file "/go/src/github.com/gravitational/teleport/integrations/operator/Dockerfile" @@ -12907,6 +14089,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -12935,6 +14121,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-operator-v12-arm-builder" --platform "linux/arm" --tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-arm --file "/go/src/github.com/gravitational/teleport/integrations/operator/Dockerfile" @@ -12946,6 +14133,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -12974,6 +14165,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-operator-v12-arm64-builder" --platform "linux/arm64" --tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-arm64 --file "/go/src/github.com/gravitational/teleport/integrations/operator/Dockerfile" @@ -12985,6 +14177,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -13008,6 +14204,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-operator:$(cat "/go/var/full-version")-$TIMESTAMP-amd64 > /dev/null 2>&1 && echo 'Found existing @@ -13030,6 +14227,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -13045,6 +14246,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-operator:$(cat "/go/var/full-version")-$TIMESTAMP-arm > /dev/null 2>&1 && echo 'Found existing @@ -13067,6 +14269,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -13082,6 +14288,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-operator:$(cat "/go/var/full-version")-$TIMESTAMP-arm64 > /dev/null 2>&1 && echo 'Found existing @@ -13104,6 +14311,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -13117,6 +14328,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-operator:$(cat "/go/var/major-version")-$TIMESTAMP > /dev/null 2>&1 && echo 'Found existing image, @@ -13129,6 +14341,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -13144,6 +14360,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-operator:$(cat "/go/var/minor-version")-$TIMESTAMP > /dev/null 2>&1 && echo 'Found existing image, @@ -13156,6 +14373,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -13171,6 +14392,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-operator:$(cat "/go/var/full-version")-$TIMESTAMP > /dev/null 2>&1 && echo 'Found existing image, @@ -13183,6 +14405,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -13198,6 +14424,7 @@ steps: - docker pull --platform "linux/amd64" drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-amd64 - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-operator:$(cat "/go/var/full-version")-amd64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-amd64 quay.io/gravitational/teleport-operator:$(cat "/go/var/full-version")-amd64 @@ -13210,6 +14437,10 @@ steps: - docker push quay.io/gravitational/teleport-operator:$(cat "/go/var/minor-version")-amd64 - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -13227,6 +14458,7 @@ steps: - docker pull --platform "linux/arm" drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-arm - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-operator:$(cat "/go/var/full-version")-arm > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-arm quay.io/gravitational/teleport-operator:$(cat "/go/var/full-version")-arm @@ -13239,6 +14471,10 @@ steps: - docker push quay.io/gravitational/teleport-operator:$(cat "/go/var/minor-version")-arm - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -13256,6 +14492,7 @@ steps: - docker pull --platform "linux/arm64" drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-arm64 - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-operator:$(cat "/go/var/full-version")-arm64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-arm64 quay.io/gravitational/teleport-operator:$(cat "/go/var/full-version")-arm64 @@ -13268,6 +14505,10 @@ steps: - docker push quay.io/gravitational/teleport-operator:$(cat "/go/var/minor-version")-arm64 - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -13283,6 +14524,7 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create quay.io/gravitational/teleport-operator:$(cat "/go/var/major-version") --amend quay.io/gravitational/teleport-operator:$(cat "/go/var/major-version")-amd64 --amend quay.io/gravitational/teleport-operator:$(cat "/go/var/major-version")-arm @@ -13290,6 +14532,10 @@ steps: - docker manifest push quay.io/gravitational/teleport-operator:$(cat "/go/var/major-version") - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -13307,6 +14553,7 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create quay.io/gravitational/teleport-operator:$(cat "/go/var/minor-version") --amend quay.io/gravitational/teleport-operator:$(cat "/go/var/minor-version")-amd64 --amend quay.io/gravitational/teleport-operator:$(cat "/go/var/minor-version")-arm @@ -13314,6 +14561,10 @@ steps: - docker manifest push quay.io/gravitational/teleport-operator:$(cat "/go/var/minor-version") - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -13331,6 +14582,7 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-operator:$(cat "/go/var/full-version") > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker manifest create quay.io/gravitational/teleport-operator:$(cat "/go/var/full-version") --amend @@ -13340,6 +14592,10 @@ steps: docker manifest push quay.io/gravitational/teleport-operator:$(cat "/go/var/full-version")) - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -13361,6 +14617,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/full-version")-amd64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-amd64 public.ecr.aws/gravitational/teleport-operator:$(cat @@ -13375,6 +14632,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -13390,6 +14651,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/full-version")-arm > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-arm public.ecr.aws/gravitational/teleport-operator:$(cat @@ -13404,6 +14666,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -13419,6 +14685,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/full-version")-arm64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-arm64 public.ecr.aws/gravitational/teleport-operator:$(cat @@ -13433,6 +14700,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -13446,6 +14717,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/major-version") --amend public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/major-version")-amd64 --amend public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/major-version")-arm @@ -13454,6 +14726,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -13469,6 +14745,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/minor-version") --amend public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/minor-version")-amd64 --amend public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/minor-version")-arm @@ -13477,6 +14754,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -13492,6 +14773,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/full-version") > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker manifest create public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/full-version") @@ -13502,6 +14784,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -13527,6 +14813,10 @@ volumes: temp: {} - name: dockersock temp: {} +- name: dockerconfig + temp: {} +image_pull_secrets: +- DOCKERHUB_CREDENTIALS --- ################################################ @@ -13568,15 +14858,25 @@ steps: "v11" - name: Wait for docker image: docker + pull: if-not-exists commands: - timeout 30s /bin/sh -c 'while [ ! -S /var/run/docker.sock ]; do sleep 1; done' + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin + environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker depends_on: - Find the latest available semver for v11 - name: Wait for docker registry image: alpine + pull: if-not-exists commands: - apk add curl - timeout 30s /bin/sh -c 'while [ "$(curl -s -o /dev/null -w %{http_code} http://drone-docker-registry:5000/)" @@ -13585,6 +14885,7 @@ steps: - Find the latest available semver for v11 - name: Check out code image: alpine/git:latest + pull: if-not-exists commands: - mkdir -pv "/go/src/github.com/gravitational/teleport" - cd "/go/src/github.com/gravitational/teleport" @@ -13611,6 +14912,7 @@ steps: - Find the latest available semver for v11 - name: Assume ECR - staging AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -13637,6 +14939,7 @@ steps: - Find the latest available semver for v11 - name: Assume ECR - authenticated-pull AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -13664,6 +14967,7 @@ steps: - Find the latest available semver for v11 - name: Assume ECR - production AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -13691,6 +14995,7 @@ steps: - Find the latest available semver for v11 - name: Assume S3 Download AWS Role for teleport image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -13785,6 +15090,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-v11-amd64-builder" --target "teleport" --platform "linux/amd64" --tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-amd64 --file "/go/build/Dockerfile-teleport" --build-arg DEB_PATH=teleport_$(cat "/go/var/full-version")_amd64.deb @@ -13795,6 +15101,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -13846,6 +15156,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-v11-arm-builder" --target "teleport" --platform "linux/arm" --tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-arm --file "/go/build/Dockerfile-teleport" --build-arg DEB_PATH=teleport_$(cat "/go/var/full-version")_arm.deb @@ -13856,6 +15167,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -13907,6 +15222,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-v11-arm64-builder" --target "teleport" --platform "linux/arm64" --tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-arm64 --file "/go/build/Dockerfile-teleport" --build-arg DEB_PATH=teleport_$(cat "/go/var/full-version")_arm64.deb @@ -13917,6 +15233,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -13933,6 +15253,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport:$(cat "/go/var/full-version")-$TIMESTAMP-amd64 > /dev/null 2>&1 && echo 'Found existing @@ -13955,6 +15276,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -13969,6 +15294,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport:$(cat "/go/var/full-version")-$TIMESTAMP-arm > /dev/null 2>&1 && echo 'Found existing @@ -13991,6 +15317,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -14006,6 +15336,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport:$(cat "/go/var/full-version")-$TIMESTAMP-arm64 > /dev/null 2>&1 && echo 'Found existing @@ -14028,6 +15359,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -14041,6 +15376,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport:$(cat "/go/var/major-version")-$TIMESTAMP > /dev/null 2>&1 && echo 'Found existing image, @@ -14053,6 +15389,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -14068,6 +15408,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport:$(cat "/go/var/minor-version")-$TIMESTAMP > /dev/null 2>&1 && echo 'Found existing image, @@ -14080,6 +15421,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -14095,6 +15440,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport:$(cat "/go/var/full-version")-$TIMESTAMP > /dev/null 2>&1 && echo 'Found existing image, @@ -14107,6 +15453,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -14122,6 +15472,7 @@ steps: - docker pull --platform "linux/amd64" drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-amd64 - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport:$(cat "/go/var/full-version")-amd64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-amd64 quay.io/gravitational/teleport:$(cat "/go/var/full-version")-amd64 @@ -14134,6 +15485,10 @@ steps: - docker push quay.io/gravitational/teleport:$(cat "/go/var/minor-version")-amd64 - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -14150,6 +15505,7 @@ steps: commands: - docker pull --platform "linux/arm" drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-arm - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport:$(cat "/go/var/full-version")-arm > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-arm quay.io/gravitational/teleport:$(cat "/go/var/full-version")-arm @@ -14162,6 +15518,10 @@ steps: - docker push quay.io/gravitational/teleport:$(cat "/go/var/minor-version")-arm - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -14179,6 +15539,7 @@ steps: - docker pull --platform "linux/arm64" drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-arm64 - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport:$(cat "/go/var/full-version")-arm64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-arm64 quay.io/gravitational/teleport:$(cat "/go/var/full-version")-arm64 @@ -14191,6 +15552,10 @@ steps: - docker push quay.io/gravitational/teleport:$(cat "/go/var/minor-version")-arm64 - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -14206,6 +15571,7 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create quay.io/gravitational/teleport:$(cat "/go/var/major-version") --amend quay.io/gravitational/teleport:$(cat "/go/var/major-version")-amd64 --amend quay.io/gravitational/teleport:$(cat "/go/var/major-version")-arm --amend quay.io/gravitational/teleport:$(cat @@ -14213,6 +15579,10 @@ steps: - docker manifest push quay.io/gravitational/teleport:$(cat "/go/var/major-version") - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -14230,6 +15600,7 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create quay.io/gravitational/teleport:$(cat "/go/var/minor-version") --amend quay.io/gravitational/teleport:$(cat "/go/var/minor-version")-amd64 --amend quay.io/gravitational/teleport:$(cat "/go/var/minor-version")-arm --amend quay.io/gravitational/teleport:$(cat @@ -14237,6 +15608,10 @@ steps: - docker manifest push quay.io/gravitational/teleport:$(cat "/go/var/minor-version") - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -14254,6 +15629,7 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport:$(cat "/go/var/full-version") > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker manifest create quay.io/gravitational/teleport:$(cat "/go/var/full-version") --amend quay.io/gravitational/teleport:$(cat @@ -14262,6 +15638,10 @@ steps: docker manifest push quay.io/gravitational/teleport:$(cat "/go/var/full-version")) - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -14283,6 +15663,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport:$(cat "/go/var/full-version")-amd64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-amd64 public.ecr.aws/gravitational/teleport:$(cat "/go/var/full-version")-amd64 @@ -14296,6 +15677,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -14310,6 +15695,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport:$(cat "/go/var/full-version")-arm > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-arm public.ecr.aws/gravitational/teleport:$(cat "/go/var/full-version")-arm @@ -14323,6 +15709,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -14338,6 +15728,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport:$(cat "/go/var/full-version")-arm64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-arm64 public.ecr.aws/gravitational/teleport:$(cat "/go/var/full-version")-arm64 @@ -14351,6 +15742,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -14364,6 +15759,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create public.ecr.aws/gravitational/teleport:$(cat "/go/var/major-version") --amend public.ecr.aws/gravitational/teleport:$(cat "/go/var/major-version")-amd64 --amend public.ecr.aws/gravitational/teleport:$(cat "/go/var/major-version")-arm @@ -14372,6 +15768,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -14387,6 +15787,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create public.ecr.aws/gravitational/teleport:$(cat "/go/var/minor-version") --amend public.ecr.aws/gravitational/teleport:$(cat "/go/var/minor-version")-amd64 --amend public.ecr.aws/gravitational/teleport:$(cat "/go/var/minor-version")-arm @@ -14395,6 +15796,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -14410,6 +15815,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport:$(cat "/go/var/full-version") > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker manifest create public.ecr.aws/gravitational/teleport:$(cat "/go/var/full-version") --amend @@ -14420,6 +15826,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -14431,6 +15841,7 @@ steps: - Tag and push image "teleport:v11-arm64" to ECR - production - name: Assume S3 Download AWS Role for teleport-ent image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -14525,6 +15936,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-ent-v11-amd64-builder" --target "teleport" --platform "linux/amd64" --tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-amd64 --file "/go/build/Dockerfile-teleport-ent" --build-arg @@ -14535,6 +15947,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -14586,6 +16002,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-ent-v11-arm-builder" --target "teleport" --platform "linux/arm" --tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-arm --file "/go/build/Dockerfile-teleport-ent" --build-arg DEB_PATH=teleport-ent_$(cat @@ -14596,6 +16013,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -14647,6 +16068,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-ent-v11-arm64-builder" --target "teleport" --platform "linux/arm64" --tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-arm64 --file "/go/build/Dockerfile-teleport-ent" --build-arg @@ -14657,6 +16079,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -14673,6 +16099,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/full-version")-$TIMESTAMP-amd64 > /dev/null 2>&1 && echo 'Found existing @@ -14695,6 +16122,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -14710,6 +16141,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/full-version")-$TIMESTAMP-arm > /dev/null 2>&1 && echo 'Found existing @@ -14732,6 +16164,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -14747,6 +16183,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/full-version")-$TIMESTAMP-arm64 > /dev/null 2>&1 && echo 'Found existing @@ -14769,6 +16206,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -14782,6 +16223,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/major-version")-$TIMESTAMP > /dev/null 2>&1 && echo 'Found existing image, @@ -14794,6 +16236,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -14809,6 +16255,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/minor-version")-$TIMESTAMP > /dev/null 2>&1 && echo 'Found existing image, @@ -14821,6 +16268,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -14836,6 +16287,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/full-version")-$TIMESTAMP > /dev/null 2>&1 && echo 'Found existing image, @@ -14848,6 +16300,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -14863,6 +16319,7 @@ steps: - docker pull --platform "linux/amd64" drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-amd64 - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-amd64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-amd64 quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-amd64 @@ -14875,6 +16332,10 @@ steps: - docker push quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version")-amd64 - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -14892,6 +16353,7 @@ steps: - docker pull --platform "linux/arm" drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-arm - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-arm > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-arm quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-arm @@ -14904,6 +16366,10 @@ steps: - docker push quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version")-arm - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -14921,6 +16387,7 @@ steps: - docker pull --platform "linux/arm64" drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-arm64 - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-arm64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-arm64 quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-arm64 @@ -14933,6 +16400,10 @@ steps: - docker push quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version")-arm64 - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -14948,6 +16419,7 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create quay.io/gravitational/teleport-ent:$(cat "/go/var/major-version") --amend quay.io/gravitational/teleport-ent:$(cat "/go/var/major-version")-amd64 --amend quay.io/gravitational/teleport-ent:$(cat "/go/var/major-version")-arm @@ -14955,6 +16427,10 @@ steps: - docker manifest push quay.io/gravitational/teleport-ent:$(cat "/go/var/major-version") - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -14972,6 +16448,7 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version") --amend quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version")-amd64 --amend quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version")-arm @@ -14979,6 +16456,10 @@ steps: - docker manifest push quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version") - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -14996,6 +16477,7 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version") > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker manifest create quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version") --amend @@ -15005,6 +16487,10 @@ steps: "/go/var/full-version")) - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -15026,6 +16512,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/full-version")-amd64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-amd64 public.ecr.aws/gravitational/teleport-ent:$(cat @@ -15040,6 +16527,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -15055,6 +16546,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/full-version")-arm > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-arm public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/full-version")-arm @@ -15068,6 +16560,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -15083,6 +16579,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/full-version")-arm64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-arm64 public.ecr.aws/gravitational/teleport-ent:$(cat @@ -15097,6 +16594,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -15110,6 +16611,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/major-version") --amend public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/major-version")-amd64 --amend public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/major-version")-arm @@ -15118,6 +16620,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -15133,6 +16639,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/minor-version") --amend public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/minor-version")-amd64 --amend public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/minor-version")-arm @@ -15141,6 +16648,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -15156,6 +16667,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/full-version") > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker manifest create public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/full-version") @@ -15166,6 +16678,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -15177,6 +16693,7 @@ steps: - Tag and push image "teleport-ent:v11-arm64" to ECR - production - name: Assume S3 Download AWS Role for teleport-ent-fips image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -15272,6 +16789,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-ent-v11-fips-amd64-builder" --target "teleport-fips" --platform "linux/amd64" --tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-fips-amd64 --file "/go/build/Dockerfile-teleport-ent-fips" @@ -15283,6 +16801,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -15299,6 +16821,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/full-version")-$TIMESTAMP-fips-amd64 > /dev/null 2>&1 && echo 'Found @@ -15321,6 +16844,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -15334,6 +16861,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/major-version")-$TIMESTAMP-fips > /dev/null 2>&1 && echo 'Found existing @@ -15344,6 +16872,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -15357,6 +16889,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/minor-version")-$TIMESTAMP-fips > /dev/null 2>&1 && echo 'Found existing @@ -15367,6 +16900,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -15380,6 +16917,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/full-version")-$TIMESTAMP-fips > /dev/null 2>&1 && echo 'Found existing @@ -15390,6 +16928,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -15403,6 +16945,7 @@ steps: - docker pull --platform "linux/amd64" drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-fips-amd64 - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-fips-amd64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-fips-amd64 quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-fips-amd64 @@ -15415,6 +16958,10 @@ steps: - docker push quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version")-fips-amd64 - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -15430,11 +16977,16 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create quay.io/gravitational/teleport-ent:$(cat "/go/var/major-version")-fips --amend quay.io/gravitational/teleport-ent:$(cat "/go/var/major-version")-fips-amd64 - docker manifest push quay.io/gravitational/teleport-ent:$(cat "/go/var/major-version")-fips - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -15450,11 +17002,16 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version")-fips --amend quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version")-fips-amd64 - docker manifest push quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version")-fips - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -15470,6 +17027,7 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-fips > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker manifest create quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-fips --amend @@ -15477,6 +17035,10 @@ steps: docker manifest push quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-fips) - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -15496,6 +17058,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/full-version")-fips-amd64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-fips-amd64 public.ecr.aws/gravitational/teleport-ent:$(cat @@ -15510,6 +17073,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -15523,12 +17090,17 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/major-version")-fips --amend public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/major-version")-fips-amd64 - docker manifest push public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/major-version")-fips - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -15542,12 +17114,17 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/minor-version")-fips --amend public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/minor-version")-fips-amd64 - docker manifest push public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/minor-version")-fips - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -15561,6 +17138,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/full-version")-fips > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker manifest create public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/full-version")-fips @@ -15569,6 +17147,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -15589,6 +17171,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-operator-v11-amd64-builder" --platform "linux/amd64" --tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-amd64 --file "/go/src/github.com/gravitational/teleport/integrations/operator/Dockerfile" @@ -15600,6 +17183,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -15628,6 +17215,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-operator-v11-arm-builder" --platform "linux/arm" --tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-arm --file "/go/src/github.com/gravitational/teleport/integrations/operator/Dockerfile" @@ -15639,6 +17227,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -15667,6 +17259,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-operator-v11-arm64-builder" --platform "linux/arm64" --tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-arm64 --file "/go/src/github.com/gravitational/teleport/integrations/operator/Dockerfile" @@ -15678,6 +17271,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -15701,6 +17298,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-operator:$(cat "/go/var/full-version")-$TIMESTAMP-amd64 > /dev/null 2>&1 && echo 'Found existing @@ -15723,6 +17321,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -15738,6 +17340,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-operator:$(cat "/go/var/full-version")-$TIMESTAMP-arm > /dev/null 2>&1 && echo 'Found existing @@ -15760,6 +17363,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -15775,6 +17382,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-operator:$(cat "/go/var/full-version")-$TIMESTAMP-arm64 > /dev/null 2>&1 && echo 'Found existing @@ -15797,6 +17405,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -15810,6 +17422,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-operator:$(cat "/go/var/major-version")-$TIMESTAMP > /dev/null 2>&1 && echo 'Found existing image, @@ -15822,6 +17435,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -15837,6 +17454,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-operator:$(cat "/go/var/minor-version")-$TIMESTAMP > /dev/null 2>&1 && echo 'Found existing image, @@ -15849,6 +17467,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -15864,6 +17486,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-operator:$(cat "/go/var/full-version")-$TIMESTAMP > /dev/null 2>&1 && echo 'Found existing image, @@ -15876,6 +17499,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -15891,6 +17518,7 @@ steps: - docker pull --platform "linux/amd64" drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-amd64 - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-operator:$(cat "/go/var/full-version")-amd64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-amd64 quay.io/gravitational/teleport-operator:$(cat "/go/var/full-version")-amd64 @@ -15903,6 +17531,10 @@ steps: - docker push quay.io/gravitational/teleport-operator:$(cat "/go/var/minor-version")-amd64 - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -15920,6 +17552,7 @@ steps: - docker pull --platform "linux/arm" drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-arm - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-operator:$(cat "/go/var/full-version")-arm > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-arm quay.io/gravitational/teleport-operator:$(cat "/go/var/full-version")-arm @@ -15932,6 +17565,10 @@ steps: - docker push quay.io/gravitational/teleport-operator:$(cat "/go/var/minor-version")-arm - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -15949,6 +17586,7 @@ steps: - docker pull --platform "linux/arm64" drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-arm64 - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-operator:$(cat "/go/var/full-version")-arm64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-arm64 quay.io/gravitational/teleport-operator:$(cat "/go/var/full-version")-arm64 @@ -15961,6 +17599,10 @@ steps: - docker push quay.io/gravitational/teleport-operator:$(cat "/go/var/minor-version")-arm64 - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -15976,6 +17618,7 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create quay.io/gravitational/teleport-operator:$(cat "/go/var/major-version") --amend quay.io/gravitational/teleport-operator:$(cat "/go/var/major-version")-amd64 --amend quay.io/gravitational/teleport-operator:$(cat "/go/var/major-version")-arm @@ -15983,6 +17626,10 @@ steps: - docker manifest push quay.io/gravitational/teleport-operator:$(cat "/go/var/major-version") - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -16000,6 +17647,7 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create quay.io/gravitational/teleport-operator:$(cat "/go/var/minor-version") --amend quay.io/gravitational/teleport-operator:$(cat "/go/var/minor-version")-amd64 --amend quay.io/gravitational/teleport-operator:$(cat "/go/var/minor-version")-arm @@ -16007,6 +17655,10 @@ steps: - docker manifest push quay.io/gravitational/teleport-operator:$(cat "/go/var/minor-version") - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -16024,6 +17676,7 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-operator:$(cat "/go/var/full-version") > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker manifest create quay.io/gravitational/teleport-operator:$(cat "/go/var/full-version") --amend @@ -16033,6 +17686,10 @@ steps: docker manifest push quay.io/gravitational/teleport-operator:$(cat "/go/var/full-version")) - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -16054,6 +17711,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/full-version")-amd64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-amd64 public.ecr.aws/gravitational/teleport-operator:$(cat @@ -16068,6 +17726,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -16083,6 +17745,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/full-version")-arm > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-arm public.ecr.aws/gravitational/teleport-operator:$(cat @@ -16097,6 +17760,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -16112,6 +17779,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/full-version")-arm64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-arm64 public.ecr.aws/gravitational/teleport-operator:$(cat @@ -16126,6 +17794,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -16139,6 +17811,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/major-version") --amend public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/major-version")-amd64 --amend public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/major-version")-arm @@ -16147,6 +17820,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -16162,6 +17839,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/minor-version") --amend public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/minor-version")-amd64 --amend public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/minor-version")-arm @@ -16170,6 +17848,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -16185,6 +17867,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/full-version") > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker manifest create public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/full-version") @@ -16195,6 +17878,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -16220,6 +17907,10 @@ volumes: temp: {} - name: dockersock temp: {} +- name: dockerconfig + temp: {} +image_pull_secrets: +- DOCKERHUB_CREDENTIALS --- ################################################ @@ -16261,15 +17952,25 @@ steps: "v10" - name: Wait for docker image: docker + pull: if-not-exists commands: - timeout 30s /bin/sh -c 'while [ ! -S /var/run/docker.sock ]; do sleep 1; done' + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin + environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker depends_on: - Find the latest available semver for v10 - name: Wait for docker registry image: alpine + pull: if-not-exists commands: - apk add curl - timeout 30s /bin/sh -c 'while [ "$(curl -s -o /dev/null -w %{http_code} http://drone-docker-registry:5000/)" @@ -16278,6 +17979,7 @@ steps: - Find the latest available semver for v10 - name: Check out code image: alpine/git:latest + pull: if-not-exists commands: - mkdir -pv "/go/src/github.com/gravitational/teleport" - cd "/go/src/github.com/gravitational/teleport" @@ -16304,6 +18006,7 @@ steps: - Find the latest available semver for v10 - name: Assume ECR - staging AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -16330,6 +18033,7 @@ steps: - Find the latest available semver for v10 - name: Assume ECR - authenticated-pull AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -16357,6 +18061,7 @@ steps: - Find the latest available semver for v10 - name: Assume ECR - production AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -16384,6 +18089,7 @@ steps: - Find the latest available semver for v10 - name: Assume S3 Download AWS Role for teleport image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -16478,6 +18184,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-v10-amd64-builder" --target "teleport" --platform "linux/amd64" --tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-amd64 --file "/go/build/Dockerfile-teleport" --build-arg DEB_PATH=teleport_$(cat "/go/var/full-version")_amd64.deb @@ -16488,6 +18195,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -16539,6 +18250,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-v10-arm-builder" --target "teleport" --platform "linux/arm" --tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-arm --file "/go/build/Dockerfile-teleport" --build-arg DEB_PATH=teleport_$(cat "/go/var/full-version")_arm.deb @@ -16549,6 +18261,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -16600,6 +18316,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-v10-arm64-builder" --target "teleport" --platform "linux/arm64" --tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-arm64 --file "/go/build/Dockerfile-teleport" --build-arg DEB_PATH=teleport_$(cat "/go/var/full-version")_arm64.deb @@ -16610,6 +18327,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -16626,6 +18347,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport:$(cat "/go/var/full-version")-$TIMESTAMP-amd64 > /dev/null 2>&1 && echo 'Found existing @@ -16648,6 +18370,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -16662,6 +18388,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport:$(cat "/go/var/full-version")-$TIMESTAMP-arm > /dev/null 2>&1 && echo 'Found existing @@ -16684,6 +18411,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -16699,6 +18430,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport:$(cat "/go/var/full-version")-$TIMESTAMP-arm64 > /dev/null 2>&1 && echo 'Found existing @@ -16721,6 +18453,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -16734,6 +18470,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport:$(cat "/go/var/major-version")-$TIMESTAMP > /dev/null 2>&1 && echo 'Found existing image, @@ -16746,6 +18483,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -16761,6 +18502,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport:$(cat "/go/var/minor-version")-$TIMESTAMP > /dev/null 2>&1 && echo 'Found existing image, @@ -16773,6 +18515,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -16788,6 +18534,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport:$(cat "/go/var/full-version")-$TIMESTAMP > /dev/null 2>&1 && echo 'Found existing image, @@ -16800,6 +18547,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -16815,6 +18566,7 @@ steps: - docker pull --platform "linux/amd64" drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-amd64 - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport:$(cat "/go/var/full-version")-amd64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-amd64 quay.io/gravitational/teleport:$(cat "/go/var/full-version")-amd64 @@ -16827,6 +18579,10 @@ steps: - docker push quay.io/gravitational/teleport:$(cat "/go/var/minor-version")-amd64 - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -16843,6 +18599,7 @@ steps: commands: - docker pull --platform "linux/arm" drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-arm - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport:$(cat "/go/var/full-version")-arm > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-arm quay.io/gravitational/teleport:$(cat "/go/var/full-version")-arm @@ -16855,6 +18612,10 @@ steps: - docker push quay.io/gravitational/teleport:$(cat "/go/var/minor-version")-arm - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -16872,6 +18633,7 @@ steps: - docker pull --platform "linux/arm64" drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-arm64 - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport:$(cat "/go/var/full-version")-arm64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-arm64 quay.io/gravitational/teleport:$(cat "/go/var/full-version")-arm64 @@ -16884,6 +18646,10 @@ steps: - docker push quay.io/gravitational/teleport:$(cat "/go/var/minor-version")-arm64 - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -16899,6 +18665,7 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create quay.io/gravitational/teleport:$(cat "/go/var/major-version") --amend quay.io/gravitational/teleport:$(cat "/go/var/major-version")-amd64 --amend quay.io/gravitational/teleport:$(cat "/go/var/major-version")-arm --amend quay.io/gravitational/teleport:$(cat @@ -16906,6 +18673,10 @@ steps: - docker manifest push quay.io/gravitational/teleport:$(cat "/go/var/major-version") - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -16923,6 +18694,7 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create quay.io/gravitational/teleport:$(cat "/go/var/minor-version") --amend quay.io/gravitational/teleport:$(cat "/go/var/minor-version")-amd64 --amend quay.io/gravitational/teleport:$(cat "/go/var/minor-version")-arm --amend quay.io/gravitational/teleport:$(cat @@ -16930,6 +18702,10 @@ steps: - docker manifest push quay.io/gravitational/teleport:$(cat "/go/var/minor-version") - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -16947,6 +18723,7 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport:$(cat "/go/var/full-version") > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker manifest create quay.io/gravitational/teleport:$(cat "/go/var/full-version") --amend quay.io/gravitational/teleport:$(cat @@ -16955,6 +18732,10 @@ steps: docker manifest push quay.io/gravitational/teleport:$(cat "/go/var/full-version")) - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -16976,6 +18757,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport:$(cat "/go/var/full-version")-amd64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-amd64 public.ecr.aws/gravitational/teleport:$(cat "/go/var/full-version")-amd64 @@ -16989,6 +18771,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -17003,6 +18789,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport:$(cat "/go/var/full-version")-arm > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-arm public.ecr.aws/gravitational/teleport:$(cat "/go/var/full-version")-arm @@ -17016,6 +18803,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -17031,6 +18822,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport:$(cat "/go/var/full-version")-arm64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport:$(cat "/go/var/full-version")-arm64 public.ecr.aws/gravitational/teleport:$(cat "/go/var/full-version")-arm64 @@ -17044,6 +18836,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -17057,6 +18853,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create public.ecr.aws/gravitational/teleport:$(cat "/go/var/major-version") --amend public.ecr.aws/gravitational/teleport:$(cat "/go/var/major-version")-amd64 --amend public.ecr.aws/gravitational/teleport:$(cat "/go/var/major-version")-arm @@ -17065,6 +18862,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -17080,6 +18881,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create public.ecr.aws/gravitational/teleport:$(cat "/go/var/minor-version") --amend public.ecr.aws/gravitational/teleport:$(cat "/go/var/minor-version")-amd64 --amend public.ecr.aws/gravitational/teleport:$(cat "/go/var/minor-version")-arm @@ -17088,6 +18890,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -17103,6 +18909,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport:$(cat "/go/var/full-version") > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker manifest create public.ecr.aws/gravitational/teleport:$(cat "/go/var/full-version") --amend @@ -17113,6 +18920,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -17124,6 +18935,7 @@ steps: - Tag and push image "teleport:v10-arm64" to ECR - production - name: Assume S3 Download AWS Role for teleport-ent image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -17218,6 +19030,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-ent-v10-amd64-builder" --target "teleport" --platform "linux/amd64" --tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-amd64 --file "/go/build/Dockerfile-teleport-ent" --build-arg @@ -17228,6 +19041,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -17279,6 +19096,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-ent-v10-arm-builder" --target "teleport" --platform "linux/arm" --tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-arm --file "/go/build/Dockerfile-teleport-ent" --build-arg DEB_PATH=teleport-ent_$(cat @@ -17289,6 +19107,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -17340,6 +19162,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-ent-v10-arm64-builder" --target "teleport" --platform "linux/arm64" --tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-arm64 --file "/go/build/Dockerfile-teleport-ent" --build-arg @@ -17350,6 +19173,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -17366,6 +19193,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/full-version")-$TIMESTAMP-amd64 > /dev/null 2>&1 && echo 'Found existing @@ -17388,6 +19216,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -17403,6 +19235,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/full-version")-$TIMESTAMP-arm > /dev/null 2>&1 && echo 'Found existing @@ -17425,6 +19258,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -17440,6 +19277,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/full-version")-$TIMESTAMP-arm64 > /dev/null 2>&1 && echo 'Found existing @@ -17462,6 +19300,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -17475,6 +19317,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/major-version")-$TIMESTAMP > /dev/null 2>&1 && echo 'Found existing image, @@ -17487,6 +19330,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -17502,6 +19349,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/minor-version")-$TIMESTAMP > /dev/null 2>&1 && echo 'Found existing image, @@ -17514,6 +19362,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -17529,6 +19381,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/full-version")-$TIMESTAMP > /dev/null 2>&1 && echo 'Found existing image, @@ -17541,6 +19394,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -17556,6 +19413,7 @@ steps: - docker pull --platform "linux/amd64" drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-amd64 - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-amd64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-amd64 quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-amd64 @@ -17568,6 +19426,10 @@ steps: - docker push quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version")-amd64 - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -17585,6 +19447,7 @@ steps: - docker pull --platform "linux/arm" drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-arm - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-arm > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-arm quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-arm @@ -17597,6 +19460,10 @@ steps: - docker push quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version")-arm - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -17614,6 +19481,7 @@ steps: - docker pull --platform "linux/arm64" drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-arm64 - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-arm64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-arm64 quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-arm64 @@ -17626,6 +19494,10 @@ steps: - docker push quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version")-arm64 - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -17641,6 +19513,7 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create quay.io/gravitational/teleport-ent:$(cat "/go/var/major-version") --amend quay.io/gravitational/teleport-ent:$(cat "/go/var/major-version")-amd64 --amend quay.io/gravitational/teleport-ent:$(cat "/go/var/major-version")-arm @@ -17648,6 +19521,10 @@ steps: - docker manifest push quay.io/gravitational/teleport-ent:$(cat "/go/var/major-version") - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -17665,6 +19542,7 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version") --amend quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version")-amd64 --amend quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version")-arm @@ -17672,6 +19550,10 @@ steps: - docker manifest push quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version") - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -17689,6 +19571,7 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version") > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker manifest create quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version") --amend @@ -17698,6 +19581,10 @@ steps: "/go/var/full-version")) - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -17719,6 +19606,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/full-version")-amd64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-amd64 public.ecr.aws/gravitational/teleport-ent:$(cat @@ -17733,6 +19621,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -17748,6 +19640,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/full-version")-arm > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-arm public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/full-version")-arm @@ -17761,6 +19654,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -17776,6 +19673,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/full-version")-arm64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-arm64 public.ecr.aws/gravitational/teleport-ent:$(cat @@ -17790,6 +19688,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -17803,6 +19705,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/major-version") --amend public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/major-version")-amd64 --amend public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/major-version")-arm @@ -17811,6 +19714,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -17826,6 +19733,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/minor-version") --amend public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/minor-version")-amd64 --amend public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/minor-version")-arm @@ -17834,6 +19742,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -17849,6 +19761,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/full-version") > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker manifest create public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/full-version") @@ -17859,6 +19772,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -17870,6 +19787,7 @@ steps: - Tag and push image "teleport-ent:v10-arm64" to ECR - production - name: Assume S3 Download AWS Role for teleport-ent-fips image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -17965,6 +19883,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-ent-v10-fips-amd64-builder" --target "teleport-fips" --platform "linux/amd64" --tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-fips-amd64 --file "/go/build/Dockerfile-teleport-ent-fips" @@ -17976,6 +19895,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -17992,6 +19915,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/full-version")-$TIMESTAMP-fips-amd64 > /dev/null 2>&1 && echo 'Found @@ -18014,6 +19938,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -18027,6 +19955,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/major-version")-$TIMESTAMP-fips > /dev/null 2>&1 && echo 'Found existing @@ -18037,6 +19966,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -18050,6 +19983,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/minor-version")-$TIMESTAMP-fips > /dev/null 2>&1 && echo 'Found existing @@ -18060,6 +19994,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -18073,6 +20011,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-ent:$(cat "/go/var/full-version")-$TIMESTAMP-fips > /dev/null 2>&1 && echo 'Found existing @@ -18083,6 +20022,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -18096,6 +20039,7 @@ steps: - docker pull --platform "linux/amd64" drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-fips-amd64 - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-fips-amd64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-fips-amd64 quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-fips-amd64 @@ -18108,6 +20052,10 @@ steps: - docker push quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version")-fips-amd64 - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -18123,11 +20071,16 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create quay.io/gravitational/teleport-ent:$(cat "/go/var/major-version")-fips --amend quay.io/gravitational/teleport-ent:$(cat "/go/var/major-version")-fips-amd64 - docker manifest push quay.io/gravitational/teleport-ent:$(cat "/go/var/major-version")-fips - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -18143,11 +20096,16 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version")-fips --amend quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version")-fips-amd64 - docker manifest push quay.io/gravitational/teleport-ent:$(cat "/go/var/minor-version")-fips - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -18163,6 +20121,7 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-fips > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker manifest create quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-fips --amend @@ -18170,6 +20129,10 @@ steps: docker manifest push quay.io/gravitational/teleport-ent:$(cat "/go/var/full-version")-fips) - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -18189,6 +20152,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/full-version")-fips-amd64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-ent:$(cat "/go/var/full-version")-fips-amd64 public.ecr.aws/gravitational/teleport-ent:$(cat @@ -18203,6 +20167,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -18216,12 +20184,17 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/major-version")-fips --amend public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/major-version")-fips-amd64 - docker manifest push public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/major-version")-fips - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -18235,12 +20208,17 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/minor-version")-fips --amend public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/minor-version")-fips-amd64 - docker manifest push public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/minor-version")-fips - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -18254,6 +20232,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/full-version")-fips > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker manifest create public.ecr.aws/gravitational/teleport-ent:$(cat "/go/var/full-version")-fips @@ -18262,6 +20241,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -18282,6 +20265,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-operator-v10-amd64-builder" --platform "linux/amd64" --tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-amd64 --file "/go/src/github.com/gravitational/teleport/integrations/operator/Dockerfile" @@ -18293,6 +20277,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -18321,6 +20309,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-operator-v10-arm-builder" --platform "linux/arm" --tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-arm --file "/go/src/github.com/gravitational/teleport/integrations/operator/Dockerfile" @@ -18332,6 +20321,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -18360,6 +20353,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker buildx build --push --builder "teleport-operator-v10-arm64-builder" --platform "linux/arm64" --tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-arm64 --file "/go/src/github.com/gravitational/teleport/integrations/operator/Dockerfile" @@ -18371,6 +20365,10 @@ steps: environment: AWS_PROFILE: ecr-authenticated-pull DOCKER_BUILDKIT: "1" + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -18394,6 +20392,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-operator:$(cat "/go/var/full-version")-$TIMESTAMP-amd64 > /dev/null 2>&1 && echo 'Found existing @@ -18416,6 +20415,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -18431,6 +20434,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-operator:$(cat "/go/var/full-version")-$TIMESTAMP-arm > /dev/null 2>&1 && echo 'Found existing @@ -18453,6 +20457,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -18468,6 +20476,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-operator:$(cat "/go/var/full-version")-$TIMESTAMP-arm64 > /dev/null 2>&1 && echo 'Found existing @@ -18490,6 +20499,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -18503,6 +20516,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-operator:$(cat "/go/var/major-version")-$TIMESTAMP > /dev/null 2>&1 && echo 'Found existing image, @@ -18515,6 +20529,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -18530,6 +20548,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-operator:$(cat "/go/var/minor-version")-$TIMESTAMP > /dev/null 2>&1 && echo 'Found existing image, @@ -18542,6 +20561,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -18557,6 +20580,7 @@ steps: - apk add --no-cache aws-cli - aws ecr get-login-password --region=us-west-2 | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - TIMESTAMP=$(date -d @"$DRONE_BUILD_CREATED" '+%Y%m%d%H%M') - docker manifest inspect 146628656107.dkr.ecr.us-west-2.amazonaws.com/gravitational/teleport-operator:$(cat "/go/var/full-version")-$TIMESTAMP > /dev/null 2>&1 && echo 'Found existing image, @@ -18569,6 +20593,10 @@ steps: - docker logout "146628656107.dkr.ecr.us-west-2.amazonaws.com" environment: AWS_PROFILE: ecr-staging + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -18584,6 +20612,7 @@ steps: - docker pull --platform "linux/amd64" drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-amd64 - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-operator:$(cat "/go/var/full-version")-amd64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-amd64 quay.io/gravitational/teleport-operator:$(cat "/go/var/full-version")-amd64 @@ -18596,6 +20625,10 @@ steps: - docker push quay.io/gravitational/teleport-operator:$(cat "/go/var/minor-version")-amd64 - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -18613,6 +20646,7 @@ steps: - docker pull --platform "linux/arm" drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-arm - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-operator:$(cat "/go/var/full-version")-arm > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-arm quay.io/gravitational/teleport-operator:$(cat "/go/var/full-version")-arm @@ -18625,6 +20659,10 @@ steps: - docker push quay.io/gravitational/teleport-operator:$(cat "/go/var/minor-version")-arm - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -18642,6 +20680,7 @@ steps: - docker pull --platform "linux/arm64" drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-arm64 - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-operator:$(cat "/go/var/full-version")-arm64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-arm64 quay.io/gravitational/teleport-operator:$(cat "/go/var/full-version")-arm64 @@ -18654,6 +20693,10 @@ steps: - docker push quay.io/gravitational/teleport-operator:$(cat "/go/var/minor-version")-arm64 - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -18669,6 +20712,7 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create quay.io/gravitational/teleport-operator:$(cat "/go/var/major-version") --amend quay.io/gravitational/teleport-operator:$(cat "/go/var/major-version")-amd64 --amend quay.io/gravitational/teleport-operator:$(cat "/go/var/major-version")-arm @@ -18676,6 +20720,10 @@ steps: - docker manifest push quay.io/gravitational/teleport-operator:$(cat "/go/var/major-version") - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -18693,6 +20741,7 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create quay.io/gravitational/teleport-operator:$(cat "/go/var/minor-version") --amend quay.io/gravitational/teleport-operator:$(cat "/go/var/minor-version")-amd64 --amend quay.io/gravitational/teleport-operator:$(cat "/go/var/minor-version")-arm @@ -18700,6 +20749,10 @@ steps: - docker manifest push quay.io/gravitational/teleport-operator:$(cat "/go/var/minor-version") - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -18717,6 +20770,7 @@ steps: image: docker commands: - docker login -u="$QUAY_USERNAME" -p="$QUAY_PASSWORD" "quay.io" + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect quay.io/gravitational/teleport-operator:$(cat "/go/var/full-version") > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker manifest create quay.io/gravitational/teleport-operator:$(cat "/go/var/full-version") --amend @@ -18726,6 +20780,10 @@ steps: docker manifest push quay.io/gravitational/teleport-operator:$(cat "/go/var/full-version")) - docker logout "quay.io" environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME QUAY_PASSWORD: from_secret: PRODUCTION_QUAYIO_DOCKER_PASSWORD QUAY_USERNAME: @@ -18747,6 +20805,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/full-version")-amd64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-amd64 public.ecr.aws/gravitational/teleport-operator:$(cat @@ -18761,6 +20820,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -18776,6 +20839,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/full-version")-arm > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-arm public.ecr.aws/gravitational/teleport-operator:$(cat @@ -18790,6 +20854,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -18805,6 +20873,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/full-version")-arm64 > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker tag drone-docker-registry:5000/teleport-operator:$(cat "/go/var/full-version")-arm64 public.ecr.aws/gravitational/teleport-operator:$(cat @@ -18819,6 +20888,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -18832,6 +20905,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/major-version") --amend public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/major-version")-amd64 --amend public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/major-version")-arm @@ -18840,6 +20914,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -18855,6 +20933,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest create public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/minor-version") --amend public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/minor-version")-amd64 --amend public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/minor-version")-arm @@ -18863,6 +20942,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -18878,6 +20961,7 @@ steps: - apk add --no-cache aws-cli - aws ecr-public get-login-password --region=us-east-1 | docker login -u="AWS" --password-stdin public.ecr.aws + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin - docker manifest inspect public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/full-version") > /dev/null 2>&1 && echo 'Found existing image, skipping' || (docker manifest create public.ecr.aws/gravitational/teleport-operator:$(cat "/go/var/full-version") @@ -18888,6 +20972,10 @@ steps: - docker logout "public.ecr.aws" environment: AWS_PROFILE: ecr-production + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: awsconfig path: /root/.aws @@ -18913,6 +21001,10 @@ volumes: temp: {} - name: dockersock temp: {} +- name: dockerconfig + temp: {} +image_pull_secrets: +- DOCKERHUB_CREDENTIALS --- ################################################ @@ -18952,13 +21044,23 @@ steps: && exit 1)' - name: Wait for docker image: docker + pull: if-not-exists commands: - timeout 30s /bin/sh -c 'while [ ! -S /var/run/docker.sock ]; do sleep 1; done' + - printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin + environment: + DOCKERHUB_PASSWORD: + from_secret: DOCKERHUB_READONLY_TOKEN + DOCKERHUB_USERNAME: + from_secret: DOCKERHUB_USERNAME volumes: - name: dockersock path: /var/run + - name: dockerconfig + path: /root/.docker - name: Assume AWS Role image: amazon/aws-cli + pull: if-not-exists commands: - aws sts get-caller-identity - |- @@ -19030,15 +21132,19 @@ services: - name: dockersock path: /var/run volumes: -- name: dockersock - temp: {} - name: tmpfs temp: medium: memory - name: awsconfig temp: {} +- name: dockersock + temp: {} +- name: dockerconfig + temp: {} +image_pull_secrets: +- DOCKERHUB_CREDENTIALS --- kind: signature -hmac: 4107c52101a8fbd297c24a1408d6bb96999140c0a3d67cc7fba23abaa447ea38 +hmac: 3a2e1dfd7cd5045cb27658233858cb21a861d6595ef5c807321e0d8af0f65bdb ... diff --git a/.github/ISSUE_TEMPLATE/testplan.md b/.github/ISSUE_TEMPLATE/testplan.md index 3add8a982ee85..02fbc9bb615d5 100644 --- a/.github/ISSUE_TEMPLATE/testplan.md +++ b/.github/ISSUE_TEMPLATE/testplan.md @@ -30,7 +30,7 @@ as well as an upgrade of the previous version of Teleport. - [ ] RBAC - Make sure that invalid and valid attempts are reflected in audit log. + Make sure that invalid and valid attempts are reflected in audit log. Do this with both Teleport and [Agentless nodes](https://goteleport.com/docs/server-access/guides/openssh/). - [ ] Successfully connect to node with correct role - [ ] Unsuccessfully connect to a node in a role restricting access by label @@ -166,18 +166,32 @@ as well as an upgrade of the previous version of Teleport. - [ ] tsh ssh \ - [ ] tsh ssh \ + - [ ] tsh ssh \ + - [ ] tsh ssh \ - [ ] tsh ssh -A \ - [ ] tsh ssh -A \ + - [ ] tsh ssh -A \ + - [ ] tsh ssh -A \ - [ ] tsh ssh \ ls - [ ] tsh ssh \ ls + - [ ] tsh ssh \ ls + - [ ] tsh ssh \ ls - [ ] tsh join \ - [ ] tsh join \ + - [ ] tsh join \ + - [ ] tsh join \ - [ ] tsh play \ - [ ] tsh play \ + - [ ] tsh play \ + - [ ] tsh play \ - [ ] tsh scp \ - [ ] tsh scp \ + - [ ] tsh scp \ + - [ ] tsh scp \ - [ ] tsh ssh -L \ - [ ] tsh ssh -L \ + - [ ] tsh ssh -L \ + - [ ] tsh ssh -L \ - [ ] tsh ls - [ ] tsh clusters @@ -185,14 +199,24 @@ as well as an upgrade of the previous version of Teleport. Make sure to test both recording and regular proxy modes. - [ ] ssh \ - [ ] ssh \ + - [ ] ssh \ + - [ ] ssh \ - [ ] ssh -A \ - [ ] ssh -A \ + - [ ] ssh -A \ + - [ ] ssh -A \ - [ ] ssh \ ls - [ ] ssh \ ls + - [ ] ssh \ ls + - [ ] ssh \ ls - [ ] scp \ - [ ] scp \ + - [ ] scp \ + - [ ] scp \ - [ ] ssh -L \ - [ ] ssh -L \ + - [ ] ssh -L \ + - [ ] ssh -L \ - [ ] Verify proxy jump functionality Log into leaf cluster via root, shut down the root proxy and verify proxy jump works. @@ -206,6 +230,7 @@ as well as an upgrade of the previous version of Teleport. - [ ] Interact with a cluster using the Web UI - [ ] Connect to a Teleport node - [ ] Connect to a OpenSSH node + - [ ] Connect to a Agentless node - [ ] Check agent forwarding is correct based on role and proxy mode. - [ ] `tsh` CA loading @@ -241,12 +266,18 @@ interactive sessions the 12 combinations are below. - [ ] Connect to a OpenSSH node in a local cluster using OpenSSH. - [ ] Connect to a OpenSSH node in a local cluster using Teleport. - [ ] Connect to a OpenSSH node in a local cluster using the Web UI. +- [ ] Connect to an Agentless node in a local cluster using OpenSSH. +- [ ] Connect to an Agentless node in a local cluster using Teleport. +- [ ] Connect to an Agentless node in a local cluster using the Web UI. - [ ] Connect to a Teleport node in a local cluster using OpenSSH. - [ ] Connect to a Teleport node in a local cluster using Teleport. - [ ] Connect to a Teleport node in a local cluster using the Web UI. - [ ] Connect to a OpenSSH node in a remote cluster using OpenSSH. - [ ] Connect to a OpenSSH node in a remote cluster using Teleport. - [ ] Connect to a OpenSSH node in a remote cluster using the Web UI. +- [ ] Connect to an Agentless node in a remote cluster using OpenSSH. +- [ ] Connect to an Agentless node in a remote cluster using Teleport. +- [ ] Connect to an Agentless node in a remote cluster using the Web UI. - [ ] Connect to a Teleport node in a remote cluster using OpenSSH. - [ ] Connect to a Teleport node in a remote cluster using Teleport. - [ ] Connect to a Teleport node in a remote cluster using the Web UI. diff --git a/.github/workflows/build-macos-bypass.yaml b/.github/workflows/build-macos-bypass.yaml index 8d3fca222d12d..926949aa865f9 100644 --- a/.github/workflows/build-macos-bypass.yaml +++ b/.github/workflows/build-macos-bypass.yaml @@ -14,6 +14,7 @@ run-name: Skip Build on Mac OS on: pull_request: paths-ignore: + - '.github/workflows/build-macos.yaml' - '**.go' - 'go.mod' - 'go.sum' @@ -22,8 +23,10 @@ on: - 'Cargo.lock' - 'build.assets/Makefile' - 'build.assets/Dockerfile*' + - 'Makefile' merge_group: paths-ignore: + - '.github/workflows/build-macos.yaml' - '**.go' - 'go.mod' - 'go.sum' @@ -32,6 +35,7 @@ on: - 'Cargo.lock' - 'build.assets/Makefile' - 'build.assets/Dockerfile*' + - 'Makefile' jobs: build: diff --git a/.github/workflows/build-macos.yaml b/.github/workflows/build-macos.yaml index 0861d5cef66e2..aa7a0813f04a5 100644 --- a/.github/workflows/build-macos.yaml +++ b/.github/workflows/build-macos.yaml @@ -4,6 +4,7 @@ run-name: Build on Mac OS on: pull_request: paths: + - '.github/workflows/build-macos.yaml' - '**.go' - 'go.mod' - 'go.sum' @@ -12,8 +13,10 @@ on: - 'Cargo.lock' - 'build.assets/Makefile' - 'build.assets/Dockerfile*' + - 'Makefile' merge_group: paths: + - '.github/workflows/build-macos.yaml' - '**.go' - 'go.mod' - 'go.sum' @@ -22,6 +25,7 @@ on: - 'Cargo.lock' - 'build.assets/Makefile' - 'build.assets/Dockerfile*' + - 'Makefile' jobs: build: @@ -36,15 +40,37 @@ jobs: - name: Checkout Teleport uses: actions/checkout@v3 - - name: Get Go version - id: go-version - shell: bash - run: echo "go-version=$(make --no-print-directory print-go-version | tr -d '\n')" >> $GITHUB_OUTPUT + - name: Determine Toolchain Versions and cache paths + run: | + echo NODE_VERSION=$(make -C build.assets print-node-version) >> $GITHUB_ENV + echo GOLANG_VERSION=$(make -C build.assets print-go-version | sed 's/^go//') >> $GITHUB_ENV + echo RUST_VERSION=$(make -C build.assets print-rust-version) >> $GITHUB_ENV + echo PKG_CONFIG_PATH="$(build.assets/build-fido2-macos.sh pkg_config_path)" >> $GITHUB_ENV - - name: Setup Go + - name: Print versions + run: | + echo "make: $(make --version)" + echo "node: ${NODE_VERSION}" + echo "go: ${GOLANG_VERSION}" + echo "rust: ${RUST_VERSION}" + + - name: Install Node Toolchain + uses: actions/setup-node@v3 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: Setup yarn + run: | + corepack enable yarn + + - name: Install Go Toolchain uses: actions/setup-go@v3 with: - go-version: ${{ steps.go-version.outputs.go-version }} + go-version: ${{ env.GOLANG_VERSION }} + + - name: Configure Rust Toolchain + run: | + rustup override set ${{ env.RUST_VERSION }} - name: Build run: make binaries diff --git a/.github/workflows/build-windows-bypass.yaml b/.github/workflows/build-windows-bypass.yaml index dfb4733c12e14..29569d25b30d7 100644 --- a/.github/workflows/build-windows-bypass.yaml +++ b/.github/workflows/build-windows-bypass.yaml @@ -16,20 +16,24 @@ on: # We only build tsh on Windows so only consider Go code as tsh doesn't # run any Rust. paths-ignore: + - '.github/workflows/build-windows.yaml' - '**.go' - 'go.mod' - 'go.sum' - 'build.assets/Makefile' - 'build.assets/Dockerfile*' + - 'Makefile' merge_group: # We only build tsh on Windows so only consider Go code as tsh doesn't # run any Rust. paths-ignore: + - '.github/workflows/build-windows.yaml' - '**.go' - 'go.mod' - 'go.sum' - 'build.assets/Makefile' - 'build.assets/Dockerfile*' + - 'Makefile' jobs: build: diff --git a/.github/workflows/build-windows.yaml b/.github/workflows/build-windows.yaml index 2034816ec1838..a83073c27dabc 100644 --- a/.github/workflows/build-windows.yaml +++ b/.github/workflows/build-windows.yaml @@ -6,20 +6,24 @@ on: # We only build tsh on Windows so only consider Go code as tsh doesn't # run any Rust. paths: + - '.github/workflows/build-windows.yaml' - '**.go' - 'go.mod' - 'go.sum' - 'build.assets/Makefile' - 'build.assets/Dockerfile*' + - 'Makefile' merge_group: # We only build tsh on Windows so only consider Go code as tsh doesn't # run any Rust. paths: + - '.github/workflows/build-windows.yaml' - '**.go' - 'go.mod' - 'go.sum' - 'build.assets/Makefile' - 'build.assets/Dockerfile*' + - 'Makefile' jobs: build: diff --git a/.github/workflows/flaky-tests-bypass.yaml b/.github/workflows/flaky-tests-bypass.yaml index ef22dca17b19d..d4a1c3e020fd5 100644 --- a/.github/workflows/flaky-tests-bypass.yaml +++ b/.github/workflows/flaky-tests-bypass.yaml @@ -1,21 +1,20 @@ # This workflow is required to ensure that required Github check passes even if -# the actual "Flaky Tests Detector" workflow is skipped due to path filtering. -# Otherwise it will stay forever pending. +# the actual "Flaky Tests Detector" workflow is skipped due to path filtering. Otherwise +# it will stay forever pending. Another bypass is used for the merge queue. # # See "Handling skipped but required checks" for more info: # # https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/troubleshooting-required-status-checks#handling-skipped-but-required-checks # # Note both workflows must have the same name. - name: Flaky Tests Detector -run-name: Skip Flaky Tests Detector - ${{ github.run_id }} - @${{ github.actor }} +run-name: Flaky Tests Detector on: pull_request: paths-ignore: - '**.go' - + - '.github/workflows/flaky-tests.yaml' jobs: test: name: Flaky Tests Detector diff --git a/.github/workflows/flaky-tests-merge-queue.yaml b/.github/workflows/flaky-tests-merge-queue.yaml new file mode 100644 index 0000000000000..9e39e3d942525 --- /dev/null +++ b/.github/workflows/flaky-tests-merge-queue.yaml @@ -0,0 +1,25 @@ +# This check runs only on PRs that are in the merge queue. +# +# PRs in the merge queue have already been approved but the reviewers check +# is still required so this workflow allows the required check to succeed, +# otherwise PRs in the merge queue would be blocked indefinitely. +# +# See "Handling skipped but required checks" for more info: +# +# https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/troubleshooting-required-status-checks#handling-skipped-but-required-checks +# +# Note both workflows must have the same name. +name: Flaky Tests Detector +on: + merge_group: + +jobs: + test: + name: Flaky Tests Detector + runs-on: ubuntu-latest + + permissions: + contents: none + + steps: + - run: 'echo "Skipping reviewers check in merge queue"' diff --git a/.github/workflows/flaky-tests.yaml b/.github/workflows/flaky-tests.yaml index 05d6f4a5c34bd..10d7cd0a90263 100644 --- a/.github/workflows/flaky-tests.yaml +++ b/.github/workflows/flaky-tests.yaml @@ -5,6 +5,7 @@ on: pull_request: paths: - '**.go' + - '.github/workflows/flaky-tests.yaml' jobs: test: diff --git a/.github/workflows/integration-tests-non-root-bypass.yaml b/.github/workflows/integration-tests-non-root-bypass.yaml index 930e09ac0e698..89c0a9187cba8 100644 --- a/.github/workflows/integration-tests-non-root-bypass.yaml +++ b/.github/workflows/integration-tests-non-root-bypass.yaml @@ -14,18 +14,22 @@ run-name: Skip Integration Tests (Non-root) - ${{ github.run_id }} - @${{ github on: pull_request: paths-ignore: + - '.github/workflows/integration-tests-non-root.yaml' - '**.go' - 'go.mod' - 'go.sum' - 'build.assets/Makefile' - 'build.assets/Dockerfile*' + - 'Makefile' merge_group: paths-ignore: + - '.github/workflows/integration-tests-non-root.yaml' - '**.go' - 'go.mod' - 'go.sum' - 'build.assets/Makefile' - 'build.assets/Dockerfile*' + - 'Makefile' jobs: test: diff --git a/.github/workflows/integration-tests-non-root.yaml b/.github/workflows/integration-tests-non-root.yaml index 25425b200a177..e4ec70055f1a9 100644 --- a/.github/workflows/integration-tests-non-root.yaml +++ b/.github/workflows/integration-tests-non-root.yaml @@ -8,18 +8,22 @@ on: - branch/* pull_request: paths: + - '.github/workflows/integration-tests-non-root.yaml' - '**.go' - 'go.mod' - 'go.sum' - 'build.assets/Makefile' - 'build.assets/Dockerfile*' + - 'Makefile' merge_group: paths: + - '.github/workflows/integration-tests-non-root.yaml' - '**.go' - 'go.mod' - 'go.sum' - 'build.assets/Makefile' - 'build.assets/Dockerfile*' + - 'Makefile' jobs: test: diff --git a/.github/workflows/integration-tests-root-bypass.yaml b/.github/workflows/integration-tests-root-bypass.yaml index 3f868e3d4ee9b..b7e5de133830b 100644 --- a/.github/workflows/integration-tests-root-bypass.yaml +++ b/.github/workflows/integration-tests-root-bypass.yaml @@ -14,18 +14,22 @@ run-name: Skip Integration Tests (Root) - ${{ github.run_id }} - @${{ github.act on: pull_request: paths-ignore: + - '.github/workflows/integration-tests-root.yaml' - '**.go' - 'go.mod' - 'go.sum' - 'build.assets/Makefile' - 'build.assets/Dockerfile*' + - 'Makefile' merge_group: paths-ignore: + - '.github/workflows/integration-tests-root.yaml' - '**.go' - 'go.mod' - 'go.sum' - 'build.assets/Makefile' - 'build.assets/Dockerfile*' + - 'Makefile' jobs: test: diff --git a/.github/workflows/integration-tests-root.yaml b/.github/workflows/integration-tests-root.yaml index e50a4578a57d0..31e9242c4e3d6 100644 --- a/.github/workflows/integration-tests-root.yaml +++ b/.github/workflows/integration-tests-root.yaml @@ -8,18 +8,22 @@ on: - branch/* pull_request: paths: + - '.github/workflows/integration-tests-root.yaml' - '**.go' - 'go.mod' - 'go.sum' - 'build.assets/Makefile' - 'build.assets/Dockerfile*' + - 'Makefile' merge_group: paths: + - '.github/workflows/integration-tests-root.yaml' - '**.go' - 'go.mod' - 'go.sum' - 'build.assets/Makefile' - 'build.assets/Dockerfile*' + - 'Makefile' jobs: test: diff --git a/.github/workflows/lint-bypass.yaml b/.github/workflows/lint-bypass.yaml index e10bfebe0e02b..bb77e088aab95 100644 --- a/.github/workflows/lint-bypass.yaml +++ b/.github/workflows/lint-bypass.yaml @@ -22,10 +22,10 @@ on: jobs: lint: name: Lint (Go) - runs-on: ubuntu-latest - + runs-on: ubuntu-latest + permissions: contents: none steps: - - run: 'echo "No changes to verify"' + - run: 'echo "No changes to verify"' diff --git a/.github/workflows/unit-tests-code-bypass.yaml b/.github/workflows/unit-tests-code-bypass.yaml index f3b21330aed3f..b23cf8c9f7f31 100644 --- a/.github/workflows/unit-tests-code-bypass.yaml +++ b/.github/workflows/unit-tests-code-bypass.yaml @@ -14,18 +14,22 @@ run-name: Skip Unit Tests (Go) - ${{ github.run_id }} - @${{ github.actor }} on: pull_request: paths-ignore: + - '.github/workflows/unit-tests-code.yaml' - '**.go' - 'go.mod' - 'go.sum' - 'build.assets/Makefile' - 'build.assets/Dockerfile*' + - 'Makefile' merge_group: paths-ignore: + - '.github/workflows/unit-tests-code.yaml' - '**.go' - 'go.mod' - 'go.sum' - 'build.assets/Makefile' - 'build.assets/Dockerfile*' + - 'Makefile' jobs: test: diff --git a/.github/workflows/unit-tests-code.yaml b/.github/workflows/unit-tests-code.yaml index b5aa2e5c8e69b..35c212f4c114b 100644 --- a/.github/workflows/unit-tests-code.yaml +++ b/.github/workflows/unit-tests-code.yaml @@ -8,18 +8,22 @@ on: - branch/* pull_request: paths: + - '.github/workflows/unit-tests-code.yaml' - '**.go' - 'go.mod' - 'go.sum' - 'build.assets/Makefile' - 'build.assets/Dockerfile*' + - 'Makefile' merge_group: paths: + - '.github/workflows/unit-tests-code.yaml' - '**.go' - 'go.mod' - 'go.sum' - 'build.assets/Makefile' - 'build.assets/Dockerfile*' + - 'Makefile' jobs: test: diff --git a/.github/workflows/unit-tests-helm-bypass.yaml b/.github/workflows/unit-tests-helm-bypass.yaml index cb593e17a5dd4..f91ca5d0f1f47 100644 --- a/.github/workflows/unit-tests-helm-bypass.yaml +++ b/.github/workflows/unit-tests-helm-bypass.yaml @@ -14,9 +14,11 @@ run-name: Skip Unit Tests (Helm) - ${{ github.run_id }} - @${{ github.actor }} on: pull_request: paths-ignore: + - '.github/workflows/unit-tests-helm.yaml' - 'examples/chart/**' merge_group: paths-ignore: + - '.github/workflows/unit-tests-helm.yaml' - 'examples/chart/**' jobs: diff --git a/.github/workflows/unit-tests-helm.yaml b/.github/workflows/unit-tests-helm.yaml index 65f72e3e2a8e7..85409d0a2e21e 100644 --- a/.github/workflows/unit-tests-helm.yaml +++ b/.github/workflows/unit-tests-helm.yaml @@ -4,9 +4,11 @@ run-name: Unit Tests (Helm) - ${{ github.run_id }} - @${{ github.actor }} on: pull_request: paths: + - '.github/workflows/unit-tests-helm.yaml' - 'examples/chart/**' merge_group: paths: + - '.github/workflows/unit-tests-helm.yaml' - 'examples/chart/**' jobs: diff --git a/.github/workflows/unit-tests-rust-bypass.yaml b/.github/workflows/unit-tests-rust-bypass.yaml index c06ae56331447..2e93cb9429688 100644 --- a/.github/workflows/unit-tests-rust-bypass.yaml +++ b/.github/workflows/unit-tests-rust-bypass.yaml @@ -14,18 +14,22 @@ run-name: Skip Unit Tests (Rust) - ${{ github.run_id }} - @${{ github.actor }} on: pull_request: paths-ignore: + - '.github/workflows/unit-tests-rust.yaml' - '**.rs' - 'Cargo.toml' - 'Cargo.lock' - 'build.assets/Makefile' - 'build.assets/Dockerfile*' + - 'Makefile' merge_group: paths-ignore: + - '.github/workflows/unit-tests-rust.yaml' - '**.rs' - 'Cargo.toml' - 'Cargo.lock' - 'build.assets/Makefile' - 'build.assets/Dockerfile*' + - 'Makefile' jobs: test: diff --git a/.github/workflows/unit-tests-rust.yaml b/.github/workflows/unit-tests-rust.yaml index 919e307c3ca8d..fb67de7103ce7 100644 --- a/.github/workflows/unit-tests-rust.yaml +++ b/.github/workflows/unit-tests-rust.yaml @@ -4,18 +4,22 @@ run-name: Unit Tests (Rust) - ${{ github.run_id }} - @${{ github.actor }} on: pull_request: paths: + - '.github/workflows/unit-tests-rust.yaml' - '**.rs' - 'Cargo.toml' - 'Cargo.lock' - 'build.assets/Makefile' - 'build.assets/Dockerfile*' + - 'Makefile' merge_group: paths: + - '.github/workflows/unit-tests-rust.yaml' - '**.rs' - 'Cargo.toml' - 'Cargo.lock' - 'build.assets/Makefile' - 'build.assets/Dockerfile*' + - 'Makefile' jobs: test: diff --git a/.github/workflows/unit-tests-ui-bypass.yaml b/.github/workflows/unit-tests-ui-bypass.yaml index fb40cf5a473a8..0ced0e414dfc4 100644 --- a/.github/workflows/unit-tests-ui-bypass.yaml +++ b/.github/workflows/unit-tests-ui-bypass.yaml @@ -14,9 +14,11 @@ run-name: Unit Tests UI - ${{ github.run_id }} - @${{ github.actor }} on: pull_request: paths-ignore: + - '.github/workflows/unit-tests-ui.yaml' - 'web/**' merge_group: paths-ignore: + - '.github/workflows/unit-tests-ui.yaml' - 'web/**' jobs: diff --git a/.github/workflows/unit-tests-ui.yaml b/.github/workflows/unit-tests-ui.yaml index f98d99a6eaf29..02c9360808500 100644 --- a/.github/workflows/unit-tests-ui.yaml +++ b/.github/workflows/unit-tests-ui.yaml @@ -4,9 +4,11 @@ run-name: Unit Tests UI - ${{ github.run_id }} - @${{ github.actor }} on: pull_request: paths: + - '.github/workflows/unit-tests-ui.yaml' - 'web/**' merge_group: paths: + - '.github/workflows/unit-tests-ui.yaml' - 'web/**' jobs: diff --git a/Makefile b/Makefile index 3c9500ba15df2..37171562187cf 100644 --- a/Makefile +++ b/Makefile @@ -79,6 +79,11 @@ endif CHECK_CARGO := $(shell cargo --version 2>/dev/null) CHECK_RUST := $(shell rustc --version 2>/dev/null) +# Have cargo use sparse crates.io protocol: +# https://blog.rust-lang.org/2023/03/09/Rust-1.68.0.html +# TODO: Delete when it becomes default in Rust 1.70.0 +export CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse + with_rdpclient := no RDPCLIENT_MESSAGE := without-Windows-RDP-client @@ -207,7 +212,7 @@ ifeq ("$(ARCH)","arm64") CGOFLAG += CC=aarch64-linux-gnu-gcc endif else ifeq ("$(ARCH)","arm") -CGOFLAG = CGO_ENABLED=1 +CGOFLAG = CGO_ENABLED=1 # ARM builds need to specify the correct C compiler ifeq ($(IS_NATIVE_BUILD),"no") @@ -952,12 +957,13 @@ $(VERSRC): Makefile # - build binaries with 'make release' # - run `make tag` and use its output to 'git tag' and 'git push --tags' .PHONY: update-tag +update-tag: TAG_REMOTE ?= origin update-tag: @test $(VERSION) git tag $(GITTAG) git tag api/$(GITTAG) (cd e && git tag $(GITTAG) && git push origin $(GITTAG)) - git push origin $(GITTAG) && git push origin api/$(GITTAG) + git push $(TAG_REMOTE) $(GITTAG) && git push $(TAG_REMOTE) api/$(GITTAG) .PHONY: test-package test-package: remove-temp-files @@ -1141,14 +1147,14 @@ pkg: chmod +x $(BUILDDIR)/build-package.sh # arch and runtime are currently ignored on OS X # we pass them through for consistency - they will be dropped by the build script - cd $(BUILDDIR) && ./build-package.sh -t oss -v $(VERSION) -p pkg -a $(ARCH) $(RUNTIME_SECTION) $(TARBALL_PATH_SECTION) + cd $(BUILDDIR) && ./build-package.sh -t oss -v $(VERSION) -p pkg -b $(TELEPORT_BUNDLEID) -a $(ARCH) $(RUNTIME_SECTION) $(TARBALL_PATH_SECTION) if [ -f e/Makefile ]; then $(MAKE) -C e pkg; fi # build tsh client-only .pkg .PHONY: pkg-tsh pkg-tsh: $(eval export DEVELOPER_ID_APPLICATION DEVELOPER_ID_INSTALLER) - ./build.assets/build-pkg-tsh.sh -t oss -v $(VERSION) $(TARBALL_PATH_SECTION) + ./build.assets/build-pkg-tsh.sh -t oss -v $(VERSION) -b $(TSH_BUNDLEID) $(TARBALL_PATH_SECTION) mkdir -p $(BUILDDIR)/ mv tsh*.pkg* $(BUILDDIR)/ diff --git a/lib/srv/alpnproxy/dialer.go b/api/client/alpn.go similarity index 85% rename from lib/srv/alpnproxy/dialer.go rename to api/client/alpn.go index f4b3e183bb97a..15ac2451194cc 100644 --- a/lib/srv/alpnproxy/dialer.go +++ b/api/client/alpn.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package alpnproxy +package client import ( "context" @@ -23,16 +23,8 @@ import ( "time" "github.com/gravitational/trace" - - apiclient "github.com/gravitational/teleport/api/client" ) -// ContextDialer represents network dialer interface that uses context -type ContextDialer interface { - // DialContext is a function that dials the specified address - DialContext(ctx context.Context, network, addr string) (net.Conn, error) -} - // ALPNDialerConfig is the config for ALPNDialer. type ALPNDialerConfig struct { // KeepAlivePeriod defines period between keep alives. @@ -66,7 +58,7 @@ func (d ALPNDialer) DialContext(ctx context.Context, network, addr string) (net. return nil, trace.BadParameter("missing TLS config") } - dialer := apiclient.NewDialer(ctx, d.cfg.DialTimeout, d.cfg.DialTimeout, apiclient.WithTLSConfig(d.cfg.TLSConfig)) + dialer := NewDialer(ctx, d.cfg.DialTimeout, d.cfg.DialTimeout, WithTLSConfig(d.cfg.TLSConfig)) if d.cfg.ALPNConnUpgradeRequired { dialer = newALPNConnUpgradeDialer(dialer, &tls.Config{ InsecureSkipVerify: d.cfg.TLSConfig.InsecureSkipVerify, diff --git a/lib/srv/alpnproxy/conn_upgrade.go b/api/client/alpn_conn_upgrade.go similarity index 92% rename from lib/srv/alpnproxy/conn_upgrade.go rename to api/client/alpn_conn_upgrade.go index 1425ae70ee274..c82f91dd7ff63 100644 --- a/lib/srv/alpnproxy/conn_upgrade.go +++ b/api/client/alpn_conn_upgrade.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package alpnproxy +package client import ( "bufio" @@ -30,11 +30,9 @@ import ( "github.com/gravitational/trace" "github.com/sirupsen/logrus" - "github.com/gravitational/teleport" - apiclient "github.com/gravitational/teleport/api/client" + "github.com/gravitational/teleport/api/constants" "github.com/gravitational/teleport/api/defaults" "github.com/gravitational/teleport/api/utils" - "github.com/gravitational/teleport/lib/srv/alpnproxy/common" ) // IsALPNConnUpgradeRequired returns true if a tunnel is required through a HTTP @@ -57,7 +55,7 @@ func IsALPNConnUpgradeRequired(addr string, insecure bool) bool { Timeout: defaults.DefaultIOTimeout, } tlsConfig := &tls.Config{ - NextProtos: []string{string(common.ProtocolReverseTunnel)}, + NextProtos: []string{string(constants.ALPNSNIProtocolReverseTunnel)}, InsecureSkipVerify: insecure, } testConn, err := tls.DialWithDialer(netDialer, "tcp", addr, tlsConfig) @@ -145,12 +143,12 @@ func isALPNConnUpgradeRequiredByEnv(addr, envValue string) bool { // alpnConnUpgradeDialer makes an "HTTP" upgrade call to the Proxy Service then // tunnels the connection with this connection upgrade. type alpnConnUpgradeDialer struct { - dialer apiclient.ContextDialer + dialer ContextDialer tlsConfig *tls.Config } // newALPNConnUpgradeDialer creates a new alpnConnUpgradeDialer. -func newALPNConnUpgradeDialer(dialer apiclient.ContextDialer, tlsConfig *tls.Config) ContextDialer { +func newALPNConnUpgradeDialer(dialer ContextDialer, tlsConfig *tls.Config) ContextDialer { return &alpnConnUpgradeDialer{ dialer: dialer, tlsConfig: tlsConfig, @@ -187,7 +185,7 @@ func (d alpnConnUpgradeDialer) DialContext(ctx context.Context, network, addr st err = upgradeConnThroughWebAPI(tlsConn, url.URL{ Host: addr, Scheme: "https", - Path: teleport.WebAPIConnUpgrade, + Path: constants.WebAPIConnUpgrade, }) if err != nil { defer tlsConn.Close() @@ -203,7 +201,7 @@ func upgradeConnThroughWebAPI(conn net.Conn, api url.URL) error { } // For now, only "alpn" is supported. - req.Header.Add(teleport.WebAPIConnUpgradeHeader, teleport.WebAPIConnUpgradeTypeALPN) + req.Header.Add(constants.WebAPIConnUpgradeHeader, constants.WebAPIConnUpgradeTypeALPN) // Send the request and check if upgrade is successful. if err = req.Write(conn); err != nil { @@ -219,7 +217,7 @@ func upgradeConnThroughWebAPI(conn net.Conn, api url.URL) error { if http.StatusNotFound == resp.StatusCode { return trace.NotImplemented( "connection upgrade call to %q failed with status code %v. Please upgrade the server and try again.", - teleport.WebAPIConnUpgrade, + constants.WebAPIConnUpgrade, resp.StatusCode, ) } diff --git a/lib/srv/alpnproxy/conn_upgrade_test.go b/api/client/alpn_conn_upgrade_test.go similarity index 85% rename from lib/srv/alpnproxy/conn_upgrade_test.go rename to api/client/alpn_conn_upgrade_test.go index c01d01eb4d131..4c02b3ebfebb6 100644 --- a/lib/srv/alpnproxy/conn_upgrade_test.go +++ b/api/client/alpn_conn_upgrade_test.go @@ -14,13 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -package alpnproxy +package client import ( "context" "crypto/tls" "crypto/x509" - "crypto/x509/pkix" "errors" "net" "net/http" @@ -31,11 +30,8 @@ import ( "github.com/stretchr/testify/require" - "github.com/gravitational/teleport" - apiclient "github.com/gravitational/teleport/api/client" - "github.com/gravitational/teleport/lib/defaults" - "github.com/gravitational/teleport/lib/srv/alpnproxy/common" - "github.com/gravitational/teleport/lib/tlsca" + "github.com/gravitational/teleport/api/constants" + "github.com/gravitational/teleport/api/fixtures" ) func TestIsALPNConnUpgradeRequired(t *testing.T) { @@ -55,7 +51,7 @@ func TestIsALPNConnUpgradeRequired(t *testing.T) { }, { name: "upgrade not required (proto negotiated)", - serverProtos: []string{string(common.ProtocolReverseTunnel)}, + serverProtos: []string{string(constants.ALPNSNIProtocolReverseTunnel)}, insecure: true, expectedResult: false, }, @@ -67,7 +63,7 @@ func TestIsALPNConnUpgradeRequired(t *testing.T) { }, { name: "upgrade not required (other handshake error)", - serverProtos: []string{string(common.ProtocolReverseTunnel)}, + serverProtos: []string{string(constants.ALPNSNIProtocolReverseTunnel)}, insecure: false, // to cause handshake error expectedResult: false, }, @@ -138,7 +134,7 @@ func TestALPNConnUpgradeDialer(t *testing.T) { pool.AddCert(server.Certificate()) tlsConfig := &tls.Config{RootCAs: pool} - preDialer := apiclient.NewDialer(ctx, 0, 5*time.Second, apiclient.WithTLSConfig(tlsConfig)) + preDialer := NewDialer(ctx, 0, 5*time.Second) dialer := newALPNConnUpgradeDialer(preDialer, tlsConfig) conn, err := dialer.DialContext(ctx, "tcp", addr.Host) require.NoError(t, err) @@ -158,7 +154,7 @@ func TestALPNConnUpgradeDialer(t *testing.T) { require.NoError(t, err) tlsConfig := &tls.Config{InsecureSkipVerify: true} - preDialer := apiclient.NewDialer(ctx, 0, 5*time.Second, apiclient.WithTLSConfig(tlsConfig)) + preDialer := NewDialer(ctx, 0, 5*time.Second) dialer := newALPNConnUpgradeDialer(preDialer, tlsConfig) _, err = dialer.DialContext(ctx, "tcp", addr.Host) require.Error(t, err) @@ -207,12 +203,7 @@ func mustStartMockALPNServer(t *testing.T, supportedProtos []string) *mockALPNSe listener.Close() }) - caKey, caCert, err := tlsca.GenerateSelfSignedCA(pkix.Name{ - CommonName: "localhost", - }, []string{"localhost"}, defaults.CATTL) - require.NoError(t, err) - - cert, err := tls.X509KeyPair(caCert, caKey) + cert, err := tls.X509KeyPair([]byte(fixtures.TLSCACertPEM), []byte(fixtures.TLSCAKeyPEM)) require.NoError(t, err) m := &mockALPNServer{ @@ -228,8 +219,8 @@ func mustStartMockALPNServer(t *testing.T, supportedProtos []string) *mockALPNSe // upgrade request and sends back some data inside the tunnel. func mockConnUpgradeHandler(t *testing.T, upgradeType string, write []byte) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, teleport.WebAPIConnUpgrade, r.URL.Path) - require.Equal(t, upgradeType, r.Header.Get(teleport.WebAPIConnUpgradeHeader)) + require.Equal(t, constants.WebAPIConnUpgrade, r.URL.Path) + require.Equal(t, upgradeType, r.Header.Get(constants.WebAPIConnUpgradeHeader)) hj, ok := w.(http.Hijacker) require.True(t, ok) diff --git a/api/client/client.go b/api/client/client.go index ac4e3afa5a05f..54b4bbe28d16f 100644 --- a/api/client/client.go +++ b/api/client/client.go @@ -613,6 +613,66 @@ func (c *Client) DevicesClient() devicepb.DeviceTrustServiceClient { return devicepb.NewDeviceTrustServiceClient(c.conn) } +// CreateDeviceResource creates a device using its resource representation. +// Prefer using [DevicesClient] directly if you can. +func (c *Client) CreateDeviceResource(ctx context.Context, res *types.DeviceV1) (*types.DeviceV1, error) { + dev, err := types.DeviceFromResource(res) + if err != nil { + return nil, trace.Wrap(err) + } + + created, err := c.DevicesClient().CreateDevice(ctx, &devicepb.CreateDeviceRequest{ + Device: dev, + CreateAsResource: true, + }, c.callOpts...) + if err != nil { + return nil, trail.FromGRPC(err) + } + return types.DeviceToResource(created), nil +} + +// DeleteDeviceResource deletes a device using its ID (either devicepb.Device.Id +// or its Metadata.Name). +// Prefer using [DevicesClient] directly if you can. +func (c *Client) DeleteDeviceResource(ctx context.Context, id string) error { + _, err := c.DevicesClient().DeleteDevice(ctx, &devicepb.DeleteDeviceRequest{ + DeviceId: id, + }, c.callOpts...) + return trail.FromGRPC(err) +} + +// GetDeviceResource reads a device using its ID (either devicepb.Device.Id +// or its Metadata.Name). +// Prefer using [DevicesClient] directly if you can. +func (c *Client) GetDeviceResource(ctx context.Context, id string) (*types.DeviceV1, error) { + dev, err := c.DevicesClient().GetDevice(ctx, &devicepb.GetDeviceRequest{ + DeviceId: id, + }, c.callOpts...) + if err != nil { + return nil, trail.FromGRPC(err) + } + return types.DeviceToResource(dev), nil +} + +// UpsertDeviceResource creates or updates a device using its resource +// representation. +// Prefer using [DevicesClient] directly if you can. +func (c *Client) UpsertDeviceResource(ctx context.Context, res *types.DeviceV1) (*types.DeviceV1, error) { + dev, err := types.DeviceFromResource(res) + if err != nil { + return nil, trace.Wrap(err) + } + + upserted, err := c.DevicesClient().UpsertDevice(ctx, &devicepb.UpsertDeviceRequest{ + Device: dev, + CreateAsResource: true, + }, c.callOpts...) + if err != nil { + return nil, trail.FromGRPC(err) + } + return types.DeviceToResource(upserted), nil +} + // LoginRuleClient returns an unadorned Login Rule client, using the underlying // Auth gRPC connection. // Clients connecting to non-Enterprise clusters, or older Teleport versions, diff --git a/api/client/proto/authservice.pb.go b/api/client/proto/authservice.pb.go index 6ae38cf2cf165..9609361ec1fa3 100644 --- a/api/client/proto/authservice.pb.go +++ b/api/client/proto/authservice.pb.go @@ -346,6 +346,7 @@ type Event struct { // *Event_UIConfig // *Event_OktaImportRule // *Event_OktaAssignment + // *Event_Integration Resource isEvent_Resource `protobuf_oneof:"Resource"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` @@ -508,6 +509,9 @@ type Event_OktaImportRule struct { type Event_OktaAssignment struct { OktaAssignment *types.OktaAssignmentV1 `protobuf:"bytes,41,opt,name=OktaAssignment,proto3,oneof" json:"okta_assignment,omitempty"` } +type Event_Integration struct { + Integration *types.IntegrationV1 `protobuf:"bytes,42,opt,name=Integration,proto3,oneof" json:"integration,omitempty"` +} func (*Event_ResourceHeader) isEvent_Resource() {} func (*Event_CertAuthority) isEvent_Resource() {} @@ -548,6 +552,7 @@ func (*Event_UserGroup) isEvent_Resource() {} func (*Event_UIConfig) isEvent_Resource() {} func (*Event_OktaImportRule) isEvent_Resource() {} func (*Event_OktaAssignment) isEvent_Resource() {} +func (*Event_Integration) isEvent_Resource() {} func (m *Event) GetResource() isEvent_Resource { if m != nil { @@ -836,6 +841,13 @@ func (m *Event) GetOktaAssignment() *types.OktaAssignmentV1 { return nil } +func (m *Event) GetIntegration() *types.IntegrationV1 { + if x, ok := m.GetResource().(*Event_Integration); ok { + return x.Integration + } + return nil +} + // XXX_OneofWrappers is for the internal use of the proto package. func (*Event) XXX_OneofWrappers() []interface{} { return []interface{}{ @@ -878,6 +890,7 @@ func (*Event) XXX_OneofWrappers() []interface{} { (*Event_UIConfig)(nil), (*Event_OktaImportRule)(nil), (*Event_OktaAssignment)(nil), + (*Event_Integration)(nil), } } @@ -14263,823 +14276,824 @@ func init() { } var fileDescriptor_0ffcffcda38ae159 = []byte{ - // 13042 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0xbd, 0x5d, 0x6c, 0x1c, 0x49, - 0x92, 0x18, 0xac, 0x6e, 0xfe, 0x07, 0xff, 0x5a, 0x29, 0xfe, 0xb4, 0x9a, 0x94, 0x5a, 0x2a, 0x8d, - 0x34, 0x9a, 0xb9, 0x5d, 0xfd, 0x50, 0xf3, 0x3f, 0xb3, 0x33, 0xdb, 0xdd, 0xa4, 0x44, 0x4a, 0x14, - 0xc9, 0xad, 0xa6, 0x9a, 0xb3, 0xbb, 0xb3, 0xdb, 0x5b, 0xec, 0x4e, 0x91, 0xf5, 0xa9, 0xd9, 0xd5, - 0x5b, 0x55, 0x2d, 0x8d, 0xee, 0x83, 0x7f, 0xce, 0xe7, 0xbb, 0x07, 0xff, 0xae, 0x81, 0x3b, 0xdb, - 0x87, 0x7b, 0xb0, 0x01, 0x1b, 0x06, 0x6c, 0xc0, 0x86, 0x61, 0xf8, 0x70, 0x80, 0x61, 0xc0, 0x38, - 0xdb, 0x80, 0xd7, 0x06, 0x0c, 0xf8, 0xc1, 0x07, 0x03, 0x3e, 0x80, 0xf6, 0x2d, 0xfc, 0x44, 0xc0, - 0x4f, 0x86, 0xfd, 0xb0, 0x4f, 0x46, 0xfe, 0x56, 0x66, 0x55, 0x56, 0x35, 0x39, 0xc3, 0x1d, 0xbf, - 0x48, 0xec, 0xcc, 0x88, 0xc8, 0xcc, 0xc8, 0xac, 0xc8, 0xc8, 0xc8, 0xc8, 0x08, 0xb8, 0x13, 0xe2, - 0x0e, 0xee, 0x79, 0x7e, 0x78, 0xb7, 0x83, 0x0f, 0x9c, 0xd6, 0xeb, 0xbb, 0xad, 0x8e, 0x8b, 0xbb, - 0xe1, 0xdd, 0x9e, 0xef, 0x85, 0xde, 0x5d, 0xa7, 0x1f, 0x1e, 0x06, 0xd8, 0x7f, 0xe9, 0xb6, 0xf0, - 0x1d, 0x5a, 0x82, 0x46, 0xe8, 0x7f, 0xa5, 0xb9, 0x03, 0xef, 0xc0, 0x63, 0x30, 0xe4, 0x2f, 0x56, - 0x59, 0x5a, 0x3a, 0xf0, 0xbc, 0x83, 0x0e, 0x66, 0xc8, 0xfb, 0xfd, 0xe7, 0x77, 0xf1, 0x51, 0x2f, - 0x7c, 0xcd, 0x2b, 0xcb, 0xf1, 0xca, 0xd0, 0x3d, 0xc2, 0x41, 0xe8, 0x1c, 0xf5, 0x38, 0xc0, 0xed, - 0xcc, 0xae, 0xb4, 0xb0, 0x1f, 0x06, 0x1c, 0xf2, 0xad, 0x38, 0x64, 0xf8, 0xba, 0x87, 0x83, 0xbb, - 0xf8, 0x25, 0xee, 0x86, 0xe2, 0x3f, 0x0e, 0x7a, 0xdd, 0x0c, 0x4a, 0xff, 0xe5, 0x20, 0xdf, 0x36, - 0x83, 0xbc, 0xc2, 0xfb, 0x64, 0xf8, 0x5d, 0xf9, 0xc7, 0x00, 0x70, 0xdf, 0xe9, 0xf5, 0xb0, 0x1f, - 0xfd, 0x91, 0xe8, 0x6b, 0x3f, 0x70, 0x0e, 0x30, 0xef, 0xe3, 0xcb, 0xfb, 0xea, 0x4f, 0x06, 0x6a, - 0xfd, 0xc9, 0x32, 0x8c, 0xac, 0x91, 0x02, 0xf4, 0x01, 0x0c, 0xef, 0xbe, 0xee, 0xe1, 0x62, 0xee, - 0x5a, 0xee, 0xf6, 0xcc, 0x4a, 0x81, 0xd5, 0xdf, 0xd9, 0xee, 0x61, 0xdf, 0x09, 0x5d, 0xaf, 0x5b, - 0x45, 0x27, 0xc7, 0xe5, 0x19, 0xd2, 0xee, 0xb7, 0xbc, 0x23, 0x37, 0xa4, 0x5c, 0xb6, 0x29, 0x06, - 0xda, 0x83, 0x19, 0x1b, 0x07, 0x5e, 0xdf, 0x6f, 0xe1, 0x75, 0xec, 0xb4, 0xb1, 0x5f, 0xcc, 0x5f, - 0xcb, 0xdd, 0x9e, 0x5c, 0x99, 0xbf, 0xc3, 0x86, 0xac, 0x57, 0x56, 0x17, 0x4e, 0x8e, 0xcb, 0xc8, - 0xe7, 0x65, 0x11, 0xb1, 0xf5, 0x0b, 0x76, 0x8c, 0x0c, 0xfa, 0x02, 0xa6, 0x6b, 0xd8, 0x0f, 0x2b, - 0xfd, 0xf0, 0xd0, 0xf3, 0xdd, 0xf0, 0x75, 0x71, 0x88, 0xd2, 0x5d, 0xe0, 0x74, 0xb5, 0xba, 0xc6, - 0x4a, 0x75, 0xf9, 0xe4, 0xb8, 0x5c, 0x24, 0x73, 0xd6, 0x74, 0x44, 0xa9, 0x46, 0x5e, 0x27, 0x86, - 0x3e, 0x87, 0xa9, 0x7a, 0xe8, 0x84, 0x6e, 0x6b, 0xd7, 0x7b, 0x81, 0xbb, 0x41, 0x71, 0x58, 0xeb, - 0xb4, 0x5a, 0xd5, 0x58, 0xa9, 0x2e, 0x9d, 0x1c, 0x97, 0x17, 0x03, 0x5a, 0xd6, 0x0c, 0x69, 0xa1, - 0x46, 0x5a, 0xa3, 0x84, 0x7e, 0x02, 0x33, 0x3b, 0xbe, 0xf7, 0xd2, 0x0d, 0x5c, 0xaf, 0x4b, 0x8b, - 0x8a, 0x23, 0x94, 0xf6, 0x22, 0xa7, 0xad, 0x57, 0x36, 0x56, 0xaa, 0x57, 0x4e, 0x8e, 0xcb, 0x97, - 0x7b, 0xa2, 0x94, 0x35, 0xa0, 0x73, 0x46, 0x47, 0x41, 0xbb, 0x30, 0x59, 0xeb, 0xf4, 0x83, 0x10, - 0xfb, 0x5b, 0xce, 0x11, 0x2e, 0x8e, 0x52, 0xf2, 0x73, 0x82, 0x2f, 0x51, 0x4d, 0x63, 0xa5, 0x5a, - 0x3a, 0x39, 0x2e, 0x2f, 0xb4, 0x58, 0x51, 0xb3, 0xeb, 0x1c, 0xe9, 0x2c, 0x57, 0xc9, 0xa0, 0xf7, - 0x61, 0xf8, 0x59, 0x80, 0xfd, 0xe2, 0x38, 0x25, 0x37, 0xcd, 0xc9, 0x91, 0xa2, 0xc6, 0x0a, 0x9b, - 0xff, 0x7e, 0x80, 0x7d, 0x0d, 0x9f, 0x22, 0x10, 0x44, 0xdb, 0xeb, 0xe0, 0xe2, 0x84, 0x86, 0x48, - 0x8a, 0x1a, 0xef, 0x31, 0x44, 0xdf, 0xeb, 0xe8, 0x0d, 0x53, 0x04, 0xb4, 0x01, 0x13, 0xa4, 0xe5, - 0xa0, 0xe7, 0xb4, 0x70, 0x11, 0x28, 0x76, 0x81, 0x63, 0xcb, 0xf2, 0xea, 0xe2, 0xc9, 0x71, 0xf9, - 0x52, 0x57, 0xfc, 0xd4, 0xa8, 0x44, 0xd8, 0xe8, 0x33, 0x18, 0xad, 0x63, 0xff, 0x25, 0xf6, 0x8b, - 0x93, 0x94, 0xce, 0xac, 0x98, 0x48, 0x5a, 0xd8, 0x58, 0xa9, 0xce, 0x9d, 0x1c, 0x97, 0x0b, 0x01, - 0xfd, 0xa5, 0xd1, 0xe0, 0x68, 0x64, 0xb5, 0xd9, 0xf8, 0x25, 0xf6, 0x03, 0xbc, 0xdb, 0xef, 0x76, - 0x71, 0xa7, 0x38, 0xa5, 0xad, 0x36, 0xad, 0x4e, 0xac, 0x36, 0x9f, 0x15, 0x36, 0x43, 0x5a, 0xaa, - 0xaf, 0x36, 0x0d, 0x01, 0x1d, 0x42, 0x81, 0xfd, 0x55, 0xf3, 0xba, 0x5d, 0xdc, 0x22, 0x9f, 0x54, - 0x71, 0x9a, 0x36, 0x70, 0x99, 0x37, 0x10, 0xaf, 0x6e, 0xac, 0x54, 0xcb, 0x27, 0xc7, 0xe5, 0x25, - 0x46, 0xbb, 0xd9, 0x92, 0x15, 0x5a, 0x33, 0x09, 0xaa, 0x64, 0x1c, 0x95, 0x56, 0x0b, 0x07, 0x81, - 0x8d, 0x7f, 0xda, 0xc7, 0x41, 0x58, 0x9c, 0xd1, 0xc6, 0xa1, 0xd5, 0x35, 0x1e, 0xb0, 0x71, 0x38, - 0xb4, 0xb0, 0xe9, 0xb3, 0x52, 0x7d, 0x1c, 0x1a, 0x02, 0xda, 0x01, 0xa8, 0xf4, 0x7a, 0x75, 0x1c, - 0x90, 0xc5, 0x58, 0x9c, 0xa5, 0xa4, 0x2f, 0x71, 0xd2, 0x7b, 0x78, 0x9f, 0x57, 0x34, 0x56, 0xaa, - 0x97, 0x4f, 0x8e, 0xcb, 0xf3, 0x4e, 0xaf, 0xd7, 0x0c, 0x58, 0x91, 0x46, 0x54, 0xa1, 0xc1, 0xf8, - 0x7e, 0xe4, 0x85, 0x98, 0x2f, 0xc5, 0x62, 0x21, 0xc6, 0x77, 0xa5, 0x4e, 0xf4, 0xd7, 0xa7, 0x85, - 0x4d, 0xbe, 0xac, 0xe3, 0x7c, 0x57, 0x10, 0xc8, 0xb7, 0xb8, 0xea, 0x84, 0xce, 0xbe, 0x13, 0x60, - 0xbe, 0x3c, 0x2e, 0x6a, 0xdf, 0xa2, 0x5e, 0xd9, 0x78, 0xc0, 0xbe, 0xc5, 0x36, 0x2f, 0x6d, 0x1a, - 0xd6, 0x4b, 0x8c, 0x1e, 0xe1, 0x48, 0x34, 0xf0, 0x22, 0x1a, 0xc0, 0x91, 0x57, 0x78, 0xdf, 0xcc, - 0x91, 0x08, 0x14, 0xad, 0xc3, 0xf8, 0x1e, 0xde, 0x67, 0x92, 0xe3, 0x12, 0xa5, 0x77, 0x31, 0xa2, - 0xc7, 0x64, 0xc6, 0x03, 0xf6, 0x55, 0x10, 0x6a, 0x49, 0x69, 0x21, 0xb1, 0xd1, 0x6f, 0xe5, 0x60, - 0x51, 0x7c, 0xe1, 0x38, 0x7c, 0xe5, 0xf9, 0x2f, 0xdc, 0xee, 0x41, 0xcd, 0xeb, 0x3e, 0x77, 0x0f, - 0x8a, 0x73, 0x94, 0xf2, 0xb5, 0x98, 0xd0, 0x88, 0x41, 0x35, 0x56, 0xaa, 0x6f, 0x9e, 0x1c, 0x97, - 0x6f, 0x48, 0x01, 0x22, 0xeb, 0xc9, 0x82, 0x7c, 0xee, 0x1e, 0x68, 0x0d, 0xa7, 0xb5, 0x85, 0x7e, - 0x23, 0x07, 0x0b, 0x7c, 0x74, 0x36, 0x6e, 0x79, 0x7e, 0x3b, 0xea, 0xc6, 0x3c, 0xed, 0x46, 0x59, - 0x7e, 0xad, 0x26, 0xa0, 0xc6, 0x4a, 0xf5, 0xd6, 0xc9, 0x71, 0xd9, 0xe2, 0x8c, 0x6b, 0xfa, 0xa2, - 0xda, 0xd4, 0x89, 0x94, 0x86, 0xc8, 0x4a, 0x20, 0xc2, 0x7f, 0xc7, 0xc7, 0xcf, 0xb1, 0x8f, 0xbb, - 0x2d, 0x5c, 0x5c, 0xd0, 0x56, 0x82, 0x5e, 0x29, 0xa4, 0x32, 0xd9, 0x4a, 0x9a, 0x3d, 0x59, 0xac, - 0xaf, 0x04, 0x1d, 0x05, 0xfd, 0x14, 0x10, 0x67, 0x40, 0xa5, 0xdf, 0x76, 0x43, 0x3e, 0xc0, 0x45, - 0xda, 0xca, 0x92, 0xce, 0x67, 0x05, 0xa0, 0xb1, 0x52, 0xb5, 0x4e, 0x8e, 0xcb, 0x57, 0x05, 0x8b, - 0x1d, 0x52, 0x65, 0x1a, 0x98, 0x81, 0x38, 0x91, 0xbc, 0x9b, 0x5e, 0xeb, 0x45, 0xb1, 0xa8, 0x49, - 0x5e, 0x52, 0x24, 0x44, 0x76, 0xc7, 0x6b, 0xbd, 0xd0, 0x25, 0x2f, 0xa9, 0x45, 0x21, 0x5c, 0xe2, - 0xb3, 0x64, 0xe3, 0x20, 0xf4, 0x5d, 0x2a, 0x3b, 0x82, 0xe2, 0x65, 0x4a, 0x67, 0x59, 0xc8, 0xe0, - 0x24, 0x44, 0xe3, 0x1d, 0xd6, 0x5b, 0xbe, 0x10, 0x9a, 0xbe, 0x52, 0xa7, 0x35, 0x63, 0x22, 0x8f, - 0xfe, 0x0c, 0xcc, 0xef, 0xb9, 0xdd, 0xb6, 0xf7, 0x2a, 0x58, 0xc5, 0xc1, 0x8b, 0xd0, 0xeb, 0xd5, - 0x99, 0xa6, 0x57, 0x2c, 0xd1, 0x76, 0xaf, 0x8a, 0x65, 0x6e, 0x82, 0x69, 0x3c, 0xa8, 0xde, 0x3c, - 0x39, 0x2e, 0x5f, 0x7f, 0xc5, 0x2a, 0x9b, 0x6d, 0x56, 0xdb, 0xe4, 0xca, 0xa2, 0xd6, 0xb8, 0xb9, - 0x15, 0xb2, 0x04, 0xf4, 0x8a, 0xe2, 0x92, 0xb6, 0x04, 0xf4, 0x4a, 0x21, 0x0c, 0x62, 0x0d, 0xea, - 0x4b, 0x40, 0x47, 0x41, 0x8f, 0x60, 0x5c, 0x88, 0x87, 0xe2, 0xb2, 0xf6, 0xe9, 0x8a, 0xe2, 0xc6, - 0x03, 0xa6, 0x01, 0x09, 0x11, 0xa3, 0x7f, 0xb9, 0x02, 0x0a, 0x6d, 0xc2, 0x04, 0x95, 0x91, 0x54, - 0x64, 0x5d, 0xa1, 0x94, 0x90, 0x58, 0xa8, 0xa2, 0xbc, 0xf1, 0xa0, 0x5a, 0x3c, 0x39, 0x2e, 0xcf, - 0x31, 0x29, 0x9b, 0x10, 0x54, 0x11, 0x01, 0xf4, 0x00, 0x86, 0x2a, 0xbd, 0x5e, 0xf1, 0x2a, 0xa5, - 0x33, 0x15, 0xd1, 0x69, 0x3c, 0xa8, 0x5e, 0x3c, 0x39, 0x2e, 0x4f, 0x3b, 0x3d, 0x7d, 0x58, 0x04, - 0x1a, 0xed, 0x43, 0xa1, 0xde, 0xf5, 0x5e, 0x3d, 0xef, 0x38, 0x2f, 0xb0, 0x10, 0x6f, 0xe5, 0x74, - 0xf1, 0x46, 0x37, 0xab, 0x40, 0x20, 0x18, 0x85, 0x5c, 0x82, 0x1e, 0xd9, 0x16, 0x9f, 0xf4, 0xf7, - 0xb1, 0xdf, 0xc5, 0x21, 0x0e, 0xf8, 0x68, 0xaf, 0x69, 0xdb, 0x62, 0xbc, 0xba, 0xf1, 0x80, 0xb5, - 0xf4, 0x42, 0x96, 0x9b, 0xc6, 0x9e, 0xa0, 0x8a, 0x3a, 0x70, 0x31, 0x2a, 0x13, 0x5b, 0xcd, 0x75, - 0xda, 0x54, 0x29, 0xd1, 0x54, 0xb4, 0xdd, 0x5c, 0x3b, 0x39, 0x2e, 0x2f, 0x2b, 0x6d, 0x99, 0xb6, - 0x9c, 0x24, 0x61, 0xf4, 0x04, 0x26, 0x36, 0xba, 0x41, 0xe8, 0x74, 0x3a, 0xd8, 0x2f, 0x5a, 0xda, - 0xf4, 0xc9, 0xf2, 0xc6, 0x7d, 0x26, 0xc4, 0x5d, 0x51, 0xa0, 0xcf, 0x9e, 0x84, 0x43, 0x6d, 0x98, - 0x55, 0xf7, 0x1c, 0xf2, 0xbd, 0xdc, 0xa0, 0x24, 0x8b, 0x86, 0x4d, 0x8c, 0x7c, 0x29, 0xf7, 0xab, - 0x57, 0x4f, 0x8e, 0xcb, 0x25, 0x6d, 0x17, 0x8b, 0x7f, 0x22, 0x71, 0x92, 0xe8, 0x2f, 0x10, 0x19, - 0x5d, 0x79, 0xba, 0xb9, 0xd1, 0xde, 0xe1, 0x45, 0x54, 0xe9, 0x24, 0xfa, 0xfc, 0x1b, 0xba, 0x8c, - 0x36, 0x02, 0x35, 0xee, 0xb3, 0x9d, 0x22, 0x70, 0x8e, 0x3a, 0x4d, 0xb7, 0x2d, 0xbf, 0xcb, 0x66, - 0x8f, 0x03, 0xc4, 0x84, 0xb4, 0x91, 0x08, 0xfa, 0x11, 0xcc, 0xc8, 0x1a, 0xb6, 0xe2, 0x6e, 0xa6, - 0xaf, 0x38, 0x3a, 0x48, 0xa5, 0xbd, 0xe4, 0x82, 0x8b, 0x11, 0x23, 0x5f, 0x15, 0x51, 0x58, 0x1f, - 0xf9, 0x5e, 0xbf, 0x57, 0xbc, 0xa5, 0x4d, 0x8b, 0x2c, 0x6f, 0xdc, 0x67, 0x5f, 0x15, 0xd1, 0x75, - 0x9b, 0x07, 0xa4, 0x44, 0x9f, 0x17, 0x09, 0x48, 0xf6, 0xe9, 0x67, 0x1b, 0x5c, 0xca, 0xbf, 0xa9, - 0x7d, 0xec, 0xa2, 0x58, 0x4c, 0x71, 0xdf, 0x35, 0x09, 0x74, 0x89, 0x8d, 0x1c, 0x98, 0xd9, 0x7e, - 0x11, 0x3a, 0x1b, 0x47, 0xe4, 0xd4, 0x66, 0xf7, 0x3b, 0xb8, 0x78, 0x5b, 0x13, 0x4c, 0x7a, 0xa5, - 0x98, 0x5f, 0xef, 0x45, 0xe8, 0x34, 0x5d, 0x5a, 0xdc, 0xf4, 0xfb, 0x31, 0x05, 0x3b, 0x46, 0x90, - 0xc8, 0x3e, 0x52, 0x52, 0x09, 0x02, 0xf7, 0xa0, 0x7b, 0x84, 0xbb, 0x61, 0xf1, 0xad, 0x44, 0x13, - 0x51, 0x65, 0xe3, 0x3e, 0x93, 0x7d, 0xb4, 0x09, 0x47, 0x16, 0x27, 0x5b, 0x88, 0x50, 0xaa, 0x00, - 0xe3, 0xe2, 0x00, 0xf7, 0x78, 0x78, 0x7c, 0xac, 0x30, 0x6e, 0xad, 0xc3, 0xc8, 0x9e, 0x13, 0xb6, - 0x0e, 0xd1, 0x67, 0x30, 0xf2, 0xc4, 0xed, 0xb6, 0x83, 0x62, 0xee, 0xda, 0x10, 0xd5, 0xf1, 0xd9, - 0xe9, 0x92, 0x56, 0x92, 0x8a, 0xea, 0xe2, 0xcf, 0x8f, 0xcb, 0x17, 0x4e, 0x8e, 0xcb, 0xb3, 0x2f, - 0x08, 0x98, 0x72, 0xc4, 0x64, 0x78, 0xd6, 0x3f, 0xcf, 0xc3, 0x84, 0x84, 0x46, 0xcb, 0x30, 0x4c, - 0xfe, 0xa7, 0x67, 0xd5, 0x89, 0xea, 0xf8, 0xc9, 0x71, 0x79, 0x98, 0xe0, 0xd9, 0xb4, 0x14, 0xad, - 0xc0, 0xe4, 0xa6, 0xe7, 0xb4, 0xeb, 0xb8, 0xe5, 0xe3, 0x30, 0xa0, 0x87, 0xd1, 0xf1, 0x6a, 0xe1, - 0xe4, 0xb8, 0x3c, 0xd5, 0xf1, 0x9c, 0x76, 0x33, 0x60, 0xe5, 0xb6, 0x0a, 0x44, 0x28, 0xd2, 0x93, - 0xd4, 0x50, 0x44, 0x91, 0x9c, 0x38, 0x6c, 0x5a, 0x8a, 0x1e, 0xc3, 0xe8, 0x43, 0xb7, 0x43, 0x04, - 0xc6, 0x30, 0xed, 0xff, 0x72, 0xbc, 0xff, 0x77, 0x58, 0xf5, 0x5a, 0x37, 0xf4, 0x5f, 0xb3, 0x83, - 0xc6, 0x73, 0x5a, 0xa0, 0x0c, 0x84, 0x53, 0x40, 0xf7, 0x60, 0xac, 0xde, 0xdf, 0xa7, 0xdd, 0x1f, - 0xa1, 0x8d, 0xd1, 0xdd, 0x20, 0xe8, 0xef, 0x37, 0xc9, 0x10, 0x14, 0x04, 0x01, 0x56, 0xfa, 0x10, - 0x26, 0x15, 0xf2, 0xa8, 0x00, 0x43, 0x2f, 0xf0, 0x6b, 0x36, 0x76, 0x9b, 0xfc, 0x89, 0xe6, 0x60, - 0xe4, 0xa5, 0xd3, 0xe9, 0x63, 0x3a, 0xd4, 0x09, 0x9b, 0xfd, 0xf8, 0x28, 0xff, 0x41, 0xce, 0xfa, - 0x83, 0x51, 0x28, 0xac, 0x7b, 0x41, 0x48, 0x4e, 0xbe, 0x52, 0x85, 0xbf, 0x01, 0xa3, 0xa4, 0x6c, - 0x63, 0x95, 0xf3, 0x6f, 0xf2, 0xe4, 0xb8, 0x3c, 0x76, 0xe8, 0x05, 0x61, 0xd3, 0x6d, 0xdb, 0xbc, - 0x0a, 0xbd, 0x05, 0xe3, 0x5b, 0x5e, 0x1b, 0x53, 0xa6, 0x50, 0xb2, 0xd5, 0xe9, 0x93, 0xe3, 0xf2, - 0x44, 0xd7, 0x6b, 0x63, 0x7a, 0x8a, 0xb4, 0x65, 0x35, 0x6a, 0xf0, 0xd3, 0x1f, 0xe3, 0x5d, 0x95, - 0xf0, 0x8e, 0x1c, 0xf7, 0x7e, 0x79, 0x5c, 0x7e, 0xef, 0xc0, 0x0d, 0x0f, 0xfb, 0xfb, 0x77, 0x5a, - 0xde, 0xd1, 0xdd, 0x03, 0xdf, 0x79, 0xe9, 0x86, 0xd4, 0x96, 0xe0, 0x74, 0xee, 0x4a, 0x0b, 0x85, - 0xd3, 0x73, 0xb9, 0x65, 0xa4, 0xfe, 0x3a, 0x08, 0xf1, 0x11, 0xa1, 0xc4, 0x0f, 0x87, 0x7b, 0x30, - 0x57, 0x69, 0xb7, 0x5d, 0x86, 0xb1, 0xe3, 0xbb, 0xdd, 0x96, 0xdb, 0x73, 0x3a, 0x01, 0x9d, 0x83, - 0x89, 0xea, 0x8d, 0x93, 0xe3, 0x72, 0xd9, 0x91, 0xf5, 0xcd, 0x9e, 0x04, 0x50, 0x78, 0x68, 0x24, - 0x80, 0x1e, 0xc0, 0xf8, 0xea, 0x56, 0x9d, 0x1e, 0x1d, 0x8b, 0x23, 0x94, 0x18, 0xfd, 0x48, 0xdb, - 0xdd, 0x80, 0x0e, 0x4d, 0x25, 0x20, 0x01, 0xd1, 0x7b, 0x30, 0xb5, 0xd3, 0xdf, 0xef, 0xb8, 0xad, - 0xdd, 0xcd, 0xfa, 0x13, 0xfc, 0x9a, 0x9e, 0xb9, 0xa7, 0x98, 0x8a, 0xd5, 0xa3, 0xe5, 0xcd, 0xb0, - 0x13, 0x34, 0x5f, 0xe0, 0xd7, 0xb6, 0x06, 0x17, 0xe1, 0xd5, 0xeb, 0xeb, 0x04, 0x6f, 0x2c, 0x81, - 0x17, 0x04, 0x87, 0x2a, 0x1e, 0x83, 0x43, 0x77, 0x01, 0xd8, 0x49, 0xa6, 0xd2, 0x6e, 0xb3, 0x23, - 0xf9, 0x44, 0x75, 0xf6, 0xe4, 0xb8, 0x3c, 0xc9, 0xcf, 0x3e, 0x4e, 0xbb, 0xed, 0xdb, 0x0a, 0x08, - 0xaa, 0xc1, 0xb8, 0xed, 0x31, 0x06, 0xf3, 0x83, 0xf8, 0xac, 0x3c, 0x88, 0xb3, 0x62, 0x6e, 0x7a, - 0xe1, 0xbf, 0xd4, 0x51, 0x0a, 0x08, 0x54, 0x86, 0xb1, 0x2d, 0xaf, 0xe6, 0xb4, 0x0e, 0xd9, 0x71, - 0x7c, 0xbc, 0x3a, 0x72, 0x72, 0x5c, 0xce, 0x7d, 0xdb, 0x16, 0xa5, 0xe8, 0x25, 0x4c, 0x46, 0x13, - 0x15, 0x14, 0x27, 0x29, 0xfb, 0x76, 0x4f, 0x8e, 0xcb, 0x0b, 0x01, 0x2d, 0x6e, 0x92, 0xa9, 0x57, - 0x38, 0xf8, 0x35, 0x56, 0x81, 0xda, 0x10, 0xea, 0xc0, 0x95, 0x67, 0x64, 0x43, 0xdc, 0xef, 0xe0, - 0xa8, 0xb8, 0x12, 0x04, 0xd8, 0x27, 0xb4, 0x36, 0x56, 0xe9, 0x69, 0x7d, 0x82, 0x1f, 0x13, 0xa2, - 0x9e, 0x10, 0xd9, 0xc5, 0x40, 0x9a, 0xae, 0xfa, 0x71, 0x65, 0x13, 0xb3, 0xfe, 0x45, 0x0e, 0xd0, - 0x76, 0x0f, 0x77, 0xeb, 0xf5, 0x75, 0xf2, 0xe9, 0x88, 0x2f, 0xe7, 0x36, 0x8c, 0x13, 0xe9, 0x4f, - 0x16, 0x09, 0xff, 0x76, 0xa6, 0x4e, 0x8e, 0xcb, 0xe3, 0x7d, 0x5e, 0x66, 0xcb, 0x5a, 0xf4, 0x2d, - 0x98, 0x60, 0xb3, 0x49, 0xa6, 0x3c, 0x4f, 0xa7, 0x7c, 0xe6, 0xe4, 0xb8, 0x0c, 0x7c, 0xca, 0xc9, - 0x74, 0x47, 0x00, 0xe8, 0x26, 0x0c, 0xed, 0xee, 0x6e, 0xd2, 0x0f, 0x68, 0xa8, 0x7a, 0xe9, 0xe4, - 0xb8, 0x3c, 0x14, 0x86, 0x9d, 0x5f, 0x1e, 0x97, 0xc7, 0x57, 0xfb, 0xcc, 0xfa, 0x66, 0x93, 0x7a, - 0x74, 0x13, 0xc6, 0x84, 0xe2, 0x32, 0x1c, 0x7d, 0xb9, 0x5c, 0x23, 0xb1, 0x45, 0x9d, 0xf5, 0x6b, - 0x30, 0xa9, 0xf4, 0x9d, 0x88, 0x36, 0xf2, 0x3f, 0xed, 0xf0, 0x14, 0x13, 0x6d, 0x2d, 0x32, 0x26, - 0x5a, 0x6a, 0xfd, 0xfe, 0x14, 0x14, 0x48, 0xaf, 0x35, 0x09, 0xa1, 0xf5, 0x3e, 0x37, 0xa8, 0xf7, - 0x2a, 0x57, 0xf2, 0x99, 0x5c, 0xa9, 0xc3, 0xd8, 0xda, 0x97, 0x3d, 0xd7, 0xc7, 0x01, 0x37, 0xe5, - 0x95, 0xee, 0x30, 0x0b, 0xed, 0x1d, 0x61, 0xa1, 0xbd, 0xb3, 0x2b, 0x2c, 0xb4, 0xd5, 0x2b, 0x7c, - 0x4b, 0xb8, 0x88, 0x19, 0x4a, 0x34, 0x7b, 0x3f, 0xfb, 0x6f, 0xe5, 0x9c, 0x2d, 0x28, 0xa1, 0x6f, - 0xc1, 0xe8, 0x43, 0xcf, 0x3f, 0x72, 0x42, 0xce, 0x14, 0x26, 0x7e, 0x69, 0x89, 0x26, 0x7e, 0x69, - 0x09, 0x7a, 0x08, 0x33, 0xb6, 0xd7, 0x0f, 0xf1, 0xae, 0x27, 0x58, 0xc9, 0xa4, 0x30, 0xdd, 0x50, - 0x7d, 0x52, 0xd3, 0x0c, 0xbd, 0xa4, 0x96, 0x67, 0xc7, 0xb0, 0xd0, 0x1a, 0xcc, 0x68, 0x86, 0x91, - 0xa0, 0x38, 0x4a, 0x3f, 0x05, 0x76, 0x68, 0xd4, 0xcc, 0x29, 0xaa, 0x3c, 0x89, 0x21, 0xa1, 0x2d, - 0x93, 0x56, 0x3a, 0x46, 0x7b, 0x34, 0x50, 0xf3, 0x34, 0xe9, 0x9d, 0x18, 0x66, 0x79, 0x47, 0xe5, - 0x31, 0x64, 0x9c, 0x9b, 0x53, 0xd8, 0x96, 0x15, 0xab, 0xad, 0xde, 0xe0, 0x5c, 0x5e, 0x92, 0x63, - 0x4f, 0x1e, 0x4c, 0xec, 0x38, 0x4d, 0x22, 0x41, 0xe5, 0xee, 0x30, 0x41, 0x7b, 0xcb, 0x8c, 0x74, - 0x62, 0x77, 0x50, 0x65, 0x8b, 0xdc, 0x27, 0x36, 0x61, 0xe4, 0x59, 0xe0, 0x1c, 0x30, 0xc9, 0x32, - 0xb3, 0x72, 0x9d, 0xf7, 0x28, 0xbe, 0xfa, 0xa8, 0x5d, 0x97, 0x02, 0xd2, 0x4f, 0x61, 0x96, 0x1a, - 0xad, 0x55, 0x8d, 0x80, 0xd6, 0xa1, 0xef, 0x01, 0xf0, 0x5e, 0x91, 0x93, 0xcd, 0x24, 0x57, 0xbf, - 0xb4, 0x41, 0x56, 0x7a, 0xbd, 0xea, 0x55, 0x3e, 0xbe, 0x05, 0x39, 0x3e, 0xed, 0xac, 0x63, 0x2b, - 0x44, 0xd0, 0x67, 0x30, 0x45, 0x05, 0x8f, 0x98, 0xd1, 0x29, 0x3a, 0xa3, 0xd4, 0xf4, 0x4b, 0x65, - 0x89, 0x61, 0x3e, 0x35, 0x04, 0xf4, 0x67, 0x61, 0x9e, 0x93, 0x8b, 0x1d, 0x33, 0xa7, 0xf9, 0xb1, - 0x5a, 0xeb, 0x9e, 0x0e, 0x53, 0x7d, 0x9b, 0xf7, 0xd4, 0x92, 0x3d, 0x4d, 0x3d, 0x78, 0xda, 0xe6, - 0x66, 0xd0, 0x06, 0xcc, 0x3e, 0x0b, 0xb0, 0x36, 0x86, 0x19, 0x2a, 0xc5, 0xe9, 0x89, 0xa9, 0x1f, - 0xe0, 0x66, 0xda, 0x38, 0xe2, 0x78, 0xc8, 0x06, 0xb4, 0xea, 0x7b, 0xbd, 0xd8, 0x1a, 0x9f, 0xa5, - 0x1c, 0xa1, 0x06, 0x80, 0xb6, 0xef, 0xf5, 0x9a, 0xe9, 0x0b, 0xdd, 0x80, 0x8d, 0x7e, 0x0c, 0x0b, - 0x91, 0x9d, 0x72, 0xd5, 0x75, 0x0e, 0xba, 0x5e, 0x10, 0xba, 0xad, 0x8d, 0x55, 0x6a, 0xf2, 0xe3, - 0xc2, 0x3b, 0xb2, 0x73, 0x36, 0xdb, 0x12, 0x44, 0x17, 0xde, 0x29, 0x54, 0xd0, 0x0f, 0x61, 0x9a, - 0xb7, 0xc5, 0xed, 0xe2, 0x17, 0xb3, 0x17, 0x9a, 0x04, 0xe6, 0x36, 0x6a, 0xf1, 0x93, 0x29, 0x38, - 0x3a, 0x2d, 0xf4, 0x05, 0x4c, 0x3e, 0x7d, 0x58, 0xb1, 0x71, 0xd0, 0xf3, 0xba, 0x01, 0xe6, 0x76, - 0xbe, 0xab, 0x9c, 0xf4, 0xd3, 0x87, 0x95, 0x4a, 0x3f, 0x3c, 0xc4, 0xdd, 0xd0, 0x6d, 0x39, 0x21, - 0x16, 0x50, 0xcc, 0xf8, 0x7e, 0xf4, 0xdc, 0x69, 0xfa, 0xbc, 0x44, 0x19, 0x85, 0x4a, 0xce, 0xfa, - 0x1c, 0x26, 0xe4, 0xb2, 0x47, 0x63, 0x30, 0x54, 0xe9, 0x74, 0x0a, 0x17, 0xc8, 0x1f, 0xf5, 0xfa, - 0x7a, 0x21, 0x87, 0x66, 0x00, 0xa2, 0x6f, 0xbd, 0x90, 0x47, 0x53, 0x91, 0x99, 0xa1, 0x30, 0x44, - 0xe1, 0x7b, 0xbd, 0xc2, 0x30, 0x42, 0x71, 0xfb, 0x46, 0x61, 0xc4, 0xfa, 0x18, 0x26, 0xe4, 0x40, - 0xd0, 0x2c, 0x4c, 0x3e, 0xdb, 0xaa, 0xef, 0xac, 0xd5, 0x36, 0x1e, 0x6e, 0xac, 0xad, 0x16, 0x2e, - 0xa0, 0x2b, 0x70, 0x79, 0xb7, 0xbe, 0xde, 0x5c, 0xad, 0x36, 0x37, 0xb7, 0x6b, 0x95, 0xcd, 0xe6, - 0x8e, 0xbd, 0xfd, 0xf9, 0xf7, 0x9b, 0xbb, 0xcf, 0xb6, 0xb6, 0xd6, 0x36, 0x0b, 0x39, 0xeb, 0xbf, - 0xe4, 0x12, 0xf2, 0x84, 0xa8, 0xd7, 0xfc, 0xd4, 0xb6, 0x15, 0xed, 0x83, 0x54, 0xbd, 0x16, 0x27, - 0x3e, 0xca, 0x3e, 0x15, 0x88, 0x6c, 0x11, 0x3b, 0x84, 0x51, 0x2d, 0xaf, 0xa3, 0x6e, 0x11, 0x3d, - 0x5e, 0x66, 0xcb, 0x5a, 0xb4, 0xa2, 0x6c, 0x26, 0x43, 0x91, 0x7e, 0x2c, 0x36, 0x13, 0x55, 0xb0, - 0xc8, 0x6d, 0x65, 0x45, 0x31, 0xba, 0x0c, 0x47, 0x38, 0x06, 0x41, 0x26, 0xe1, 0xac, 0x7e, 0xca, - 0xa7, 0x8a, 0x3e, 0x4e, 0xd8, 0x88, 0xd8, 0x08, 0xa9, 0x2c, 0x8a, 0x7d, 0x91, 0x09, 0xf3, 0x4f, - 0x19, 0x46, 0x36, 0xbd, 0x03, 0xb7, 0xcb, 0x07, 0x39, 0x71, 0x72, 0x5c, 0x1e, 0xe9, 0x90, 0x02, - 0x9b, 0x95, 0x5b, 0x7f, 0x6d, 0x48, 0x15, 0x5b, 0xf2, 0xd8, 0x91, 0x33, 0x1e, 0x3b, 0xbe, 0x05, - 0x13, 0xfc, 0xe0, 0xba, 0xb1, 0xca, 0x29, 0xd2, 0x6d, 0x58, 0x98, 0x41, 0xdd, 0xb6, 0x1d, 0x01, - 0x10, 0x85, 0x91, 0xed, 0xc9, 0x54, 0x61, 0x1c, 0x8a, 0x14, 0x46, 0xbe, 0x6b, 0x33, 0x85, 0x31, - 0x02, 0x21, 0x13, 0xa9, 0x5e, 0x22, 0x0d, 0x47, 0x13, 0xa9, 0x5e, 0x17, 0xe9, 0x57, 0x44, 0x1f, - 0x01, 0x54, 0xf6, 0xea, 0x54, 0x5d, 0xb2, 0xb7, 0xf8, 0xd6, 0x49, 0x17, 0xb9, 0xf3, 0x2a, 0xe0, - 0x0a, 0x97, 0xaf, 0x6a, 0x96, 0x0a, 0x34, 0xaa, 0xc2, 0x74, 0xe5, 0xd7, 0xfb, 0x3e, 0xde, 0x68, - 0x93, 0xef, 0x24, 0x64, 0x2a, 0xf4, 0x04, 0xbf, 0x80, 0x20, 0x15, 0x4d, 0x97, 0xd7, 0x28, 0x04, - 0x74, 0x14, 0xb4, 0x0d, 0x17, 0x1f, 0xd5, 0x84, 0xd5, 0xa0, 0xd2, 0x6a, 0x79, 0xfd, 0x6e, 0xc8, - 0xf7, 0xcb, 0xeb, 0x27, 0xc7, 0xe5, 0x2b, 0x07, 0xad, 0xc8, 0xf0, 0xe0, 0xb0, 0x6a, 0x75, 0xc3, - 0x4c, 0xe0, 0x5a, 0x1d, 0x98, 0x79, 0x84, 0x43, 0xb2, 0x94, 0x84, 0xf2, 0x93, 0x3d, 0x27, 0x9f, - 0xc0, 0xe4, 0x9e, 0x1b, 0x1e, 0xea, 0x87, 0x4b, 0xca, 0x81, 0x57, 0x6e, 0x78, 0x28, 0x0e, 0x97, - 0xea, 0x67, 0xae, 0x80, 0x5b, 0x6b, 0x30, 0xcb, 0x5b, 0x93, 0xba, 0xd6, 0x8a, 0x4e, 0x30, 0x17, - 0x9d, 0x56, 0x55, 0x82, 0x3a, 0x99, 0x3f, 0xc8, 0xc3, 0x7c, 0xed, 0xd0, 0xe9, 0x1e, 0xe0, 0x1d, - 0x27, 0x08, 0x5e, 0x79, 0x7e, 0x5b, 0xe9, 0x3c, 0xbd, 0xc2, 0x4b, 0x74, 0x9e, 0xde, 0xd3, 0xad, - 0xc0, 0xe4, 0x76, 0xa7, 0x2d, 0x70, 0xb8, 0x5e, 0x4a, 0xdb, 0xf2, 0x3a, 0xed, 0x66, 0x4f, 0xd0, - 0x52, 0x81, 0x08, 0xce, 0x16, 0x7e, 0x25, 0x71, 0x86, 0x22, 0x9c, 0x2e, 0x7e, 0xa5, 0xe0, 0x28, - 0x40, 0x68, 0x0d, 0x2e, 0xd6, 0x71, 0xcb, 0xeb, 0xb6, 0x1f, 0x3a, 0xad, 0xd0, 0xf3, 0xd9, 0x4d, - 0xc6, 0x70, 0xa4, 0x27, 0x04, 0xb4, 0xb2, 0xf9, 0x9c, 0xd6, 0xb2, 0x0b, 0x0c, 0x3b, 0x89, 0x81, - 0xb6, 0xe9, 0x3d, 0x08, 0xbd, 0x08, 0xe7, 0x37, 0xa8, 0x37, 0xef, 0xc8, 0x9b, 0xf1, 0x9a, 0x8f, - 0xe9, 0xa2, 0x70, 0x3a, 0x52, 0x71, 0x97, 0x62, 0x97, 0x0a, 0x17, 0x01, 0x69, 0x4b, 0x22, 0xd6, - 0x33, 0x98, 0xde, 0xe9, 0xf4, 0x0f, 0xdc, 0x2e, 0x11, 0x03, 0x75, 0xfc, 0x53, 0xb4, 0x0a, 0x10, - 0x15, 0x70, 0xe3, 0x84, 0x30, 0x35, 0x45, 0x15, 0x8d, 0x07, 0xfc, 0x43, 0xa2, 0x25, 0x54, 0x41, - 0xb2, 0x15, 0x3c, 0xeb, 0x2f, 0x0d, 0x01, 0xe2, 0x13, 0x50, 0x0f, 0x9d, 0x10, 0xd7, 0x71, 0x48, - 0x84, 0xed, 0x02, 0xe4, 0xe5, 0x19, 0x7b, 0xf4, 0xe4, 0xb8, 0x9c, 0x77, 0xdb, 0x76, 0x7e, 0x63, - 0x15, 0xbd, 0x03, 0x23, 0x14, 0x8c, 0xf2, 0x7f, 0x46, 0xb6, 0xa7, 0x52, 0x60, 0x92, 0x23, 0x20, - 0x7f, 0xda, 0x0c, 0x18, 0xbd, 0x0b, 0x13, 0xab, 0xb8, 0x83, 0x0f, 0x9c, 0xd0, 0x13, 0x5f, 0x37, - 0x3b, 0xb5, 0x8a, 0x42, 0x65, 0xcd, 0x45, 0x90, 0x44, 0x3b, 0xb6, 0xb1, 0x13, 0x78, 0x5d, 0x55, - 0x3b, 0xf6, 0x69, 0x89, 0xaa, 0x1d, 0x33, 0x18, 0xf4, 0xbb, 0x39, 0x98, 0xac, 0x74, 0xbb, 0xfc, - 0x34, 0x18, 0x70, 0xae, 0xcf, 0xdf, 0x91, 0x0e, 0x06, 0x9b, 0xce, 0x3e, 0xee, 0x34, 0x9c, 0x4e, - 0x1f, 0x07, 0xd5, 0x2f, 0x88, 0xc2, 0xf2, 0x5f, 0x8f, 0xcb, 0x1f, 0x9f, 0xe1, 0x7c, 0x17, 0xb9, - 0x2a, 0xec, 0xfa, 0x8e, 0x1b, 0x06, 0xf4, 0x92, 0x30, 0x6a, 0x50, 0xfd, 0x6e, 0x94, 0x7e, 0xa0, - 0xb7, 0x60, 0x84, 0x9d, 0x37, 0x99, 0x92, 0x4d, 0x65, 0x71, 0xec, 0xa0, 0x69, 0x33, 0x08, 0xeb, - 0x86, 0xdc, 0xef, 0x36, 0x56, 0xd3, 0xa6, 0xc0, 0xaa, 0xc1, 0xf2, 0x23, 0x1c, 0xda, 0x38, 0xc0, - 0xa1, 0x58, 0xb4, 0x74, 0xc9, 0x45, 0x26, 0x92, 0x31, 0xfa, 0x5b, 0x22, 0xd3, 0xf9, 0x60, 0x0b, - 0x55, 0xd4, 0x58, 0x7f, 0x31, 0x07, 0xe5, 0x9a, 0x8f, 0xd9, 0x7e, 0x9f, 0x42, 0x28, 0x5b, 0x98, - 0x2c, 0x73, 0x9f, 0x8b, 0x7c, 0x54, 0x4b, 0xb8, 0xc4, 0xfd, 0x2a, 0x4e, 0x77, 0x2a, 0xb4, 0x9e, - 0xc3, 0xbc, 0x8d, 0xbb, 0xf8, 0x15, 0x39, 0xcd, 0x6a, 0xa7, 0xb8, 0x32, 0x8c, 0xb0, 0x2f, 0x2f, - 0x31, 0x04, 0x56, 0x7e, 0xb6, 0x43, 0xaa, 0xf5, 0x0f, 0xf3, 0x50, 0x60, 0xc3, 0xad, 0x7a, 0xe1, - 0xe9, 0xc6, 0xc7, 0x47, 0x90, 0x1f, 0x70, 0xae, 0xbd, 0x15, 0x71, 0x7b, 0x28, 0x52, 0x0e, 0x68, - 0x57, 0xc9, 0x1e, 0x27, 0x2a, 0xc9, 0x80, 0xd8, 0x2a, 0x60, 0x16, 0x20, 0x3a, 0x20, 0xba, 0x0a, - 0xf8, 0xdc, 0xa3, 0xdf, 0xce, 0xc1, 0x28, 0x5b, 0x57, 0xd9, 0x2b, 0x77, 0xef, 0x7c, 0x56, 0x6e, - 0x21, 0xa4, 0x7f, 0xa9, 0xdf, 0x11, 0xab, 0xb3, 0xfe, 0x71, 0x1e, 0x2e, 0x2a, 0xbc, 0x62, 0x62, - 0x09, 0xbd, 0xc5, 0x74, 0x1b, 0x85, 0x61, 0xd4, 0xa6, 0x46, 0x0d, 0xcd, 0xd1, 0x49, 0x99, 0x72, - 0xee, 0x2d, 0x18, 0x27, 0x43, 0x8a, 0x9b, 0xdf, 0xe8, 0x0e, 0xcb, 0x40, 0x45, 0xf5, 0xa9, 0xb9, - 0x77, 0x17, 0xc6, 0xe9, 0x9f, 0x64, 0x46, 0x86, 0xd3, 0x67, 0x44, 0x02, 0x21, 0x17, 0xe0, 0xb1, - 0xe7, 0x76, 0x9f, 0xe2, 0xf0, 0xd0, 0x13, 0xc6, 0xca, 0x0d, 0x22, 0x07, 0xff, 0x3f, 0xcf, 0xed, - 0x36, 0x8f, 0x68, 0xf1, 0x59, 0xcd, 0x3b, 0x11, 0x41, 0x5b, 0x21, 0x6e, 0xdd, 0x83, 0x02, 0x11, - 0x59, 0xa7, 0x5f, 0x5a, 0xd6, 0x1c, 0xa0, 0x47, 0x38, 0xac, 0x7a, 0xda, 0x66, 0x6a, 0x4d, 0xc3, - 0xe4, 0x8e, 0xdb, 0x3d, 0x10, 0x3f, 0xff, 0xe9, 0x10, 0x4c, 0xb1, 0xdf, 0x7c, 0x06, 0x62, 0x2a, - 0x4f, 0xee, 0x34, 0x2a, 0xcf, 0x07, 0x30, 0xcd, 0x6f, 0x9e, 0xb0, 0x4f, 0x6f, 0x24, 0xd8, 0x7c, - 0xd0, 0x23, 0x03, 0xbb, 0x79, 0x6a, 0xbe, 0x64, 0x35, 0xb6, 0x0e, 0x88, 0x36, 0x61, 0x86, 0x15, - 0x3c, 0xc4, 0x4e, 0xd8, 0x8f, 0xac, 0x1e, 0xb3, 0xfc, 0xd4, 0x20, 0x8a, 0x99, 0x3c, 0xe3, 0xb4, - 0x9e, 0xf3, 0x42, 0x3b, 0x86, 0x8b, 0x3e, 0x83, 0xd9, 0x1d, 0xdf, 0xfb, 0xf2, 0xb5, 0xa2, 0xe4, - 0x31, 0x91, 0x3e, 0x7f, 0x72, 0x5c, 0xbe, 0xd8, 0x23, 0x55, 0x4d, 0x55, 0xd5, 0x8b, 0x43, 0x93, - 0x35, 0xb5, 0x11, 0x54, 0x3d, 0xdf, 0xed, 0x1e, 0xd0, 0xd9, 0x1c, 0x67, 0x6b, 0xca, 0x0d, 0x9a, - 0xfb, 0xb4, 0xd0, 0x96, 0xd5, 0x31, 0xe3, 0xe3, 0xd8, 0x60, 0xe3, 0xe3, 0x3d, 0x80, 0x4d, 0xcf, - 0x69, 0x57, 0x3a, 0x9d, 0x5a, 0x25, 0xa0, 0x26, 0x07, 0xd5, 0xe4, 0xee, 0x74, 0x3a, 0xcd, 0x96, - 0x13, 0xd8, 0x0a, 0xcc, 0xe3, 0xe1, 0xf1, 0xd1, 0xc2, 0x98, 0x3d, 0xbb, 0xe9, 0xb6, 0x70, 0x37, - 0xc0, 0x7b, 0x8e, 0xdf, 0x75, 0xbb, 0x07, 0x81, 0xf5, 0x27, 0xc3, 0x30, 0x2e, 0x87, 0x7c, 0x47, - 0x3d, 0xf6, 0x70, 0xd5, 0x88, 0x4a, 0xa8, 0xc8, 0x2c, 0x62, 0x2b, 0x10, 0xe8, 0x32, 0xbb, 0xe6, - 0x64, 0x4a, 0xd9, 0x18, 0x59, 0xdd, 0x4e, 0xaf, 0xc7, 0x2e, 0x33, 0x17, 0x20, 0xbf, 0x5a, 0xa5, - 0xfc, 0x1f, 0x67, 0x3b, 0x41, 0x7b, 0xdf, 0xce, 0xaf, 0x56, 0xc9, 0x2a, 0xdb, 0xde, 0x58, 0xad, - 0x51, 0x56, 0x8e, 0xb3, 0x55, 0xe6, 0xb9, 0xed, 0x96, 0x4d, 0x4b, 0x49, 0x6d, 0xbd, 0xf2, 0x74, - 0x93, 0xb3, 0x8b, 0xd6, 0x06, 0xce, 0x51, 0xc7, 0xa6, 0xa5, 0xe4, 0xa8, 0xc0, 0x4e, 0xb8, 0x35, - 0xaf, 0x1b, 0xfa, 0x5e, 0x27, 0xa0, 0x1a, 0xed, 0x38, 0x9b, 0x4e, 0x7e, 0x34, 0x6e, 0xf1, 0x2a, - 0x3b, 0x06, 0x8a, 0xf6, 0x60, 0xb1, 0xd2, 0x7e, 0xe9, 0x74, 0x5b, 0xb8, 0xcd, 0x6a, 0xf6, 0x3c, - 0xff, 0xc5, 0xf3, 0x8e, 0xf7, 0x2a, 0xa0, 0xfc, 0x1e, 0xe7, 0x96, 0x24, 0x0e, 0x22, 0x4e, 0xda, - 0xaf, 0x04, 0x90, 0x9d, 0x86, 0x4d, 0xa4, 0x64, 0xad, 0xe3, 0xf5, 0xdb, 0x7c, 0x16, 0xa8, 0x94, - 0x6c, 0x91, 0x02, 0x9b, 0x95, 0x13, 0x2e, 0xad, 0xd7, 0x9f, 0x52, 0xbb, 0x0d, 0xe7, 0xd2, 0x61, - 0x70, 0x64, 0x93, 0x32, 0x74, 0x13, 0xc6, 0xc4, 0xa9, 0x87, 0x99, 0x7f, 0xa9, 0x85, 0x51, 0x9c, - 0x76, 0x44, 0x1d, 0xf9, 0x24, 0x6c, 0xdc, 0xf2, 0x5e, 0x62, 0xff, 0x75, 0xcd, 0x6b, 0x63, 0x61, - 0x65, 0xe0, 0xa7, 0x68, 0x56, 0xd1, 0x6c, 0x91, 0x1a, 0x5b, 0x07, 0x24, 0x0d, 0x30, 0xc5, 0x29, - 0xa0, 0xbe, 0x43, 0xbc, 0x01, 0xa6, 0x58, 0x05, 0xb6, 0xa8, 0x43, 0xab, 0x70, 0xb1, 0xd2, 0x0f, - 0xbd, 0x23, 0x27, 0x74, 0x5b, 0xcf, 0x7a, 0x07, 0xbe, 0x43, 0x1a, 0x29, 0x50, 0x04, 0x7a, 0xb4, - 0x73, 0x44, 0x65, 0xb3, 0xcf, 0x6b, 0xed, 0x24, 0xc2, 0xe3, 0xe1, 0xf1, 0xc9, 0xc2, 0xd4, 0xe3, - 0xe1, 0xf1, 0xa9, 0xc2, 0xf4, 0xe3, 0xe1, 0xf1, 0xe9, 0xc2, 0x8c, 0x75, 0x1f, 0x2e, 0x32, 0x39, - 0x73, 0x6a, 0x85, 0xdf, 0xda, 0x01, 0xa8, 0xe3, 0x23, 0xa7, 0x77, 0xe8, 0x91, 0x15, 0x59, 0x55, - 0x7f, 0x71, 0x85, 0x11, 0x49, 0xdf, 0x15, 0x5e, 0xd1, 0x78, 0x20, 0xce, 0x69, 0x02, 0xd2, 0x56, - 0xb0, 0xac, 0xff, 0x98, 0x07, 0x44, 0x7d, 0x38, 0xea, 0xa1, 0x8f, 0x9d, 0x23, 0xd1, 0x8d, 0x0f, - 0x61, 0x8a, 0x6d, 0x19, 0xac, 0x98, 0x76, 0x87, 0x68, 0xa3, 0x4c, 0x56, 0xa8, 0x55, 0xeb, 0x17, - 0x6c, 0x0d, 0x94, 0xa0, 0xda, 0x38, 0xe8, 0x1f, 0x09, 0xd4, 0xbc, 0x86, 0xaa, 0x56, 0x11, 0x54, - 0xf5, 0x37, 0xfa, 0x0c, 0x66, 0x6a, 0xde, 0x51, 0x8f, 0xf0, 0x84, 0x23, 0x0f, 0xf1, 0x9d, 0x93, - 0xb7, 0xab, 0x55, 0xae, 0x5f, 0xb0, 0x63, 0xe0, 0x68, 0x0b, 0x2e, 0x3d, 0xec, 0xf4, 0x83, 0xc3, - 0x4a, 0xb7, 0x5d, 0xeb, 0x78, 0x81, 0xa0, 0x32, 0xcc, 0xed, 0xbb, 0x5c, 0xd2, 0x25, 0x21, 0xd6, - 0x2f, 0xd8, 0x26, 0x44, 0x74, 0x93, 0x3b, 0xa4, 0xf2, 0x1d, 0x7c, 0xfa, 0x0e, 0xf7, 0x57, 0xdd, - 0xee, 0xe2, 0xed, 0xe7, 0xeb, 0x17, 0x6c, 0x56, 0x5b, 0x9d, 0x80, 0x31, 0x21, 0xe5, 0xef, 0x92, - 0xc5, 0x22, 0xd9, 0x49, 0xb4, 0xe5, 0x7e, 0x80, 0x4a, 0x30, 0xfe, 0xac, 0x47, 0x84, 0x8f, 0x50, - 0xe1, 0x6c, 0xf9, 0xdb, 0xfa, 0x96, 0xce, 0x69, 0xb4, 0xac, 0x9e, 0xb3, 0x19, 0x70, 0x54, 0x60, - 0xad, 0xeb, 0xcc, 0xcd, 0x86, 0xd6, 0xda, 0xcd, 0xc7, 0xda, 0x2d, 0xc4, 0x79, 0x6d, 0xcd, 0x1b, - 0x99, 0x67, 0x7d, 0x0e, 0x57, 0x9f, 0xf5, 0xc8, 0xa1, 0xa6, 0xd2, 0xeb, 0x75, 0xdc, 0x16, 0xdd, - 0x4f, 0xd9, 0x6e, 0x20, 0x16, 0xcb, 0x7b, 0xd2, 0xdb, 0x31, 0x97, 0xea, 0x1b, 0x02, 0x27, 0xc7, - 0xe5, 0x51, 0xb6, 0xab, 0x08, 0x27, 0x47, 0xeb, 0x67, 0x39, 0xb8, 0xca, 0xbe, 0x80, 0x54, 0xd2, - 0xbf, 0xa6, 0xfa, 0x64, 0x2a, 0x6a, 0x8a, 0xf4, 0xc0, 0x54, 0xbd, 0x2e, 0xa3, 0xbb, 0xc4, 0x7c, - 0xfa, 0x5d, 0x62, 0xe6, 0xe5, 0xaa, 0xf5, 0x3d, 0xb0, 0x78, 0x8f, 0x3a, 0x9d, 0x44, 0xa7, 0x82, - 0xaf, 0xd2, 0x2b, 0xeb, 0x7f, 0xe6, 0x61, 0xf1, 0x11, 0xee, 0x62, 0xdf, 0xa1, 0xe3, 0xd4, 0x34, - 0xf2, 0xd3, 0xdf, 0xe1, 0x48, 0x75, 0x33, 0x9f, 0xa2, 0x6e, 0x5e, 0x86, 0xa1, 0x67, 0xf6, 0x06, - 0x1f, 0x16, 0x15, 0xa4, 0x7d, 0xdf, 0xb5, 0x49, 0x19, 0xda, 0x88, 0x6e, 0x3a, 0x86, 0x07, 0xde, - 0x74, 0x5c, 0xe2, 0x96, 0xdf, 0x31, 0x7e, 0xd3, 0xa1, 0xdf, 0x6f, 0x6c, 0x29, 0x3a, 0x2d, 0x11, - 0x37, 0x6f, 0xf3, 0x6f, 0x2a, 0x65, 0x80, 0x5c, 0x3d, 0x65, 0x57, 0xd1, 0x74, 0x09, 0x30, 0x2d, - 0x55, 0xe8, 0xa6, 0xa5, 0xef, 0xc1, 0xa4, 0x02, 0x62, 0xb8, 0x4e, 0xfe, 0x96, 0x7a, 0x9d, 0x3c, - 0xb9, 0xb2, 0x10, 0xe9, 0xd0, 0xf5, 0x90, 0x68, 0x07, 0x4c, 0x89, 0x56, 0xaf, 0x99, 0x3f, 0x86, - 0x62, 0xb2, 0x37, 0x5c, 0xe5, 0x1a, 0x74, 0x0a, 0xb1, 0x56, 0x61, 0xee, 0x11, 0x0e, 0x23, 0x87, - 0x50, 0xe5, 0x12, 0x2a, 0xf6, 0x9d, 0x65, 0x58, 0xbf, 0xac, 0x3a, 0xcc, 0xc7, 0xa8, 0xf0, 0xf6, - 0x3f, 0x82, 0x31, 0xe1, 0x4a, 0x92, 0x4b, 0x77, 0x25, 0xa1, 0xeb, 0x96, 0x53, 0xb6, 0x05, 0x82, - 0xb5, 0x07, 0x0b, 0x1a, 0xd1, 0x40, 0x52, 0xfd, 0x0e, 0x8c, 0x8b, 0xb2, 0x98, 0xd9, 0x40, 0x23, - 0x4b, 0x97, 0x56, 0x20, 0x90, 0x25, 0x8a, 0x75, 0x08, 0x0b, 0x9b, 0x6e, 0xa0, 0x53, 0x66, 0xa3, - 0x5e, 0x82, 0x89, 0x9e, 0x73, 0x80, 0x9b, 0x81, 0xfb, 0xeb, 0x6c, 0x7d, 0x8e, 0xd8, 0xe3, 0xa4, - 0xa0, 0xee, 0xfe, 0x3a, 0x46, 0x57, 0x00, 0x68, 0x25, 0xe5, 0x1f, 0x17, 0x2f, 0x14, 0x9c, 0x9d, - 0xe7, 0x10, 0x0c, 0x93, 0x65, 0xcc, 0x16, 0xa4, 0x4d, 0xff, 0xb6, 0x7c, 0x58, 0x4c, 0xb4, 0xc4, - 0xc7, 0x70, 0x17, 0x64, 0xd7, 0x32, 0xc6, 0x60, 0x4b, 0x20, 0x74, 0x0b, 0x66, 0xbb, 0xf8, 0xcb, - 0xb0, 0x99, 0xe8, 0xc3, 0x34, 0x29, 0xde, 0x11, 0xfd, 0xb0, 0x7e, 0x44, 0x4f, 0xd7, 0x71, 0x5f, - 0xaf, 0x73, 0x63, 0x5e, 0x07, 0x4a, 0x64, 0x48, 0xba, 0x6b, 0xcf, 0xaf, 0x8c, 0x81, 0x2f, 0x61, - 0xc9, 0xd8, 0xda, 0xaf, 0x9a, 0x89, 0x7f, 0x9a, 0x87, 0x45, 0xb6, 0x4b, 0x25, 0x3f, 0x8d, 0xd3, - 0xcb, 0xb0, 0x6f, 0xc4, 0x28, 0x7c, 0xcf, 0x60, 0x14, 0xa6, 0x28, 0xaa, 0x51, 0x58, 0x33, 0x05, - 0x7f, 0x60, 0x36, 0x05, 0x53, 0x05, 0x52, 0x37, 0x05, 0xc7, 0x0d, 0xc0, 0x6b, 0xe9, 0x06, 0x60, - 0x6a, 0x0e, 0x33, 0x18, 0x80, 0x0d, 0x66, 0xdf, 0xc7, 0xc3, 0xe3, 0xf9, 0xc2, 0x90, 0xd5, 0x80, - 0x62, 0x92, 0xc5, 0xe7, 0x20, 0x37, 0xfe, 0x30, 0x07, 0x57, 0xb8, 0x86, 0x11, 0xfb, 0x08, 0xce, - 0x3e, 0x83, 0xef, 0xc2, 0x14, 0xc7, 0xdd, 0x8d, 0x16, 0x0b, 0xf3, 0xda, 0x14, 0x92, 0x90, 0x89, - 0x53, 0x0d, 0x0c, 0xbd, 0xab, 0x9c, 0xf6, 0x99, 0x05, 0xe9, 0x32, 0xd9, 0x2e, 0x99, 0x59, 0x20, - 0xf5, 0xcc, 0x6f, 0x7d, 0x01, 0x57, 0xd3, 0x3a, 0x7e, 0x0e, 0x7c, 0xf9, 0xa3, 0x1c, 0x2c, 0x71, - 0xf2, 0xda, 0xe7, 0xf4, 0x95, 0x44, 0xfe, 0x19, 0xfc, 0x0e, 0x1e, 0xc3, 0x24, 0x69, 0x50, 0xf4, - 0x5b, 0x7f, 0x46, 0xa4, 0xd4, 0xac, 0x3a, 0xa1, 0xc3, 0xaf, 0xb2, 0x9c, 0xa3, 0x8e, 0xf0, 0x28, - 0xb4, 0x55, 0x64, 0xeb, 0x07, 0xb0, 0x6c, 0x1e, 0xc2, 0x39, 0xf0, 0xe7, 0x31, 0x94, 0x0c, 0x82, - 0xf3, 0xab, 0x6d, 0x88, 0xdf, 0x87, 0x25, 0x23, 0xad, 0x73, 0xe8, 0xe6, 0x3a, 0xd9, 0xee, 0xc3, - 0x73, 0x98, 0x42, 0x6b, 0x0f, 0x2e, 0x1b, 0x28, 0x9d, 0x43, 0x17, 0x1f, 0xc1, 0xa2, 0x54, 0x73, - 0xbf, 0x56, 0x0f, 0x9f, 0xc2, 0x15, 0x46, 0xe8, 0x7c, 0x66, 0xe5, 0x09, 0x2c, 0x71, 0x72, 0xe7, - 0xc0, 0xbd, 0x75, 0x58, 0x8e, 0x4e, 0xb3, 0x06, 0x5d, 0xe2, 0xd4, 0x42, 0xc6, 0xda, 0x84, 0x6b, - 0x11, 0xa5, 0x94, 0x8d, 0xf5, 0xf4, 0xd4, 0x98, 0x2e, 0x16, 0xcd, 0xd2, 0x39, 0xea, 0x62, 0x11, - 0xe0, 0xb9, 0xa9, 0x13, 0x1b, 0x70, 0x89, 0x11, 0xd6, 0xf5, 0xd6, 0x15, 0x55, 0x6f, 0x35, 0xbe, - 0xc0, 0x49, 0xaa, 0xb2, 0x4f, 0xa9, 0x2a, 0x2b, 0x40, 0xa2, 0x1e, 0xbe, 0x0b, 0xa3, 0xfc, 0x91, - 0x21, 0xeb, 0x9f, 0x81, 0x18, 0xd3, 0xd4, 0x19, 0x1a, 0x07, 0xb6, 0x7e, 0x0c, 0x57, 0xd8, 0x31, - 0x30, 0xee, 0xcc, 0x2e, 0xa6, 0xe4, 0x3b, 0xb1, 0x53, 0x60, 0x86, 0xcf, 0xbc, 0xe9, 0x30, 0xb8, - 0x2f, 0xd6, 0x76, 0x1a, 0xfd, 0x53, 0x79, 0x8a, 0x8a, 0xd3, 0x5d, 0xde, 0x78, 0xba, 0xbb, 0x01, - 0xd7, 0xe5, 0xe9, 0x2e, 0xde, 0x8c, 0x34, 0xdb, 0xfe, 0x00, 0x96, 0xd8, 0x40, 0xf5, 0xa7, 0x55, - 0xa2, 0x1b, 0x1f, 0xc7, 0x86, 0x99, 0xfa, 0x76, 0xcb, 0x34, 0xc8, 0xbf, 0x9a, 0x13, 0x9f, 0x9c, - 0x99, 0xf8, 0x37, 0x7d, 0xdc, 0xdd, 0x82, 0xb2, 0x64, 0x88, 0xde, 0xa3, 0xaf, 0x76, 0xd6, 0x7d, - 0x0a, 0xf3, 0x09, 0xf7, 0x7f, 0xa2, 0xb0, 0xa2, 0x77, 0xc8, 0x67, 0x41, 0x0b, 0xc4, 0xb2, 0x4b, - 0x7d, 0x2e, 0x60, 0x4b, 0x48, 0xab, 0x09, 0xcb, 0xc9, 0xa9, 0x70, 0x5b, 0xc2, 0x31, 0x08, 0x7d, - 0x46, 0x3e, 0x61, 0xf6, 0x06, 0x21, 0x37, 0xe0, 0x0d, 0x02, 0xff, 0x8e, 0x19, 0xba, 0xc0, 0xb2, - 0x2c, 0x21, 0x6a, 0x62, 0xe3, 0x27, 0xad, 0x8b, 0xf5, 0xf0, 0x4f, 0x72, 0x80, 0x44, 0x5d, 0xad, - 0x6e, 0x8b, 0xb6, 0x2f, 0xc3, 0x50, 0xad, 0x6e, 0x73, 0x87, 0x44, 0x7a, 0xde, 0x6e, 0x05, 0xbe, - 0x4d, 0xca, 0xe2, 0x6a, 0x6b, 0xfe, 0x34, 0x6a, 0xeb, 0x06, 0xa0, 0xba, 0x7b, 0xd0, 0xdd, 0x73, - 0xc3, 0x43, 0xd9, 0x58, 0x85, 0x9b, 0x88, 0xe9, 0x5b, 0xbd, 0xc0, 0x3d, 0xe8, 0x36, 0xe9, 0x2d, - 0xbc, 0x7c, 0x49, 0xd1, 0x72, 0x6c, 0x03, 0x92, 0xf5, 0x43, 0xb8, 0xa4, 0xf5, 0x97, 0x7f, 0xf7, - 0x99, 0xae, 0x97, 0xe8, 0x16, 0x8c, 0xd5, 0x2a, 0xf4, 0xc6, 0x8e, 0x5a, 0x18, 0xa6, 0x98, 0x84, - 0x6a, 0x39, 0x4d, 0xfa, 0xee, 0xdc, 0x16, 0x95, 0xd6, 0xdf, 0x1f, 0x56, 0xa8, 0x2b, 0xde, 0xa8, - 0x19, 0xec, 0xb8, 0x0f, 0xc0, 0xd6, 0x94, 0xc2, 0x0d, 0xa2, 0x32, 0x4e, 0xf2, 0x4b, 0x06, 0x26, - 0xc4, 0x6d, 0x05, 0xe8, 0xb4, 0x3e, 0xa8, 0xdc, 0xfb, 0x87, 0x21, 0x89, 0x9b, 0x38, 0xe9, 0xfd, - 0xc3, 0x49, 0x07, 0xb6, 0x0a, 0x84, 0x7e, 0x1c, 0xf7, 0xcb, 0x1a, 0xa1, 0x17, 0xdf, 0x6f, 0x70, - 0x43, 0x86, 0x61, 0x6c, 0x67, 0x73, 0xcd, 0x7a, 0x05, 0xf3, 0x04, 0xd7, 0x7d, 0x4e, 0x9d, 0xaf, - 0xd6, 0xbe, 0x0c, 0x71, 0x97, 0xed, 0x06, 0xa3, 0xb4, 0x9d, 0x9b, 0x19, 0xed, 0x44, 0xc0, 0xfc, - 0xa1, 0x74, 0x44, 0xa7, 0x89, 0x65, 0x9d, 0x6d, 0xa6, 0x4f, 0x57, 0x9d, 0xbd, 0xb9, 0xd6, 0x6d, - 0xf7, 0x3c, 0x57, 0x1e, 0x43, 0xd8, 0xaa, 0xf3, 0x3b, 0x4d, 0xcc, 0xcb, 0x6d, 0x15, 0xc8, 0xba, - 0x95, 0xe9, 0x8f, 0x35, 0x0e, 0xc3, 0xbb, 0xb5, 0xdd, 0xcd, 0x42, 0xce, 0xba, 0x0b, 0xa0, 0xb4, - 0x04, 0x30, 0xba, 0xb5, 0x6d, 0x3f, 0xad, 0x6c, 0x16, 0x2e, 0xa0, 0x79, 0xb8, 0xb8, 0xb7, 0xb1, - 0xb5, 0xba, 0xbd, 0x57, 0x6f, 0xd6, 0x9f, 0x56, 0xec, 0xdd, 0x5a, 0xc5, 0x5e, 0x2d, 0xe4, 0xac, - 0x2f, 0x60, 0x4e, 0x1f, 0xe1, 0xb9, 0x2e, 0xc2, 0x10, 0x2e, 0x49, 0x0d, 0xe8, 0xf1, 0xde, 0xae, - 0xe2, 0xbd, 0xc2, 0x8f, 0x54, 0xf1, 0x0b, 0x35, 0x7e, 0xf8, 0xe2, 0xdf, 0x9d, 0x02, 0xa4, 0x5d, - 0x83, 0xe6, 0x33, 0xaf, 0x41, 0xad, 0xf7, 0x61, 0x4e, 0x6f, 0xf5, 0xb4, 0x46, 0xa5, 0x37, 0xa8, - 0x5b, 0x8f, 0xe2, 0xd1, 0x48, 0xce, 0xf6, 0x51, 0x17, 0xb9, 0x2c, 0x7e, 0x1f, 0x0a, 0x1c, 0x2a, - 0xda, 0xab, 0x6f, 0x08, 0xab, 0x1f, 0x93, 0x99, 0xfa, 0x63, 0x76, 0xe1, 0x64, 0xf0, 0xa6, 0xb8, - 0x47, 0x18, 0xd4, 0xc2, 0xdf, 0xca, 0x41, 0x31, 0xe6, 0x1c, 0x58, 0x3b, 0x74, 0x3a, 0x1d, 0xdc, - 0x3d, 0xc0, 0xe8, 0x36, 0x0c, 0xef, 0x6e, 0xef, 0xee, 0x70, 0x3b, 0xdb, 0x1c, 0x5f, 0xa6, 0xa4, - 0x48, 0xc2, 0xd8, 0x14, 0x02, 0x3d, 0x81, 0x8b, 0xc2, 0x89, 0x45, 0x56, 0xf1, 0x63, 0xcc, 0x95, - 0x6c, 0x97, 0x98, 0x24, 0xde, 0xe3, 0xe1, 0xf1, 0x5c, 0x21, 0x6f, 0xfd, 0x6e, 0x0e, 0x16, 0x53, - 0xdc, 0x16, 0xd1, 0x5b, 0x5a, 0xc7, 0x2e, 0x29, 0x1d, 0x13, 0x20, 0xeb, 0x17, 0x78, 0xcf, 0x6a, - 0x8a, 0x8f, 0xce, 0xd0, 0x19, 0x7c, 0x74, 0xf8, 0x33, 0x65, 0x0a, 0xc7, 0x5f, 0x0e, 0x31, 0x4f, - 0xc8, 0x59, 0x98, 0xd6, 0x38, 0x60, 0x59, 0x30, 0xa5, 0xb6, 0x4c, 0xd8, 0x5c, 0xf3, 0xda, 0x92, - 0xcd, 0xe4, 0x6f, 0xeb, 0x6f, 0xe4, 0x60, 0x8e, 0xba, 0x53, 0x1e, 0xb8, 0xe4, 0xbb, 0x8a, 0x58, - 0xbc, 0xa2, 0x8d, 0x64, 0x59, 0x1b, 0x49, 0x0c, 0x56, 0x0e, 0xe9, 0xa3, 0xc4, 0x90, 0x96, 0x4d, - 0x43, 0xa2, 0xa7, 0x40, 0xd7, 0xeb, 0x6a, 0x23, 0x51, 0xae, 0x25, 0xfe, 0x76, 0x0e, 0x2e, 0x29, - 0x7d, 0x92, 0xfd, 0xbf, 0xaf, 0x75, 0x69, 0xc9, 0xd0, 0xa5, 0x04, 0x93, 0xab, 0x89, 0x1e, 0xbd, - 0x91, 0xd5, 0xa3, 0x81, 0x3c, 0xfe, 0xe3, 0x1c, 0xcc, 0x1b, 0x79, 0x80, 0x16, 0x88, 0xaa, 0xd5, - 0xf2, 0x71, 0xc8, 0xd9, 0xcb, 0x7f, 0x91, 0xf2, 0x8d, 0x20, 0xe8, 0xf3, 0xd8, 0x1e, 0x13, 0x36, - 0xff, 0x85, 0xde, 0x80, 0xe9, 0x1d, 0xec, 0xbb, 0x5e, 0x9b, 0x79, 0x6f, 0xb1, 0x1b, 0xee, 0x69, - 0x5b, 0x2f, 0x44, 0xcb, 0x30, 0x51, 0xe9, 0x1c, 0x78, 0xbe, 0x1b, 0x1e, 0xb2, 0x9b, 0xa1, 0x09, - 0x3b, 0x2a, 0x20, 0xb4, 0x57, 0xdd, 0x03, 0xe1, 0xb4, 0x31, 0x6d, 0xf3, 0x5f, 0xa8, 0x08, 0x63, - 0xc2, 0xc0, 0x43, 0xcd, 0x43, 0xb6, 0xf8, 0x49, 0x30, 0xbe, 0x67, 0xd3, 0x45, 0x40, 0x5f, 0xd3, - 0xd8, 0xfc, 0x97, 0xf5, 0x36, 0xcc, 0x99, 0xf8, 0x68, 0x5c, 0x32, 0x7f, 0x3e, 0x0f, 0x97, 0x2a, - 0xed, 0xf6, 0xd3, 0x87, 0x95, 0x55, 0xac, 0x2a, 0x38, 0xef, 0xc0, 0xf0, 0x46, 0xd7, 0x0d, 0xb9, - 0x76, 0x23, 0x1c, 0x7c, 0x0d, 0x90, 0x04, 0x8a, 0xcc, 0x10, 0xf9, 0x1f, 0xd9, 0x70, 0x69, 0xed, - 0x4b, 0x37, 0x08, 0xdd, 0xee, 0x81, 0xea, 0x25, 0x9c, 0x3f, 0x8d, 0x97, 0xf0, 0xfa, 0x05, 0xdb, - 0x84, 0x8c, 0x76, 0x61, 0x61, 0x0b, 0xbf, 0x32, 0x2c, 0x21, 0xf9, 0x78, 0x42, 0x92, 0x35, 0xac, - 0x9c, 0x14, 0x5c, 0x75, 0x85, 0xfe, 0x76, 0x9e, 0xbe, 0xb0, 0x52, 0x06, 0xc6, 0x5b, 0x7e, 0x06, - 0x73, 0x4a, 0x87, 0x22, 0x89, 0x93, 0xe3, 0xef, 0x40, 0x8d, 0xc3, 0x51, 0x3f, 0x24, 0x23, 0x3a, - 0xda, 0x83, 0x45, 0xbd, 0x53, 0x11, 0x65, 0xfd, 0x63, 0x30, 0x81, 0xac, 0x5f, 0xb0, 0xd3, 0xb0, - 0xd1, 0x0a, 0x0c, 0x55, 0x5a, 0x2f, 0x38, 0x5b, 0xcc, 0x53, 0xc6, 0x46, 0x56, 0x69, 0xbd, 0xa0, - 0xaf, 0x9b, 0x5b, 0x2f, 0xb4, 0xef, 0xe1, 0xdf, 0xe6, 0x60, 0x31, 0x65, 0x86, 0xd1, 0x55, 0x00, - 0x56, 0xa8, 0xc8, 0x76, 0xa5, 0x84, 0xa8, 0x5a, 0xec, 0x17, 0xf5, 0xe4, 0x1a, 0xa2, 0x1a, 0x87, - 0x78, 0x87, 0x10, 0x55, 0xd8, 0x0a, 0x10, 0xda, 0x81, 0x49, 0xf6, 0x8b, 0x3d, 0x87, 0x18, 0xa6, - 0x38, 0x48, 0xc3, 0x61, 0xef, 0x1f, 0xa8, 0x4a, 0xd2, 0xa6, 0x05, 0xcd, 0xf8, 0x33, 0x08, 0x95, - 0x04, 0x37, 0x67, 0xd6, 0xe2, 0xa3, 0x90, 0x83, 0x46, 0xb7, 0x61, 0x94, 0x15, 0xf2, 0x39, 0x14, - 0x51, 0x56, 0x22, 0x60, 0x5e, 0x6f, 0xfd, 0xdd, 0x1c, 0x2c, 0xb0, 0xbd, 0x2d, 0xf1, 0x69, 0xbc, - 0xaf, 0x7d, 0x1a, 0xd7, 0x65, 0x87, 0x4d, 0xc0, 0xda, 0xd7, 0x51, 0xd5, 0x7d, 0xe7, 0x4f, 0xfb, - 0x55, 0xa8, 0x48, 0xea, 0xba, 0xfd, 0x7b, 0x39, 0x61, 0xdd, 0x49, 0x2e, 0xdd, 0x35, 0x98, 0xfa, - 0x6a, 0x4b, 0x56, 0x43, 0x43, 0xef, 0xb2, 0x15, 0x95, 0xcf, 0x1e, 0x69, 0xe6, 0xa2, 0xfa, 0x04, - 0x4a, 0xe9, 0xac, 0x19, 0xb4, 0xac, 0xac, 0x87, 0x06, 0xec, 0xaf, 0x32, 0x9d, 0xfd, 0x04, 0x9d, - 0xfa, 0xeb, 0x6e, 0x4b, 0xcc, 0xe8, 0xad, 0xb8, 0x9f, 0x63, 0xaa, 0xef, 0x98, 0xda, 0xdb, 0x7c, - 0x74, 0x8d, 0xc0, 0x17, 0x27, 0x55, 0xdb, 0xd4, 0xee, 0xff, 0xcb, 0xbc, 0xbe, 0x16, 0xbf, 0x4a, - 0xa3, 0x35, 0x98, 0xde, 0xc2, 0xaf, 0x12, 0xed, 0x52, 0xbf, 0x98, 0x2e, 0x7e, 0xd5, 0x54, 0xda, - 0x56, 0x1d, 0xc6, 0x35, 0x1c, 0xb4, 0x0f, 0x33, 0x42, 0x6a, 0x9c, 0x56, 0x78, 0xb2, 0xb7, 0x60, - 0xa4, 0x85, 0x94, 0x97, 0x1b, 0x31, 0x8a, 0xe7, 0xff, 0x3d, 0x5b, 0x3b, 0x50, 0x4c, 0x72, 0x8f, - 0xb7, 0xf6, 0xce, 0xa0, 0xb9, 0x67, 0x66, 0x90, 0xb6, 0xbe, 0x0e, 0xd6, 0xa9, 0x69, 0x4a, 0xc2, - 0x48, 0x5b, 0xc3, 0xbd, 0xf8, 0x64, 0x50, 0xff, 0x1a, 0x31, 0x19, 0xea, 0x73, 0x64, 0xe1, 0xf6, - 0x5a, 0xa3, 0xd6, 0x3d, 0x95, 0x12, 0xef, 0xd8, 0xdb, 0x30, 0xc6, 0x8b, 0xe4, 0x33, 0xef, 0xf8, - 0xaa, 0x14, 0x00, 0xd6, 0xef, 0xe5, 0xe0, 0x32, 0xb5, 0x35, 0xba, 0xdd, 0x83, 0x0e, 0x7e, 0x16, - 0xe8, 0x9e, 0xab, 0xdf, 0xd6, 0x04, 0xcd, 0x62, 0xca, 0xfb, 0x9d, 0x5f, 0x95, 0x78, 0xf9, 0x3b, - 0x39, 0x28, 0x99, 0xfa, 0x76, 0xbe, 0x12, 0xe6, 0x0e, 0x3f, 0x96, 0xe5, 0xb9, 0x15, 0x85, 0xa1, - 0xcb, 0x36, 0xc5, 0x60, 0xc9, 0x20, 0xc9, 0xff, 0x9a, 0x68, 0xf9, 0x3f, 0x39, 0x98, 0xdb, 0x08, - 0x68, 0xf7, 0x7f, 0xda, 0x77, 0x7d, 0x2c, 0xdd, 0xff, 0xef, 0x98, 0x9e, 0x13, 0xd2, 0x79, 0x35, - 0x87, 0xa9, 0x78, 0x47, 0x79, 0x39, 0x93, 0xcf, 0x7a, 0x27, 0xa8, 0xc5, 0x26, 0xb9, 0x05, 0xc3, - 0x5b, 0x44, 0x9d, 0x1a, 0xe2, 0xeb, 0x8f, 0x61, 0x90, 0x22, 0xfa, 0xc8, 0x85, 0x74, 0x99, 0xfc, - 0x40, 0x0f, 0x13, 0x4f, 0x69, 0x86, 0x07, 0xbf, 0x83, 0x4b, 0x06, 0x55, 0xa9, 0x8e, 0xc3, 0xe8, - 0xae, 0xe3, 0x1f, 0xe0, 0xd0, 0xfa, 0x01, 0x94, 0xb8, 0x97, 0x0f, 0xb3, 0xde, 0x52, 0x5f, 0xa0, - 0x20, 0x72, 0xe4, 0xca, 0xf2, 0xcc, 0xb9, 0x0a, 0x50, 0x0f, 0x1d, 0x3f, 0xdc, 0xe8, 0xb6, 0xf1, - 0x97, 0x74, 0xb4, 0x23, 0xb6, 0x52, 0x62, 0xbd, 0x0b, 0x13, 0x72, 0x08, 0xf4, 0x2c, 0xa7, 0x68, - 0x8c, 0x74, 0x38, 0x73, 0xda, 0xe3, 0x1e, 0xf1, 0xa2, 0xe7, 0x01, 0xcc, 0xc7, 0xa6, 0x82, 0xaf, - 0x93, 0x12, 0x99, 0x30, 0x56, 0xc6, 0x5c, 0x17, 0x6d, 0xf9, 0xdb, 0xaa, 0xc1, 0xc5, 0xc4, 0x4c, - 0x23, 0x44, 0x5f, 0x7b, 0xb1, 0x73, 0x3a, 0xd9, 0x50, 0xea, 0xf5, 0x75, 0x52, 0xb6, 0xbb, 0x59, - 0x67, 0xce, 0xd9, 0xa4, 0x6c, 0x77, 0xb3, 0x5e, 0x1d, 0x65, 0x2b, 0xc7, 0xfa, 0x47, 0x79, 0x7a, - 0x7c, 0x4d, 0xf0, 0x20, 0x66, 0x3b, 0x54, 0xed, 0x97, 0x55, 0x98, 0xa0, 0x23, 0x5e, 0x15, 0xcf, - 0x0f, 0xb2, 0x1d, 0x53, 0xc6, 0x7f, 0x7e, 0x5c, 0xbe, 0x40, 0xbd, 0x51, 0x22, 0x34, 0xf4, 0x29, - 0x8c, 0xad, 0x75, 0xdb, 0x94, 0xc2, 0xd0, 0x19, 0x28, 0x08, 0x24, 0x32, 0x0f, 0xb4, 0xcb, 0x44, - 0x15, 0xe2, 0x06, 0x24, 0x5b, 0x29, 0xa1, 0x6c, 0x76, 0x8f, 0x5c, 0xe6, 0x00, 0x36, 0x62, 0xb3, - 0x1f, 0x84, 0x9b, 0xb4, 0x0b, 0xe2, 0xe9, 0xfd, 0x84, 0x2d, 0x7f, 0x23, 0x0b, 0x46, 0xb6, 0xfd, - 0x36, 0x7f, 0x38, 0x3b, 0xb3, 0x32, 0x25, 0x62, 0x17, 0x92, 0x32, 0x9b, 0x55, 0x59, 0xff, 0x2b, - 0x07, 0x8b, 0x8f, 0x70, 0x68, 0x5c, 0x37, 0x1a, 0x57, 0x72, 0x5f, 0x9b, 0x2b, 0xf9, 0xaf, 0xc2, - 0x15, 0x39, 0xea, 0xa1, 0xb4, 0x51, 0x0f, 0xa7, 0x8d, 0x7a, 0x24, 0x7d, 0xd4, 0x8f, 0x60, 0x94, - 0x0d, 0x15, 0xdd, 0x80, 0x91, 0x8d, 0x10, 0x1f, 0x45, 0x66, 0x0d, 0xd5, 0xad, 0xce, 0x66, 0x75, - 0xe4, 0xc4, 0xb5, 0xe9, 0x04, 0xa1, 0x78, 0x0e, 0x30, 0x61, 0x8b, 0x9f, 0xd6, 0x4f, 0xe8, 0xc3, - 0xa5, 0x4d, 0xaf, 0xf5, 0x42, 0xb1, 0x52, 0x8f, 0xb1, 0xaf, 0x32, 0x7e, 0xab, 0x41, 0xa0, 0x58, - 0x8d, 0x2d, 0x20, 0xd0, 0x35, 0x98, 0xdc, 0xe8, 0x3e, 0xf4, 0xfc, 0x16, 0xde, 0xee, 0x76, 0x18, - 0xf5, 0x71, 0x5b, 0x2d, 0xe2, 0xb6, 0x18, 0xde, 0x42, 0x64, 0x8b, 0xa1, 0x05, 0x31, 0x5b, 0x0c, - 0x0b, 0x6f, 0x65, 0xb3, 0x3a, 0x6e, 0xea, 0x21, 0x7f, 0x67, 0x19, 0x62, 0xa4, 0xc5, 0x66, 0x10, - 0xe0, 0x3e, 0x5c, 0xb6, 0x71, 0xaf, 0xe3, 0x10, 0x85, 0xeb, 0xc8, 0x63, 0xf0, 0x72, 0xcc, 0xd7, - 0x0c, 0xfe, 0xe3, 0xba, 0x51, 0x59, 0x76, 0x39, 0x9f, 0xd1, 0xe5, 0x23, 0xb8, 0xfe, 0x08, 0x87, - 0xc6, 0x18, 0x55, 0xd1, 0xe0, 0xd7, 0x61, 0x3c, 0xd0, 0xed, 0xf7, 0x83, 0xc2, 0x63, 0xf1, 0x1b, - 0x2e, 0x4e, 0x47, 0xfe, 0x65, 0x7d, 0x06, 0xe5, 0xb4, 0xe6, 0x4e, 0xe7, 0x03, 0xeb, 0xc2, 0xb5, - 0x74, 0x02, 0x72, 0x5b, 0x14, 0xb6, 0x7e, 0x79, 0x74, 0xce, 0xee, 0xad, 0x7e, 0x3d, 0xc0, 0xff, - 0xb0, 0xaa, 0xc2, 0x1b, 0xf0, 0x6b, 0x74, 0xb7, 0x49, 0xaf, 0xd1, 0x75, 0x02, 0x11, 0x5f, 0x2b, - 0x30, 0x2e, 0xca, 0x38, 0x5f, 0x53, 0xc3, 0x7f, 0x51, 0x86, 0xb6, 0x05, 0x01, 0x89, 0x66, 0xfd, - 0x44, 0x5c, 0x29, 0xe9, 0x18, 0xa7, 0x7b, 0x14, 0x73, 0x9a, 0x3b, 0x24, 0xcb, 0x83, 0xcb, 0x3a, - 0x6d, 0xd5, 0xf0, 0x5f, 0x50, 0x0c, 0xff, 0xcc, 0xde, 0x7f, 0x4d, 0x37, 0x44, 0xe7, 0xf9, 0xba, - 0x8c, 0x8a, 0xd0, 0x55, 0xd5, 0xbc, 0x3f, 0x95, 0x7c, 0x45, 0x74, 0x0f, 0x4a, 0xa6, 0x06, 0x15, - 0x03, 0x8a, 0xb4, 0x21, 0xf3, 0xc8, 0x11, 0xbf, 0x91, 0x03, 0x4b, 0xf3, 0x8c, 0xd2, 0x22, 0x39, - 0xc9, 0x4f, 0xe6, 0x2d, 0x21, 0xd8, 0xa8, 0x2f, 0x16, 0xf3, 0x8d, 0xef, 0x90, 0x02, 0xf5, 0xe9, - 0x16, 0x93, 0x76, 0xf7, 0x60, 0x6c, 0x0b, 0x7f, 0x19, 0x89, 0x1f, 0xa6, 0x8b, 0x52, 0x6f, 0xa9, - 0x17, 0x58, 0x7d, 0x14, 0x2a, 0xc0, 0x88, 0x22, 0x74, 0x23, 0xb3, 0x0f, 0xbc, 0xff, 0xfb, 0x50, - 0x88, 0xd7, 0xf1, 0xb9, 0x1f, 0x18, 0xd4, 0x8a, 0xbe, 0xae, 0x88, 0xc7, 0xb2, 0x0a, 0xec, 0x04, - 0xbd, 0xb3, 0xf7, 0x1e, 0x7d, 0x08, 0xb0, 0xeb, 0x85, 0x4e, 0xa7, 0x46, 0x6d, 0x5c, 0x54, 0xf0, - 0xb3, 0x8b, 0xa7, 0x90, 0x94, 0x36, 0xe3, 0xaf, 0x57, 0x15, 0x60, 0xeb, 0xbb, 0xf4, 0x8b, 0x34, - 0x77, 0xfa, 0x74, 0x1f, 0x49, 0x0d, 0x6e, 0xc4, 0x3c, 0x11, 0xbe, 0x02, 0x91, 0x10, 0xe6, 0x09, - 0xfb, 0x65, 0x48, 0xac, 0x6f, 0x66, 0xd6, 0xff, 0x43, 0x8e, 0xb9, 0x4f, 0xaa, 0xcd, 0xf2, 0x89, - 0xae, 0x01, 0x44, 0xa5, 0x31, 0xff, 0x7c, 0x35, 0xc2, 0x17, 0x3d, 0xbc, 0x46, 0x11, 0xbe, 0x02, - 0x5b, 0x41, 0xfb, 0x66, 0x67, 0xf2, 0x01, 0x75, 0x3f, 0x90, 0xad, 0x9f, 0x8e, 0xef, 0xef, 0x09, - 0x1b, 0xcd, 0x19, 0xf1, 0x0e, 0x61, 0x4e, 0x0b, 0x82, 0x1c, 0x45, 0x75, 0x8d, 0x82, 0x3f, 0x4f, - 0x54, 0x3f, 0xf9, 0xe5, 0x71, 0xf9, 0x83, 0xb3, 0xbc, 0xea, 0x12, 0x34, 0x77, 0xe5, 0xe3, 0x45, - 0x6b, 0x11, 0x86, 0x6a, 0xf6, 0x26, 0x15, 0x55, 0xf6, 0xa6, 0x14, 0x55, 0xf6, 0xa6, 0xf5, 0xaf, - 0xf2, 0x50, 0x66, 0x6f, 0x97, 0xa9, 0xd7, 0x4a, 0x74, 0x56, 0x52, 0xdc, 0x60, 0x4e, 0x6b, 0x21, - 0x88, 0xbd, 0x4d, 0xce, 0x9f, 0xe6, 0x6d, 0xf2, 0xff, 0xff, 0xd5, 0xad, 0xaa, 0x2c, 0xc0, 0x5d, - 0x64, 0x18, 0x60, 0xb5, 0x26, 0x0b, 0x41, 0x4a, 0x13, 0x49, 0x93, 0xc6, 0xf0, 0xd9, 0x4d, 0x1a, - 0xd6, 0xdf, 0xcc, 0xc3, 0xb5, 0x74, 0x0e, 0xf2, 0x96, 0x56, 0xb5, 0xa8, 0xb4, 0x19, 0xde, 0x36, - 0xf4, 0x4b, 0x50, 0xa2, 0xd2, 0xc6, 0x23, 0xd1, 0x8a, 0xf7, 0x3b, 0xb1, 0x5b, 0x2a, 0xed, 0x59, - 0x8f, 0x88, 0xe9, 0xcd, 0x8a, 0xb4, 0xc0, 0x52, 0xbc, 0x0c, 0xed, 0xc3, 0xe2, 0x8e, 0xef, 0xbe, - 0x74, 0x42, 0xfc, 0x04, 0xbf, 0xde, 0xf1, 0x3a, 0x6e, 0xeb, 0xf5, 0x5a, 0xd7, 0xd9, 0xef, 0xe0, - 0x36, 0xbf, 0x71, 0xbf, 0x7d, 0x72, 0x5c, 0x7e, 0xa3, 0xc7, 0x40, 0xc8, 0x67, 0xd6, 0xec, 0x51, - 0xa0, 0x26, 0x66, 0x50, 0x0a, 0xd1, 0x34, 0x42, 0xd6, 0xbf, 0xcf, 0xc1, 0x12, 0x55, 0x8f, 0xf9, - 0x3d, 0x81, 0x68, 0xfc, 0x2b, 0x39, 0x5d, 0xaa, 0x03, 0xe4, 0x2b, 0x8b, 0x3a, 0x5d, 0x6a, 0xef, - 0x9b, 0x6c, 0x0d, 0x0c, 0x6d, 0xc0, 0x24, 0xff, 0xad, 0x18, 0x83, 0xe7, 0x15, 0xf1, 0x43, 0x17, - 0x2e, 0xb3, 0x05, 0xd1, 0x65, 0xca, 0x89, 0x35, 0xe9, 0xab, 0x5f, 0x15, 0xd7, 0xfa, 0x45, 0x1e, - 0x96, 0x1b, 0xd8, 0x77, 0x9f, 0xbf, 0x4e, 0x19, 0xcc, 0x36, 0xcc, 0x89, 0x22, 0x3a, 0x66, 0xfd, - 0x83, 0x61, 0x41, 0x6b, 0x44, 0x57, 0x03, 0x02, 0xd0, 0x94, 0xdf, 0x8f, 0x11, 0xf1, 0x0c, 0xee, - 0x94, 0xef, 0xc0, 0x78, 0x2c, 0x1e, 0x00, 0x9d, 0x7f, 0xf1, 0xbd, 0xe9, 0x31, 0x0e, 0xe5, 0x87, - 0xf7, 0x9b, 0xe9, 0x17, 0x8e, 0xdc, 0x2e, 0x30, 0x28, 0x9a, 0x0a, 0xfd, 0xfc, 0xc8, 0xa7, 0xe7, - 0x28, 0xb5, 0x86, 0xcf, 0x6f, 0xfd, 0x82, 0x9d, 0xd6, 0x52, 0x75, 0x12, 0x26, 0x2a, 0xf4, 0x3a, - 0x94, 0x1c, 0xc3, 0xff, 0x77, 0x1e, 0xae, 0x8a, 0x17, 0x39, 0x29, 0x6c, 0xfe, 0x1c, 0x16, 0x45, - 0x51, 0xa5, 0x47, 0xb6, 0x7f, 0xdc, 0xd6, 0x39, 0xcd, 0x02, 0x47, 0x09, 0x4e, 0x3b, 0x1c, 0x26, - 0x62, 0x76, 0x1a, 0xfa, 0xf9, 0x98, 0x37, 0x3f, 0x35, 0x45, 0x67, 0xa0, 0x66, 0x46, 0x55, 0x02, - 0xea, 0x21, 0xdf, 0x55, 0x69, 0xd8, 0x4e, 0x98, 0x47, 0x87, 0xbf, 0xae, 0x79, 0x74, 0xfd, 0x42, - 0xdc, 0x40, 0x5a, 0x9d, 0x81, 0xa9, 0x2d, 0xfc, 0x2a, 0xe2, 0xfb, 0x6f, 0xe5, 0x62, 0x0f, 0x08, - 0x89, 0xbe, 0xc0, 0x5e, 0x12, 0xe6, 0xa2, 0x07, 0xfe, 0xf4, 0x01, 0xa1, 0xaa, 0x2f, 0x30, 0xd0, - 0x0d, 0x18, 0x63, 0x0e, 0xb8, 0xed, 0x53, 0x9c, 0xb4, 0xe5, 0xd3, 0x9a, 0x16, 0x43, 0x61, 0x87, - 0x6e, 0x8e, 0x6f, 0x3d, 0x81, 0xeb, 0xdc, 0xff, 0x5b, 0x9f, 0x7c, 0xda, 0xd0, 0x19, 0x37, 0x23, - 0xcb, 0x81, 0xab, 0x8f, 0x70, 0x5c, 0xf4, 0x68, 0x4f, 0x8f, 0x3e, 0x83, 0x59, 0xad, 0x5c, 0x52, - 0xa4, 0x3a, 0xa6, 0x5c, 0x43, 0x92, 0x74, 0x1c, 0xda, 0xba, 0x66, 0x6a, 0x42, 0xed, 0xac, 0x85, - 0x69, 0x04, 0x28, 0x3f, 0xba, 0x13, 0x0e, 0xce, 0x20, 0xf5, 0x6e, 0x2b, 0xdf, 0x35, 0x93, 0x78, - 0x2c, 0x4a, 0x8f, 0xd8, 0x47, 0x65, 0xad, 0x35, 0x0d, 0x93, 0x35, 0xaf, 0x1b, 0xe2, 0x2f, 0xa9, - 0xe2, 0x62, 0xcd, 0xc0, 0x94, 0xa8, 0xea, 0xe0, 0x20, 0xb0, 0x7e, 0x7f, 0x08, 0x2c, 0xce, 0x58, - 0x93, 0x2d, 0x54, 0xf0, 0x63, 0x3f, 0xd1, 0x59, 0xbe, 0x51, 0x2d, 0xa8, 0x16, 0xdf, 0xa8, 0x96, - 0xad, 0x3c, 0xaa, 0xb5, 0xb5, 0xa2, 0x52, 0x3d, 0xaa, 0x6d, 0x7c, 0xf4, 0x3f, 0x4c, 0x11, 0x93, - 0xec, 0x63, 0xa3, 0x01, 0xa5, 0x53, 0xc4, 0xa4, 0x46, 0xd7, 0x2c, 0x32, 0x6d, 0x8d, 0x0d, 0x5c, - 0x81, 0x40, 0xf2, 0xe5, 0xa4, 0xac, 0xe1, 0xbe, 0x45, 0xac, 0xa0, 0x99, 0x48, 0xa2, 0xa0, 0x12, - 0x41, 0xcf, 0x74, 0x5e, 0xf2, 0xef, 0x51, 0xf8, 0x60, 0xa8, 0x55, 0x8c, 0x6a, 0x4f, 0x29, 0xd1, - 0x73, 0x52, 0x68, 0xb0, 0x8a, 0x7d, 0xfb, 0x77, 0xa4, 0x17, 0x3e, 0xd9, 0x48, 0xdd, 0x0e, 0xe6, - 0x4f, 0x4e, 0xc4, 0xb4, 0xf4, 0xcd, 0x77, 0xd9, 0xb9, 0x53, 0xc9, 0x68, 0x1a, 0x96, 0x13, 0x73, - 0xf4, 0xb4, 0x0b, 0x14, 0x13, 0x7d, 0xeb, 0x38, 0x27, 0xde, 0x1e, 0x24, 0x2e, 0x78, 0xcf, 0xaa, - 0x17, 0x56, 0xb5, 0x3b, 0xd9, 0x7c, 0xca, 0x9d, 0xac, 0x76, 0x83, 0x15, 0x0e, 0xb8, 0xa4, 0x1d, - 0xfa, 0xfa, 0x97, 0x3a, 0xff, 0x63, 0x04, 0x2e, 0xee, 0x38, 0x07, 0x6e, 0x97, 0xc8, 0x1e, 0x11, - 0x29, 0x17, 0x55, 0x12, 0x09, 0x0a, 0xb2, 0x9d, 0x5c, 0x0d, 0x19, 0x08, 0x56, 0xd4, 0x58, 0xe1, - 0xf9, 0xb4, 0xf7, 0xa0, 0x7a, 0x44, 0xf0, 0x0f, 0x35, 0x1b, 0x7e, 0x22, 0x59, 0x06, 0xf5, 0xba, - 0xeb, 0x7a, 0xed, 0x58, 0xd2, 0x0e, 0x6a, 0x07, 0x4f, 0x46, 0x51, 0x1f, 0x39, 0xe7, 0x28, 0xea, - 0x3f, 0x80, 0xc9, 0x27, 0xfd, 0x7d, 0x99, 0x10, 0x62, 0x74, 0x60, 0x94, 0x6e, 0x3a, 0x07, 0x2f, - 0xfa, 0xfb, 0xe6, 0x94, 0x10, 0x2a, 0x31, 0x63, 0xc4, 0xf1, 0xb1, 0x5f, 0x49, 0xc4, 0xf1, 0xd4, - 0x60, 0xf7, 0xe3, 0xdf, 0x48, 0xb0, 0x7b, 0x43, 0xd4, 0xf0, 0x89, 0x73, 0x8f, 0x1a, 0x5e, 0x05, - 0x18, 0xf7, 0xa3, 0xa0, 0xcf, 0xc3, 0x85, 0x11, 0xeb, 0xdf, 0x8c, 0xc1, 0x1c, 0x39, 0x9d, 0x8b, - 0x15, 0x1e, 0x44, 0xdb, 0xdf, 0x94, 0x28, 0x53, 0x0e, 0x9b, 0x5c, 0x53, 0x65, 0xe5, 0xcd, 0x58, - 0x82, 0x21, 0x0d, 0x01, 0xbd, 0xab, 0xde, 0x6d, 0xe4, 0x95, 0xb0, 0x93, 0xc9, 0xdc, 0x30, 0xea, - 0xa5, 0xc7, 0x5b, 0x9a, 0x69, 0x3d, 0xd3, 0x16, 0xf1, 0x20, 0x6e, 0x6f, 0xe7, 0xf1, 0xaa, 0xe8, - 0xc6, 0xa0, 0x9f, 0xfd, 0x23, 0x43, 0xfc, 0x33, 0x18, 0xa5, 0xc1, 0x65, 0xc4, 0x03, 0xdd, 0x37, - 0xb9, 0x90, 0x30, 0x31, 0x81, 0x85, 0xa1, 0x09, 0x94, 0x40, 0xd1, 0x1d, 0x5a, 0xa0, 0xc6, 0x90, - 0x61, 0x20, 0x68, 0x17, 0x2e, 0xed, 0xf8, 0xb8, 0xcd, 0x7d, 0x4e, 0x7b, 0x3e, 0x3f, 0xca, 0xb1, - 0x97, 0x72, 0x34, 0x04, 0x63, 0x4f, 0x54, 0x37, 0xb1, 0xac, 0x57, 0xa5, 0xac, 0x01, 0x1d, 0xad, - 0xc1, 0x4c, 0x1d, 0x3b, 0x7e, 0xeb, 0xf0, 0x09, 0x7e, 0x4d, 0x36, 0x87, 0xa0, 0x38, 0x16, 0xc5, - 0x2d, 0x0d, 0x68, 0x0d, 0x19, 0x28, 0xad, 0x52, 0xaf, 0xbc, 0x75, 0x24, 0xf4, 0x5d, 0x18, 0xad, - 0x7b, 0x7e, 0x58, 0x7d, 0x1d, 0x4b, 0x16, 0xc4, 0x0a, 0xab, 0x97, 0x45, 0xec, 0xd6, 0xc0, 0xf3, - 0xc3, 0xe6, 0xbe, 0xca, 0x37, 0x8e, 0x87, 0x1e, 0x12, 0xcd, 0x93, 0x68, 0xc3, 0xd2, 0x6c, 0xc2, - 0x02, 0x52, 0x70, 0xed, 0x92, 0xaa, 0xd0, 0x26, 0xdb, 0x49, 0x0c, 0x0b, 0xbd, 0x86, 0x39, 0x7d, - 0xfd, 0xf3, 0x48, 0xdd, 0xa0, 0xa5, 0xdd, 0x30, 0x81, 0x54, 0x6f, 0xf3, 0x5e, 0x5e, 0x8b, 0x7f, - 0x65, 0x89, 0xe0, 0xdd, 0xc6, 0x26, 0xd0, 0x53, 0x1a, 0x3a, 0x97, 0x71, 0xa6, 0x12, 0x88, 0x80, - 0xc8, 0x64, 0x10, 0x34, 0x16, 0x5d, 0x9f, 0x7e, 0x43, 0x94, 0xa3, 0x4e, 0x10, 0x8f, 0x8b, 0x6c, - 0x27, 0x50, 0xd1, 0x0e, 0x5c, 0x7c, 0x16, 0xe0, 0x1d, 0x1f, 0xbf, 0x74, 0xf1, 0x2b, 0x41, 0x6f, - 0x8a, 0xd2, 0xa3, 0xd3, 0x4d, 0xe8, 0xf5, 0x58, 0xad, 0x89, 0x60, 0x12, 0xb9, 0xf4, 0x21, 0x4c, - 0x2a, 0xeb, 0xed, 0x4c, 0x91, 0xc3, 0xff, 0x38, 0xc7, 0x4c, 0x7b, 0xca, 0x02, 0xe6, 0x96, 0x85, - 0x6d, 0x98, 0x90, 0x85, 0xf2, 0x61, 0x81, 0xd0, 0x4e, 0x62, 0xbb, 0x1b, 0xfb, 0x7c, 0xc4, 0xd7, - 0xad, 0xf6, 0x36, 0xa2, 0xf1, 0xcd, 0x9a, 0xdb, 0x7e, 0x33, 0x7a, 0x82, 0xc8, 0x9f, 0x4b, 0xfa, - 0x4e, 0xeb, 0x45, 0x64, 0xef, 0xfc, 0x31, 0xf9, 0x3e, 0xd4, 0x0a, 0x9e, 0xe3, 0x68, 0x51, 0x4f, - 0x50, 0xc3, 0x2b, 0x45, 0x98, 0x7c, 0xf9, 0x12, 0x93, 0x15, 0xeb, 0x1f, 0x8e, 0x8a, 0x40, 0x9d, - 0x6f, 0x67, 0x2d, 0x9b, 0xbd, 0xa0, 0x33, 0xf6, 0xe0, 0xbd, 0xe4, 0x1b, 0x30, 0x9a, 0x8f, 0x20, - 0x7a, 0x03, 0xa6, 0xb2, 0x31, 0x7a, 0x0d, 0xf6, 0x0c, 0x96, 0x6c, 0x7c, 0xe4, 0xbd, 0xc4, 0xe7, - 0x4b, 0xf6, 0x87, 0x70, 0x59, 0x27, 0xf8, 0xac, 0xd7, 0xa6, 0xa1, 0x33, 0xd8, 0xad, 0xa7, 0x31, - 0x94, 0x1d, 0x47, 0x60, 0xa1, 0xec, 0x58, 0x70, 0x23, 0xf2, 0xa7, 0x2a, 0x6f, 0x69, 0x9d, 0xe5, - 0xc1, 0xb2, 0x4e, 0xbc, 0xd2, 0x6e, 0xef, 0x38, 0x7e, 0xe8, 0xb6, 0xdc, 0x9e, 0xd3, 0x0d, 0xd1, - 0x36, 0x4c, 0x2a, 0x3f, 0x63, 0xba, 0x8d, 0x52, 0xc3, 0x66, 0xbf, 0x17, 0x15, 0xa8, 0x3a, 0x98, - 0x02, 0x67, 0x61, 0x28, 0xc7, 0xd9, 0x43, 0x58, 0xa6, 0xb6, 0x59, 0x85, 0x69, 0xe5, 0xa7, 0x3c, - 0x2a, 0xd0, 0x30, 0x95, 0x4a, 0x0b, 0x3a, 0xc3, 0x74, 0x14, 0xab, 0x05, 0x25, 0x13, 0xd3, 0x68, - 0x48, 0x87, 0xd7, 0x68, 0x2d, 0x0a, 0x0e, 0x31, 0xf8, 0xb6, 0x79, 0x36, 0x2d, 0x30, 0x84, 0xf5, - 0xd7, 0x87, 0x61, 0x89, 0x4f, 0xc6, 0x79, 0xce, 0x38, 0xfa, 0x09, 0x4c, 0x2a, 0x73, 0xcc, 0x99, - 0x7e, 0x4d, 0x38, 0xa8, 0xa4, 0xad, 0x05, 0xa6, 0x83, 0xf5, 0x69, 0x41, 0x33, 0x36, 0xdd, 0x44, - 0x07, 0x53, 0x97, 0x4d, 0x07, 0x66, 0xf4, 0x89, 0xe6, 0x6a, 0xe8, 0x0d, 0x63, 0x23, 0x3a, 0xa8, - 0x88, 0x8b, 0xd4, 0x6e, 0x1a, 0xa7, 0x9b, 0xa6, 0x65, 0xd2, 0x17, 0xd1, 0x97, 0x70, 0x31, 0x31, - 0xcb, 0xfc, 0x58, 0x75, 0xcb, 0xd8, 0x60, 0x02, 0x9a, 0xc5, 0xe2, 0xf6, 0x69, 0x71, 0x6a, 0xb3, - 0xc9, 0x46, 0x50, 0x1b, 0xa6, 0xd4, 0x89, 0xe7, 0x7a, 0xf2, 0xf5, 0x0c, 0x56, 0x32, 0x40, 0xa6, - 0x14, 0x71, 0x5e, 0xd2, 0xb9, 0xd7, 0x33, 0x19, 0x6a, 0x54, 0xab, 0xe3, 0x30, 0xca, 0x7e, 0x13, - 0x11, 0xb0, 0xe3, 0xe3, 0x00, 0x77, 0x5b, 0x58, 0xf5, 0x35, 0xfa, 0xba, 0x22, 0xe0, 0xdf, 0xe5, - 0xa0, 0x68, 0xa2, 0x5b, 0xc7, 0xdd, 0x36, 0xda, 0x81, 0x42, 0xbc, 0x21, 0xbe, 0xaa, 0x2d, 0xb1, - 0x2b, 0xa4, 0x77, 0x89, 0xe8, 0xcd, 0x89, 0x6e, 0x6e, 0xc1, 0x45, 0xa5, 0xec, 0x8c, 0x4e, 0x5d, - 0x49, 0x54, 0xf5, 0xe8, 0xbb, 0x4e, 0x7d, 0xd7, 0x56, 0xbd, 0x23, 0xc7, 0xed, 0x12, 0x05, 0x51, - 0x09, 0xe3, 0x00, 0x51, 0x29, 0xe7, 0x0d, 0x3b, 0x1e, 0xd2, 0x52, 0xe1, 0xe0, 0x28, 0x41, 0xac, - 0x4f, 0xa8, 0x04, 0xe7, 0x87, 0x0a, 0xf6, 0x48, 0x46, 0x12, 0xbb, 0x06, 0x23, 0xbb, 0x9b, 0xf5, - 0x5a, 0x85, 0x3f, 0xb9, 0x61, 0x4f, 0x3b, 0x3b, 0x41, 0xb3, 0xe5, 0xd8, 0xac, 0xc2, 0xfa, 0x98, - 0x46, 0xaf, 0xe3, 0xb1, 0xcf, 0x24, 0xde, 0x4d, 0x18, 0xe3, 0x45, 0x1c, 0x93, 0x5e, 0x0d, 0x77, - 0x38, 0x94, 0xa8, 0xb3, 0x76, 0x84, 0x7e, 0xdd, 0xc1, 0x4e, 0xa0, 0x6c, 0xcc, 0x1f, 0x10, 0x55, - 0x9c, 0x95, 0xf1, 0x7d, 0x79, 0x46, 0x86, 0x16, 0xa5, 0xc5, 0xec, 0xb8, 0x2c, 0x60, 0x6c, 0xf9, - 0x97, 0xf5, 0x47, 0x79, 0x98, 0x13, 0x01, 0x5c, 0x34, 0x53, 0xc0, 0xc0, 0x10, 0x92, 0xdf, 0xd7, - 0x63, 0xe4, 0xd4, 0x64, 0x8c, 0x9c, 0xaf, 0x91, 0xf7, 0x81, 0x47, 0xd7, 0x39, 0xe5, 0x83, 0xb4, - 0x27, 0x52, 0xfb, 0x1e, 0xd6, 0xb4, 0x6f, 0xd3, 0x78, 0x34, 0xed, 0x9b, 0x4e, 0x0b, 0xd3, 0xbe, - 0x85, 0xce, 0xfd, 0x75, 0x14, 0xa6, 0x0f, 0xc8, 0xd2, 0xd2, 0x9a, 0x3c, 0xed, 0x5b, 0xa5, 0x4d, - 0xfa, 0x08, 0x7e, 0x7b, 0x63, 0xb5, 0x46, 0xd6, 0x34, 0xef, 0xaa, 0x98, 0x81, 0xbb, 0xd4, 0x6b, - 0x8d, 0xd3, 0x54, 0x17, 0x26, 0x15, 0xb1, 0x3c, 0xf4, 0x83, 0x02, 0x62, 0x3d, 0x90, 0x4f, 0xea, - 0x0d, 0xd4, 0xd2, 0xe2, 0xa1, 0x6e, 0xd1, 0x60, 0x01, 0x8f, 0xe8, 0x7c, 0x9d, 0x47, 0x27, 0x7e, - 0x2f, 0xc7, 0xa2, 0x0f, 0xd4, 0xb7, 0x95, 0x00, 0xed, 0xdd, 0xe7, 0x9e, 0x62, 0x09, 0x55, 0x9a, - 0x51, 0x72, 0xf9, 0x50, 0x4b, 0x28, 0xcd, 0xb9, 0xc7, 0x9f, 0xfc, 0xd1, 0xac, 0x38, 0x76, 0x1c, - 0x1a, 0x7d, 0x08, 0xd3, 0x4a, 0x91, 0xdc, 0xa4, 0x59, 0xe4, 0x3d, 0x15, 0xdd, 0x6d, 0xdb, 0x3a, - 0xa4, 0xf5, 0x5b, 0x79, 0x58, 0xca, 0xc8, 0xfe, 0x41, 0xcf, 0x80, 0xf4, 0x00, 0x2f, 0x39, 0xc5, - 0x63, 0x16, 0xd3, 0xe7, 0x8d, 0x9a, 0x8c, 0x94, 0x80, 0xe8, 0x13, 0x98, 0x54, 0x93, 0x91, 0xe4, - 0x95, 0xc0, 0xd8, 0xe6, 0x04, 0x24, 0x2a, 0x38, 0x0a, 0x00, 0xa2, 0x9e, 0xf0, 0x77, 0xc3, 0x75, - 0xfa, 0x02, 0x35, 0xca, 0x64, 0x72, 0x2e, 0x29, 0x55, 0x94, 0x66, 0xac, 0xbf, 0x9c, 0x87, 0xab, - 0x19, 0x7c, 0xa8, 0xe3, 0xf0, 0xff, 0x05, 0x2b, 0x62, 0xf9, 0x65, 0x86, 0xbe, 0xa1, 0xfc, 0x32, - 0xd6, 0xef, 0xe4, 0x61, 0xe1, 0x59, 0x2f, 0xa0, 0xce, 0xa5, 0x1b, 0xdd, 0x97, 0xb8, 0x1b, 0x7a, - 0xfe, 0x6b, 0xea, 0x1c, 0x87, 0xde, 0x85, 0x91, 0x75, 0xdc, 0xe9, 0x78, 0x7c, 0x5b, 0xbb, 0x22, - 0x8c, 0xd3, 0x71, 0x68, 0x0a, 0xb4, 0x7e, 0xc1, 0x66, 0xd0, 0xe8, 0x43, 0x98, 0x58, 0xc7, 0x8e, - 0x1f, 0xee, 0x63, 0x47, 0x68, 0xae, 0x97, 0x39, 0xaa, 0x82, 0xc2, 0x01, 0xd6, 0x2f, 0xd8, 0x11, - 0x34, 0x5a, 0x81, 0xe1, 0x1d, 0xaf, 0x7b, 0x20, 0x5f, 0x9f, 0xa5, 0x34, 0x48, 0x60, 0xd6, 0x2f, - 0xd8, 0x14, 0x16, 0x3d, 0x85, 0xe9, 0xca, 0x01, 0xee, 0x86, 0x4f, 0x71, 0xe8, 0xb4, 0x9d, 0xd0, - 0xe1, 0x1a, 0xce, 0xcd, 0x34, 0x64, 0x0d, 0x98, 0xe6, 0x79, 0x55, 0x0b, 0xaa, 0x23, 0x30, 0xf4, - 0x34, 0x38, 0xb0, 0x7e, 0x96, 0x83, 0xe2, 0xaa, 0xf7, 0xaa, 0x6b, 0x64, 0xcc, 0xfb, 0x3a, 0x63, - 0x84, 0x0b, 0xb4, 0x01, 0x3e, 0xc6, 0x9a, 0x77, 0x60, 0x78, 0xc7, 0xed, 0x1e, 0xc4, 0x36, 0x75, - 0x03, 0x1e, 0x81, 0xa2, 0x23, 0x74, 0xbb, 0x07, 0xa2, 0x4b, 0x6f, 0xc1, 0x62, 0x0a, 0x24, 0x9a, - 0x91, 0xe2, 0x6d, 0x98, 0x8a, 0xb5, 0x37, 0x61, 0xde, 0xc8, 0xb4, 0x04, 0xe0, 0xbf, 0xce, 0x19, - 0x66, 0x9f, 0xf5, 0xb5, 0x08, 0x63, 0x22, 0xf0, 0x2b, 0xdb, 0x07, 0xc4, 0x4f, 0xea, 0x9c, 0x29, - 0xbe, 0x0e, 0x1e, 0xea, 0x4f, 0x7e, 0x04, 0x0d, 0xe5, 0xf1, 0x3d, 0x5b, 0xc3, 0x1f, 0x7d, 0x8d, - 0x95, 0x2a, 0x69, 0x91, 0x36, 0xd7, 0xbd, 0x20, 0xec, 0x4a, 0xdf, 0x01, 0x5b, 0xfe, 0xb6, 0xfe, - 0x73, 0x9e, 0x86, 0x0d, 0xcc, 0x98, 0x66, 0x32, 0xee, 0xed, 0x3a, 0x1f, 0x47, 0x7e, 0xbb, 0x8e, - 0x96, 0x61, 0x62, 0xbb, 0xae, 0xc5, 0xb5, 0xb5, 0xa3, 0x02, 0xf4, 0x36, 0x4b, 0x1e, 0x56, 0xf1, - 0x5b, 0x87, 0x6e, 0x88, 0x5b, 0x61, 0xdf, 0xe7, 0xc2, 0xc9, 0x4e, 0x94, 0x23, 0x0b, 0xa6, 0x1e, - 0x75, 0xdc, 0xfd, 0x96, 0x20, 0xc6, 0x3a, 0xa7, 0x95, 0xa1, 0x5b, 0x30, 0xc3, 0x93, 0x1a, 0xb2, - 0xb0, 0xbf, 0x3c, 0xfb, 0x96, 0x1d, 0x2b, 0x25, 0xed, 0xd6, 0xbc, 0x6e, 0xe8, 0xb8, 0x5d, 0xec, - 0xdb, 0xfd, 0x6e, 0xe8, 0xf2, 0x14, 0xd7, 0x13, 0x76, 0xa2, 0x1c, 0xbd, 0x03, 0xf3, 0xb2, 0x6c, - 0xdb, 0x6f, 0x1d, 0xe2, 0x20, 0xf4, 0x69, 0x88, 0x74, 0xfa, 0x18, 0xdb, 0x36, 0x57, 0xd2, 0x16, - 0x3a, 0x5e, 0xbf, 0xbd, 0xd6, 0x7d, 0xe9, 0xfa, 0x1e, 0x4b, 0x87, 0x37, 0xce, 0x5b, 0x88, 0x95, - 0x5b, 0x3b, 0xc6, 0x2f, 0xe0, 0x6b, 0x2c, 0x0e, 0xab, 0x06, 0x28, 0x29, 0x01, 0xd0, 0xb7, 0x61, - 0xa2, 0x5e, 0x5f, 0xd7, 0xee, 0x00, 0xe2, 0x66, 0x79, 0x3b, 0x82, 0xb0, 0xde, 0x83, 0x05, 0x49, - 0x84, 0xc5, 0xbc, 0x54, 0x5c, 0xc0, 0x79, 0x82, 0x14, 0xe9, 0x79, 0x1e, 0x15, 0x58, 0x3f, 0x4c, - 0xe0, 0xd5, 0xfb, 0x47, 0x47, 0x8e, 0xff, 0x1a, 0x55, 0x74, 0xbc, 0xa1, 0x81, 0xb2, 0xae, 0x3a, - 0xfc, 0xf3, 0xe3, 0xf2, 0x05, 0x95, 0xb8, 0x0d, 0x73, 0xda, 0x17, 0x29, 0xba, 0x54, 0x8a, 0x6f, - 0x24, 0xca, 0xa7, 0x72, 0x15, 0x80, 0x07, 0xc5, 0xdd, 0xf4, 0x0e, 0xb8, 0x67, 0xb0, 0x52, 0x62, - 0x3d, 0x84, 0xf9, 0x18, 0x4d, 0xae, 0x58, 0x7d, 0x1b, 0xa4, 0x2a, 0x48, 0x89, 0x0e, 0x55, 0x2f, - 0xfe, 0xf2, 0xb8, 0x3c, 0x4d, 0x96, 0xc5, 0x9d, 0x28, 0xb4, 0x95, 0xf8, 0xcb, 0x7a, 0xaa, 0x6a, - 0xec, 0x95, 0x8e, 0xf6, 0xa6, 0xe3, 0x3e, 0x8c, 0xb2, 0x92, 0x58, 0x00, 0x19, 0x15, 0x9a, 0x8f, - 0x96, 0x03, 0x5a, 0xf3, 0xd4, 0x6f, 0x8b, 0xfe, 0xa8, 0x44, 0x1e, 0xc2, 0xd6, 0x33, 0x16, 0xcd, - 0x30, 0x2a, 0x96, 0x41, 0x6a, 0x86, 0x2b, 0x91, 0x27, 0xb3, 0x30, 0x4b, 0x0a, 0xb8, 0xae, 0xf7, - 0xaa, 0x83, 0xdb, 0x07, 0x98, 0xe6, 0x51, 0x9c, 0xe2, 0x66, 0xc9, 0x61, 0x87, 0x10, 0xa0, 0x68, - 0xd6, 0x67, 0x30, 0x5f, 0xeb, 0x60, 0xc7, 0x8f, 0xb7, 0x87, 0x6e, 0xc1, 0x18, 0x2d, 0xd3, 0xaf, - 0xc4, 0x1c, 0x52, 0x44, 0xaf, 0xc4, 0x78, 0x25, 0x51, 0x32, 0x59, 0x5c, 0x0f, 0x75, 0x48, 0x91, - 0x7e, 0x37, 0x42, 0x7f, 0xc7, 0xfc, 0x84, 0x0c, 0xa3, 0x67, 0x70, 0xd6, 0xa7, 0xf4, 0x22, 0xda, - 0x94, 0x86, 0xe7, 0x74, 0x7e, 0x68, 0x7f, 0x0e, 0x96, 0x2b, 0xbd, 0x1e, 0xee, 0xb6, 0x23, 0x44, - 0x72, 0x0c, 0x3e, 0x9d, 0x7f, 0x2f, 0xaa, 0xc0, 0x08, 0x85, 0x96, 0xa6, 0x09, 0xde, 0x5d, 0x43, - 0x77, 0x28, 0x1c, 0xd7, 0xb9, 0x69, 0x03, 0x0c, 0xd3, 0x6a, 0xc3, 0x62, 0xbd, 0xbf, 0x7f, 0xe4, - 0xb2, 0x8c, 0x3b, 0xd4, 0x47, 0x5e, 0xb4, 0xbd, 0x21, 0x02, 0xd0, 0x32, 0x66, 0xdc, 0xbe, 0x23, - 0x64, 0xf3, 0x1d, 0x7a, 0xbb, 0xc7, 0xfd, 0xe6, 0x5f, 0xde, 0xbf, 0x13, 0xa1, 0xd2, 0xed, 0x90, - 0xb5, 0x42, 0xab, 0x79, 0x90, 0x5a, 0xeb, 0x12, 0x5c, 0x54, 0x8f, 0x79, 0x6c, 0x85, 0xcc, 0xc3, - 0x25, 0xfd, 0xf8, 0xc6, 0x8a, 0xbf, 0x80, 0x39, 0x66, 0x97, 0x64, 0x11, 0x81, 0x56, 0xa2, 0xe0, - 0x37, 0xf9, 0xc6, 0x4a, 0xec, 0x4e, 0x90, 0x3a, 0x69, 0xca, 0x58, 0x6f, 0x8d, 0x15, 0xe6, 0x4c, - 0xf4, 0x72, 0x45, 0x33, 0x12, 0xe4, 0x1b, 0x2b, 0xd5, 0x31, 0x7e, 0xf6, 0x20, 0xd4, 0xd9, 0xf4, - 0xff, 0x4a, 0xa8, 0xaf, 0x50, 0x6f, 0xd4, 0x75, 0xec, 0xd0, 0xbb, 0x66, 0xb3, 0x4f, 0xdf, 0x0c, - 0xe4, 0xdd, 0xb6, 0xd8, 0x7a, 0xdc, 0xb6, 0xf5, 0x87, 0x39, 0xb8, 0xcd, 0xcc, 0x16, 0x66, 0x3c, - 0x7a, 0x9a, 0x48, 0x41, 0x46, 0x1f, 0x00, 0x4b, 0x8e, 0xc1, 0xed, 0x8e, 0x16, 0xef, 0x79, 0x16, - 0x25, 0x86, 0x80, 0x2a, 0x30, 0xa5, 0x5e, 0x4a, 0xc7, 0xde, 0x0c, 0xa7, 0xd8, 0x15, 0xec, 0xc9, - 0xa3, 0xe7, 0x8e, 0xbc, 0xa8, 0xfe, 0x18, 0x0a, 0x95, 0x20, 0x70, 0x83, 0xd0, 0x89, 0x16, 0xcd, - 0x9b, 0x30, 0xdb, 0xf2, 0xba, 0x2f, 0xb1, 0x1f, 0x38, 0x5c, 0xc1, 0xe5, 0xbd, 0x9d, 0x51, 0x8b, - 0x37, 0xda, 0xd6, 0x3f, 0xcb, 0x29, 0xd8, 0x4f, 0x71, 0x40, 0x13, 0x3e, 0x9d, 0x16, 0x1b, 0x21, - 0xa0, 0x09, 0x22, 0xf8, 0x8e, 0x42, 0xff, 0x46, 0x8f, 0x60, 0x8a, 0x3b, 0xb7, 0x34, 0xe9, 0x4e, - 0x79, 0x96, 0x47, 0x39, 0x93, 0x1c, 0x93, 0xd4, 0x91, 0xcd, 0xac, 0xe7, 0xbc, 0xee, 0x78, 0x4e, - 0x9b, 0xee, 0xde, 0x53, 0xb6, 0xf8, 0x69, 0xd5, 0x69, 0x40, 0xcf, 0x78, 0xb7, 0x23, 0xe1, 0xf6, - 0x00, 0xc6, 0x8f, 0x78, 0x99, 0x74, 0xa9, 0xe7, 0x8f, 0xb0, 0x63, 0x38, 0xb6, 0x04, 0x7c, 0xfb, - 0x6d, 0x98, 0xd8, 0xee, 0x61, 0x26, 0x9c, 0xd1, 0x38, 0x0c, 0x6f, 0x6c, 0x6d, 0xec, 0xb2, 0x9c, - 0x57, 0x3b, 0xcf, 0x76, 0x0b, 0x39, 0x04, 0x30, 0xba, 0xba, 0xb6, 0xb9, 0xb6, 0xbb, 0x56, 0xc8, - 0xbf, 0xdd, 0x54, 0x2f, 0xf4, 0xd1, 0x12, 0x2c, 0xae, 0xae, 0x35, 0x36, 0x6a, 0x6b, 0xcd, 0xdd, - 0xef, 0xef, 0xac, 0x35, 0xf5, 0x08, 0x2a, 0x73, 0x50, 0x50, 0x2b, 0x77, 0xb7, 0x77, 0x77, 0x0a, - 0x39, 0x54, 0x84, 0x39, 0xb5, 0x74, 0x6f, 0xad, 0x5a, 0x79, 0xb6, 0xbb, 0xbe, 0x55, 0x18, 0xb2, - 0x86, 0xc7, 0xf3, 0x85, 0xfc, 0xdb, 0x3f, 0xd1, 0x6e, 0xfb, 0xd1, 0x32, 0x14, 0x39, 0xf8, 0xb3, - 0x7a, 0xe5, 0x51, 0x7a, 0x13, 0xac, 0xf6, 0xe9, 0xc3, 0x4a, 0x21, 0x87, 0xae, 0xc0, 0x65, 0xad, - 0x74, 0xa7, 0x52, 0xaf, 0xef, 0x6d, 0xdb, 0xab, 0x9b, 0x6b, 0xf5, 0x7a, 0x21, 0xff, 0xf6, 0x2d, - 0xfe, 0x5c, 0x07, 0xcd, 0x00, 0xac, 0xae, 0xd5, 0x6b, 0x6b, 0x5b, 0xab, 0x1b, 0x5b, 0x8f, 0x0a, - 0x17, 0xd0, 0x34, 0x4c, 0x54, 0xe4, 0xcf, 0xdc, 0xca, 0x3f, 0xf8, 0x2b, 0x39, 0x98, 0x24, 0x8b, - 0x50, 0xdc, 0xe3, 0xee, 0xb2, 0x1d, 0x25, 0xce, 0x7c, 0x94, 0x60, 0x31, 0x5f, 0x8b, 0xa5, 0x1b, - 0xd2, 0x02, 0x92, 0x31, 0x65, 0x1b, 0x44, 0x0d, 0xa0, 0x21, 0xa6, 0xe3, 0x8b, 0x31, 0x6d, 0xea, - 0x4a, 0x0b, 0x89, 0x25, 0xb5, 0x46, 0x84, 0x01, 0xfa, 0x42, 0xd1, 0x28, 0xf8, 0xbe, 0xcd, 0x43, - 0x65, 0xa7, 0xaa, 0x0f, 0x54, 0x60, 0x96, 0x32, 0x0e, 0x0c, 0x14, 0xe0, 0x76, 0xee, 0x5e, 0x0e, - 0xd9, 0xd4, 0x54, 0x16, 0x53, 0x59, 0x24, 0x65, 0xb3, 0x0a, 0x54, 0x4a, 0xa9, 0x16, 0x9a, 0xce, - 0x63, 0x98, 0x26, 0x9a, 0x84, 0xac, 0x45, 0x4b, 0x71, 0x78, 0x45, 0x79, 0x29, 0x2d, 0x9b, 0x2b, - 0x65, 0x4c, 0xbb, 0x29, 0xda, 0x3f, 0xc2, 0x2a, 0xa2, 0xa1, 0xcf, 0xab, 0x69, 0xbe, 0xbb, 0x2d, - 0xcc, 0xee, 0x0a, 0x4b, 0x17, 0x63, 0xc5, 0x8d, 0xfb, 0xf7, 0x72, 0xa8, 0x4e, 0xdf, 0x3c, 0x69, - 0x2a, 0x09, 0x12, 0x6e, 0x01, 0x49, 0x5d, 0x85, 0xf5, 0xa6, 0x1c, 0xcd, 0xae, 0x59, 0x97, 0xd9, - 0x02, 0x94, 0xdc, 0xe9, 0xd1, 0xb5, 0x68, 0x2a, 0xcc, 0x4a, 0x40, 0xea, 0xf4, 0xae, 0xc1, 0x0c, - 0xf7, 0xe5, 0xe2, 0xba, 0x07, 0xca, 0xd2, 0x5e, 0x52, 0xc9, 0x3c, 0xa2, 0x7c, 0x92, 0xfa, 0x0b, - 0x2a, 0x29, 0xab, 0x34, 0xa6, 0xd4, 0x94, 0x96, 0x8c, 0x75, 0x7c, 0x7c, 0x0f, 0x61, 0x46, 0x57, - 0x85, 0x90, 0x98, 0x20, 0xa3, 0x86, 0x94, 0xda, 0xa1, 0x26, 0x2c, 0x3e, 0x75, 0x5c, 0x7a, 0x3a, - 0xe0, 0xa6, 0x6e, 0x61, 0xa8, 0x46, 0xe5, 0x0c, 0xcb, 0x75, 0x1d, 0x77, 0xdb, 0xa5, 0x41, 0xaf, - 0x7d, 0xe9, 0xca, 0xad, 0x8b, 0x1d, 0x5d, 0x37, 0xf4, 0x23, 0x4b, 0x0f, 0xe9, 0x6f, 0xba, 0xbb, - 0x29, 0xa5, 0x5d, 0x37, 0xa2, 0xa7, 0x54, 0xa5, 0x88, 0x51, 0x54, 0xd6, 0xc4, 0x99, 0xc9, 0x15, - 0xa9, 0x47, 0x61, 0xe8, 0xc6, 0xef, 0x0d, 0x03, 0x94, 0xc2, 0xb8, 0x54, 0x62, 0xf7, 0x72, 0xe8, - 0x0b, 0xb0, 0xd2, 0xc8, 0xed, 0xb9, 0xe1, 0x21, 0xbf, 0x37, 0x5f, 0x32, 0x12, 0xe0, 0x1f, 0x4a, - 0x06, 0x75, 0x1b, 0xe6, 0x4c, 0x37, 0x9c, 0x92, 0xa1, 0x19, 0xd7, 0x9f, 0xa9, 0xab, 0xc0, 0x26, - 0x8a, 0x51, 0x3b, 0x7d, 0x92, 0x32, 0x2e, 0xd8, 0x52, 0x69, 0x7e, 0x02, 0x33, 0x64, 0x95, 0x3c, - 0xc1, 0xb8, 0x57, 0xe9, 0xb8, 0x2f, 0x71, 0x80, 0xc4, 0x4b, 0x78, 0x59, 0x94, 0x86, 0x7b, 0x3b, - 0x87, 0x7e, 0x0d, 0x26, 0x69, 0x5e, 0x71, 0xfe, 0x70, 0x73, 0x4a, 0xcd, 0x35, 0x5e, 0x12, 0xbf, - 0x68, 0xe5, 0xbd, 0x1c, 0xfa, 0x0e, 0x8c, 0x3d, 0xc2, 0x21, 0x75, 0xcb, 0xba, 0x2e, 0x8d, 0xfd, - 0xec, 0x62, 0x7d, 0xa3, 0x2b, 0x5d, 0x60, 0x44, 0x87, 0xe3, 0xe7, 0x49, 0x74, 0x17, 0x80, 0x09, - 0x04, 0x4a, 0x21, 0x5e, 0x5d, 0x4a, 0x74, 0x1b, 0x3d, 0x22, 0x1b, 0x71, 0x07, 0x87, 0xf8, 0xb4, - 0x4d, 0xa6, 0xf1, 0x68, 0x13, 0x66, 0x64, 0x18, 0xc0, 0x2d, 0xea, 0xd7, 0x6b, 0xc5, 0x88, 0x05, - 0x67, 0xa0, 0xf6, 0x11, 0xf9, 0x2a, 0x98, 0xf1, 0x5d, 0x46, 0x09, 0x40, 0x69, 0x71, 0x03, 0x24, - 0x13, 0x19, 0x98, 0x82, 0x2b, 0x53, 0xa5, 0x4b, 0xdc, 0x78, 0xf2, 0xf4, 0x18, 0x2e, 0x86, 0x92, - 0xda, 0xae, 0x1e, 0x31, 0x20, 0x92, 0xb9, 0x69, 0x81, 0x0e, 0x4a, 0xd7, 0x33, 0x20, 0x98, 0xb8, - 0xa3, 0x92, 0x64, 0x95, 0x9c, 0x35, 0x59, 0x33, 0x6a, 0x66, 0x67, 0x61, 0x4e, 0x4c, 0x66, 0xaa, - 0x2e, 0xa1, 0x64, 0x15, 0xd9, 0xf5, 0xb4, 0x97, 0xea, 0xd1, 0xae, 0x67, 0x08, 0x25, 0x10, 0xed, - 0x7a, 0xc6, 0xc7, 0xed, 0x4f, 0xd8, 0xe9, 0x57, 0xcb, 0xef, 0xda, 0x58, 0x41, 0xc2, 0x47, 0x4f, - 0xab, 0xe0, 0x1f, 0xf6, 0x82, 0xa9, 0xae, 0xf1, 0xe0, 0x5e, 0x0e, 0xad, 0xc1, 0x25, 0xe9, 0x86, - 0x1d, 0x55, 0xa1, 0x14, 0x84, 0xd4, 0x45, 0xf0, 0x19, 0x5c, 0xe2, 0x4b, 0x4a, 0x23, 0x53, 0x90, - 0xd2, 0x81, 0xdf, 0x00, 0xa4, 0x12, 0x78, 0x0c, 0xf3, 0xf5, 0xd8, 0xa0, 0xd8, 0x7d, 0xf5, 0x65, - 0x9d, 0x84, 0x92, 0xe4, 0x2f, 0x95, 0xd6, 0x13, 0x40, 0xec, 0x80, 0x29, 0xc8, 0xbd, 0x74, 0xf1, - 0x2b, 0x74, 0x25, 0x36, 0x24, 0x52, 0x48, 0xc1, 0xa8, 0x78, 0x49, 0x63, 0x11, 0xda, 0x65, 0xc9, - 0x0d, 0x58, 0x02, 0x21, 0xa7, 0xe7, 0xec, 0xbb, 0x1d, 0x37, 0x74, 0x31, 0x59, 0x61, 0x2a, 0x82, - 0x5a, 0x25, 0xa6, 0xf1, 0x72, 0x2a, 0x04, 0xfa, 0x14, 0xa6, 0x1f, 0xe1, 0x30, 0xca, 0x63, 0x88, - 0x16, 0x13, 0x99, 0x0f, 0xf9, 0xd4, 0x89, 0x47, 0x3f, 0x7a, 0xf2, 0xc4, 0x0d, 0x28, 0x30, 0xe9, - 0xa8, 0x90, 0xb8, 0x92, 0x20, 0xc1, 0x41, 0x1c, 0xdf, 0x39, 0x0a, 0x52, 0xb9, 0x75, 0x97, 0x19, - 0x84, 0x91, 0x58, 0xb6, 0xaa, 0xfa, 0x75, 0x49, 0x2b, 0x93, 0x21, 0x57, 0xe6, 0x8d, 0x09, 0xfc, - 0x90, 0xa2, 0xfc, 0xa6, 0x66, 0xe5, 0x2b, 0xa1, 0xf8, 0x93, 0x9c, 0xc6, 0x03, 0x24, 0x63, 0xc1, - 0x1b, 0x88, 0xde, 0xd2, 0x76, 0xec, 0xb3, 0xd1, 0xfd, 0x14, 0x26, 0x64, 0x26, 0x38, 0x29, 0x56, - 0xe2, 0x79, 0xf4, 0x4a, 0xc5, 0x64, 0x05, 0x1f, 0xe9, 0x27, 0x2c, 0xef, 0xa3, 0x8e, 0x1f, 0x4f, - 0x96, 0x96, 0xca, 0xd8, 0x0f, 0x61, 0x52, 0x49, 0x93, 0x26, 0x17, 0x72, 0x32, 0x75, 0x5a, 0x69, - 0x5a, 0xe9, 0x7b, 0x63, 0xe5, 0x5e, 0x0e, 0xdd, 0xa5, 0x5b, 0x0b, 0xf5, 0x49, 0x9f, 0x8f, 0xd0, - 0x94, 0xc4, 0x49, 0x31, 0x14, 0xf4, 0x3e, 0x7d, 0x88, 0x5f, 0xeb, 0xfb, 0x3e, 0xee, 0x32, 0xbc, - 0x34, 0x0d, 0x22, 0x86, 0xf8, 0x29, 0x15, 0x26, 0x0a, 0x22, 0xbb, 0x00, 0x1e, 0x84, 0xcd, 0x42, - 0x32, 0xde, 0xcb, 0x91, 0xe3, 0xa7, 0xc8, 0xaa, 0x8a, 0x16, 0xf4, 0xae, 0xa6, 0x0f, 0xef, 0x01, - 0x00, 0x63, 0x36, 0xed, 0xa9, 0x5e, 0x9d, 0xca, 0xce, 0x07, 0x64, 0xbf, 0x6c, 0x9f, 0x11, 0xe9, - 0x53, 0xb1, 0x67, 0x52, 0xa4, 0xa2, 0x36, 0x85, 0x2a, 0x3b, 0xd3, 0xf0, 0x89, 0xc2, 0xab, 0x25, - 0x7b, 0x8d, 0x14, 0x5e, 0x53, 0x0e, 0xd8, 0x54, 0x3a, 0x1b, 0x50, 0xa8, 0xb4, 0xa8, 0x1c, 0x97, - 0xc9, 0xa8, 0xe4, 0x69, 0x23, 0x5e, 0x21, 0x68, 0xcd, 0xc7, 0x73, 0x5b, 0x6d, 0x62, 0x87, 0xc6, - 0x26, 0x58, 0x94, 0x3a, 0x41, 0xac, 0xca, 0x8c, 0x91, 0x71, 0xba, 0x98, 0xab, 0x91, 0xf3, 0x50, - 0xe7, 0xeb, 0x91, 0xf9, 0x88, 0xca, 0x32, 0x25, 0x51, 0xd7, 0x42, 0x1c, 0x5f, 0x9e, 0xc3, 0x84, - 0xf3, 0x8d, 0x04, 0xad, 0xc0, 0x2c, 0x7f, 0x09, 0x2d, 0xd9, 0x92, 0x86, 0x9d, 0xd6, 0xfc, 0xfb, - 0x30, 0xb3, 0x46, 0x64, 0x7d, 0xbf, 0xed, 0xb2, 0x78, 0x2c, 0x48, 0x0f, 0xb0, 0x91, 0x8a, 0xb8, - 0x2e, 0xf2, 0x44, 0x2a, 0x19, 0xac, 0xe4, 0x57, 0x9a, 0x4c, 0x12, 0x56, 0x9a, 0x13, 0x64, 0xd5, - 0x64, 0x57, 0xfc, 0x9c, 0xbc, 0x98, 0x92, 0x33, 0x0a, 0xdd, 0xd4, 0xce, 0x7e, 0x69, 0x89, 0x9f, - 0x0c, 0xda, 0xde, 0xe7, 0x4a, 0x14, 0xfd, 0x14, 0x9a, 0xd9, 0xc9, 0xa4, 0x52, 0xc7, 0x2d, 0x23, - 0x28, 0x18, 0x93, 0x3e, 0xa1, 0xb7, 0x74, 0xea, 0x19, 0x89, 0xa1, 0x52, 0x5b, 0xa0, 0x67, 0x6b, - 0x3d, 0x27, 0x11, 0xba, 0x9a, 0x9d, 0x3a, 0x49, 0x39, 0x5b, 0xa7, 0x24, 0x33, 0x7a, 0x4c, 0x97, - 0x59, 0x14, 0x6a, 0x1f, 0xa9, 0x27, 0xd5, 0x78, 0xa6, 0x01, 0xa9, 0x42, 0x99, 0x13, 0x13, 0x3d, - 0xa2, 0xe2, 0x52, 0x09, 0xdb, 0x9f, 0x2a, 0xf0, 0xae, 0x98, 0xe8, 0x04, 0xca, 0x5e, 0x38, 0x1b, - 0x4b, 0xf1, 0x23, 0xcd, 0x23, 0xe6, 0x24, 0x43, 0xa5, 0xab, 0x69, 0xd5, 0x9c, 0x62, 0x5d, 0x64, - 0x7a, 0x55, 0x46, 0x7a, 0x55, 0xdb, 0xa1, 0x92, 0x83, 0x2d, 0xa7, 0xd6, 0x4b, 0xde, 0x15, 0xe2, - 0x29, 0x19, 0x24, 0xd1, 0x94, 0x5c, 0x0d, 0x19, 0x22, 0x71, 0x4e, 0x5d, 0x1a, 0x03, 0x39, 0x98, - 0x46, 0x67, 0x17, 0xe6, 0x8d, 0x19, 0x14, 0xa4, 0x1a, 0x91, 0x95, 0x5f, 0x21, 0x95, 0x2a, 0x86, - 0x05, 0x73, 0x12, 0x15, 0xf4, 0x86, 0x7e, 0xf4, 0x37, 0xa7, 0x94, 0x28, 0xdd, 0x1c, 0x00, 0xc5, - 0x19, 0xfa, 0x05, 0xdd, 0x36, 0x13, 0x6d, 0x5c, 0x57, 0x8c, 0x01, 0x29, 0x0d, 0x58, 0x59, 0x20, - 0x72, 0x0d, 0xcc, 0x99, 0x92, 0x38, 0xa5, 0xb2, 0xf8, 0x46, 0x3a, 0xcd, 0x68, 0x61, 0x35, 0x44, - 0xdc, 0x82, 0x54, 0xce, 0x64, 0x26, 0xdb, 0xc8, 0x38, 0x4d, 0x96, 0xe4, 0x7a, 0x38, 0x7d, 0x97, - 0xd3, 0x2d, 0x43, 0x73, 0xa6, 0x14, 0x2f, 0x71, 0xc3, 0x8d, 0x29, 0x83, 0x87, 0x64, 0x43, 0x66, - 0x8e, 0x98, 0x06, 0x33, 0xe2, 0xe8, 0xd4, 0x55, 0x23, 0x8e, 0x91, 0xf4, 0xb5, 0x74, 0x80, 0x68, - 0x45, 0x18, 0x72, 0x55, 0xc9, 0x15, 0x91, 0x9e, 0x35, 0x4b, 0xae, 0x88, 0xac, 0x54, 0x57, 0xb6, - 0xf8, 0xe8, 0x52, 0xd8, 0x92, 0x91, 0xd8, 0x24, 0xe3, 0xc8, 0x55, 0x8c, 0x26, 0x2e, 0xd6, 0xed, - 0xb3, 0x4e, 0xdb, 0x17, 0x70, 0x39, 0x35, 0x89, 0x09, 0x7a, 0x33, 0xf1, 0x41, 0xa7, 0x70, 0x22, - 0xbd, 0xa7, 0xd3, 0x5a, 0xfe, 0x11, 0x69, 0xc5, 0x8a, 0xa5, 0x3a, 0x49, 0x88, 0x7e, 0x43, 0x1e, - 0x14, 0x26, 0xfa, 0x95, 0x5c, 0x26, 0xa7, 0x11, 0xfd, 0xa6, 0xd4, 0x27, 0x52, 0xa6, 0x2a, 0xfd, - 0x12, 0x2a, 0x5d, 0xbc, 0xe2, 0x2c, 0x32, 0xf5, 0x34, 0x5d, 0x4b, 0xa3, 0xb3, 0x4a, 0x8f, 0x1c, - 0x22, 0xb5, 0x09, 0xba, 0xac, 0xb1, 0x49, 0xdb, 0x6e, 0x4b, 0xda, 0xe0, 0xf4, 0x9d, 0xb6, 0x46, - 0xcd, 0xc5, 0x32, 0x95, 0x4a, 0x6a, 0x2f, 0x96, 0x92, 0x34, 0x34, 0x53, 0xb1, 0xe4, 0x02, 0xeb, - 0xcd, 0x72, 0x9c, 0x39, 0x5a, 0x87, 0xd2, 0x87, 0x84, 0x54, 0xd6, 0x0c, 0xe8, 0x52, 0xba, 0xaa, - 0x7b, 0x89, 0x1d, 0x1e, 0x58, 0x60, 0x31, 0xf1, 0x20, 0x71, 0x41, 0xda, 0xbd, 0x94, 0xd2, 0x0c, - 0x33, 0xc7, 0x0e, 0x75, 0x55, 0x32, 0x64, 0x85, 0x91, 0x32, 0x34, 0x33, 0x69, 0x8c, 0x41, 0xcd, - 0x93, 0x52, 0x39, 0x95, 0x62, 0x66, 0x9a, 0x98, 0xd4, 0x9e, 0xfe, 0x58, 0x91, 0xca, 0x89, 0xdc, - 0x2f, 0xe8, 0x76, 0x5c, 0xc7, 0x4b, 0x4b, 0x0f, 0x93, 0x21, 0xf5, 0xe7, 0x4c, 0x69, 0x63, 0x14, - 0xdb, 0x6d, 0x6a, 0x4e, 0x19, 0x03, 0x17, 0xa4, 0x78, 0x4b, 0xa1, 0x96, 0x91, 0x44, 0x26, 0xb5, - 0x87, 0x3f, 0x50, 0xc4, 0x5b, 0x2c, 0xd9, 0x8b, 0x34, 0x2a, 0x0c, 0xc8, 0x06, 0x93, 0x4a, 0x7b, - 0x8b, 0x3a, 0xb7, 0x25, 0x33, 0xb5, 0x48, 0xdd, 0x25, 0x2b, 0x8f, 0x8b, 0xd1, 0xb4, 0x3b, 0x9f, - 0x1c, 0x22, 0xa1, 0xb7, 0x10, 0x33, 0xcc, 0x0e, 0xea, 0x98, 0x94, 0xc3, 0x86, 0x0c, 0x2f, 0x31, - 0x39, 0x9c, 0x9e, 0x03, 0x26, 0xe3, 0xc4, 0x34, 0x5b, 0x77, 0x0f, 0xba, 0x4a, 0xba, 0x15, 0x79, - 0x5e, 0x4a, 0xa6, 0x8c, 0x91, 0x22, 0xc6, 0x94, 0x9d, 0x65, 0x3b, 0x72, 0x7a, 0x57, 0x13, 0x67, - 0xa0, 0x52, 0x7a, 0xbe, 0x10, 0x29, 0x6e, 0x8c, 0x99, 0x36, 0x14, 0x82, 0x6a, 0xd6, 0x0a, 0x49, - 0xd0, 0x90, 0x40, 0x43, 0x12, 0x34, 0xa6, 0xb9, 0x60, 0x26, 0x18, 0xdb, 0xeb, 0x60, 0xd5, 0x04, - 0xa3, 0xe4, 0x9c, 0x88, 0xd9, 0x42, 0xd0, 0xc7, 0xd4, 0x12, 0x92, 0x6d, 0x3e, 0x59, 0xd4, 0x29, - 0xa9, 0xb7, 0xf8, 0xfc, 0x32, 0x80, 0x36, 0xa8, 0x53, 0x1e, 0x6c, 0xdc, 0xa0, 0x48, 0xba, 0x71, - 0x43, 0xed, 0x68, 0xba, 0x9d, 0x74, 0x4a, 0x8d, 0x74, 0x2c, 0x79, 0x65, 0x08, 0xc7, 0x2e, 0x79, - 0x65, 0x0a, 0x72, 0x4e, 0xcf, 0xc0, 0xbb, 0xe2, 0x24, 0x1f, 0xd1, 0xbb, 0x92, 0x19, 0xa5, 0xbc, - 0x74, 0x35, 0x3b, 0xb4, 0x37, 0xbf, 0xc7, 0x2b, 0xc4, 0x83, 0x31, 0x23, 0x53, 0x90, 0x79, 0x25, - 0xc6, 0xb5, 0x3c, 0x0d, 0xa5, 0x46, 0x71, 0xde, 0x11, 0xc6, 0x6a, 0x9d, 0x6e, 0x4a, 0xa8, 0x71, - 0x95, 0x74, 0xb6, 0x82, 0x12, 0xc5, 0x65, 0x56, 0xcf, 0xa6, 0x89, 0xb8, 0xcf, 0xaa, 0x82, 0x62, - 0x08, 0xe5, 0xec, 0x8a, 0x47, 0x92, 0xe6, 0xc4, 0x25, 0x6f, 0xe9, 0x67, 0xbd, 0x8c, 0x18, 0x1f, - 0x03, 0x6f, 0x4a, 0xd1, 0x8f, 0x44, 0x9a, 0xd3, 0x64, 0x58, 0xff, 0x9b, 0x31, 0xb3, 0xab, 0x39, - 0x2a, 0x44, 0x29, 0x2b, 0x6b, 0x00, 0x7a, 0x4a, 0xaf, 0xd8, 0xb7, 0x37, 0x56, 0x6b, 0xdc, 0x3d, - 0xcc, 0xf3, 0x13, 0xd7, 0x56, 0x7b, 0x6e, 0x78, 0xc8, 0xf2, 0x5c, 0x28, 0xd2, 0x87, 0x81, 0x68, - 0x88, 0x8d, 0x07, 0xa8, 0x4e, 0x35, 0x77, 0xad, 0xd4, 0x70, 0x73, 0x65, 0x20, 0x58, 0x32, 0x13, - 0xa4, 0x99, 0xba, 0xa8, 0x62, 0x40, 0x3e, 0x3c, 0xbd, 0x9b, 0x29, 0x7d, 0xc8, 0xd2, 0x2f, 0xd8, - 0xb2, 0x31, 0x93, 0x39, 0xad, 0xf8, 0x7e, 0x04, 0xf3, 0x8c, 0xe1, 0xb1, 0x17, 0x29, 0x5a, 0x7f, - 0x94, 0xf2, 0x52, 0x4a, 0x39, 0xda, 0xa2, 0x9e, 0x1b, 0xf1, 0x52, 0xe5, 0x14, 0x63, 0x7e, 0xf2, - 0x92, 0x4a, 0x8f, 0x4d, 0x25, 0x51, 0xdb, 0xbf, 0xd2, 0x54, 0x6a, 0x88, 0x8d, 0x15, 0x3e, 0x95, - 0x5a, 0xe9, 0xd9, 0xa6, 0x32, 0x46, 0x50, 0x9f, 0x4a, 0xbd, 0x9b, 0x29, 0x7d, 0x18, 0x3c, 0x95, - 0x66, 0x32, 0x67, 0x9e, 0xca, 0xd8, 0x73, 0x20, 0xad, 0x3f, 0xa6, 0xa9, 0x8c, 0xc3, 0xb3, 0xa9, - 0x8c, 0x97, 0xc6, 0x0e, 0xa4, 0x19, 0x53, 0x19, 0xc7, 0xfc, 0x1e, 0xa5, 0xc7, 0xde, 0x1b, 0x9d, - 0x69, 0x32, 0x45, 0xec, 0x8a, 0x18, 0x6a, 0xe3, 0x01, 0xda, 0xa3, 0xd6, 0x90, 0x58, 0xf9, 0xe9, - 0x26, 0x74, 0x39, 0x8d, 0x28, 0x9d, 0xd2, 0x0d, 0xa1, 0x67, 0xc5, 0xbb, 0x9b, 0xda, 0x97, 0xac, - 0xf9, 0x60, 0xd3, 0x1a, 0x27, 0x75, 0xd6, 0x89, 0x7d, 0x2a, 0x84, 0x66, 0xe2, 0xc9, 0x56, 0xac, - 0x57, 0xea, 0xe4, 0xa6, 0xd6, 0x70, 0x17, 0xb3, 0x64, 0xb9, 0x62, 0x27, 0x4a, 0x7b, 0x1b, 0x36, - 0x90, 0x6a, 0xe2, 0x0d, 0x98, 0x4a, 0x35, 0xed, 0x81, 0x98, 0xa4, 0x9a, 0xc4, 0x5e, 0xa5, 0x9f, - 0xed, 0xae, 0x4f, 0x8e, 0x49, 0xed, 0xe4, 0x19, 0x4a, 0xe7, 0x9f, 0xb8, 0xd1, 0xd4, 0xc1, 0x1b, - 0x2b, 0x68, 0x83, 0x2e, 0x40, 0xbd, 0x38, 0xeb, 0x90, 0x69, 0x26, 0x43, 0xd7, 0xc7, 0xba, 0x74, - 0xad, 0xd5, 0xfb, 0x94, 0xd6, 0x76, 0x7a, 0xa7, 0xe4, 0x09, 0xfc, 0x94, 0xa3, 0x4b, 0x5b, 0x1d, - 0x4c, 0x0b, 0x64, 0x07, 0xde, 0x41, 0x9c, 0x89, 0x3b, 0xfb, 0xa2, 0xef, 0xc2, 0x84, 0x40, 0x1e, - 0xcc, 0x90, 0x38, 0x36, 0x65, 0xc8, 0xa7, 0x30, 0xa9, 0xf8, 0x1a, 0xa3, 0xb4, 0x96, 0x32, 0x54, - 0xca, 0x49, 0xc5, 0x13, 0xfa, 0xec, 0xf8, 0xab, 0x30, 0xad, 0x79, 0x52, 0x4b, 0x45, 0xc8, 0xe4, - 0x5f, 0x9d, 0x45, 0x45, 0xf3, 0x98, 0x96, 0x54, 0x4c, 0x7e, 0xd4, 0xd9, 0x4a, 0x99, 0xf2, 0x2a, - 0x54, 0x51, 0xca, 0x92, 0xcf, 0x53, 0x15, 0xa5, 0xcc, 0xf4, 0x90, 0xf4, 0x3b, 0x30, 0xc9, 0x97, - 0x47, 0xe6, 0xcc, 0xa6, 0x9f, 0x96, 0xe7, 0x15, 0x9f, 0xc1, 0x7e, 0xdb, 0x0d, 0x6b, 0x5e, 0xf7, - 0xb9, 0x7b, 0x30, 0x70, 0x92, 0x93, 0x28, 0x8d, 0x15, 0xd4, 0xa0, 0x31, 0xb6, 0x45, 0xe4, 0x73, - 0x1c, 0xbe, 0xf2, 0xfc, 0x17, 0x6e, 0xf7, 0x60, 0x00, 0xc9, 0x6b, 0x3a, 0xc9, 0x38, 0x1e, 0xa3, - 0x5b, 0x4f, 0xa7, 0x3b, 0x10, 0x3f, 0xe3, 0xb4, 0xbc, 0x4c, 0xef, 0xed, 0xcf, 0xda, 0xe3, 0xf4, - 0x9b, 0x83, 0xcb, 0x91, 0xb7, 0x9d, 0x8d, 0x5b, 0x9e, 0xdf, 0x1e, 0x4c, 0xac, 0xac, 0xfb, 0xb6, - 0xc5, 0xd0, 0x1a, 0x2b, 0x84, 0x6a, 0x3d, 0x95, 0xea, 0x20, 0xec, 0x8c, 0xdd, 0x62, 0x89, 0x8e, - 0xfd, 0x8c, 0xbd, 0x4d, 0xff, 0x32, 0x88, 0x04, 0x26, 0x92, 0x7e, 0xc7, 0xc7, 0xcf, 0xb1, 0x4f, - 0x5d, 0x26, 0x07, 0x39, 0x0b, 0xea, 0xe0, 0x8d, 0x15, 0x42, 0xa5, 0x9e, 0xa0, 0x92, 0x06, 0x9d, - 0xa5, 0x28, 0xd1, 0xa1, 0x9d, 0xb2, 0x37, 0x69, 0x64, 0x3e, 0xa0, 0x36, 0xcb, 0x67, 0x1b, 0x03, - 0x38, 0x22, 0x9c, 0x78, 0x05, 0x60, 0xe3, 0x3e, 0xc1, 0xac, 0x2b, 0x98, 0x49, 0x88, 0xd4, 0x36, - 0xbf, 0x2b, 0x8c, 0x93, 0x03, 0x9b, 0x4d, 0xf7, 0x46, 0x98, 0x90, 0xf9, 0x3f, 0x90, 0x72, 0xac, - 0xd7, 0xb2, 0x5b, 0x94, 0xa6, 0x55, 0x97, 0xc1, 0x00, 0x55, 0x98, 0x16, 0xad, 0xe6, 0xc1, 0x50, - 0xee, 0x45, 0x8d, 0x09, 0x32, 0xe2, 0x24, 0x98, 0x59, 0x62, 0xd3, 0x6b, 0xbd, 0x50, 0xcd, 0x12, - 0x4a, 0x62, 0x85, 0x92, 0x9e, 0xf6, 0x80, 0x6f, 0x48, 0x34, 0xf7, 0x81, 0xea, 0xa0, 0xa1, 0xa6, - 0x56, 0x50, 0xcd, 0x12, 0x7a, 0x12, 0x08, 0x69, 0x96, 0xa0, 0x0d, 0xea, 0x94, 0x07, 0x9b, 0x25, - 0x28, 0x92, 0x6e, 0x96, 0x50, 0x3b, 0x9a, 0x2e, 0x2e, 0x50, 0x32, 0x0b, 0x84, 0x54, 0x78, 0x53, - 0x13, 0x44, 0x64, 0xf8, 0x5e, 0x5c, 0x32, 0x24, 0xae, 0x91, 0xc7, 0xfd, 0xf4, 0xa4, 0x36, 0x25, - 0xdd, 0x91, 0xe0, 0x5e, 0x0e, 0x6d, 0xd1, 0x44, 0xe8, 0x5c, 0x80, 0xd9, 0x38, 0x08, 0x7d, 0x97, - 0x3e, 0x7a, 0x4a, 0xdf, 0xad, 0x85, 0x7e, 0x6b, 0xc0, 0x69, 0xbc, 0x43, 0xe8, 0xd5, 0xcd, 0xf4, - 0x32, 0xf1, 0x32, 0x2c, 0x3a, 0xdc, 0xfc, 0x77, 0x96, 0x2e, 0xa6, 0x2f, 0xf1, 0x31, 0x76, 0xeb, - 0x9d, 0x8e, 0x5a, 0x88, 0x42, 0x1d, 0x72, 0x8d, 0xfd, 0x0e, 0x8c, 0x32, 0xa4, 0xd4, 0x3d, 0x72, - 0x4a, 0xc5, 0x41, 0xf7, 0x85, 0x8b, 0x16, 0x41, 0xd1, 0xaa, 0x52, 0xfb, 0x75, 0x1f, 0x26, 0x98, - 0x2d, 0xff, 0xf4, 0x28, 0x1f, 0x0b, 0x47, 0xae, 0xac, 0x8e, 0xa5, 0xfb, 0x36, 0x4e, 0xab, 0x17, - 0xde, 0x67, 0x67, 0xe4, 0x77, 0xe8, 0x7d, 0x8a, 0x30, 0x5b, 0xa6, 0xe3, 0xcf, 0xc7, 0x82, 0x03, - 0x72, 0x96, 0x32, 0x01, 0x29, 0x33, 0x40, 0xa5, 0x75, 0xff, 0x62, 0x02, 0x1b, 0x7d, 0x2c, 0x9e, - 0x0f, 0x48, 0xe4, 0x24, 0x50, 0x06, 0xcf, 0x66, 0x18, 0x9b, 0xbf, 0x0a, 0xb2, 0x14, 0xb0, 0x03, - 0xbb, 0x7d, 0x9a, 0x7b, 0x9f, 0xc1, 0xac, 0x4b, 0xa3, 0xb2, 0x4d, 0x15, 0xaf, 0x44, 0xd8, 0xca, - 0x74, 0x42, 0x57, 0xd3, 0x23, 0x5d, 0xd2, 0xc9, 0x78, 0x4c, 0x0f, 0x56, 0xc9, 0x6c, 0x5e, 0x69, - 0xc3, 0xcb, 0x88, 0x9c, 0x19, 0x9d, 0x24, 0x93, 0xe4, 0x32, 0xd0, 0xb2, 0x0e, 0xa6, 0x6c, 0xc2, - 0xce, 0x87, 0xdc, 0x86, 0x70, 0x40, 0x3a, 0xfd, 0x60, 0x33, 0x94, 0x20, 0xc3, 0x4d, 0xd3, 0xc0, - 0xb9, 0x48, 0x23, 0xf7, 0x23, 0xaa, 0xff, 0x99, 0x53, 0xf8, 0xa4, 0x12, 0xbb, 0xad, 0x5c, 0x56, - 0x66, 0x27, 0xff, 0x79, 0x41, 0xdf, 0x65, 0x98, 0x03, 0x7b, 0xde, 0x1a, 0x40, 0x45, 0x70, 0xe2, - 0xcd, 0x81, 0x70, 0xf2, 0xde, 0x62, 0x89, 0xed, 0xb0, 0xe6, 0xf6, 0x06, 0x04, 0x2a, 0x35, 0x5c, - 0x25, 0xa5, 0xe4, 0xc7, 0x11, 0x04, 0x75, 0xef, 0xae, 0xcc, 0x31, 0xa4, 0xb1, 0xff, 0x7b, 0x50, - 0x8e, 0x6e, 0x64, 0xcf, 0x36, 0x09, 0xe9, 0x1a, 0x3d, 0x4a, 0x66, 0x0d, 0x42, 0x59, 0x01, 0x24, - 0x4b, 0xd7, 0xd3, 0x38, 0x1c, 0x28, 0x57, 0xfd, 0xdc, 0x97, 0x24, 0x16, 0xe2, 0x36, 0x2d, 0x58, - 0x6e, 0x86, 0xed, 0x88, 0x3f, 0x54, 0x39, 0x17, 0x42, 0xc9, 0xd9, 0x3e, 0x3b, 0x21, 0x79, 0x61, - 0x1a, 0x23, 0x64, 0x65, 0x4c, 0xef, 0x59, 0xfc, 0x41, 0xe2, 0x53, 0x71, 0xd6, 0x09, 0x75, 0xa2, - 0xc7, 0x19, 0xc9, 0xd4, 0x46, 0x52, 0x97, 0x4b, 0x4d, 0xb3, 0x24, 0x67, 0x37, 0x23, 0x2f, 0x52, - 0x8d, 0x7c, 0xa6, 0xac, 0x09, 0x2d, 0xaf, 0x4a, 0xcd, 0xde, 0x8c, 0xac, 0x0e, 0x86, 0x84, 0x2b, - 0x25, 0x10, 0x95, 0xf6, 0x26, 0xaa, 0x43, 0x89, 0x2d, 0x11, 0xd3, 0x03, 0x76, 0xe9, 0x51, 0x6f, - 0xaa, 0xcc, 0x38, 0x5d, 0xd4, 0xa1, 0xc4, 0x96, 0xcb, 0x79, 0x12, 0x6d, 0xd2, 0x1c, 0x7a, 0x46, - 0x8a, 0x37, 0x95, 0x67, 0x89, 0xe9, 0x61, 0x01, 0x4a, 0xd9, 0x0d, 0xa3, 0x1f, 0xc2, 0xbc, 0x31, - 0x2e, 0x80, 0xbc, 0xd3, 0xce, 0x8a, 0x1a, 0x30, 0x88, 0xf8, 0x0b, 0x28, 0xa6, 0xa5, 0x4d, 0x89, - 0x3c, 0xfc, 0xb3, 0x33, 0xd3, 0x48, 0x99, 0x3a, 0x30, 0xff, 0xca, 0x16, 0xcc, 0x99, 0x52, 0x91, - 0xc8, 0x8f, 0x23, 0x23, 0x4f, 0x89, 0xf1, 0x19, 0xc1, 0x0e, 0xcc, 0x1b, 0xd3, 0x81, 0x48, 0xce, - 0x64, 0x25, 0x0b, 0x31, 0x52, 0xfc, 0x1c, 0x16, 0x53, 0x72, 0x5f, 0x44, 0x17, 0x6f, 0x99, 0xb9, - 0x31, 0x32, 0x1c, 0x00, 0x4a, 0xe9, 0x69, 0x15, 0xa4, 0xdf, 0xc7, 0xc0, 0xcc, 0x0b, 0x25, 0x63, - 0xae, 0x19, 0xb4, 0x4b, 0x17, 0xa1, 0x29, 0xcf, 0x82, 0xba, 0x08, 0x33, 0xf2, 0x30, 0xa4, 0x3c, - 0xff, 0x58, 0x4c, 0x49, 0xad, 0x90, 0x41, 0xf5, 0x14, 0xbd, 0xdd, 0x12, 0xf2, 0x5f, 0x8f, 0xb5, - 0x1f, 0xf3, 0x25, 0x34, 0x06, 0xe2, 0x37, 0xf6, 0x53, 0x79, 0x6e, 0xdc, 0xe9, 0x64, 0xa8, 0x41, - 0x48, 0x7d, 0x6f, 0x4c, 0x20, 0x1b, 0xf7, 0xc9, 0x21, 0x42, 0xc5, 0xcd, 0x92, 0xa8, 0x09, 0x64, - 0xaa, 0x78, 0x7e, 0x04, 0x53, 0x75, 0xb5, 0x71, 0x43, 0x23, 0xa9, 0x8b, 0x42, 0x7a, 0xd9, 0x0f, - 0xee, 0xfb, 0xc0, 0x5b, 0xb1, 0x4a, 0xa7, 0x73, 0xaa, 0x51, 0xa4, 0xda, 0x64, 0xb5, 0xc8, 0xc6, - 0x52, 0x52, 0x9b, 0x02, 0x76, 0x4b, 0x9b, 0xac, 0x39, 0x18, 0xf2, 0x1a, 0x65, 0x69, 0x14, 0x17, - 0x32, 0xe3, 0x0c, 0x2e, 0x17, 0x91, 0x21, 0xfc, 0xe4, 0x13, 0xf5, 0x21, 0x38, 0x8b, 0x26, 0x99, - 0x61, 0x44, 0x8c, 0x3f, 0x00, 0x8f, 0x85, 0x9f, 0x6c, 0x40, 0x51, 0xc4, 0x75, 0x63, 0x91, 0xd5, - 0xa2, 0x48, 0x52, 0x91, 0x6b, 0x54, 0x7a, 0xe0, 0xb7, 0x0c, 0xbe, 0x15, 0xe2, 0x31, 0x53, 0xa4, - 0xe5, 0x28, 0x25, 0x98, 0x4a, 0xc6, 0x6a, 0x80, 0x28, 0x32, 0x8a, 0xb4, 0xcf, 0x24, 0x82, 0xa5, - 0x94, 0x2e, 0x1b, 0x6a, 0xa4, 0x66, 0x35, 0xa5, 0xc6, 0x51, 0x91, 0x9e, 0x23, 0x86, 0xe0, 0x2a, - 0xa5, 0x25, 0x63, 0x1d, 0x27, 0x14, 0xc2, 0x52, 0x46, 0x0e, 0x41, 0xa9, 0xad, 0x0e, 0xce, 0x75, - 0x58, 0x7a, 0xfb, 0x34, 0xa0, 0xbc, 0x55, 0x2c, 0xe3, 0x34, 0x26, 0xa1, 0xd0, 0x9b, 0x06, 0x57, - 0x5f, 0x53, 0x7a, 0xbe, 0xd2, 0xa0, 0xf4, 0x85, 0x68, 0x0f, 0x96, 0x63, 0xae, 0xc8, 0x7a, 0x4b, - 0x83, 0x08, 0xa4, 0xce, 0xe0, 0x1e, 0x2c, 0xf3, 0xb7, 0xd1, 0xe7, 0x4c, 0x78, 0x1f, 0x96, 0xb3, - 0x12, 0x13, 0xa2, 0xb7, 0xcd, 0xee, 0xc6, 0x46, 0xf6, 0xa4, 0xab, 0xae, 0xd7, 0x92, 0x6e, 0xc7, - 0xb1, 0x79, 0x3f, 0xab, 0x58, 0x79, 0x0a, 0x33, 0x7a, 0x52, 0x42, 0xa4, 0x8a, 0x8e, 0x44, 0x8a, - 0xc4, 0xd2, 0x95, 0x94, 0x5a, 0xbe, 0x3e, 0x3e, 0xa5, 0x82, 0x5e, 0x56, 0xa8, 0xf1, 0x12, 0xe2, - 0x49, 0xff, 0x4a, 0x86, 0x0c, 0x87, 0xe8, 0x3b, 0x30, 0x1b, 0xbd, 0x6f, 0x63, 0x24, 0x0c, 0x60, - 0x19, 0xf6, 0xa2, 0xd9, 0xe8, 0xa5, 0xdb, 0xd9, 0xd1, 0xd7, 0x85, 0xb4, 0x8f, 0xd0, 0xaf, 0x24, - 0x3c, 0xb0, 0xb5, 0x31, 0x9c, 0x46, 0xe8, 0x2b, 0xbc, 0x3d, 0xeb, 0xec, 0xb4, 0xe8, 0xe7, 0x66, - 0x0e, 0x10, 0xa4, 0x7e, 0x6e, 0x99, 0x41, 0x8c, 0xa4, 0x86, 0x99, 0x42, 0xa7, 0x03, 0xd7, 0x07, - 0x86, 0x34, 0x42, 0x77, 0xb5, 0xc8, 0x02, 0x83, 0x83, 0x1f, 0xa5, 0x0d, 0xa9, 0xba, 0xfa, 0xf3, - 0x3f, 0xbd, 0x9a, 0xfb, 0xf9, 0x2f, 0xae, 0xe6, 0xfe, 0xd3, 0x2f, 0xae, 0xe6, 0xfe, 0xfb, 0x2f, - 0xae, 0xe6, 0x7e, 0xb0, 0x72, 0xba, 0xf8, 0x82, 0xad, 0x8e, 0x8b, 0xbb, 0xe1, 0x5d, 0x46, 0x6e, - 0x94, 0xfe, 0xf7, 0xe0, 0xff, 0x06, 0x00, 0x00, 0xff, 0xff, 0x38, 0x14, 0x7f, 0xf2, 0x96, 0xcd, - 0x00, 0x00, + // 13068 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0xbd, 0x6b, 0x6c, 0x1c, 0x49, + 0x92, 0x18, 0xac, 0x6e, 0xbe, 0x83, 0xaf, 0x56, 0x8a, 0x8f, 0x56, 0x93, 0x52, 0x4b, 0xa5, 0x91, + 0x46, 0x33, 0xb7, 0xab, 0x07, 0x35, 0xef, 0x99, 0x9d, 0xd9, 0xee, 0x26, 0x25, 0x52, 0xa2, 0x48, + 0x6e, 0x35, 0xd5, 0x9c, 0xdd, 0x9d, 0xdd, 0xde, 0x62, 0x77, 0x8a, 0xac, 0x4f, 0xcd, 0xae, 0xde, + 0xaa, 0x6a, 0x69, 0x74, 0x1f, 0xfc, 0x38, 0x9f, 0xef, 0x7e, 0xf8, 0xb9, 0x06, 0xee, 0x6c, 0x1f, + 0xee, 0x87, 0x0d, 0xd8, 0x30, 0x60, 0x03, 0x36, 0x0e, 0x86, 0x0f, 0x07, 0x18, 0x06, 0x8c, 0xb3, + 0x0d, 0x78, 0x6d, 0xc0, 0x80, 0x7f, 0xf8, 0x60, 0xc0, 0x06, 0x68, 0xdf, 0xc2, 0xbf, 0x08, 0xf8, + 0x97, 0x61, 0xff, 0xd8, 0x5f, 0x46, 0x3e, 0x2b, 0xb3, 0x2a, 0xab, 0x9a, 0x9c, 0xe1, 0x8e, 0xff, + 0x48, 0xec, 0xcc, 0x88, 0xc8, 0x67, 0x45, 0x46, 0x44, 0x46, 0x46, 0xc0, 0x9d, 0x10, 0x77, 0x70, + 0xcf, 0xf3, 0xc3, 0xbb, 0x1d, 0x7c, 0xe0, 0xb4, 0x5e, 0xdf, 0x6d, 0x75, 0x5c, 0xdc, 0x0d, 0xef, + 0xf6, 0x7c, 0x2f, 0xf4, 0xee, 0x3a, 0xfd, 0xf0, 0x30, 0xc0, 0xfe, 0x4b, 0xb7, 0x85, 0xef, 0xd0, + 0x12, 0x34, 0x42, 0xff, 0x2b, 0xcd, 0x1d, 0x78, 0x07, 0x1e, 0x83, 0x21, 0x7f, 0xb1, 0xca, 0xd2, + 0xd2, 0x81, 0xe7, 0x1d, 0x74, 0x30, 0x43, 0xde, 0xef, 0x3f, 0xbf, 0x8b, 0x8f, 0x7a, 0xe1, 0x6b, + 0x5e, 0x59, 0x8e, 0x57, 0x86, 0xee, 0x11, 0x0e, 0x42, 0xe7, 0xa8, 0xc7, 0x01, 0x6e, 0x67, 0x76, + 0xa5, 0x85, 0xfd, 0x30, 0xe0, 0x90, 0x6f, 0xc5, 0x21, 0xc3, 0xd7, 0x3d, 0x1c, 0xdc, 0xc5, 0x2f, + 0x71, 0x37, 0x14, 0xff, 0x71, 0xd0, 0xeb, 0x66, 0x50, 0xfa, 0x2f, 0x07, 0xf9, 0xb6, 0x19, 0xe4, + 0x15, 0xde, 0x27, 0xc3, 0xef, 0xca, 0x3f, 0x06, 0x80, 0xfb, 0x4e, 0xaf, 0x87, 0xfd, 0xe8, 0x8f, + 0x44, 0x5f, 0xfb, 0x81, 0x73, 0x80, 0x79, 0x1f, 0x5f, 0xde, 0x57, 0x7f, 0x32, 0x50, 0xeb, 0x0f, + 0xae, 0xc0, 0xc8, 0x1a, 0x29, 0x40, 0x1f, 0xc0, 0xf0, 0xee, 0xeb, 0x1e, 0x2e, 0xe6, 0xae, 0xe5, + 0x6e, 0xcf, 0xac, 0x14, 0x58, 0xfd, 0x9d, 0xed, 0x1e, 0xf6, 0x9d, 0xd0, 0xf5, 0xba, 0x55, 0x74, + 0x72, 0x5c, 0x9e, 0x21, 0xed, 0x7e, 0xcb, 0x3b, 0x72, 0x43, 0x3a, 0xcb, 0x36, 0xc5, 0x40, 0x7b, + 0x30, 0x63, 0xe3, 0xc0, 0xeb, 0xfb, 0x2d, 0xbc, 0x8e, 0x9d, 0x36, 0xf6, 0x8b, 0xf9, 0x6b, 0xb9, + 0xdb, 0x93, 0x2b, 0xf3, 0x77, 0xd8, 0x90, 0xf5, 0xca, 0xea, 0xc2, 0xc9, 0x71, 0x19, 0xf9, 0xbc, + 0x2c, 0x22, 0xb6, 0x7e, 0xc1, 0x8e, 0x91, 0x41, 0x5f, 0xc0, 0x74, 0x0d, 0xfb, 0x61, 0xa5, 0x1f, + 0x1e, 0x7a, 0xbe, 0x1b, 0xbe, 0x2e, 0x0e, 0x51, 0xba, 0x0b, 0x9c, 0xae, 0x56, 0xd7, 0x58, 0xa9, + 0x2e, 0x9f, 0x1c, 0x97, 0x8b, 0x64, 0xcd, 0x9a, 0x8e, 0x28, 0xd5, 0xc8, 0xeb, 0xc4, 0xd0, 0xe7, + 0x30, 0x55, 0x0f, 0x9d, 0xd0, 0x6d, 0xed, 0x7a, 0x2f, 0x70, 0x37, 0x28, 0x0e, 0x6b, 0x9d, 0x56, + 0xab, 0x1a, 0x2b, 0xd5, 0xa5, 0x93, 0xe3, 0xf2, 0x62, 0x40, 0xcb, 0x9a, 0x21, 0x2d, 0xd4, 0x48, + 0x6b, 0x94, 0xd0, 0x4f, 0x60, 0x66, 0xc7, 0xf7, 0x5e, 0xba, 0x81, 0xeb, 0x75, 0x69, 0x51, 0x71, + 0x84, 0xd2, 0x5e, 0xe4, 0xb4, 0xf5, 0xca, 0xc6, 0x4a, 0xf5, 0xca, 0xc9, 0x71, 0xf9, 0x72, 0x4f, + 0x94, 0xb2, 0x06, 0xf4, 0x99, 0xd1, 0x51, 0xd0, 0x2e, 0x4c, 0xd6, 0x3a, 0xfd, 0x20, 0xc4, 0xfe, + 0x96, 0x73, 0x84, 0x8b, 0xa3, 0x94, 0xfc, 0x9c, 0x98, 0x97, 0xa8, 0xa6, 0xb1, 0x52, 0x2d, 0x9d, + 0x1c, 0x97, 0x17, 0x5a, 0xac, 0xa8, 0xd9, 0x75, 0x8e, 0xf4, 0x29, 0x57, 0xc9, 0xa0, 0xf7, 0x61, + 0xf8, 0x59, 0x80, 0xfd, 0xe2, 0x38, 0x25, 0x37, 0xcd, 0xc9, 0x91, 0xa2, 0xc6, 0x0a, 0x5b, 0xff, + 0x7e, 0x80, 0x7d, 0x0d, 0x9f, 0x22, 0x10, 0x44, 0xdb, 0xeb, 0xe0, 0xe2, 0x84, 0x86, 0x48, 0x8a, + 0x1a, 0xef, 0x31, 0x44, 0xdf, 0xeb, 0xe8, 0x0d, 0x53, 0x04, 0xb4, 0x01, 0x13, 0xa4, 0xe5, 0xa0, + 0xe7, 0xb4, 0x70, 0x11, 0x28, 0x76, 0x81, 0x63, 0xcb, 0xf2, 0xea, 0xe2, 0xc9, 0x71, 0xf9, 0x52, + 0x57, 0xfc, 0xd4, 0xa8, 0x44, 0xd8, 0xe8, 0x33, 0x18, 0xad, 0x63, 0xff, 0x25, 0xf6, 0x8b, 0x93, + 0x94, 0xce, 0xac, 0x58, 0x48, 0x5a, 0xd8, 0x58, 0xa9, 0xce, 0x9d, 0x1c, 0x97, 0x0b, 0x01, 0xfd, + 0xa5, 0xd1, 0xe0, 0x68, 0x64, 0xb7, 0xd9, 0xf8, 0x25, 0xf6, 0x03, 0xbc, 0xdb, 0xef, 0x76, 0x71, + 0xa7, 0x38, 0xa5, 0xed, 0x36, 0xad, 0x4e, 0xec, 0x36, 0x9f, 0x15, 0x36, 0x43, 0x5a, 0xaa, 0xef, + 0x36, 0x0d, 0x01, 0x1d, 0x42, 0x81, 0xfd, 0x55, 0xf3, 0xba, 0x5d, 0xdc, 0x22, 0x9f, 0x54, 0x71, + 0x9a, 0x36, 0x70, 0x99, 0x37, 0x10, 0xaf, 0x6e, 0xac, 0x54, 0xcb, 0x27, 0xc7, 0xe5, 0x25, 0x46, + 0xbb, 0xd9, 0x92, 0x15, 0x5a, 0x33, 0x09, 0xaa, 0x64, 0x1c, 0x95, 0x56, 0x0b, 0x07, 0x81, 0x8d, + 0x7f, 0xda, 0xc7, 0x41, 0x58, 0x9c, 0xd1, 0xc6, 0xa1, 0xd5, 0x35, 0x1e, 0xb0, 0x71, 0x38, 0xb4, + 0xb0, 0xe9, 0xb3, 0x52, 0x7d, 0x1c, 0x1a, 0x02, 0xda, 0x01, 0xa8, 0xf4, 0x7a, 0x75, 0x1c, 0x90, + 0xcd, 0x58, 0x9c, 0xa5, 0xa4, 0x2f, 0x71, 0xd2, 0x7b, 0x78, 0x9f, 0x57, 0x34, 0x56, 0xaa, 0x97, + 0x4f, 0x8e, 0xcb, 0xf3, 0x4e, 0xaf, 0xd7, 0x0c, 0x58, 0x91, 0x46, 0x54, 0xa1, 0xc1, 0xe6, 0xfd, + 0xc8, 0x0b, 0x31, 0xdf, 0x8a, 0xc5, 0x42, 0x6c, 0xde, 0x95, 0x3a, 0xd1, 0x5f, 0x9f, 0x16, 0x36, + 0xf9, 0xb6, 0x8e, 0xcf, 0xbb, 0x82, 0x40, 0xbe, 0xc5, 0x55, 0x27, 0x74, 0xf6, 0x9d, 0x00, 0xf3, + 0xed, 0x71, 0x51, 0xfb, 0x16, 0xf5, 0xca, 0xc6, 0x03, 0xf6, 0x2d, 0xb6, 0x79, 0x69, 0xd3, 0xb0, + 0x5f, 0x62, 0xf4, 0xc8, 0x8c, 0x44, 0x03, 0x2f, 0xa2, 0x01, 0x33, 0xf2, 0x0a, 0xef, 0x9b, 0x67, + 0x24, 0x02, 0x45, 0xeb, 0x30, 0xbe, 0x87, 0xf7, 0x19, 0xe7, 0xb8, 0x44, 0xe9, 0x5d, 0x8c, 0xe8, + 0x31, 0x9e, 0xf1, 0x80, 0x7d, 0x15, 0x84, 0x5a, 0x92, 0x5b, 0x48, 0x6c, 0xf4, 0x5b, 0x39, 0x58, + 0x14, 0x5f, 0x38, 0x0e, 0x5f, 0x79, 0xfe, 0x0b, 0xb7, 0x7b, 0x50, 0xf3, 0xba, 0xcf, 0xdd, 0x83, + 0xe2, 0x1c, 0xa5, 0x7c, 0x2d, 0xc6, 0x34, 0x62, 0x50, 0x8d, 0x95, 0xea, 0x9b, 0x27, 0xc7, 0xe5, + 0x1b, 0x92, 0x81, 0xc8, 0x7a, 0xb2, 0x21, 0x9f, 0xbb, 0x07, 0x5a, 0xc3, 0x69, 0x6d, 0xa1, 0xdf, + 0xc8, 0xc1, 0x02, 0x1f, 0x9d, 0x8d, 0x5b, 0x9e, 0xdf, 0x8e, 0xba, 0x31, 0x4f, 0xbb, 0x51, 0x96, + 0x5f, 0xab, 0x09, 0xa8, 0xb1, 0x52, 0xbd, 0x75, 0x72, 0x5c, 0xb6, 0xf8, 0xc4, 0x35, 0x7d, 0x51, + 0x6d, 0xea, 0x44, 0x4a, 0x43, 0x64, 0x27, 0x10, 0xe6, 0xbf, 0xe3, 0xe3, 0xe7, 0xd8, 0xc7, 0xdd, + 0x16, 0x2e, 0x2e, 0x68, 0x3b, 0x41, 0xaf, 0x14, 0x5c, 0x99, 0x1c, 0x25, 0xcd, 0x9e, 0x2c, 0xd6, + 0x77, 0x82, 0x8e, 0x82, 0x7e, 0x0a, 0x88, 0x4f, 0x40, 0xa5, 0xdf, 0x76, 0x43, 0x3e, 0xc0, 0x45, + 0xda, 0xca, 0x92, 0x3e, 0xcf, 0x0a, 0x40, 0x63, 0xa5, 0x6a, 0x9d, 0x1c, 0x97, 0xaf, 0x8a, 0x29, + 0x76, 0x48, 0x95, 0x69, 0x60, 0x06, 0xe2, 0x84, 0xf3, 0x6e, 0x7a, 0xad, 0x17, 0xc5, 0xa2, 0xc6, + 0x79, 0x49, 0x91, 0x60, 0xd9, 0x1d, 0xaf, 0xf5, 0x42, 0xe7, 0xbc, 0xa4, 0x16, 0x85, 0x70, 0x89, + 0xaf, 0x92, 0x8d, 0x83, 0xd0, 0x77, 0x29, 0xef, 0x08, 0x8a, 0x97, 0x29, 0x9d, 0x65, 0xc1, 0x83, + 0x93, 0x10, 0x8d, 0x77, 0x58, 0x6f, 0xf9, 0x46, 0x68, 0xfa, 0x4a, 0x9d, 0xd6, 0x8c, 0x89, 0x3c, + 0xfa, 0x33, 0x30, 0xbf, 0xe7, 0x76, 0xdb, 0xde, 0xab, 0x60, 0x15, 0x07, 0x2f, 0x42, 0xaf, 0x57, + 0x67, 0x92, 0x5e, 0xb1, 0x44, 0xdb, 0xbd, 0x2a, 0xb6, 0xb9, 0x09, 0xa6, 0xf1, 0xa0, 0x7a, 0xf3, + 0xe4, 0xb8, 0x7c, 0xfd, 0x15, 0xab, 0x6c, 0xb6, 0x59, 0x6d, 0x93, 0x0b, 0x8b, 0x5a, 0xe3, 0xe6, + 0x56, 0xc8, 0x16, 0xd0, 0x2b, 0x8a, 0x4b, 0xda, 0x16, 0xd0, 0x2b, 0x05, 0x33, 0x88, 0x35, 0xa8, + 0x6f, 0x01, 0x1d, 0x05, 0x3d, 0x82, 0x71, 0xc1, 0x1e, 0x8a, 0xcb, 0xda, 0xa7, 0x2b, 0x8a, 0x1b, + 0x0f, 0x98, 0x04, 0x24, 0x58, 0x8c, 0xfe, 0xe5, 0x0a, 0x28, 0xb4, 0x09, 0x13, 0x94, 0x47, 0x52, + 0x96, 0x75, 0x85, 0x52, 0x42, 0x62, 0xa3, 0x8a, 0xf2, 0xc6, 0x83, 0x6a, 0xf1, 0xe4, 0xb8, 0x3c, + 0xc7, 0xb8, 0x6c, 0x82, 0x51, 0x45, 0x04, 0xd0, 0x03, 0x18, 0xaa, 0xf4, 0x7a, 0xc5, 0xab, 0x94, + 0xce, 0x54, 0x44, 0xa7, 0xf1, 0xa0, 0x7a, 0xf1, 0xe4, 0xb8, 0x3c, 0xed, 0xf4, 0xf4, 0x61, 0x11, + 0x68, 0xb4, 0x0f, 0x85, 0x7a, 0xd7, 0x7b, 0xf5, 0xbc, 0xe3, 0xbc, 0xc0, 0x82, 0xbd, 0x95, 0xd3, + 0xd9, 0x1b, 0x3d, 0xac, 0x02, 0x81, 0x60, 0x64, 0x72, 0x09, 0x7a, 0xe4, 0x58, 0x7c, 0xd2, 0xdf, + 0xc7, 0x7e, 0x17, 0x87, 0x38, 0xe0, 0xa3, 0xbd, 0xa6, 0x1d, 0x8b, 0xf1, 0xea, 0xc6, 0x03, 0xd6, + 0xd2, 0x0b, 0x59, 0x6e, 0x1a, 0x7b, 0x82, 0x2a, 0xea, 0xc0, 0xc5, 0xa8, 0x4c, 0x1c, 0x35, 0xd7, + 0x69, 0x53, 0xa5, 0x44, 0x53, 0xd1, 0x71, 0x73, 0xed, 0xe4, 0xb8, 0xbc, 0xac, 0xb4, 0x65, 0x3a, + 0x72, 0x92, 0x84, 0xd1, 0x13, 0x98, 0xd8, 0xe8, 0x06, 0xa1, 0xd3, 0xe9, 0x60, 0xbf, 0x68, 0x69, + 0xcb, 0x27, 0xcb, 0x1b, 0xf7, 0x19, 0x13, 0x77, 0x45, 0x81, 0xbe, 0x7a, 0x12, 0x0e, 0xb5, 0x61, + 0x56, 0x3d, 0x73, 0xc8, 0xf7, 0x72, 0x83, 0x92, 0x2c, 0x1a, 0x0e, 0x31, 0xf2, 0xa5, 0xdc, 0xaf, + 0x5e, 0x3d, 0x39, 0x2e, 0x97, 0xb4, 0x53, 0x2c, 0xfe, 0x89, 0xc4, 0x49, 0xa2, 0xbf, 0x40, 0x78, + 0x74, 0xe5, 0xe9, 0xe6, 0x46, 0x7b, 0x87, 0x17, 0x51, 0xa1, 0x93, 0xc8, 0xf3, 0x6f, 0xe8, 0x3c, + 0xda, 0x08, 0xd4, 0xb8, 0xcf, 0x4e, 0x8a, 0xc0, 0x39, 0xea, 0x34, 0xdd, 0xb6, 0xfc, 0x2e, 0x9b, + 0x3d, 0x0e, 0x10, 0x63, 0xd2, 0x46, 0x22, 0xe8, 0x47, 0x30, 0x23, 0x6b, 0xd8, 0x8e, 0xbb, 0x99, + 0xbe, 0xe3, 0xe8, 0x20, 0x95, 0xf6, 0x92, 0x1b, 0x2e, 0x46, 0x8c, 0x7c, 0x55, 0x44, 0x60, 0x7d, + 0xe4, 0x7b, 0xfd, 0x5e, 0xf1, 0x96, 0xb6, 0x2c, 0xb2, 0xbc, 0x71, 0x9f, 0x7d, 0x55, 0x44, 0xd6, + 0x6d, 0x1e, 0x90, 0x12, 0x7d, 0x5d, 0x24, 0x20, 0x39, 0xa7, 0x9f, 0x6d, 0x70, 0x2e, 0xff, 0xa6, + 0xf6, 0xb1, 0x8b, 0x62, 0xb1, 0xc4, 0x7d, 0xd7, 0xc4, 0xd0, 0x25, 0x36, 0x72, 0x60, 0x66, 0xfb, + 0x45, 0xe8, 0x6c, 0x1c, 0x11, 0xad, 0xcd, 0xee, 0x77, 0x70, 0xf1, 0xb6, 0xc6, 0x98, 0xf4, 0x4a, + 0xb1, 0xbe, 0xde, 0x8b, 0xd0, 0x69, 0xba, 0xb4, 0xb8, 0xe9, 0xf7, 0x63, 0x02, 0x76, 0x8c, 0x20, + 0xe1, 0x7d, 0xa4, 0xa4, 0x12, 0x04, 0xee, 0x41, 0xf7, 0x08, 0x77, 0xc3, 0xe2, 0x5b, 0x89, 0x26, + 0xa2, 0xca, 0xc6, 0x7d, 0xc6, 0xfb, 0x68, 0x13, 0x8e, 0x2c, 0x4e, 0xb6, 0x10, 0xa1, 0xa0, 0x3a, + 0x4c, 0x6e, 0x74, 0x43, 0x7c, 0xc0, 0x14, 0xc6, 0xe2, 0xdb, 0x9a, 0x52, 0xa2, 0xd4, 0x34, 0xee, + 0x33, 0x51, 0xc8, 0x8d, 0x8a, 0x74, 0x9d, 0x44, 0x81, 0xad, 0x02, 0x8c, 0x0b, 0xad, 0xf0, 0xf1, + 0xf0, 0xf8, 0x58, 0x61, 0xdc, 0x5a, 0x87, 0x91, 0x3d, 0x27, 0x6c, 0x1d, 0xa2, 0xcf, 0x60, 0xe4, + 0x89, 0xdb, 0x6d, 0x07, 0xc5, 0xdc, 0xb5, 0x21, 0xaa, 0x38, 0x30, 0x95, 0x95, 0x56, 0x92, 0x8a, + 0xea, 0xe2, 0xcf, 0x8f, 0xcb, 0x17, 0x4e, 0x8e, 0xcb, 0xb3, 0x2f, 0x08, 0x98, 0xa2, 0xb7, 0x32, + 0x3c, 0xeb, 0x9f, 0xe5, 0x61, 0x42, 0x42, 0xa3, 0x65, 0x18, 0x26, 0xff, 0x53, 0x05, 0x78, 0xa2, + 0x3a, 0x7e, 0x72, 0x5c, 0x1e, 0x26, 0x78, 0x36, 0x2d, 0x45, 0x2b, 0x30, 0xb9, 0xe9, 0x39, 0xed, + 0x3a, 0x6e, 0xf9, 0x38, 0x0c, 0xa8, 0x86, 0x3b, 0x5e, 0x2d, 0x9c, 0x1c, 0x97, 0xa7, 0x3a, 0x9e, + 0xd3, 0x6e, 0x06, 0xac, 0xdc, 0x56, 0x81, 0x08, 0x45, 0xaa, 0x9e, 0x0d, 0x45, 0x14, 0x89, 0x1a, + 0x63, 0xd3, 0x52, 0xf4, 0x18, 0x46, 0x1f, 0xba, 0x1d, 0xc2, 0x85, 0x86, 0x69, 0xff, 0x97, 0xe3, + 0xfd, 0xbf, 0xc3, 0xaa, 0xd7, 0xba, 0xa1, 0xff, 0x9a, 0x69, 0x2f, 0xcf, 0x69, 0x81, 0x32, 0x10, + 0x4e, 0x01, 0xdd, 0x83, 0xb1, 0x7a, 0x7f, 0x9f, 0x76, 0x7f, 0x84, 0x36, 0x46, 0x8f, 0x98, 0xa0, + 0xbf, 0xdf, 0x24, 0x43, 0x50, 0x10, 0x04, 0x58, 0xe9, 0x43, 0x98, 0x54, 0xc8, 0xa3, 0x02, 0x0c, + 0xbd, 0xc0, 0xaf, 0xd9, 0xd8, 0x6d, 0xf2, 0x27, 0x9a, 0x83, 0x91, 0x97, 0x4e, 0xa7, 0x8f, 0xe9, + 0x50, 0x27, 0x6c, 0xf6, 0xe3, 0xa3, 0xfc, 0x07, 0x39, 0xeb, 0x0f, 0x47, 0xa1, 0xb0, 0xee, 0x05, + 0x21, 0x51, 0xa7, 0xa5, 0x5e, 0x70, 0x03, 0x46, 0x49, 0xd9, 0xc6, 0x2a, 0x9f, 0xbf, 0xc9, 0x93, + 0xe3, 0xf2, 0xd8, 0xa1, 0x17, 0x84, 0x4d, 0xb7, 0x6d, 0xf3, 0x2a, 0xf4, 0x16, 0x8c, 0x6f, 0x79, + 0x6d, 0x4c, 0x27, 0x85, 0x92, 0xad, 0x4e, 0x9f, 0x1c, 0x97, 0x27, 0xba, 0x5e, 0x1b, 0x53, 0xd5, + 0xd4, 0x96, 0xd5, 0xa8, 0xc1, 0x55, 0x4a, 0x36, 0x77, 0x55, 0x32, 0x77, 0x44, 0x87, 0xfc, 0xe5, + 0x71, 0xf9, 0xbd, 0x03, 0x37, 0x3c, 0xec, 0xef, 0xdf, 0x69, 0x79, 0x47, 0x77, 0x0f, 0x7c, 0xe7, + 0xa5, 0x1b, 0xd2, 0x9d, 0xe2, 0x74, 0xee, 0x4a, 0xb3, 0x87, 0xd3, 0x73, 0xb9, 0xb9, 0xa5, 0xfe, + 0x3a, 0x08, 0xf1, 0x11, 0xa1, 0xc4, 0x35, 0xce, 0x3d, 0x98, 0xab, 0xb4, 0xdb, 0x2e, 0xc3, 0xd8, + 0xf1, 0xdd, 0x6e, 0xcb, 0xed, 0x39, 0x9d, 0x80, 0xae, 0xc1, 0x44, 0xf5, 0xc6, 0xc9, 0x71, 0xb9, + 0xec, 0xc8, 0xfa, 0x66, 0x4f, 0x02, 0x28, 0x73, 0x68, 0x24, 0x80, 0x1e, 0xc0, 0xf8, 0xea, 0x56, + 0x9d, 0xea, 0xa3, 0xc5, 0x11, 0x4a, 0x8c, 0x7e, 0xf9, 0xed, 0x6e, 0x40, 0x87, 0xa6, 0x12, 0x90, + 0x80, 0xe8, 0x3d, 0x98, 0xda, 0xe9, 0xef, 0x77, 0xdc, 0xd6, 0xee, 0x66, 0xfd, 0x09, 0x7e, 0x4d, + 0x15, 0xf9, 0x29, 0x26, 0xb7, 0xf5, 0x68, 0x79, 0x33, 0xec, 0x04, 0xcd, 0x17, 0xf8, 0xb5, 0xad, + 0xc1, 0x45, 0x78, 0xf5, 0xfa, 0x3a, 0xc1, 0x1b, 0x4b, 0xe0, 0x05, 0xc1, 0xa1, 0x8a, 0xc7, 0xe0, + 0xd0, 0x5d, 0x00, 0xa6, 0x1e, 0x55, 0xda, 0x6d, 0xa6, 0xe7, 0x4f, 0x54, 0x67, 0x4f, 0x8e, 0xcb, + 0x93, 0x5c, 0xa1, 0x72, 0xda, 0x6d, 0xdf, 0x56, 0x40, 0x50, 0x0d, 0xc6, 0x6d, 0x8f, 0x4d, 0x30, + 0xd7, 0xee, 0x67, 0xa5, 0x76, 0xcf, 0x8a, 0xb9, 0x3d, 0x87, 0xff, 0x52, 0x47, 0x29, 0x20, 0x50, + 0x19, 0xc6, 0xb6, 0xbc, 0x9a, 0xd3, 0x3a, 0x64, 0x3a, 0xfe, 0x78, 0x75, 0xe4, 0xe4, 0xb8, 0x9c, + 0xfb, 0xb6, 0x2d, 0x4a, 0xd1, 0x4b, 0x98, 0x8c, 0x16, 0x2a, 0x28, 0x4e, 0xd2, 0xe9, 0xdb, 0x3d, + 0x39, 0x2e, 0x2f, 0x04, 0xb4, 0xb8, 0x49, 0x96, 0x5e, 0x99, 0xc1, 0xaf, 0xb1, 0x0b, 0xd4, 0x86, + 0x50, 0x07, 0xae, 0x3c, 0x23, 0xa7, 0xec, 0x7e, 0x07, 0x47, 0xc5, 0x95, 0x20, 0xc0, 0x3e, 0xa1, + 0xb5, 0xb1, 0x4a, 0x4d, 0x00, 0x13, 0x5c, 0xf7, 0x88, 0x7a, 0x42, 0x18, 0x22, 0x03, 0x69, 0xba, + 0xea, 0xc7, 0x95, 0x4d, 0xcc, 0xfa, 0xe7, 0x39, 0x40, 0xdb, 0x3d, 0xdc, 0xad, 0xd7, 0xd7, 0xc9, + 0xa7, 0x23, 0xbe, 0x9c, 0xdb, 0x30, 0x4e, 0x8e, 0x14, 0xb2, 0x49, 0xf8, 0xb7, 0x33, 0x75, 0x72, + 0x5c, 0x1e, 0xef, 0xf3, 0x32, 0x5b, 0xd6, 0xa2, 0x6f, 0xc1, 0x04, 0x5b, 0x4d, 0xb2, 0xe4, 0x79, + 0xba, 0xe4, 0x33, 0x27, 0xc7, 0x65, 0xe0, 0x4b, 0x4e, 0x96, 0x3b, 0x02, 0x40, 0x37, 0x61, 0x68, + 0x77, 0x77, 0x93, 0x7e, 0x40, 0x43, 0xd5, 0x4b, 0x27, 0xc7, 0xe5, 0xa1, 0x30, 0xec, 0xfc, 0xf2, + 0xb8, 0x3c, 0xbe, 0xda, 0x67, 0xbc, 0xd5, 0x26, 0xf5, 0xe8, 0x26, 0x8c, 0x09, 0x69, 0x68, 0x38, + 0xfa, 0x72, 0xb9, 0x98, 0x63, 0x8b, 0x3a, 0xeb, 0xd7, 0x60, 0x52, 0xe9, 0x3b, 0x61, 0x6d, 0xe4, + 0x7f, 0xda, 0xe1, 0x29, 0xc6, 0xda, 0x5a, 0x64, 0x4c, 0xb4, 0xd4, 0xfa, 0xfd, 0x29, 0x28, 0x90, + 0x5e, 0x6b, 0x1c, 0x42, 0xeb, 0x7d, 0x6e, 0x50, 0xef, 0xd5, 0x59, 0xc9, 0x67, 0xce, 0x4a, 0x1d, + 0xc6, 0xd6, 0xbe, 0xec, 0xb9, 0x3e, 0x0e, 0xb8, 0x7d, 0xb0, 0x74, 0x87, 0x99, 0x7d, 0xef, 0x08, + 0xb3, 0xef, 0x9d, 0x5d, 0x61, 0xf6, 0xad, 0x5e, 0xe1, 0x47, 0xc2, 0x45, 0xcc, 0x50, 0xa2, 0xd5, + 0xfb, 0xd9, 0x7f, 0x2b, 0xe7, 0x6c, 0x41, 0x09, 0x7d, 0x0b, 0x46, 0x1f, 0x7a, 0xfe, 0x91, 0x13, + 0xf2, 0x49, 0x61, 0xec, 0x97, 0x96, 0x68, 0xec, 0x97, 0x96, 0xa0, 0x87, 0x30, 0x63, 0x7b, 0xfd, + 0x10, 0xef, 0x7a, 0x62, 0x2a, 0x19, 0x17, 0xa6, 0xa7, 0xb4, 0x4f, 0x6a, 0x9a, 0xa1, 0x97, 0x14, + 0x1d, 0xed, 0x18, 0x16, 0x5a, 0x83, 0x19, 0xcd, 0xda, 0x12, 0x14, 0x47, 0xe9, 0xa7, 0xc0, 0x34, + 0x51, 0xcd, 0x46, 0xa3, 0xf2, 0x93, 0x18, 0x12, 0xda, 0x32, 0x89, 0xba, 0x63, 0xb4, 0x47, 0x03, + 0xc5, 0x59, 0x93, 0x30, 0x8b, 0x61, 0x96, 0x77, 0x54, 0xea, 0x36, 0xe3, 0xdc, 0x46, 0xc3, 0x8e, + 0xac, 0x58, 0x6d, 0xf5, 0x06, 0x9f, 0xe5, 0x25, 0x39, 0xf6, 0xa4, 0xb6, 0x63, 0xc7, 0x69, 0x12, + 0x0e, 0x2a, 0x4f, 0x87, 0x09, 0xda, 0x5b, 0x66, 0xf9, 0x13, 0xa7, 0x83, 0xca, 0x5b, 0xe4, 0x39, + 0xb1, 0x09, 0x23, 0xcf, 0x02, 0xe7, 0x80, 0x71, 0x96, 0x99, 0x95, 0xeb, 0xbc, 0x47, 0xf1, 0xdd, + 0x47, 0x8d, 0xc5, 0x14, 0x90, 0x7e, 0x0a, 0xb3, 0xd4, 0x12, 0xae, 0x4a, 0x04, 0xb4, 0x0e, 0x7d, + 0x0f, 0x80, 0xf7, 0x8a, 0xa8, 0x4b, 0x93, 0x5c, 0xa6, 0xd3, 0x06, 0x59, 0xe9, 0xf5, 0xaa, 0x57, + 0xf9, 0xf8, 0x16, 0xe4, 0xf8, 0x34, 0x05, 0xca, 0x56, 0x88, 0xa0, 0xcf, 0x60, 0x8a, 0x32, 0x1e, + 0xb1, 0xa2, 0x53, 0x74, 0x45, 0xa9, 0x3d, 0x99, 0xf2, 0x12, 0xc3, 0x7a, 0x6a, 0x08, 0xe8, 0xcf, + 0xc2, 0x3c, 0x27, 0x17, 0xd3, 0x5d, 0xa7, 0xb9, 0xae, 0xae, 0x75, 0x4f, 0x87, 0xa9, 0xbe, 0xcd, + 0x7b, 0x6a, 0xc9, 0x9e, 0xa6, 0x6a, 0xb3, 0xb6, 0xb9, 0x19, 0xb4, 0x01, 0xb3, 0xcf, 0x02, 0xac, + 0x8d, 0x61, 0x86, 0x72, 0x71, 0xaa, 0x86, 0xf5, 0x03, 0xdc, 0x4c, 0x1b, 0x47, 0x1c, 0x0f, 0xd9, + 0x80, 0x56, 0x7d, 0xaf, 0x17, 0xdb, 0xe3, 0xb3, 0x74, 0x46, 0xa8, 0x55, 0xa1, 0xed, 0x7b, 0xbd, + 0x66, 0xfa, 0x46, 0x37, 0x60, 0xa3, 0x1f, 0xc3, 0x42, 0x64, 0xfc, 0x5c, 0x75, 0x9d, 0x83, 0xae, + 0x17, 0x84, 0x6e, 0x6b, 0x63, 0x95, 0xda, 0x11, 0x39, 0xf3, 0x8e, 0x8c, 0xa7, 0xcd, 0xb6, 0x04, + 0xd1, 0x99, 0x77, 0x0a, 0x15, 0xf4, 0x43, 0x98, 0xe6, 0x6d, 0x71, 0x63, 0xfb, 0xc5, 0xec, 0x8d, + 0x26, 0x81, 0xb9, 0xe1, 0x5b, 0xfc, 0x64, 0x02, 0x8e, 0x4e, 0x0b, 0x7d, 0x01, 0x93, 0x4f, 0x1f, + 0x56, 0x6c, 0x1c, 0xf4, 0xbc, 0x6e, 0x80, 0xb9, 0xf1, 0xf0, 0x2a, 0x27, 0xfd, 0xf4, 0x61, 0xa5, + 0xd2, 0x0f, 0x0f, 0x71, 0x37, 0x74, 0x5b, 0x4e, 0x88, 0x05, 0x14, 0xb3, 0xe8, 0x1f, 0x3d, 0x77, + 0x9a, 0x3e, 0x2f, 0x51, 0x46, 0xa1, 0x92, 0xb3, 0x3e, 0x87, 0x09, 0xb9, 0xed, 0xd1, 0x18, 0x0c, + 0x55, 0x3a, 0x9d, 0xc2, 0x05, 0xf2, 0x47, 0xbd, 0xbe, 0x5e, 0xc8, 0xa1, 0x19, 0x80, 0xe8, 0x5b, + 0x2f, 0xe4, 0xd1, 0x54, 0x64, 0xbb, 0x28, 0x0c, 0x51, 0xf8, 0x5e, 0xaf, 0x30, 0x8c, 0x50, 0xdc, + 0x68, 0x52, 0x18, 0xb1, 0x3e, 0x86, 0x09, 0x39, 0x10, 0x34, 0x0b, 0x93, 0xcf, 0xb6, 0xea, 0x3b, + 0x6b, 0xb5, 0x8d, 0x87, 0x1b, 0x6b, 0xab, 0x85, 0x0b, 0xe8, 0x0a, 0x5c, 0xde, 0xad, 0xaf, 0x37, + 0x57, 0xab, 0xcd, 0xcd, 0xed, 0x5a, 0x65, 0xb3, 0xb9, 0x63, 0x6f, 0x7f, 0xfe, 0xfd, 0xe6, 0xee, + 0xb3, 0xad, 0xad, 0xb5, 0xcd, 0x42, 0xce, 0xfa, 0xcf, 0xb9, 0x04, 0x3f, 0x21, 0xe2, 0x35, 0x57, + 0x05, 0xb7, 0xa2, 0x73, 0x90, 0x8a, 0xd7, 0x42, 0x8d, 0xa4, 0xd3, 0xa7, 0x02, 0x91, 0x23, 0x62, + 0x87, 0x4c, 0x54, 0xcb, 0xeb, 0xa8, 0x47, 0x44, 0x8f, 0x97, 0xd9, 0xb2, 0x16, 0xad, 0x28, 0x87, + 0xc9, 0x50, 0x24, 0x1f, 0x8b, 0xc3, 0x44, 0x65, 0x2c, 0xf2, 0x58, 0x59, 0x51, 0x2c, 0x39, 0xc3, + 0x11, 0x8e, 0x81, 0x91, 0x49, 0x38, 0xab, 0x9f, 0xf2, 0xa9, 0xa2, 0x8f, 0x13, 0x86, 0x27, 0x36, + 0x42, 0xca, 0x8b, 0x62, 0x5f, 0x64, 0xc2, 0xa6, 0x54, 0x86, 0x91, 0x4d, 0xef, 0xc0, 0xed, 0xf2, + 0x41, 0x4e, 0x9c, 0x1c, 0x97, 0x47, 0x3a, 0xa4, 0xc0, 0x66, 0xe5, 0xd6, 0x5f, 0x1b, 0x52, 0xd9, + 0x96, 0x54, 0x3b, 0x72, 0x46, 0xb5, 0xe3, 0x5b, 0x30, 0xc1, 0xb5, 0xe1, 0x8d, 0x55, 0x4e, 0x91, + 0x1e, 0xc3, 0xc2, 0xb6, 0xea, 0xb6, 0xed, 0x08, 0x80, 0x08, 0x8c, 0xec, 0x4c, 0xa6, 0x02, 0xe3, + 0x50, 0x24, 0x30, 0xf2, 0x53, 0x9b, 0x09, 0x8c, 0x11, 0x08, 0x59, 0x48, 0xf5, 0x66, 0x6a, 0x38, + 0x5a, 0x48, 0xf5, 0x0e, 0x4a, 0xbf, 0x77, 0xfa, 0x08, 0xa0, 0xb2, 0x57, 0xa7, 0xe2, 0x92, 0xbd, + 0xc5, 0x8f, 0x4e, 0xba, 0xc9, 0x9d, 0x57, 0x01, 0x17, 0xb8, 0x7c, 0x55, 0xb2, 0x54, 0xa0, 0x51, + 0x15, 0xa6, 0x2b, 0xbf, 0xde, 0xf7, 0xf1, 0x46, 0x9b, 0x7c, 0x27, 0x21, 0x13, 0xa1, 0x27, 0xf8, + 0xad, 0x06, 0xa9, 0x68, 0xba, 0xbc, 0x46, 0x21, 0xa0, 0xa3, 0xa0, 0x6d, 0xb8, 0xf8, 0xa8, 0x26, + 0x4c, 0x11, 0x95, 0x56, 0xcb, 0xeb, 0x77, 0x43, 0x7e, 0x5e, 0x5e, 0x3f, 0x39, 0x2e, 0x5f, 0x39, + 0x68, 0x45, 0xd6, 0x0c, 0x87, 0x55, 0xab, 0x07, 0x66, 0x02, 0xd7, 0xea, 0xc0, 0xcc, 0x23, 0x1c, + 0x92, 0xad, 0x24, 0x84, 0x9f, 0xec, 0x35, 0xf9, 0x04, 0x26, 0xf7, 0xdc, 0xf0, 0x50, 0x57, 0x2e, + 0xe9, 0x0c, 0xbc, 0x72, 0xc3, 0x43, 0xa1, 0x5c, 0xaa, 0x9f, 0xb9, 0x02, 0x6e, 0xad, 0xc1, 0x2c, + 0x6f, 0x4d, 0xca, 0x5a, 0x2b, 0x3a, 0xc1, 0x5c, 0xa4, 0xad, 0xaa, 0x04, 0x75, 0x32, 0x7f, 0x98, + 0x87, 0xf9, 0xda, 0xa1, 0xd3, 0x3d, 0xc0, 0x3b, 0x4e, 0x10, 0xbc, 0xf2, 0xfc, 0xb6, 0xd2, 0x79, + 0x7a, 0x2f, 0x98, 0xe8, 0x3c, 0xbd, 0xfc, 0x5b, 0x81, 0xc9, 0xed, 0x4e, 0x5b, 0xe0, 0x70, 0xb9, + 0x94, 0xb6, 0xe5, 0x75, 0xda, 0xcd, 0x9e, 0xa0, 0xa5, 0x02, 0x11, 0x9c, 0x2d, 0xfc, 0x4a, 0xe2, + 0x0c, 0x45, 0x38, 0x5d, 0xfc, 0x4a, 0xc1, 0x51, 0x80, 0xd0, 0x1a, 0x5c, 0xac, 0xe3, 0x96, 0xd7, + 0x6d, 0x3f, 0x74, 0x5a, 0xa1, 0xe7, 0xb3, 0xeb, 0x91, 0xe1, 0x48, 0x4e, 0x08, 0x68, 0x65, 0xf3, + 0x39, 0xad, 0x65, 0xb7, 0x22, 0x76, 0x12, 0x03, 0x6d, 0xd3, 0xcb, 0x15, 0x7a, 0xbb, 0xce, 0xaf, + 0x65, 0x6f, 0xde, 0x91, 0xd7, 0xed, 0x35, 0x1f, 0xd3, 0x4d, 0xe1, 0x74, 0xa4, 0xe0, 0x2e, 0xd9, + 0x2e, 0x65, 0x2e, 0x02, 0xd2, 0x96, 0x44, 0xac, 0x67, 0x30, 0xbd, 0xd3, 0xe9, 0x1f, 0xb8, 0x5d, + 0xc2, 0x06, 0xea, 0xf8, 0xa7, 0x68, 0x15, 0x20, 0x2a, 0xe0, 0xc6, 0x09, 0x61, 0xbf, 0x8a, 0x2a, + 0x1a, 0x0f, 0xf8, 0x87, 0x44, 0x4b, 0xa8, 0x80, 0x64, 0x2b, 0x78, 0xd6, 0x5f, 0x1a, 0x02, 0xc4, + 0x17, 0xa0, 0x1e, 0x3a, 0x21, 0xae, 0xe3, 0x90, 0x30, 0xdb, 0x05, 0xc8, 0x4b, 0x1d, 0x7b, 0xf4, + 0xe4, 0xb8, 0x9c, 0x77, 0xdb, 0x76, 0x7e, 0x63, 0x15, 0xbd, 0x03, 0x23, 0x14, 0x8c, 0xce, 0xff, + 0x8c, 0x6c, 0x4f, 0xa5, 0xc0, 0x38, 0x47, 0x40, 0xfe, 0xb4, 0x19, 0x30, 0x7a, 0x17, 0x26, 0x56, + 0x71, 0x07, 0x1f, 0x38, 0xa1, 0x27, 0xbe, 0x6e, 0xa6, 0xb5, 0x8a, 0x42, 0x65, 0xcf, 0x45, 0x90, + 0x44, 0x3a, 0xb6, 0xb1, 0x13, 0x78, 0x5d, 0x55, 0x3a, 0xf6, 0x69, 0x89, 0x2a, 0x1d, 0x33, 0x18, + 0xf4, 0xbb, 0x39, 0x98, 0xac, 0x74, 0xbb, 0x5c, 0x1b, 0x0c, 0xf8, 0xac, 0xcf, 0xdf, 0x91, 0x5e, + 0x0b, 0x9b, 0xce, 0x3e, 0xee, 0x34, 0x9c, 0x4e, 0x1f, 0x07, 0xd5, 0x2f, 0x88, 0xc0, 0xf2, 0x5f, + 0x8e, 0xcb, 0x1f, 0x9f, 0x41, 0xbf, 0x8b, 0xfc, 0x1f, 0x76, 0x7d, 0xc7, 0x0d, 0x03, 0x7a, 0xf3, + 0x18, 0x35, 0xa8, 0x7e, 0x37, 0x4a, 0x3f, 0xd0, 0x5b, 0x30, 0xc2, 0xf4, 0x4d, 0x26, 0x64, 0x53, + 0x5e, 0x1c, 0x53, 0x34, 0x6d, 0x06, 0x61, 0xdd, 0x90, 0xe7, 0xdd, 0xc6, 0x6a, 0xda, 0x12, 0x58, + 0x35, 0x58, 0x7e, 0x84, 0x43, 0x1b, 0x07, 0x38, 0x14, 0x9b, 0x96, 0x6e, 0xb9, 0xc8, 0x44, 0x32, + 0x46, 0x7f, 0x4b, 0x64, 0xba, 0x1e, 0x6c, 0xa3, 0x8a, 0x1a, 0xeb, 0x2f, 0xe6, 0xa0, 0x5c, 0xf3, + 0x31, 0x3b, 0xef, 0x53, 0x08, 0x65, 0x33, 0x93, 0x65, 0xee, 0xc8, 0x91, 0x8f, 0x6a, 0xc9, 0x2c, + 0x71, 0x67, 0x8d, 0xd3, 0x69, 0x85, 0xd6, 0x73, 0x98, 0xb7, 0x71, 0x17, 0xbf, 0x22, 0xda, 0xac, + 0xa6, 0xc5, 0x95, 0x61, 0x84, 0x7d, 0x79, 0x89, 0x21, 0xb0, 0xf2, 0xb3, 0x29, 0xa9, 0xd6, 0x3f, + 0xcc, 0x43, 0x81, 0x0d, 0xb7, 0xea, 0x85, 0xa7, 0x1b, 0x1f, 0x1f, 0x41, 0x7e, 0x80, 0x5e, 0x7b, + 0x2b, 0x9a, 0xed, 0xa1, 0x48, 0x38, 0xa0, 0x5d, 0x25, 0x67, 0x9c, 0xa8, 0x24, 0x03, 0x62, 0xbb, + 0x80, 0x59, 0x80, 0xe8, 0x80, 0xe8, 0x2e, 0xe0, 0x6b, 0x8f, 0x7e, 0x3b, 0x07, 0xa3, 0x6c, 0x5f, + 0x65, 0xef, 0xdc, 0xbd, 0xf3, 0xd9, 0xb9, 0x85, 0x90, 0xfe, 0xa5, 0x7e, 0x47, 0xac, 0xce, 0xfa, + 0xc7, 0x79, 0xb8, 0xa8, 0xcc, 0x15, 0x63, 0x4b, 0xe8, 0x2d, 0x26, 0xdb, 0x28, 0x13, 0x46, 0x6d, + 0x6a, 0xd4, 0x7a, 0x1d, 0x69, 0xca, 0x74, 0xe6, 0xde, 0x82, 0x71, 0x32, 0xa4, 0xb8, 0xf9, 0x8d, + 0x9e, 0xb0, 0x0c, 0x54, 0x54, 0x9f, 0x7a, 0xf6, 0xee, 0xc2, 0x38, 0xfd, 0x93, 0xac, 0xc8, 0x70, + 0xfa, 0x8a, 0x48, 0x20, 0xe4, 0x02, 0x3c, 0xf6, 0xdc, 0xee, 0x53, 0x1c, 0x1e, 0x7a, 0xc2, 0x58, + 0xb9, 0x41, 0xf8, 0xe0, 0xff, 0xe7, 0xb9, 0xdd, 0xe6, 0x11, 0x2d, 0x3e, 0xab, 0x79, 0x27, 0x22, + 0x68, 0x2b, 0xc4, 0xad, 0x7b, 0x50, 0x20, 0x2c, 0xeb, 0xf4, 0x5b, 0xcb, 0x9a, 0x03, 0xf4, 0x08, + 0x87, 0x55, 0x4f, 0x3b, 0x4c, 0xad, 0x69, 0x98, 0xdc, 0x71, 0xbb, 0x07, 0xe2, 0xe7, 0x1f, 0x0c, + 0xc1, 0x14, 0xfb, 0xcd, 0x57, 0x20, 0x26, 0xf2, 0xe4, 0x4e, 0x23, 0xf2, 0x7c, 0x00, 0xd3, 0xfc, + 0x3a, 0x0b, 0xfb, 0xf4, 0x9a, 0x83, 0xad, 0x07, 0x55, 0x19, 0xd8, 0x75, 0x56, 0xf3, 0x25, 0xab, + 0xb1, 0x75, 0x40, 0xb4, 0x09, 0x33, 0xac, 0xe0, 0x21, 0x76, 0xc2, 0x7e, 0x64, 0xf5, 0x98, 0xe5, + 0x5a, 0x83, 0x28, 0x66, 0xfc, 0x8c, 0xd3, 0x7a, 0xce, 0x0b, 0xed, 0x18, 0x2e, 0xfa, 0x0c, 0x66, + 0x77, 0x7c, 0xef, 0xcb, 0xd7, 0x8a, 0x90, 0xc7, 0x58, 0xfa, 0xfc, 0xc9, 0x71, 0xf9, 0x62, 0x8f, + 0x54, 0x35, 0x55, 0x51, 0x2f, 0x0e, 0x4d, 0xf6, 0xd4, 0x46, 0x50, 0xf5, 0x7c, 0xb7, 0x7b, 0x40, + 0x57, 0x73, 0x9c, 0xed, 0x29, 0x37, 0x68, 0xee, 0xd3, 0x42, 0x5b, 0x56, 0xc7, 0x8c, 0x8f, 0x63, + 0x83, 0x8d, 0x8f, 0xf7, 0x00, 0x36, 0x3d, 0xa7, 0x5d, 0xe9, 0x74, 0x6a, 0x95, 0x80, 0x9a, 0x1c, + 0x54, 0x93, 0xbb, 0xd3, 0xe9, 0x34, 0x5b, 0x4e, 0x60, 0x2b, 0x30, 0x8f, 0x87, 0xc7, 0x47, 0x0b, + 0x63, 0xf6, 0xec, 0xa6, 0xdb, 0xc2, 0xdd, 0x00, 0xef, 0x39, 0x7e, 0xd7, 0xed, 0x1e, 0x04, 0xd6, + 0x7f, 0x1d, 0x86, 0x71, 0x39, 0xe4, 0x3b, 0xaa, 0xda, 0xc3, 0x45, 0x23, 0xca, 0xa1, 0x22, 0xb3, + 0x88, 0xad, 0x40, 0xa0, 0xcb, 0xec, 0xee, 0x94, 0x09, 0x65, 0x63, 0x64, 0x77, 0x3b, 0xbd, 0x1e, + 0xbb, 0x21, 0x5d, 0x80, 0xfc, 0x6a, 0x95, 0xce, 0xff, 0x38, 0x3b, 0x09, 0xda, 0xfb, 0x76, 0x7e, + 0xb5, 0x4a, 0x76, 0xd9, 0xf6, 0xc6, 0x6a, 0x8d, 0x4e, 0xe5, 0x38, 0xdb, 0x65, 0x9e, 0xdb, 0x6e, + 0xd9, 0xb4, 0x94, 0xd4, 0xd6, 0x2b, 0x4f, 0x37, 0xf9, 0x74, 0xd1, 0xda, 0xc0, 0x39, 0xea, 0xd8, + 0xb4, 0x94, 0xa8, 0x0a, 0x4c, 0xc3, 0xad, 0x79, 0xdd, 0xd0, 0xf7, 0x3a, 0x01, 0x95, 0x68, 0xc7, + 0xd9, 0x72, 0x72, 0xd5, 0xb8, 0xc5, 0xab, 0xec, 0x18, 0x28, 0xda, 0x83, 0xc5, 0x4a, 0xfb, 0xa5, + 0xd3, 0x6d, 0xe1, 0x36, 0xab, 0xd9, 0xf3, 0xfc, 0x17, 0xcf, 0x3b, 0xde, 0xab, 0x80, 0xce, 0xf7, + 0x38, 0xb7, 0x24, 0x71, 0x10, 0xa1, 0x69, 0xbf, 0x12, 0x40, 0x76, 0x1a, 0x36, 0xe1, 0x92, 0xb5, + 0x8e, 0xd7, 0x6f, 0xf3, 0x55, 0xa0, 0x5c, 0xb2, 0x45, 0x0a, 0x6c, 0x56, 0x4e, 0x66, 0x69, 0xbd, + 0xfe, 0x94, 0xda, 0x6d, 0xf8, 0x2c, 0x1d, 0x06, 0x47, 0x36, 0x29, 0x43, 0x37, 0x61, 0x4c, 0x68, + 0x3d, 0xcc, 0xfc, 0x4b, 0x2d, 0x8c, 0x42, 0xdb, 0x11, 0x75, 0xe4, 0x93, 0xb0, 0x71, 0xcb, 0x7b, + 0x89, 0xfd, 0xd7, 0x35, 0xaf, 0x8d, 0x85, 0x95, 0x81, 0x6b, 0xd1, 0xac, 0xa2, 0xd9, 0x22, 0x35, + 0xb6, 0x0e, 0x48, 0x1a, 0x60, 0x82, 0x53, 0x40, 0x1d, 0x92, 0x78, 0x03, 0x4c, 0xb0, 0x0a, 0x6c, + 0x51, 0x87, 0x56, 0xe1, 0x62, 0xa5, 0x1f, 0x7a, 0x47, 0x4e, 0xe8, 0xb6, 0x9e, 0xf5, 0x0e, 0x7c, + 0x87, 0x34, 0x52, 0xa0, 0x08, 0x54, 0xb5, 0x73, 0x44, 0x65, 0xb3, 0xcf, 0x6b, 0xed, 0x24, 0xc2, + 0xe3, 0xe1, 0xf1, 0xc9, 0xc2, 0xd4, 0xe3, 0xe1, 0xf1, 0xa9, 0xc2, 0xf4, 0xe3, 0xe1, 0xf1, 0xe9, + 0xc2, 0x8c, 0x75, 0x1f, 0x2e, 0x32, 0x3e, 0x73, 0x6a, 0x81, 0xdf, 0xda, 0x01, 0xa8, 0xe3, 0x23, + 0xa7, 0x77, 0xe8, 0x91, 0x1d, 0x59, 0x55, 0x7f, 0x71, 0x81, 0x11, 0x49, 0x87, 0x18, 0x5e, 0xd1, + 0x78, 0x20, 0xf4, 0x34, 0x01, 0x69, 0x2b, 0x58, 0xd6, 0x7f, 0xc8, 0x03, 0xa2, 0x8e, 0x21, 0xf5, + 0xd0, 0xc7, 0xce, 0x91, 0xe8, 0xc6, 0x87, 0x30, 0xc5, 0x8e, 0x0c, 0x56, 0x4c, 0xbb, 0x43, 0xa4, + 0x51, 0xc6, 0x2b, 0xd4, 0xaa, 0xf5, 0x0b, 0xb6, 0x06, 0x4a, 0x50, 0x6d, 0x1c, 0xf4, 0x8f, 0x04, + 0x6a, 0x5e, 0x43, 0x55, 0xab, 0x08, 0xaa, 0xfa, 0x1b, 0x7d, 0x06, 0x33, 0x35, 0xef, 0xa8, 0x47, + 0xe6, 0x84, 0x23, 0x0f, 0xf1, 0x93, 0x93, 0xb7, 0xab, 0x55, 0xae, 0x5f, 0xb0, 0x63, 0xe0, 0x68, + 0x0b, 0x2e, 0x3d, 0xec, 0xf4, 0x83, 0xc3, 0x4a, 0xb7, 0x5d, 0xeb, 0x78, 0x81, 0xa0, 0x32, 0xcc, + 0xed, 0xbb, 0x9c, 0xd3, 0x25, 0x21, 0xd6, 0x2f, 0xd8, 0x26, 0x44, 0x74, 0x93, 0x7b, 0xb9, 0xf2, + 0x13, 0x7c, 0xfa, 0x0e, 0x77, 0x82, 0xdd, 0xee, 0xe2, 0xed, 0xe7, 0xeb, 0x17, 0x6c, 0x56, 0x5b, + 0x9d, 0x80, 0x31, 0xc1, 0xe5, 0xef, 0x92, 0xcd, 0x22, 0xa7, 0x93, 0x48, 0xcb, 0xfd, 0x00, 0x95, + 0x60, 0xfc, 0x59, 0x8f, 0x30, 0x1f, 0x21, 0xc2, 0xd9, 0xf2, 0xb7, 0xf5, 0x2d, 0x7d, 0xa6, 0xd1, + 0xb2, 0xaa, 0x67, 0x33, 0xe0, 0xa8, 0xc0, 0x5a, 0xd7, 0x27, 0x37, 0x1b, 0x5a, 0x6b, 0x37, 0x1f, + 0x6b, 0xb7, 0x10, 0x9f, 0x6b, 0x6b, 0xde, 0x38, 0x79, 0xd6, 0xe7, 0x70, 0xf5, 0x59, 0x8f, 0x28, + 0x35, 0x95, 0x5e, 0xaf, 0xe3, 0xb6, 0xe8, 0x79, 0xca, 0x4e, 0x03, 0xb1, 0x59, 0xde, 0x93, 0x2e, + 0x94, 0xb9, 0x54, 0x87, 0x13, 0x38, 0x39, 0x2e, 0x8f, 0xb2, 0x53, 0x45, 0x78, 0x4e, 0x5a, 0x3f, + 0xcb, 0xc1, 0x55, 0xf6, 0x05, 0xa4, 0x92, 0xfe, 0x35, 0xd5, 0xd1, 0x53, 0x11, 0x53, 0xa4, 0x5b, + 0xa7, 0xea, 0xca, 0x19, 0xdd, 0x25, 0xe6, 0xd3, 0xef, 0x12, 0x33, 0x2f, 0x57, 0xad, 0xef, 0x81, + 0xc5, 0x7b, 0xd4, 0xe9, 0x24, 0x3a, 0x15, 0x7c, 0x95, 0x5e, 0x59, 0xff, 0x33, 0x0f, 0x8b, 0x8f, + 0x70, 0x17, 0xfb, 0x0e, 0x1d, 0xa7, 0x26, 0x91, 0x9f, 0xfe, 0x0e, 0x47, 0x8a, 0x9b, 0xf9, 0x14, + 0x71, 0xf3, 0x32, 0x0c, 0x3d, 0xb3, 0x37, 0xf8, 0xb0, 0x28, 0x23, 0xed, 0xfb, 0xae, 0x4d, 0xca, + 0xd0, 0x46, 0x74, 0xd3, 0x31, 0x3c, 0xf0, 0xa6, 0xe3, 0x12, 0xb7, 0xfc, 0x8e, 0xf1, 0x9b, 0x0e, + 0xfd, 0x7e, 0x63, 0x4b, 0x91, 0x69, 0x09, 0xbb, 0x79, 0x9b, 0x7f, 0x53, 0x29, 0x03, 0xe4, 0xe2, + 0x29, 0xbb, 0x8a, 0xa6, 0x5b, 0x80, 0x49, 0xa9, 0x42, 0x36, 0x2d, 0x7d, 0x0f, 0x26, 0x15, 0x10, + 0xc3, 0x75, 0xf2, 0xb7, 0xd4, 0xeb, 0xe4, 0xc9, 0x95, 0x85, 0x48, 0x86, 0xae, 0x87, 0x44, 0x3a, + 0x60, 0x42, 0xb4, 0x7a, 0xcd, 0xfc, 0x31, 0x14, 0x93, 0xbd, 0xe1, 0x22, 0xd7, 0x20, 0x2d, 0xc4, + 0x5a, 0x85, 0xb9, 0x47, 0x38, 0x8c, 0xbc, 0x4c, 0x95, 0x4b, 0xa8, 0xd8, 0x77, 0x96, 0x61, 0xfd, + 0xb2, 0xea, 0x30, 0x1f, 0xa3, 0xc2, 0xdb, 0xff, 0x08, 0xc6, 0x84, 0x7f, 0x4a, 0x2e, 0xdd, 0x3f, + 0x85, 0xee, 0x5b, 0x4e, 0xd9, 0x16, 0x08, 0xd6, 0x1e, 0x2c, 0x68, 0x44, 0x03, 0x49, 0xf5, 0x3b, + 0x30, 0x2e, 0xca, 0x62, 0x66, 0x03, 0x8d, 0x2c, 0xdd, 0x5a, 0x81, 0x40, 0x96, 0x28, 0xd6, 0x21, + 0x2c, 0x6c, 0xba, 0x81, 0x4e, 0x99, 0x8d, 0x7a, 0x09, 0x26, 0x7a, 0xce, 0x01, 0x6e, 0x06, 0xee, + 0xaf, 0xb3, 0xfd, 0x39, 0x62, 0x8f, 0x93, 0x82, 0xba, 0xfb, 0xeb, 0x18, 0x5d, 0x01, 0xa0, 0x95, + 0x74, 0xfe, 0x38, 0x7b, 0xa1, 0xe0, 0x4c, 0x9f, 0x43, 0x30, 0x4c, 0xb6, 0x31, 0xdb, 0x90, 0x36, + 0xfd, 0xdb, 0xf2, 0x61, 0x31, 0xd1, 0x12, 0x1f, 0xc3, 0x5d, 0x90, 0x5d, 0xcb, 0x18, 0x83, 0x2d, + 0x81, 0xd0, 0x2d, 0x98, 0xed, 0xe2, 0x2f, 0xc3, 0x66, 0xa2, 0x0f, 0xd3, 0xa4, 0x78, 0x47, 0xf4, + 0xc3, 0xfa, 0x11, 0xd5, 0xae, 0xe3, 0x0e, 0x64, 0xe7, 0x36, 0x79, 0x1d, 0x28, 0x91, 0x21, 0xe9, + 0xfe, 0x42, 0xbf, 0xb2, 0x09, 0x7c, 0x09, 0x4b, 0xc6, 0xd6, 0x7e, 0xd5, 0x93, 0xf8, 0xa7, 0x79, + 0x58, 0x64, 0xa7, 0x54, 0xf2, 0xd3, 0x38, 0x3d, 0x0f, 0xfb, 0x46, 0x8c, 0xc2, 0xf7, 0x0c, 0x46, + 0x61, 0x8a, 0xa2, 0x1a, 0x85, 0x35, 0x53, 0xf0, 0x07, 0x66, 0x53, 0x30, 0x15, 0x20, 0x75, 0x53, + 0x70, 0xdc, 0x00, 0xbc, 0x96, 0x6e, 0x00, 0xa6, 0xe6, 0x30, 0x83, 0x01, 0xd8, 0x60, 0xf6, 0x7d, + 0x3c, 0x3c, 0x9e, 0x2f, 0x0c, 0x59, 0x0d, 0x28, 0x26, 0xa7, 0xf8, 0x1c, 0xf8, 0xc6, 0x1f, 0xe5, + 0xe0, 0x0a, 0x97, 0x30, 0x62, 0x1f, 0xc1, 0xd9, 0x57, 0xf0, 0x5d, 0x98, 0xe2, 0xb8, 0xbb, 0xd1, + 0x66, 0x61, 0xae, 0xa0, 0x82, 0x13, 0x32, 0x76, 0xaa, 0x81, 0xa1, 0x77, 0x15, 0x6d, 0x9f, 0x59, + 0x90, 0x2e, 0x93, 0xe3, 0x92, 0x99, 0x05, 0x52, 0x75, 0x7e, 0xeb, 0x0b, 0xb8, 0x9a, 0xd6, 0xf1, + 0x73, 0x98, 0x97, 0x3f, 0xce, 0xc1, 0x12, 0x27, 0xaf, 0x7d, 0x4e, 0x5f, 0x89, 0xe5, 0x9f, 0xc1, + 0xef, 0xe0, 0x31, 0x4c, 0x92, 0x06, 0x45, 0xbf, 0xf5, 0xb7, 0x49, 0x4a, 0xcd, 0xaa, 0x13, 0x3a, + 0xfc, 0x2a, 0xcb, 0x39, 0xea, 0x08, 0x37, 0x45, 0x5b, 0x45, 0xb6, 0x7e, 0x00, 0xcb, 0xe6, 0x21, + 0x9c, 0xc3, 0xfc, 0x3c, 0x86, 0x92, 0x81, 0x71, 0x7e, 0xb5, 0x03, 0xf1, 0xfb, 0xb0, 0x64, 0xa4, + 0x75, 0x0e, 0xdd, 0x5c, 0x27, 0xc7, 0x7d, 0x78, 0x0e, 0x4b, 0x68, 0xed, 0xc1, 0x65, 0x03, 0xa5, + 0x73, 0xe8, 0xe2, 0x23, 0x58, 0x94, 0x62, 0xee, 0xd7, 0xea, 0xe1, 0x53, 0xb8, 0xc2, 0x08, 0x9d, + 0xcf, 0xaa, 0x3c, 0x81, 0x25, 0x4e, 0xee, 0x1c, 0x66, 0x6f, 0x1d, 0x96, 0x23, 0x6d, 0xd6, 0x20, + 0x4b, 0x9c, 0x9a, 0xc9, 0x58, 0x9b, 0x70, 0x2d, 0xa2, 0x94, 0x72, 0xb0, 0x9e, 0x9e, 0x1a, 0x93, + 0xc5, 0xa2, 0x55, 0x3a, 0x47, 0x59, 0x2c, 0x02, 0x3c, 0x37, 0x71, 0x62, 0x03, 0x2e, 0x31, 0xc2, + 0xba, 0xdc, 0xba, 0xa2, 0xca, 0xad, 0xc6, 0x67, 0x3d, 0x49, 0x51, 0xf6, 0x29, 0x15, 0x65, 0x05, + 0x48, 0xd4, 0xc3, 0x77, 0x61, 0x94, 0xbf, 0x5c, 0x64, 0xfd, 0x33, 0x10, 0x63, 0x92, 0x3a, 0x43, + 0xe3, 0xc0, 0xd6, 0x8f, 0xe1, 0x0a, 0x53, 0x03, 0xe3, 0x1e, 0xf2, 0x62, 0x49, 0xbe, 0x13, 0xd3, + 0x02, 0x33, 0x1c, 0xf1, 0x4d, 0xca, 0xe0, 0xbe, 0xd8, 0xdb, 0x69, 0xf4, 0x4f, 0xe5, 0x29, 0x2a, + 0xb4, 0xbb, 0xbc, 0x51, 0xbb, 0xbb, 0x01, 0xd7, 0xa5, 0x76, 0x17, 0x6f, 0x46, 0x9a, 0x6d, 0x7f, + 0x00, 0x4b, 0x6c, 0xa0, 0xfa, 0x7b, 0x2d, 0xd1, 0x8d, 0x8f, 0x63, 0xc3, 0x4c, 0x7d, 0x10, 0x66, + 0x1a, 0xe4, 0x5f, 0xcd, 0x89, 0x4f, 0xce, 0x4c, 0xfc, 0x9b, 0x56, 0x77, 0xb7, 0xa0, 0x2c, 0x27, + 0x44, 0xef, 0xd1, 0x57, 0xd3, 0x75, 0x9f, 0xc2, 0x7c, 0xe2, 0x4d, 0x01, 0x11, 0x58, 0xd1, 0x3b, + 0xe4, 0xb3, 0xa0, 0x05, 0x62, 0xdb, 0xa5, 0xbe, 0x41, 0xb0, 0x25, 0xa4, 0xd5, 0x84, 0xe5, 0xe4, + 0x52, 0xb8, 0x2d, 0xe1, 0x18, 0x84, 0x3e, 0x23, 0x9f, 0x30, 0x7b, 0xd8, 0x90, 0x1b, 0xf0, 0xb0, + 0x81, 0x7f, 0xc7, 0x0c, 0x5d, 0x60, 0x59, 0x96, 0x60, 0x35, 0xb1, 0xf1, 0x93, 0xd6, 0xc5, 0x7e, + 0xf8, 0x27, 0x39, 0x40, 0xa2, 0xae, 0x56, 0xb7, 0x45, 0xdb, 0x97, 0x61, 0xa8, 0x56, 0xb7, 0xb9, + 0x43, 0x22, 0xd5, 0xb7, 0x5b, 0x81, 0x6f, 0x93, 0xb2, 0xb8, 0xd8, 0x9a, 0x3f, 0x8d, 0xd8, 0xba, + 0x01, 0xa8, 0xee, 0x1e, 0x74, 0xf7, 0xdc, 0xf0, 0x50, 0x36, 0x56, 0xe1, 0x26, 0x62, 0xea, 0xf5, + 0x1e, 0xb8, 0x07, 0xdd, 0x26, 0xbd, 0x85, 0x97, 0xcf, 0x33, 0x5a, 0x8e, 0x6d, 0x40, 0xb2, 0x7e, + 0x08, 0x97, 0xb4, 0xfe, 0xf2, 0xef, 0x3e, 0xd3, 0xf5, 0x12, 0xdd, 0x82, 0xb1, 0x5a, 0x85, 0xde, + 0xd8, 0x51, 0x0b, 0xc3, 0x14, 0xe3, 0x50, 0x2d, 0xa7, 0x49, 0x1f, 0xb3, 0xdb, 0xa2, 0xd2, 0xfa, + 0xfb, 0xc3, 0x0a, 0x75, 0xc5, 0x1b, 0x35, 0x63, 0x3a, 0xee, 0x03, 0xb0, 0x3d, 0xa5, 0xcc, 0x06, + 0x11, 0x19, 0x27, 0xf9, 0x25, 0x03, 0x63, 0xe2, 0xb6, 0x02, 0x74, 0x5a, 0x1f, 0x54, 0xee, 0xfd, + 0xc3, 0x90, 0xc4, 0x4d, 0x9c, 0xf4, 0xfe, 0xe1, 0xa4, 0x03, 0x5b, 0x05, 0x42, 0x3f, 0x8e, 0xfb, + 0x65, 0x8d, 0xd0, 0x8b, 0xef, 0x37, 0xb8, 0x21, 0xc3, 0x30, 0xb6, 0xb3, 0xb9, 0x66, 0xbd, 0x82, + 0x79, 0x82, 0xeb, 0x3e, 0xa7, 0xce, 0x57, 0x6b, 0x5f, 0x86, 0xb8, 0xcb, 0x4e, 0x83, 0x51, 0xda, + 0xce, 0xcd, 0x8c, 0x76, 0x22, 0x60, 0xfe, 0xfa, 0x3a, 0xa2, 0xd3, 0xc4, 0xb2, 0xce, 0x36, 0xd3, + 0xa7, 0xbb, 0xce, 0xde, 0x5c, 0xeb, 0xb6, 0x7b, 0x9e, 0x2b, 0xd5, 0x10, 0xb6, 0xeb, 0xfc, 0x4e, + 0x13, 0xf3, 0x72, 0x5b, 0x05, 0xb2, 0x6e, 0x65, 0xfa, 0x63, 0x8d, 0xc3, 0xf0, 0x6e, 0x6d, 0x77, + 0xb3, 0x90, 0xb3, 0xee, 0x02, 0x28, 0x2d, 0x01, 0x8c, 0x6e, 0x6d, 0xdb, 0x4f, 0x2b, 0x9b, 0x85, + 0x0b, 0x68, 0x1e, 0x2e, 0xee, 0x6d, 0x6c, 0xad, 0x6e, 0xef, 0xd5, 0x9b, 0xf5, 0xa7, 0x15, 0x7b, + 0xb7, 0x56, 0xb1, 0x57, 0x0b, 0x39, 0xeb, 0x0b, 0x98, 0xd3, 0x47, 0x78, 0xae, 0x9b, 0x30, 0x84, + 0x4b, 0x52, 0x02, 0x7a, 0xbc, 0xb7, 0xab, 0x78, 0xaf, 0x70, 0x95, 0x2a, 0x7e, 0xa1, 0xc6, 0x95, + 0x2f, 0xfe, 0xdd, 0x29, 0x40, 0xda, 0x35, 0x68, 0x3e, 0xf3, 0x1a, 0xd4, 0x7a, 0x1f, 0xe6, 0xf4, + 0x56, 0x4f, 0x6b, 0x54, 0x7a, 0x83, 0xba, 0xf5, 0x28, 0x1e, 0x8d, 0x44, 0xb7, 0x8f, 0xba, 0xc8, + 0x79, 0xf1, 0xfb, 0x50, 0xe0, 0x50, 0xd1, 0x59, 0x7d, 0x43, 0x58, 0xfd, 0x18, 0xcf, 0xd4, 0x5f, + 0xc8, 0x0b, 0x27, 0x83, 0x37, 0xc5, 0x3d, 0xc2, 0xa0, 0x16, 0xfe, 0x56, 0x0e, 0x8a, 0x31, 0xe7, + 0xc0, 0xda, 0xa1, 0xd3, 0xe9, 0xe0, 0xee, 0x01, 0x46, 0xb7, 0x61, 0x78, 0x77, 0x7b, 0x77, 0x87, + 0xdb, 0xd9, 0xe6, 0xf8, 0x36, 0x25, 0x45, 0x12, 0xc6, 0xa6, 0x10, 0xe8, 0x09, 0x5c, 0x14, 0x4e, + 0x2c, 0xb2, 0x8a, 0xab, 0x31, 0x57, 0xb2, 0x5d, 0x62, 0x92, 0x78, 0x8f, 0x87, 0xc7, 0x73, 0x85, + 0xbc, 0xf5, 0xbb, 0x39, 0x58, 0x4c, 0x71, 0x5b, 0x44, 0x6f, 0x69, 0x1d, 0xbb, 0xa4, 0x74, 0x4c, + 0x80, 0xac, 0x5f, 0xe0, 0x3d, 0xab, 0x29, 0x3e, 0x3a, 0x43, 0x67, 0xf0, 0xd1, 0xe1, 0x6f, 0x9f, + 0x29, 0x1c, 0x7f, 0x39, 0xc4, 0x3c, 0x21, 0x67, 0x61, 0x5a, 0x9b, 0x01, 0xcb, 0x82, 0x29, 0xb5, + 0x65, 0x32, 0xcd, 0x35, 0xaf, 0x2d, 0xa7, 0x99, 0xfc, 0x6d, 0xfd, 0x8d, 0x1c, 0xcc, 0x51, 0x77, + 0xca, 0x03, 0x97, 0x7c, 0x57, 0xd1, 0x14, 0xaf, 0x68, 0x23, 0x59, 0xd6, 0x46, 0x12, 0x83, 0x95, + 0x43, 0xfa, 0x28, 0x31, 0xa4, 0x65, 0xd3, 0x90, 0xa8, 0x16, 0xe8, 0x7a, 0x5d, 0x6d, 0x24, 0xca, + 0xb5, 0xc4, 0xdf, 0xce, 0xc1, 0x25, 0xa5, 0x4f, 0xb2, 0xff, 0xf7, 0xb5, 0x2e, 0x2d, 0x19, 0xba, + 0x94, 0x98, 0xe4, 0x6a, 0xa2, 0x47, 0x6f, 0x64, 0xf5, 0x68, 0xe0, 0x1c, 0xff, 0x49, 0x0e, 0xe6, + 0x8d, 0x73, 0x80, 0x16, 0x88, 0xa8, 0xd5, 0xf2, 0x71, 0xc8, 0xa7, 0x97, 0xff, 0x22, 0xe5, 0x1b, + 0x41, 0xd0, 0xe7, 0x01, 0x43, 0x26, 0x6c, 0xfe, 0x0b, 0xbd, 0x01, 0xd3, 0x3b, 0xd8, 0x77, 0xbd, + 0x36, 0xf3, 0xde, 0x62, 0x37, 0xdc, 0xd3, 0xb6, 0x5e, 0x88, 0x96, 0x61, 0xa2, 0xd2, 0x39, 0xf0, + 0x7c, 0x37, 0x3c, 0x64, 0x37, 0x43, 0x13, 0x76, 0x54, 0x40, 0x68, 0xaf, 0xba, 0x07, 0xc2, 0x69, + 0x63, 0xda, 0xe6, 0xbf, 0x50, 0x11, 0xc6, 0x84, 0x81, 0x87, 0x9a, 0x87, 0x6c, 0xf1, 0x93, 0x60, + 0x7c, 0xcf, 0xa6, 0x9b, 0x80, 0xbe, 0xa6, 0xb1, 0xf9, 0x2f, 0xeb, 0x6d, 0x98, 0x33, 0xcd, 0xa3, + 0x71, 0xcb, 0xfc, 0xf9, 0x3c, 0x5c, 0xaa, 0xb4, 0xdb, 0x4f, 0x1f, 0x56, 0x56, 0xb1, 0x2a, 0xe0, + 0xbc, 0x03, 0xc3, 0x1b, 0x5d, 0x37, 0xe4, 0xd2, 0x8d, 0x70, 0xf0, 0x35, 0x40, 0x12, 0x28, 0xb2, + 0x42, 0xe4, 0x7f, 0x64, 0xc3, 0xa5, 0xb5, 0x2f, 0xdd, 0x20, 0x74, 0xbb, 0x07, 0xaa, 0x97, 0x70, + 0xfe, 0x34, 0x5e, 0xc2, 0xeb, 0x17, 0x6c, 0x13, 0x32, 0xda, 0x85, 0x85, 0x2d, 0xfc, 0xca, 0xb0, + 0x85, 0xe4, 0xe3, 0x09, 0x49, 0xd6, 0xb0, 0x73, 0x52, 0x70, 0xd5, 0x1d, 0xfa, 0xdb, 0x79, 0xfa, + 0xc2, 0x4a, 0x19, 0x18, 0x6f, 0xf9, 0x19, 0xcc, 0x29, 0x1d, 0x8a, 0x38, 0x4e, 0x8e, 0x3f, 0x2e, + 0x35, 0x0e, 0x47, 0xfd, 0x90, 0x8c, 0xe8, 0x68, 0x0f, 0x16, 0xf5, 0x4e, 0x45, 0x94, 0xf5, 0x8f, + 0xc1, 0x04, 0xb2, 0x7e, 0xc1, 0x4e, 0xc3, 0x46, 0x2b, 0x30, 0x54, 0x69, 0xbd, 0xe0, 0xd3, 0x62, + 0x5e, 0x32, 0x36, 0xb2, 0x4a, 0xeb, 0x05, 0x7d, 0x32, 0xdd, 0x7a, 0xa1, 0x7d, 0x0f, 0xff, 0x26, + 0x07, 0x8b, 0x29, 0x2b, 0x8c, 0xae, 0x02, 0xb0, 0x42, 0x85, 0xb7, 0x2b, 0x25, 0x44, 0xd4, 0x62, + 0xbf, 0xa8, 0x27, 0xd7, 0x10, 0x95, 0x38, 0xc4, 0x3b, 0x84, 0xa8, 0xc2, 0x56, 0x80, 0xd0, 0x0e, + 0x4c, 0xb2, 0x5f, 0xec, 0x39, 0xc4, 0x30, 0xc5, 0x41, 0x1a, 0x0e, 0x7b, 0xff, 0x40, 0x45, 0x92, + 0x36, 0x2d, 0x68, 0xc6, 0x9f, 0x41, 0xa8, 0x24, 0xb8, 0x39, 0xb3, 0x16, 0x1f, 0x85, 0x1c, 0x34, + 0xba, 0x0d, 0xa3, 0xac, 0x90, 0xaf, 0xa1, 0x08, 0xdd, 0x12, 0x01, 0xf3, 0x7a, 0xeb, 0xef, 0xe6, + 0x60, 0x81, 0x9d, 0x6d, 0x89, 0x4f, 0xe3, 0x7d, 0xed, 0xd3, 0xb8, 0x2e, 0x3b, 0x6c, 0x02, 0xd6, + 0xbe, 0x8e, 0xaa, 0xee, 0x3b, 0x7f, 0xda, 0xaf, 0x42, 0x45, 0x52, 0xf7, 0xed, 0xdf, 0xcb, 0x09, + 0xeb, 0x4e, 0x72, 0xeb, 0xae, 0xc1, 0xd4, 0x57, 0xdb, 0xb2, 0x1a, 0x1a, 0x7a, 0x97, 0xed, 0xa8, + 0x7c, 0xf6, 0x48, 0x33, 0x37, 0xd5, 0x27, 0x50, 0x4a, 0x9f, 0x9a, 0x41, 0xdb, 0xca, 0x7a, 0x68, + 0xc0, 0xfe, 0x2a, 0xcb, 0xd9, 0x4f, 0xd0, 0xa9, 0xbf, 0xee, 0xb6, 0xc4, 0x8a, 0xde, 0x8a, 0xfb, + 0x39, 0xa6, 0xfa, 0x8e, 0xa9, 0xbd, 0xcd, 0x47, 0xd7, 0x08, 0x7c, 0x73, 0x52, 0xb1, 0x4d, 0xed, + 0xfe, 0xbf, 0xc8, 0xeb, 0x7b, 0xf1, 0xab, 0x34, 0x5a, 0x83, 0xe9, 0x2d, 0xfc, 0x2a, 0xd1, 0x2e, + 0xf5, 0x8b, 0xe9, 0xe2, 0x57, 0x4d, 0xa5, 0x6d, 0xd5, 0x61, 0x5c, 0xc3, 0x41, 0xfb, 0x30, 0x23, + 0xb8, 0xc6, 0x69, 0x99, 0x27, 0x7b, 0x0b, 0x46, 0x5a, 0x48, 0x79, 0xb9, 0x11, 0xa3, 0x78, 0xfe, + 0xdf, 0xb3, 0xb5, 0x03, 0xc5, 0xe4, 0xec, 0xf1, 0xd6, 0xde, 0x19, 0xb4, 0xf6, 0xcc, 0x0c, 0xd2, + 0xd6, 0xf7, 0xc1, 0x3a, 0x35, 0x4d, 0x49, 0x18, 0x69, 0x6b, 0xb8, 0x17, 0x5f, 0x0c, 0xea, 0x5f, + 0x23, 0x16, 0x43, 0x7d, 0x8e, 0x2c, 0xdc, 0x5e, 0x6b, 0xd4, 0xba, 0xa7, 0x52, 0xe2, 0x1d, 0x7b, + 0x1b, 0xc6, 0x78, 0x91, 0x7c, 0xe6, 0x1d, 0xdf, 0x95, 0x02, 0xc0, 0xfa, 0xbd, 0x1c, 0x5c, 0xa6, + 0xb6, 0x46, 0xb7, 0x7b, 0xd0, 0xc1, 0xcf, 0x02, 0xdd, 0x73, 0xf5, 0xdb, 0x1a, 0xa3, 0x59, 0x4c, + 0x79, 0xbf, 0xf3, 0xab, 0x62, 0x2f, 0x7f, 0x27, 0x07, 0x25, 0x53, 0xdf, 0xce, 0x97, 0xc3, 0xdc, + 0xe1, 0x6a, 0x59, 0x9e, 0x5b, 0x51, 0x18, 0xba, 0x6c, 0x53, 0x0c, 0x96, 0x0c, 0x92, 0xfc, 0xaf, + 0xb1, 0x96, 0xff, 0x93, 0x83, 0xb9, 0x8d, 0x80, 0x76, 0xff, 0xa7, 0x7d, 0xd7, 0xc7, 0xd2, 0xfd, + 0xff, 0x8e, 0xe9, 0x39, 0x21, 0x5d, 0x57, 0x73, 0xec, 0x8b, 0x77, 0x94, 0x97, 0x33, 0xf9, 0xac, + 0x77, 0x82, 0x5a, 0xc0, 0x93, 0x5b, 0x30, 0xbc, 0x45, 0xc4, 0xa9, 0x21, 0xbe, 0xff, 0x18, 0x06, + 0x29, 0xa2, 0x8f, 0x5c, 0x48, 0x97, 0xc9, 0x0f, 0xf4, 0x30, 0xf1, 0x94, 0x66, 0x78, 0xf0, 0x3b, + 0xb8, 0x64, 0xa4, 0x96, 0xea, 0x38, 0x8c, 0xee, 0x3a, 0xfe, 0x01, 0x0e, 0xad, 0x1f, 0x40, 0x89, + 0x7b, 0xf9, 0x30, 0xeb, 0x2d, 0xf5, 0x05, 0x0a, 0x22, 0x47, 0xae, 0x2c, 0xcf, 0x9c, 0xab, 0x00, + 0xf5, 0xd0, 0xf1, 0xc3, 0x8d, 0x6e, 0x1b, 0x7f, 0x49, 0x47, 0x3b, 0x62, 0x2b, 0x25, 0xd6, 0xbb, + 0x30, 0x21, 0x87, 0x40, 0x75, 0x39, 0x45, 0x62, 0xa4, 0xc3, 0x99, 0xd3, 0x1e, 0xf7, 0x88, 0x17, + 0x3d, 0x0f, 0x60, 0x3e, 0xb6, 0x14, 0x7c, 0x9f, 0x94, 0xc8, 0x82, 0xb1, 0x32, 0xe6, 0xba, 0x68, + 0xcb, 0xdf, 0x56, 0x0d, 0x2e, 0x26, 0x56, 0x1a, 0x21, 0xfa, 0xda, 0x8b, 0xe9, 0xe9, 0xe4, 0x40, + 0xa9, 0xd7, 0xd7, 0x49, 0xd9, 0xee, 0x66, 0x9d, 0x39, 0x67, 0x93, 0xb2, 0xdd, 0xcd, 0x7a, 0x75, + 0x94, 0xed, 0x1c, 0xeb, 0x1f, 0xe5, 0xa9, 0xfa, 0x9a, 0x98, 0x83, 0x98, 0xed, 0x50, 0xb5, 0x5f, + 0x56, 0x61, 0x82, 0x8e, 0x78, 0x55, 0x3c, 0x3f, 0xc8, 0x76, 0x4c, 0x19, 0xff, 0xf9, 0x71, 0xf9, + 0x02, 0xf5, 0x46, 0x89, 0xd0, 0xd0, 0xa7, 0x30, 0xb6, 0xd6, 0x6d, 0x53, 0x0a, 0x43, 0x67, 0xa0, + 0x20, 0x90, 0xc8, 0x3a, 0xd0, 0x2e, 0x13, 0x51, 0x88, 0x1b, 0x90, 0x6c, 0xa5, 0x84, 0x4e, 0xb3, + 0x7b, 0xe4, 0x32, 0x07, 0xb0, 0x11, 0x9b, 0xfd, 0x20, 0xb3, 0x49, 0xbb, 0x20, 0x9e, 0xde, 0x4f, + 0xd8, 0xf2, 0x37, 0xb2, 0x60, 0x64, 0xdb, 0x6f, 0xf3, 0x87, 0xb3, 0x33, 0x2b, 0x53, 0x22, 0x20, + 0x22, 0x29, 0xb3, 0x59, 0x95, 0xf5, 0xbf, 0x72, 0xb0, 0xf8, 0x08, 0x87, 0xc6, 0x7d, 0xa3, 0xcd, + 0x4a, 0xee, 0x6b, 0xcf, 0x4a, 0xfe, 0xab, 0xcc, 0x8a, 0x1c, 0xf5, 0x50, 0xda, 0xa8, 0x87, 0xd3, + 0x46, 0x3d, 0x92, 0x3e, 0xea, 0x47, 0x30, 0xca, 0x86, 0x8a, 0x6e, 0xc0, 0xc8, 0x46, 0x88, 0x8f, + 0x22, 0xb3, 0x86, 0xea, 0x56, 0x67, 0xb3, 0x3a, 0xa2, 0x71, 0x6d, 0x3a, 0x41, 0x28, 0x9e, 0x03, + 0x4c, 0xd8, 0xe2, 0xa7, 0xf5, 0x13, 0xfa, 0x70, 0x69, 0xd3, 0x6b, 0xbd, 0x50, 0xac, 0xd4, 0x63, + 0xec, 0xab, 0x8c, 0xdf, 0x6a, 0x10, 0x28, 0x56, 0x63, 0x0b, 0x08, 0x74, 0x0d, 0x26, 0x37, 0xba, + 0x0f, 0x3d, 0xbf, 0x85, 0xb7, 0xbb, 0x1d, 0x46, 0x7d, 0xdc, 0x56, 0x8b, 0xb8, 0x2d, 0x86, 0xb7, + 0x10, 0xd9, 0x62, 0x68, 0x41, 0xcc, 0x16, 0xc3, 0x62, 0x66, 0xd9, 0xac, 0x8e, 0x9b, 0x7a, 0xc8, + 0xdf, 0x59, 0x86, 0x18, 0x69, 0xb1, 0x19, 0x04, 0xb8, 0x0f, 0x97, 0x6d, 0xdc, 0xeb, 0x38, 0x44, + 0xe0, 0x3a, 0xf2, 0x18, 0xbc, 0x1c, 0xf3, 0x35, 0x83, 0xff, 0xb8, 0x6e, 0x54, 0x96, 0x5d, 0xce, + 0x67, 0x74, 0xf9, 0x08, 0xae, 0x3f, 0xc2, 0xa1, 0x31, 0xf0, 0x55, 0x34, 0xf8, 0x75, 0x18, 0x0f, + 0x74, 0xfb, 0xfd, 0xa0, 0x98, 0x5b, 0xfc, 0x86, 0x8b, 0xd3, 0x91, 0x7f, 0x59, 0x9f, 0x41, 0x39, + 0xad, 0xb9, 0xd3, 0xf9, 0xc0, 0xba, 0x70, 0x2d, 0x9d, 0x80, 0x3c, 0x16, 0x85, 0xad, 0x5f, 0xaa, + 0xce, 0xd9, 0xbd, 0xd5, 0xaf, 0x07, 0xf8, 0x1f, 0x56, 0x55, 0x78, 0x03, 0x7e, 0x8d, 0xee, 0x36, + 0xe9, 0x35, 0xba, 0x4e, 0x20, 0x9a, 0xd7, 0x0a, 0x8c, 0x8b, 0x32, 0x3e, 0xaf, 0xa9, 0x31, 0xc5, + 0xe8, 0x84, 0xb6, 0x05, 0x01, 0x89, 0x66, 0xfd, 0x44, 0x5c, 0x29, 0xe9, 0x18, 0xa7, 0x7b, 0x14, + 0x73, 0x9a, 0x3b, 0x24, 0xcb, 0x83, 0xcb, 0x3a, 0x6d, 0xd5, 0xf0, 0x5f, 0x50, 0x0c, 0xff, 0xcc, + 0xde, 0x7f, 0x4d, 0x37, 0x44, 0xe7, 0xf9, 0xbe, 0x8c, 0x8a, 0xd0, 0x55, 0xd5, 0xbc, 0x3f, 0x95, + 0x7c, 0x45, 0x74, 0x0f, 0x4a, 0xa6, 0x06, 0x15, 0x03, 0x8a, 0xb4, 0x21, 0xf3, 0xc8, 0x11, 0xbf, + 0x91, 0x03, 0x4b, 0xf3, 0x8c, 0xd2, 0xc2, 0x43, 0xc9, 0x4f, 0xe6, 0x2d, 0xc1, 0xd8, 0xa8, 0x2f, + 0x16, 0xf3, 0x8d, 0xef, 0x90, 0x02, 0xf5, 0xe9, 0x16, 0xe3, 0x76, 0xf7, 0x60, 0x6c, 0x0b, 0x7f, + 0x19, 0xb1, 0x1f, 0x26, 0x8b, 0x52, 0x6f, 0xa9, 0x17, 0x58, 0x7d, 0x14, 0x2a, 0xc0, 0x88, 0x20, + 0x74, 0x23, 0xb3, 0x0f, 0xbc, 0xff, 0xfb, 0x50, 0x88, 0xd7, 0xf1, 0xb5, 0x1f, 0x18, 0x29, 0x8b, + 0xbe, 0xae, 0x88, 0x07, 0xc8, 0x0a, 0xec, 0x04, 0xbd, 0xb3, 0xf7, 0x1e, 0x7d, 0x08, 0xb0, 0xeb, + 0x85, 0x4e, 0xa7, 0x46, 0x6d, 0x5c, 0x94, 0xf1, 0xb3, 0x8b, 0xa7, 0x90, 0x94, 0x36, 0xe3, 0xaf, + 0x57, 0x15, 0x60, 0xeb, 0xbb, 0xf4, 0x8b, 0x34, 0x77, 0xfa, 0x74, 0x1f, 0x49, 0x0d, 0x6e, 0xc4, + 0x3c, 0x11, 0xbe, 0x02, 0x91, 0x10, 0xe6, 0xc9, 0xf4, 0xcb, 0x38, 0x5b, 0xdf, 0xcc, 0xaa, 0xff, + 0xfb, 0x1c, 0x73, 0x9f, 0x54, 0x9b, 0xe5, 0x0b, 0x5d, 0x03, 0x88, 0x4a, 0x63, 0xfe, 0xf9, 0x6a, + 0xd8, 0x30, 0xaa, 0xbc, 0x46, 0x61, 0xc3, 0x02, 0x5b, 0x41, 0xfb, 0x66, 0x57, 0xf2, 0x01, 0x75, + 0x3f, 0x90, 0xad, 0x9f, 0x6e, 0xde, 0xdf, 0x13, 0x36, 0x9a, 0x33, 0xe2, 0x1d, 0xc2, 0x9c, 0x16, + 0x59, 0x39, 0x0a, 0x15, 0x1b, 0x45, 0x94, 0x9e, 0xa8, 0x7e, 0xf2, 0xcb, 0xe3, 0xf2, 0x07, 0x67, + 0x79, 0xd5, 0x25, 0x68, 0xee, 0xca, 0xc7, 0x8b, 0xd6, 0x22, 0x0c, 0xd5, 0xec, 0x4d, 0xca, 0xaa, + 0xec, 0x4d, 0xc9, 0xaa, 0xec, 0x4d, 0xeb, 0x5f, 0xe6, 0xa1, 0xcc, 0xde, 0x2e, 0x53, 0xaf, 0x95, + 0x48, 0x57, 0x52, 0xdc, 0x60, 0x4e, 0x6b, 0x21, 0x88, 0xbd, 0x4d, 0xce, 0x9f, 0xe6, 0x6d, 0xf2, + 0xff, 0xff, 0xd5, 0xad, 0xaa, 0x2c, 0x6a, 0x5e, 0x64, 0x18, 0x60, 0xb5, 0x26, 0x0b, 0x41, 0x4a, + 0x13, 0x49, 0x93, 0xc6, 0xf0, 0xd9, 0x4d, 0x1a, 0xd6, 0xdf, 0xcc, 0xc3, 0xb5, 0xf4, 0x19, 0xe4, + 0x2d, 0xad, 0x6a, 0xa1, 0x6e, 0x33, 0xbc, 0x6d, 0xe8, 0x97, 0xa0, 0x84, 0xba, 0x8d, 0x87, 0xb7, + 0x15, 0xef, 0x77, 0x62, 0xb7, 0x54, 0xda, 0xb3, 0x1e, 0x11, 0x28, 0x9c, 0x15, 0x69, 0x81, 0xa5, + 0x78, 0x19, 0xda, 0x87, 0xc5, 0x1d, 0xdf, 0x7d, 0xe9, 0x84, 0xf8, 0x09, 0x7e, 0xbd, 0xe3, 0x75, + 0xdc, 0xd6, 0xeb, 0xb5, 0xae, 0xb3, 0xdf, 0xc1, 0x6d, 0x7e, 0xe3, 0x7e, 0xfb, 0xe4, 0xb8, 0xfc, + 0x46, 0x8f, 0x81, 0x90, 0xcf, 0xac, 0xd9, 0xa3, 0x40, 0x4d, 0xcc, 0xa0, 0x14, 0xa2, 0x69, 0x84, + 0xac, 0x7f, 0x97, 0x83, 0x25, 0x2a, 0x1e, 0xf3, 0x7b, 0x02, 0xd1, 0xf8, 0x57, 0x72, 0xba, 0x54, + 0x07, 0xc8, 0x77, 0x16, 0x75, 0xba, 0xd4, 0xde, 0x37, 0xd9, 0x1a, 0x18, 0xda, 0x80, 0x49, 0xfe, + 0x5b, 0x31, 0x06, 0xcf, 0x2b, 0xec, 0x87, 0x6e, 0x5c, 0x66, 0x0b, 0xa2, 0xdb, 0x94, 0x13, 0x6b, + 0xd2, 0x57, 0xbf, 0x2a, 0xae, 0xf5, 0x8b, 0x3c, 0x2c, 0x37, 0xb0, 0xef, 0x3e, 0x7f, 0x9d, 0x32, + 0x98, 0x6d, 0x98, 0x13, 0x45, 0x74, 0xcc, 0xfa, 0x07, 0xc3, 0x82, 0xd6, 0x88, 0xae, 0x06, 0x04, + 0xa0, 0x29, 0xbf, 0x1f, 0x23, 0xe2, 0x19, 0xdc, 0x29, 0xdf, 0x81, 0xf1, 0x58, 0x3c, 0x00, 0xba, + 0xfe, 0xe2, 0x7b, 0xd3, 0x03, 0x27, 0xca, 0x0f, 0xef, 0x37, 0xd3, 0x2f, 0x1c, 0xb9, 0x5d, 0x60, + 0x50, 0x34, 0x15, 0xfa, 0xf9, 0x91, 0x4f, 0xcf, 0x51, 0x6a, 0x0d, 0x9f, 0xdf, 0xfa, 0x05, 0x3b, + 0xad, 0xa5, 0xea, 0x24, 0x4c, 0x54, 0xe8, 0x75, 0x28, 0x51, 0xc3, 0xff, 0x77, 0x1e, 0xae, 0x8a, + 0x17, 0x39, 0x29, 0xd3, 0xfc, 0x39, 0x2c, 0x8a, 0xa2, 0x4a, 0x8f, 0x1c, 0xff, 0xb8, 0xad, 0xcf, + 0x34, 0x0b, 0x1c, 0x25, 0x66, 0xda, 0xe1, 0x30, 0xd1, 0x64, 0xa7, 0xa1, 0x9f, 0x8f, 0x79, 0xf3, + 0x53, 0x53, 0x74, 0x06, 0x6a, 0x66, 0x54, 0x39, 0xa0, 0x1e, 0xb3, 0x51, 0xe5, 0x86, 0xed, 0x84, + 0x79, 0x74, 0xf8, 0xeb, 0x9a, 0x47, 0xd7, 0x2f, 0xc4, 0x0d, 0xa4, 0xd5, 0x19, 0x98, 0xda, 0xc2, + 0xaf, 0xa2, 0x79, 0xff, 0xad, 0x5c, 0xec, 0x01, 0x21, 0x91, 0x17, 0xd8, 0x4b, 0xc2, 0x5c, 0xf4, + 0xc0, 0x9f, 0x3e, 0x20, 0x54, 0xe5, 0x05, 0x06, 0xba, 0x01, 0x63, 0xcc, 0x01, 0xb7, 0x7d, 0x0a, + 0x4d, 0x5b, 0x3e, 0xad, 0x69, 0x31, 0x14, 0xa6, 0x74, 0x73, 0x7c, 0xeb, 0x09, 0x5c, 0xe7, 0xfe, + 0xdf, 0xfa, 0xe2, 0xd3, 0x86, 0xce, 0x78, 0x18, 0x59, 0x0e, 0x5c, 0x7d, 0x84, 0xe3, 0xac, 0x47, + 0x7b, 0x7a, 0xf4, 0x19, 0xcc, 0x6a, 0xe5, 0x92, 0x22, 0x95, 0x31, 0xe5, 0x1e, 0x92, 0xa4, 0xe3, + 0xd0, 0xd6, 0x35, 0x53, 0x13, 0x6a, 0x67, 0x2d, 0x4c, 0x23, 0x40, 0xf9, 0xd1, 0x9d, 0x70, 0x70, + 0x06, 0xae, 0x77, 0x5b, 0xf9, 0xae, 0x19, 0xc7, 0x63, 0x51, 0x7a, 0xc4, 0x39, 0x2a, 0x6b, 0xad, + 0x69, 0x98, 0xac, 0x79, 0xdd, 0x10, 0x7f, 0x49, 0x05, 0x17, 0x6b, 0x06, 0xa6, 0x44, 0x55, 0x07, + 0x07, 0x81, 0xf5, 0xfb, 0x43, 0x60, 0xf1, 0x89, 0x35, 0xd9, 0x42, 0xc5, 0x7c, 0xec, 0x27, 0x3a, + 0xcb, 0x0f, 0xaa, 0x05, 0xd5, 0xe2, 0x1b, 0xd5, 0xb2, 0x9d, 0x47, 0xa5, 0xb6, 0x56, 0x54, 0xaa, + 0x87, 0xca, 0x8d, 0x8f, 0xfe, 0x87, 0x29, 0x6c, 0x92, 0x7d, 0x6c, 0x34, 0x4a, 0x75, 0x0a, 0x9b, + 0xd4, 0xe8, 0x9a, 0x59, 0xa6, 0xad, 0x4d, 0x03, 0x17, 0x20, 0x90, 0x7c, 0x39, 0x29, 0x6b, 0xb8, + 0x6f, 0x11, 0x2b, 0x68, 0x26, 0x32, 0x33, 0xa8, 0x44, 0xd0, 0x33, 0x7d, 0x2e, 0xf9, 0xf7, 0x28, + 0x7c, 0x30, 0xd4, 0x2a, 0x46, 0xb5, 0xa7, 0x94, 0xe8, 0x89, 0x2e, 0x34, 0x58, 0xc5, 0xbe, 0xfd, + 0x3b, 0xd2, 0x0b, 0x9f, 0x1c, 0xa4, 0x6e, 0x07, 0xf3, 0x27, 0x27, 0x62, 0x59, 0xfa, 0xe6, 0xbb, + 0xec, 0xdc, 0xa9, 0x78, 0x34, 0x0d, 0xcb, 0x89, 0x39, 0x7a, 0xda, 0x05, 0x8a, 0x89, 0xbe, 0x75, + 0x9c, 0x13, 0x6f, 0x0f, 0x12, 0x17, 0xbc, 0x67, 0x95, 0x0b, 0xab, 0xda, 0x9d, 0x6c, 0x3e, 0xe5, + 0x4e, 0x56, 0xbb, 0xc1, 0x0a, 0x07, 0x5c, 0xd2, 0x0e, 0x7d, 0xfd, 0x4b, 0x9d, 0xff, 0x31, 0x02, + 0x17, 0x77, 0x9c, 0x03, 0xb7, 0x4b, 0x78, 0x8f, 0x88, 0x94, 0x8b, 0x2a, 0x89, 0xac, 0x07, 0xd9, + 0x4e, 0xae, 0x86, 0xb4, 0x06, 0x2b, 0x6a, 0x00, 0xf2, 0x7c, 0xda, 0x7b, 0x50, 0x3d, 0xcc, 0xf8, + 0x87, 0x9a, 0x0d, 0x3f, 0x91, 0x81, 0x83, 0x7a, 0xdd, 0x75, 0xbd, 0x76, 0x2c, 0x13, 0x08, 0xb5, + 0x83, 0x27, 0x43, 0xb3, 0x8f, 0x9c, 0x73, 0x68, 0xf6, 0x1f, 0xc0, 0xe4, 0x93, 0xfe, 0xbe, 0xcc, + 0x32, 0x31, 0x3a, 0x30, 0xf4, 0x37, 0x5d, 0x83, 0x17, 0xfd, 0x7d, 0x73, 0x9e, 0x09, 0x95, 0x98, + 0x31, 0x8c, 0xf9, 0xd8, 0xaf, 0x24, 0x8c, 0x79, 0x6a, 0x04, 0xfd, 0xf1, 0x6f, 0x24, 0x82, 0xbe, + 0x21, 0x14, 0xf9, 0xc4, 0xb9, 0x87, 0x22, 0xaf, 0x02, 0x8c, 0xfb, 0x51, 0xd0, 0xe7, 0xe1, 0xc2, + 0x88, 0xf5, 0xaf, 0xc7, 0x60, 0x8e, 0x68, 0xe7, 0x62, 0x87, 0x07, 0xd1, 0xf1, 0x37, 0x25, 0xca, + 0x14, 0x65, 0x93, 0x4b, 0xaa, 0xac, 0xbc, 0x19, 0xcb, 0x5a, 0xa4, 0x21, 0xa0, 0x77, 0xd5, 0xbb, + 0x8d, 0xbc, 0x12, 0x76, 0x32, 0x99, 0x70, 0x46, 0xbd, 0xf4, 0x78, 0x4b, 0x33, 0xad, 0x67, 0xda, + 0x22, 0x1e, 0xc4, 0xed, 0xed, 0x3c, 0x5e, 0x15, 0x3d, 0x18, 0x74, 0xdd, 0x3f, 0x32, 0xc4, 0x3f, + 0x83, 0x51, 0x1a, 0x5c, 0x46, 0x3c, 0xd0, 0x7d, 0x93, 0x33, 0x09, 0xd3, 0x24, 0xb0, 0x30, 0x34, + 0x81, 0x12, 0x28, 0xba, 0x43, 0x0b, 0xd4, 0x18, 0x32, 0x0c, 0x04, 0xed, 0xc2, 0xa5, 0x1d, 0x1f, + 0xb7, 0xb9, 0xcf, 0x69, 0xcf, 0xe7, 0xaa, 0x1c, 0x7b, 0x29, 0x47, 0x43, 0x30, 0xf6, 0x44, 0x75, + 0x13, 0xcb, 0x7a, 0x95, 0xcb, 0x1a, 0xd0, 0xd1, 0x1a, 0xcc, 0xd4, 0xb1, 0xe3, 0xb7, 0x0e, 0x9f, + 0xe0, 0xd7, 0xe4, 0x70, 0x08, 0x8a, 0x63, 0x51, 0xdc, 0xd2, 0x80, 0xd6, 0x90, 0x81, 0xd2, 0x2a, + 0xf5, 0xca, 0x5b, 0x47, 0x42, 0xdf, 0x85, 0xd1, 0xba, 0xe7, 0x87, 0xd5, 0xd7, 0xb1, 0x0c, 0x44, + 0xac, 0xb0, 0x7a, 0x59, 0xc4, 0x6e, 0x0d, 0x3c, 0x3f, 0x6c, 0xee, 0xab, 0xf3, 0xc6, 0xf1, 0xd0, + 0x43, 0x22, 0x79, 0x12, 0x69, 0x58, 0x9a, 0x4d, 0x58, 0x40, 0x0a, 0x2e, 0x5d, 0x52, 0x11, 0xda, + 0x64, 0x3b, 0x89, 0x61, 0xa1, 0xd7, 0x30, 0xa7, 0xef, 0x7f, 0x1e, 0xa9, 0x1b, 0xb4, 0x5c, 0x1e, + 0x26, 0x90, 0xea, 0x6d, 0xde, 0xcb, 0x6b, 0xf1, 0xaf, 0x2c, 0x11, 0xbc, 0xdb, 0xd8, 0x04, 0x7a, + 0x4a, 0x43, 0xe7, 0xb2, 0x99, 0xa9, 0x04, 0x22, 0x20, 0x32, 0x19, 0x04, 0x8d, 0x45, 0xd7, 0xa7, + 0xdf, 0x10, 0x9d, 0x51, 0x27, 0x88, 0xc7, 0x45, 0xb6, 0x13, 0xa8, 0x68, 0x07, 0x2e, 0x3e, 0x0b, + 0xf0, 0x8e, 0x8f, 0x5f, 0xba, 0xf8, 0x95, 0xa0, 0x37, 0x45, 0xe9, 0xd1, 0xe5, 0x26, 0xf4, 0x7a, + 0xac, 0xd6, 0x44, 0x30, 0x89, 0x5c, 0xfa, 0x10, 0x26, 0x95, 0xfd, 0x76, 0xa6, 0xc8, 0xe1, 0x7f, + 0x92, 0x63, 0xa6, 0x3d, 0x65, 0x03, 0x73, 0xcb, 0xc2, 0x36, 0x4c, 0xc8, 0x42, 0xf9, 0xb0, 0x40, + 0x48, 0x27, 0xb1, 0xd3, 0x8d, 0x7d, 0x3e, 0xe2, 0xeb, 0x56, 0x7b, 0x1b, 0xd1, 0xf8, 0x66, 0xcd, + 0x6d, 0xbf, 0x19, 0x3d, 0x41, 0xe4, 0xcf, 0x25, 0x7d, 0xa7, 0xf5, 0x22, 0xb2, 0x77, 0xfe, 0x98, + 0x7c, 0x1f, 0x6a, 0x05, 0x4f, 0x9c, 0xb4, 0xa8, 0x67, 0xbd, 0xe1, 0x95, 0x22, 0xf6, 0xbe, 0x7c, + 0x89, 0xc9, 0x8a, 0xf5, 0x0f, 0x47, 0x45, 0xa0, 0xce, 0xb7, 0xb3, 0x96, 0xcd, 0x5e, 0xd0, 0x19, + 0x7b, 0xf0, 0x5e, 0xf2, 0x0d, 0x18, 0x4d, 0x72, 0x10, 0xbd, 0x01, 0x53, 0xa7, 0x31, 0x7a, 0x0d, + 0xf6, 0x0c, 0x96, 0x6c, 0x7c, 0xe4, 0xbd, 0xc4, 0xe7, 0x4b, 0xf6, 0x87, 0x70, 0x59, 0x27, 0xf8, + 0xac, 0xd7, 0xa6, 0xa1, 0x33, 0xd8, 0xad, 0xa7, 0x31, 0x94, 0x1d, 0x47, 0x60, 0xa1, 0xec, 0x58, + 0x70, 0x23, 0xf2, 0xa7, 0xca, 0x6f, 0x69, 0x9d, 0xe5, 0xc1, 0xb2, 0x4e, 0xbc, 0xd2, 0x6e, 0xef, + 0x38, 0x7e, 0xe8, 0xb6, 0xdc, 0x9e, 0xd3, 0x0d, 0xd1, 0x36, 0x4c, 0x2a, 0x3f, 0x63, 0xb2, 0x8d, + 0x52, 0xc3, 0x56, 0xbf, 0x17, 0x15, 0xa8, 0x32, 0x98, 0x02, 0x67, 0x61, 0x28, 0xc7, 0xa7, 0x87, + 0x4c, 0x99, 0xda, 0x66, 0x15, 0xa6, 0x95, 0x9f, 0x52, 0x55, 0xa0, 0x61, 0x2a, 0x95, 0x16, 0xf4, + 0x09, 0xd3, 0x51, 0xac, 0x16, 0x94, 0x4c, 0x93, 0x46, 0x43, 0x3a, 0xbc, 0x46, 0x6b, 0x51, 0x70, + 0x88, 0xc1, 0xb7, 0xcd, 0xb3, 0x69, 0x81, 0x21, 0xac, 0xbf, 0x3e, 0x0c, 0x4b, 0x7c, 0x31, 0xce, + 0x73, 0xc5, 0xd1, 0x4f, 0x60, 0x52, 0x59, 0x63, 0x3e, 0xe9, 0xd7, 0x84, 0x83, 0x4a, 0xda, 0x5e, + 0x60, 0x32, 0x58, 0x9f, 0x16, 0x34, 0x63, 0xcb, 0x4d, 0x64, 0x30, 0x75, 0xdb, 0x74, 0x60, 0x46, + 0x5f, 0x68, 0x2e, 0x86, 0xde, 0x30, 0x36, 0xa2, 0x83, 0x8a, 0xb8, 0x48, 0xed, 0xa6, 0x71, 0xb9, + 0x69, 0xae, 0x27, 0x7d, 0x13, 0x7d, 0x09, 0x17, 0x13, 0xab, 0xcc, 0xd5, 0xaa, 0x5b, 0xc6, 0x06, + 0x13, 0xd0, 0x2c, 0x16, 0xb7, 0x4f, 0x8b, 0x53, 0x9b, 0x4d, 0x36, 0x82, 0xda, 0x30, 0xa5, 0x2e, + 0x3c, 0x97, 0x93, 0xaf, 0x67, 0x4c, 0x25, 0x03, 0x64, 0x42, 0x11, 0x9f, 0x4b, 0xba, 0xf6, 0x7a, + 0x7a, 0x44, 0x8d, 0x6a, 0x75, 0x1c, 0x46, 0xd9, 0x6f, 0xc2, 0x02, 0x76, 0x7c, 0x1c, 0xe0, 0x6e, + 0x0b, 0xab, 0xbe, 0x46, 0x5f, 0x97, 0x05, 0xfc, 0xdb, 0x1c, 0x14, 0x4d, 0x74, 0xeb, 0xb8, 0xdb, + 0x46, 0x3b, 0x50, 0x88, 0x37, 0xc4, 0x77, 0xb5, 0x25, 0x4e, 0x85, 0xf4, 0x2e, 0x11, 0xb9, 0x39, + 0xd1, 0xcd, 0x2d, 0xb8, 0xa8, 0x94, 0x9d, 0xd1, 0xa9, 0x2b, 0x89, 0xaa, 0xaa, 0xbe, 0xeb, 0xd4, + 0x77, 0x6d, 0xd5, 0x3b, 0x72, 0xdc, 0x2e, 0x11, 0x10, 0x95, 0x30, 0x0e, 0x10, 0x95, 0xf2, 0xb9, + 0x61, 0xea, 0x21, 0x2d, 0x15, 0x0e, 0x8e, 0x12, 0xc4, 0xfa, 0x84, 0x72, 0x70, 0xae, 0x54, 0xb0, + 0x47, 0x32, 0x92, 0xd8, 0x35, 0x18, 0xd9, 0xdd, 0xac, 0xd7, 0x2a, 0xfc, 0xc9, 0x0d, 0x7b, 0xda, + 0xd9, 0x09, 0x9a, 0x2d, 0xc7, 0x66, 0x15, 0xd6, 0xc7, 0x34, 0x7a, 0x1d, 0x8f, 0x7d, 0x26, 0xf1, + 0x6e, 0xc2, 0x18, 0x2f, 0xe2, 0x98, 0xf4, 0x6a, 0xb8, 0xc3, 0xa1, 0x44, 0x9d, 0xb5, 0x23, 0xe4, + 0xeb, 0x0e, 0x76, 0x02, 0xe5, 0x60, 0xfe, 0x80, 0x88, 0xe2, 0xac, 0x8c, 0x9f, 0xcb, 0x33, 0x32, + 0xb4, 0x28, 0x2d, 0x66, 0xea, 0xb2, 0x80, 0xb1, 0xe5, 0x5f, 0xd6, 0x1f, 0xe7, 0x61, 0x4e, 0x04, + 0x70, 0xd1, 0x4c, 0x01, 0x03, 0x43, 0x48, 0x7e, 0x5f, 0x8f, 0x91, 0x53, 0x93, 0x31, 0x72, 0xbe, + 0x46, 0xde, 0x07, 0x1e, 0x5d, 0xe7, 0x94, 0x0f, 0xd2, 0x9e, 0x48, 0xe9, 0x7b, 0x58, 0x93, 0xbe, + 0x4d, 0xe3, 0xd1, 0xa4, 0x6f, 0xba, 0x2c, 0x4c, 0xfa, 0x16, 0x32, 0xf7, 0xd7, 0x11, 0x98, 0x3e, + 0x20, 0x5b, 0x4b, 0x6b, 0xf2, 0xb4, 0x6f, 0x95, 0x36, 0xe9, 0x23, 0xf8, 0xed, 0x8d, 0xd5, 0x1a, + 0xd9, 0xd3, 0xbc, 0xab, 0x62, 0x05, 0xee, 0x52, 0xaf, 0x35, 0x4e, 0x53, 0xdd, 0x98, 0x94, 0xc5, + 0xf2, 0xd0, 0x0f, 0x0a, 0x88, 0xf5, 0x40, 0x3e, 0xa9, 0x37, 0x50, 0x4b, 0x8b, 0x87, 0xba, 0x45, + 0x83, 0x05, 0x3c, 0xa2, 0xeb, 0x75, 0x1e, 0x9d, 0xf8, 0xbd, 0x1c, 0x8b, 0x3e, 0x50, 0xdf, 0x56, + 0x02, 0xb4, 0x77, 0x9f, 0x7b, 0x8a, 0x25, 0x54, 0x69, 0x46, 0xc9, 0xe5, 0x43, 0x2d, 0xa1, 0x34, + 0x91, 0x1f, 0x7f, 0xf2, 0x47, 0xb3, 0xe2, 0xd8, 0x71, 0x68, 0xf4, 0x21, 0x4c, 0x2b, 0x45, 0xf2, + 0x90, 0x66, 0x91, 0xf7, 0x54, 0x74, 0xb7, 0x6d, 0xeb, 0x90, 0xd6, 0x6f, 0xe5, 0x61, 0x29, 0x23, + 0xfb, 0x07, 0xd5, 0x01, 0xa9, 0x02, 0x2f, 0x67, 0x8a, 0xc7, 0x2c, 0xa6, 0xcf, 0x1b, 0x35, 0x1e, + 0x29, 0x01, 0xd1, 0x27, 0x30, 0xa9, 0x26, 0x23, 0xc9, 0x2b, 0x81, 0xb1, 0xcd, 0x09, 0x48, 0x54, + 0x70, 0x14, 0x00, 0x44, 0x3d, 0xe1, 0xef, 0x86, 0xeb, 0xf4, 0x05, 0x6a, 0x94, 0xc9, 0xe4, 0x5c, + 0x52, 0xaa, 0x28, 0xcd, 0x58, 0x7f, 0x39, 0x0f, 0x57, 0x33, 0xe6, 0xa1, 0x8e, 0xc3, 0xff, 0x17, + 0x53, 0x11, 0xcb, 0x2f, 0x33, 0xf4, 0x0d, 0xe5, 0x97, 0xb1, 0x7e, 0x27, 0x0f, 0x0b, 0xcf, 0x7a, + 0x01, 0x75, 0x2e, 0xdd, 0xe8, 0xbe, 0xc4, 0xdd, 0xd0, 0xf3, 0x5f, 0x53, 0xe7, 0x38, 0xf4, 0x2e, + 0x8c, 0xac, 0xe3, 0x4e, 0xc7, 0xe3, 0xc7, 0xda, 0x15, 0x61, 0x9c, 0x8e, 0x43, 0x53, 0xa0, 0xf5, + 0x0b, 0x36, 0x83, 0x46, 0x1f, 0xc2, 0xc4, 0x3a, 0x76, 0xfc, 0x70, 0x1f, 0x3b, 0x42, 0x72, 0xbd, + 0xcc, 0x51, 0x15, 0x14, 0x0e, 0xb0, 0x7e, 0xc1, 0x8e, 0xa0, 0xd1, 0x0a, 0x0c, 0xef, 0x78, 0xdd, + 0x03, 0xf9, 0xfa, 0x2c, 0xa5, 0x41, 0x02, 0xb3, 0x7e, 0xc1, 0xa6, 0xb0, 0xe8, 0x29, 0x4c, 0x57, + 0x0e, 0x70, 0x37, 0x7c, 0x8a, 0x43, 0xa7, 0xed, 0x84, 0x0e, 0x97, 0x70, 0x6e, 0xa6, 0x21, 0x6b, + 0xc0, 0x34, 0x79, 0xac, 0x5a, 0x50, 0x1d, 0x81, 0xa1, 0xa7, 0xc1, 0x81, 0xf5, 0xb3, 0x1c, 0x14, + 0x57, 0xbd, 0x57, 0x5d, 0xe3, 0xc4, 0xbc, 0xaf, 0x4f, 0x8c, 0x70, 0x81, 0x36, 0xc0, 0xc7, 0xa6, + 0xe6, 0x1d, 0x18, 0xde, 0x71, 0xbb, 0x07, 0xb1, 0x43, 0xdd, 0x80, 0x47, 0xa0, 0xe8, 0x08, 0xdd, + 0xee, 0x81, 0xe8, 0xd2, 0x5b, 0xb0, 0x98, 0x02, 0x89, 0x66, 0x24, 0x7b, 0x1b, 0xa6, 0x6c, 0xed, + 0x4d, 0x98, 0x37, 0x4e, 0x5a, 0x02, 0xf0, 0x5f, 0xe5, 0x0c, 0xab, 0xcf, 0xfa, 0x5a, 0x84, 0x31, + 0x11, 0xf8, 0x95, 0x9d, 0x03, 0xe2, 0x27, 0x75, 0xce, 0x14, 0x5f, 0x07, 0x0f, 0xf5, 0x27, 0x3f, + 0x82, 0x86, 0xf2, 0xf8, 0x9e, 0xed, 0xe1, 0x8f, 0xbe, 0xc6, 0x4e, 0x95, 0xb4, 0x48, 0x9b, 0xeb, + 0x5e, 0x10, 0x76, 0xa5, 0xef, 0x80, 0x2d, 0x7f, 0x5b, 0xff, 0x29, 0x4f, 0xc3, 0x06, 0x66, 0x2c, + 0x33, 0x19, 0xf7, 0x76, 0x9d, 0x8f, 0x23, 0xbf, 0x5d, 0x47, 0xcb, 0x30, 0xb1, 0x5d, 0xd7, 0xe2, + 0xda, 0xda, 0x51, 0x01, 0x7a, 0x9b, 0x25, 0x0f, 0xab, 0xf8, 0xad, 0x43, 0x37, 0xc4, 0xad, 0xb0, + 0xef, 0x73, 0xe6, 0x64, 0x27, 0xca, 0x91, 0x05, 0x53, 0x8f, 0x3a, 0xee, 0x7e, 0x4b, 0x10, 0x63, + 0x9d, 0xd3, 0xca, 0xd0, 0x2d, 0x98, 0xe1, 0x99, 0x12, 0x59, 0xd8, 0x5f, 0x9e, 0x7d, 0xcb, 0x8e, + 0x95, 0x92, 0x76, 0x6b, 0x5e, 0x37, 0x74, 0xdc, 0x2e, 0xf6, 0xed, 0x7e, 0x37, 0x74, 0x79, 0xde, + 0xec, 0x09, 0x3b, 0x51, 0x8e, 0xde, 0x81, 0x79, 0x59, 0xb6, 0xed, 0xb7, 0x0e, 0x71, 0x10, 0xfa, + 0x34, 0x44, 0x3a, 0x7d, 0x8c, 0x6d, 0x9b, 0x2b, 0x69, 0x0b, 0x1d, 0xaf, 0xdf, 0x5e, 0xeb, 0xbe, + 0x74, 0x7d, 0x8f, 0xe5, 0xd8, 0x1b, 0xe7, 0x2d, 0xc4, 0xca, 0xad, 0x1d, 0xe3, 0x17, 0xf0, 0x35, + 0x36, 0x87, 0x55, 0x03, 0x94, 0xe4, 0x00, 0xe8, 0xdb, 0x30, 0x51, 0xaf, 0xaf, 0x6b, 0x77, 0x00, + 0x71, 0xb3, 0xbc, 0x1d, 0x41, 0x58, 0xef, 0xc1, 0x82, 0x24, 0xc2, 0x62, 0x5e, 0x2a, 0x2e, 0xe0, + 0x3c, 0x41, 0x8a, 0xf4, 0x3c, 0x8f, 0x0a, 0xac, 0x1f, 0x26, 0xf0, 0xea, 0xfd, 0xa3, 0x23, 0xc7, + 0x7f, 0x8d, 0x2a, 0x3a, 0xde, 0xd0, 0x40, 0x5e, 0x57, 0x1d, 0xfe, 0xf9, 0x71, 0xf9, 0x82, 0x4a, + 0xdc, 0x86, 0x39, 0xed, 0x8b, 0x14, 0x5d, 0x2a, 0xc5, 0x0f, 0x12, 0xe5, 0x53, 0xb9, 0x0a, 0xc0, + 0x83, 0xe2, 0x6e, 0x7a, 0x07, 0xdc, 0x33, 0x58, 0x29, 0xb1, 0x1e, 0xc2, 0x7c, 0x8c, 0x26, 0x17, + 0xac, 0xbe, 0x0d, 0x52, 0x14, 0xa4, 0x44, 0x87, 0xaa, 0x17, 0x7f, 0x79, 0x5c, 0x9e, 0x26, 0xdb, + 0xe2, 0x4e, 0x14, 0xda, 0x4a, 0xfc, 0x65, 0x3d, 0x55, 0x25, 0xf6, 0x4a, 0x47, 0x7b, 0xd3, 0x71, + 0x1f, 0x46, 0x59, 0x49, 0x2c, 0x80, 0x8c, 0x0a, 0xcd, 0x47, 0xcb, 0x01, 0xad, 0x79, 0xea, 0xb7, + 0x45, 0x7f, 0x54, 0x22, 0x0f, 0x61, 0xeb, 0x19, 0x8b, 0x66, 0x18, 0x15, 0xcb, 0x20, 0x35, 0xc3, + 0x95, 0xc8, 0x93, 0x59, 0x98, 0x25, 0x05, 0x5c, 0xd7, 0x7b, 0xd5, 0xc1, 0xed, 0x03, 0x4c, 0x36, + 0x5c, 0x75, 0x8a, 0x9b, 0x25, 0x87, 0x1d, 0x42, 0x80, 0xa2, 0x59, 0x9f, 0xc1, 0x7c, 0xad, 0x83, + 0x1d, 0x3f, 0xde, 0x1e, 0xba, 0x05, 0x63, 0xb4, 0x4c, 0xbf, 0x12, 0x73, 0x48, 0x11, 0xbd, 0x12, + 0xe3, 0x95, 0x44, 0xc8, 0x64, 0x71, 0x3d, 0xd4, 0x21, 0x45, 0xf2, 0xdd, 0x08, 0xfd, 0x1d, 0xf3, + 0x13, 0x32, 0x8c, 0x9e, 0xc1, 0x59, 0x9f, 0xd2, 0x8b, 0x68, 0x53, 0x1a, 0x9e, 0xd3, 0xf9, 0xa1, + 0xfd, 0x39, 0x58, 0xae, 0xf4, 0x7a, 0xb8, 0xdb, 0x8e, 0x10, 0x89, 0x1a, 0x7c, 0x3a, 0xff, 0x5e, + 0x54, 0x81, 0x11, 0x0a, 0x2d, 0x4d, 0x13, 0xbc, 0xbb, 0x86, 0xee, 0x50, 0x38, 0x2e, 0x73, 0xd3, + 0x06, 0x18, 0xa6, 0xd5, 0x86, 0xc5, 0x7a, 0x7f, 0xff, 0xc8, 0x65, 0x19, 0x77, 0xa8, 0x8f, 0xbc, + 0x68, 0x7b, 0x43, 0x04, 0xa0, 0x65, 0x93, 0x71, 0xfb, 0x8e, 0xe0, 0xcd, 0x77, 0xe8, 0xed, 0x1e, + 0xf7, 0x9b, 0x7f, 0x79, 0xff, 0x4e, 0x84, 0x4a, 0x8f, 0x43, 0xd6, 0x0a, 0xad, 0xe6, 0x41, 0x6a, + 0xad, 0x4b, 0x70, 0x51, 0x55, 0xf3, 0xd8, 0x0e, 0x99, 0x87, 0x4b, 0xba, 0xfa, 0xc6, 0x8a, 0xbf, + 0x80, 0x39, 0x66, 0x97, 0x64, 0x11, 0x81, 0x56, 0xa2, 0xe0, 0x37, 0xf9, 0xc6, 0x4a, 0xec, 0x4e, + 0x90, 0x3a, 0x69, 0xca, 0x58, 0x6f, 0x8d, 0x15, 0xe6, 0x4c, 0xf4, 0x72, 0x45, 0x33, 0x12, 0xe4, + 0x1b, 0x2b, 0xd5, 0x31, 0xae, 0x7b, 0x10, 0xea, 0x6c, 0xf9, 0x7f, 0x25, 0xd4, 0x57, 0xa8, 0x37, + 0xea, 0x3a, 0x76, 0xe8, 0x5d, 0xb3, 0xd9, 0xa7, 0x6f, 0x06, 0xf2, 0x6e, 0x5b, 0x1c, 0x3d, 0x6e, + 0xdb, 0xfa, 0xa3, 0x1c, 0xdc, 0x66, 0x66, 0x0b, 0x33, 0x1e, 0xd5, 0x26, 0x52, 0x90, 0xd1, 0x07, + 0xc0, 0x92, 0x63, 0x70, 0xbb, 0xa3, 0xc5, 0x7b, 0x9e, 0x45, 0x89, 0x21, 0xa0, 0x0a, 0x4c, 0xa9, + 0x97, 0xd2, 0xb1, 0x37, 0xc3, 0x29, 0x76, 0x05, 0x7b, 0xf2, 0xe8, 0xb9, 0x23, 0x2f, 0xaa, 0x3f, + 0x86, 0x42, 0x25, 0x08, 0xdc, 0x20, 0x74, 0xa2, 0x4d, 0xf3, 0x26, 0xcc, 0xb6, 0xbc, 0xee, 0x4b, + 0xec, 0x07, 0x0e, 0x17, 0x70, 0x79, 0x6f, 0x67, 0xd4, 0xe2, 0x8d, 0xb6, 0xf5, 0x4f, 0x73, 0x0a, + 0xf6, 0x53, 0x1c, 0xd0, 0x84, 0x4f, 0xa7, 0xc5, 0x46, 0x08, 0x68, 0x82, 0x08, 0x7e, 0xa2, 0xd0, + 0xbf, 0xd1, 0x23, 0x98, 0xe2, 0xce, 0x2d, 0x4d, 0x7a, 0x52, 0x9e, 0xe5, 0x51, 0xce, 0x24, 0xc7, + 0x24, 0x75, 0xe4, 0x30, 0xeb, 0x39, 0xaf, 0x3b, 0x9e, 0xd3, 0xa6, 0xa7, 0xf7, 0x94, 0x2d, 0x7e, + 0x5a, 0x75, 0x1a, 0xd0, 0x33, 0xde, 0xed, 0x88, 0xb9, 0x3d, 0x80, 0xf1, 0x23, 0x5e, 0x26, 0x5d, + 0xea, 0xf9, 0x23, 0xec, 0x18, 0x8e, 0x2d, 0x01, 0xdf, 0x7e, 0x1b, 0x26, 0xb6, 0x7b, 0x98, 0x31, + 0x67, 0x34, 0x0e, 0xc3, 0x1b, 0x5b, 0x1b, 0xbb, 0x2c, 0xe7, 0xd5, 0xce, 0xb3, 0xdd, 0x42, 0x0e, + 0x01, 0x8c, 0xae, 0xae, 0x6d, 0xae, 0xed, 0xae, 0x15, 0xf2, 0x6f, 0x37, 0xd5, 0x0b, 0x7d, 0xb4, + 0x04, 0x8b, 0xab, 0x6b, 0x8d, 0x8d, 0xda, 0x5a, 0x73, 0xf7, 0xfb, 0x3b, 0x6b, 0x4d, 0x3d, 0x82, + 0xca, 0x1c, 0x14, 0xd4, 0xca, 0xdd, 0xed, 0xdd, 0x9d, 0x42, 0x0e, 0x15, 0x61, 0x4e, 0x2d, 0xdd, + 0x5b, 0xab, 0x56, 0x9e, 0xed, 0xae, 0x6f, 0x15, 0x86, 0xac, 0xe1, 0xf1, 0x7c, 0x21, 0xff, 0xf6, + 0x4f, 0xb4, 0xdb, 0x7e, 0xb4, 0x0c, 0x45, 0x0e, 0xfe, 0xac, 0x5e, 0x79, 0x94, 0xde, 0x04, 0xab, + 0x7d, 0xfa, 0xb0, 0x52, 0xc8, 0xa1, 0x2b, 0x70, 0x59, 0x2b, 0xdd, 0xa9, 0xd4, 0xeb, 0x7b, 0xdb, + 0xf6, 0xea, 0xe6, 0x5a, 0xbd, 0x5e, 0xc8, 0xbf, 0x7d, 0x8b, 0x3f, 0xd7, 0x41, 0x33, 0x00, 0xab, + 0x6b, 0xf5, 0xda, 0xda, 0xd6, 0xea, 0xc6, 0xd6, 0xa3, 0xc2, 0x05, 0x34, 0x0d, 0x13, 0x15, 0xf9, + 0x33, 0xb7, 0xf2, 0x0f, 0xfe, 0x4a, 0x0e, 0x26, 0xc9, 0x26, 0x14, 0xf7, 0xb8, 0xbb, 0xec, 0x44, + 0x89, 0x4f, 0x3e, 0x4a, 0x4c, 0x31, 0xdf, 0x8b, 0xa5, 0x1b, 0xd2, 0x02, 0x92, 0xb1, 0x64, 0x1b, + 0x44, 0x0c, 0xa0, 0x21, 0xa6, 0xe3, 0x9b, 0x31, 0x6d, 0xe9, 0x4a, 0x0b, 0x89, 0x2d, 0xb5, 0x46, + 0x98, 0x01, 0xfa, 0x42, 0x91, 0x28, 0xf8, 0xb9, 0xcd, 0x43, 0x65, 0xa7, 0x8a, 0x0f, 0x94, 0x61, + 0x96, 0x32, 0x14, 0x06, 0x0a, 0x70, 0x3b, 0x77, 0x2f, 0x87, 0x6c, 0x6a, 0x2a, 0x8b, 0x89, 0x2c, + 0x92, 0xb2, 0x59, 0x04, 0x2a, 0xa5, 0x54, 0x0b, 0x49, 0xe7, 0x31, 0x4c, 0x13, 0x49, 0x42, 0xd6, + 0xa2, 0xa5, 0x38, 0xbc, 0x22, 0xbc, 0x94, 0x96, 0xcd, 0x95, 0x32, 0xa6, 0xdd, 0x14, 0xed, 0x1f, + 0x99, 0x2a, 0x22, 0xa1, 0xcf, 0xab, 0xb9, 0xc3, 0xbb, 0x2d, 0xcc, 0xee, 0x0a, 0x4b, 0x17, 0x63, + 0xc5, 0x8d, 0xfb, 0xf7, 0x72, 0xa8, 0x4e, 0xdf, 0x3c, 0x69, 0x22, 0x09, 0x12, 0x6e, 0x01, 0x49, + 0x59, 0x85, 0xf5, 0xa6, 0x1c, 0xad, 0xae, 0x59, 0x96, 0xd9, 0x02, 0x94, 0x3c, 0xe9, 0xd1, 0xb5, + 0x68, 0x29, 0xcc, 0x42, 0x40, 0xea, 0xf2, 0xae, 0xc1, 0x0c, 0xf7, 0xe5, 0xe2, 0xb2, 0x07, 0xca, + 0x92, 0x5e, 0x52, 0xc9, 0x3c, 0xa2, 0xf3, 0x24, 0xe5, 0x17, 0x54, 0x52, 0x76, 0x69, 0x4c, 0xa8, + 0x29, 0x2d, 0x19, 0xeb, 0xf8, 0xf8, 0x1e, 0xc2, 0x8c, 0x2e, 0x0a, 0x21, 0xb1, 0x40, 0x46, 0x09, + 0x29, 0xb5, 0x43, 0x4d, 0x58, 0x7c, 0xea, 0xb8, 0x54, 0x3b, 0xe0, 0xa6, 0x6e, 0x61, 0xa8, 0x46, + 0xe5, 0x0c, 0xcb, 0x75, 0x1d, 0x77, 0xdb, 0xa5, 0x41, 0xaf, 0x7d, 0xe9, 0xce, 0xad, 0x8b, 0x13, + 0x5d, 0x37, 0xf4, 0x23, 0x4b, 0x0f, 0xe9, 0x6f, 0xba, 0xbb, 0x29, 0xa5, 0x5d, 0x37, 0xa2, 0xa7, + 0x54, 0xa4, 0x88, 0x51, 0x54, 0xf6, 0xc4, 0x99, 0xc9, 0x15, 0xa9, 0x47, 0x61, 0xe8, 0xc6, 0xef, + 0x0d, 0x03, 0x94, 0x32, 0x71, 0xa9, 0xc4, 0xee, 0xe5, 0xd0, 0x17, 0x60, 0xa5, 0x91, 0xdb, 0x73, + 0xc3, 0x43, 0x7e, 0x6f, 0xbe, 0x64, 0x24, 0xc0, 0x3f, 0x94, 0x0c, 0xea, 0x36, 0xcc, 0x99, 0x6e, + 0x38, 0xe5, 0x84, 0x66, 0x5c, 0x7f, 0xa6, 0xee, 0x02, 0x9b, 0x08, 0x46, 0xed, 0xf4, 0x45, 0xca, + 0xb8, 0x60, 0x4b, 0xa5, 0xf9, 0x09, 0xcc, 0x90, 0x5d, 0xf2, 0x04, 0xe3, 0x5e, 0xa5, 0xe3, 0xbe, + 0xc4, 0x01, 0x12, 0x2f, 0xe1, 0x65, 0x51, 0x1a, 0xee, 0xed, 0x1c, 0xfa, 0x35, 0x98, 0xa4, 0x79, + 0xc5, 0xf9, 0xc3, 0xcd, 0x29, 0x35, 0xd7, 0x78, 0x49, 0xfc, 0xa2, 0x95, 0xf7, 0x72, 0xe8, 0x3b, + 0x30, 0xf6, 0x08, 0x87, 0xd4, 0x2d, 0xeb, 0xba, 0x34, 0xf6, 0xb3, 0x8b, 0xf5, 0x8d, 0xae, 0x74, + 0x81, 0x11, 0x1d, 0x8e, 0xeb, 0x93, 0xe8, 0x2e, 0x00, 0x63, 0x08, 0x94, 0x42, 0xbc, 0xba, 0x94, + 0xe8, 0x36, 0x7a, 0x44, 0x0e, 0xe2, 0x0e, 0x0e, 0xf1, 0x69, 0x9b, 0x4c, 0x9b, 0xa3, 0x4d, 0x98, + 0x91, 0x61, 0x00, 0xb7, 0xa8, 0x5f, 0xaf, 0x15, 0x23, 0x16, 0x9c, 0x81, 0xda, 0x47, 0xe4, 0xab, + 0x60, 0xc6, 0x77, 0x19, 0x25, 0x00, 0xa5, 0xc5, 0x0d, 0x90, 0x93, 0xc8, 0xc0, 0x14, 0x5c, 0x99, + 0x2a, 0x5d, 0xe2, 0xc6, 0x93, 0xa7, 0xc7, 0x70, 0x31, 0x94, 0xd4, 0x76, 0xf5, 0x88, 0x01, 0x11, + 0xcf, 0x4d, 0x0b, 0x74, 0x50, 0xba, 0x9e, 0x01, 0xc1, 0xd8, 0x1d, 0xe5, 0x24, 0xab, 0x44, 0xd7, + 0x64, 0xcd, 0xa8, 0x99, 0x9d, 0x85, 0x39, 0x31, 0x99, 0xa9, 0xba, 0x84, 0x92, 0x55, 0xe4, 0xd4, + 0xd3, 0x5e, 0xaa, 0x47, 0xa7, 0x9e, 0x21, 0x94, 0x40, 0x74, 0xea, 0x19, 0x1f, 0xb7, 0x3f, 0x61, + 0xda, 0xaf, 0x96, 0xdf, 0xb5, 0xb1, 0x82, 0x84, 0x8f, 0x9e, 0x56, 0xc1, 0x3f, 0xec, 0x05, 0x53, + 0x5d, 0xe3, 0xc1, 0xbd, 0x1c, 0x5a, 0x83, 0x4b, 0xd2, 0x0d, 0x3b, 0xaa, 0x42, 0x29, 0x08, 0xa9, + 0x9b, 0xe0, 0x33, 0xb8, 0xc4, 0xb7, 0x94, 0x46, 0xa6, 0x20, 0xb9, 0x03, 0xbf, 0x01, 0x48, 0x25, + 0xf0, 0x18, 0xe6, 0xeb, 0xb1, 0x41, 0xb1, 0xfb, 0xea, 0xcb, 0x3a, 0x09, 0x25, 0xc9, 0x5f, 0x2a, + 0xad, 0x27, 0x80, 0x98, 0x82, 0x29, 0xc8, 0xbd, 0x74, 0xf1, 0x2b, 0x74, 0x25, 0x36, 0x24, 0x52, + 0x48, 0xc1, 0x28, 0x7b, 0x49, 0x9b, 0x22, 0xb4, 0xcb, 0x92, 0x1b, 0xb0, 0x04, 0x42, 0x4e, 0xcf, + 0xd9, 0x77, 0x3b, 0x6e, 0xe8, 0x62, 0xb2, 0xc3, 0x54, 0x04, 0xb5, 0x4a, 0x2c, 0xe3, 0xe5, 0x54, + 0x08, 0xf4, 0x29, 0x4c, 0x3f, 0xc2, 0x61, 0x94, 0xc7, 0x10, 0x2d, 0x26, 0x32, 0x1f, 0xf2, 0xa5, + 0x13, 0x8f, 0x7e, 0xf4, 0xe4, 0x89, 0x1b, 0x50, 0x60, 0xdc, 0x51, 0x21, 0x71, 0x25, 0x41, 0x82, + 0x83, 0x38, 0xbe, 0x73, 0x14, 0xa4, 0xce, 0xd6, 0x5d, 0x66, 0x10, 0x46, 0x62, 0xdb, 0xaa, 0xe2, + 0xd7, 0x25, 0xad, 0x4c, 0x86, 0x5c, 0x99, 0x37, 0x26, 0xf0, 0x43, 0x8a, 0xf0, 0x9b, 0x9a, 0x95, + 0xaf, 0x84, 0xe2, 0x4f, 0x72, 0x1a, 0x0f, 0x90, 0x8c, 0x05, 0x6f, 0x20, 0x7a, 0x4b, 0x3b, 0xb1, + 0xcf, 0x46, 0xf7, 0x53, 0x98, 0x90, 0x99, 0xe0, 0x24, 0x5b, 0x89, 0xe7, 0xd1, 0x2b, 0x15, 0x93, + 0x15, 0x7c, 0xa4, 0x9f, 0xb0, 0xbc, 0x8f, 0x3a, 0x7e, 0x3c, 0x59, 0x5a, 0xea, 0xc4, 0x7e, 0x08, + 0x93, 0x4a, 0x9a, 0x34, 0xb9, 0x91, 0x93, 0xa9, 0xd3, 0x4a, 0xd3, 0x4a, 0xdf, 0x1b, 0x2b, 0xf7, + 0x72, 0xe8, 0x2e, 0x3d, 0x5a, 0xa8, 0x4f, 0xfa, 0x7c, 0x84, 0xa6, 0x24, 0x4e, 0x8a, 0xa1, 0xa0, + 0xf7, 0xe9, 0x43, 0xfc, 0x5a, 0xdf, 0xf7, 0x71, 0x97, 0xe1, 0xa5, 0x49, 0x10, 0x31, 0xc4, 0x4f, + 0x29, 0x33, 0x51, 0x10, 0xd9, 0x05, 0xf0, 0x20, 0x6c, 0x16, 0x92, 0xf1, 0x5e, 0x8e, 0xa8, 0x9f, + 0x22, 0xab, 0x2a, 0x5a, 0xd0, 0xbb, 0x9a, 0x3e, 0xbc, 0x07, 0x00, 0x6c, 0xb2, 0x69, 0x4f, 0xf5, + 0xea, 0xd4, 0xe9, 0x7c, 0x40, 0xce, 0xcb, 0xf6, 0x19, 0x91, 0x3e, 0x15, 0x67, 0x26, 0x45, 0x2a, + 0x6a, 0x4b, 0xa8, 0x4e, 0x67, 0x1a, 0x3e, 0x11, 0x78, 0xb5, 0x64, 0xaf, 0x91, 0xc0, 0x6b, 0xca, + 0x01, 0x9b, 0x4a, 0x67, 0x03, 0x0a, 0x95, 0x16, 0xe5, 0xe3, 0x32, 0x19, 0x95, 0xd4, 0x36, 0xe2, + 0x15, 0x82, 0xd6, 0x7c, 0x3c, 0xb7, 0xd5, 0x26, 0x76, 0x68, 0x6c, 0x82, 0x45, 0x29, 0x13, 0xc4, + 0xaa, 0xcc, 0x18, 0x19, 0xda, 0xc5, 0x5c, 0x8d, 0xe8, 0x43, 0x9d, 0xaf, 0x47, 0xe6, 0x23, 0xca, + 0xcb, 0x94, 0x44, 0x5d, 0x0b, 0x71, 0x7c, 0xa9, 0x87, 0x09, 0xe7, 0x1b, 0x09, 0x5a, 0x81, 0x59, + 0xfe, 0x12, 0x5a, 0x4e, 0x4b, 0x1a, 0x76, 0x5a, 0xf3, 0xef, 0xc3, 0xcc, 0x1a, 0xe1, 0xf5, 0xfd, + 0xb6, 0xcb, 0xe2, 0xb1, 0x20, 0x3d, 0xc0, 0x46, 0x2a, 0xe2, 0xba, 0xc8, 0x13, 0xa9, 0x64, 0xb0, + 0x92, 0x5f, 0x69, 0x32, 0x49, 0x58, 0x69, 0x4e, 0x90, 0x55, 0x93, 0x5d, 0x71, 0x3d, 0x79, 0x31, + 0x25, 0x67, 0x14, 0xba, 0xa9, 0xe9, 0x7e, 0x69, 0x89, 0x9f, 0x0c, 0xd2, 0xde, 0xe7, 0x4a, 0x14, + 0xfd, 0x14, 0x9a, 0xd9, 0xc9, 0xa4, 0x52, 0xc7, 0x2d, 0x23, 0x28, 0x18, 0x93, 0x3e, 0xa1, 0xb7, + 0x74, 0xea, 0x19, 0x89, 0xa1, 0x52, 0x5b, 0xa0, 0xba, 0xb5, 0x9e, 0x93, 0x08, 0x5d, 0xcd, 0x4e, + 0x9d, 0xa4, 0xe8, 0xd6, 0x29, 0xc9, 0x8c, 0x1e, 0xd3, 0x6d, 0x16, 0x85, 0xda, 0x47, 0xaa, 0xa6, + 0x1a, 0xcf, 0x34, 0x20, 0x45, 0x28, 0x73, 0x62, 0xa2, 0x47, 0x94, 0x5d, 0x2a, 0x61, 0xfb, 0x53, + 0x19, 0xde, 0x15, 0x13, 0x9d, 0x40, 0x39, 0x0b, 0x67, 0x63, 0x29, 0x7e, 0xa4, 0x79, 0xc4, 0x9c, + 0x64, 0xa8, 0x74, 0x35, 0xad, 0x9a, 0x53, 0xac, 0x8b, 0x4c, 0xaf, 0xca, 0x48, 0xaf, 0x6a, 0x27, + 0x54, 0x72, 0xb0, 0xe5, 0xd4, 0x7a, 0x39, 0x77, 0x85, 0x78, 0x4a, 0x06, 0x49, 0x34, 0x25, 0x57, + 0x43, 0x06, 0x4b, 0x9c, 0x53, 0xb7, 0xc6, 0xc0, 0x19, 0x4c, 0xa3, 0xb3, 0x0b, 0xf3, 0xc6, 0x0c, + 0x0a, 0x52, 0x8c, 0xc8, 0xca, 0xaf, 0x90, 0x4a, 0x15, 0xc3, 0x82, 0x39, 0x89, 0x0a, 0x7a, 0x43, + 0x57, 0xfd, 0xcd, 0x29, 0x25, 0x4a, 0x37, 0x07, 0x40, 0xf1, 0x09, 0xfd, 0x82, 0x1e, 0x9b, 0x89, + 0x36, 0xae, 0x2b, 0xc6, 0x80, 0x94, 0x06, 0xac, 0x2c, 0x10, 0xb9, 0x07, 0xe6, 0x4c, 0x49, 0x9c, + 0x52, 0xa7, 0xf8, 0x46, 0x3a, 0xcd, 0x68, 0x63, 0x35, 0x44, 0xdc, 0x82, 0xd4, 0x99, 0xc9, 0x4c, + 0xb6, 0x91, 0xa1, 0x4d, 0x96, 0xe4, 0x7e, 0x38, 0x7d, 0x97, 0xd3, 0x2d, 0x43, 0x73, 0xa6, 0x14, + 0x2f, 0x71, 0xc3, 0x8d, 0x29, 0x83, 0x87, 0x9c, 0x86, 0xcc, 0x1c, 0x31, 0x0d, 0x66, 0xc4, 0xd1, + 0xa9, 0xab, 0x46, 0x1c, 0x23, 0xe9, 0x6b, 0xe9, 0x00, 0xd1, 0x8e, 0x30, 0xe4, 0xaa, 0x92, 0x3b, + 0x22, 0x3d, 0x6b, 0x96, 0xdc, 0x11, 0x59, 0xa9, 0xae, 0x6c, 0xf1, 0xd1, 0xa5, 0x4c, 0x4b, 0x46, + 0x62, 0x93, 0x0c, 0x95, 0xab, 0x18, 0x2d, 0x5c, 0xac, 0xdb, 0x67, 0x5d, 0xb6, 0x2f, 0xe0, 0x72, + 0x6a, 0x12, 0x13, 0xf4, 0x66, 0xe2, 0x83, 0x4e, 0x99, 0x89, 0xf4, 0x9e, 0x4e, 0x6b, 0xf9, 0x47, + 0xa4, 0x15, 0x2b, 0x96, 0xea, 0x24, 0xc1, 0xfa, 0x0d, 0x79, 0x50, 0x18, 0xeb, 0x57, 0x72, 0x99, + 0x9c, 0x86, 0xf5, 0x9b, 0x52, 0x9f, 0x48, 0x9e, 0xaa, 0xf4, 0x4b, 0x88, 0x74, 0xf1, 0x8a, 0xb3, + 0xf0, 0xd4, 0xd3, 0x74, 0x2d, 0x8d, 0xce, 0x2a, 0x55, 0x39, 0x44, 0x6a, 0x13, 0x74, 0x59, 0x9b, + 0x26, 0xed, 0xb8, 0x2d, 0x69, 0x83, 0xd3, 0x4f, 0xda, 0x1a, 0x35, 0x17, 0xcb, 0x54, 0x2a, 0xa9, + 0xbd, 0x58, 0x4a, 0xd2, 0xd0, 0x4c, 0xc5, 0x72, 0x16, 0x58, 0x6f, 0x96, 0xe3, 0x93, 0xa3, 0x75, + 0x28, 0x7d, 0x48, 0x48, 0x9d, 0x9a, 0x01, 0x5d, 0x4a, 0x17, 0x75, 0x2f, 0x31, 0xe5, 0x81, 0x05, + 0x16, 0x13, 0x0f, 0x12, 0x17, 0xa4, 0xdd, 0x4b, 0x29, 0xcd, 0x30, 0x73, 0xec, 0x50, 0x57, 0x25, + 0x43, 0x56, 0x18, 0xc9, 0x43, 0x33, 0x93, 0xc6, 0x18, 0xc4, 0x3c, 0xc9, 0x95, 0x53, 0x29, 0x66, + 0xa6, 0x89, 0x49, 0xed, 0xe9, 0x8f, 0x15, 0xae, 0x9c, 0xc8, 0xfd, 0x82, 0x6e, 0xc7, 0x65, 0xbc, + 0xb4, 0xf4, 0x30, 0x19, 0x5c, 0x7f, 0xce, 0x94, 0x36, 0x46, 0xb1, 0xdd, 0xa6, 0xe6, 0x94, 0x31, + 0xcc, 0x82, 0x64, 0x6f, 0x29, 0xd4, 0x32, 0x92, 0xc8, 0xa4, 0xf6, 0xf0, 0x07, 0x0a, 0x7b, 0x8b, + 0x25, 0x7b, 0x91, 0x46, 0x85, 0x01, 0xd9, 0x60, 0x52, 0x69, 0x6f, 0x51, 0xe7, 0xb6, 0x64, 0xa6, + 0x16, 0x29, 0xbb, 0x64, 0xe5, 0x71, 0x31, 0x9a, 0x76, 0xe7, 0x93, 0x43, 0x24, 0xf4, 0x16, 0x62, + 0x86, 0xd9, 0x41, 0x1d, 0x93, 0x7c, 0xd8, 0x90, 0xe1, 0x25, 0xc6, 0x87, 0xd3, 0x73, 0xc0, 0x64, + 0x68, 0x4c, 0xb3, 0x75, 0xf7, 0xa0, 0xab, 0xa4, 0x5b, 0x91, 0xfa, 0x52, 0x32, 0x65, 0x8c, 0x64, + 0x31, 0xa6, 0xec, 0x2c, 0xdb, 0x91, 0xd3, 0xbb, 0x9a, 0x38, 0x03, 0x95, 0xd2, 0xf3, 0x85, 0x48, + 0x76, 0x63, 0xcc, 0xb4, 0xa1, 0x10, 0x54, 0xb3, 0x56, 0x48, 0x82, 0x86, 0x04, 0x1a, 0x92, 0xa0, + 0x31, 0xcd, 0x05, 0x33, 0xc1, 0xd8, 0x5e, 0x07, 0xab, 0x26, 0x18, 0x25, 0xe7, 0x44, 0xcc, 0x16, + 0x82, 0x3e, 0xa6, 0x96, 0x90, 0x6c, 0xf3, 0xc9, 0xa2, 0x4e, 0x49, 0xbd, 0xc5, 0xe7, 0x97, 0x01, + 0xb4, 0x41, 0x9d, 0xf2, 0x60, 0xe3, 0x06, 0x45, 0xd2, 0x8d, 0x1b, 0x6a, 0x47, 0xd3, 0xed, 0xa4, + 0x53, 0x6a, 0xa4, 0x63, 0x39, 0x57, 0x86, 0x70, 0xec, 0x72, 0xae, 0x4c, 0x41, 0xce, 0xa9, 0x0e, + 0xbc, 0x2b, 0x34, 0xf9, 0x88, 0xde, 0x95, 0xcc, 0x28, 0xe5, 0xa5, 0xab, 0xd9, 0xa1, 0xbd, 0xf9, + 0x3d, 0x5e, 0x21, 0x1e, 0x8c, 0x19, 0x99, 0x82, 0xcc, 0x2b, 0x31, 0xae, 0xa5, 0x36, 0x94, 0x1a, + 0xc5, 0x79, 0x47, 0x18, 0xab, 0x75, 0xba, 0x29, 0xa1, 0xc6, 0x55, 0xd2, 0xd9, 0x02, 0x4a, 0x14, + 0x97, 0x59, 0xd5, 0x4d, 0x13, 0x71, 0x9f, 0x55, 0x01, 0xc5, 0x10, 0xca, 0xd9, 0x15, 0x8f, 0x24, + 0xcd, 0x89, 0x4b, 0xde, 0xd2, 0x75, 0xbd, 0x8c, 0x18, 0x1f, 0x03, 0x6f, 0x4a, 0xd1, 0x8f, 0x44, + 0x9a, 0xd3, 0x64, 0x58, 0xff, 0x9b, 0x31, 0xb3, 0xab, 0x39, 0x2a, 0x44, 0x29, 0x2b, 0x6b, 0x00, + 0x7a, 0x4a, 0xaf, 0xd8, 0xb7, 0x37, 0x56, 0x6b, 0xdc, 0x3d, 0xcc, 0xf3, 0x13, 0xd7, 0x56, 0x7b, + 0x6e, 0x78, 0xc8, 0xf2, 0x5c, 0x28, 0xdc, 0x87, 0x81, 0x68, 0x88, 0x8d, 0x07, 0xa8, 0x4e, 0x25, + 0x77, 0xad, 0xd4, 0x70, 0x73, 0x65, 0x20, 0x58, 0x32, 0x13, 0xa4, 0x99, 0xba, 0xa8, 0x60, 0x40, + 0x3e, 0x3c, 0xbd, 0x9b, 0x29, 0x7d, 0xc8, 0x92, 0x2f, 0xd8, 0xb6, 0x31, 0x93, 0x39, 0x2d, 0xfb, + 0x7e, 0x04, 0xf3, 0x6c, 0xc2, 0x63, 0x2f, 0x52, 0xb4, 0xfe, 0x28, 0xe5, 0xa5, 0x94, 0x72, 0xb4, + 0x45, 0x3d, 0x37, 0xe2, 0xa5, 0x8a, 0x16, 0x63, 0x7e, 0xf2, 0x92, 0x4a, 0x8f, 0x2d, 0x25, 0x11, + 0xdb, 0xbf, 0xd2, 0x52, 0x6a, 0x88, 0x8d, 0x15, 0xbe, 0x94, 0x5a, 0xe9, 0xd9, 0x96, 0x32, 0x46, + 0x50, 0x5f, 0x4a, 0xbd, 0x9b, 0x29, 0x7d, 0x18, 0xbc, 0x94, 0x66, 0x32, 0x67, 0x5e, 0xca, 0xd8, + 0x73, 0x20, 0xad, 0x3f, 0xa6, 0xa5, 0x8c, 0xc3, 0xb3, 0xa5, 0x8c, 0x97, 0xc6, 0x14, 0xd2, 0x8c, + 0xa5, 0x8c, 0x63, 0x7e, 0x8f, 0xd2, 0x63, 0xef, 0x8d, 0xce, 0xb4, 0x98, 0x22, 0x76, 0x45, 0x0c, + 0xb5, 0xf1, 0x00, 0xed, 0x51, 0x6b, 0x48, 0xac, 0xfc, 0x74, 0x0b, 0xba, 0x9c, 0x46, 0x94, 0x2e, + 0xe9, 0x86, 0x90, 0xb3, 0xe2, 0xdd, 0x4d, 0xed, 0x4b, 0xd6, 0x7a, 0xb0, 0x65, 0x8d, 0x93, 0x3a, + 0xeb, 0xc2, 0x3e, 0x15, 0x4c, 0x33, 0xf1, 0x64, 0x2b, 0xd6, 0x2b, 0x75, 0x71, 0x53, 0x6b, 0xb8, + 0x8b, 0x59, 0xb2, 0x5c, 0xb1, 0x13, 0xa5, 0xbd, 0x0d, 0x1b, 0x48, 0x35, 0xf1, 0x06, 0x4c, 0xa5, + 0x9a, 0xf6, 0x40, 0x4c, 0x52, 0x4d, 0x62, 0xaf, 0xd2, 0xcf, 0x76, 0xd7, 0x27, 0x6a, 0x52, 0x3b, + 0xa9, 0x43, 0xe9, 0xf3, 0x27, 0x6e, 0x34, 0x75, 0xf0, 0xc6, 0x0a, 0xda, 0xa0, 0x1b, 0x50, 0x2f, + 0xce, 0x52, 0x32, 0xcd, 0x64, 0xe8, 0xfe, 0x58, 0x97, 0xae, 0xb5, 0x7a, 0x9f, 0xd2, 0xda, 0x4e, + 0xef, 0x94, 0xd4, 0xc0, 0x4f, 0x39, 0xba, 0xb4, 0xdd, 0xc1, 0xa4, 0x40, 0xa6, 0xf0, 0x0e, 0x9a, + 0x99, 0xb8, 0xb3, 0x2f, 0xfa, 0x2e, 0x4c, 0x08, 0xe4, 0xc1, 0x13, 0x12, 0xc7, 0xa6, 0x13, 0xf2, + 0x29, 0x4c, 0x2a, 0xbe, 0xc6, 0x28, 0xad, 0xa5, 0x0c, 0x91, 0x72, 0x52, 0xf1, 0x84, 0x3e, 0x3b, + 0xfe, 0x2a, 0x4c, 0x6b, 0x9e, 0xd4, 0x52, 0x10, 0x32, 0xf9, 0x57, 0x67, 0x51, 0xd1, 0x3c, 0xa6, + 0x25, 0x15, 0x93, 0x1f, 0x75, 0xb6, 0x50, 0xa6, 0xbc, 0x0a, 0x55, 0x84, 0xb2, 0xe4, 0xf3, 0x54, + 0x45, 0x28, 0x33, 0x3d, 0x24, 0xfd, 0x0e, 0x4c, 0xf2, 0xed, 0x91, 0xb9, 0xb2, 0xe9, 0xda, 0xf2, + 0xbc, 0xe2, 0x33, 0xd8, 0x6f, 0xbb, 0x61, 0xcd, 0xeb, 0x3e, 0x77, 0x0f, 0x06, 0x2e, 0x72, 0x12, + 0xa5, 0xb1, 0x82, 0x1a, 0x34, 0xc6, 0xb6, 0x88, 0x7c, 0x8e, 0xc3, 0x57, 0x9e, 0xff, 0xc2, 0xed, + 0x1e, 0x0c, 0x20, 0x79, 0x4d, 0x27, 0x19, 0xc7, 0x63, 0x74, 0xeb, 0xe9, 0x74, 0x07, 0xe2, 0x67, + 0x68, 0xcb, 0xcb, 0xf4, 0xde, 0xfe, 0xac, 0x3d, 0x4e, 0xbf, 0x39, 0xb8, 0x1c, 0x79, 0xdb, 0xd9, + 0xb8, 0xe5, 0xf9, 0xed, 0xc1, 0xc4, 0xca, 0xba, 0x6f, 0x5b, 0x0c, 0xad, 0xb1, 0x42, 0xa8, 0xd6, + 0x53, 0xa9, 0x0e, 0xc2, 0xce, 0x38, 0x2d, 0x96, 0xe8, 0xd8, 0xcf, 0xd8, 0xdb, 0xf4, 0x2f, 0x83, + 0x70, 0x60, 0xc2, 0xe9, 0x77, 0x7c, 0xfc, 0x1c, 0xfb, 0xd4, 0x65, 0x72, 0x90, 0xb3, 0xa0, 0x0e, + 0xde, 0x58, 0x21, 0x54, 0xea, 0x09, 0x2a, 0x69, 0xd0, 0x59, 0x82, 0x12, 0x1d, 0xda, 0x29, 0x7b, + 0x93, 0x46, 0xe6, 0x03, 0x6a, 0xb3, 0x7c, 0xb6, 0x31, 0x60, 0x46, 0x84, 0x13, 0xaf, 0x00, 0x6c, + 0xdc, 0x27, 0x98, 0x75, 0x05, 0x33, 0x09, 0x91, 0xda, 0xe6, 0x77, 0x85, 0x71, 0x72, 0x60, 0xb3, + 0xe9, 0xde, 0x08, 0x13, 0x32, 0xff, 0x07, 0x52, 0xd4, 0x7a, 0x2d, 0xbb, 0x45, 0x69, 0x5a, 0x75, + 0x19, 0x0c, 0x50, 0x85, 0x49, 0xd1, 0x6a, 0x1e, 0x0c, 0xe5, 0x5e, 0xd4, 0x98, 0x20, 0x23, 0x4e, + 0x82, 0x99, 0x25, 0x36, 0xbd, 0xd6, 0x0b, 0xd5, 0x2c, 0xa1, 0x24, 0x56, 0x28, 0xe9, 0x69, 0x0f, + 0xf8, 0x81, 0x44, 0x73, 0x1f, 0xa8, 0x0e, 0x1a, 0x6a, 0x6a, 0x05, 0xd5, 0x2c, 0xa1, 0x27, 0x81, + 0x90, 0x66, 0x09, 0xda, 0xa0, 0x4e, 0x79, 0xb0, 0x59, 0x82, 0x22, 0xe9, 0x66, 0x09, 0xb5, 0xa3, + 0xe9, 0xec, 0x02, 0x25, 0xb3, 0x40, 0x48, 0x81, 0x37, 0x35, 0x41, 0x44, 0x86, 0xef, 0xc5, 0x25, + 0x43, 0xe2, 0x1a, 0xa9, 0xee, 0xa7, 0x27, 0xb5, 0x29, 0xe9, 0x8e, 0x04, 0xf7, 0x72, 0x68, 0x8b, + 0x26, 0x42, 0xe7, 0x0c, 0xcc, 0xc6, 0x41, 0xe8, 0xbb, 0xf4, 0xd1, 0x53, 0xfa, 0x69, 0x2d, 0xe4, + 0x5b, 0x03, 0x4e, 0xe3, 0x1d, 0x42, 0xaf, 0x6e, 0xa6, 0x97, 0x89, 0x97, 0x61, 0xd1, 0xe1, 0xe6, + 0xbf, 0xb3, 0x74, 0x31, 0x7d, 0x8b, 0x8f, 0xb1, 0x5b, 0xef, 0x74, 0xd4, 0x42, 0x14, 0xea, 0x90, + 0x4b, 0xec, 0x77, 0x60, 0x94, 0x21, 0xa5, 0x9e, 0x91, 0x53, 0x2a, 0x0e, 0xba, 0x2f, 0x5c, 0xb4, + 0x08, 0x8a, 0x56, 0x95, 0xda, 0xaf, 0xfb, 0x30, 0xc1, 0x6c, 0xf9, 0xa7, 0x47, 0xf9, 0x58, 0x38, + 0x72, 0x65, 0x75, 0x2c, 0xdd, 0xb7, 0x71, 0x5a, 0xbd, 0xf0, 0x3e, 0xfb, 0x44, 0x7e, 0x87, 0xde, + 0xa7, 0x08, 0xb3, 0x65, 0x3a, 0xfe, 0x7c, 0x2c, 0x38, 0x20, 0x9f, 0x52, 0xc6, 0x20, 0x65, 0x06, + 0xa8, 0xb4, 0xee, 0x5f, 0x4c, 0x60, 0xa3, 0x8f, 0xc5, 0xf3, 0x01, 0x89, 0x9c, 0x04, 0xca, 0x98, + 0xb3, 0x19, 0x36, 0xcd, 0x5f, 0x05, 0x59, 0x32, 0xd8, 0x81, 0xdd, 0x3e, 0xcd, 0xbd, 0xcf, 0xe0, + 0xa9, 0x4b, 0xa3, 0xb2, 0x4d, 0x05, 0xaf, 0x44, 0xd8, 0xca, 0x74, 0x42, 0x57, 0xd3, 0x23, 0x5d, + 0xd2, 0xc5, 0x78, 0x4c, 0x15, 0xab, 0x64, 0x36, 0xaf, 0xb4, 0xe1, 0x65, 0x44, 0xce, 0x8c, 0x34, + 0xc9, 0x24, 0xb9, 0x0c, 0xb4, 0x2c, 0xc5, 0x94, 0x2d, 0xd8, 0xf9, 0x90, 0xdb, 0x10, 0x0e, 0x48, + 0xa7, 0x1f, 0x6c, 0x86, 0x10, 0x64, 0xb8, 0x69, 0x1a, 0xb8, 0x16, 0x69, 0xe4, 0x7e, 0x44, 0xe5, + 0x3f, 0x73, 0x0a, 0x9f, 0x54, 0x62, 0xb7, 0x95, 0xcb, 0xca, 0xec, 0xe4, 0x3f, 0x2f, 0xe8, 0xbb, + 0x0c, 0x73, 0x60, 0xcf, 0x5b, 0x03, 0xa8, 0x88, 0x99, 0x78, 0x73, 0x20, 0x9c, 0xbc, 0xb7, 0x58, + 0x62, 0x27, 0xac, 0xb9, 0xbd, 0x01, 0x81, 0x4a, 0x0d, 0x57, 0x49, 0x29, 0xf9, 0x71, 0x04, 0x41, + 0xdd, 0xbb, 0x2b, 0x73, 0x0c, 0x69, 0xd3, 0xff, 0x3d, 0x28, 0x47, 0x37, 0xb2, 0x67, 0x5b, 0x84, + 0x74, 0x89, 0x1e, 0x25, 0xb3, 0x06, 0xa1, 0xac, 0x00, 0x92, 0xa5, 0xeb, 0x69, 0x33, 0x1c, 0x28, + 0x57, 0xfd, 0xdc, 0x97, 0x24, 0x16, 0xe2, 0x36, 0x2d, 0x58, 0x6e, 0x86, 0xed, 0x88, 0x3f, 0x54, + 0x39, 0x17, 0x42, 0xc9, 0xd5, 0x3e, 0x3b, 0x21, 0x79, 0x61, 0x1a, 0x23, 0x64, 0x65, 0x2c, 0xef, + 0x59, 0xfc, 0x41, 0xe2, 0x4b, 0x71, 0xd6, 0x05, 0x75, 0xa2, 0xc7, 0x19, 0xc9, 0xd4, 0x46, 0x52, + 0x96, 0x4b, 0x4d, 0xb3, 0x24, 0x57, 0x37, 0x23, 0x2f, 0x52, 0x8d, 0x7c, 0xa6, 0xac, 0x09, 0x2d, + 0xaf, 0x4a, 0xcd, 0xde, 0x8c, 0xac, 0x0e, 0x86, 0x84, 0x2b, 0x25, 0x10, 0x95, 0xf6, 0x26, 0xaa, + 0x43, 0x89, 0x6d, 0x11, 0xd3, 0x03, 0x76, 0xe9, 0x51, 0x6f, 0xaa, 0xcc, 0xd0, 0x2e, 0xea, 0x50, + 0x62, 0xdb, 0xe5, 0x3c, 0x89, 0x36, 0x69, 0x0e, 0x3d, 0x23, 0xc5, 0x9b, 0xca, 0xb3, 0xc4, 0xf4, + 0xb0, 0x00, 0xa5, 0xec, 0x86, 0xd1, 0x0f, 0x61, 0xde, 0x18, 0x17, 0x40, 0xde, 0x69, 0x67, 0x45, + 0x0d, 0x18, 0x44, 0xfc, 0x05, 0x14, 0xd3, 0xd2, 0xa6, 0x44, 0x1e, 0xfe, 0xd9, 0x99, 0x69, 0x24, + 0x4f, 0x1d, 0x98, 0x7f, 0x65, 0x0b, 0xe6, 0x4c, 0xa9, 0x48, 0xe4, 0xc7, 0x91, 0x91, 0xa7, 0xc4, + 0xf8, 0x8c, 0x60, 0x07, 0xe6, 0x8d, 0xe9, 0x40, 0xe4, 0xcc, 0x64, 0x25, 0x0b, 0x31, 0x52, 0xfc, + 0x1c, 0x16, 0x53, 0x72, 0x5f, 0x44, 0x17, 0x6f, 0x99, 0xb9, 0x31, 0x32, 0x1c, 0x00, 0x4a, 0xe9, + 0x69, 0x15, 0xa4, 0xdf, 0xc7, 0xc0, 0xcc, 0x0b, 0x25, 0x63, 0xae, 0x19, 0xb4, 0x4b, 0x37, 0xa1, + 0x29, 0xcf, 0x82, 0xba, 0x09, 0x33, 0xf2, 0x30, 0xa4, 0x3c, 0xff, 0x58, 0x4c, 0x49, 0xad, 0x90, + 0x41, 0xf5, 0x14, 0xbd, 0xdd, 0x12, 0xfc, 0x5f, 0x8f, 0xb5, 0x1f, 0xf3, 0x25, 0x34, 0x06, 0xe2, + 0x37, 0xf6, 0x53, 0x79, 0x6e, 0xdc, 0xe9, 0x64, 0x88, 0x41, 0x48, 0x7d, 0x6f, 0x4c, 0x20, 0x1b, + 0xf7, 0x89, 0x12, 0xa1, 0xe2, 0x66, 0x71, 0xd4, 0x04, 0x32, 0x15, 0x3c, 0x3f, 0x82, 0xa9, 0xba, + 0xda, 0xb8, 0xa1, 0x91, 0xd4, 0x4d, 0x21, 0xbd, 0xec, 0x07, 0xf7, 0x7d, 0xe0, 0xad, 0x58, 0xa5, + 0xd3, 0x39, 0xd5, 0x28, 0x52, 0x6d, 0xb2, 0x5a, 0x64, 0x63, 0xc9, 0xa9, 0x4d, 0x01, 0xbb, 0xa5, + 0x4d, 0xd6, 0x1c, 0x0c, 0x79, 0x8d, 0x4e, 0x69, 0x14, 0x17, 0x32, 0x43, 0x07, 0x97, 0x9b, 0xc8, + 0x10, 0x7e, 0xf2, 0x89, 0xfa, 0x10, 0x9c, 0x45, 0x93, 0xcc, 0x30, 0x22, 0xc6, 0x1f, 0x80, 0xc7, + 0xc2, 0x4f, 0x36, 0xa0, 0x28, 0xe2, 0xba, 0xb1, 0xc8, 0x6a, 0x51, 0x24, 0xa9, 0xc8, 0x35, 0x2a, + 0x3d, 0xf0, 0x5b, 0xc6, 0xbc, 0x15, 0xe2, 0x31, 0x53, 0xa4, 0xe5, 0x28, 0x25, 0x98, 0x4a, 0xc6, + 0x6e, 0x80, 0x28, 0x32, 0x8a, 0xb4, 0xcf, 0x24, 0x82, 0xa5, 0x94, 0x2e, 0x1b, 0x6a, 0xa4, 0x64, + 0x35, 0xa5, 0xc6, 0x51, 0x91, 0x9e, 0x23, 0x86, 0xe0, 0x2a, 0xa5, 0x25, 0x63, 0x1d, 0x27, 0x14, + 0xc2, 0x52, 0x46, 0x0e, 0x41, 0x29, 0xad, 0x0e, 0xce, 0x75, 0x58, 0x7a, 0xfb, 0x34, 0xa0, 0xbc, + 0x55, 0x2c, 0xe3, 0x34, 0x26, 0xa1, 0xd0, 0x9b, 0x06, 0x57, 0x5f, 0x53, 0x7a, 0xbe, 0xd2, 0xa0, + 0xf4, 0x85, 0x68, 0x0f, 0x96, 0x63, 0xae, 0xc8, 0x7a, 0x4b, 0x83, 0x08, 0xa4, 0xae, 0xe0, 0x1e, + 0x2c, 0xf3, 0xb7, 0xd1, 0xe7, 0x4c, 0x78, 0x1f, 0x96, 0xb3, 0x12, 0x13, 0xa2, 0xb7, 0xcd, 0xee, + 0xc6, 0xc6, 0xe9, 0x49, 0x17, 0x5d, 0xaf, 0x25, 0xdd, 0x8e, 0x63, 0xeb, 0x7e, 0x56, 0xb6, 0xf2, + 0x14, 0x66, 0xf4, 0xa4, 0x84, 0x48, 0x65, 0x1d, 0x89, 0x14, 0x89, 0xa5, 0x2b, 0x29, 0xb5, 0x7c, + 0x7f, 0x7c, 0x4a, 0x19, 0xbd, 0xac, 0x50, 0xe3, 0x25, 0xc4, 0x93, 0xfe, 0x95, 0x0c, 0x19, 0x0e, + 0xd1, 0x77, 0x60, 0x36, 0x7a, 0xdf, 0xc6, 0x48, 0x18, 0xc0, 0x32, 0xec, 0x45, 0xb3, 0xd1, 0x4b, + 0xb7, 0xb3, 0xa3, 0xaf, 0x0b, 0x6e, 0x1f, 0xa1, 0x5f, 0x49, 0x78, 0x60, 0x6b, 0x63, 0x38, 0x0d, + 0xd3, 0x57, 0xe6, 0xf6, 0xac, 0xab, 0xd3, 0xa2, 0x9f, 0x9b, 0x39, 0x40, 0x90, 0xfa, 0xb9, 0x65, + 0x06, 0x31, 0x92, 0x12, 0x66, 0x0a, 0x9d, 0x0e, 0x5c, 0x1f, 0x18, 0xd2, 0x08, 0xdd, 0xd5, 0x22, + 0x0b, 0x0c, 0x0e, 0x7e, 0x94, 0x36, 0xa4, 0xea, 0xea, 0xcf, 0xff, 0xf4, 0x6a, 0xee, 0xe7, 0xbf, + 0xb8, 0x9a, 0xfb, 0x8f, 0xbf, 0xb8, 0x9a, 0xfb, 0xef, 0xbf, 0xb8, 0x9a, 0xfb, 0xc1, 0xca, 0xe9, + 0xe2, 0x0b, 0xb6, 0x3a, 0x2e, 0xee, 0x86, 0x77, 0x19, 0xb9, 0x51, 0xfa, 0xdf, 0x83, 0xff, 0x1b, + 0x00, 0x00, 0xff, 0xff, 0x31, 0xd7, 0xd7, 0x7d, 0xeb, 0xcd, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -25658,6 +25672,29 @@ func (m *Event_OktaAssignment) MarshalToSizedBuffer(dAtA []byte) (int, error) { } return len(dAtA) - i, nil } +func (m *Event_Integration) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Event_Integration) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + if m.Integration != nil { + { + size, err := m.Integration.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintAuthservice(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2 + i-- + dAtA[i] = 0xd2 + } + return len(dAtA) - i, nil +} func (m *Watch) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -26142,12 +26179,12 @@ func (m *UserCertsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x22 } - n45, err45 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Expires):]) - if err45 != nil { - return 0, err45 + n46, err46 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Expires):]) + if err46 != nil { + return 0, err46 } - i -= n45 - i = encodeVarintAuthservice(dAtA, i, uint64(n45)) + i -= n46 + i = encodeVarintAuthservice(dAtA, i, uint64(n46)) i-- dAtA[i] = 0x1a if len(m.Username) > 0 { @@ -27764,12 +27801,12 @@ func (m *GenerateAppTokenRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) dAtA[i] = 0x2a } } - n57, err57 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Expires):]) - if err57 != nil { - return 0, err57 + n58, err58 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Expires):]) + if err58 != nil { + return 0, err58 } - i -= n57 - i = encodeVarintAuthservice(dAtA, i, uint64(n57)) + i -= n58 + i = encodeVarintAuthservice(dAtA, i, uint64(n58)) i-- dAtA[i] = 0x22 if len(m.URI) > 0 { @@ -31343,21 +31380,21 @@ func (m *GetEventsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { dAtA[i] = 0x22 } } - n99, err99 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.EndDate, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.EndDate):]) - if err99 != nil { - return 0, err99 - } - i -= n99 - i = encodeVarintAuthservice(dAtA, i, uint64(n99)) - i-- - dAtA[i] = 0x1a - n100, err100 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.StartDate, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.StartDate):]) + n100, err100 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.EndDate, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.EndDate):]) if err100 != nil { return 0, err100 } i -= n100 i = encodeVarintAuthservice(dAtA, i, uint64(n100)) i-- + dAtA[i] = 0x1a + n101, err101 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.StartDate, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.StartDate):]) + if err101 != nil { + return 0, err101 + } + i -= n101 + i = encodeVarintAuthservice(dAtA, i, uint64(n101)) + i-- dAtA[i] = 0x12 if len(m.Namespace) > 0 { i -= len(m.Namespace) @@ -31410,21 +31447,21 @@ func (m *GetSessionEventsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) i-- dAtA[i] = 0x18 } - n101, err101 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.EndDate, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.EndDate):]) - if err101 != nil { - return 0, err101 - } - i -= n101 - i = encodeVarintAuthservice(dAtA, i, uint64(n101)) - i-- - dAtA[i] = 0x12 - n102, err102 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.StartDate, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.StartDate):]) + n102, err102 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.EndDate, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.EndDate):]) if err102 != nil { return 0, err102 } i -= n102 i = encodeVarintAuthservice(dAtA, i, uint64(n102)) i-- + dAtA[i] = 0x12 + n103, err103 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.StartDate, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.StartDate):]) + if err103 != nil { + return 0, err103 + } + i -= n103 + i = encodeVarintAuthservice(dAtA, i, uint64(n103)) + i-- dAtA[i] = 0xa return len(dAtA) - i, nil } @@ -32748,12 +32785,12 @@ func (m *RecoveryCodes) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } - n109, err109 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Created, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Created):]) - if err109 != nil { - return 0, err109 + n110, err110 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Created, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Created):]) + if err110 != nil { + return 0, err110 } - i -= n109 - i = encodeVarintAuthservice(dAtA, i, uint64(n109)) + i -= n110 + i = encodeVarintAuthservice(dAtA, i, uint64(n110)) i-- dAtA[i] = 0x12 if len(m.Codes) > 0 { @@ -33786,12 +33823,12 @@ func (m *SessionTrackerUpdateExpiry) MarshalToSizedBuffer(dAtA []byte) (int, err copy(dAtA[i:], m.XXX_unrecognized) } if m.Expires != nil { - n126, err126 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.Expires):]) - if err126 != nil { - return 0, err126 + n127, err127 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.Expires, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.Expires):]) + if err127 != nil { + return 0, err127 } - i -= n126 - i = encodeVarintAuthservice(dAtA, i, uint64(n126)) + i -= n127 + i = encodeVarintAuthservice(dAtA, i, uint64(n127)) i-- dAtA[i] = 0xa } @@ -35777,12 +35814,12 @@ func (m *AssistantMessage) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x22 } - n146, err146 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.CreatedTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.CreatedTime):]) - if err146 != nil { - return 0, err146 + n147, err147 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.CreatedTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.CreatedTime):]) + if err147 != nil { + return 0, err147 } - i -= n146 - i = encodeVarintAuthservice(dAtA, i, uint64(n146)) + i -= n147 + i = encodeVarintAuthservice(dAtA, i, uint64(n147)) i-- dAtA[i] = 0x1a if len(m.Type) > 0 { @@ -36340,6 +36377,18 @@ func (m *Event_OktaAssignment) Size() (n int) { } return n } +func (m *Event_Integration) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Integration != nil { + l = m.Integration.Size() + n += 2 + l + sovAuthservice(uint64(l)) + } + return n +} func (m *Watch) Size() (n int) { if m == nil { return 0 @@ -42568,6 +42617,41 @@ func (m *Event) Unmarshal(dAtA []byte) error { } m.Resource = &Event_OktaAssignment{v} iNdEx = postIndex + case 42: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Integration", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthservice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthAuthservice + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthAuthservice + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &types.IntegrationV1{} + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Resource = &Event_Integration{v} + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipAuthservice(dAtA[iNdEx:]) diff --git a/api/client/proxy/client.go b/api/client/proxy/client.go index 9d9738323a643..b0299c8fa97bf 100644 --- a/api/client/proxy/client.go +++ b/api/client/proxy/client.go @@ -17,9 +17,11 @@ package proxy import ( "context" "crypto/tls" + "encoding/asn1" "io" "net" "strings" + "sync/atomic" "time" "github.com/gravitational/trace" @@ -64,9 +66,6 @@ type ClientConfig struct { ProxySSHAddress string // TLSRoutingEnabled indicates if the cluster is using TLS Routing. TLSRoutingEnabled bool - // ClusterName is the name of the Teleport cluster that the client - // will be connected to. - ClusterName string // TLSConfig contains the tls.Config required for mTLS connections. TLSConfig *tls.Config // UnaryInterceptors are optional [grpc.UnaryClientInterceptor] to apply @@ -91,6 +90,8 @@ type ClientConfig struct { clientCreds func() client.Credentials } +// CheckAndSetDefaults ensures required options are present and +// sets the default value of any that are omitted. func (c *ClientConfig) CheckAndSetDefaults() error { if c.ProxyWebAddress == "" { return trace.BadParameter("missing required parameter ProxyWebAddress") @@ -98,9 +99,6 @@ func (c *ClientConfig) CheckAndSetDefaults() error { if c.ProxySSHAddress == "" { return trace.BadParameter("missing required parameter ProxySSHAddress") } - if c.ClusterName == "" { - return trace.BadParameter("missing required parameter ClusterName") - } if c.SSHDialer == nil { return trace.BadParameter("missing required parameter SSHDialer") } @@ -112,16 +110,27 @@ func (c *ClientConfig) CheckAndSetDefaults() error { } if c.TLSConfig != nil { - if !slices.Contains(c.TLSConfig.NextProtos, protocolProxySSHGRPC) { - tlsCfg := c.TLSConfig.Clone() - tlsCfg.NextProtos = append(tlsCfg.NextProtos, protocolProxySSHGRPC) - c.TLSConfig = tlsCfg - } c.clientCreds = func() client.Credentials { return client.LoadTLS(c.TLSConfig.Clone()) } c.creds = func() credentials.TransportCredentials { - return credentials.NewTLS(c.TLSConfig.Clone()) + tlsCfg := c.TLSConfig.Clone() + if !slices.Contains(c.TLSConfig.NextProtos, protocolProxySSHGRPC) { + tlsCfg.NextProtos = append(tlsCfg.NextProtos, protocolProxySSHGRPC) + } + + // This logic still appears to be necessary to force client to always send + // a certificate regardless of the server setting. Otherwise the client may pick + // not to send the client certificate by looking at certificate request. + if len(tlsCfg.Certificates) > 0 { + cert := tlsCfg.Certificates[0] + tlsCfg.Certificates = nil + tlsCfg.GetClientCertificate = func(_ *tls.CertificateRequestInfo) (*tls.Certificate, error) { + return &cert, nil + } + } + + return credentials.NewTLS(tlsCfg) } } else { c.clientCreds = func() client.Credentials { @@ -135,6 +144,8 @@ func (c *ClientConfig) CheckAndSetDefaults() error { return nil } +// insecureCredentials implements [client.Credentials] and is used by tests +// to connect to the Auth server without mTLS. type insecureCredentials struct{} func (mc insecureCredentials) Dialer(client.Config) (client.ContextDialer, error) { @@ -164,6 +175,9 @@ type Client struct { transport *transportv1.Client // sshClient is the established SSH connection to the Proxy. sshClient *tracessh.Client + // clusterName as determined by inspecting the certificate presented by + // the Proxy during the connection handshake. + clusterName *clusterName } // protocolProxySSHGRPC is TLS ALPN protocol value used to indicate gRPC @@ -198,23 +212,93 @@ func NewClient(ctx context.Context, cfg ClientConfig) (*Client, error) { } clt, sshErr := newSSHClient(ctx, &cfg) - if sshErr == nil { - return clt, nil + // Only aggregate errors if there was an issue dialing the grpc server so + // that helpers like trace.IsAccessDenied will still work. + if grpcErr == nil { + return clt, trace.Wrap(sshErr) } return nil, trace.NewAggregate(grpcErr, sshErr) } +// clusterName stores the name of the cluster +// in a protected manner which allows it to +// be set during handshakes with the server. +type clusterName struct { + name atomic.Pointer[string] +} + +func (c *clusterName) get() string { + name := c.name.Load() + if name != nil { + return *name + } + return "" +} + +func (c *clusterName) set(name string) { + c.name.CompareAndSwap(nil, &name) +} + +// clusterCredentials is a [credentials.TransportCredentials] implementation +// that obtains the name of the cluster being connected to from the certificate +// presented by the server. This allows the client to determine the cluster name when +// connecting via jump hosts. +type clusterCredentials struct { + credentials.TransportCredentials + clusterName *clusterName +} + +var ( + // teleportClusterASN1ExtensionOID is an extension ID used when encoding/decoding + // origin teleport cluster name into certificates. + teleportClusterASN1ExtensionOID = asn1.ObjectIdentifier{1, 3, 9999, 1, 7} +) + +// ClientHandshake performs the handshake with the wrapped [credentials.TransportCredentials] and +// then inspects the provided cert for the [teleportClusterASN1ExtensionOID] to determine +// the cluster that the server belongs to. +func (c *clusterCredentials) ClientHandshake(ctx context.Context, authority string, conn net.Conn) (net.Conn, credentials.AuthInfo, error) { + conn, info, err := c.TransportCredentials.ClientHandshake(ctx, authority, conn) + if err != nil { + return conn, info, trace.Wrap(err) + } + + tlsInfo, ok := info.(credentials.TLSInfo) + if !ok { + return conn, info, nil + } + + certs := tlsInfo.State.PeerCertificates + if len(certs) == 0 { + return conn, info, nil + } + + clientCert := certs[0] + for _, attr := range clientCert.Subject.Names { + if attr.Type.Equal(teleportClusterASN1ExtensionOID) { + val, ok := attr.Value.(string) + if ok { + c.clusterName.set(val) + break + } + } + } + + return conn, info, nil +} + // newGRPCClient creates a Client that is connected via gRPC. func newGRPCClient(ctx context.Context, cfg *ClientConfig) (_ *Client, err error) { dialCtx, cancel := context.WithTimeout(ctx, cfg.DialTimeout) defer cancel() + c := &clusterName{} conn, err := grpc.DialContext( dialCtx, cfg.ProxySSHAddress, append(cfg.DialOpts, - grpc.WithTransportCredentials(cfg.creds()), + grpc.WithTransportCredentials(&clusterCredentials{TransportCredentials: cfg.creds(), clusterName: c}), grpc.WithChainUnaryInterceptor( append(cfg.UnaryInterceptors, otelgrpc.UnaryClientInterceptor(), @@ -245,25 +329,71 @@ func newGRPCClient(ctx context.Context, cfg *ClientConfig) (_ *Client, err error } return &Client{ - cfg: cfg, - grpcConn: conn, - transport: transport, + cfg: cfg, + grpcConn: conn, + transport: transport, + clusterName: c, }, nil } +// teleportAuthority is the extension set by the server +// which contains the name of the cluster it is in. +const teleportAuthority = "x-teleport-authority" + +// clusterCallback is a [ssh.HostKeyCallback] that obtains the name +// of the cluster being connected to from the certificate presented by the server. +// This allows the client to determine the cluster name when using jump hosts. +func clusterCallback(c *clusterName, wrapped ssh.HostKeyCallback) ssh.HostKeyCallback { + return func(hostname string, remote net.Addr, key ssh.PublicKey) error { + if err := wrapped(hostname, remote, key); err != nil { + return trace.Wrap(err) + } + + cert, ok := key.(*ssh.Certificate) + if !ok { + return nil + } + + clusterName, ok := cert.Permissions.Extensions[teleportAuthority] + if ok { + c.set(clusterName) + } + + return nil + } +} + // newSSHClient creates a Client that is connected via SSH. func newSSHClient(ctx context.Context, cfg *ClientConfig) (*Client, error) { - clt, err := cfg.SSHDialer.Dial(ctx, "tcp", cfg.ProxySSHAddress, cfg.SSHConfig) + c := &clusterName{} + clientCfg := &ssh.ClientConfig{ + User: cfg.SSHConfig.User, + Auth: cfg.SSHConfig.Auth, + HostKeyCallback: clusterCallback(c, cfg.SSHConfig.HostKeyCallback), + BannerCallback: cfg.SSHConfig.BannerCallback, + ClientVersion: cfg.SSHConfig.ClientVersion, + HostKeyAlgorithms: cfg.SSHConfig.HostKeyAlgorithms, + Timeout: cfg.SSHConfig.Timeout, + } + + clt, err := cfg.SSHDialer.Dial(ctx, "tcp", cfg.ProxySSHAddress, clientCfg) if err != nil { return nil, trace.Wrap(err) } return &Client{ - cfg: cfg, - sshClient: clt, + cfg: cfg, + sshClient: clt, + clusterName: c, }, nil } +// ClusterName returns the name of the cluster that the +// connected Proxy is a member of. +func (c *Client) ClusterName() string { + return c.clusterName.get() +} + // Close attempts to close both the gRPC and SSH connections. func (c *Client) Close() error { var errs []error @@ -486,7 +616,7 @@ func dialSSH(ctx context.Context, clt *tracessh.Client, proxyAddress, targetAddr // read the stderr output from the failed SSH session and append // it to the end of our own message: serverErrorMsg, _ := io.ReadAll(sessionError) - return nil, trace.ConnectionProblem(err, "failed connecting to host %s: %v. %v", targetAddress, serverErrorMsg, err) + return nil, trace.ConnectionProblem(err, "failed connecting to host %s: %s. %v", targetAddress, serverErrorMsg, err) } return conn, nil diff --git a/api/client/proxy/client_test.go b/api/client/proxy/client_test.go index d579c905ca3bf..ebee00737f62a 100644 --- a/api/client/proxy/client_test.go +++ b/api/client/proxy/client_test.go @@ -18,7 +18,10 @@ import ( "context" "crypto/rand" "crypto/rsa" + "crypto/tls" "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" "encoding/pem" "errors" "fmt" @@ -36,6 +39,7 @@ import ( "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/agent" "google.golang.org/grpc" + "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/test/bufconn" "google.golang.org/protobuf/testing/protocmp" @@ -375,7 +379,6 @@ func (f *fakeProxy) clientConfig(t *testing.T) ClientConfig { return ClientConfig{ ProxyWebAddress: "127.0.0.1", ProxySSHAddress: "127.0.0.1", - ClusterName: "test", SSHDialer: SSHDialerFunc(func(ctx context.Context, network string, addr string, config *ssh.ClientConfig) (*tracessh.Client, error) { conn, chans, reqs, err := f.fakeSSHServer.newClientConn() if err != nil { @@ -884,3 +887,193 @@ func TestClient_SSHConfig(t *testing.T) { require.Equal(t, user, sshConfig.User) require.Empty(t, cmp.Diff(cfg.SSHConfig, sshConfig, cmpopts.IgnoreFields(ssh.ClientConfig{}, "User", "Auth", "HostKeyCallback"))) } + +type fakeTransportCredentials struct { + credentials.TransportCredentials + info credentials.AuthInfo + err error +} + +type fakeAuthInfo struct{} + +func (f fakeAuthInfo) AuthType() string { + return "test" +} + +func (t fakeTransportCredentials) ClientHandshake(ctx context.Context, addr string, conn net.Conn) (net.Conn, credentials.AuthInfo, error) { + return conn, t.info, t.err +} + +func TestClusterCredentials(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + expectedClusterName string + credentials fakeTransportCredentials + errAssertion require.ErrorAssertionFunc + }{ + { + name: "handshake error", + credentials: fakeTransportCredentials{err: context.Canceled}, + errAssertion: require.Error, + }, + { + name: "no tls auth info", + credentials: fakeTransportCredentials{info: fakeAuthInfo{}}, + errAssertion: require.NoError, + }, + { + name: "no server cert", + credentials: fakeTransportCredentials{info: credentials.TLSInfo{}}, + errAssertion: require.NoError, + }, + { + name: "no cluster oid set", + credentials: fakeTransportCredentials{info: credentials.TLSInfo{ + State: tls.ConnectionState{ + PeerCertificates: []*x509.Certificate{ + { + Subject: pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + { + Type: asn1.ObjectIdentifier{1, 3, 9999, 0, 1}, + }, + { + Type: asn1.ObjectIdentifier{1, 3, 9999, 2, 1}, + }, + { + Type: asn1.ObjectIdentifier{1, 3, 9999, 0, 2}, + }, + { + Type: asn1.ObjectIdentifier{1, 3, 9999, 2, 2}, + }, + }, + }, + }, + }, + }, + }}, + errAssertion: require.NoError, + }, { + name: "cluster name presented", + expectedClusterName: "test-cluster", + credentials: fakeTransportCredentials{info: credentials.TLSInfo{ + State: tls.ConnectionState{ + PeerCertificates: []*x509.Certificate{ + { + Subject: pkix.Name{ + Names: []pkix.AttributeTypeAndValue{ + { + Type: asn1.ObjectIdentifier{1, 3, 9999, 2, 1}, + }, + { + Type: asn1.ObjectIdentifier{1, 3, 9999, 0, 2}, + }, + { + Type: asn1.ObjectIdentifier{1, 3, 9999, 2, 2}, + }, + { + Type: teleportClusterASN1ExtensionOID, + Value: "test-cluster", + }, + }, + }, + }, + }, + }, + }}, + errAssertion: require.NoError, + }, + } + + for _, test := range cases { + t.Run(test.name, func(t *testing.T) { + c := &clusterName{} + creds := clusterCredentials{TransportCredentials: test.credentials, clusterName: c} + _, _, err := creds.ClientHandshake(context.Background(), "127.0.0.1", nil) + test.errAssertion(t, err) + require.Equal(t, test.expectedClusterName, c.get()) + }) + } +} + +type fakePublicKey struct{} + +func (f fakePublicKey) Type() string { + return "test" +} + +func (f fakePublicKey) Marshal() []byte { + return nil +} + +func (f fakePublicKey) Verify(data []byte, sig *ssh.Signature) error { + return trace.NotImplemented("") +} + +func TestClusterCallback(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + hostKeyCB ssh.HostKeyCallback + publicKey ssh.PublicKey + expectedClusterName string + errAssertion require.ErrorAssertionFunc + }{ + { + name: "handshake failure", + hostKeyCB: func(hostname string, remote net.Addr, key ssh.PublicKey) error { + return context.Canceled + }, + errAssertion: require.Error, + }, + { + name: "invalid certificate", + publicKey: fakePublicKey{}, + hostKeyCB: func(hostname string, remote net.Addr, key ssh.PublicKey) error { + return nil + }, + errAssertion: require.NoError, + }, + { + name: "no authority present", + publicKey: &ssh.Certificate{ + Permissions: ssh.Permissions{ + Extensions: map[string]string{}, + }, + }, + hostKeyCB: func(hostname string, remote net.Addr, key ssh.PublicKey) error { + return nil + }, + errAssertion: require.NoError, + }, + + { + name: "cluster name presented", + expectedClusterName: "test-cluster", + publicKey: &ssh.Certificate{ + Permissions: ssh.Permissions{ + Extensions: map[string]string{ + teleportAuthority: "test-cluster", + }, + }, + }, + hostKeyCB: func(hostname string, remote net.Addr, key ssh.PublicKey) error { + return nil + }, + errAssertion: require.NoError, + }, + } + + for _, test := range cases { + t.Run(test.name, func(t *testing.T) { + c := &clusterName{} + err := clusterCallback(c, test.hostKeyCB)("test", addr("127.0.0.1"), test.publicKey) + test.errAssertion(t, err) + require.Equal(t, test.expectedClusterName, c.get()) + + }) + } +} diff --git a/api/constants/constants.go b/api/constants/constants.go index 50c409cd6aca1..47b3b6c513c29 100644 --- a/api/constants/constants.go +++ b/api/constants/constants.go @@ -402,3 +402,15 @@ const ( // TimeoutGetClusterAlerts is the timeout for grabbing cluster alerts from tctl and tsh TimeoutGetClusterAlerts = time.Millisecond * 500 ) + +const ( + // WebAPIConnUpgrade is the HTTP web API to make the connection upgrade + // call. + WebAPIConnUpgrade = "/webapi/connectionupgrade" + // WebAPIConnUpgradeHeader is the header used to indicate the requested + // connection upgrade types in the connection upgrade API. + WebAPIConnUpgradeHeader = "Upgrade" + // WebAPIConnUpgradeTypeALPN is a connection upgrade type that specifies + // the upgraded connection should be handled by the ALPN handler. + WebAPIConnUpgradeTypeALPN = "alpn" +) diff --git a/api/defaults/defaults.go b/api/defaults/defaults.go index db9ca0ca7ca66..6520400e47dbe 100644 --- a/api/defaults/defaults.go +++ b/api/defaults/defaults.go @@ -32,7 +32,7 @@ const ( DefaultIOTimeout = 30 * time.Second // DefaultIdleTimeout is a default idle connection timeout. - DefaultIdleTimeout = 360 * time.Second + DefaultIdleTimeout = 30 * time.Second // KeepAliveCountMax is the number of keep-alive messages that can be sent // without receiving a response from the client before the client is diff --git a/api/fixtures/fixtures.go b/api/fixtures/fixtures.go new file mode 100644 index 0000000000000..573e4d3c9f651 --- /dev/null +++ b/api/fixtures/fixtures.go @@ -0,0 +1,64 @@ +// Copyright 2023 Gravitational, Inc +// +// 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. + +package fixtures + +const ( + TLSCACertPEM = `-----BEGIN CERTIFICATE----- +MIIDKjCCAhKgAwIBAgIQJtJDJZZBkg/afM8d2ZJCTjANBgkqhkiG9w0BAQsFADBA +MRUwEwYDVQQKEwxUZWxlcG9ydCBPU1MxJzAlBgNVBAMTHnRlbGVwb3J0LmxvY2Fs +aG9zdC5sb2NhbGRvbWFpbjAeFw0xNzA1MDkxOTQwMzZaFw0yNzA1MDcxOTQwMzZa +MEAxFTATBgNVBAoTDFRlbGVwb3J0IE9TUzEnMCUGA1UEAxMedGVsZXBvcnQubG9j +YWxob3N0LmxvY2FsZG9tYWluMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAuKFLaf2iII/xDR+m2Yj6PnUEa+qzqwxsdLUjnunFZaAXG+hZm4Ml80SCiBgI +gTHQlJyLIkTtuRoH5aeMyz1ERUCtii4ZsTqDrjjUybxP4r+4HVX6m34s6hwEr8Fi +fts9pMp4iS3tQguRc28gPdDo/T6VrJTVYUfUUsNDRtIrlB5O9igqqLnuaY9eqGi4 +PUx0G0wRYJpRywoj8G0IkpfQTiX+CAC7dt5ws7ZrnGqCNBLGi5bGsaMmptVbsSEp +1TenntF54V1iR49IV5JqDhm1S0HmkleoJzKdc+6sP/xNepz9PJzuF9d9NubTLWgB +sK28YItcmWHdHXD/ODxVaehRjwIDAQABoyAwHjAOBgNVHQ8BAf8EBAMCB4AwDAYD +VR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAAVU6sNBdj76saHwOxGSdnEqQ +o2tMuR3msSM4F6wFK2UkKepsD7CYIf/PzNSNUqA5JIEUVeMqGyiHuAbU4C655nT1 +IyJX1D/+r73sSp5jbIpQm2xoQGZnj6g/Kltw8OSOAw+DsMF/PLVqoWJp07u6ew/m +NxWsJKcZ5k+q4eMxci9mKRHHqsquWKXzQlURMNFI+mGaFwrKM4dmzaR0BEc+ilSx +QqUvQ74smsLK+zhNikmgjlGC5ob9g8XkhVAkJMAh2rb9onDNiRl68iAgczP88mXu +vN/o98dypzsPxXmw6tkDqIRPUAUbh465rlY5sKMmRgXi2rUfl/QV5nbozUo/HQ== +-----END CERTIFICATE-----` + TLSCAKeyPEM = `-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAuKFLaf2iII/xDR+m2Yj6PnUEa+qzqwxsdLUjnunFZaAXG+hZ +m4Ml80SCiBgIgTHQlJyLIkTtuRoH5aeMyz1ERUCtii4ZsTqDrjjUybxP4r+4HVX6 +m34s6hwEr8Fifts9pMp4iS3tQguRc28gPdDo/T6VrJTVYUfUUsNDRtIrlB5O9igq +qLnuaY9eqGi4PUx0G0wRYJpRywoj8G0IkpfQTiX+CAC7dt5ws7ZrnGqCNBLGi5bG +saMmptVbsSEp1TenntF54V1iR49IV5JqDhm1S0HmkleoJzKdc+6sP/xNepz9PJzu +F9d9NubTLWgBsK28YItcmWHdHXD/ODxVaehRjwIDAQABAoIBABy4orWrShRMsA/9 +k4QVpfAfXf+3tBlwxlJld1QaQ6XqgI3L2FyzyyyLxM6NBo2qhSsJKy+6j0yTOxVD +ukhHkJ5BUH3FbCPA2Yk5uAhl7ft1HZwaqvCTcUM99pCswbjAPFetU5DrfxQeHpNZ +fyd+ny/+E2SUhpkqhmIVlBqpSTQyOywbiEvZ6ZiFmncdHhXaCy3YZsylrKUGPzsJ +jfU2iOE167eTOIjPStsaoCPv9jLSyy2OvuNNudS+Y1qkFz8ZGvPp+HB+Iig+AlAE +7KMzNrIW7PlHTDgUly1cRCl3+84yE2mJ97+hHiEy//HIwVDUpI529i2hMYM/u4qz +Wso/2tkCgYEA2FdE4bmCrZiA9eS8qobwGLE1+MJME4YwfJkynZUHHX93xORPQ66e +WYpN7/xbMvBDa8LZZYVTNVtZ/SkEUaTb5NQW2zXKoIutk1PFBb8NbA0m8Ss/mOJA +d5nUYGr987O9fRh1yP9TksBshHB/5A8U2UG8MFFCNvJTZDPRkuSlMiUCgYEA2nnb +hAJrhY7PaF6jdfimGvvponkUiEbWLppg7/SjgPg+QgqIwuLybryXyOAp+TEnNzgU +ujAjhNtIiyB/B13TDxOgUgWUWPbPvUAWGEvwI9h+RLie1umGHd48G1NR76fwqSf1 +y7z3YRnq8vCdz8ywB3o5GO6SH6QkMJBIxfIMlKMCgYA55akOi7oYQT8KD4waSwCI +ayyZhU4cz4W8Yrd0CsUbtNhVvhAked/w8J2JA01Y5Yn1lfDeRX8OQYNkyAxa2Tbs +F4KCafPvYVIzonCQ6B9sclygoEVl4e8E0wtOPnP2O30TtG8ZOpOgK5UfIIhpfUvE +FN6LQ8PntpRwtZl5qW04bQKBgGnHhFxHG64fthZPdA9jY3E/NSCgRSuyOHN59aNY +rG1+RA6PsSXC4iRxlYAB4PCxNs6KjaaUNi5WSaprAnYbnFv5Ya802l20qmJ0C/6Z +jdydLo2xYd6mVHRTrICCd/J0OpZ8LYsGpDPUa6hSjeYVscj9CXYj1IYTYB5PTZzh +k+vHAoGBAJyA+RtBF5m64/TqhZFcesTtnpWaRhQ50xXnNVF3W1eKGPtdTDKOaENA +LJxgC1GdoEz2ilXW802H9QrdKf9GPqxwi2TVzfO6pzWkdZcmbItu+QCCFz+co+r8 ++ki49FmlfbR5YVPN+8X40aLQB4xDkCHwRwTkrigzWQhIOv8NAhDA +-----END RSA PRIVATE KEY-----` +) diff --git a/api/gen/proto/go/teleport/devicetrust/v1/device.pb.go b/api/gen/proto/go/teleport/devicetrust/v1/device.pb.go index c6e79c7835ab2..36fef2d25f046 100644 --- a/api/gen/proto/go/teleport/devicetrust/v1/device.pb.go +++ b/api/gen/proto/go/teleport/devicetrust/v1/device.pb.go @@ -135,6 +135,12 @@ type Device struct { // Only present in certain read modes. // Transient. CollectedData []*DeviceCollectedData `protobuf:"bytes,10,rep,name=collected_data,json=collectedData,proto3" json:"collected_data,omitempty"` + // Source of the device. + // Devices managed directly via Teleport (`tctl`, Web UI, etc) have no + // assigned source. + Source *DeviceSource `protobuf:"bytes,11,opt,name=source,proto3" json:"source,omitempty"` + // Device information acquired from an external source. + Profile *DeviceProfile `protobuf:"bytes,12,opt,name=profile,proto3" json:"profile,omitempty"` } func (x *Device) Reset() { @@ -239,6 +245,20 @@ func (x *Device) GetCollectedData() []*DeviceCollectedData { return nil } +func (x *Device) GetSource() *DeviceSource { + if x != nil { + return x.Source + } + return nil +} + +func (x *Device) GetProfile() *DeviceProfile { + if x != nil { + return x.Profile + } + return nil +} + // DeviceCredential represents the current enrolled public key of a device. type DeviceCredential struct { state protoimpl.MessageState @@ -312,65 +332,78 @@ var file_teleport_devicetrust_v1_device_proto_rawDesc = []byte{ 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x31, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2f, 0x76, 0x31, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x65, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x5f, 0x74, 0x6f, 0x6b, - 0x65, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x25, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, + 0x65, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2c, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2f, 0x76, - 0x31, 0x2f, 0x6f, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, - 0xcb, 0x04, 0x0a, 0x06, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x70, - 0x69, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x38, 0x0a, 0x07, 0x6f, - 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x74, + 0x31, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2b, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2f, 0x76, 0x31, 0x2f, + 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x1a, 0x25, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x64, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2f, 0x76, 0x31, 0x2f, 0x6f, 0x73, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xcc, 0x05, 0x0a, 0x06, 0x44, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x70, 0x69, 0x56, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x38, 0x0a, 0x07, 0x6f, 0x73, 0x5f, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x4f, 0x53, 0x54, 0x79, 0x70, 0x65, 0x52, 0x06, 0x6f, 0x73, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x61, 0x67, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x73, 0x73, 0x65, 0x74, 0x54, 0x61, 0x67, 0x12, 0x3b, 0x0a, + 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x3b, 0x0a, 0x0b, 0x75, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x75, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x4d, 0x0a, 0x0c, 0x65, 0x6e, 0x72, 0x6f, 0x6c, + 0x6c, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, + 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, + 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x45, 0x6e, + 0x72, 0x6f, 0x6c, 0x6c, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x0b, 0x65, 0x6e, 0x72, 0x6f, 0x6c, + 0x6c, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x50, 0x0a, 0x0d, 0x65, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, + 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, + 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, + 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x45, 0x6e, + 0x72, 0x6f, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0c, 0x65, 0x6e, 0x72, 0x6f, + 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x49, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, - 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x53, 0x54, 0x79, 0x70, 0x65, 0x52, 0x06, 0x6f, - 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, - 0x61, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x73, 0x73, 0x65, 0x74, 0x54, - 0x61, 0x67, 0x12, 0x3b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, - 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, - 0x3b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x4d, 0x0a, 0x0c, - 0x65, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x45, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x0b, - 0x65, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x50, 0x0a, 0x0d, 0x65, - 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x08, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x45, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, - 0x0c, 0x65, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x49, 0x0a, - 0x0a, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x29, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x0a, 0x63, 0x72, - 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x12, 0x53, 0x0a, 0x0e, 0x63, 0x6f, 0x6c, 0x6c, - 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x2c, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x52, 0x0d, - 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x22, 0x48, 0x0a, - 0x10, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, - 0x6c, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, - 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x5f, - 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x70, 0x75, 0x62, 0x6c, 0x69, - 0x63, 0x4b, 0x65, 0x79, 0x44, 0x65, 0x72, 0x2a, 0x84, 0x01, 0x0a, 0x12, 0x44, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x45, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x24, - 0x0a, 0x20, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x45, 0x4e, 0x52, 0x4f, 0x4c, 0x4c, 0x5f, - 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, - 0x45, 0x44, 0x10, 0x00, 0x12, 0x25, 0x0a, 0x21, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x45, - 0x4e, 0x52, 0x4f, 0x4c, 0x4c, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x4e, 0x4f, 0x54, - 0x5f, 0x45, 0x4e, 0x52, 0x4f, 0x4c, 0x4c, 0x45, 0x44, 0x10, 0x01, 0x12, 0x21, 0x0a, 0x1d, 0x44, - 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x45, 0x4e, 0x52, 0x4f, 0x4c, 0x4c, 0x5f, 0x53, 0x54, 0x41, - 0x54, 0x55, 0x53, 0x5f, 0x45, 0x4e, 0x52, 0x4f, 0x4c, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x42, 0x4c, - 0x5a, 0x4a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, - 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2f, 0x67, 0x6f, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x64, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x72, 0x65, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x61, 0x6c, 0x12, 0x53, 0x0a, 0x0e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, + 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x74, 0x65, + 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, + 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6c, 0x6c, + 0x65, 0x63, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x52, 0x0d, 0x63, 0x6f, 0x6c, 0x6c, 0x65, + 0x63, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x12, 0x3d, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, + 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, + 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x40, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x66, 0x69, + 0x6c, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, + 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, + 0x52, 0x07, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x48, 0x0a, 0x10, 0x44, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x24, 0x0a, + 0x0e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x64, 0x65, 0x72, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, + 0x44, 0x65, 0x72, 0x2a, 0x84, 0x01, 0x0a, 0x12, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x45, 0x6e, + 0x72, 0x6f, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x24, 0x0a, 0x20, 0x44, 0x45, + 0x56, 0x49, 0x43, 0x45, 0x5f, 0x45, 0x4e, 0x52, 0x4f, 0x4c, 0x4c, 0x5f, 0x53, 0x54, 0x41, 0x54, + 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, + 0x12, 0x25, 0x0a, 0x21, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x45, 0x4e, 0x52, 0x4f, 0x4c, + 0x4c, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x45, 0x4e, 0x52, + 0x4f, 0x4c, 0x4c, 0x45, 0x44, 0x10, 0x01, 0x12, 0x21, 0x0a, 0x1d, 0x44, 0x45, 0x56, 0x49, 0x43, + 0x45, 0x5f, 0x45, 0x4e, 0x52, 0x4f, 0x4c, 0x4c, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, + 0x45, 0x4e, 0x52, 0x4f, 0x4c, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x42, 0x4c, 0x5a, 0x4a, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x76, 0x69, 0x74, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, + 0x61, 0x70, 0x69, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, + 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x74, 0x72, 0x75, 0x73, 0x74, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -395,6 +428,8 @@ var file_teleport_devicetrust_v1_device_proto_goTypes = []interface{}{ (*timestamppb.Timestamp)(nil), // 4: google.protobuf.Timestamp (*DeviceEnrollToken)(nil), // 5: teleport.devicetrust.v1.DeviceEnrollToken (*DeviceCollectedData)(nil), // 6: teleport.devicetrust.v1.DeviceCollectedData + (*DeviceSource)(nil), // 7: teleport.devicetrust.v1.DeviceSource + (*DeviceProfile)(nil), // 8: teleport.devicetrust.v1.DeviceProfile } var file_teleport_devicetrust_v1_device_proto_depIdxs = []int32{ 3, // 0: teleport.devicetrust.v1.Device.os_type:type_name -> teleport.devicetrust.v1.OSType @@ -404,11 +439,13 @@ var file_teleport_devicetrust_v1_device_proto_depIdxs = []int32{ 0, // 4: teleport.devicetrust.v1.Device.enroll_status:type_name -> teleport.devicetrust.v1.DeviceEnrollStatus 2, // 5: teleport.devicetrust.v1.Device.credential:type_name -> teleport.devicetrust.v1.DeviceCredential 6, // 6: teleport.devicetrust.v1.Device.collected_data:type_name -> teleport.devicetrust.v1.DeviceCollectedData - 7, // [7:7] is the sub-list for method output_type - 7, // [7:7] is the sub-list for method input_type - 7, // [7:7] is the sub-list for extension type_name - 7, // [7:7] is the sub-list for extension extendee - 0, // [0:7] is the sub-list for field type_name + 7, // 7: teleport.devicetrust.v1.Device.source:type_name -> teleport.devicetrust.v1.DeviceSource + 8, // 8: teleport.devicetrust.v1.Device.profile:type_name -> teleport.devicetrust.v1.DeviceProfile + 9, // [9:9] is the sub-list for method output_type + 9, // [9:9] is the sub-list for method input_type + 9, // [9:9] is the sub-list for extension type_name + 9, // [9:9] is the sub-list for extension extendee + 0, // [0:9] is the sub-list for field type_name } func init() { file_teleport_devicetrust_v1_device_proto_init() } @@ -418,6 +455,8 @@ func file_teleport_devicetrust_v1_device_proto_init() { } file_teleport_devicetrust_v1_device_collected_data_proto_init() file_teleport_devicetrust_v1_device_enroll_token_proto_init() + file_teleport_devicetrust_v1_device_profile_proto_init() + file_teleport_devicetrust_v1_device_source_proto_init() file_teleport_devicetrust_v1_os_type_proto_init() if !protoimpl.UnsafeEnabled { file_teleport_devicetrust_v1_device_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { diff --git a/api/gen/proto/go/teleport/devicetrust/v1/device_collected_data.pb.go b/api/gen/proto/go/teleport/devicetrust/v1/device_collected_data.pb.go index e11c445f3fc3c..348b67c844781 100644 --- a/api/gen/proto/go/teleport/devicetrust/v1/device_collected_data.pb.go +++ b/api/gen/proto/go/teleport/devicetrust/v1/device_collected_data.pb.go @@ -56,6 +56,23 @@ type DeviceCollectedData struct { // Device serial number. // Required for macOS devices. SerialNumber string `protobuf:"bytes,4,opt,name=serial_number,json=serialNumber,proto3" json:"serial_number,omitempty"` + // Non-descriptive model identifier. + // Example: "MacBookPro9,2". + ModelIdentifier string `protobuf:"bytes,5,opt,name=model_identifier,json=modelIdentifier,proto3" json:"model_identifier,omitempty"` + // OS version number, without the leading 'v'. + // Example: "13.2.1". + OsVersion string `protobuf:"bytes,6,opt,name=os_version,json=osVersion,proto3" json:"os_version,omitempty"` + // OS build identifier. Augments the os_version. + // Example: "22D68". + OsBuild string `protobuf:"bytes,7,opt,name=os_build,json=osBuild,proto3" json:"os_build,omitempty"` + // OS username (distinct from the Teleport user). + OsUsername string `protobuf:"bytes,8,opt,name=os_username,json=osUsername,proto3" json:"os_username,omitempty"` + // Jamf binary version, without the leading 'v'. + // Example: "9.27" or "10.44.1-t1677509507". + JamfBinaryVersion string `protobuf:"bytes,9,opt,name=jamf_binary_version,json=jamfBinaryVersion,proto3" json:"jamf_binary_version,omitempty"` + // Unmodified output of `/usr/bin/profiles status -type enrollment`. + // Used to verify the presence of an enrollment profile. + MacosEnrollmentProfiles string `protobuf:"bytes,10,opt,name=macos_enrollment_profiles,json=macosEnrollmentProfiles,proto3" json:"macos_enrollment_profiles,omitempty"` } func (x *DeviceCollectedData) Reset() { @@ -118,6 +135,48 @@ func (x *DeviceCollectedData) GetSerialNumber() string { return "" } +func (x *DeviceCollectedData) GetModelIdentifier() string { + if x != nil { + return x.ModelIdentifier + } + return "" +} + +func (x *DeviceCollectedData) GetOsVersion() string { + if x != nil { + return x.OsVersion + } + return "" +} + +func (x *DeviceCollectedData) GetOsBuild() string { + if x != nil { + return x.OsBuild + } + return "" +} + +func (x *DeviceCollectedData) GetOsUsername() string { + if x != nil { + return x.OsUsername + } + return "" +} + +func (x *DeviceCollectedData) GetJamfBinaryVersion() string { + if x != nil { + return x.JamfBinaryVersion + } + return "" +} + +func (x *DeviceCollectedData) GetMacosEnrollmentProfiles() string { + if x != nil { + return x.MacosEnrollmentProfiles + } + return "" +} + var File_teleport_devicetrust_v1_device_collected_data_proto protoreflect.FileDescriptor var file_teleport_devicetrust_v1_device_collected_data_proto_rawDesc = []byte{ @@ -130,7 +189,7 @@ var file_teleport_devicetrust_v1_device_collected_data_proto_rawDesc = []byte{ 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x25, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2f, 0x76, 0x31, 0x2f, 0x6f, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xf0, 0x01, 0x0a, 0x13, 0x44, 0x65, 0x76, 0x69, 0x63, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe2, 0x03, 0x0a, 0x13, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x12, 0x3d, 0x0a, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, @@ -145,12 +204,28 @@ var file_teleport_devicetrust_v1_device_collected_data_proto_rawDesc = []byte{ 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x53, 0x54, 0x79, 0x70, 0x65, 0x52, 0x06, 0x6f, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x65, 0x72, - 0x69, 0x61, 0x6c, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x42, 0x4c, 0x5a, 0x4a, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x76, 0x69, 0x74, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x61, - 0x70, 0x69, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x2f, - 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, - 0x72, 0x75, 0x73, 0x74, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x69, 0x61, 0x6c, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x29, 0x0a, 0x10, 0x6d, 0x6f, 0x64, + 0x65, 0x6c, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x66, 0x69, 0x65, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x73, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x73, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x73, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x1f, + 0x0a, 0x0b, 0x6f, 0x73, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x73, 0x55, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, + 0x2e, 0x0a, 0x13, 0x6a, 0x61, 0x6d, 0x66, 0x5f, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x5f, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x6a, 0x61, + 0x6d, 0x66, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, + 0x3a, 0x0a, 0x19, 0x6d, 0x61, 0x63, 0x6f, 0x73, 0x5f, 0x65, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x6d, + 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x17, 0x6d, 0x61, 0x63, 0x6f, 0x73, 0x45, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x6d, + 0x65, 0x6e, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x42, 0x4c, 0x5a, 0x4a, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x76, 0x69, 0x74, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, + 0x6f, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( diff --git a/api/gen/proto/go/teleport/devicetrust/v1/device_profile.pb.go b/api/gen/proto/go/teleport/devicetrust/v1/device_profile.pb.go new file mode 100644 index 0000000000000..af1de8d989368 --- /dev/null +++ b/api/gen/proto/go/teleport/devicetrust/v1/device_profile.pb.go @@ -0,0 +1,236 @@ +// Copyright 2023 Gravitational, Inc +// +// 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. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.30.0 +// protoc (unknown) +// source: teleport/devicetrust/v1/device_profile.proto + +package v1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Device profile information acquired from an external source. +// If present, it's used to further validate collected data. +type DeviceProfile struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Latest profile update time. + // System managed. + UpdateTime *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty"` + // Non-descriptive model identifier. + // Example: "MacBookPro9,2". + ModelIdentifier string `protobuf:"bytes,2,opt,name=model_identifier,json=modelIdentifier,proto3" json:"model_identifier,omitempty"` + // OS version number, without the leading 'v'. + // See the Device's os_type for the general OS category. + // Example: "13.2.1". + OsVersion string `protobuf:"bytes,3,opt,name=os_version,json=osVersion,proto3" json:"os_version,omitempty"` + // OS build identifier. Augments the os_version. + // Example: "22D68". + OsBuild string `protobuf:"bytes,4,opt,name=os_build,json=osBuild,proto3" json:"os_build,omitempty"` + // Known OS users (distinct from the Teleport user). + OsUsernames []string `protobuf:"bytes,5,rep,name=os_usernames,json=osUsernames,proto3" json:"os_usernames,omitempty"` + // Jamf binary version, without the leading 'v'. + // Example: "9.27" or "10.44.1-t1677509507". + JamfBinaryVersion string `protobuf:"bytes,6,opt,name=jamf_binary_version,json=jamfBinaryVersion,proto3" json:"jamf_binary_version,omitempty"` +} + +func (x *DeviceProfile) Reset() { + *x = DeviceProfile{} + if protoimpl.UnsafeEnabled { + mi := &file_teleport_devicetrust_v1_device_profile_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeviceProfile) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeviceProfile) ProtoMessage() {} + +func (x *DeviceProfile) ProtoReflect() protoreflect.Message { + mi := &file_teleport_devicetrust_v1_device_profile_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeviceProfile.ProtoReflect.Descriptor instead. +func (*DeviceProfile) Descriptor() ([]byte, []int) { + return file_teleport_devicetrust_v1_device_profile_proto_rawDescGZIP(), []int{0} +} + +func (x *DeviceProfile) GetUpdateTime() *timestamppb.Timestamp { + if x != nil { + return x.UpdateTime + } + return nil +} + +func (x *DeviceProfile) GetModelIdentifier() string { + if x != nil { + return x.ModelIdentifier + } + return "" +} + +func (x *DeviceProfile) GetOsVersion() string { + if x != nil { + return x.OsVersion + } + return "" +} + +func (x *DeviceProfile) GetOsBuild() string { + if x != nil { + return x.OsBuild + } + return "" +} + +func (x *DeviceProfile) GetOsUsernames() []string { + if x != nil { + return x.OsUsernames + } + return nil +} + +func (x *DeviceProfile) GetJamfBinaryVersion() string { + if x != nil { + return x.JamfBinaryVersion + } + return "" +} + +var File_teleport_devicetrust_v1_device_profile_proto protoreflect.FileDescriptor + +var file_teleport_devicetrust_v1_device_profile_proto_rawDesc = []byte{ + 0x0a, 0x2c, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2f, 0x76, 0x31, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x17, + 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, + 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x84, 0x02, 0x0a, 0x0d, 0x44, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x3b, 0x0a, 0x0b, 0x75, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x75, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x6d, 0x6f, 0x64, 0x65, 0x6c, + 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, + 0x65, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x73, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x73, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x21, 0x0a, 0x0c, + 0x6f, 0x73, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x0b, 0x6f, 0x73, 0x55, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x12, + 0x2e, 0x0a, 0x13, 0x6a, 0x61, 0x6d, 0x66, 0x5f, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x5f, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x6a, 0x61, + 0x6d, 0x66, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x42, + 0x4c, 0x5a, 0x4a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, + 0x61, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x6c, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x64, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_teleport_devicetrust_v1_device_profile_proto_rawDescOnce sync.Once + file_teleport_devicetrust_v1_device_profile_proto_rawDescData = file_teleport_devicetrust_v1_device_profile_proto_rawDesc +) + +func file_teleport_devicetrust_v1_device_profile_proto_rawDescGZIP() []byte { + file_teleport_devicetrust_v1_device_profile_proto_rawDescOnce.Do(func() { + file_teleport_devicetrust_v1_device_profile_proto_rawDescData = protoimpl.X.CompressGZIP(file_teleport_devicetrust_v1_device_profile_proto_rawDescData) + }) + return file_teleport_devicetrust_v1_device_profile_proto_rawDescData +} + +var file_teleport_devicetrust_v1_device_profile_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_teleport_devicetrust_v1_device_profile_proto_goTypes = []interface{}{ + (*DeviceProfile)(nil), // 0: teleport.devicetrust.v1.DeviceProfile + (*timestamppb.Timestamp)(nil), // 1: google.protobuf.Timestamp +} +var file_teleport_devicetrust_v1_device_profile_proto_depIdxs = []int32{ + 1, // 0: teleport.devicetrust.v1.DeviceProfile.update_time:type_name -> google.protobuf.Timestamp + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_teleport_devicetrust_v1_device_profile_proto_init() } +func file_teleport_devicetrust_v1_device_profile_proto_init() { + if File_teleport_devicetrust_v1_device_profile_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_teleport_devicetrust_v1_device_profile_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeviceProfile); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_teleport_devicetrust_v1_device_profile_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_teleport_devicetrust_v1_device_profile_proto_goTypes, + DependencyIndexes: file_teleport_devicetrust_v1_device_profile_proto_depIdxs, + MessageInfos: file_teleport_devicetrust_v1_device_profile_proto_msgTypes, + }.Build() + File_teleport_devicetrust_v1_device_profile_proto = out.File + file_teleport_devicetrust_v1_device_profile_proto_rawDesc = nil + file_teleport_devicetrust_v1_device_profile_proto_goTypes = nil + file_teleport_devicetrust_v1_device_profile_proto_depIdxs = nil +} diff --git a/api/gen/proto/go/teleport/devicetrust/v1/device_source.pb.go b/api/gen/proto/go/teleport/devicetrust/v1/device_source.pb.go new file mode 100644 index 0000000000000..518fb77c6cb8c --- /dev/null +++ b/api/gen/proto/go/teleport/devicetrust/v1/device_source.pb.go @@ -0,0 +1,250 @@ +// Copyright 2023 Gravitational, Inc +// +// 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. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.30.0 +// protoc (unknown) +// source: teleport/devicetrust/v1/device_source.proto + +package v1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Origin of a device. +type DeviceOrigin int32 + +const ( + // Unspecified or absent origin. + DeviceOrigin_DEVICE_ORIGIN_UNSPECIFIED DeviceOrigin = 0 + // Devices originated from direct API usage. + DeviceOrigin_DEVICE_ORIGIN_API DeviceOrigin = 1 + // Devices originated from Jamf sync. + DeviceOrigin_DEVICE_ORIGIN_JAMF DeviceOrigin = 2 + // Source originated from Microsoft Intune sync. + DeviceOrigin_DEVICE_ORIGIN_INTUNE DeviceOrigin = 3 +) + +// Enum value maps for DeviceOrigin. +var ( + DeviceOrigin_name = map[int32]string{ + 0: "DEVICE_ORIGIN_UNSPECIFIED", + 1: "DEVICE_ORIGIN_API", + 2: "DEVICE_ORIGIN_JAMF", + 3: "DEVICE_ORIGIN_INTUNE", + } + DeviceOrigin_value = map[string]int32{ + "DEVICE_ORIGIN_UNSPECIFIED": 0, + "DEVICE_ORIGIN_API": 1, + "DEVICE_ORIGIN_JAMF": 2, + "DEVICE_ORIGIN_INTUNE": 3, + } +) + +func (x DeviceOrigin) Enum() *DeviceOrigin { + p := new(DeviceOrigin) + *p = x + return p +} + +func (x DeviceOrigin) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (DeviceOrigin) Descriptor() protoreflect.EnumDescriptor { + return file_teleport_devicetrust_v1_device_source_proto_enumTypes[0].Descriptor() +} + +func (DeviceOrigin) Type() protoreflect.EnumType { + return &file_teleport_devicetrust_v1_device_source_proto_enumTypes[0] +} + +func (x DeviceOrigin) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use DeviceOrigin.Descriptor instead. +func (DeviceOrigin) EnumDescriptor() ([]byte, []int) { + return file_teleport_devicetrust_v1_device_source_proto_rawDescGZIP(), []int{0} +} + +// Source of device, for devices that are managed by external systems +// (for example, MDMs). +type DeviceSource struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Name of the source. + // Matches the name of the corresponding MDM service, if applicable. + // Readonly. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Origin of the source. + // Readonly. + Origin DeviceOrigin `protobuf:"varint,2,opt,name=origin,proto3,enum=teleport.devicetrust.v1.DeviceOrigin" json:"origin,omitempty"` +} + +func (x *DeviceSource) Reset() { + *x = DeviceSource{} + if protoimpl.UnsafeEnabled { + mi := &file_teleport_devicetrust_v1_device_source_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeviceSource) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeviceSource) ProtoMessage() {} + +func (x *DeviceSource) ProtoReflect() protoreflect.Message { + mi := &file_teleport_devicetrust_v1_device_source_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeviceSource.ProtoReflect.Descriptor instead. +func (*DeviceSource) Descriptor() ([]byte, []int) { + return file_teleport_devicetrust_v1_device_source_proto_rawDescGZIP(), []int{0} +} + +func (x *DeviceSource) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *DeviceSource) GetOrigin() DeviceOrigin { + if x != nil { + return x.Origin + } + return DeviceOrigin_DEVICE_ORIGIN_UNSPECIFIED +} + +var File_teleport_devicetrust_v1_device_source_proto protoreflect.FileDescriptor + +var file_teleport_devicetrust_v1_device_source_proto_rawDesc = []byte{ + 0x0a, 0x2b, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2f, 0x76, 0x31, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x17, 0x74, + 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, + 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x22, 0x61, 0x0a, 0x0c, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3d, 0x0a, 0x06, 0x6f, 0x72, + 0x69, 0x67, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x25, 0x2e, 0x74, 0x65, 0x6c, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x4f, 0x72, 0x69, 0x67, 0x69, + 0x6e, 0x52, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x2a, 0x76, 0x0a, 0x0c, 0x44, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x12, 0x1d, 0x0a, 0x19, 0x44, 0x45, 0x56, + 0x49, 0x43, 0x45, 0x5f, 0x4f, 0x52, 0x49, 0x47, 0x49, 0x4e, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, + 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x44, 0x45, 0x56, 0x49, + 0x43, 0x45, 0x5f, 0x4f, 0x52, 0x49, 0x47, 0x49, 0x4e, 0x5f, 0x41, 0x50, 0x49, 0x10, 0x01, 0x12, + 0x16, 0x0a, 0x12, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x4f, 0x52, 0x49, 0x47, 0x49, 0x4e, + 0x5f, 0x4a, 0x41, 0x4d, 0x46, 0x10, 0x02, 0x12, 0x18, 0x0a, 0x14, 0x44, 0x45, 0x56, 0x49, 0x43, + 0x45, 0x5f, 0x4f, 0x52, 0x49, 0x47, 0x49, 0x4e, 0x5f, 0x49, 0x4e, 0x54, 0x55, 0x4e, 0x45, 0x10, + 0x03, 0x42, 0x4c, 0x5a, 0x4a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x67, 0x72, 0x61, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, + 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2f, 0x76, 0x31, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_teleport_devicetrust_v1_device_source_proto_rawDescOnce sync.Once + file_teleport_devicetrust_v1_device_source_proto_rawDescData = file_teleport_devicetrust_v1_device_source_proto_rawDesc +) + +func file_teleport_devicetrust_v1_device_source_proto_rawDescGZIP() []byte { + file_teleport_devicetrust_v1_device_source_proto_rawDescOnce.Do(func() { + file_teleport_devicetrust_v1_device_source_proto_rawDescData = protoimpl.X.CompressGZIP(file_teleport_devicetrust_v1_device_source_proto_rawDescData) + }) + return file_teleport_devicetrust_v1_device_source_proto_rawDescData +} + +var file_teleport_devicetrust_v1_device_source_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_teleport_devicetrust_v1_device_source_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_teleport_devicetrust_v1_device_source_proto_goTypes = []interface{}{ + (DeviceOrigin)(0), // 0: teleport.devicetrust.v1.DeviceOrigin + (*DeviceSource)(nil), // 1: teleport.devicetrust.v1.DeviceSource +} +var file_teleport_devicetrust_v1_device_source_proto_depIdxs = []int32{ + 0, // 0: teleport.devicetrust.v1.DeviceSource.origin:type_name -> teleport.devicetrust.v1.DeviceOrigin + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_teleport_devicetrust_v1_device_source_proto_init() } +func file_teleport_devicetrust_v1_device_source_proto_init() { + if File_teleport_devicetrust_v1_device_source_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_teleport_devicetrust_v1_device_source_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeviceSource); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_teleport_devicetrust_v1_device_source_proto_rawDesc, + NumEnums: 1, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_teleport_devicetrust_v1_device_source_proto_goTypes, + DependencyIndexes: file_teleport_devicetrust_v1_device_source_proto_depIdxs, + EnumInfos: file_teleport_devicetrust_v1_device_source_proto_enumTypes, + MessageInfos: file_teleport_devicetrust_v1_device_source_proto_msgTypes, + }.Build() + File_teleport_devicetrust_v1_device_source_proto = out.File + file_teleport_devicetrust_v1_device_source_proto_rawDesc = nil + file_teleport_devicetrust_v1_device_source_proto_goTypes = nil + file_teleport_devicetrust_v1_device_source_proto_depIdxs = nil +} diff --git a/api/gen/proto/go/teleport/devicetrust/v1/devicetrust_service.pb.go b/api/gen/proto/go/teleport/devicetrust/v1/devicetrust_service.pb.go index b57dd63d99c6a..176836f634bc2 100644 --- a/api/gen/proto/go/teleport/devicetrust/v1/devicetrust_service.pb.go +++ b/api/gen/proto/go/teleport/devicetrust/v1/devicetrust_service.pb.go @@ -91,6 +91,115 @@ func (DeviceView) EnumDescriptor() ([]byte, []int) { return file_teleport_devicetrust_v1_devicetrust_service_proto_rawDescGZIP(), []int{0} } +// Mode of sync for SyncInventory. +type SyncInventoryMode int32 + +const ( + SyncInventoryMode_SYNC_INVENTORY_MODE_UNSPECIFIED SyncInventoryMode = 0 + // Partial inventory sync; not all devices in the external inventory are sent + // to the stream. + // Partial sync precludes inventory cleanup. + SyncInventoryMode_SYNC_INVENTORY_MODE_PARTIAL SyncInventoryMode = 1 + // Full inventory sync; all devices in the external inventory must be sent to + // the stream. + // Full sync allows for handling of missing devices. + SyncInventoryMode_SYNC_INVENTORY_MODE_FULL SyncInventoryMode = 2 +) + +// Enum value maps for SyncInventoryMode. +var ( + SyncInventoryMode_name = map[int32]string{ + 0: "SYNC_INVENTORY_MODE_UNSPECIFIED", + 1: "SYNC_INVENTORY_MODE_PARTIAL", + 2: "SYNC_INVENTORY_MODE_FULL", + } + SyncInventoryMode_value = map[string]int32{ + "SYNC_INVENTORY_MODE_UNSPECIFIED": 0, + "SYNC_INVENTORY_MODE_PARTIAL": 1, + "SYNC_INVENTORY_MODE_FULL": 2, + } +) + +func (x SyncInventoryMode) Enum() *SyncInventoryMode { + p := new(SyncInventoryMode) + *p = x + return p +} + +func (x SyncInventoryMode) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (SyncInventoryMode) Descriptor() protoreflect.EnumDescriptor { + return file_teleport_devicetrust_v1_devicetrust_service_proto_enumTypes[1].Descriptor() +} + +func (SyncInventoryMode) Type() protoreflect.EnumType { + return &file_teleport_devicetrust_v1_devicetrust_service_proto_enumTypes[1] +} + +func (x SyncInventoryMode) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use SyncInventoryMode.Descriptor instead. +func (SyncInventoryMode) EnumDescriptor() ([]byte, []int) { + return file_teleport_devicetrust_v1_devicetrust_service_proto_rawDescGZIP(), []int{1} +} + +// Device action for SyncInventory. +// Typically triggered at the end of FULL syncs.. +type SyncInventoryDeviceAction int32 + +const ( + SyncInventoryDeviceAction_SYNC_INVENTORY_DEVICE_ACTION_UNSPECIFIED SyncInventoryDeviceAction = 0 + // Noop is a no-action. + SyncInventoryDeviceAction_SYNC_INVENTORY_DEVICE_ACTION_NOOP SyncInventoryDeviceAction = 1 + // Deletes the device if the action condition is met. + SyncInventoryDeviceAction_SYNC_INVENTORY_DEVICE_ACTION_DELETE SyncInventoryDeviceAction = 2 +) + +// Enum value maps for SyncInventoryDeviceAction. +var ( + SyncInventoryDeviceAction_name = map[int32]string{ + 0: "SYNC_INVENTORY_DEVICE_ACTION_UNSPECIFIED", + 1: "SYNC_INVENTORY_DEVICE_ACTION_NOOP", + 2: "SYNC_INVENTORY_DEVICE_ACTION_DELETE", + } + SyncInventoryDeviceAction_value = map[string]int32{ + "SYNC_INVENTORY_DEVICE_ACTION_UNSPECIFIED": 0, + "SYNC_INVENTORY_DEVICE_ACTION_NOOP": 1, + "SYNC_INVENTORY_DEVICE_ACTION_DELETE": 2, + } +) + +func (x SyncInventoryDeviceAction) Enum() *SyncInventoryDeviceAction { + p := new(SyncInventoryDeviceAction) + *p = x + return p +} + +func (x SyncInventoryDeviceAction) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (SyncInventoryDeviceAction) Descriptor() protoreflect.EnumDescriptor { + return file_teleport_devicetrust_v1_devicetrust_service_proto_enumTypes[2].Descriptor() +} + +func (SyncInventoryDeviceAction) Type() protoreflect.EnumType { + return &file_teleport_devicetrust_v1_devicetrust_service_proto_enumTypes[2] +} + +func (x SyncInventoryDeviceAction) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use SyncInventoryDeviceAction.Descriptor instead. +func (SyncInventoryDeviceAction) EnumDescriptor() ([]byte, []int) { + return file_teleport_devicetrust_v1_devicetrust_service_proto_rawDescGZIP(), []int{2} +} + // Request for CreateDevice. type CreateDeviceRequest struct { state protoimpl.MessageState @@ -737,6 +846,9 @@ type DeviceOrStatus struct { // ID of the created device. // Only present if the status is OK. Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` + // If true the action attempted against the device was a delete, instead of a + // create or update. + Deleted bool `protobuf:"varint,3,opt,name=deleted,proto3" json:"deleted,omitempty"` } func (x *DeviceOrStatus) Reset() { @@ -785,6 +897,13 @@ func (x *DeviceOrStatus) GetId() string { return "" } +func (x *DeviceOrStatus) GetDeleted() bool { + if x != nil { + return x.Deleted + } + return false +} + // Request for CreateDeviceEnrollToken. type CreateDeviceEnrollTokenRequest struct { state protoimpl.MessageState @@ -793,6 +912,10 @@ type CreateDeviceEnrollTokenRequest struct { // ID of the device. DeviceId string `protobuf:"bytes,1,opt,name=device_id,json=deviceId,proto3" json:"device_id,omitempty"` + // Device collected data. + // Used to authorize issuance of device enrollment tokens for auto-enrollment. + // Not required otherwise. + DeviceData *DeviceCollectedData `protobuf:"bytes,2,opt,name=device_data,json=deviceData,proto3" json:"device_data,omitempty"` } func (x *CreateDeviceEnrollTokenRequest) Reset() { @@ -834,6 +957,13 @@ func (x *CreateDeviceEnrollTokenRequest) GetDeviceId() string { return "" } +func (x *CreateDeviceEnrollTokenRequest) GetDeviceData() *DeviceCollectedData { + if x != nil { + return x.DeviceData + } + return nil +} + // Request for EnrollDevice. // // macOS enrollment flow: @@ -1626,6 +1756,477 @@ func (x *AuthenticateDeviceChallengeResponse) GetSignature() []byte { return nil } +// Request for SyncInventory. +// +// A typical message sequence is as follows: +// (-> means client-to-server, <- means server-to-client) +// -> SyncInventoryStart +// <- SyncInventoryAck +// (loop) +// -> SyncInventoryDevices (add/remove devices) +// <- SyncInventoryResult +// (end loop) +// -> SyncInventoryEnd +// <- SyncInventoryResult (missing devices) +type SyncInventoryRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Payload: + // + // *SyncInventoryRequest_Start + // *SyncInventoryRequest_End + // *SyncInventoryRequest_DevicesToUpsert + // *SyncInventoryRequest_DevicesToRemove + Payload isSyncInventoryRequest_Payload `protobuf_oneof:"payload"` +} + +func (x *SyncInventoryRequest) Reset() { + *x = SyncInventoryRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_teleport_devicetrust_v1_devicetrust_service_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SyncInventoryRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SyncInventoryRequest) ProtoMessage() {} + +func (x *SyncInventoryRequest) ProtoReflect() protoreflect.Message { + mi := &file_teleport_devicetrust_v1_devicetrust_service_proto_msgTypes[25] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SyncInventoryRequest.ProtoReflect.Descriptor instead. +func (*SyncInventoryRequest) Descriptor() ([]byte, []int) { + return file_teleport_devicetrust_v1_devicetrust_service_proto_rawDescGZIP(), []int{25} +} + +func (m *SyncInventoryRequest) GetPayload() isSyncInventoryRequest_Payload { + if m != nil { + return m.Payload + } + return nil +} + +func (x *SyncInventoryRequest) GetStart() *SyncInventoryStart { + if x, ok := x.GetPayload().(*SyncInventoryRequest_Start); ok { + return x.Start + } + return nil +} + +func (x *SyncInventoryRequest) GetEnd() *SyncInventoryEnd { + if x, ok := x.GetPayload().(*SyncInventoryRequest_End); ok { + return x.End + } + return nil +} + +func (x *SyncInventoryRequest) GetDevicesToUpsert() *SyncInventoryDevices { + if x, ok := x.GetPayload().(*SyncInventoryRequest_DevicesToUpsert); ok { + return x.DevicesToUpsert + } + return nil +} + +func (x *SyncInventoryRequest) GetDevicesToRemove() *SyncInventoryDevices { + if x, ok := x.GetPayload().(*SyncInventoryRequest_DevicesToRemove); ok { + return x.DevicesToRemove + } + return nil +} + +type isSyncInventoryRequest_Payload interface { + isSyncInventoryRequest_Payload() +} + +type SyncInventoryRequest_Start struct { + Start *SyncInventoryStart `protobuf:"bytes,1,opt,name=start,proto3,oneof"` +} + +type SyncInventoryRequest_End struct { + End *SyncInventoryEnd `protobuf:"bytes,2,opt,name=end,proto3,oneof"` +} + +type SyncInventoryRequest_DevicesToUpsert struct { + DevicesToUpsert *SyncInventoryDevices `protobuf:"bytes,3,opt,name=devices_to_upsert,json=devicesToUpsert,proto3,oneof"` +} + +type SyncInventoryRequest_DevicesToRemove struct { + DevicesToRemove *SyncInventoryDevices `protobuf:"bytes,4,opt,name=devices_to_remove,json=devicesToRemove,proto3,oneof"` +} + +func (*SyncInventoryRequest_Start) isSyncInventoryRequest_Payload() {} + +func (*SyncInventoryRequest_End) isSyncInventoryRequest_Payload() {} + +func (*SyncInventoryRequest_DevicesToUpsert) isSyncInventoryRequest_Payload() {} + +func (*SyncInventoryRequest_DevicesToRemove) isSyncInventoryRequest_Payload() {} + +// Response for SyncInventory. +type SyncInventoryResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Payload: + // + // *SyncInventoryResponse_Ack + // *SyncInventoryResponse_Result + Payload isSyncInventoryResponse_Payload `protobuf_oneof:"payload"` +} + +func (x *SyncInventoryResponse) Reset() { + *x = SyncInventoryResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_teleport_devicetrust_v1_devicetrust_service_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SyncInventoryResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SyncInventoryResponse) ProtoMessage() {} + +func (x *SyncInventoryResponse) ProtoReflect() protoreflect.Message { + mi := &file_teleport_devicetrust_v1_devicetrust_service_proto_msgTypes[26] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SyncInventoryResponse.ProtoReflect.Descriptor instead. +func (*SyncInventoryResponse) Descriptor() ([]byte, []int) { + return file_teleport_devicetrust_v1_devicetrust_service_proto_rawDescGZIP(), []int{26} +} + +func (m *SyncInventoryResponse) GetPayload() isSyncInventoryResponse_Payload { + if m != nil { + return m.Payload + } + return nil +} + +func (x *SyncInventoryResponse) GetAck() *SyncInventoryAck { + if x, ok := x.GetPayload().(*SyncInventoryResponse_Ack); ok { + return x.Ack + } + return nil +} + +func (x *SyncInventoryResponse) GetResult() *SyncInventoryResult { + if x, ok := x.GetPayload().(*SyncInventoryResponse_Result); ok { + return x.Result + } + return nil +} + +type isSyncInventoryResponse_Payload interface { + isSyncInventoryResponse_Payload() +} + +type SyncInventoryResponse_Ack struct { + Ack *SyncInventoryAck `protobuf:"bytes,1,opt,name=ack,proto3,oneof"` +} + +type SyncInventoryResponse_Result struct { + Result *SyncInventoryResult `protobuf:"bytes,2,opt,name=result,proto3,oneof"` +} + +func (*SyncInventoryResponse_Ack) isSyncInventoryResponse_Payload() {} + +func (*SyncInventoryResponse_Result) isSyncInventoryResponse_Payload() {} + +// SyncInventoryStart starts the inventory sync. +type SyncInventoryStart struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Source of the inventory sync. + // Acquired from the mTLS certificate for MDM services, must be explicitly + // supplied otherwise. + // This source is used for all devices. The `source` field in individual + // devices is ignored by this RPC. + Source *DeviceSource `protobuf:"bytes,1,opt,name=source,proto3" json:"source,omitempty"` + // Mode of syncing. + // Required. + Mode SyncInventoryMode `protobuf:"varint,2,opt,name=mode,proto3,enum=teleport.devicetrust.v1.SyncInventoryMode" json:"mode,omitempty"` + // Action for devices missing from the external inventory, but present in + // Teleport. + // Only executed if mode is FULL and mdm_sync_successful is set to true in the + // SyncInventoryEnd message. + OnMissingAction SyncInventoryDeviceAction `protobuf:"varint,3,opt,name=on_missing_action,json=onMissingAction,proto3,enum=teleport.devicetrust.v1.SyncInventoryDeviceAction" json:"on_missing_action,omitempty"` +} + +func (x *SyncInventoryStart) Reset() { + *x = SyncInventoryStart{} + if protoimpl.UnsafeEnabled { + mi := &file_teleport_devicetrust_v1_devicetrust_service_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SyncInventoryStart) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SyncInventoryStart) ProtoMessage() {} + +func (x *SyncInventoryStart) ProtoReflect() protoreflect.Message { + mi := &file_teleport_devicetrust_v1_devicetrust_service_proto_msgTypes[27] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SyncInventoryStart.ProtoReflect.Descriptor instead. +func (*SyncInventoryStart) Descriptor() ([]byte, []int) { + return file_teleport_devicetrust_v1_devicetrust_service_proto_rawDescGZIP(), []int{27} +} + +func (x *SyncInventoryStart) GetSource() *DeviceSource { + if x != nil { + return x.Source + } + return nil +} + +func (x *SyncInventoryStart) GetMode() SyncInventoryMode { + if x != nil { + return x.Mode + } + return SyncInventoryMode_SYNC_INVENTORY_MODE_UNSPECIFIED +} + +func (x *SyncInventoryStart) GetOnMissingAction() SyncInventoryDeviceAction { + if x != nil { + return x.OnMissingAction + } + return SyncInventoryDeviceAction_SYNC_INVENTORY_DEVICE_ACTION_UNSPECIFIED +} + +// SyncInventoryEnd ends the inventory sync, signaling that no more +// SyncInventoryDevices messages will be sent by the client. +type SyncInventoryEnd struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // True if the external sync was fully successful (for example, all reads from + // an MDM API were successful). + // If false the sync's on_missing_action is skipped. + ExternalSyncSuccessful bool `protobuf:"varint,1,opt,name=external_sync_successful,json=externalSyncSuccessful,proto3" json:"external_sync_successful,omitempty"` +} + +func (x *SyncInventoryEnd) Reset() { + *x = SyncInventoryEnd{} + if protoimpl.UnsafeEnabled { + mi := &file_teleport_devicetrust_v1_devicetrust_service_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SyncInventoryEnd) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SyncInventoryEnd) ProtoMessage() {} + +func (x *SyncInventoryEnd) ProtoReflect() protoreflect.Message { + mi := &file_teleport_devicetrust_v1_devicetrust_service_proto_msgTypes[28] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SyncInventoryEnd.ProtoReflect.Descriptor instead. +func (*SyncInventoryEnd) Descriptor() ([]byte, []int) { + return file_teleport_devicetrust_v1_devicetrust_service_proto_rawDescGZIP(), []int{28} +} + +func (x *SyncInventoryEnd) GetExternalSyncSuccessful() bool { + if x != nil { + return x.ExternalSyncSuccessful + } + return false +} + +// SyncInventoryDevices transports devices to add/update/remove. +// Removals only need identifying fields to be set. +type SyncInventoryDevices struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Devices to sync. + Devices []*Device `protobuf:"bytes,1,rep,name=devices,proto3" json:"devices,omitempty"` +} + +func (x *SyncInventoryDevices) Reset() { + *x = SyncInventoryDevices{} + if protoimpl.UnsafeEnabled { + mi := &file_teleport_devicetrust_v1_devicetrust_service_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SyncInventoryDevices) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SyncInventoryDevices) ProtoMessage() {} + +func (x *SyncInventoryDevices) ProtoReflect() protoreflect.Message { + mi := &file_teleport_devicetrust_v1_devicetrust_service_proto_msgTypes[29] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SyncInventoryDevices.ProtoReflect.Descriptor instead. +func (*SyncInventoryDevices) Descriptor() ([]byte, []int) { + return file_teleport_devicetrust_v1_devicetrust_service_proto_rawDescGZIP(), []int{29} +} + +func (x *SyncInventoryDevices) GetDevices() []*Device { + if x != nil { + return x.Devices + } + return nil +} + +// SyncInventoryAck is used to confirm successful processing of messages that +// lack a more specific response. +type SyncInventoryAck struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *SyncInventoryAck) Reset() { + *x = SyncInventoryAck{} + if protoimpl.UnsafeEnabled { + mi := &file_teleport_devicetrust_v1_devicetrust_service_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SyncInventoryAck) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SyncInventoryAck) ProtoMessage() {} + +func (x *SyncInventoryAck) ProtoReflect() protoreflect.Message { + mi := &file_teleport_devicetrust_v1_devicetrust_service_proto_msgTypes[30] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SyncInventoryAck.ProtoReflect.Descriptor instead. +func (*SyncInventoryAck) Descriptor() ([]byte, []int) { + return file_teleport_devicetrust_v1_devicetrust_service_proto_rawDescGZIP(), []int{30} +} + +// SyncInventoryResult is the response for SyncInventoryDevices or +// SyncInventoryEnd +// It lists all synced/deleted devices. +type SyncInventoryResult struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Devices modified, in the same order as the input when applicable. + Devices []*DeviceOrStatus `protobuf:"bytes,1,rep,name=devices,proto3" json:"devices,omitempty"` +} + +func (x *SyncInventoryResult) Reset() { + *x = SyncInventoryResult{} + if protoimpl.UnsafeEnabled { + mi := &file_teleport_devicetrust_v1_devicetrust_service_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SyncInventoryResult) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SyncInventoryResult) ProtoMessage() {} + +func (x *SyncInventoryResult) ProtoReflect() protoreflect.Message { + mi := &file_teleport_devicetrust_v1_devicetrust_service_proto_msgTypes[31] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SyncInventoryResult.ProtoReflect.Descriptor instead. +func (*SyncInventoryResult) Descriptor() ([]byte, []int) { + return file_teleport_devicetrust_v1_devicetrust_service_proto_rawDescGZIP(), []int{31} +} + +func (x *SyncInventoryResult) GetDevices() []*DeviceOrStatus { + if x != nil { + return x.Devices + } + return nil +} + var File_teleport_devicetrust_v1_devicetrust_service_proto protoreflect.FileDescriptor var file_teleport_devicetrust_v1_devicetrust_service_proto_rawDesc = []byte{ @@ -1648,283 +2249,382 @@ var file_teleport_devicetrust_v1_devicetrust_service_proto_rawDesc = []byte{ 0x31, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2f, 0x76, 0x31, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x65, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x1a, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x64, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, - 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x22, 0xac, 0x01, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x37, 0x0a, 0x06, 0x64, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x74, 0x65, - 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, - 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x06, 0x64, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x65, - 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x11, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x2c, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x61, - 0x73, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x10, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x73, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x22, 0x8b, 0x01, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x37, 0x0a, 0x06, 0x64, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x74, 0x65, 0x6c, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, - 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x06, 0x64, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x12, 0x3b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x61, - 0x73, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, - 0x4d, 0x61, 0x73, 0x6b, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x73, 0x6b, - 0x22, 0x7c, 0x0a, 0x13, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x74, 0x6f, 0x1a, 0x2b, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x64, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2f, 0x76, 0x31, 0x2f, 0x64, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, + 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x74, 0x72, 0x75, 0x73, 0x74, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x63, 0x65, + 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x22, 0xac, 0x01, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x37, 0x0a, 0x06, 0x64, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, + 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x06, 0x64, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x65, 0x6e, 0x72, 0x6f, + 0x6c, 0x6c, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x54, 0x6f, 0x6b, 0x65, + 0x6e, 0x12, 0x2c, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x61, 0x73, 0x5f, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x63, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x73, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, + 0x8b, 0x01, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x37, 0x0a, 0x06, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x06, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x12, 0x2c, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x61, 0x73, 0x5f, 0x72, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x63, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x41, 0x73, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0x32, - 0x0a, 0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x49, 0x64, 0x22, 0x30, 0x0a, 0x12, 0x46, 0x69, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x09, 0x69, 0x64, 0x5f, 0x6f, - 0x72, 0x5f, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x69, 0x64, 0x4f, - 0x72, 0x54, 0x61, 0x67, 0x22, 0x50, 0x0a, 0x13, 0x46, 0x69, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x07, 0x64, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x74, - 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, - 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x07, 0x64, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x22, 0x2f, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x22, 0x89, 0x01, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, - 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, - 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, - 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x37, 0x0a, 0x04, 0x76, 0x69, - 0x65, 0x77, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x56, 0x69, 0x65, 0x77, 0x52, 0x04, 0x76, - 0x69, 0x65, 0x77, 0x22, 0x78, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x07, 0x64, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x74, 0x65, - 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, - 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x07, 0x64, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, - 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, - 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x83, 0x01, - 0x0a, 0x18, 0x42, 0x75, 0x6c, 0x6b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x39, 0x0a, 0x07, 0x64, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x74, 0x65, - 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, - 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x07, 0x64, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, - 0x61, 0x73, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x10, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x73, 0x52, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x22, 0x5e, 0x0a, 0x19, 0x42, 0x75, 0x6c, 0x6b, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x41, 0x0a, 0x07, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x27, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x4f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x07, 0x64, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x73, 0x22, 0x4c, 0x0a, 0x0e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x4f, 0x72, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2a, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, - 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, - 0x64, 0x22, 0x3d, 0x0a, 0x1e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x45, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, - 0x22, 0xd4, 0x01, 0x0a, 0x13, 0x45, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x44, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3f, 0x0a, 0x04, 0x69, 0x6e, 0x69, 0x74, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, - 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x45, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x6e, 0x69, - 0x74, 0x48, 0x00, 0x52, 0x04, 0x69, 0x6e, 0x69, 0x74, 0x12, 0x71, 0x0a, 0x18, 0x6d, 0x61, 0x63, - 0x6f, 0x73, 0x5f, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x5f, 0x72, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x74, 0x65, - 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, - 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x4f, 0x53, 0x45, 0x6e, 0x72, 0x6f, 0x6c, - 0x6c, 0x43, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x48, 0x00, 0x52, 0x16, 0x6d, 0x61, 0x63, 0x6f, 0x73, 0x43, 0x68, 0x61, 0x6c, 0x6c, - 0x65, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x09, 0x0a, 0x07, - 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0xc5, 0x01, 0x0a, 0x14, 0x45, 0x6e, 0x72, 0x6f, - 0x6c, 0x6c, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x48, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x2c, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x72, 0x6f, - 0x6c, 0x6c, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x48, - 0x00, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x58, 0x0a, 0x0f, 0x6d, 0x61, - 0x63, 0x6f, 0x73, 0x5f, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, - 0x63, 0x4f, 0x53, 0x45, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x43, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, - 0x67, 0x65, 0x48, 0x00, 0x52, 0x0e, 0x6d, 0x61, 0x63, 0x6f, 0x73, 0x43, 0x68, 0x61, 0x6c, 0x6c, - 0x65, 0x6e, 0x67, 0x65, 0x42, 0x09, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, - 0xdf, 0x01, 0x0a, 0x10, 0x45, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x49, 0x6e, 0x69, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x72, - 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x49, 0x64, 0x12, - 0x4d, 0x0a, 0x0b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, + 0x12, 0x3b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, + 0x6b, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x73, 0x6b, 0x22, 0x7c, 0x0a, + 0x13, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x37, 0x0a, 0x06, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x44, 0x61, - 0x74, 0x61, 0x52, 0x0a, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x44, 0x61, 0x74, 0x61, 0x12, 0x41, - 0x0a, 0x05, 0x6d, 0x61, 0x63, 0x6f, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, - 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, - 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x4f, 0x53, 0x45, 0x6e, 0x72, - 0x6f, 0x6c, 0x6c, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x05, 0x6d, 0x61, 0x63, 0x6f, - 0x73, 0x22, 0x4e, 0x0a, 0x13, 0x45, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x44, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x37, 0x0a, 0x06, 0x64, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x06, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2c, 0x0a, + 0x12, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x61, 0x73, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x41, 0x73, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0x32, 0x0a, 0x13, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x22, + 0x30, 0x0a, 0x12, 0x46, 0x69, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x09, 0x69, 0x64, 0x5f, 0x6f, 0x72, 0x5f, 0x74, + 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x69, 0x64, 0x4f, 0x72, 0x54, 0x61, + 0x67, 0x22, 0x50, 0x0a, 0x13, 0x46, 0x69, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x07, 0x64, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x74, 0x65, 0x6c, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x07, 0x64, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x73, 0x22, 0x2f, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x49, 0x64, 0x22, 0x89, 0x01, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, + 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, + 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, + 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, + 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x37, 0x0a, 0x04, 0x76, 0x69, 0x65, 0x77, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x56, 0x69, 0x65, 0x77, 0x52, 0x04, 0x76, 0x69, 0x65, 0x77, + 0x22, 0x78, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x07, 0x64, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x06, 0x64, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x22, 0x3a, 0x0a, 0x12, 0x4d, 0x61, 0x63, 0x4f, 0x53, 0x45, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, - 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x75, 0x62, 0x6c, 0x69, - 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x0c, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x44, 0x65, 0x72, 0x22, 0x34, 0x0a, - 0x14, 0x4d, 0x61, 0x63, 0x4f, 0x53, 0x45, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x43, 0x68, 0x61, 0x6c, - 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, - 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, - 0x6e, 0x67, 0x65, 0x22, 0x3c, 0x0a, 0x1c, 0x4d, 0x61, 0x63, 0x4f, 0x53, 0x45, 0x6e, 0x72, 0x6f, - 0x6c, 0x6c, 0x43, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x22, 0xdc, 0x01, 0x0a, 0x19, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, - 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x45, 0x0a, 0x04, 0x69, 0x6e, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, + 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x07, 0x64, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, + 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x83, 0x01, 0x0a, 0x18, 0x42, + 0x75, 0x6c, 0x6b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x39, 0x0a, 0x07, 0x64, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, + 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x07, 0x64, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x61, 0x73, 0x5f, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x73, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x22, 0x5e, 0x0a, 0x19, 0x42, 0x75, 0x6c, 0x6b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, + 0x07, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, + 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x4f, + 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x07, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, + 0x22, 0x66, 0x0a, 0x0e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x4f, 0x72, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x12, 0x2a, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0e, + 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, + 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x22, 0x8c, 0x01, 0x0a, 0x1e, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x64, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x12, 0x4d, 0x0a, 0x0b, 0x64, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, + 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, + 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, + 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x52, 0x0a, 0x64, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x44, 0x61, 0x74, 0x61, 0x22, 0xd4, 0x01, 0x0a, 0x13, 0x45, 0x6e, 0x72, 0x6f, + 0x6c, 0x6c, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x3f, 0x0a, 0x04, 0x69, 0x6e, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, - 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, - 0x63, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x6e, 0x69, 0x74, 0x48, 0x00, - 0x52, 0x04, 0x69, 0x6e, 0x69, 0x74, 0x12, 0x6d, 0x0a, 0x12, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, + 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x44, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x49, 0x6e, 0x69, 0x74, 0x48, 0x00, 0x52, 0x04, 0x69, 0x6e, 0x69, 0x74, + 0x12, 0x71, 0x0a, 0x18, 0x6d, 0x61, 0x63, 0x6f, 0x73, 0x5f, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x3c, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, - 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, - 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x48, 0x00, 0x52, 0x11, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x09, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, - 0x22, 0xd7, 0x01, 0x0a, 0x1a, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, - 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x54, 0x0a, 0x09, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, - 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, - 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x48, 0x00, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6c, - 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x12, 0x58, 0x0a, 0x11, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x63, 0x65, - 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x29, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x43, - 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x48, 0x00, 0x52, 0x10, 0x75, - 0x73, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x42, - 0x09, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0xe4, 0x01, 0x0a, 0x16, 0x41, + 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, + 0x4f, 0x53, 0x45, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x43, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x16, 0x6d, 0x61, 0x63, + 0x6f, 0x73, 0x43, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x42, 0x09, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0xc5, + 0x01, 0x0a, 0x14, 0x45, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x48, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, + 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, + 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, + 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x48, 0x00, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, + 0x73, 0x12, 0x58, 0x0a, 0x0f, 0x6d, 0x61, 0x63, 0x6f, 0x73, 0x5f, 0x63, 0x68, 0x61, 0x6c, 0x6c, + 0x65, 0x6e, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x74, 0x65, 0x6c, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, + 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x4f, 0x53, 0x45, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, + 0x43, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x48, 0x00, 0x52, 0x0e, 0x6d, 0x61, 0x63, + 0x6f, 0x73, 0x43, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x42, 0x09, 0x0a, 0x07, 0x70, + 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0xdf, 0x01, 0x0a, 0x10, 0x45, 0x6e, 0x72, 0x6f, 0x6c, + 0x6c, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x6e, 0x69, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x74, + 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, + 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x4d, 0x0a, 0x0b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x74, 0x65, + 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, + 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6c, 0x6c, + 0x65, 0x63, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x52, 0x0a, 0x64, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x44, 0x61, 0x74, 0x61, 0x12, 0x41, 0x0a, 0x05, 0x6d, 0x61, 0x63, 0x6f, 0x73, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, + 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4d, + 0x61, 0x63, 0x4f, 0x53, 0x45, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, + 0x64, 0x52, 0x05, 0x6d, 0x61, 0x63, 0x6f, 0x73, 0x22, 0x4e, 0x0a, 0x13, 0x45, 0x6e, 0x72, 0x6f, + 0x6c, 0x6c, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, + 0x37, 0x0a, 0x06, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1f, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x52, 0x06, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x22, 0x3a, 0x0a, 0x12, 0x4d, 0x61, 0x63, 0x4f, + 0x53, 0x45, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x24, + 0x0a, 0x0e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x64, 0x65, 0x72, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, + 0x79, 0x44, 0x65, 0x72, 0x22, 0x34, 0x0a, 0x14, 0x4d, 0x61, 0x63, 0x4f, 0x53, 0x45, 0x6e, 0x72, + 0x6f, 0x6c, 0x6c, 0x43, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x12, 0x1c, 0x0a, 0x09, + 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x09, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x22, 0x3c, 0x0a, 0x1c, 0x4d, 0x61, + 0x63, 0x4f, 0x53, 0x45, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x43, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, + 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, + 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, + 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0xdc, 0x01, 0x0a, 0x19, 0x41, 0x75, 0x74, + 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x45, 0x0a, 0x04, 0x69, 0x6e, 0x69, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, + 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x49, 0x6e, 0x69, 0x74, 0x12, 0x56, 0x0a, 0x11, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x63, 0x65, - 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x29, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x43, - 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x52, 0x10, 0x75, 0x73, 0x65, - 0x72, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x12, 0x23, 0x0a, - 0x0d, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, - 0x49, 0x64, 0x12, 0x4d, 0x0a, 0x0b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x64, 0x61, 0x74, - 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, + 0x65, 0x49, 0x6e, 0x69, 0x74, 0x48, 0x00, 0x52, 0x04, 0x69, 0x6e, 0x69, 0x74, 0x12, 0x6d, 0x0a, + 0x12, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3c, 0x2e, 0x74, 0x65, 0x6c, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, + 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x11, 0x63, 0x68, 0x61, 0x6c, 0x6c, + 0x65, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x09, 0x0a, 0x07, + 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0xd7, 0x01, 0x0a, 0x1a, 0x41, 0x75, 0x74, 0x68, + 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x09, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, + 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x74, 0x65, 0x6c, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, + 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x48, + 0x00, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x12, 0x58, 0x0a, 0x11, + 0x75, 0x73, 0x65, 0x72, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, + 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, + 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x65, 0x73, 0x48, 0x00, 0x52, 0x10, 0x75, 0x73, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x42, 0x09, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, + 0x64, 0x22, 0xe4, 0x01, 0x0a, 0x16, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, + 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x6e, 0x69, 0x74, 0x12, 0x56, 0x0a, 0x11, + 0x75, 0x73, 0x65, 0x72, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, - 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x65, - 0x64, 0x44, 0x61, 0x74, 0x61, 0x52, 0x0a, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x44, 0x61, 0x74, - 0x61, 0x22, 0x3b, 0x0a, 0x1b, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, - 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, - 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x22, 0x43, - 0x0a, 0x23, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x44, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x43, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, - 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, - 0x75, 0x72, 0x65, 0x2a, 0x59, 0x0a, 0x0a, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x56, 0x69, 0x65, - 0x77, 0x12, 0x1b, 0x0a, 0x17, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x56, 0x49, 0x45, 0x57, - 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x14, - 0x0a, 0x10, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x56, 0x49, 0x45, 0x57, 0x5f, 0x4c, 0x49, - 0x53, 0x54, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x56, - 0x49, 0x45, 0x57, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x10, 0x02, 0x32, 0xa5, - 0x09, 0x0a, 0x12, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x54, 0x72, 0x75, 0x73, 0x74, 0x53, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5d, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2c, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x65, 0x73, 0x52, 0x10, 0x75, 0x73, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x65, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x72, 0x65, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x4d, 0x0a, 0x0b, 0x64, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, + 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, + 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x52, 0x0a, 0x64, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x44, 0x61, 0x74, 0x61, 0x22, 0x3b, 0x0a, 0x1b, 0x41, 0x75, 0x74, 0x68, + 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x68, + 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x68, 0x61, 0x6c, 0x6c, + 0x65, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6c, + 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x22, 0x43, 0x0a, 0x23, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, + 0x69, 0x63, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x68, 0x61, 0x6c, 0x6c, + 0x65, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, + 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0xdf, 0x02, 0x0a, 0x14, 0x53, + 0x79, 0x6e, 0x63, 0x49, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x43, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x79, 0x6e, + 0x63, 0x49, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x53, 0x74, 0x61, 0x72, 0x74, 0x48, + 0x00, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x3d, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, - 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x12, 0x5d, 0x0a, 0x0c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x65, + 0x53, 0x79, 0x6e, 0x63, 0x49, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x45, 0x6e, 0x64, + 0x48, 0x00, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x5b, 0x0a, 0x11, 0x64, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x73, 0x5f, 0x74, 0x6f, 0x5f, 0x75, 0x70, 0x73, 0x65, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x79, 0x6e, + 0x63, 0x49, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x73, 0x48, 0x00, 0x52, 0x0f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x55, 0x70, + 0x73, 0x65, 0x72, 0x74, 0x12, 0x5b, 0x0a, 0x11, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x5f, + 0x74, 0x6f, 0x5f, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x2d, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x49, 0x6e, + 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x48, 0x00, + 0x52, 0x0f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x54, 0x6f, 0x52, 0x65, 0x6d, 0x6f, 0x76, + 0x65, 0x42, 0x09, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0xa9, 0x01, 0x0a, + 0x15, 0x53, 0x79, 0x6e, 0x63, 0x49, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x03, 0x61, 0x63, 0x6b, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x79, + 0x6e, 0x63, 0x49, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x41, 0x63, 0x6b, 0x48, 0x00, + 0x52, 0x03, 0x61, 0x63, 0x6b, 0x12, 0x46, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x53, 0x79, 0x6e, 0x63, 0x49, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, + 0x75, 0x6c, 0x74, 0x48, 0x00, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x42, 0x09, 0x0a, + 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0xf3, 0x01, 0x0a, 0x12, 0x53, 0x79, 0x6e, + 0x63, 0x49, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, + 0x3d, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x25, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x3e, + 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2a, 0x2e, 0x74, + 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, + 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x49, 0x6e, 0x76, 0x65, 0x6e, + 0x74, 0x6f, 0x72, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x5e, + 0x0a, 0x11, 0x6f, 0x6e, 0x5f, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x32, 0x2e, 0x74, 0x65, 0x6c, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x49, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, + 0x79, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0f, 0x6f, + 0x6e, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x4c, + 0x0a, 0x10, 0x53, 0x79, 0x6e, 0x63, 0x49, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x45, + 0x6e, 0x64, 0x12, 0x38, 0x0a, 0x18, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x73, + 0x79, 0x6e, 0x63, 0x5f, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x79, + 0x6e, 0x63, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, 0x22, 0x51, 0x0a, 0x14, + 0x53, 0x79, 0x6e, 0x63, 0x49, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x44, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x73, 0x12, 0x39, 0x0a, 0x07, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x07, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x22, + 0x12, 0x0a, 0x10, 0x53, 0x79, 0x6e, 0x63, 0x49, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, + 0x41, 0x63, 0x6b, 0x22, 0x58, 0x0a, 0x13, 0x53, 0x79, 0x6e, 0x63, 0x49, 0x6e, 0x76, 0x65, 0x6e, + 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x41, 0x0a, 0x07, 0x64, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x74, 0x65, + 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, + 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x4f, 0x72, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x52, 0x07, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2a, 0x59, 0x0a, + 0x0a, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x56, 0x69, 0x65, 0x77, 0x12, 0x1b, 0x0a, 0x17, 0x44, + 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x56, 0x49, 0x45, 0x57, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, + 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x44, 0x45, 0x56, 0x49, + 0x43, 0x45, 0x5f, 0x56, 0x49, 0x45, 0x57, 0x5f, 0x4c, 0x49, 0x53, 0x54, 0x10, 0x01, 0x12, 0x18, + 0x0a, 0x14, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x56, 0x49, 0x45, 0x57, 0x5f, 0x52, 0x45, + 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x10, 0x02, 0x2a, 0x77, 0x0a, 0x11, 0x53, 0x79, 0x6e, 0x63, + 0x49, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, + 0x1f, 0x53, 0x59, 0x4e, 0x43, 0x5f, 0x49, 0x4e, 0x56, 0x45, 0x4e, 0x54, 0x4f, 0x52, 0x59, 0x5f, + 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, + 0x10, 0x00, 0x12, 0x1f, 0x0a, 0x1b, 0x53, 0x59, 0x4e, 0x43, 0x5f, 0x49, 0x4e, 0x56, 0x45, 0x4e, + 0x54, 0x4f, 0x52, 0x59, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x50, 0x41, 0x52, 0x54, 0x49, 0x41, + 0x4c, 0x10, 0x01, 0x12, 0x1c, 0x0a, 0x18, 0x53, 0x59, 0x4e, 0x43, 0x5f, 0x49, 0x4e, 0x56, 0x45, + 0x4e, 0x54, 0x4f, 0x52, 0x59, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x46, 0x55, 0x4c, 0x4c, 0x10, + 0x02, 0x2a, 0x99, 0x01, 0x0a, 0x19, 0x53, 0x79, 0x6e, 0x63, 0x49, 0x6e, 0x76, 0x65, 0x6e, 0x74, + 0x6f, 0x72, 0x79, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x2c, 0x0a, 0x28, 0x53, 0x59, 0x4e, 0x43, 0x5f, 0x49, 0x4e, 0x56, 0x45, 0x4e, 0x54, 0x4f, 0x52, + 0x59, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, + 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x25, 0x0a, + 0x21, 0x53, 0x59, 0x4e, 0x43, 0x5f, 0x49, 0x4e, 0x56, 0x45, 0x4e, 0x54, 0x4f, 0x52, 0x59, 0x5f, + 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, + 0x4f, 0x50, 0x10, 0x01, 0x12, 0x27, 0x0a, 0x23, 0x53, 0x59, 0x4e, 0x43, 0x5f, 0x49, 0x4e, 0x56, + 0x45, 0x4e, 0x54, 0x4f, 0x52, 0x59, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x41, 0x43, + 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x10, 0x02, 0x32, 0x99, 0x0a, + 0x0a, 0x12, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x54, 0x72, 0x75, 0x73, 0x74, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x12, 0x5d, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2c, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, - 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x12, 0x5d, 0x0a, 0x0c, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x44, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x12, 0x5d, 0x0a, 0x0c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2c, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, - 0x73, 0x65, 0x72, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x64, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x12, 0x54, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x12, 0x5d, 0x0a, 0x0c, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2c, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x68, 0x0a, 0x0b, 0x46, 0x69, 0x6e, 0x64, - 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x2b, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, - 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, - 0x31, 0x2e, 0x46, 0x69, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, - 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x46, - 0x69, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x57, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, - 0x29, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x74, 0x65, 0x6c, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, - 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x68, 0x0a, 0x0b, 0x4c, - 0x69, 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x2b, 0x2e, 0x74, 0x65, 0x6c, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, - 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, - 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, - 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7a, 0x0a, 0x11, 0x42, 0x75, 0x6c, 0x6b, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x31, 0x2e, 0x74, 0x65, 0x6c, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, - 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x6c, 0x6b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, - 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, - 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x6c, 0x6b, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x7e, 0x0a, 0x17, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x45, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x37, 0x2e, 0x74, - 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, - 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x45, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, - 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x54, 0x6f, 0x6b, 0x65, - 0x6e, 0x12, 0x6f, 0x0a, 0x0c, 0x45, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x44, 0x65, 0x76, 0x69, 0x63, + 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, + 0x65, 0x72, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1f, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x12, 0x54, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2c, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x72, 0x6f, - 0x6c, 0x6c, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x2d, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, - 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, - 0x30, 0x01, 0x12, 0x81, 0x01, 0x0a, 0x12, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, - 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x32, 0x2e, 0x74, 0x65, 0x6c, 0x65, + 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x68, 0x0a, 0x0b, 0x46, 0x69, 0x6e, 0x64, 0x44, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x2b, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x46, 0x69, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x69, + 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x57, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x29, + 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, - 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, - 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, - 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, - 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, - 0x63, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x4c, 0x5a, 0x4a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x61, 0x70, 0x69, 0x2f, - 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x2f, 0x74, 0x65, 0x6c, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, - 0x74, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x68, 0x0a, 0x0b, 0x4c, 0x69, + 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x2b, 0x2e, 0x74, 0x65, 0x6c, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7a, 0x0a, 0x11, 0x42, 0x75, 0x6c, 0x6b, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x31, 0x2e, 0x74, 0x65, 0x6c, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x6c, 0x6b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x74, + 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, + 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x6c, 0x6b, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x7e, 0x0a, 0x17, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x45, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x37, 0x2e, 0x74, 0x65, + 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, + 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x45, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, + 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x12, 0x6f, 0x0a, 0x0c, 0x45, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x12, 0x2c, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x72, 0x6f, 0x6c, + 0x6c, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, + 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x72, 0x6f, 0x6c, 0x6c, 0x44, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, + 0x01, 0x12, 0x81, 0x01, 0x0a, 0x12, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, + 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x32, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, + 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x44, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x74, + 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, + 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, + 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x28, 0x01, 0x30, 0x01, 0x12, 0x72, 0x0a, 0x0d, 0x53, 0x79, 0x6e, 0x63, 0x49, 0x6e, 0x76, + 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x2d, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x49, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x2e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, + 0x53, 0x79, 0x6e, 0x63, 0x49, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x4c, 0x5a, 0x4a, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x76, 0x69, 0x74, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x61, + 0x70, 0x69, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x2f, + 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, + 0x72, 0x75, 0x73, 0x74, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1939,94 +2639,118 @@ func file_teleport_devicetrust_v1_devicetrust_service_proto_rawDescGZIP() []byte return file_teleport_devicetrust_v1_devicetrust_service_proto_rawDescData } -var file_teleport_devicetrust_v1_devicetrust_service_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_teleport_devicetrust_v1_devicetrust_service_proto_msgTypes = make([]protoimpl.MessageInfo, 25) +var file_teleport_devicetrust_v1_devicetrust_service_proto_enumTypes = make([]protoimpl.EnumInfo, 3) +var file_teleport_devicetrust_v1_devicetrust_service_proto_msgTypes = make([]protoimpl.MessageInfo, 32) var file_teleport_devicetrust_v1_devicetrust_service_proto_goTypes = []interface{}{ (DeviceView)(0), // 0: teleport.devicetrust.v1.DeviceView - (*CreateDeviceRequest)(nil), // 1: teleport.devicetrust.v1.CreateDeviceRequest - (*UpdateDeviceRequest)(nil), // 2: teleport.devicetrust.v1.UpdateDeviceRequest - (*UpsertDeviceRequest)(nil), // 3: teleport.devicetrust.v1.UpsertDeviceRequest - (*DeleteDeviceRequest)(nil), // 4: teleport.devicetrust.v1.DeleteDeviceRequest - (*FindDevicesRequest)(nil), // 5: teleport.devicetrust.v1.FindDevicesRequest - (*FindDevicesResponse)(nil), // 6: teleport.devicetrust.v1.FindDevicesResponse - (*GetDeviceRequest)(nil), // 7: teleport.devicetrust.v1.GetDeviceRequest - (*ListDevicesRequest)(nil), // 8: teleport.devicetrust.v1.ListDevicesRequest - (*ListDevicesResponse)(nil), // 9: teleport.devicetrust.v1.ListDevicesResponse - (*BulkCreateDevicesRequest)(nil), // 10: teleport.devicetrust.v1.BulkCreateDevicesRequest - (*BulkCreateDevicesResponse)(nil), // 11: teleport.devicetrust.v1.BulkCreateDevicesResponse - (*DeviceOrStatus)(nil), // 12: teleport.devicetrust.v1.DeviceOrStatus - (*CreateDeviceEnrollTokenRequest)(nil), // 13: teleport.devicetrust.v1.CreateDeviceEnrollTokenRequest - (*EnrollDeviceRequest)(nil), // 14: teleport.devicetrust.v1.EnrollDeviceRequest - (*EnrollDeviceResponse)(nil), // 15: teleport.devicetrust.v1.EnrollDeviceResponse - (*EnrollDeviceInit)(nil), // 16: teleport.devicetrust.v1.EnrollDeviceInit - (*EnrollDeviceSuccess)(nil), // 17: teleport.devicetrust.v1.EnrollDeviceSuccess - (*MacOSEnrollPayload)(nil), // 18: teleport.devicetrust.v1.MacOSEnrollPayload - (*MacOSEnrollChallenge)(nil), // 19: teleport.devicetrust.v1.MacOSEnrollChallenge - (*MacOSEnrollChallengeResponse)(nil), // 20: teleport.devicetrust.v1.MacOSEnrollChallengeResponse - (*AuthenticateDeviceRequest)(nil), // 21: teleport.devicetrust.v1.AuthenticateDeviceRequest - (*AuthenticateDeviceResponse)(nil), // 22: teleport.devicetrust.v1.AuthenticateDeviceResponse - (*AuthenticateDeviceInit)(nil), // 23: teleport.devicetrust.v1.AuthenticateDeviceInit - (*AuthenticateDeviceChallenge)(nil), // 24: teleport.devicetrust.v1.AuthenticateDeviceChallenge - (*AuthenticateDeviceChallengeResponse)(nil), // 25: teleport.devicetrust.v1.AuthenticateDeviceChallengeResponse - (*Device)(nil), // 26: teleport.devicetrust.v1.Device - (*fieldmaskpb.FieldMask)(nil), // 27: google.protobuf.FieldMask - (*status.Status)(nil), // 28: google.rpc.Status - (*DeviceCollectedData)(nil), // 29: teleport.devicetrust.v1.DeviceCollectedData - (*UserCertificates)(nil), // 30: teleport.devicetrust.v1.UserCertificates - (*emptypb.Empty)(nil), // 31: google.protobuf.Empty - (*DeviceEnrollToken)(nil), // 32: teleport.devicetrust.v1.DeviceEnrollToken + (SyncInventoryMode)(0), // 1: teleport.devicetrust.v1.SyncInventoryMode + (SyncInventoryDeviceAction)(0), // 2: teleport.devicetrust.v1.SyncInventoryDeviceAction + (*CreateDeviceRequest)(nil), // 3: teleport.devicetrust.v1.CreateDeviceRequest + (*UpdateDeviceRequest)(nil), // 4: teleport.devicetrust.v1.UpdateDeviceRequest + (*UpsertDeviceRequest)(nil), // 5: teleport.devicetrust.v1.UpsertDeviceRequest + (*DeleteDeviceRequest)(nil), // 6: teleport.devicetrust.v1.DeleteDeviceRequest + (*FindDevicesRequest)(nil), // 7: teleport.devicetrust.v1.FindDevicesRequest + (*FindDevicesResponse)(nil), // 8: teleport.devicetrust.v1.FindDevicesResponse + (*GetDeviceRequest)(nil), // 9: teleport.devicetrust.v1.GetDeviceRequest + (*ListDevicesRequest)(nil), // 10: teleport.devicetrust.v1.ListDevicesRequest + (*ListDevicesResponse)(nil), // 11: teleport.devicetrust.v1.ListDevicesResponse + (*BulkCreateDevicesRequest)(nil), // 12: teleport.devicetrust.v1.BulkCreateDevicesRequest + (*BulkCreateDevicesResponse)(nil), // 13: teleport.devicetrust.v1.BulkCreateDevicesResponse + (*DeviceOrStatus)(nil), // 14: teleport.devicetrust.v1.DeviceOrStatus + (*CreateDeviceEnrollTokenRequest)(nil), // 15: teleport.devicetrust.v1.CreateDeviceEnrollTokenRequest + (*EnrollDeviceRequest)(nil), // 16: teleport.devicetrust.v1.EnrollDeviceRequest + (*EnrollDeviceResponse)(nil), // 17: teleport.devicetrust.v1.EnrollDeviceResponse + (*EnrollDeviceInit)(nil), // 18: teleport.devicetrust.v1.EnrollDeviceInit + (*EnrollDeviceSuccess)(nil), // 19: teleport.devicetrust.v1.EnrollDeviceSuccess + (*MacOSEnrollPayload)(nil), // 20: teleport.devicetrust.v1.MacOSEnrollPayload + (*MacOSEnrollChallenge)(nil), // 21: teleport.devicetrust.v1.MacOSEnrollChallenge + (*MacOSEnrollChallengeResponse)(nil), // 22: teleport.devicetrust.v1.MacOSEnrollChallengeResponse + (*AuthenticateDeviceRequest)(nil), // 23: teleport.devicetrust.v1.AuthenticateDeviceRequest + (*AuthenticateDeviceResponse)(nil), // 24: teleport.devicetrust.v1.AuthenticateDeviceResponse + (*AuthenticateDeviceInit)(nil), // 25: teleport.devicetrust.v1.AuthenticateDeviceInit + (*AuthenticateDeviceChallenge)(nil), // 26: teleport.devicetrust.v1.AuthenticateDeviceChallenge + (*AuthenticateDeviceChallengeResponse)(nil), // 27: teleport.devicetrust.v1.AuthenticateDeviceChallengeResponse + (*SyncInventoryRequest)(nil), // 28: teleport.devicetrust.v1.SyncInventoryRequest + (*SyncInventoryResponse)(nil), // 29: teleport.devicetrust.v1.SyncInventoryResponse + (*SyncInventoryStart)(nil), // 30: teleport.devicetrust.v1.SyncInventoryStart + (*SyncInventoryEnd)(nil), // 31: teleport.devicetrust.v1.SyncInventoryEnd + (*SyncInventoryDevices)(nil), // 32: teleport.devicetrust.v1.SyncInventoryDevices + (*SyncInventoryAck)(nil), // 33: teleport.devicetrust.v1.SyncInventoryAck + (*SyncInventoryResult)(nil), // 34: teleport.devicetrust.v1.SyncInventoryResult + (*Device)(nil), // 35: teleport.devicetrust.v1.Device + (*fieldmaskpb.FieldMask)(nil), // 36: google.protobuf.FieldMask + (*status.Status)(nil), // 37: google.rpc.Status + (*DeviceCollectedData)(nil), // 38: teleport.devicetrust.v1.DeviceCollectedData + (*UserCertificates)(nil), // 39: teleport.devicetrust.v1.UserCertificates + (*DeviceSource)(nil), // 40: teleport.devicetrust.v1.DeviceSource + (*emptypb.Empty)(nil), // 41: google.protobuf.Empty + (*DeviceEnrollToken)(nil), // 42: teleport.devicetrust.v1.DeviceEnrollToken } var file_teleport_devicetrust_v1_devicetrust_service_proto_depIdxs = []int32{ - 26, // 0: teleport.devicetrust.v1.CreateDeviceRequest.device:type_name -> teleport.devicetrust.v1.Device - 26, // 1: teleport.devicetrust.v1.UpdateDeviceRequest.device:type_name -> teleport.devicetrust.v1.Device - 27, // 2: teleport.devicetrust.v1.UpdateDeviceRequest.update_mask:type_name -> google.protobuf.FieldMask - 26, // 3: teleport.devicetrust.v1.UpsertDeviceRequest.device:type_name -> teleport.devicetrust.v1.Device - 26, // 4: teleport.devicetrust.v1.FindDevicesResponse.devices:type_name -> teleport.devicetrust.v1.Device + 35, // 0: teleport.devicetrust.v1.CreateDeviceRequest.device:type_name -> teleport.devicetrust.v1.Device + 35, // 1: teleport.devicetrust.v1.UpdateDeviceRequest.device:type_name -> teleport.devicetrust.v1.Device + 36, // 2: teleport.devicetrust.v1.UpdateDeviceRequest.update_mask:type_name -> google.protobuf.FieldMask + 35, // 3: teleport.devicetrust.v1.UpsertDeviceRequest.device:type_name -> teleport.devicetrust.v1.Device + 35, // 4: teleport.devicetrust.v1.FindDevicesResponse.devices:type_name -> teleport.devicetrust.v1.Device 0, // 5: teleport.devicetrust.v1.ListDevicesRequest.view:type_name -> teleport.devicetrust.v1.DeviceView - 26, // 6: teleport.devicetrust.v1.ListDevicesResponse.devices:type_name -> teleport.devicetrust.v1.Device - 26, // 7: teleport.devicetrust.v1.BulkCreateDevicesRequest.devices:type_name -> teleport.devicetrust.v1.Device - 12, // 8: teleport.devicetrust.v1.BulkCreateDevicesResponse.devices:type_name -> teleport.devicetrust.v1.DeviceOrStatus - 28, // 9: teleport.devicetrust.v1.DeviceOrStatus.status:type_name -> google.rpc.Status - 16, // 10: teleport.devicetrust.v1.EnrollDeviceRequest.init:type_name -> teleport.devicetrust.v1.EnrollDeviceInit - 20, // 11: teleport.devicetrust.v1.EnrollDeviceRequest.macos_challenge_response:type_name -> teleport.devicetrust.v1.MacOSEnrollChallengeResponse - 17, // 12: teleport.devicetrust.v1.EnrollDeviceResponse.success:type_name -> teleport.devicetrust.v1.EnrollDeviceSuccess - 19, // 13: teleport.devicetrust.v1.EnrollDeviceResponse.macos_challenge:type_name -> teleport.devicetrust.v1.MacOSEnrollChallenge - 29, // 14: teleport.devicetrust.v1.EnrollDeviceInit.device_data:type_name -> teleport.devicetrust.v1.DeviceCollectedData - 18, // 15: teleport.devicetrust.v1.EnrollDeviceInit.macos:type_name -> teleport.devicetrust.v1.MacOSEnrollPayload - 26, // 16: teleport.devicetrust.v1.EnrollDeviceSuccess.device:type_name -> teleport.devicetrust.v1.Device - 23, // 17: teleport.devicetrust.v1.AuthenticateDeviceRequest.init:type_name -> teleport.devicetrust.v1.AuthenticateDeviceInit - 25, // 18: teleport.devicetrust.v1.AuthenticateDeviceRequest.challenge_response:type_name -> teleport.devicetrust.v1.AuthenticateDeviceChallengeResponse - 24, // 19: teleport.devicetrust.v1.AuthenticateDeviceResponse.challenge:type_name -> teleport.devicetrust.v1.AuthenticateDeviceChallenge - 30, // 20: teleport.devicetrust.v1.AuthenticateDeviceResponse.user_certificates:type_name -> teleport.devicetrust.v1.UserCertificates - 30, // 21: teleport.devicetrust.v1.AuthenticateDeviceInit.user_certificates:type_name -> teleport.devicetrust.v1.UserCertificates - 29, // 22: teleport.devicetrust.v1.AuthenticateDeviceInit.device_data:type_name -> teleport.devicetrust.v1.DeviceCollectedData - 1, // 23: teleport.devicetrust.v1.DeviceTrustService.CreateDevice:input_type -> teleport.devicetrust.v1.CreateDeviceRequest - 2, // 24: teleport.devicetrust.v1.DeviceTrustService.UpdateDevice:input_type -> teleport.devicetrust.v1.UpdateDeviceRequest - 3, // 25: teleport.devicetrust.v1.DeviceTrustService.UpsertDevice:input_type -> teleport.devicetrust.v1.UpsertDeviceRequest - 4, // 26: teleport.devicetrust.v1.DeviceTrustService.DeleteDevice:input_type -> teleport.devicetrust.v1.DeleteDeviceRequest - 5, // 27: teleport.devicetrust.v1.DeviceTrustService.FindDevices:input_type -> teleport.devicetrust.v1.FindDevicesRequest - 7, // 28: teleport.devicetrust.v1.DeviceTrustService.GetDevice:input_type -> teleport.devicetrust.v1.GetDeviceRequest - 8, // 29: teleport.devicetrust.v1.DeviceTrustService.ListDevices:input_type -> teleport.devicetrust.v1.ListDevicesRequest - 10, // 30: teleport.devicetrust.v1.DeviceTrustService.BulkCreateDevices:input_type -> teleport.devicetrust.v1.BulkCreateDevicesRequest - 13, // 31: teleport.devicetrust.v1.DeviceTrustService.CreateDeviceEnrollToken:input_type -> teleport.devicetrust.v1.CreateDeviceEnrollTokenRequest - 14, // 32: teleport.devicetrust.v1.DeviceTrustService.EnrollDevice:input_type -> teleport.devicetrust.v1.EnrollDeviceRequest - 21, // 33: teleport.devicetrust.v1.DeviceTrustService.AuthenticateDevice:input_type -> teleport.devicetrust.v1.AuthenticateDeviceRequest - 26, // 34: teleport.devicetrust.v1.DeviceTrustService.CreateDevice:output_type -> teleport.devicetrust.v1.Device - 26, // 35: teleport.devicetrust.v1.DeviceTrustService.UpdateDevice:output_type -> teleport.devicetrust.v1.Device - 26, // 36: teleport.devicetrust.v1.DeviceTrustService.UpsertDevice:output_type -> teleport.devicetrust.v1.Device - 31, // 37: teleport.devicetrust.v1.DeviceTrustService.DeleteDevice:output_type -> google.protobuf.Empty - 6, // 38: teleport.devicetrust.v1.DeviceTrustService.FindDevices:output_type -> teleport.devicetrust.v1.FindDevicesResponse - 26, // 39: teleport.devicetrust.v1.DeviceTrustService.GetDevice:output_type -> teleport.devicetrust.v1.Device - 9, // 40: teleport.devicetrust.v1.DeviceTrustService.ListDevices:output_type -> teleport.devicetrust.v1.ListDevicesResponse - 11, // 41: teleport.devicetrust.v1.DeviceTrustService.BulkCreateDevices:output_type -> teleport.devicetrust.v1.BulkCreateDevicesResponse - 32, // 42: teleport.devicetrust.v1.DeviceTrustService.CreateDeviceEnrollToken:output_type -> teleport.devicetrust.v1.DeviceEnrollToken - 15, // 43: teleport.devicetrust.v1.DeviceTrustService.EnrollDevice:output_type -> teleport.devicetrust.v1.EnrollDeviceResponse - 22, // 44: teleport.devicetrust.v1.DeviceTrustService.AuthenticateDevice:output_type -> teleport.devicetrust.v1.AuthenticateDeviceResponse - 34, // [34:45] is the sub-list for method output_type - 23, // [23:34] is the sub-list for method input_type - 23, // [23:23] is the sub-list for extension type_name - 23, // [23:23] is the sub-list for extension extendee - 0, // [0:23] is the sub-list for field type_name + 35, // 6: teleport.devicetrust.v1.ListDevicesResponse.devices:type_name -> teleport.devicetrust.v1.Device + 35, // 7: teleport.devicetrust.v1.BulkCreateDevicesRequest.devices:type_name -> teleport.devicetrust.v1.Device + 14, // 8: teleport.devicetrust.v1.BulkCreateDevicesResponse.devices:type_name -> teleport.devicetrust.v1.DeviceOrStatus + 37, // 9: teleport.devicetrust.v1.DeviceOrStatus.status:type_name -> google.rpc.Status + 38, // 10: teleport.devicetrust.v1.CreateDeviceEnrollTokenRequest.device_data:type_name -> teleport.devicetrust.v1.DeviceCollectedData + 18, // 11: teleport.devicetrust.v1.EnrollDeviceRequest.init:type_name -> teleport.devicetrust.v1.EnrollDeviceInit + 22, // 12: teleport.devicetrust.v1.EnrollDeviceRequest.macos_challenge_response:type_name -> teleport.devicetrust.v1.MacOSEnrollChallengeResponse + 19, // 13: teleport.devicetrust.v1.EnrollDeviceResponse.success:type_name -> teleport.devicetrust.v1.EnrollDeviceSuccess + 21, // 14: teleport.devicetrust.v1.EnrollDeviceResponse.macos_challenge:type_name -> teleport.devicetrust.v1.MacOSEnrollChallenge + 38, // 15: teleport.devicetrust.v1.EnrollDeviceInit.device_data:type_name -> teleport.devicetrust.v1.DeviceCollectedData + 20, // 16: teleport.devicetrust.v1.EnrollDeviceInit.macos:type_name -> teleport.devicetrust.v1.MacOSEnrollPayload + 35, // 17: teleport.devicetrust.v1.EnrollDeviceSuccess.device:type_name -> teleport.devicetrust.v1.Device + 25, // 18: teleport.devicetrust.v1.AuthenticateDeviceRequest.init:type_name -> teleport.devicetrust.v1.AuthenticateDeviceInit + 27, // 19: teleport.devicetrust.v1.AuthenticateDeviceRequest.challenge_response:type_name -> teleport.devicetrust.v1.AuthenticateDeviceChallengeResponse + 26, // 20: teleport.devicetrust.v1.AuthenticateDeviceResponse.challenge:type_name -> teleport.devicetrust.v1.AuthenticateDeviceChallenge + 39, // 21: teleport.devicetrust.v1.AuthenticateDeviceResponse.user_certificates:type_name -> teleport.devicetrust.v1.UserCertificates + 39, // 22: teleport.devicetrust.v1.AuthenticateDeviceInit.user_certificates:type_name -> teleport.devicetrust.v1.UserCertificates + 38, // 23: teleport.devicetrust.v1.AuthenticateDeviceInit.device_data:type_name -> teleport.devicetrust.v1.DeviceCollectedData + 30, // 24: teleport.devicetrust.v1.SyncInventoryRequest.start:type_name -> teleport.devicetrust.v1.SyncInventoryStart + 31, // 25: teleport.devicetrust.v1.SyncInventoryRequest.end:type_name -> teleport.devicetrust.v1.SyncInventoryEnd + 32, // 26: teleport.devicetrust.v1.SyncInventoryRequest.devices_to_upsert:type_name -> teleport.devicetrust.v1.SyncInventoryDevices + 32, // 27: teleport.devicetrust.v1.SyncInventoryRequest.devices_to_remove:type_name -> teleport.devicetrust.v1.SyncInventoryDevices + 33, // 28: teleport.devicetrust.v1.SyncInventoryResponse.ack:type_name -> teleport.devicetrust.v1.SyncInventoryAck + 34, // 29: teleport.devicetrust.v1.SyncInventoryResponse.result:type_name -> teleport.devicetrust.v1.SyncInventoryResult + 40, // 30: teleport.devicetrust.v1.SyncInventoryStart.source:type_name -> teleport.devicetrust.v1.DeviceSource + 1, // 31: teleport.devicetrust.v1.SyncInventoryStart.mode:type_name -> teleport.devicetrust.v1.SyncInventoryMode + 2, // 32: teleport.devicetrust.v1.SyncInventoryStart.on_missing_action:type_name -> teleport.devicetrust.v1.SyncInventoryDeviceAction + 35, // 33: teleport.devicetrust.v1.SyncInventoryDevices.devices:type_name -> teleport.devicetrust.v1.Device + 14, // 34: teleport.devicetrust.v1.SyncInventoryResult.devices:type_name -> teleport.devicetrust.v1.DeviceOrStatus + 3, // 35: teleport.devicetrust.v1.DeviceTrustService.CreateDevice:input_type -> teleport.devicetrust.v1.CreateDeviceRequest + 4, // 36: teleport.devicetrust.v1.DeviceTrustService.UpdateDevice:input_type -> teleport.devicetrust.v1.UpdateDeviceRequest + 5, // 37: teleport.devicetrust.v1.DeviceTrustService.UpsertDevice:input_type -> teleport.devicetrust.v1.UpsertDeviceRequest + 6, // 38: teleport.devicetrust.v1.DeviceTrustService.DeleteDevice:input_type -> teleport.devicetrust.v1.DeleteDeviceRequest + 7, // 39: teleport.devicetrust.v1.DeviceTrustService.FindDevices:input_type -> teleport.devicetrust.v1.FindDevicesRequest + 9, // 40: teleport.devicetrust.v1.DeviceTrustService.GetDevice:input_type -> teleport.devicetrust.v1.GetDeviceRequest + 10, // 41: teleport.devicetrust.v1.DeviceTrustService.ListDevices:input_type -> teleport.devicetrust.v1.ListDevicesRequest + 12, // 42: teleport.devicetrust.v1.DeviceTrustService.BulkCreateDevices:input_type -> teleport.devicetrust.v1.BulkCreateDevicesRequest + 15, // 43: teleport.devicetrust.v1.DeviceTrustService.CreateDeviceEnrollToken:input_type -> teleport.devicetrust.v1.CreateDeviceEnrollTokenRequest + 16, // 44: teleport.devicetrust.v1.DeviceTrustService.EnrollDevice:input_type -> teleport.devicetrust.v1.EnrollDeviceRequest + 23, // 45: teleport.devicetrust.v1.DeviceTrustService.AuthenticateDevice:input_type -> teleport.devicetrust.v1.AuthenticateDeviceRequest + 28, // 46: teleport.devicetrust.v1.DeviceTrustService.SyncInventory:input_type -> teleport.devicetrust.v1.SyncInventoryRequest + 35, // 47: teleport.devicetrust.v1.DeviceTrustService.CreateDevice:output_type -> teleport.devicetrust.v1.Device + 35, // 48: teleport.devicetrust.v1.DeviceTrustService.UpdateDevice:output_type -> teleport.devicetrust.v1.Device + 35, // 49: teleport.devicetrust.v1.DeviceTrustService.UpsertDevice:output_type -> teleport.devicetrust.v1.Device + 41, // 50: teleport.devicetrust.v1.DeviceTrustService.DeleteDevice:output_type -> google.protobuf.Empty + 8, // 51: teleport.devicetrust.v1.DeviceTrustService.FindDevices:output_type -> teleport.devicetrust.v1.FindDevicesResponse + 35, // 52: teleport.devicetrust.v1.DeviceTrustService.GetDevice:output_type -> teleport.devicetrust.v1.Device + 11, // 53: teleport.devicetrust.v1.DeviceTrustService.ListDevices:output_type -> teleport.devicetrust.v1.ListDevicesResponse + 13, // 54: teleport.devicetrust.v1.DeviceTrustService.BulkCreateDevices:output_type -> teleport.devicetrust.v1.BulkCreateDevicesResponse + 42, // 55: teleport.devicetrust.v1.DeviceTrustService.CreateDeviceEnrollToken:output_type -> teleport.devicetrust.v1.DeviceEnrollToken + 17, // 56: teleport.devicetrust.v1.DeviceTrustService.EnrollDevice:output_type -> teleport.devicetrust.v1.EnrollDeviceResponse + 24, // 57: teleport.devicetrust.v1.DeviceTrustService.AuthenticateDevice:output_type -> teleport.devicetrust.v1.AuthenticateDeviceResponse + 29, // 58: teleport.devicetrust.v1.DeviceTrustService.SyncInventory:output_type -> teleport.devicetrust.v1.SyncInventoryResponse + 47, // [47:59] is the sub-list for method output_type + 35, // [35:47] is the sub-list for method input_type + 35, // [35:35] is the sub-list for extension type_name + 35, // [35:35] is the sub-list for extension extendee + 0, // [0:35] is the sub-list for field type_name } func init() { file_teleport_devicetrust_v1_devicetrust_service_proto_init() } @@ -2037,6 +2761,7 @@ func file_teleport_devicetrust_v1_devicetrust_service_proto_init() { file_teleport_devicetrust_v1_device_proto_init() file_teleport_devicetrust_v1_device_collected_data_proto_init() file_teleport_devicetrust_v1_device_enroll_token_proto_init() + file_teleport_devicetrust_v1_device_source_proto_init() file_teleport_devicetrust_v1_user_certificates_proto_init() if !protoimpl.UnsafeEnabled { file_teleport_devicetrust_v1_devicetrust_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { @@ -2339,6 +3064,90 @@ func file_teleport_devicetrust_v1_devicetrust_service_proto_init() { return nil } } + file_teleport_devicetrust_v1_devicetrust_service_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SyncInventoryRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_teleport_devicetrust_v1_devicetrust_service_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SyncInventoryResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_teleport_devicetrust_v1_devicetrust_service_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SyncInventoryStart); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_teleport_devicetrust_v1_devicetrust_service_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SyncInventoryEnd); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_teleport_devicetrust_v1_devicetrust_service_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SyncInventoryDevices); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_teleport_devicetrust_v1_devicetrust_service_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SyncInventoryAck); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_teleport_devicetrust_v1_devicetrust_service_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SyncInventoryResult); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } file_teleport_devicetrust_v1_devicetrust_service_proto_msgTypes[13].OneofWrappers = []interface{}{ (*EnrollDeviceRequest_Init)(nil), @@ -2356,13 +3165,23 @@ func file_teleport_devicetrust_v1_devicetrust_service_proto_init() { (*AuthenticateDeviceResponse_Challenge)(nil), (*AuthenticateDeviceResponse_UserCertificates)(nil), } + file_teleport_devicetrust_v1_devicetrust_service_proto_msgTypes[25].OneofWrappers = []interface{}{ + (*SyncInventoryRequest_Start)(nil), + (*SyncInventoryRequest_End)(nil), + (*SyncInventoryRequest_DevicesToUpsert)(nil), + (*SyncInventoryRequest_DevicesToRemove)(nil), + } + file_teleport_devicetrust_v1_devicetrust_service_proto_msgTypes[26].OneofWrappers = []interface{}{ + (*SyncInventoryResponse_Ack)(nil), + (*SyncInventoryResponse_Result)(nil), + } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_teleport_devicetrust_v1_devicetrust_service_proto_rawDesc, - NumEnums: 1, - NumMessages: 25, + NumEnums: 3, + NumMessages: 32, NumExtensions: 0, NumServices: 1, }, diff --git a/api/gen/proto/go/teleport/devicetrust/v1/devicetrust_service_grpc.pb.go b/api/gen/proto/go/teleport/devicetrust/v1/devicetrust_service_grpc.pb.go index e0db094e17f49..c3ceceac5de19 100644 --- a/api/gen/proto/go/teleport/devicetrust/v1/devicetrust_service_grpc.pb.go +++ b/api/gen/proto/go/teleport/devicetrust/v1/devicetrust_service_grpc.pb.go @@ -45,6 +45,7 @@ const ( DeviceTrustService_CreateDeviceEnrollToken_FullMethodName = "/teleport.devicetrust.v1.DeviceTrustService/CreateDeviceEnrollToken" DeviceTrustService_EnrollDevice_FullMethodName = "/teleport.devicetrust.v1.DeviceTrustService/EnrollDevice" DeviceTrustService_AuthenticateDevice_FullMethodName = "/teleport.devicetrust.v1.DeviceTrustService/AuthenticateDevice" + DeviceTrustService_SyncInventory_FullMethodName = "/teleport.devicetrust.v1.DeviceTrustService/SyncInventory" ) // DeviceTrustServiceClient is the client API for DeviceTrustService service. @@ -113,6 +114,13 @@ type DeviceTrustServiceClient interface { // // Only registered and enrolled devices may perform device authentication. AuthenticateDevice(ctx context.Context, opts ...grpc.CallOption) (DeviceTrustService_AuthenticateDeviceClient, error) + // Syncs device inventory from a source exterior to Teleport, for example an + // MDM. + // Allows both partial and full syncs; for the latter, devices missing from + // the external inventory are handled as specified. + // Authorized either by a valid MDM service certificate or the appropriate + // "device" permissions (create/update/delete). + SyncInventory(ctx context.Context, opts ...grpc.CallOption) (DeviceTrustService_SyncInventoryClient, error) } type deviceTrustServiceClient struct { @@ -266,6 +274,37 @@ func (x *deviceTrustServiceAuthenticateDeviceClient) Recv() (*AuthenticateDevice return m, nil } +func (c *deviceTrustServiceClient) SyncInventory(ctx context.Context, opts ...grpc.CallOption) (DeviceTrustService_SyncInventoryClient, error) { + stream, err := c.cc.NewStream(ctx, &DeviceTrustService_ServiceDesc.Streams[2], DeviceTrustService_SyncInventory_FullMethodName, opts...) + if err != nil { + return nil, err + } + x := &deviceTrustServiceSyncInventoryClient{stream} + return x, nil +} + +type DeviceTrustService_SyncInventoryClient interface { + Send(*SyncInventoryRequest) error + Recv() (*SyncInventoryResponse, error) + grpc.ClientStream +} + +type deviceTrustServiceSyncInventoryClient struct { + grpc.ClientStream +} + +func (x *deviceTrustServiceSyncInventoryClient) Send(m *SyncInventoryRequest) error { + return x.ClientStream.SendMsg(m) +} + +func (x *deviceTrustServiceSyncInventoryClient) Recv() (*SyncInventoryResponse, error) { + m := new(SyncInventoryResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + // DeviceTrustServiceServer is the server API for DeviceTrustService service. // All implementations must embed UnimplementedDeviceTrustServiceServer // for forward compatibility @@ -332,6 +371,13 @@ type DeviceTrustServiceServer interface { // // Only registered and enrolled devices may perform device authentication. AuthenticateDevice(DeviceTrustService_AuthenticateDeviceServer) error + // Syncs device inventory from a source exterior to Teleport, for example an + // MDM. + // Allows both partial and full syncs; for the latter, devices missing from + // the external inventory are handled as specified. + // Authorized either by a valid MDM service certificate or the appropriate + // "device" permissions (create/update/delete). + SyncInventory(DeviceTrustService_SyncInventoryServer) error mustEmbedUnimplementedDeviceTrustServiceServer() } @@ -372,6 +418,9 @@ func (UnimplementedDeviceTrustServiceServer) EnrollDevice(DeviceTrustService_Enr func (UnimplementedDeviceTrustServiceServer) AuthenticateDevice(DeviceTrustService_AuthenticateDeviceServer) error { return status.Errorf(codes.Unimplemented, "method AuthenticateDevice not implemented") } +func (UnimplementedDeviceTrustServiceServer) SyncInventory(DeviceTrustService_SyncInventoryServer) error { + return status.Errorf(codes.Unimplemented, "method SyncInventory not implemented") +} func (UnimplementedDeviceTrustServiceServer) mustEmbedUnimplementedDeviceTrustServiceServer() {} // UnsafeDeviceTrustServiceServer may be embedded to opt out of forward compatibility for this service. @@ -599,6 +648,32 @@ func (x *deviceTrustServiceAuthenticateDeviceServer) Recv() (*AuthenticateDevice return m, nil } +func _DeviceTrustService_SyncInventory_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(DeviceTrustServiceServer).SyncInventory(&deviceTrustServiceSyncInventoryServer{stream}) +} + +type DeviceTrustService_SyncInventoryServer interface { + Send(*SyncInventoryResponse) error + Recv() (*SyncInventoryRequest, error) + grpc.ServerStream +} + +type deviceTrustServiceSyncInventoryServer struct { + grpc.ServerStream +} + +func (x *deviceTrustServiceSyncInventoryServer) Send(m *SyncInventoryResponse) error { + return x.ServerStream.SendMsg(m) +} + +func (x *deviceTrustServiceSyncInventoryServer) Recv() (*SyncInventoryRequest, error) { + m := new(SyncInventoryRequest) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + // DeviceTrustService_ServiceDesc is the grpc.ServiceDesc for DeviceTrustService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -656,6 +731,12 @@ var DeviceTrustService_ServiceDesc = grpc.ServiceDesc{ ServerStreams: true, ClientStreams: true, }, + { + StreamName: "SyncInventory", + Handler: _DeviceTrustService_SyncInventory_Handler, + ServerStreams: true, + ClientStreams: true, + }, }, Metadata: "teleport/devicetrust/v1/devicetrust_service.proto", } diff --git a/api/gen/proto/go/teleport/integration/v1/integration_service.pb.go b/api/gen/proto/go/teleport/integration/v1/integration_service.pb.go new file mode 100644 index 0000000000000..c2f634328b8d7 --- /dev/null +++ b/api/gen/proto/go/teleport/integration/v1/integration_service.pb.go @@ -0,0 +1,646 @@ +// Copyright 2023 Gravitational, Inc +// +// 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. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.30.0 +// protoc (unknown) +// source: teleport/integration/v1/integration_service.proto + +package v1 + +import ( + types "github.com/gravitational/teleport/api/types" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + emptypb "google.golang.org/protobuf/types/known/emptypb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// ListIntegrationsRequest is a request for a paginated list of Integrations. +type ListIntegrationsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Limit is the maximum amount of resources to retrieve. + Limit int32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"` + // NextKey is the key for the next page of Integrations. + NextKey string `protobuf:"bytes,2,opt,name=next_key,json=nextKey,proto3" json:"next_key,omitempty"` +} + +func (x *ListIntegrationsRequest) Reset() { + *x = ListIntegrationsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_teleport_integration_v1_integration_service_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListIntegrationsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListIntegrationsRequest) ProtoMessage() {} + +func (x *ListIntegrationsRequest) ProtoReflect() protoreflect.Message { + mi := &file_teleport_integration_v1_integration_service_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListIntegrationsRequest.ProtoReflect.Descriptor instead. +func (*ListIntegrationsRequest) Descriptor() ([]byte, []int) { + return file_teleport_integration_v1_integration_service_proto_rawDescGZIP(), []int{0} +} + +func (x *ListIntegrationsRequest) GetLimit() int32 { + if x != nil { + return x.Limit + } + return 0 +} + +func (x *ListIntegrationsRequest) GetNextKey() string { + if x != nil { + return x.NextKey + } + return "" +} + +// ListIntegrationsResponse is the response for ListIntegrationsRequest. +type ListIntegrationsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Integrations is a list of Integrations. + Integrations []*types.IntegrationV1 `protobuf:"bytes,1,rep,name=integrations,proto3" json:"integrations,omitempty"` + // NextKey is the key for the next page of Integrations. + NextKey string `protobuf:"bytes,2,opt,name=next_key,json=nextKey,proto3" json:"next_key,omitempty"` + // TotalCount is the total number of integrations in all pages. + TotalCount int32 `protobuf:"varint,3,opt,name=total_count,json=totalCount,proto3" json:"total_count,omitempty"` +} + +func (x *ListIntegrationsResponse) Reset() { + *x = ListIntegrationsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_teleport_integration_v1_integration_service_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListIntegrationsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListIntegrationsResponse) ProtoMessage() {} + +func (x *ListIntegrationsResponse) ProtoReflect() protoreflect.Message { + mi := &file_teleport_integration_v1_integration_service_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListIntegrationsResponse.ProtoReflect.Descriptor instead. +func (*ListIntegrationsResponse) Descriptor() ([]byte, []int) { + return file_teleport_integration_v1_integration_service_proto_rawDescGZIP(), []int{1} +} + +func (x *ListIntegrationsResponse) GetIntegrations() []*types.IntegrationV1 { + if x != nil { + return x.Integrations + } + return nil +} + +func (x *ListIntegrationsResponse) GetNextKey() string { + if x != nil { + return x.NextKey + } + return "" +} + +func (x *ListIntegrationsResponse) GetTotalCount() int32 { + if x != nil { + return x.TotalCount + } + return 0 +} + +// GetIntegrationRequest is a request for a specific Integration resource. +type GetIntegrationRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Name is the name of the Integration to be requested. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *GetIntegrationRequest) Reset() { + *x = GetIntegrationRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_teleport_integration_v1_integration_service_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetIntegrationRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetIntegrationRequest) ProtoMessage() {} + +func (x *GetIntegrationRequest) ProtoReflect() protoreflect.Message { + mi := &file_teleport_integration_v1_integration_service_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetIntegrationRequest.ProtoReflect.Descriptor instead. +func (*GetIntegrationRequest) Descriptor() ([]byte, []int) { + return file_teleport_integration_v1_integration_service_proto_rawDescGZIP(), []int{2} +} + +func (x *GetIntegrationRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +// CreateIntegrationRequest is the request to create the provided integration. +type CreateIntegrationRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Integration is the integration to be created. + Integration *types.IntegrationV1 `protobuf:"bytes,1,opt,name=integration,proto3" json:"integration,omitempty"` +} + +func (x *CreateIntegrationRequest) Reset() { + *x = CreateIntegrationRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_teleport_integration_v1_integration_service_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateIntegrationRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateIntegrationRequest) ProtoMessage() {} + +func (x *CreateIntegrationRequest) ProtoReflect() protoreflect.Message { + mi := &file_teleport_integration_v1_integration_service_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateIntegrationRequest.ProtoReflect.Descriptor instead. +func (*CreateIntegrationRequest) Descriptor() ([]byte, []int) { + return file_teleport_integration_v1_integration_service_proto_rawDescGZIP(), []int{3} +} + +func (x *CreateIntegrationRequest) GetIntegration() *types.IntegrationV1 { + if x != nil { + return x.Integration + } + return nil +} + +// UpdateIntegrationRequest is the request to update the provided integration. +type UpdateIntegrationRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Integration is the integration to be created. + Integration *types.IntegrationV1 `protobuf:"bytes,1,opt,name=integration,proto3" json:"integration,omitempty"` +} + +func (x *UpdateIntegrationRequest) Reset() { + *x = UpdateIntegrationRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_teleport_integration_v1_integration_service_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateIntegrationRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateIntegrationRequest) ProtoMessage() {} + +func (x *UpdateIntegrationRequest) ProtoReflect() protoreflect.Message { + mi := &file_teleport_integration_v1_integration_service_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateIntegrationRequest.ProtoReflect.Descriptor instead. +func (*UpdateIntegrationRequest) Descriptor() ([]byte, []int) { + return file_teleport_integration_v1_integration_service_proto_rawDescGZIP(), []int{4} +} + +func (x *UpdateIntegrationRequest) GetIntegration() *types.IntegrationV1 { + if x != nil { + return x.Integration + } + return nil +} + +// DeleteIntegrationRequest is a request for deleting a specific Integration resource. +type DeleteIntegrationRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Name is the name of the Integration to be deleted. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *DeleteIntegrationRequest) Reset() { + *x = DeleteIntegrationRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_teleport_integration_v1_integration_service_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteIntegrationRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteIntegrationRequest) ProtoMessage() {} + +func (x *DeleteIntegrationRequest) ProtoReflect() protoreflect.Message { + mi := &file_teleport_integration_v1_integration_service_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteIntegrationRequest.ProtoReflect.Descriptor instead. +func (*DeleteIntegrationRequest) Descriptor() ([]byte, []int) { + return file_teleport_integration_v1_integration_service_proto_rawDescGZIP(), []int{5} +} + +func (x *DeleteIntegrationRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +// DeleteAllIntegrationsRequest is the request for deleting all integrations. +type DeleteAllIntegrationsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *DeleteAllIntegrationsRequest) Reset() { + *x = DeleteAllIntegrationsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_teleport_integration_v1_integration_service_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteAllIntegrationsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteAllIntegrationsRequest) ProtoMessage() {} + +func (x *DeleteAllIntegrationsRequest) ProtoReflect() protoreflect.Message { + mi := &file_teleport_integration_v1_integration_service_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteAllIntegrationsRequest.ProtoReflect.Descriptor instead. +func (*DeleteAllIntegrationsRequest) Descriptor() ([]byte, []int) { + return file_teleport_integration_v1_integration_service_proto_rawDescGZIP(), []int{6} +} + +var File_teleport_integration_v1_integration_service_proto protoreflect.FileDescriptor + +var file_teleport_integration_v1_integration_service_proto_rawDesc = []byte{ + 0x0a, 0x31, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x12, 0x17, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, + 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x1a, 0x1b, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, + 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x21, 0x74, 0x65, 0x6c, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x2f, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x4a, 0x0a, 0x17, + 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x19, 0x0a, + 0x08, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x6e, 0x65, 0x78, 0x74, 0x4b, 0x65, 0x79, 0x22, 0x90, 0x01, 0x0a, 0x18, 0x4c, 0x69, 0x73, + 0x74, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x38, 0x0a, 0x0c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x74, 0x79, + 0x70, 0x65, 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x56, + 0x31, 0x52, 0x0c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, + 0x19, 0x0a, 0x08, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x6e, 0x65, 0x78, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, + 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x2b, 0x0a, 0x15, 0x47, + 0x65, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x52, 0x0a, 0x18, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x36, 0x0a, 0x0b, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x74, 0x79, 0x70, 0x65, + 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x31, 0x52, + 0x0b, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x52, 0x0a, 0x18, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x36, 0x0a, 0x0b, 0x69, 0x6e, 0x74, 0x65, + 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, + 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x56, 0x31, 0x52, 0x0b, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x22, 0x2e, 0x0a, 0x18, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x22, 0x1e, 0x0a, 0x1c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x6c, 0x49, 0x6e, 0x74, + 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x32, 0xe9, 0x04, 0x0a, 0x12, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x77, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x49, + 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x30, 0x2e, 0x74, 0x65, + 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, + 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x74, 0x65, + 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x56, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x2e, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, + 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, + 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x67, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x31, 0x12, 0x5c, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x31, 0x2e, + 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x6e, + 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x14, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x31, 0x12, 0x5c, 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x31, 0x2e, 0x74, 0x65, + 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x74, 0x65, + 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, + 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x56, 0x31, 0x12, 0x5e, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x49, 0x6e, + 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x31, 0x2e, 0x74, 0x65, 0x6c, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x12, 0x66, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, + 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x35, 0x2e, + 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, + 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x4c, 0x5a, 0x4a, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x76, 0x69, + 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, + 0x67, 0x6f, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, + 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, +} + +var ( + file_teleport_integration_v1_integration_service_proto_rawDescOnce sync.Once + file_teleport_integration_v1_integration_service_proto_rawDescData = file_teleport_integration_v1_integration_service_proto_rawDesc +) + +func file_teleport_integration_v1_integration_service_proto_rawDescGZIP() []byte { + file_teleport_integration_v1_integration_service_proto_rawDescOnce.Do(func() { + file_teleport_integration_v1_integration_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_teleport_integration_v1_integration_service_proto_rawDescData) + }) + return file_teleport_integration_v1_integration_service_proto_rawDescData +} + +var file_teleport_integration_v1_integration_service_proto_msgTypes = make([]protoimpl.MessageInfo, 7) +var file_teleport_integration_v1_integration_service_proto_goTypes = []interface{}{ + (*ListIntegrationsRequest)(nil), // 0: teleport.integration.v1.ListIntegrationsRequest + (*ListIntegrationsResponse)(nil), // 1: teleport.integration.v1.ListIntegrationsResponse + (*GetIntegrationRequest)(nil), // 2: teleport.integration.v1.GetIntegrationRequest + (*CreateIntegrationRequest)(nil), // 3: teleport.integration.v1.CreateIntegrationRequest + (*UpdateIntegrationRequest)(nil), // 4: teleport.integration.v1.UpdateIntegrationRequest + (*DeleteIntegrationRequest)(nil), // 5: teleport.integration.v1.DeleteIntegrationRequest + (*DeleteAllIntegrationsRequest)(nil), // 6: teleport.integration.v1.DeleteAllIntegrationsRequest + (*types.IntegrationV1)(nil), // 7: types.IntegrationV1 + (*emptypb.Empty)(nil), // 8: google.protobuf.Empty +} +var file_teleport_integration_v1_integration_service_proto_depIdxs = []int32{ + 7, // 0: teleport.integration.v1.ListIntegrationsResponse.integrations:type_name -> types.IntegrationV1 + 7, // 1: teleport.integration.v1.CreateIntegrationRequest.integration:type_name -> types.IntegrationV1 + 7, // 2: teleport.integration.v1.UpdateIntegrationRequest.integration:type_name -> types.IntegrationV1 + 0, // 3: teleport.integration.v1.IntegrationService.ListIntegrations:input_type -> teleport.integration.v1.ListIntegrationsRequest + 2, // 4: teleport.integration.v1.IntegrationService.GetIntegration:input_type -> teleport.integration.v1.GetIntegrationRequest + 3, // 5: teleport.integration.v1.IntegrationService.CreateIntegration:input_type -> teleport.integration.v1.CreateIntegrationRequest + 4, // 6: teleport.integration.v1.IntegrationService.UpdateIntegration:input_type -> teleport.integration.v1.UpdateIntegrationRequest + 5, // 7: teleport.integration.v1.IntegrationService.DeleteIntegration:input_type -> teleport.integration.v1.DeleteIntegrationRequest + 6, // 8: teleport.integration.v1.IntegrationService.DeleteAllIntegrations:input_type -> teleport.integration.v1.DeleteAllIntegrationsRequest + 1, // 9: teleport.integration.v1.IntegrationService.ListIntegrations:output_type -> teleport.integration.v1.ListIntegrationsResponse + 7, // 10: teleport.integration.v1.IntegrationService.GetIntegration:output_type -> types.IntegrationV1 + 7, // 11: teleport.integration.v1.IntegrationService.CreateIntegration:output_type -> types.IntegrationV1 + 7, // 12: teleport.integration.v1.IntegrationService.UpdateIntegration:output_type -> types.IntegrationV1 + 8, // 13: teleport.integration.v1.IntegrationService.DeleteIntegration:output_type -> google.protobuf.Empty + 8, // 14: teleport.integration.v1.IntegrationService.DeleteAllIntegrations:output_type -> google.protobuf.Empty + 9, // [9:15] is the sub-list for method output_type + 3, // [3:9] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name +} + +func init() { file_teleport_integration_v1_integration_service_proto_init() } +func file_teleport_integration_v1_integration_service_proto_init() { + if File_teleport_integration_v1_integration_service_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_teleport_integration_v1_integration_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListIntegrationsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_teleport_integration_v1_integration_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListIntegrationsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_teleport_integration_v1_integration_service_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetIntegrationRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_teleport_integration_v1_integration_service_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateIntegrationRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_teleport_integration_v1_integration_service_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateIntegrationRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_teleport_integration_v1_integration_service_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteIntegrationRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_teleport_integration_v1_integration_service_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteAllIntegrationsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_teleport_integration_v1_integration_service_proto_rawDesc, + NumEnums: 0, + NumMessages: 7, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_teleport_integration_v1_integration_service_proto_goTypes, + DependencyIndexes: file_teleport_integration_v1_integration_service_proto_depIdxs, + MessageInfos: file_teleport_integration_v1_integration_service_proto_msgTypes, + }.Build() + File_teleport_integration_v1_integration_service_proto = out.File + file_teleport_integration_v1_integration_service_proto_rawDesc = nil + file_teleport_integration_v1_integration_service_proto_goTypes = nil + file_teleport_integration_v1_integration_service_proto_depIdxs = nil +} diff --git a/api/gen/proto/go/teleport/integration/v1/integration_service_grpc.pb.go b/api/gen/proto/go/teleport/integration/v1/integration_service_grpc.pb.go new file mode 100644 index 0000000000000..ec23efa6326fd --- /dev/null +++ b/api/gen/proto/go/teleport/integration/v1/integration_service_grpc.pb.go @@ -0,0 +1,322 @@ +// Copyright 2023 Gravitational, Inc +// +// 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. + +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.3.0 +// - protoc (unknown) +// source: teleport/integration/v1/integration_service.proto + +package v1 + +import ( + context "context" + types "github.com/gravitational/teleport/api/types" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + emptypb "google.golang.org/protobuf/types/known/emptypb" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +const ( + IntegrationService_ListIntegrations_FullMethodName = "/teleport.integration.v1.IntegrationService/ListIntegrations" + IntegrationService_GetIntegration_FullMethodName = "/teleport.integration.v1.IntegrationService/GetIntegration" + IntegrationService_CreateIntegration_FullMethodName = "/teleport.integration.v1.IntegrationService/CreateIntegration" + IntegrationService_UpdateIntegration_FullMethodName = "/teleport.integration.v1.IntegrationService/UpdateIntegration" + IntegrationService_DeleteIntegration_FullMethodName = "/teleport.integration.v1.IntegrationService/DeleteIntegration" + IntegrationService_DeleteAllIntegrations_FullMethodName = "/teleport.integration.v1.IntegrationService/DeleteAllIntegrations" +) + +// IntegrationServiceClient is the client API for IntegrationService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type IntegrationServiceClient interface { + // ListIntegrations returns a paginated list of Integration resources. + ListIntegrations(ctx context.Context, in *ListIntegrationsRequest, opts ...grpc.CallOption) (*ListIntegrationsResponse, error) + // GetIntegration returns the specified Integration resource. + GetIntegration(ctx context.Context, in *GetIntegrationRequest, opts ...grpc.CallOption) (*types.IntegrationV1, error) + // CreateIntegration creates a new Integration resource. + CreateIntegration(ctx context.Context, in *CreateIntegrationRequest, opts ...grpc.CallOption) (*types.IntegrationV1, error) + // UpdateIntegration updates an existing Integration resource. + UpdateIntegration(ctx context.Context, in *UpdateIntegrationRequest, opts ...grpc.CallOption) (*types.IntegrationV1, error) + // DeleteIntegration removes the specified Integration resource. + DeleteIntegration(ctx context.Context, in *DeleteIntegrationRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + // DeleteAllIntegrations removes all Integrations. + DeleteAllIntegrations(ctx context.Context, in *DeleteAllIntegrationsRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) +} + +type integrationServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewIntegrationServiceClient(cc grpc.ClientConnInterface) IntegrationServiceClient { + return &integrationServiceClient{cc} +} + +func (c *integrationServiceClient) ListIntegrations(ctx context.Context, in *ListIntegrationsRequest, opts ...grpc.CallOption) (*ListIntegrationsResponse, error) { + out := new(ListIntegrationsResponse) + err := c.cc.Invoke(ctx, IntegrationService_ListIntegrations_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *integrationServiceClient) GetIntegration(ctx context.Context, in *GetIntegrationRequest, opts ...grpc.CallOption) (*types.IntegrationV1, error) { + out := new(types.IntegrationV1) + err := c.cc.Invoke(ctx, IntegrationService_GetIntegration_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *integrationServiceClient) CreateIntegration(ctx context.Context, in *CreateIntegrationRequest, opts ...grpc.CallOption) (*types.IntegrationV1, error) { + out := new(types.IntegrationV1) + err := c.cc.Invoke(ctx, IntegrationService_CreateIntegration_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *integrationServiceClient) UpdateIntegration(ctx context.Context, in *UpdateIntegrationRequest, opts ...grpc.CallOption) (*types.IntegrationV1, error) { + out := new(types.IntegrationV1) + err := c.cc.Invoke(ctx, IntegrationService_UpdateIntegration_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *integrationServiceClient) DeleteIntegration(ctx context.Context, in *DeleteIntegrationRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, IntegrationService_DeleteIntegration_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *integrationServiceClient) DeleteAllIntegrations(ctx context.Context, in *DeleteAllIntegrationsRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, IntegrationService_DeleteAllIntegrations_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// IntegrationServiceServer is the server API for IntegrationService service. +// All implementations must embed UnimplementedIntegrationServiceServer +// for forward compatibility +type IntegrationServiceServer interface { + // ListIntegrations returns a paginated list of Integration resources. + ListIntegrations(context.Context, *ListIntegrationsRequest) (*ListIntegrationsResponse, error) + // GetIntegration returns the specified Integration resource. + GetIntegration(context.Context, *GetIntegrationRequest) (*types.IntegrationV1, error) + // CreateIntegration creates a new Integration resource. + CreateIntegration(context.Context, *CreateIntegrationRequest) (*types.IntegrationV1, error) + // UpdateIntegration updates an existing Integration resource. + UpdateIntegration(context.Context, *UpdateIntegrationRequest) (*types.IntegrationV1, error) + // DeleteIntegration removes the specified Integration resource. + DeleteIntegration(context.Context, *DeleteIntegrationRequest) (*emptypb.Empty, error) + // DeleteAllIntegrations removes all Integrations. + DeleteAllIntegrations(context.Context, *DeleteAllIntegrationsRequest) (*emptypb.Empty, error) + mustEmbedUnimplementedIntegrationServiceServer() +} + +// UnimplementedIntegrationServiceServer must be embedded to have forward compatible implementations. +type UnimplementedIntegrationServiceServer struct { +} + +func (UnimplementedIntegrationServiceServer) ListIntegrations(context.Context, *ListIntegrationsRequest) (*ListIntegrationsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListIntegrations not implemented") +} +func (UnimplementedIntegrationServiceServer) GetIntegration(context.Context, *GetIntegrationRequest) (*types.IntegrationV1, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetIntegration not implemented") +} +func (UnimplementedIntegrationServiceServer) CreateIntegration(context.Context, *CreateIntegrationRequest) (*types.IntegrationV1, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateIntegration not implemented") +} +func (UnimplementedIntegrationServiceServer) UpdateIntegration(context.Context, *UpdateIntegrationRequest) (*types.IntegrationV1, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateIntegration not implemented") +} +func (UnimplementedIntegrationServiceServer) DeleteIntegration(context.Context, *DeleteIntegrationRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteIntegration not implemented") +} +func (UnimplementedIntegrationServiceServer) DeleteAllIntegrations(context.Context, *DeleteAllIntegrationsRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteAllIntegrations not implemented") +} +func (UnimplementedIntegrationServiceServer) mustEmbedUnimplementedIntegrationServiceServer() {} + +// UnsafeIntegrationServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to IntegrationServiceServer will +// result in compilation errors. +type UnsafeIntegrationServiceServer interface { + mustEmbedUnimplementedIntegrationServiceServer() +} + +func RegisterIntegrationServiceServer(s grpc.ServiceRegistrar, srv IntegrationServiceServer) { + s.RegisterService(&IntegrationService_ServiceDesc, srv) +} + +func _IntegrationService_ListIntegrations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListIntegrationsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(IntegrationServiceServer).ListIntegrations(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: IntegrationService_ListIntegrations_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(IntegrationServiceServer).ListIntegrations(ctx, req.(*ListIntegrationsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _IntegrationService_GetIntegration_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetIntegrationRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(IntegrationServiceServer).GetIntegration(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: IntegrationService_GetIntegration_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(IntegrationServiceServer).GetIntegration(ctx, req.(*GetIntegrationRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _IntegrationService_CreateIntegration_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateIntegrationRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(IntegrationServiceServer).CreateIntegration(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: IntegrationService_CreateIntegration_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(IntegrationServiceServer).CreateIntegration(ctx, req.(*CreateIntegrationRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _IntegrationService_UpdateIntegration_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateIntegrationRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(IntegrationServiceServer).UpdateIntegration(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: IntegrationService_UpdateIntegration_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(IntegrationServiceServer).UpdateIntegration(ctx, req.(*UpdateIntegrationRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _IntegrationService_DeleteIntegration_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteIntegrationRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(IntegrationServiceServer).DeleteIntegration(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: IntegrationService_DeleteIntegration_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(IntegrationServiceServer).DeleteIntegration(ctx, req.(*DeleteIntegrationRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _IntegrationService_DeleteAllIntegrations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteAllIntegrationsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(IntegrationServiceServer).DeleteAllIntegrations(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: IntegrationService_DeleteAllIntegrations_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(IntegrationServiceServer).DeleteAllIntegrations(ctx, req.(*DeleteAllIntegrationsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// IntegrationService_ServiceDesc is the grpc.ServiceDesc for IntegrationService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var IntegrationService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "teleport.integration.v1.IntegrationService", + HandlerType: (*IntegrationServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ListIntegrations", + Handler: _IntegrationService_ListIntegrations_Handler, + }, + { + MethodName: "GetIntegration", + Handler: _IntegrationService_GetIntegration_Handler, + }, + { + MethodName: "CreateIntegration", + Handler: _IntegrationService_CreateIntegration_Handler, + }, + { + MethodName: "UpdateIntegration", + Handler: _IntegrationService_UpdateIntegration_Handler, + }, + { + MethodName: "DeleteIntegration", + Handler: _IntegrationService_DeleteIntegration_Handler, + }, + { + MethodName: "DeleteAllIntegrations", + Handler: _IntegrationService_DeleteAllIntegrations_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "teleport/integration/v1/integration_service.proto", +} diff --git a/api/gen/proto/go/teleport/plugins/v1/plugin_service.pb.go b/api/gen/proto/go/teleport/plugins/v1/plugin_service.pb.go index e10f0dc858509..4608d99e65116 100644 --- a/api/gen/proto/go/teleport/plugins/v1/plugin_service.pb.go +++ b/api/gen/proto/go/teleport/plugins/v1/plugin_service.pb.go @@ -36,6 +36,68 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +// PluginType represents a single type of hosted plugin +// that can be onboarded. +type PluginType struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Type is a string corresponding to api.PluginTypeXXX constants + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + // OAuthClientID contains the client ID of the OAuth application + // that is used with this plugin's API provider. + // For plugins that are not authenticated via OAuth, + // this will be empty. + OauthClientId string `protobuf:"bytes,2,opt,name=oauth_client_id,json=oauthClientId,proto3" json:"oauth_client_id,omitempty"` +} + +func (x *PluginType) Reset() { + *x = PluginType{} + if protoimpl.UnsafeEnabled { + mi := &file_teleport_plugins_v1_plugin_service_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PluginType) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PluginType) ProtoMessage() {} + +func (x *PluginType) ProtoReflect() protoreflect.Message { + mi := &file_teleport_plugins_v1_plugin_service_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PluginType.ProtoReflect.Descriptor instead. +func (*PluginType) Descriptor() ([]byte, []int) { + return file_teleport_plugins_v1_plugin_service_proto_rawDescGZIP(), []int{0} +} + +func (x *PluginType) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *PluginType) GetOauthClientId() string { + if x != nil { + return x.OauthClientId + } + return "" +} + // CreatePluginRequest creates a new plugin from the given spec and initial credentials. type CreatePluginRequest struct { state protoimpl.MessageState @@ -54,7 +116,7 @@ type CreatePluginRequest struct { func (x *CreatePluginRequest) Reset() { *x = CreatePluginRequest{} if protoimpl.UnsafeEnabled { - mi := &file_teleport_plugins_v1_plugin_service_proto_msgTypes[0] + mi := &file_teleport_plugins_v1_plugin_service_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -67,7 +129,7 @@ func (x *CreatePluginRequest) String() string { func (*CreatePluginRequest) ProtoMessage() {} func (x *CreatePluginRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_plugins_v1_plugin_service_proto_msgTypes[0] + mi := &file_teleport_plugins_v1_plugin_service_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -80,7 +142,7 @@ func (x *CreatePluginRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreatePluginRequest.ProtoReflect.Descriptor instead. func (*CreatePluginRequest) Descriptor() ([]byte, []int) { - return file_teleport_plugins_v1_plugin_service_proto_rawDescGZIP(), []int{0} + return file_teleport_plugins_v1_plugin_service_proto_rawDescGZIP(), []int{1} } func (x *CreatePluginRequest) GetPlugin() *types.PluginV1 { @@ -110,7 +172,7 @@ type GetPluginRequest struct { func (x *GetPluginRequest) Reset() { *x = GetPluginRequest{} if protoimpl.UnsafeEnabled { - mi := &file_teleport_plugins_v1_plugin_service_proto_msgTypes[1] + mi := &file_teleport_plugins_v1_plugin_service_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -123,7 +185,7 @@ func (x *GetPluginRequest) String() string { func (*GetPluginRequest) ProtoMessage() {} func (x *GetPluginRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_plugins_v1_plugin_service_proto_msgTypes[1] + mi := &file_teleport_plugins_v1_plugin_service_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -136,7 +198,7 @@ func (x *GetPluginRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetPluginRequest.ProtoReflect.Descriptor instead. func (*GetPluginRequest) Descriptor() ([]byte, []int) { - return file_teleport_plugins_v1_plugin_service_proto_rawDescGZIP(), []int{1} + return file_teleport_plugins_v1_plugin_service_proto_rawDescGZIP(), []int{2} } func (x *GetPluginRequest) GetName() string { @@ -162,7 +224,7 @@ type ListPluginsRequest struct { func (x *ListPluginsRequest) Reset() { *x = ListPluginsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_teleport_plugins_v1_plugin_service_proto_msgTypes[2] + mi := &file_teleport_plugins_v1_plugin_service_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -175,7 +237,7 @@ func (x *ListPluginsRequest) String() string { func (*ListPluginsRequest) ProtoMessage() {} func (x *ListPluginsRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_plugins_v1_plugin_service_proto_msgTypes[2] + mi := &file_teleport_plugins_v1_plugin_service_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -188,7 +250,7 @@ func (x *ListPluginsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPluginsRequest.ProtoReflect.Descriptor instead. func (*ListPluginsRequest) Descriptor() ([]byte, []int) { - return file_teleport_plugins_v1_plugin_service_proto_rawDescGZIP(), []int{2} + return file_teleport_plugins_v1_plugin_service_proto_rawDescGZIP(), []int{3} } func (x *ListPluginsRequest) GetPageSize() int32 { @@ -221,7 +283,7 @@ type ListPluginsResponse struct { func (x *ListPluginsResponse) Reset() { *x = ListPluginsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_teleport_plugins_v1_plugin_service_proto_msgTypes[3] + mi := &file_teleport_plugins_v1_plugin_service_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -234,7 +296,7 @@ func (x *ListPluginsResponse) String() string { func (*ListPluginsResponse) ProtoMessage() {} func (x *ListPluginsResponse) ProtoReflect() protoreflect.Message { - mi := &file_teleport_plugins_v1_plugin_service_proto_msgTypes[3] + mi := &file_teleport_plugins_v1_plugin_service_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -247,7 +309,7 @@ func (x *ListPluginsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPluginsResponse.ProtoReflect.Descriptor instead. func (*ListPluginsResponse) Descriptor() ([]byte, []int) { - return file_teleport_plugins_v1_plugin_service_proto_rawDescGZIP(), []int{3} + return file_teleport_plugins_v1_plugin_service_proto_rawDescGZIP(), []int{4} } func (x *ListPluginsResponse) GetPlugins() []*types.PluginV1 { @@ -277,7 +339,7 @@ type DeletePluginRequest struct { func (x *DeletePluginRequest) Reset() { *x = DeletePluginRequest{} if protoimpl.UnsafeEnabled { - mi := &file_teleport_plugins_v1_plugin_service_proto_msgTypes[4] + mi := &file_teleport_plugins_v1_plugin_service_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -290,7 +352,7 @@ func (x *DeletePluginRequest) String() string { func (*DeletePluginRequest) ProtoMessage() {} func (x *DeletePluginRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_plugins_v1_plugin_service_proto_msgTypes[4] + mi := &file_teleport_plugins_v1_plugin_service_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -303,7 +365,7 @@ func (x *DeletePluginRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeletePluginRequest.ProtoReflect.Descriptor instead. func (*DeletePluginRequest) Descriptor() ([]byte, []int) { - return file_teleport_plugins_v1_plugin_service_proto_rawDescGZIP(), []int{4} + return file_teleport_plugins_v1_plugin_service_proto_rawDescGZIP(), []int{5} } func (x *DeletePluginRequest) GetName() string { @@ -329,7 +391,7 @@ type SetPluginCredentialsRequest struct { func (x *SetPluginCredentialsRequest) Reset() { *x = SetPluginCredentialsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_teleport_plugins_v1_plugin_service_proto_msgTypes[5] + mi := &file_teleport_plugins_v1_plugin_service_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -342,7 +404,7 @@ func (x *SetPluginCredentialsRequest) String() string { func (*SetPluginCredentialsRequest) ProtoMessage() {} func (x *SetPluginCredentialsRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_plugins_v1_plugin_service_proto_msgTypes[5] + mi := &file_teleport_plugins_v1_plugin_service_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -355,7 +417,7 @@ func (x *SetPluginCredentialsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetPluginCredentialsRequest.ProtoReflect.Descriptor instead. func (*SetPluginCredentialsRequest) Descriptor() ([]byte, []int) { - return file_teleport_plugins_v1_plugin_service_proto_rawDescGZIP(), []int{5} + return file_teleport_plugins_v1_plugin_service_proto_rawDescGZIP(), []int{6} } func (x *SetPluginCredentialsRequest) GetName() string { @@ -387,7 +449,7 @@ type SetPluginStatusRequest struct { func (x *SetPluginStatusRequest) Reset() { *x = SetPluginStatusRequest{} if protoimpl.UnsafeEnabled { - mi := &file_teleport_plugins_v1_plugin_service_proto_msgTypes[6] + mi := &file_teleport_plugins_v1_plugin_service_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -400,7 +462,7 @@ func (x *SetPluginStatusRequest) String() string { func (*SetPluginStatusRequest) ProtoMessage() {} func (x *SetPluginStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_teleport_plugins_v1_plugin_service_proto_msgTypes[6] + mi := &file_teleport_plugins_v1_plugin_service_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -413,7 +475,7 @@ func (x *SetPluginStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetPluginStatusRequest.ProtoReflect.Descriptor instead. func (*SetPluginStatusRequest) Descriptor() ([]byte, []int) { - return file_teleport_plugins_v1_plugin_service_proto_rawDescGZIP(), []int{6} + return file_teleport_plugins_v1_plugin_service_proto_rawDescGZIP(), []int{7} } func (x *SetPluginStatusRequest) GetName() string { @@ -430,6 +492,95 @@ func (x *SetPluginStatusRequest) GetStatus() *types.PluginStatusV1 { return nil } +// GetAvailablePluginTypesRequest is the request type for GetAvailablePluginTypes +type GetAvailablePluginTypesRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *GetAvailablePluginTypesRequest) Reset() { + *x = GetAvailablePluginTypesRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_teleport_plugins_v1_plugin_service_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetAvailablePluginTypesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetAvailablePluginTypesRequest) ProtoMessage() {} + +func (x *GetAvailablePluginTypesRequest) ProtoReflect() protoreflect.Message { + mi := &file_teleport_plugins_v1_plugin_service_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetAvailablePluginTypesRequest.ProtoReflect.Descriptor instead. +func (*GetAvailablePluginTypesRequest) Descriptor() ([]byte, []int) { + return file_teleport_plugins_v1_plugin_service_proto_rawDescGZIP(), []int{8} +} + +// GetAvailablePluginTypesResponse is a response to for GetAvailablePluginTypes +type GetAvailablePluginTypesResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // PluginTypes is a list of hosted plugins + // that the auth service supports. + PluginTypes []*PluginType `protobuf:"bytes,1,rep,name=plugin_types,json=pluginTypes,proto3" json:"plugin_types,omitempty"` +} + +func (x *GetAvailablePluginTypesResponse) Reset() { + *x = GetAvailablePluginTypesResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_teleport_plugins_v1_plugin_service_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetAvailablePluginTypesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetAvailablePluginTypesResponse) ProtoMessage() {} + +func (x *GetAvailablePluginTypesResponse) ProtoReflect() protoreflect.Message { + mi := &file_teleport_plugins_v1_plugin_service_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetAvailablePluginTypesResponse.ProtoReflect.Descriptor instead. +func (*GetAvailablePluginTypesResponse) Descriptor() ([]byte, []int) { + return file_teleport_plugins_v1_plugin_service_proto_rawDescGZIP(), []int{9} +} + +func (x *GetAvailablePluginTypesResponse) GetPluginTypes() []*PluginType { + if x != nil { + return x.PluginTypes + } + return nil +} + var File_teleport_plugins_v1_plugin_service_proto protoreflect.FileDescriptor var file_teleport_plugins_v1_plugin_service_proto_rawDesc = []byte{ @@ -441,84 +592,105 @@ var file_teleport_plugins_v1_plugin_service_proto_rawDesc = []byte{ 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x21, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, - 0x98, 0x01, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x06, 0x70, 0x6c, 0x75, 0x67, 0x69, - 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, - 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x56, 0x31, 0x52, 0x06, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, - 0x12, 0x58, 0x0a, 0x15, 0x62, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x5f, 0x63, 0x72, - 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x23, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x42, 0x6f, - 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, - 0x6c, 0x73, 0x56, 0x31, 0x52, 0x14, 0x62, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x43, - 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x22, 0x26, 0x0a, 0x10, 0x47, 0x65, - 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, - 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x22, 0x4e, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, - 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, - 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6b, - 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4b, - 0x65, 0x79, 0x22, 0x5b, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x29, 0x0a, 0x07, 0x70, 0x6c, 0x75, - 0x67, 0x69, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x74, 0x79, 0x70, - 0x65, 0x73, 0x2e, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x56, 0x31, 0x52, 0x07, 0x70, 0x6c, 0x75, - 0x67, 0x69, 0x6e, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x6b, 0x65, 0x79, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x78, 0x74, 0x4b, 0x65, 0x79, 0x22, - 0x29, 0x0a, 0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x6f, 0x0a, 0x1b, 0x53, 0x65, - 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, - 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3c, 0x0a, - 0x0b, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x50, 0x6c, 0x75, 0x67, 0x69, - 0x6e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x56, 0x31, 0x52, 0x0b, - 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x22, 0x5b, 0x0a, 0x16, 0x53, - 0x65, 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2d, 0x0a, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x79, 0x70, 0x65, - 0x73, 0x2e, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x56, 0x31, - 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x32, 0x94, 0x04, 0x0a, 0x0d, 0x50, 0x6c, 0x75, - 0x67, 0x69, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x50, 0x0a, 0x0c, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x12, 0x28, 0x2e, 0x74, 0x65, 0x6c, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x2e, 0x76, 0x31, - 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x43, 0x0a, 0x09, - 0x47, 0x65, 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x12, 0x25, 0x2e, 0x74, 0x65, 0x6c, 0x65, - 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, - 0x47, 0x65, 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x0f, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x56, - 0x31, 0x12, 0x50, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6c, 0x75, 0x67, 0x69, - 0x6e, 0x12, 0x28, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x70, 0x6c, 0x75, - 0x67, 0x69, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6c, - 0x75, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x12, 0x60, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69, - 0x6e, 0x73, 0x12, 0x27, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x70, 0x6c, - 0x75, 0x67, 0x69, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6c, 0x75, - 0x67, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x74, 0x65, - 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x2e, 0x76, - 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x60, 0x0a, 0x14, 0x53, 0x65, 0x74, 0x50, 0x6c, 0x75, 0x67, - 0x69, 0x6e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x12, 0x30, 0x2e, + 0x48, 0x0a, 0x0a, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, + 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6f, 0x61, 0x75, 0x74, + 0x68, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x98, 0x01, 0x0a, 0x13, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x27, 0x0a, 0x06, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x0f, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, + 0x56, 0x31, 0x52, 0x06, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x12, 0x58, 0x0a, 0x15, 0x62, 0x6f, + 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x61, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x74, 0x79, 0x70, 0x65, + 0x73, 0x2e, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x42, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, + 0x70, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x56, 0x31, 0x52, 0x14, + 0x62, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x61, 0x6c, 0x73, 0x22, 0x26, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69, + 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x4e, 0x0a, 0x12, + 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, + 0x1b, 0x0a, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4b, 0x65, 0x79, 0x22, 0x5b, 0x0a, 0x13, + 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x29, 0x0a, 0x07, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x50, 0x6c, 0x75, + 0x67, 0x69, 0x6e, 0x56, 0x31, 0x52, 0x07, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x12, 0x19, + 0x0a, 0x08, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x6e, 0x65, 0x78, 0x74, 0x4b, 0x65, 0x79, 0x22, 0x29, 0x0a, 0x13, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x6f, 0x0a, 0x1b, 0x53, 0x65, 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69, + 0x6e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3c, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x74, + 0x79, 0x70, 0x65, 0x73, 0x2e, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x43, 0x72, 0x65, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x56, 0x31, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x61, 0x6c, 0x73, 0x22, 0x5b, 0x0a, 0x16, 0x53, 0x65, 0x74, 0x50, 0x6c, 0x75, 0x67, + 0x69, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x2d, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x50, 0x6c, 0x75, 0x67, + 0x69, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x56, 0x31, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x22, 0x20, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, + 0x6c, 0x65, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x22, 0x65, 0x0a, 0x1f, 0x47, 0x65, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, + 0x61, 0x62, 0x6c, 0x65, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x0c, 0x70, 0x6c, 0x75, 0x67, 0x69, + 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, + 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, + 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, + 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x73, 0x32, 0x9b, 0x05, 0x0a, 0x0d, + 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x50, 0x0a, + 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x12, 0x28, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, - 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x43, 0x72, 0x65, - 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x56, 0x0a, 0x0f, 0x53, 0x65, 0x74, 0x50, 0x6c, - 0x75, 0x67, 0x69, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2b, 0x2e, 0x74, 0x65, 0x6c, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x2e, 0x76, 0x31, - 0x2e, 0x53, 0x65, 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, - 0x48, 0x5a, 0x46, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, - 0x61, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x6c, 0x65, - 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x70, - 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, + 0x43, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x12, 0x25, 0x2e, 0x74, + 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x50, 0x6c, 0x75, 0x67, + 0x69, 0x6e, 0x56, 0x31, 0x12, 0x50, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x6c, + 0x75, 0x67, 0x69, 0x6e, 0x12, 0x28, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, + 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x60, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6c, + 0x75, 0x67, 0x69, 0x6e, 0x73, 0x12, 0x27, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, + 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x60, 0x0a, 0x14, 0x53, 0x65, 0x74, 0x50, + 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, + 0x12, 0x30, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x70, 0x6c, 0x75, 0x67, + 0x69, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, + 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x56, 0x0a, 0x0f, 0x53, 0x65, + 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2b, 0x2e, + 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, + 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, + 0x74, 0x79, 0x12, 0x84, 0x01, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, + 0x62, 0x6c, 0x65, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x33, + 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, + 0x65, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x70, + 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x76, 0x61, + 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x48, 0x5a, 0x46, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x76, 0x69, 0x74, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x61, + 0x70, 0x69, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x2f, + 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, + 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -533,44 +705,50 @@ func file_teleport_plugins_v1_plugin_service_proto_rawDescGZIP() []byte { return file_teleport_plugins_v1_plugin_service_proto_rawDescData } -var file_teleport_plugins_v1_plugin_service_proto_msgTypes = make([]protoimpl.MessageInfo, 7) +var file_teleport_plugins_v1_plugin_service_proto_msgTypes = make([]protoimpl.MessageInfo, 10) var file_teleport_plugins_v1_plugin_service_proto_goTypes = []interface{}{ - (*CreatePluginRequest)(nil), // 0: teleport.plugins.v1.CreatePluginRequest - (*GetPluginRequest)(nil), // 1: teleport.plugins.v1.GetPluginRequest - (*ListPluginsRequest)(nil), // 2: teleport.plugins.v1.ListPluginsRequest - (*ListPluginsResponse)(nil), // 3: teleport.plugins.v1.ListPluginsResponse - (*DeletePluginRequest)(nil), // 4: teleport.plugins.v1.DeletePluginRequest - (*SetPluginCredentialsRequest)(nil), // 5: teleport.plugins.v1.SetPluginCredentialsRequest - (*SetPluginStatusRequest)(nil), // 6: teleport.plugins.v1.SetPluginStatusRequest - (*types.PluginV1)(nil), // 7: types.PluginV1 - (*types.PluginBootstrapCredentialsV1)(nil), // 8: types.PluginBootstrapCredentialsV1 - (*types.PluginCredentialsV1)(nil), // 9: types.PluginCredentialsV1 - (*types.PluginStatusV1)(nil), // 10: types.PluginStatusV1 - (*emptypb.Empty)(nil), // 11: google.protobuf.Empty + (*PluginType)(nil), // 0: teleport.plugins.v1.PluginType + (*CreatePluginRequest)(nil), // 1: teleport.plugins.v1.CreatePluginRequest + (*GetPluginRequest)(nil), // 2: teleport.plugins.v1.GetPluginRequest + (*ListPluginsRequest)(nil), // 3: teleport.plugins.v1.ListPluginsRequest + (*ListPluginsResponse)(nil), // 4: teleport.plugins.v1.ListPluginsResponse + (*DeletePluginRequest)(nil), // 5: teleport.plugins.v1.DeletePluginRequest + (*SetPluginCredentialsRequest)(nil), // 6: teleport.plugins.v1.SetPluginCredentialsRequest + (*SetPluginStatusRequest)(nil), // 7: teleport.plugins.v1.SetPluginStatusRequest + (*GetAvailablePluginTypesRequest)(nil), // 8: teleport.plugins.v1.GetAvailablePluginTypesRequest + (*GetAvailablePluginTypesResponse)(nil), // 9: teleport.plugins.v1.GetAvailablePluginTypesResponse + (*types.PluginV1)(nil), // 10: types.PluginV1 + (*types.PluginBootstrapCredentialsV1)(nil), // 11: types.PluginBootstrapCredentialsV1 + (*types.PluginCredentialsV1)(nil), // 12: types.PluginCredentialsV1 + (*types.PluginStatusV1)(nil), // 13: types.PluginStatusV1 + (*emptypb.Empty)(nil), // 14: google.protobuf.Empty } var file_teleport_plugins_v1_plugin_service_proto_depIdxs = []int32{ - 7, // 0: teleport.plugins.v1.CreatePluginRequest.plugin:type_name -> types.PluginV1 - 8, // 1: teleport.plugins.v1.CreatePluginRequest.bootstrap_credentials:type_name -> types.PluginBootstrapCredentialsV1 - 7, // 2: teleport.plugins.v1.ListPluginsResponse.plugins:type_name -> types.PluginV1 - 9, // 3: teleport.plugins.v1.SetPluginCredentialsRequest.credentials:type_name -> types.PluginCredentialsV1 - 10, // 4: teleport.plugins.v1.SetPluginStatusRequest.status:type_name -> types.PluginStatusV1 - 0, // 5: teleport.plugins.v1.PluginService.CreatePlugin:input_type -> teleport.plugins.v1.CreatePluginRequest - 1, // 6: teleport.plugins.v1.PluginService.GetPlugin:input_type -> teleport.plugins.v1.GetPluginRequest - 4, // 7: teleport.plugins.v1.PluginService.DeletePlugin:input_type -> teleport.plugins.v1.DeletePluginRequest - 2, // 8: teleport.plugins.v1.PluginService.ListPlugins:input_type -> teleport.plugins.v1.ListPluginsRequest - 5, // 9: teleport.plugins.v1.PluginService.SetPluginCredentials:input_type -> teleport.plugins.v1.SetPluginCredentialsRequest - 6, // 10: teleport.plugins.v1.PluginService.SetPluginStatus:input_type -> teleport.plugins.v1.SetPluginStatusRequest - 11, // 11: teleport.plugins.v1.PluginService.CreatePlugin:output_type -> google.protobuf.Empty - 7, // 12: teleport.plugins.v1.PluginService.GetPlugin:output_type -> types.PluginV1 - 11, // 13: teleport.plugins.v1.PluginService.DeletePlugin:output_type -> google.protobuf.Empty - 3, // 14: teleport.plugins.v1.PluginService.ListPlugins:output_type -> teleport.plugins.v1.ListPluginsResponse - 11, // 15: teleport.plugins.v1.PluginService.SetPluginCredentials:output_type -> google.protobuf.Empty - 11, // 16: teleport.plugins.v1.PluginService.SetPluginStatus:output_type -> google.protobuf.Empty - 11, // [11:17] is the sub-list for method output_type - 5, // [5:11] is the sub-list for method input_type - 5, // [5:5] is the sub-list for extension type_name - 5, // [5:5] is the sub-list for extension extendee - 0, // [0:5] is the sub-list for field type_name + 10, // 0: teleport.plugins.v1.CreatePluginRequest.plugin:type_name -> types.PluginV1 + 11, // 1: teleport.plugins.v1.CreatePluginRequest.bootstrap_credentials:type_name -> types.PluginBootstrapCredentialsV1 + 10, // 2: teleport.plugins.v1.ListPluginsResponse.plugins:type_name -> types.PluginV1 + 12, // 3: teleport.plugins.v1.SetPluginCredentialsRequest.credentials:type_name -> types.PluginCredentialsV1 + 13, // 4: teleport.plugins.v1.SetPluginStatusRequest.status:type_name -> types.PluginStatusV1 + 0, // 5: teleport.plugins.v1.GetAvailablePluginTypesResponse.plugin_types:type_name -> teleport.plugins.v1.PluginType + 1, // 6: teleport.plugins.v1.PluginService.CreatePlugin:input_type -> teleport.plugins.v1.CreatePluginRequest + 2, // 7: teleport.plugins.v1.PluginService.GetPlugin:input_type -> teleport.plugins.v1.GetPluginRequest + 5, // 8: teleport.plugins.v1.PluginService.DeletePlugin:input_type -> teleport.plugins.v1.DeletePluginRequest + 3, // 9: teleport.plugins.v1.PluginService.ListPlugins:input_type -> teleport.plugins.v1.ListPluginsRequest + 6, // 10: teleport.plugins.v1.PluginService.SetPluginCredentials:input_type -> teleport.plugins.v1.SetPluginCredentialsRequest + 7, // 11: teleport.plugins.v1.PluginService.SetPluginStatus:input_type -> teleport.plugins.v1.SetPluginStatusRequest + 8, // 12: teleport.plugins.v1.PluginService.GetAvailablePluginTypes:input_type -> teleport.plugins.v1.GetAvailablePluginTypesRequest + 14, // 13: teleport.plugins.v1.PluginService.CreatePlugin:output_type -> google.protobuf.Empty + 10, // 14: teleport.plugins.v1.PluginService.GetPlugin:output_type -> types.PluginV1 + 14, // 15: teleport.plugins.v1.PluginService.DeletePlugin:output_type -> google.protobuf.Empty + 4, // 16: teleport.plugins.v1.PluginService.ListPlugins:output_type -> teleport.plugins.v1.ListPluginsResponse + 14, // 17: teleport.plugins.v1.PluginService.SetPluginCredentials:output_type -> google.protobuf.Empty + 14, // 18: teleport.plugins.v1.PluginService.SetPluginStatus:output_type -> google.protobuf.Empty + 9, // 19: teleport.plugins.v1.PluginService.GetAvailablePluginTypes:output_type -> teleport.plugins.v1.GetAvailablePluginTypesResponse + 13, // [13:20] is the sub-list for method output_type + 6, // [6:13] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name } func init() { file_teleport_plugins_v1_plugin_service_proto_init() } @@ -580,7 +758,7 @@ func file_teleport_plugins_v1_plugin_service_proto_init() { } if !protoimpl.UnsafeEnabled { file_teleport_plugins_v1_plugin_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CreatePluginRequest); i { + switch v := v.(*PluginType); i { case 0: return &v.state case 1: @@ -592,7 +770,7 @@ func file_teleport_plugins_v1_plugin_service_proto_init() { } } file_teleport_plugins_v1_plugin_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetPluginRequest); i { + switch v := v.(*CreatePluginRequest); i { case 0: return &v.state case 1: @@ -604,7 +782,7 @@ func file_teleport_plugins_v1_plugin_service_proto_init() { } } file_teleport_plugins_v1_plugin_service_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListPluginsRequest); i { + switch v := v.(*GetPluginRequest); i { case 0: return &v.state case 1: @@ -616,7 +794,7 @@ func file_teleport_plugins_v1_plugin_service_proto_init() { } } file_teleport_plugins_v1_plugin_service_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListPluginsResponse); i { + switch v := v.(*ListPluginsRequest); i { case 0: return &v.state case 1: @@ -628,7 +806,7 @@ func file_teleport_plugins_v1_plugin_service_proto_init() { } } file_teleport_plugins_v1_plugin_service_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DeletePluginRequest); i { + switch v := v.(*ListPluginsResponse); i { case 0: return &v.state case 1: @@ -640,7 +818,7 @@ func file_teleport_plugins_v1_plugin_service_proto_init() { } } file_teleport_plugins_v1_plugin_service_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SetPluginCredentialsRequest); i { + switch v := v.(*DeletePluginRequest); i { case 0: return &v.state case 1: @@ -652,6 +830,18 @@ func file_teleport_plugins_v1_plugin_service_proto_init() { } } file_teleport_plugins_v1_plugin_service_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SetPluginCredentialsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_teleport_plugins_v1_plugin_service_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SetPluginStatusRequest); i { case 0: return &v.state @@ -663,6 +853,30 @@ func file_teleport_plugins_v1_plugin_service_proto_init() { return nil } } + file_teleport_plugins_v1_plugin_service_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetAvailablePluginTypesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_teleport_plugins_v1_plugin_service_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetAvailablePluginTypesResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -670,7 +884,7 @@ func file_teleport_plugins_v1_plugin_service_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_teleport_plugins_v1_plugin_service_proto_rawDesc, NumEnums: 0, - NumMessages: 7, + NumMessages: 10, NumExtensions: 0, NumServices: 1, }, diff --git a/api/gen/proto/go/teleport/plugins/v1/plugin_service_grpc.pb.go b/api/gen/proto/go/teleport/plugins/v1/plugin_service_grpc.pb.go index 33547f6824a26..17808cf464d02 100644 --- a/api/gen/proto/go/teleport/plugins/v1/plugin_service_grpc.pb.go +++ b/api/gen/proto/go/teleport/plugins/v1/plugin_service_grpc.pb.go @@ -35,12 +35,13 @@ import ( const _ = grpc.SupportPackageIsVersion7 const ( - PluginService_CreatePlugin_FullMethodName = "/teleport.plugins.v1.PluginService/CreatePlugin" - PluginService_GetPlugin_FullMethodName = "/teleport.plugins.v1.PluginService/GetPlugin" - PluginService_DeletePlugin_FullMethodName = "/teleport.plugins.v1.PluginService/DeletePlugin" - PluginService_ListPlugins_FullMethodName = "/teleport.plugins.v1.PluginService/ListPlugins" - PluginService_SetPluginCredentials_FullMethodName = "/teleport.plugins.v1.PluginService/SetPluginCredentials" - PluginService_SetPluginStatus_FullMethodName = "/teleport.plugins.v1.PluginService/SetPluginStatus" + PluginService_CreatePlugin_FullMethodName = "/teleport.plugins.v1.PluginService/CreatePlugin" + PluginService_GetPlugin_FullMethodName = "/teleport.plugins.v1.PluginService/GetPlugin" + PluginService_DeletePlugin_FullMethodName = "/teleport.plugins.v1.PluginService/DeletePlugin" + PluginService_ListPlugins_FullMethodName = "/teleport.plugins.v1.PluginService/ListPlugins" + PluginService_SetPluginCredentials_FullMethodName = "/teleport.plugins.v1.PluginService/SetPluginCredentials" + PluginService_SetPluginStatus_FullMethodName = "/teleport.plugins.v1.PluginService/SetPluginStatus" + PluginService_GetAvailablePluginTypes_FullMethodName = "/teleport.plugins.v1.PluginService/GetAvailablePluginTypes" ) // PluginServiceClient is the client API for PluginService service. @@ -59,6 +60,9 @@ type PluginServiceClient interface { SetPluginCredentials(ctx context.Context, in *SetPluginCredentialsRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) // SetPluginCredentials sets the status for the given plugin. SetPluginStatus(ctx context.Context, in *SetPluginStatusRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + // GetAvailablePluginTypes returns the types of plugins + // that the auth server supports onboarding. + GetAvailablePluginTypes(ctx context.Context, in *GetAvailablePluginTypesRequest, opts ...grpc.CallOption) (*GetAvailablePluginTypesResponse, error) } type pluginServiceClient struct { @@ -123,6 +127,15 @@ func (c *pluginServiceClient) SetPluginStatus(ctx context.Context, in *SetPlugin return out, nil } +func (c *pluginServiceClient) GetAvailablePluginTypes(ctx context.Context, in *GetAvailablePluginTypesRequest, opts ...grpc.CallOption) (*GetAvailablePluginTypesResponse, error) { + out := new(GetAvailablePluginTypesResponse) + err := c.cc.Invoke(ctx, PluginService_GetAvailablePluginTypes_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // PluginServiceServer is the server API for PluginService service. // All implementations must embed UnimplementedPluginServiceServer // for forward compatibility @@ -139,6 +152,9 @@ type PluginServiceServer interface { SetPluginCredentials(context.Context, *SetPluginCredentialsRequest) (*emptypb.Empty, error) // SetPluginCredentials sets the status for the given plugin. SetPluginStatus(context.Context, *SetPluginStatusRequest) (*emptypb.Empty, error) + // GetAvailablePluginTypes returns the types of plugins + // that the auth server supports onboarding. + GetAvailablePluginTypes(context.Context, *GetAvailablePluginTypesRequest) (*GetAvailablePluginTypesResponse, error) mustEmbedUnimplementedPluginServiceServer() } @@ -164,6 +180,9 @@ func (UnimplementedPluginServiceServer) SetPluginCredentials(context.Context, *S func (UnimplementedPluginServiceServer) SetPluginStatus(context.Context, *SetPluginStatusRequest) (*emptypb.Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method SetPluginStatus not implemented") } +func (UnimplementedPluginServiceServer) GetAvailablePluginTypes(context.Context, *GetAvailablePluginTypesRequest) (*GetAvailablePluginTypesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetAvailablePluginTypes not implemented") +} func (UnimplementedPluginServiceServer) mustEmbedUnimplementedPluginServiceServer() {} // UnsafePluginServiceServer may be embedded to opt out of forward compatibility for this service. @@ -285,6 +304,24 @@ func _PluginService_SetPluginStatus_Handler(srv interface{}, ctx context.Context return interceptor(ctx, in, info, handler) } +func _PluginService_GetAvailablePluginTypes_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetAvailablePluginTypesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PluginServiceServer).GetAvailablePluginTypes(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: PluginService_GetAvailablePluginTypes_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PluginServiceServer).GetAvailablePluginTypes(ctx, req.(*GetAvailablePluginTypesRequest)) + } + return interceptor(ctx, in, info, handler) +} + // PluginService_ServiceDesc is the grpc.ServiceDesc for PluginService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -316,6 +353,10 @@ var PluginService_ServiceDesc = grpc.ServiceDesc{ MethodName: "SetPluginStatus", Handler: _PluginService_SetPluginStatus_Handler, }, + { + MethodName: "GetAvailablePluginTypes", + Handler: _PluginService_GetAvailablePluginTypes_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "teleport/plugins/v1/plugin_service.proto", diff --git a/api/gen/proto/go/usageevents/v1/usageevents.pb.go b/api/gen/proto/go/usageevents/v1/usageevents.pb.go index 36033888d48a4..57e663baa9f46 100644 --- a/api/gen/proto/go/usageevents/v1/usageevents.pb.go +++ b/api/gen/proto/go/usageevents/v1/usageevents.pb.go @@ -26,24 +26,43 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package type DiscoverResource int32 const ( - DiscoverResource_DISCOVER_RESOURCE_UNSPECIFIED DiscoverResource = 0 - DiscoverResource_DISCOVER_RESOURCE_SERVER DiscoverResource = 1 - DiscoverResource_DISCOVER_RESOURCE_KUBERNETES DiscoverResource = 2 - DiscoverResource_DISCOVER_RESOURCE_DATABASE_POSTGRES_SELF_HOSTED DiscoverResource = 3 - DiscoverResource_DISCOVER_RESOURCE_DATABASE_MYSQL_SELF_HOSTED DiscoverResource = 4 - DiscoverResource_DISCOVER_RESOURCE_DATABASE_MONGODB_SELF_HOSTED DiscoverResource = 5 - DiscoverResource_DISCOVER_RESOURCE_DATABASE_POSTGRES_RDS DiscoverResource = 6 - DiscoverResource_DISCOVER_RESOURCE_DATABASE_MYSQL_RDS DiscoverResource = 7 - DiscoverResource_DISCOVER_RESOURCE_APPLICATION_HTTP DiscoverResource = 8 - DiscoverResource_DISCOVER_RESOURCE_APPLICATION_TCP DiscoverResource = 9 - DiscoverResource_DISCOVER_RESOURCE_WINDOWS_DESKTOP DiscoverResource = 10 - DiscoverResource_DISCOVER_RESOURCE_DATABASE_SQLSERVER_RDS DiscoverResource = 11 - DiscoverResource_DISCOVER_RESOURCE_DATABASE_POSTGRES_REDSHIFT DiscoverResource = 12 - DiscoverResource_DISCOVER_RESOURCE_DATABASE_SQLSERVER_SELF_HOSTED DiscoverResource = 13 - DiscoverResource_DISCOVER_RESOURCE_DATABASE_REDIS_SELF_HOSTED DiscoverResource = 14 - DiscoverResource_DISCOVER_RESOURCE_DATABASE_POSTGRES_GCP DiscoverResource = 15 - DiscoverResource_DISCOVER_RESOURCE_DATABASE_MYSQL_GCP DiscoverResource = 16 - DiscoverResource_DISCOVER_RESOURCE_DATABASE_SQLSERVER_GCP DiscoverResource = 17 + DiscoverResource_DISCOVER_RESOURCE_UNSPECIFIED DiscoverResource = 0 + DiscoverResource_DISCOVER_RESOURCE_SERVER DiscoverResource = 1 + DiscoverResource_DISCOVER_RESOURCE_KUBERNETES DiscoverResource = 2 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_POSTGRES_SELF_HOSTED DiscoverResource = 3 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_MYSQL_SELF_HOSTED DiscoverResource = 4 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_MONGODB_SELF_HOSTED DiscoverResource = 5 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_POSTGRES_RDS DiscoverResource = 6 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_MYSQL_RDS DiscoverResource = 7 + DiscoverResource_DISCOVER_RESOURCE_APPLICATION_HTTP DiscoverResource = 8 + DiscoverResource_DISCOVER_RESOURCE_APPLICATION_TCP DiscoverResource = 9 + DiscoverResource_DISCOVER_RESOURCE_WINDOWS_DESKTOP DiscoverResource = 10 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_SQLSERVER_RDS DiscoverResource = 11 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_POSTGRES_REDSHIFT DiscoverResource = 12 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_SQLSERVER_SELF_HOSTED DiscoverResource = 13 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_REDIS_SELF_HOSTED DiscoverResource = 14 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_POSTGRES_GCP DiscoverResource = 15 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_MYSQL_GCP DiscoverResource = 16 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_SQLSERVER_GCP DiscoverResource = 17 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_POSTGRES_REDSHIFT_SERVERLESS DiscoverResource = 18 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_POSTGRES_AZURE DiscoverResource = 19 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_DYNAMODB DiscoverResource = 20 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_CASSANDRA_KEYSPACES DiscoverResource = 21 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_CASSANDRA_SELF_HOSTED DiscoverResource = 22 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_ELASTICSEARCH_SELF_HOSTED DiscoverResource = 23 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_REDIS_ELASTICACHE DiscoverResource = 24 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_REDIS_MEMORYDB DiscoverResource = 25 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_REDIS_AZURE_CACHE DiscoverResource = 26 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_REDIS_CLUSTER_SELF_HOSTED DiscoverResource = 27 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_MYSQL_AZURE DiscoverResource = 28 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_SQLSERVER_AZURE DiscoverResource = 29 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_SQLSERVER_MICROSOFT DiscoverResource = 30 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_COCKROACHDB_SELF_HOSTED DiscoverResource = 31 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_MONGODB_ATLAS DiscoverResource = 32 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_SNOWFLAKE DiscoverResource = 33 + DiscoverResource_DISCOVER_RESOURCE_DOC_DATABASE_RDS_PROXY DiscoverResource = 34 + DiscoverResource_DISCOVER_RESOURCE_DOC_DATABASE_HIGH_AVAILABILITY DiscoverResource = 35 + DiscoverResource_DISCOVER_RESOURCE_DOC_DATABASE_DYNAMIC_REGISTRATION DiscoverResource = 36 ) var DiscoverResource_name = map[int32]string{ @@ -65,27 +84,65 @@ var DiscoverResource_name = map[int32]string{ 15: "DISCOVER_RESOURCE_DATABASE_POSTGRES_GCP", 16: "DISCOVER_RESOURCE_DATABASE_MYSQL_GCP", 17: "DISCOVER_RESOURCE_DATABASE_SQLSERVER_GCP", + 18: "DISCOVER_RESOURCE_DATABASE_POSTGRES_REDSHIFT_SERVERLESS", + 19: "DISCOVER_RESOURCE_DATABASE_POSTGRES_AZURE", + 20: "DISCOVER_RESOURCE_DATABASE_DYNAMODB", + 21: "DISCOVER_RESOURCE_DATABASE_CASSANDRA_KEYSPACES", + 22: "DISCOVER_RESOURCE_DATABASE_CASSANDRA_SELF_HOSTED", + 23: "DISCOVER_RESOURCE_DATABASE_ELASTICSEARCH_SELF_HOSTED", + 24: "DISCOVER_RESOURCE_DATABASE_REDIS_ELASTICACHE", + 25: "DISCOVER_RESOURCE_DATABASE_REDIS_MEMORYDB", + 26: "DISCOVER_RESOURCE_DATABASE_REDIS_AZURE_CACHE", + 27: "DISCOVER_RESOURCE_DATABASE_REDIS_CLUSTER_SELF_HOSTED", + 28: "DISCOVER_RESOURCE_DATABASE_MYSQL_AZURE", + 29: "DISCOVER_RESOURCE_DATABASE_SQLSERVER_AZURE", + 30: "DISCOVER_RESOURCE_DATABASE_SQLSERVER_MICROSOFT", + 31: "DISCOVER_RESOURCE_DATABASE_COCKROACHDB_SELF_HOSTED", + 32: "DISCOVER_RESOURCE_DATABASE_MONGODB_ATLAS", + 33: "DISCOVER_RESOURCE_DATABASE_SNOWFLAKE", + 34: "DISCOVER_RESOURCE_DOC_DATABASE_RDS_PROXY", + 35: "DISCOVER_RESOURCE_DOC_DATABASE_HIGH_AVAILABILITY", + 36: "DISCOVER_RESOURCE_DOC_DATABASE_DYNAMIC_REGISTRATION", } var DiscoverResource_value = map[string]int32{ - "DISCOVER_RESOURCE_UNSPECIFIED": 0, - "DISCOVER_RESOURCE_SERVER": 1, - "DISCOVER_RESOURCE_KUBERNETES": 2, - "DISCOVER_RESOURCE_DATABASE_POSTGRES_SELF_HOSTED": 3, - "DISCOVER_RESOURCE_DATABASE_MYSQL_SELF_HOSTED": 4, - "DISCOVER_RESOURCE_DATABASE_MONGODB_SELF_HOSTED": 5, - "DISCOVER_RESOURCE_DATABASE_POSTGRES_RDS": 6, - "DISCOVER_RESOURCE_DATABASE_MYSQL_RDS": 7, - "DISCOVER_RESOURCE_APPLICATION_HTTP": 8, - "DISCOVER_RESOURCE_APPLICATION_TCP": 9, - "DISCOVER_RESOURCE_WINDOWS_DESKTOP": 10, - "DISCOVER_RESOURCE_DATABASE_SQLSERVER_RDS": 11, - "DISCOVER_RESOURCE_DATABASE_POSTGRES_REDSHIFT": 12, - "DISCOVER_RESOURCE_DATABASE_SQLSERVER_SELF_HOSTED": 13, - "DISCOVER_RESOURCE_DATABASE_REDIS_SELF_HOSTED": 14, - "DISCOVER_RESOURCE_DATABASE_POSTGRES_GCP": 15, - "DISCOVER_RESOURCE_DATABASE_MYSQL_GCP": 16, - "DISCOVER_RESOURCE_DATABASE_SQLSERVER_GCP": 17, + "DISCOVER_RESOURCE_UNSPECIFIED": 0, + "DISCOVER_RESOURCE_SERVER": 1, + "DISCOVER_RESOURCE_KUBERNETES": 2, + "DISCOVER_RESOURCE_DATABASE_POSTGRES_SELF_HOSTED": 3, + "DISCOVER_RESOURCE_DATABASE_MYSQL_SELF_HOSTED": 4, + "DISCOVER_RESOURCE_DATABASE_MONGODB_SELF_HOSTED": 5, + "DISCOVER_RESOURCE_DATABASE_POSTGRES_RDS": 6, + "DISCOVER_RESOURCE_DATABASE_MYSQL_RDS": 7, + "DISCOVER_RESOURCE_APPLICATION_HTTP": 8, + "DISCOVER_RESOURCE_APPLICATION_TCP": 9, + "DISCOVER_RESOURCE_WINDOWS_DESKTOP": 10, + "DISCOVER_RESOURCE_DATABASE_SQLSERVER_RDS": 11, + "DISCOVER_RESOURCE_DATABASE_POSTGRES_REDSHIFT": 12, + "DISCOVER_RESOURCE_DATABASE_SQLSERVER_SELF_HOSTED": 13, + "DISCOVER_RESOURCE_DATABASE_REDIS_SELF_HOSTED": 14, + "DISCOVER_RESOURCE_DATABASE_POSTGRES_GCP": 15, + "DISCOVER_RESOURCE_DATABASE_MYSQL_GCP": 16, + "DISCOVER_RESOURCE_DATABASE_SQLSERVER_GCP": 17, + "DISCOVER_RESOURCE_DATABASE_POSTGRES_REDSHIFT_SERVERLESS": 18, + "DISCOVER_RESOURCE_DATABASE_POSTGRES_AZURE": 19, + "DISCOVER_RESOURCE_DATABASE_DYNAMODB": 20, + "DISCOVER_RESOURCE_DATABASE_CASSANDRA_KEYSPACES": 21, + "DISCOVER_RESOURCE_DATABASE_CASSANDRA_SELF_HOSTED": 22, + "DISCOVER_RESOURCE_DATABASE_ELASTICSEARCH_SELF_HOSTED": 23, + "DISCOVER_RESOURCE_DATABASE_REDIS_ELASTICACHE": 24, + "DISCOVER_RESOURCE_DATABASE_REDIS_MEMORYDB": 25, + "DISCOVER_RESOURCE_DATABASE_REDIS_AZURE_CACHE": 26, + "DISCOVER_RESOURCE_DATABASE_REDIS_CLUSTER_SELF_HOSTED": 27, + "DISCOVER_RESOURCE_DATABASE_MYSQL_AZURE": 28, + "DISCOVER_RESOURCE_DATABASE_SQLSERVER_AZURE": 29, + "DISCOVER_RESOURCE_DATABASE_SQLSERVER_MICROSOFT": 30, + "DISCOVER_RESOURCE_DATABASE_COCKROACHDB_SELF_HOSTED": 31, + "DISCOVER_RESOURCE_DATABASE_MONGODB_ATLAS": 32, + "DISCOVER_RESOURCE_DATABASE_SNOWFLAKE": 33, + "DISCOVER_RESOURCE_DOC_DATABASE_RDS_PROXY": 34, + "DISCOVER_RESOURCE_DOC_DATABASE_HIGH_AVAILABILITY": 35, + "DISCOVER_RESOURCE_DOC_DATABASE_DYNAMIC_REGISTRATION": 36, } func (x DiscoverResource) String() string { @@ -2135,129 +2192,145 @@ func init() { } var fileDescriptor_94cf2ca1c69fd564 = []byte{ - // 1949 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x9a, 0xcd, 0x6f, 0xe4, 0x48, - 0xf9, 0xc7, 0xed, 0xce, 0x4c, 0x26, 0x53, 0xb3, 0x93, 0xf4, 0xd6, 0x6f, 0x76, 0xa6, 0x33, 0x79, - 0xef, 0x79, 0x4b, 0x32, 0xa3, 0xf4, 0x66, 0x76, 0x7f, 0xb0, 0xb0, 0x0b, 0xa3, 0x8e, 0xdb, 0x89, - 0x7b, 0x27, 0x49, 0xf7, 0x96, 0xbb, 0x67, 0x05, 0x97, 0x52, 0xc5, 0xae, 0xf4, 0x58, 0xe3, 0xb6, - 0x5b, 0x76, 0xb9, 0x43, 0x0b, 0xa1, 0x15, 0x08, 0x21, 0xa4, 0x91, 0x10, 0xdc, 0x90, 0x90, 0x90, - 0x10, 0x1c, 0x10, 0xe2, 0xce, 0x99, 0x1b, 0x47, 0xfe, 0x04, 0x34, 0xff, 0x03, 0x07, 0x10, 0x68, - 0x91, 0xcb, 0xaf, 0xfd, 0x6a, 0x33, 0xe2, 0x94, 0xbd, 0xc5, 0xae, 0xa7, 0x9e, 0xe7, 0xf3, 0x3c, - 0x55, 0xb6, 0xeb, 0xfb, 0xa4, 0xc1, 0x0e, 0xa3, 0x26, 0xed, 0xd9, 0x0e, 0xab, 0x78, 0x2e, 0xe9, - 0x50, 0xda, 0xa7, 0x16, 0x73, 0x2b, 0xfd, 0xfd, 0xf4, 0xe5, 0x5e, 0xcf, 0xb1, 0x99, 0x0d, 0xef, - 0x44, 0xa6, 0x7b, 0xe9, 0xb1, 0xfe, 0x7e, 0x79, 0x17, 0xc0, 0x76, 0xfd, 0x80, 0x58, 0x16, 0x75, - 0x24, 0xd3, 0xd0, 0x5e, 0xc9, 0xfe, 0x08, 0xbc, 0x05, 0xae, 0x12, 0x93, 0x3a, 0xac, 0x24, 0x6e, - 0x8a, 0xdb, 0xd7, 0x51, 0x70, 0x51, 0x3e, 0x04, 0xdb, 0xed, 0x7a, 0xc3, 0x3a, 0xb3, 0x89, 0xa3, - 0x4b, 0x76, 0xb7, 0x67, 0x52, 0x46, 0x8f, 0xec, 0x96, 0x5d, 0x23, 0xee, 0xcb, 0xe0, 0x66, 0xe2, - 0xe1, 0x2e, 0x58, 0xf0, 0x5c, 0xea, 0x58, 0xa4, 0x4b, 0x43, 0x27, 0xf1, 0x75, 0xf9, 0x01, 0xb8, - 0x17, 0xfb, 0xa9, 0xea, 0xfa, 0xa1, 0xe1, 0xb8, 0x0c, 0x51, 0xd7, 0xf6, 0x1c, 0x8d, 0x26, 0x2e, - 0xca, 0xbb, 0xa9, 0x70, 0xa3, 0x66, 0xc7, 0x84, 0xa5, 0x81, 0xcb, 0xcf, 0xc0, 0x56, 0x6c, 0xab, - 0x52, 0x26, 0x39, 0x54, 0xa7, 0x16, 0x33, 0x88, 0xa9, 0x7a, 0x67, 0x5d, 0x83, 0x65, 0x33, 0xfd, - 0x00, 0x3c, 0x88, 0x1d, 0x20, 0xda, 0x31, 0x5c, 0xdf, 0xff, 0x4b, 0x62, 0x9a, 0xd4, 0xea, 0xd0, - 0x9c, 0x4e, 0xe0, 0x32, 0x58, 0xe8, 0x9e, 0x13, 0xcc, 0x06, 0x3d, 0x5a, 0x2a, 0xf0, 0xb1, 0x6b, - 0xdd, 0x73, 0xd2, 0x1a, 0xf4, 0x28, 0x5c, 0x03, 0xc0, 0xb4, 0x3b, 0x86, 0x85, 0xcf, 0x4d, 0xfb, - 0xa2, 0x34, 0xc7, 0x07, 0xaf, 0xf3, 0x3b, 0x87, 0xa6, 0x7d, 0x11, 0xf0, 0x23, 0xaa, 0xd9, 0x7d, - 0xea, 0x0c, 0x24, 0x5b, 0xa7, 0xae, 0x64, 0x5b, 0xcc, 0xb0, 0x3c, 0x9a, 0xb3, 0xa6, 0x1f, 0x83, - 0xb5, 0x31, 0x07, 0xbd, 0x41, 0xce, 0xc9, 0x9f, 0x80, 0xf5, 0x91, 0xc9, 0x4d, 0xc7, 0xb0, 0x58, - 0xce, 0xd9, 0x65, 0x50, 0xac, 0x19, 0x2e, 0x9f, 0x7c, 0x42, 0x19, 0xd1, 0x09, 0x23, 0x70, 0x11, - 0x14, 0x0c, 0x3d, 0xb4, 0x2c, 0x18, 0x7a, 0x99, 0x80, 0x52, 0x64, 0x13, 0x2d, 0x61, 0x6c, 0x2b, - 0x83, 0x05, 0x27, 0xbc, 0xc7, 0x67, 0x2c, 0x3e, 0xdd, 0xd9, 0x9b, 0xb2, 0x5d, 0xf7, 0x46, 0x9d, - 0xa0, 0x78, 0x6a, 0xf9, 0x15, 0x80, 0xd1, 0xa8, 0xca, 0x68, 0x4f, 0x65, 0x84, 0x79, 0x2e, 0x7c, - 0x06, 0xe6, 0x5d, 0xfe, 0x57, 0xe8, 0xfa, 0x51, 0xa6, 0xeb, 0x60, 0x22, 0x0a, 0xa7, 0xf9, 0x8f, - 0x02, 0x75, 0x1c, 0xdb, 0x09, 0x17, 0x34, 0xb8, 0x28, 0xff, 0x4e, 0x04, 0xb7, 0xdb, 0xf5, 0xd4, - 0x14, 0x87, 0x51, 0x3d, 0x28, 0x95, 0x0c, 0x16, 0xba, 0x61, 0x6a, 0x3c, 0xe6, 0x8d, 0x1c, 0xe9, - 0x44, 0xb5, 0x40, 0xf1, 0x54, 0x28, 0xc5, 0xe0, 0x05, 0xee, 0xe4, 0x71, 0x0e, 0xf0, 0x28, 0xeb, - 0x08, 0xbe, 0xfc, 0x6f, 0x11, 0x6c, 0x26, 0x98, 0x51, 0xd1, 0x54, 0x6a, 0x52, 0x8d, 0x19, 0xb6, - 0xf5, 0x3f, 0x05, 0x3e, 0x49, 0x2d, 0x63, 0x80, 0xbc, 0x9f, 0x7b, 0x19, 0x13, 0x77, 0x91, 0x8b, - 0x54, 0xfe, 0x73, 0x6f, 0x9f, 0xff, 0x3f, 0x44, 0xb0, 0x9a, 0xe4, 0x5f, 0xa3, 0x3d, 0xd3, 0x1e, - 0xa8, 0xd4, 0xe9, 0x1b, 0x1a, 0xbd, 0xf4, 0xb9, 0xff, 0x4b, 0x04, 0x1b, 0xa9, 0xdc, 0x09, 0x23, - 0x67, 0xc4, 0xa5, 0xd1, 0xbb, 0xed, 0xd2, 0xa7, 0xff, 0xc3, 0x02, 0xb8, 0x3f, 0x9e, 0xbe, 0x64, - 0x5b, 0xe7, 0x46, 0xc7, 0x73, 0xe8, 0x49, 0xeb, 0x58, 0xbd, 0xf4, 0x35, 0xf8, 0x59, 0x01, 0xec, - 0xa7, 0xb7, 0xbf, 0xfb, 0x8a, 0xd9, 0xbd, 0xaa, 0xc6, 0x8c, 0x3e, 0xad, 0x19, 0x0e, 0xd5, 0x98, - 0xed, 0x0c, 0x5a, 0xb6, 0x6d, 0xba, 0x75, 0xcb, 0x65, 0xc4, 0x34, 0x2f, 0x7d, 0x41, 0x5e, 0x17, - 0xc0, 0x5e, 0x56, 0x41, 0xe2, 0x2d, 0x72, 0xe9, 0xab, 0xf1, 0xc7, 0x02, 0x78, 0x98, 0x54, 0xa3, - 0xea, 0x31, 0x3b, 0xfa, 0x9b, 0xea, 0x51, 0x6c, 0xf7, 0xb2, 0x57, 0x01, 0x3e, 0x02, 0x4b, 0x91, - 0x43, 0x17, 0x6b, 0xb6, 0x67, 0xb1, 0xd2, 0x95, 0x4d, 0x71, 0x7b, 0x0e, 0x2d, 0xc6, 0xb7, 0x25, - 0xff, 0x6e, 0xf9, 0x27, 0x05, 0xb0, 0x33, 0xe3, 0x8d, 0x52, 0xaf, 0x9e, 0x34, 0x6d, 0xd3, 0xd0, - 0x06, 0x97, 0x7e, 0xdf, 0x7c, 0x29, 0x82, 0x72, 0x52, 0x08, 0xff, 0xa8, 0xa8, 0x19, 0x3d, 0x62, - 0xba, 0x5f, 0x9d, 0x27, 0xe7, 0x9f, 0xa2, 0x7f, 0xdc, 0x8e, 0x0c, 0x5a, 0xd4, 0x65, 0x92, 0x6d, - 0x59, 0x5f, 0x91, 0x43, 0xd5, 0xdf, 0x45, 0x50, 0x4a, 0x92, 0x8f, 0x84, 0xa0, 0x7e, 0xe9, 0xf3, - 0x5e, 0x01, 0xcb, 0xed, 0xba, 0xe4, 0x50, 0xc2, 0xe8, 0x29, 0xbd, 0x40, 0xb6, 0x99, 0x16, 0xab, - 0x1b, 0xfe, 0x86, 0x18, 0x1a, 0x54, 0x49, 0x3f, 0x6d, 0xb0, 0xe5, 0x9f, 0xc6, 0x86, 0x67, 0x13, - 0x4b, 0xa3, 0x66, 0xca, 0xe4, 0x09, 0xd8, 0x1d, 0x31, 0x79, 0x61, 0xd0, 0x8b, 0x9a, 0xad, 0x79, - 0x5d, 0x6a, 0x31, 0xe2, 0x6f, 0xae, 0x94, 0xf5, 0x97, 0x2b, 0x60, 0xa9, 0xed, 0xc3, 0xf3, 0xcb, - 0x86, 0x45, 0x1b, 0xe7, 0xb0, 0x0d, 0x96, 0x3c, 0x03, 0x9f, 0x71, 0x39, 0x8f, 0x35, 0xdf, 0x36, - 0x5c, 0x84, 0xe9, 0x09, 0x8f, 0xab, 0x7f, 0x45, 0x40, 0x37, 0x3d, 0x23, 0x75, 0x17, 0xfe, 0x4a, - 0x04, 0x3b, 0x9e, 0x81, 0xed, 0x40, 0x1e, 0x63, 0x2d, 0x5c, 0x72, 0xdc, 0xb1, 0x31, 0xb3, 0xb1, - 0x1e, 0xc9, 0xff, 0x30, 0x62, 0x50, 0xe2, 0xea, 0x8c, 0x88, 0xf9, 0x7a, 0x08, 0x8a, 0x80, 0xee, - 0x79, 0x46, 0xa6, 0x2d, 0x7c, 0x2d, 0x82, 0x7b, 0x29, 0x3a, 0xa2, 0xeb, 0xf8, 0xdc, 0x70, 0x5c, - 0x86, 0xa3, 0xe5, 0x0f, 0xb9, 0xae, 0x70, 0xae, 0x4f, 0xb2, 0xb9, 0xa6, 0xf7, 0x24, 0x14, 0x01, - 0xad, 0xc7, 0x48, 0x13, 0xcd, 0x46, 0x6b, 0x35, 0x81, 0xc6, 0x24, 0x2c, 0x5e, 0x9d, 0xab, 0x79, - 0x6b, 0x95, 0xd1, 0x00, 0x19, 0xaa, 0xd5, 0x74, 0x5b, 0xf8, 0x63, 0x11, 0x6c, 0xa6, 0xe8, 0x5c, - 0xca, 0xb0, 0x16, 0xf7, 0x4a, 0xb0, 0xcb, 0xfb, 0x1c, 0xa5, 0x79, 0x0e, 0xf5, 0xcd, 0x6c, 0xa8, - 0x69, 0x9d, 0x16, 0x45, 0x40, 0xab, 0x31, 0xcd, 0x04, 0x23, 0xf8, 0x73, 0x11, 0xdc, 0x4f, 0x61, - 0x38, 0xa1, 0x28, 0xc1, 0x5a, 0xd4, 0x71, 0x89, 0x50, 0xae, 0x71, 0x94, 0x6f, 0x67, 0xa3, 0xcc, - 0xea, 0xd9, 0x28, 0x02, 0xda, 0x8c, 0x71, 0xa6, 0x18, 0x46, 0x95, 0x71, 0xc2, 0x2e, 0x08, 0xd6, - 0x6c, 0x9d, 0x9f, 0x07, 0x82, 0x2e, 0x4c, 0xb8, 0x5c, 0x0b, 0x99, 0x95, 0xc9, 0xe8, 0xe1, 0x04, - 0x95, 0x99, 0x6e, 0x04, 0xbf, 0x07, 0x56, 0x27, 0x51, 0xf4, 0x06, 0x21, 0xc1, 0x75, 0x4e, 0xf0, - 0xb5, 0xfc, 0x04, 0xe9, 0x26, 0x90, 0x22, 0xa0, 0xd2, 0x58, 0xf4, 0xd0, 0x00, 0x7e, 0x1f, 0xac, - 0x8d, 0x47, 0xee, 0x39, 0x86, 0xc5, 0xc2, 0xd0, 0x80, 0x87, 0xfe, 0x7a, 0xde, 0xd0, 0x23, 0x2d, - 0x24, 0x45, 0x40, 0xcb, 0x23, 0xb1, 0x13, 0x0b, 0x68, 0x82, 0x65, 0xcf, 0xc0, 0x7a, 0xf8, 0xf2, - 0xc5, 0x6e, 0xd0, 0x50, 0xc1, 0xdc, 0x79, 0xe9, 0x06, 0x0f, 0x5c, 0x99, 0x11, 0x78, 0x52, 0x23, - 0x46, 0x11, 0xd0, 0x6d, 0xcf, 0x98, 0xd8, 0xa2, 0x79, 0x1d, 0x6c, 0xbf, 0x38, 0x5c, 0xfc, 0x68, - 0xba, 0x51, 0x67, 0x24, 0x8c, 0xfc, 0x0e, 0x8f, 0xfc, 0x8d, 0x1c, 0x91, 0x27, 0xf7, 0x56, 0x82, - 0x9d, 0x97, 0xd1, 0x7f, 0xf9, 0x82, 0x6f, 0xbc, 0x18, 0x46, 0xe7, 0x5d, 0x0a, 0xec, 0x06, 0x6d, - 0x8a, 0x10, 0xe4, 0x26, 0x07, 0xf9, 0xff, 0x1c, 0x20, 0xe3, 0x4d, 0x8e, 0x60, 0xcf, 0xcd, 0x68, - 0x82, 0xfc, 0x34, 0x78, 0x81, 0x26, 0x04, 0xe1, 0xd1, 0x36, 0x79, 0x2e, 0x03, 0x88, 0x45, 0x0e, - 0xf1, 0x51, 0x1e, 0x88, 0x49, 0xdd, 0x06, 0x45, 0x40, 0x1b, 0x29, 0x8e, 0x89, 0x0d, 0x89, 0x5f, - 0x06, 0x6f, 0xcf, 0x71, 0x14, 0x2d, 0x3a, 0x5b, 0xe2, 0x2e, 0x33, 0xdd, 0x10, 0x68, 0x89, 0x03, - 0x7d, 0xeb, 0xbf, 0x00, 0x1a, 0xd7, 0xff, 0x8a, 0x80, 0xee, 0x8f, 0x53, 0x25, 0x76, 0xcc, 0x0c, - 0x25, 0xd0, 0x9f, 0x45, 0xf0, 0xd1, 0xf0, 0x3a, 0x71, 0xf5, 0x88, 0x09, 0x97, 0x8f, 0x58, 0x8f, - 0xf4, 0x23, 0x66, 0xbe, 0xa2, 0xc6, 0x46, 0x20, 0xa9, 0x43, 0xd2, 0x22, 0x27, 0xfd, 0x34, 0xd7, - 0xfa, 0xe5, 0x52, 0xe9, 0x8a, 0x80, 0xf6, 0xd3, 0x8b, 0x9a, 0x4f, 0xda, 0xff, 0x49, 0x04, 0x1f, - 0xe6, 0xca, 0x21, 0x29, 0x77, 0xc0, 0xff, 0x2e, 0xe7, 0x3f, 0x7a, 0x6b, 0xfe, 0x61, 0x69, 0xa0, - 0x08, 0x68, 0x2f, 0x0b, 0x7e, 0x44, 0x4c, 0xfc, 0x5a, 0x04, 0x8f, 0xd3, 0xe4, 0xc4, 0xf3, 0x4f, - 0x1e, 0xb1, 0x5c, 0xc5, 0x89, 0x84, 0x0b, 0x80, 0x21, 0x07, 0x7e, 0x96, 0x03, 0x78, 0x96, 0xee, - 0x55, 0x04, 0xf4, 0x30, 0x01, 0x9d, 0xa9, 0x90, 0x7f, 0x2f, 0x82, 0x4a, 0xc6, 0xce, 0x35, 0x48, - 0x17, 0xf7, 0xb8, 0x44, 0x0c, 0x21, 0xff, 0x8f, 0x43, 0x1e, 0xbc, 0xcd, 0xfe, 0x1d, 0x56, 0x9b, - 0x8a, 0x80, 0x76, 0x66, 0x6c, 0xe2, 0x3a, 0xe9, 0xa6, 0xa5, 0xe9, 0x2f, 0x44, 0xf0, 0x30, 0x8d, - 0xda, 0x8b, 0x15, 0xdc, 0xd8, 0xba, 0xdf, 0xe2, 0x84, 0x1f, 0xe7, 0x20, 0x9c, 0x26, 0x03, 0x15, - 0x01, 0x95, 0x13, 0xb4, 0xa9, 0x62, 0xf1, 0x47, 0x22, 0xd8, 0x4a, 0x33, 0x31, 0xea, 0x32, 0x9f, - 0xc6, 0x1a, 0x7a, 0x1f, 0xbf, 0x97, 0xf9, 0xf5, 0x9b, 0xa1, 0xc9, 0x14, 0x01, 0xad, 0x25, 0x24, - 0x93, 0x44, 0x9b, 0x03, 0x56, 0xd2, 0x0c, 0xd1, 0x39, 0x37, 0xfa, 0x0e, 0xdd, 0xce, 0x10, 0x22, - 0xd3, 0x44, 0x51, 0xf0, 0xd9, 0x9d, 0x22, 0x98, 0x4c, 0x50, 0xf2, 0x0c, 0xff, 0x10, 0x46, 0x18, - 0xc5, 0x16, 0xbd, 0xc0, 0x8e, 0x6d, 0x46, 0xc7, 0x8d, 0x3b, 0x3c, 0xe0, 0xd3, 0x19, 0x01, 0xa7, - 0xc8, 0x11, 0x45, 0x40, 0xb7, 0x3c, 0x63, 0x7c, 0x10, 0x0e, 0xf8, 0x47, 0x7e, 0x34, 0x9a, 0x4b, - 0xfa, 0x51, 0xc8, 0x52, 0x66, 0x85, 0x67, 0x88, 0x9c, 0x20, 0xd1, 0xc9, 0x06, 0xf0, 0x0b, 0xb0, - 0x31, 0x29, 0x51, 0x2e, 0x82, 0xc2, 0xe0, 0xcb, 0x99, 0x1f, 0x98, 0x99, 0x02, 0x4a, 0x11, 0xd0, - 0xdd, 0xd1, 0xac, 0x13, 0x13, 0xf8, 0x9b, 0xe0, 0x15, 0x32, 0x4a, 0xd0, 0x37, 0xe8, 0x05, 0xd6, - 0xd3, 0x22, 0x2b, 0xa4, 0xb9, 0xcb, 0x69, 0xa4, 0xbc, 0x34, 0x33, 0xb4, 0x9a, 0x22, 0xa0, 0x07, - 0x23, 0x60, 0x93, 0xad, 0x0f, 0xae, 0x81, 0xab, 0x3c, 0xc0, 0xa7, 0x57, 0x16, 0x0a, 0xc5, 0x39, - 0xff, 0xbb, 0x1d, 0x1f, 0x92, 0x3b, 0x94, 0xc5, 0xe7, 0x22, 0x0e, 0xb8, 0xfb, 0x87, 0xf9, 0xe4, - 0x3f, 0x6f, 0xd1, 0xdb, 0x08, 0x6e, 0x81, 0xb5, 0x5a, 0x5d, 0x95, 0x1a, 0x2f, 0x64, 0x84, 0x91, - 0xac, 0x36, 0xda, 0x48, 0x92, 0x71, 0xfb, 0x54, 0x6d, 0xca, 0x52, 0xfd, 0xb0, 0x2e, 0xd7, 0x8a, - 0x02, 0x5c, 0x05, 0xa5, 0x71, 0x13, 0x55, 0x46, 0x2f, 0x64, 0x54, 0x14, 0xe1, 0x26, 0x58, 0x1d, - 0x1f, 0x7d, 0xde, 0x3e, 0x90, 0xd1, 0xa9, 0xdc, 0x92, 0xd5, 0x62, 0x01, 0x7e, 0x00, 0x2a, 0xe3, - 0x16, 0xb5, 0x6a, 0xab, 0x7a, 0x50, 0x55, 0x65, 0xdc, 0x6c, 0xa8, 0xad, 0x23, 0x24, 0xab, 0x58, - 0x95, 0x8f, 0x0f, 0xb1, 0xd2, 0x50, 0x5b, 0x72, 0xad, 0x38, 0x07, 0xdf, 0x07, 0x4f, 0x66, 0x4c, - 0x3a, 0xf9, 0x8e, 0xfa, 0xd9, 0xf1, 0xd0, 0x8c, 0x2b, 0xf0, 0x29, 0xd8, 0x9b, 0x35, 0xa3, 0x71, - 0x7a, 0xd4, 0xa8, 0x1d, 0x0c, 0xcd, 0xb9, 0x0a, 0x1f, 0x83, 0x47, 0x79, 0xd0, 0x50, 0x4d, 0x2d, - 0xce, 0xc3, 0x6d, 0x70, 0x3f, 0x13, 0xc9, 0xb7, 0xbc, 0x06, 0x1f, 0x82, 0xf2, 0xb8, 0x65, 0xb5, - 0xd9, 0x3c, 0xae, 0x4b, 0xd5, 0x56, 0xbd, 0x71, 0x8a, 0x95, 0x56, 0xab, 0x59, 0x5c, 0x80, 0x0f, - 0xc0, 0xd6, 0x6c, 0xbb, 0x96, 0xd4, 0x2c, 0x5e, 0x9f, 0x6c, 0xf6, 0x79, 0xfd, 0xb4, 0xd6, 0xf8, - 0x5c, 0xc5, 0x35, 0x59, 0x7d, 0xde, 0x6a, 0x34, 0x8b, 0x00, 0x3e, 0x01, 0xdb, 0x33, 0xf8, 0xd4, - 0xcf, 0x8e, 0x83, 0x35, 0xe3, 0x8c, 0x37, 0x32, 0x0a, 0x9c, 0xa4, 0x2e, 0xd7, 0x54, 0xa5, 0x7e, - 0xd8, 0x2a, 0xbe, 0x03, 0x3f, 0x04, 0xef, 0xe7, 0xf2, 0x9f, 0x2e, 0xf1, 0xcd, 0x8c, 0x38, 0x48, - 0xae, 0xd5, 0x87, 0x97, 0x7e, 0x31, 0xef, 0xa2, 0x1c, 0x49, 0xcd, 0xe2, 0x52, 0xae, 0x45, 0xf1, - 0x2d, 0x8b, 0xb9, 0xcb, 0xe3, 0x5b, 0xbf, 0xbb, 0xfb, 0x5b, 0x11, 0x2c, 0x0e, 0xff, 0x8b, 0x17, - 0x6e, 0x80, 0x95, 0xd8, 0x81, 0xda, 0xaa, 0xb6, 0xda, 0xea, 0xc8, 0x83, 0xb2, 0x02, 0xee, 0x8c, - 0x1a, 0xa8, 0x6d, 0x49, 0x92, 0x55, 0xb5, 0x28, 0x4e, 0x1c, 0x7c, 0x5e, 0x6f, 0x36, 0xe5, 0x5a, - 0xb1, 0x00, 0x97, 0xc1, 0x7b, 0xa3, 0x83, 0x32, 0x42, 0x0d, 0x54, 0x9c, 0x9b, 0x34, 0xaf, 0x7a, - 0xd0, 0x40, 0x7c, 0xcf, 0x1f, 0x34, 0xfe, 0xf2, 0x66, 0x5d, 0xfc, 0xeb, 0x9b, 0x75, 0xf1, 0x6f, - 0x6f, 0xd6, 0xc5, 0xef, 0x56, 0x3b, 0x06, 0x7b, 0xe9, 0x9d, 0xed, 0x69, 0x76, 0xb7, 0xd2, 0x71, - 0x48, 0xdf, 0x08, 0x5e, 0x15, 0xc4, 0xac, 0xc4, 0xbf, 0xfc, 0x20, 0x3d, 0xa3, 0xd2, 0xa1, 0x56, - 0x85, 0xff, 0xcc, 0xa3, 0xd2, 0xb1, 0x47, 0x7e, 0x0a, 0x72, 0x36, 0xcf, 0x07, 0x3e, 0xf8, 0x4f, - 0x00, 0x00, 0x00, 0xff, 0xff, 0x0c, 0x1f, 0xda, 0x74, 0x2c, 0x22, 0x00, 0x00, + // 2201 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x5a, 0xcb, 0x6f, 0xdb, 0xc8, + 0xfd, 0x17, 0xe5, 0x3c, 0x9c, 0xc9, 0xc6, 0xe1, 0xce, 0xe6, 0x21, 0xc7, 0x6f, 0xe5, 0x69, 0x27, + 0x3f, 0x7b, 0xf3, 0xf8, 0x6d, 0xd2, 0x66, 0xdb, 0x80, 0x22, 0x69, 0x93, 0x6b, 0xd9, 0xd4, 0x0e, + 0xa9, 0xa4, 0xde, 0xcb, 0x80, 0x96, 0xc6, 0x0a, 0x11, 0x8a, 0x14, 0xc8, 0x91, 0x5c, 0xa3, 0x28, + 0x16, 0x2d, 0x8a, 0xa2, 0x40, 0x80, 0xa2, 0xbd, 0x15, 0x28, 0x50, 0xa0, 0x68, 0x0f, 0x3d, 0xf4, + 0xde, 0x73, 0x6f, 0x3d, 0xf6, 0x4f, 0x28, 0x72, 0xef, 0xb1, 0x87, 0x16, 0x2d, 0xb6, 0xd0, 0xf0, + 0xa9, 0x27, 0xb9, 0x41, 0x4f, 0xde, 0x9b, 0xc9, 0xf9, 0x3e, 0x3e, 0xdf, 0x07, 0x67, 0xe6, 0xf3, + 0xb5, 0xc0, 0x3a, 0x25, 0x36, 0xe9, 0xb8, 0x1e, 0xdd, 0xea, 0xfa, 0x66, 0x8b, 0x90, 0x1e, 0x71, + 0xa8, 0xbf, 0xd5, 0x7b, 0x98, 0x7e, 0xdc, 0xec, 0x78, 0x2e, 0x75, 0xe1, 0xf5, 0x48, 0x74, 0x33, + 0xbd, 0xd6, 0x7b, 0x58, 0xde, 0x00, 0xb0, 0xae, 0x56, 0x4c, 0xc7, 0x21, 0x9e, 0x68, 0x5b, 0x8d, + 0x37, 0x72, 0x7f, 0x05, 0x5e, 0x01, 0x67, 0x4d, 0x9b, 0x78, 0xb4, 0xc4, 0xad, 0x72, 0xf7, 0x2e, + 0xa0, 0xe0, 0xa1, 0xbc, 0x0d, 0xee, 0xd5, 0x55, 0xcd, 0x39, 0x74, 0x4d, 0xaf, 0x29, 0xba, 0xed, + 0x8e, 0x4d, 0x28, 0xd9, 0x71, 0x0d, 0x57, 0x32, 0xfd, 0xd7, 0xc1, 0xcb, 0xc4, 0xc2, 0x0d, 0x30, + 0xdb, 0xf5, 0x89, 0xe7, 0x98, 0x6d, 0x12, 0x1a, 0x89, 0x9f, 0xcb, 0xb7, 0xc1, 0xcd, 0xd8, 0x8e, + 0xd0, 0x6c, 0x6e, 0x5b, 0x9e, 0x4f, 0x11, 0xf1, 0xdd, 0xae, 0xd7, 0x20, 0x89, 0x89, 0xf2, 0x46, + 0xca, 0xdd, 0xb0, 0x58, 0xd5, 0xa4, 0x69, 0xc0, 0xe5, 0x17, 0x60, 0x2d, 0x96, 0xd5, 0x09, 0x15, + 0x3d, 0xd2, 0x24, 0x0e, 0xb5, 0x4c, 0x5b, 0xef, 0x1e, 0xb6, 0x2d, 0x9a, 0x8d, 0xe9, 0x87, 0xe0, + 0x76, 0x6c, 0x00, 0x91, 0x96, 0xe5, 0xf7, 0xed, 0xbf, 0x36, 0x6d, 0x9b, 0x38, 0x2d, 0x92, 0xd3, + 0x08, 0x9c, 0x07, 0xb3, 0xed, 0x23, 0x13, 0xd3, 0x93, 0x0e, 0x29, 0x15, 0xd9, 0xda, 0xf9, 0xf6, + 0x91, 0x69, 0x9c, 0x74, 0x08, 0x5c, 0x02, 0xc0, 0x76, 0x5b, 0x96, 0x83, 0x8f, 0x6c, 0xf7, 0xb8, + 0x34, 0xc3, 0x16, 0x2f, 0xb0, 0x37, 0xdb, 0xb6, 0x7b, 0x1c, 0xe0, 0x47, 0xa4, 0xe1, 0xf6, 0x88, + 0x77, 0x22, 0xba, 0x4d, 0xe2, 0x8b, 0xae, 0x43, 0x2d, 0xa7, 0x4b, 0x72, 0xe6, 0xf4, 0x39, 0x58, + 0x1a, 0x31, 0xd0, 0x39, 0xc9, 0xa9, 0xfc, 0x29, 0x58, 0x1e, 0x52, 0xae, 0x79, 0x96, 0x43, 0x73, + 0x6a, 0x97, 0x01, 0x2f, 0x59, 0x3e, 0x53, 0xde, 0x23, 0xd4, 0x6c, 0x9a, 0xd4, 0x84, 0x73, 0xa0, + 0x68, 0x35, 0x43, 0xc9, 0xa2, 0xd5, 0x2c, 0x9b, 0xa0, 0x14, 0xc9, 0x44, 0x25, 0x8c, 0x65, 0x65, + 0x30, 0xeb, 0x85, 0xef, 0x98, 0xc6, 0xdc, 0xa3, 0xf5, 0xcd, 0x09, 0xed, 0xba, 0x39, 0x6c, 0x04, + 0xc5, 0xaa, 0xe5, 0x37, 0x00, 0x46, 0xab, 0x3a, 0x25, 0x1d, 0x9d, 0x9a, 0xb4, 0xeb, 0xc3, 0x17, + 0xe0, 0x9c, 0xcf, 0xfe, 0x0a, 0x4d, 0xdf, 0xcd, 0x34, 0x1d, 0x28, 0xa2, 0x50, 0xad, 0xff, 0x29, + 0x10, 0xcf, 0x73, 0xbd, 0xb0, 0xa0, 0xc1, 0x43, 0xf9, 0xf7, 0x1c, 0xb8, 0x56, 0x57, 0x53, 0x2a, + 0x1e, 0x25, 0xcd, 0x20, 0x55, 0x32, 0x98, 0x6d, 0x87, 0xa1, 0x31, 0x9f, 0x17, 0x73, 0x84, 0x13, + 0xe5, 0x02, 0xc5, 0xaa, 0x50, 0x8c, 0x81, 0x17, 0x99, 0x91, 0xfb, 0x39, 0x80, 0x47, 0x51, 0x47, + 0xe0, 0xcb, 0xff, 0xe1, 0xc0, 0x6a, 0x02, 0x33, 0x4a, 0x9a, 0x4e, 0x6c, 0xd2, 0xa0, 0x96, 0xeb, + 0xfc, 0x4f, 0x01, 0xef, 0xa5, 0xca, 0x18, 0x40, 0x7e, 0x98, 0xbb, 0x8c, 0x89, 0xb9, 0xc8, 0x44, + 0x2a, 0xfe, 0x99, 0xf7, 0x8f, 0xff, 0x9f, 0x1c, 0x58, 0x4c, 0xe2, 0x97, 0x48, 0xc7, 0x76, 0x4f, + 0x74, 0xe2, 0xf5, 0xac, 0x06, 0x39, 0xf5, 0xb1, 0xff, 0x9b, 0x03, 0x2b, 0xa9, 0xd8, 0x4d, 0x6a, + 0x1e, 0x9a, 0x3e, 0x89, 0xf6, 0xb6, 0x53, 0x1f, 0xfe, 0x8f, 0x8a, 0xe0, 0xd6, 0x68, 0xf8, 0xa2, + 0xeb, 0x1c, 0x59, 0xad, 0xae, 0x47, 0xf6, 0x8c, 0xaa, 0x7e, 0xea, 0x73, 0xf0, 0xf3, 0x22, 0x78, + 0x98, 0x6e, 0x7f, 0xff, 0x0d, 0x75, 0x3b, 0x42, 0x83, 0x5a, 0x3d, 0x22, 0x59, 0x1e, 0x69, 0x50, + 0xd7, 0x3b, 0x31, 0x5c, 0xd7, 0xf6, 0x55, 0xc7, 0xa7, 0xa6, 0x6d, 0x9f, 0xfa, 0x84, 0xbc, 0x2d, + 0x82, 0xcd, 0xac, 0x84, 0xc4, 0x2d, 0x72, 0xea, 0xb3, 0xf1, 0xc7, 0x22, 0xb8, 0x93, 0x64, 0x43, + 0xe8, 0x52, 0x37, 0xfa, 0x9b, 0x34, 0x23, 0xdf, 0xfe, 0x69, 0xcf, 0x02, 0xbc, 0x0b, 0x2e, 0x47, + 0x06, 0x7d, 0xdc, 0x70, 0xbb, 0x0e, 0x2d, 0x9d, 0x59, 0xe5, 0xee, 0xcd, 0xa0, 0xb9, 0xf8, 0xb5, + 0xd8, 0x7f, 0x5b, 0xfe, 0x69, 0x11, 0xac, 0x4f, 0xd9, 0x51, 0x54, 0x61, 0xaf, 0xe6, 0xda, 0x56, + 0xe3, 0xe4, 0xd4, 0xf7, 0xcd, 0x57, 0x1c, 0x28, 0x27, 0x89, 0xe8, 0x5f, 0x15, 0x1b, 0x56, 0xc7, + 0xb4, 0xfd, 0x6f, 0xce, 0x97, 0xf3, 0x2f, 0xae, 0x7f, 0xdd, 0x8e, 0x04, 0x0c, 0xe2, 0x53, 0xd1, + 0x75, 0x9c, 0x6f, 0xc8, 0xa5, 0xea, 0x1f, 0x1c, 0x28, 0x25, 0xc1, 0x47, 0x44, 0xb0, 0x79, 0xea, + 0xe3, 0x5e, 0x00, 0xf3, 0x75, 0x55, 0xf4, 0x88, 0x49, 0xc9, 0x3e, 0x39, 0x46, 0xae, 0x9d, 0x26, + 0xab, 0x2b, 0xfd, 0x86, 0x18, 0x58, 0xd4, 0xcd, 0x5e, 0x5a, 0x60, 0xad, 0x7f, 0x1b, 0x1b, 0xd4, + 0x36, 0x9d, 0x06, 0xb1, 0x53, 0x22, 0x0f, 0xc0, 0xc6, 0x90, 0xc8, 0x4b, 0x8b, 0x1c, 0x4b, 0x6e, + 0xa3, 0xdb, 0x26, 0x0e, 0x35, 0xfb, 0xcd, 0x95, 0x92, 0xfe, 0x6a, 0x01, 0x5c, 0xae, 0xf7, 0xc1, + 0xb3, 0x47, 0xcd, 0x21, 0xda, 0x11, 0xac, 0x83, 0xcb, 0x5d, 0x0b, 0x1f, 0x32, 0x3a, 0x8f, 0x1b, + 0x7d, 0xd9, 0xb0, 0x08, 0x93, 0x03, 0x1e, 0x65, 0xff, 0x4a, 0x01, 0x5d, 0xea, 0x5a, 0xa9, 0xb7, + 0xf0, 0xd7, 0x1c, 0x58, 0xef, 0x5a, 0xd8, 0x0d, 0xe8, 0x31, 0x6e, 0x84, 0x25, 0xc7, 0x2d, 0x17, + 0x53, 0x17, 0x37, 0x23, 0xfa, 0x1f, 0x7a, 0x0c, 0x52, 0x2c, 0x4c, 0xf1, 0x98, 0x6f, 0x86, 0xa0, + 0x14, 0xd0, 0xcd, 0xae, 0x95, 0x29, 0x0b, 0xdf, 0x72, 0xe0, 0x66, 0x0a, 0x9d, 0xd9, 0x6c, 0xe2, + 0x23, 0xcb, 0xf3, 0x29, 0x8e, 0xca, 0x1f, 0xe2, 0x3a, 0xc3, 0x70, 0x7d, 0x9a, 0x8d, 0x6b, 0xf2, + 0x4c, 0x42, 0x29, 0xa0, 0xe5, 0x18, 0xd2, 0x58, 0xb1, 0xe1, 0x5c, 0x8d, 0x41, 0x63, 0x9b, 0x34, + 0xae, 0xce, 0xd9, 0xbc, 0xb9, 0xca, 0x18, 0x80, 0x0c, 0xe4, 0x6a, 0xb2, 0x2c, 0xfc, 0x09, 0x07, + 0x56, 0x53, 0xe8, 0x7c, 0x42, 0x71, 0x23, 0x9e, 0x95, 0x60, 0x9f, 0xcd, 0x39, 0x4a, 0xe7, 0x18, + 0xa8, 0x6f, 0x67, 0x83, 0x9a, 0x34, 0x69, 0x51, 0x0a, 0x68, 0x31, 0x46, 0x33, 0x46, 0x08, 0xfe, + 0x82, 0x03, 0xb7, 0x52, 0x30, 0xbc, 0x90, 0x94, 0xe0, 0x46, 0x34, 0x71, 0x89, 0xa0, 0x9c, 0x67, + 0x50, 0xbe, 0x9b, 0x0d, 0x65, 0xda, 0xcc, 0x46, 0x29, 0xa0, 0xd5, 0x18, 0xce, 0x04, 0xc1, 0x28, + 0x33, 0x5e, 0x38, 0x05, 0xc1, 0x0d, 0xb7, 0xc9, 0xee, 0x03, 0xc1, 0x14, 0x26, 0x2c, 0xd7, 0x6c, + 0x66, 0x66, 0x32, 0x66, 0x38, 0x41, 0x66, 0x26, 0x0b, 0xc1, 0xef, 0x83, 0xc5, 0x71, 0x28, 0x3a, + 0x27, 0x21, 0x82, 0x0b, 0x0c, 0xc1, 0x27, 0xf9, 0x11, 0xa4, 0x87, 0x40, 0x4a, 0x01, 0x95, 0x46, + 0xbc, 0x87, 0x02, 0xf0, 0x07, 0x60, 0x69, 0xd4, 0x73, 0xc7, 0xb3, 0x1c, 0x1a, 0xba, 0x06, 0xcc, + 0xf5, 0xd3, 0xbc, 0xae, 0x87, 0x46, 0x48, 0x4a, 0x01, 0xcd, 0x0f, 0xf9, 0x4e, 0x24, 0xa0, 0x0d, + 0xe6, 0xbb, 0x16, 0x6e, 0x86, 0x9b, 0x2f, 0xf6, 0x83, 0x81, 0x0a, 0x66, 0xc6, 0x4b, 0x17, 0x99, + 0xe3, 0xad, 0x29, 0x8e, 0xc7, 0x0d, 0x62, 0x94, 0x02, 0xba, 0xd6, 0xb5, 0xc6, 0x8e, 0x68, 0xde, + 0x06, 0xed, 0x17, 0xbb, 0x8b, 0x3f, 0x4d, 0x3f, 0x9a, 0x8c, 0x84, 0x9e, 0x3f, 0x60, 0x9e, 0xbf, + 0x95, 0xc3, 0xf3, 0xf8, 0xd9, 0x4a, 0xd0, 0x79, 0x19, 0xf3, 0x97, 0x2f, 0x59, 0xe3, 0xc5, 0x60, + 0x9a, 0x6c, 0x4a, 0x81, 0xfd, 0x60, 0x4c, 0x11, 0x02, 0xb9, 0xc4, 0x80, 0xfc, 0x7f, 0x0e, 0x20, + 0xa3, 0x43, 0x8e, 0xa0, 0xe7, 0xa6, 0x0c, 0x41, 0x7e, 0x16, 0x6c, 0xa0, 0x09, 0x82, 0xf0, 0x6a, + 0x9b, 0x7c, 0x97, 0x01, 0x88, 0x39, 0x06, 0xe2, 0x59, 0x1e, 0x10, 0xe3, 0xa6, 0x0d, 0x4a, 0x01, + 0xad, 0xa4, 0x70, 0x8c, 0x1d, 0x48, 0xfc, 0x2a, 0xd8, 0x3d, 0x47, 0xa1, 0x34, 0xa2, 0xbb, 0x25, + 0x6e, 0x53, 0xdb, 0x0f, 0x01, 0x5d, 0x66, 0x80, 0xbe, 0xf3, 0x35, 0x00, 0x8d, 0xf2, 0x7f, 0xa5, + 0x80, 0x6e, 0x8d, 0xa2, 0x4a, 0xe4, 0xa8, 0x1d, 0x52, 0xa0, 0x3f, 0x73, 0xe0, 0xd9, 0x60, 0x9d, + 0x18, 0x7b, 0xc4, 0x26, 0xa3, 0x8f, 0xb8, 0x19, 0xf1, 0x47, 0x4c, 0xfb, 0x8c, 0x1a, 0x5b, 0x01, + 0xa5, 0x0e, 0x91, 0xf2, 0x0c, 0xe9, 0x67, 0xb9, 0xea, 0x97, 0x8b, 0xa5, 0x2b, 0x05, 0xf4, 0x30, + 0x5d, 0xd4, 0x7c, 0xd4, 0xfe, 0x4f, 0x1c, 0x78, 0x92, 0x2b, 0x86, 0x24, 0xdd, 0x01, 0xfe, 0x0f, + 0x19, 0xfe, 0x9d, 0xf7, 0xc6, 0x3f, 0x48, 0x0d, 0x94, 0x02, 0xda, 0xcc, 0x02, 0x3f, 0x44, 0x26, + 0x7e, 0xc3, 0x81, 0xfb, 0x69, 0xe4, 0x66, 0xb7, 0x7f, 0xf3, 0x88, 0xe9, 0x2a, 0x4e, 0x28, 0x5c, + 0x00, 0x18, 0x32, 0xc0, 0x2f, 0x72, 0x00, 0x9e, 0xc6, 0x7b, 0x95, 0x02, 0xba, 0x93, 0x00, 0x9d, + 0xca, 0x90, 0xff, 0xc0, 0x81, 0xad, 0x8c, 0xce, 0xb5, 0xcc, 0x36, 0xee, 0x30, 0x8a, 0x18, 0x82, + 0xfc, 0x88, 0x81, 0xac, 0xbc, 0x4f, 0xff, 0x0e, 0xb2, 0x4d, 0xa5, 0x80, 0xd6, 0xa7, 0x34, 0xb1, + 0x6a, 0xb6, 0xd3, 0xd4, 0xf4, 0x97, 0x1c, 0xb8, 0x93, 0x86, 0xda, 0x89, 0x19, 0xdc, 0x48, 0xdd, + 0xaf, 0x30, 0x84, 0xcf, 0x73, 0x20, 0x9c, 0x44, 0x03, 0x95, 0x02, 0x2a, 0x27, 0xd0, 0x26, 0x92, + 0xc5, 0x1f, 0x73, 0x60, 0x2d, 0x8d, 0x89, 0x12, 0x9f, 0xf6, 0xd1, 0x38, 0x03, 0xfb, 0xf1, 0xd5, + 0xcc, 0xd3, 0x6f, 0x0a, 0x27, 0x53, 0x0a, 0x68, 0x29, 0x41, 0x32, 0x8e, 0xb4, 0x79, 0x60, 0x21, + 0x8d, 0x21, 0xba, 0xe7, 0x46, 0xe7, 0xd0, 0xb5, 0x0c, 0x22, 0x32, 0x89, 0x14, 0x05, 0xc7, 0xee, + 0x04, 0xc2, 0x64, 0x83, 0x52, 0xd7, 0xea, 0x5f, 0xc2, 0x4c, 0x4a, 0xb0, 0x43, 0x8e, 0xb1, 0xe7, + 0xda, 0xd1, 0x75, 0xe3, 0x3a, 0x73, 0xf8, 0x68, 0x8a, 0xc3, 0x09, 0x74, 0x44, 0x29, 0xa0, 0x2b, + 0x5d, 0x6b, 0x74, 0x11, 0x9e, 0xb0, 0x43, 0x7e, 0xd8, 0x9b, 0x6f, 0xf6, 0x22, 0x97, 0xa5, 0xcc, + 0x0c, 0x4f, 0x21, 0x39, 0x41, 0xa0, 0xe3, 0x05, 0xe0, 0x97, 0x60, 0x65, 0x5c, 0xa0, 0x8c, 0x04, + 0x85, 0xce, 0xe7, 0x33, 0x0f, 0x98, 0xa9, 0x04, 0x4a, 0x29, 0xa0, 0x1b, 0xc3, 0x51, 0x27, 0x22, + 0xf0, 0xb7, 0xc1, 0x16, 0x32, 0x8c, 0xa0, 0x67, 0x91, 0x63, 0xdc, 0x4c, 0x93, 0xac, 0x10, 0xcd, + 0x0d, 0x86, 0x46, 0xcc, 0x8b, 0x66, 0x0a, 0x57, 0x53, 0x0a, 0xe8, 0xf6, 0x10, 0xb0, 0xf1, 0xd2, + 0x95, 0xf3, 0xe0, 0x2c, 0x73, 0xf0, 0xd9, 0x99, 0xd9, 0x22, 0x3f, 0xd3, 0x3f, 0xb7, 0xe3, 0x4b, + 0x72, 0x8b, 0xd0, 0xf8, 0x5e, 0xc4, 0x00, 0x6e, 0xfc, 0xfd, 0x52, 0xf2, 0x9f, 0xb7, 0x68, 0x37, + 0x82, 0x6b, 0x60, 0x49, 0x52, 0x75, 0x51, 0x7b, 0x29, 0x23, 0x8c, 0x64, 0x5d, 0xab, 0x23, 0x51, + 0xc6, 0xf5, 0x7d, 0xbd, 0x26, 0x8b, 0xea, 0xb6, 0x2a, 0x4b, 0x7c, 0x01, 0x2e, 0x82, 0xd2, 0xa8, + 0x88, 0x2e, 0xa3, 0x97, 0x32, 0xe2, 0x39, 0xb8, 0x0a, 0x16, 0x47, 0x57, 0x77, 0xeb, 0x15, 0x19, + 0xed, 0xcb, 0x86, 0xac, 0xf3, 0x45, 0xf8, 0x18, 0x6c, 0x8d, 0x4a, 0x48, 0x82, 0x21, 0x54, 0x04, + 0x5d, 0xc6, 0x35, 0x4d, 0x37, 0x76, 0x90, 0xac, 0x63, 0x5d, 0xae, 0x6e, 0x63, 0x45, 0xd3, 0x0d, + 0x59, 0xe2, 0x67, 0xe0, 0xc7, 0xe0, 0xc1, 0x14, 0xa5, 0xbd, 0x03, 0xfd, 0xf3, 0xea, 0x80, 0xc6, + 0x19, 0xf8, 0x08, 0x6c, 0x4e, 0xd3, 0xd0, 0xf6, 0x77, 0x34, 0xa9, 0x32, 0xa0, 0x73, 0x16, 0xde, + 0x07, 0x77, 0xf3, 0x40, 0x43, 0x92, 0xce, 0x9f, 0x83, 0xf7, 0xc0, 0xad, 0x4c, 0x48, 0x7d, 0xc9, + 0xf3, 0xf0, 0x0e, 0x28, 0x8f, 0x4a, 0x0a, 0xb5, 0x5a, 0x55, 0x15, 0x05, 0x43, 0xd5, 0xf6, 0xb1, + 0x62, 0x18, 0x35, 0x7e, 0x16, 0xde, 0x06, 0x6b, 0xd3, 0xe5, 0x0c, 0xb1, 0xc6, 0x5f, 0x18, 0x2f, + 0xf6, 0x4a, 0xdd, 0x97, 0xb4, 0x57, 0x3a, 0x96, 0x64, 0x7d, 0xd7, 0xd0, 0x6a, 0x3c, 0x80, 0x0f, + 0xc0, 0xbd, 0x29, 0xf8, 0xf4, 0xcf, 0xab, 0x41, 0xcd, 0x18, 0xc6, 0x8b, 0x19, 0x09, 0x4e, 0x42, + 0x97, 0x25, 0x5d, 0x51, 0xb7, 0x0d, 0xfe, 0x03, 0xf8, 0x04, 0x7c, 0x9c, 0xcb, 0x7e, 0x3a, 0xc5, + 0x97, 0x32, 0xfc, 0x20, 0x59, 0x52, 0x07, 0x4b, 0x3f, 0x97, 0xb7, 0x28, 0x3b, 0x62, 0x8d, 0xbf, + 0x9c, 0xab, 0x28, 0x7d, 0x49, 0x3e, 0x77, 0x7a, 0xfa, 0xd2, 0x1f, 0xc2, 0xe7, 0xe0, 0xe9, 0xd7, + 0x49, 0x4f, 0xf8, 0x3d, 0x54, 0x65, 0x5d, 0xe7, 0x21, 0xfc, 0x3f, 0xb0, 0x9e, 0x47, 0x59, 0xf8, + 0xa2, 0x8e, 0x64, 0xfe, 0x23, 0x78, 0x17, 0xdc, 0x9c, 0x22, 0x2e, 0x1d, 0xec, 0x0b, 0x7b, 0x9a, + 0x54, 0xe1, 0xaf, 0x64, 0xb4, 0xb8, 0x28, 0xe8, 0xba, 0xb0, 0x2f, 0x21, 0x01, 0xef, 0xca, 0x07, + 0x7a, 0x4d, 0x10, 0x65, 0x9d, 0xbf, 0x9a, 0x51, 0xb5, 0x44, 0x27, 0x5d, 0x83, 0x6b, 0xf0, 0x19, + 0x78, 0x32, 0x45, 0x4b, 0xae, 0x0a, 0xba, 0xa1, 0x8a, 0xba, 0x2c, 0x20, 0x51, 0x19, 0xd0, 0xbc, + 0x9e, 0xab, 0xde, 0xa1, 0xbe, 0x20, 0x2a, 0x32, 0x5f, 0xca, 0xc8, 0x56, 0xa0, 0xb1, 0x27, 0xef, + 0x69, 0xe8, 0x40, 0xaa, 0xf0, 0xf3, 0xb9, 0x1c, 0xb0, 0xcc, 0xe2, 0xc0, 0xc1, 0x8d, 0x8c, 0x60, + 0x02, 0x0d, 0xb1, 0x5a, 0xd7, 0x8d, 0xa1, 0xe6, 0x5d, 0x80, 0x1b, 0xe0, 0x4e, 0x66, 0x77, 0x05, + 0x55, 0x5c, 0x84, 0x9b, 0x60, 0x23, 0x57, 0x7f, 0x05, 0xf2, 0x4b, 0x19, 0xc5, 0x4c, 0xe4, 0xf7, + 0x54, 0x11, 0x69, 0xba, 0xb6, 0x6d, 0xf0, 0xcb, 0xf0, 0x13, 0xf0, 0x68, 0x5a, 0x31, 0x35, 0x71, + 0x17, 0x69, 0x82, 0xa8, 0x0c, 0xed, 0x73, 0x2b, 0x19, 0xbd, 0x1f, 0xed, 0x8d, 0x82, 0x51, 0x15, + 0x74, 0x7e, 0x35, 0xe3, 0x9b, 0xd2, 0xf7, 0xb5, 0x57, 0xdb, 0x55, 0x61, 0x57, 0xe6, 0xd7, 0x26, + 0xd8, 0xd5, 0xc4, 0x54, 0x76, 0x25, 0x1d, 0xd7, 0x90, 0xf6, 0xbd, 0x03, 0xbe, 0x3c, 0xa1, 0x15, + 0xd3, 0xd2, 0x8a, 0xba, 0xa3, 0x60, 0xe1, 0xa5, 0xa0, 0x56, 0x85, 0x8a, 0x5a, 0x55, 0x8d, 0x03, + 0xfe, 0x26, 0x7c, 0x0a, 0x1e, 0x67, 0x68, 0xb1, 0x2f, 0x44, 0x15, 0x31, 0x92, 0x77, 0x54, 0xdd, + 0x40, 0x6c, 0xeb, 0xe4, 0x6f, 0x6d, 0xfc, 0x8e, 0x03, 0x73, 0x83, 0xbf, 0xd2, 0x80, 0x2b, 0x60, + 0x21, 0xb6, 0xa5, 0x1b, 0x82, 0x51, 0xd7, 0x87, 0xce, 0xba, 0x05, 0x70, 0x7d, 0x58, 0x40, 0xaf, + 0x8b, 0x62, 0xff, 0xb3, 0xe6, 0xc6, 0x2e, 0xee, 0xaa, 0xb5, 0x9a, 0x2c, 0xf1, 0x45, 0x38, 0x0f, + 0xae, 0x0e, 0x2f, 0xca, 0x08, 0x69, 0x88, 0x9f, 0x19, 0xa7, 0x27, 0x54, 0x34, 0xc4, 0x8e, 0xad, + 0x8a, 0xf6, 0x97, 0x77, 0xcb, 0xdc, 0x5f, 0xdf, 0x2d, 0x73, 0x7f, 0x7b, 0xb7, 0xcc, 0x7d, 0x21, + 0xb4, 0x2c, 0xfa, 0xba, 0x7b, 0xb8, 0xd9, 0x70, 0xdb, 0x5b, 0x2d, 0xcf, 0xec, 0x59, 0xc1, 0x69, + 0x6f, 0xda, 0x5b, 0xf1, 0x8f, 0xb7, 0xcc, 0x8e, 0xb5, 0xd5, 0x22, 0xce, 0x16, 0xfb, 0xa5, 0xd6, + 0x56, 0xcb, 0x1d, 0xfa, 0x35, 0xd7, 0xe1, 0x39, 0xb6, 0xf0, 0xf8, 0xbf, 0x01, 0x00, 0x00, 0xff, + 0xff, 0x8a, 0x26, 0xe3, 0x99, 0xef, 0x25, 0x00, 0x00, } func (m *UIBannerClickEvent) Marshal() (dAtA []byte, err error) { diff --git a/api/proto/buf.yaml b/api/proto/buf.yaml index af94a3b863a5f..36fb08bdf7aa4 100644 --- a/api/proto/buf.yaml +++ b/api/proto/buf.yaml @@ -27,12 +27,17 @@ lint: - teleport/legacy/types/wrappers/wrappers.proto ignore_only: # Allow package/directory mismatch for legacy protos. + FIELD_LOWER_SNAKE_CASE: + - teleport/legacy/types/device.proto + # Allow package/directory mismatch for legacy protos. PACKAGE_DIRECTORY_MATCH: - teleport/legacy/client/proto/joinservice.proto + - teleport/legacy/types/device.proto - teleport/legacy/types/webauthn/webauthn.proto # Allow non-versioned packages for legacy protos. PACKAGE_VERSION_SUFFIX: - teleport/legacy/client/proto/joinservice.proto + - teleport/legacy/types/device.proto - teleport/legacy/types/webauthn/webauthn.proto # Allow only certain services to use streaming RPCs. # diff --git a/api/proto/teleport/devicetrust/v1/device.proto b/api/proto/teleport/devicetrust/v1/device.proto index 95762fb0b6f27..5a8164834fc34 100644 --- a/api/proto/teleport/devicetrust/v1/device.proto +++ b/api/proto/teleport/devicetrust/v1/device.proto @@ -19,6 +19,8 @@ package teleport.devicetrust.v1; import "google/protobuf/timestamp.proto"; import "teleport/devicetrust/v1/device_collected_data.proto"; import "teleport/devicetrust/v1/device_enroll_token.proto"; +import "teleport/devicetrust/v1/device_profile.proto"; +import "teleport/devicetrust/v1/device_source.proto"; import "teleport/devicetrust/v1/os_type.proto"; option go_package = "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1"; @@ -76,6 +78,14 @@ message Device { // Only present in certain read modes. // Transient. repeated DeviceCollectedData collected_data = 10; + + // Source of the device. + // Devices managed directly via Teleport (`tctl`, Web UI, etc) have no + // assigned source. + DeviceSource source = 11; + + // Device information acquired from an external source. + DeviceProfile profile = 12; } // DeviceCredential represents the current enrolled public key of a device. diff --git a/api/proto/teleport/devicetrust/v1/device_collected_data.proto b/api/proto/teleport/devicetrust/v1/device_collected_data.proto index f36aa0db69beb..cd56570010b6e 100644 --- a/api/proto/teleport/devicetrust/v1/device_collected_data.proto +++ b/api/proto/teleport/devicetrust/v1/device_collected_data.proto @@ -41,4 +41,27 @@ message DeviceCollectedData { // Device serial number. // Required for macOS devices. string serial_number = 4; + + // Non-descriptive model identifier. + // Example: "MacBookPro9,2". + string model_identifier = 5; + + // OS version number, without the leading 'v'. + // Example: "13.2.1". + string os_version = 6; + + // OS build identifier. Augments the os_version. + // Example: "22D68". + string os_build = 7; + + // OS username (distinct from the Teleport user). + string os_username = 8; + + // Jamf binary version, without the leading 'v'. + // Example: "9.27" or "10.44.1-t1677509507". + string jamf_binary_version = 9; + + // Unmodified output of `/usr/bin/profiles status -type enrollment`. + // Used to verify the presence of an enrollment profile. + string macos_enrollment_profiles = 10; } diff --git a/api/proto/teleport/devicetrust/v1/device_profile.proto b/api/proto/teleport/devicetrust/v1/device_profile.proto new file mode 100644 index 0000000000000..c49b8eaa64c7d --- /dev/null +++ b/api/proto/teleport/devicetrust/v1/device_profile.proto @@ -0,0 +1,49 @@ +// Copyright 2023 Gravitational, Inc +// +// 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. + +syntax = "proto3"; + +package teleport.devicetrust.v1; + +import "google/protobuf/timestamp.proto"; + +option go_package = "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1"; + +// Device profile information acquired from an external source. +// If present, it's used to further validate collected data. +message DeviceProfile { + // Latest profile update time. + // System managed. + google.protobuf.Timestamp update_time = 1; + + // Non-descriptive model identifier. + // Example: "MacBookPro9,2". + string model_identifier = 2; + + // OS version number, without the leading 'v'. + // See the Device's os_type for the general OS category. + // Example: "13.2.1". + string os_version = 3; + + // OS build identifier. Augments the os_version. + // Example: "22D68". + string os_build = 4; + + // Known OS users (distinct from the Teleport user). + repeated string os_usernames = 5; + + // Jamf binary version, without the leading 'v'. + // Example: "9.27" or "10.44.1-t1677509507". + string jamf_binary_version = 6; +} diff --git a/api/proto/teleport/devicetrust/v1/device_source.proto b/api/proto/teleport/devicetrust/v1/device_source.proto new file mode 100644 index 0000000000000..75bf9dea9c1c9 --- /dev/null +++ b/api/proto/teleport/devicetrust/v1/device_source.proto @@ -0,0 +1,47 @@ +// Copyright 2023 Gravitational, Inc +// +// 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. + +syntax = "proto3"; + +package teleport.devicetrust.v1; + +option go_package = "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1"; + +// Source of device, for devices that are managed by external systems +// (for example, MDMs). +message DeviceSource { + // Name of the source. + // Matches the name of the corresponding MDM service, if applicable. + // Readonly. + string name = 1; + + // Origin of the source. + // Readonly. + DeviceOrigin origin = 2; +} + +// Origin of a device. +enum DeviceOrigin { + // Unspecified or absent origin. + DEVICE_ORIGIN_UNSPECIFIED = 0; + + // Devices originated from direct API usage. + DEVICE_ORIGIN_API = 1; + + // Devices originated from Jamf sync. + DEVICE_ORIGIN_JAMF = 2; + + // Source originated from Microsoft Intune sync. + DEVICE_ORIGIN_INTUNE = 3; +} diff --git a/api/proto/teleport/devicetrust/v1/devicetrust_service.proto b/api/proto/teleport/devicetrust/v1/devicetrust_service.proto index 974bd924d1cf4..1decaf3d8f958 100644 --- a/api/proto/teleport/devicetrust/v1/devicetrust_service.proto +++ b/api/proto/teleport/devicetrust/v1/devicetrust_service.proto @@ -22,6 +22,7 @@ import "google/rpc/status.proto"; import "teleport/devicetrust/v1/device.proto"; import "teleport/devicetrust/v1/device_collected_data.proto"; import "teleport/devicetrust/v1/device_enroll_token.proto"; +import "teleport/devicetrust/v1/device_source.proto"; import "teleport/devicetrust/v1/user_certificates.proto"; option go_package = "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1"; @@ -120,6 +121,14 @@ service DeviceTrustService { // // Only registered and enrolled devices may perform device authentication. rpc AuthenticateDevice(stream AuthenticateDeviceRequest) returns (stream AuthenticateDeviceResponse); + + // Syncs device inventory from a source exterior to Teleport, for example an + // MDM. + // Allows both partial and full syncs; for the latter, devices missing from + // the external inventory are handled as specified. + // Authorized either by a valid MDM service certificate or the appropriate + // "device" permissions (create/update/delete). + rpc SyncInventory(stream SyncInventoryRequest) returns (stream SyncInventoryResponse); } // Request for CreateDevice. @@ -257,12 +266,21 @@ message DeviceOrStatus { // ID of the created device. // Only present if the status is OK. string id = 2; + + // If true the action attempted against the device was a delete, instead of a + // create or update. + bool deleted = 3; } // Request for CreateDeviceEnrollToken. message CreateDeviceEnrollTokenRequest { // ID of the device. string device_id = 1; + + // Device collected data. + // Used to authorize issuance of device enrollment tokens for auto-enrollment. + // Not required otherwise. + DeviceCollectedData device_data = 2; } // Request for EnrollDevice. @@ -386,3 +404,107 @@ message AuthenticateDeviceChallengeResponse { // Signature over the challenge, using the device key. bytes signature = 1; } + +// Request for SyncInventory. +// +// A typical message sequence is as follows: +// (-> means client-to-server, <- means server-to-client) +// -> SyncInventoryStart +// <- SyncInventoryAck +// (loop) +// -> SyncInventoryDevices (add/remove devices) +// <- SyncInventoryResult +// (end loop) +// -> SyncInventoryEnd +// <- SyncInventoryResult (missing devices) +message SyncInventoryRequest { + oneof payload { + SyncInventoryStart start = 1; + SyncInventoryEnd end = 2; + SyncInventoryDevices devices_to_upsert = 3; + SyncInventoryDevices devices_to_remove = 4; + } +} + +// Response for SyncInventory. +message SyncInventoryResponse { + oneof payload { + SyncInventoryAck ack = 1; + SyncInventoryResult result = 2; + } +} + +// SyncInventoryStart starts the inventory sync. +message SyncInventoryStart { + // Source of the inventory sync. + // Acquired from the mTLS certificate for MDM services, must be explicitly + // supplied otherwise. + // This source is used for all devices. The `source` field in individual + // devices is ignored by this RPC. + DeviceSource source = 1; + + // Mode of syncing. + // Required. + SyncInventoryMode mode = 2; + + // Action for devices missing from the external inventory, but present in + // Teleport. + // Only executed if mode is FULL and mdm_sync_successful is set to true in the + // SyncInventoryEnd message. + SyncInventoryDeviceAction on_missing_action = 3; +} + +// Mode of sync for SyncInventory. +enum SyncInventoryMode { + SYNC_INVENTORY_MODE_UNSPECIFIED = 0; + + // Partial inventory sync; not all devices in the external inventory are sent + // to the stream. + // Partial sync precludes inventory cleanup. + SYNC_INVENTORY_MODE_PARTIAL = 1; + + // Full inventory sync; all devices in the external inventory must be sent to + // the stream. + // Full sync allows for handling of missing devices. + SYNC_INVENTORY_MODE_FULL = 2; +} + +// Device action for SyncInventory. +// Typically triggered at the end of FULL syncs.. +enum SyncInventoryDeviceAction { + SYNC_INVENTORY_DEVICE_ACTION_UNSPECIFIED = 0; + + // Noop is a no-action. + SYNC_INVENTORY_DEVICE_ACTION_NOOP = 1; + + // Deletes the device if the action condition is met. + SYNC_INVENTORY_DEVICE_ACTION_DELETE = 2; +} + +// SyncInventoryEnd ends the inventory sync, signaling that no more +// SyncInventoryDevices messages will be sent by the client. +message SyncInventoryEnd { + // True if the external sync was fully successful (for example, all reads from + // an MDM API were successful). + // If false the sync's on_missing_action is skipped. + bool external_sync_successful = 1; +} + +// SyncInventoryDevices transports devices to add/update/remove. +// Removals only need identifying fields to be set. +message SyncInventoryDevices { + // Devices to sync. + repeated Device devices = 1; +} + +// SyncInventoryAck is used to confirm successful processing of messages that +// lack a more specific response. +message SyncInventoryAck {} + +// SyncInventoryResult is the response for SyncInventoryDevices or +// SyncInventoryEnd +// It lists all synced/deleted devices. +message SyncInventoryResult { + // Devices modified, in the same order as the input when applicable. + repeated DeviceOrStatus devices = 1; +} diff --git a/api/proto/teleport/integration/v1/integration_service.proto b/api/proto/teleport/integration/v1/integration_service.proto new file mode 100644 index 0000000000000..cf5ad106b80f8 --- /dev/null +++ b/api/proto/teleport/integration/v1/integration_service.proto @@ -0,0 +1,88 @@ +// Copyright 2023 Gravitational, Inc +// +// 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. + +syntax = "proto3"; + +package teleport.integration.v1; + +import "google/protobuf/empty.proto"; +import "teleport/legacy/types/types.proto"; + +option go_package = "github.com/gravitational/teleport/api/gen/proto/go/teleport/integration/v1"; + +// IntegrationService provides methods to manage Integrations with 3rd party APIs. +service IntegrationService { + // ListIntegrations returns a paginated list of Integration resources. + rpc ListIntegrations(ListIntegrationsRequest) returns (ListIntegrationsResponse); + + // GetIntegration returns the specified Integration resource. + rpc GetIntegration(GetIntegrationRequest) returns (types.IntegrationV1); + + // CreateIntegration creates a new Integration resource. + rpc CreateIntegration(CreateIntegrationRequest) returns (types.IntegrationV1); + + // UpdateIntegration updates an existing Integration resource. + rpc UpdateIntegration(UpdateIntegrationRequest) returns (types.IntegrationV1); + + // DeleteIntegration removes the specified Integration resource. + rpc DeleteIntegration(DeleteIntegrationRequest) returns (google.protobuf.Empty); + + // DeleteAllIntegrations removes all Integrations. + rpc DeleteAllIntegrations(DeleteAllIntegrationsRequest) returns (google.protobuf.Empty); +} + +// ListIntegrationsRequest is a request for a paginated list of Integrations. +message ListIntegrationsRequest { + // Limit is the maximum amount of resources to retrieve. + int32 limit = 1; + // NextKey is the key for the next page of Integrations. + string next_key = 2; +} + +// ListIntegrationsResponse is the response for ListIntegrationsRequest. +message ListIntegrationsResponse { + // Integrations is a list of Integrations. + repeated types.IntegrationV1 integrations = 1; + // NextKey is the key for the next page of Integrations. + string next_key = 2; + // TotalCount is the total number of integrations in all pages. + int32 total_count = 3; +} + +// GetIntegrationRequest is a request for a specific Integration resource. +message GetIntegrationRequest { + // Name is the name of the Integration to be requested. + string name = 1; +} + +// CreateIntegrationRequest is the request to create the provided integration. +message CreateIntegrationRequest { + // Integration is the integration to be created. + types.IntegrationV1 integration = 1; +} + +// UpdateIntegrationRequest is the request to update the provided integration. +message UpdateIntegrationRequest { + // Integration is the integration to be created. + types.IntegrationV1 integration = 1; +} + +// DeleteIntegrationRequest is a request for deleting a specific Integration resource. +message DeleteIntegrationRequest { + // Name is the name of the Integration to be deleted. + string name = 1; +} + +// DeleteAllIntegrationsRequest is the request for deleting all integrations. +message DeleteAllIntegrationsRequest {} diff --git a/api/proto/teleport/legacy/client/proto/authservice.proto b/api/proto/teleport/legacy/client/proto/authservice.proto index f195aa137d506..b51a524db1440 100644 --- a/api/proto/teleport/legacy/client/proto/authservice.proto +++ b/api/proto/teleport/legacy/client/proto/authservice.proto @@ -130,6 +130,8 @@ message Event { types.OktaImportRuleV1 OktaImportRule = 40 [(gogoproto.jsontag) = "okta_import_rule,omitempty"]; // OktaAssignment is an OktaAssignment resource. types.OktaAssignmentV1 OktaAssignment = 41 [(gogoproto.jsontag) = "okta_assignment,omitempty"]; + // Integration is an Integration resource. + types.IntegrationV1 Integration = 42 [(gogoproto.jsontag) = "integration,omitempty"]; } } diff --git a/api/proto/teleport/legacy/types/device.proto b/api/proto/teleport/legacy/types/device.proto new file mode 100644 index 0000000000000..b33a57a9eb8a0 --- /dev/null +++ b/api/proto/teleport/legacy/types/device.proto @@ -0,0 +1,84 @@ +// Copyright 2023 Gravitational, Inc +// +// 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. + +syntax = "proto3"; + +package types; + +import "gogoproto/gogo.proto"; +import "google/protobuf/timestamp.proto"; +import "teleport/legacy/types/types.proto"; + +option go_package = "github.com/gravitational/teleport/api/types"; +option (gogoproto.goproto_getters_all) = false; +option (gogoproto.marshaler_all) = true; +option (gogoproto.unmarshaler_all) = true; + +// DeviceV1 is the resource representation of teleport.devicetrust.v1.Device. +message DeviceV1 { + // Header is the common resource header. + // + // - Kind is always "device". + // - SubKind is unused. + // - Version is equivalent to teleport.devicetrust.v1.Device.api_version. + // - Metadata.Name is equivalent to teleport.devicetrust.v1.Device.Id. + ResourceHeader Header = 1 [ + (gogoproto.nullable) = false, + (gogoproto.jsontag) = "", + (gogoproto.embed) = true + ]; + // Specification of the device. + DeviceSpec spec = 5 [(gogoproto.jsontag) = "spec"]; +} + +// DeviceSpec is a device specification. +// Roughly matches teleport.devicetrust.v1.Device, with some fields changed for +// better UX. +message DeviceSpec { + string os_type = 1 [(gogoproto.jsontag) = "os_type"]; + string asset_tag = 2 [(gogoproto.jsontag) = "asset_tag"]; + google.protobuf.Timestamp create_time = 3 [ + (gogoproto.stdtime) = true, + (gogoproto.jsontag) = "create_time" + ]; + google.protobuf.Timestamp update_time = 4 [ + (gogoproto.stdtime) = true, + (gogoproto.jsontag) = "update_time" + ]; + string enroll_status = 5 [(gogoproto.jsontag) = "enroll_status"]; + DeviceCredential credential = 6 [(gogoproto.jsontag) = "credential,omitempty"]; + repeated DeviceCollectedData collected_data = 7 [(gogoproto.jsontag) = "collected_data,omitempty"]; +} + +// DeviceCredential is the resource representation of +// teleport.devicetrust.v1.DeviceCredential. +message DeviceCredential { + string id = 1 [(gogoproto.jsontag) = "id"]; + bytes public_key_der = 2 [(gogoproto.jsontag) = "public_key_der"]; +} + +// DeviceCollectedData is the resource representation of +// teleport.devicetrust.v1.DeviceCollectedData. +message DeviceCollectedData { + google.protobuf.Timestamp collect_time = 1 [ + (gogoproto.stdtime) = true, + (gogoproto.jsontag) = "collect_time" + ]; + google.protobuf.Timestamp record_time = 2 [ + (gogoproto.stdtime) = true, + (gogoproto.jsontag) = "record_time" + ]; + string os_type = 3 [(gogoproto.jsontag) = "os_type"]; + string serial_number = 4 [(gogoproto.jsontag) = "serial_number,omitempty"]; +} diff --git a/api/proto/teleport/legacy/types/types.proto b/api/proto/teleport/legacy/types/types.proto index d00e0e8be1629..b7feb188ed71d 100644 --- a/api/proto/teleport/legacy/types/types.proto +++ b/api/proto/teleport/legacy/types/types.proto @@ -5271,6 +5271,59 @@ message OktaAssignmentActionTargetV1 { string id = 2 [(gogoproto.jsontag) = "id"]; } +// IntegrationV1 represents a connection between Teleport and some other 3rd party system. +// This connection allows API access to that service from Teleport. +// Each Integration instance must have a SubKind defined which identifies the external system. +message IntegrationV1 { + option (gogoproto.goproto_stringer) = false; + option (gogoproto.stringer) = false; + + // Header is the resource header. + ResourceHeader Header = 1 [ + (gogoproto.nullable) = false, + (gogoproto.jsontag) = "", + (gogoproto.embed) = true + ]; + + // Spec is an Integration specification. + IntegrationSpecV1 Spec = 2 [ + (gogoproto.nullable) = false, + (gogoproto.jsontag) = "spec" + ]; +} + +// IntegrationSpecV1 contains properties of all the supported integrations. +message IntegrationSpecV1 { + oneof SubKindSpec { + // AWSOIDC contains the specific fields to handle the AWS OIDC Integration subkind + AWSOIDCIntegrationSpecV1 AWSOIDC = 1 [(gogoproto.jsontag) = "aws_oidc,omitempty"]; + } + + // IntegrationStatus contains the current integration status. + enum IntegrationStatus { + INTEGRATION_STATUS_UNSPECIFIED = 0; + + // Integration is not running because it was just created or was disabled by the user. + INTEGRATION_STATUS_PAUSED = 1; + + // Integration is running. + INTEGRATION_STATUS_RUNNING = 2; + + // Integration has an error and that should be fixed before receiving requests. + INTEGRATION_STATUS_ERROR = 3; + } + + // Status contains the current Integration Status + IntegrationStatus status = 2; +} + +// AWSOIDCIntegrationSpecV1 contains the spec properties for the AWS OIDC SubKind Integration. +message AWSOIDCIntegrationSpecV1 { + // RoleARN contains the Role ARN used to set up the Integration. + // This is the AWS Role that Teleport will use to issue tokens for API Calls. + string RoleARN = 1 [(gogoproto.jsontag) = "role_arn,omitempty"]; +} + // HeadlessAuthentication holds data for an ongoing headless authentication attempt. message HeadlessAuthentication { // Header is the resource header. diff --git a/api/proto/teleport/plugins/v1/plugin_service.proto b/api/proto/teleport/plugins/v1/plugin_service.proto index b5a2975d4fb16..344b365e84899 100644 --- a/api/proto/teleport/plugins/v1/plugin_service.proto +++ b/api/proto/teleport/plugins/v1/plugin_service.proto @@ -21,6 +21,19 @@ import "teleport/legacy/types/types.proto"; option go_package = "github.com/gravitational/teleport/api/gen/proto/go/teleport/plugins/v1"; +// PluginType represents a single type of hosted plugin +// that can be onboarded. +message PluginType { + // Type is a string corresponding to api.PluginTypeXXX constants + string type = 1; + + // OAuthClientID contains the client ID of the OAuth application + // that is used with this plugin's API provider. + // For plugins that are not authenticated via OAuth, + // this will be empty. + string oauth_client_id = 2; +} + // CreatePluginRequest creates a new plugin from the given spec and initial credentials. message CreatePluginRequest { // Plugin is the plugin object without live credentials. @@ -81,6 +94,16 @@ message SetPluginStatusRequest { types.PluginStatusV1 status = 2; } +// GetAvailablePluginTypesRequest is the request type for GetAvailablePluginTypes +message GetAvailablePluginTypesRequest {} + +// GetAvailablePluginTypesResponse is a response to for GetAvailablePluginTypes +message GetAvailablePluginTypesResponse { + // PluginTypes is a list of hosted plugins + // that the auth service supports. + repeated PluginType plugin_types = 1; +} + // PluginService provides CRUD operations for Plugin resources. service PluginService { // CreatePlugin creates a new plugin instance. @@ -100,4 +123,8 @@ service PluginService { // SetPluginCredentials sets the status for the given plugin. rpc SetPluginStatus(SetPluginStatusRequest) returns (google.protobuf.Empty); + + // GetAvailablePluginTypes returns the types of plugins + // that the auth server supports onboarding. + rpc GetAvailablePluginTypes(GetAvailablePluginTypesRequest) returns (GetAvailablePluginTypesResponse); } diff --git a/api/proto/teleport/usageevents/v1/usageevents.proto b/api/proto/teleport/usageevents/v1/usageevents.proto index 0bb238e7683f8..a5bfb1727052c 100644 --- a/api/proto/teleport/usageevents/v1/usageevents.proto +++ b/api/proto/teleport/usageevents/v1/usageevents.proto @@ -106,6 +106,28 @@ enum DiscoverResource { DISCOVER_RESOURCE_DATABASE_POSTGRES_GCP = 15; DISCOVER_RESOURCE_DATABASE_MYSQL_GCP = 16; DISCOVER_RESOURCE_DATABASE_SQLSERVER_GCP = 17; + + DISCOVER_RESOURCE_DATABASE_POSTGRES_REDSHIFT_SERVERLESS = 18; + DISCOVER_RESOURCE_DATABASE_POSTGRES_AZURE = 19; + DISCOVER_RESOURCE_DATABASE_DYNAMODB = 20; + DISCOVER_RESOURCE_DATABASE_CASSANDRA_KEYSPACES = 21; + DISCOVER_RESOURCE_DATABASE_CASSANDRA_SELF_HOSTED = 22; // Cassandra & ScyllaDb + DISCOVER_RESOURCE_DATABASE_ELASTICSEARCH_SELF_HOSTED = 23; + DISCOVER_RESOURCE_DATABASE_REDIS_ELASTICACHE = 24; // Elasticache & MemoryDb + DISCOVER_RESOURCE_DATABASE_REDIS_MEMORYDB = 25; + DISCOVER_RESOURCE_DATABASE_REDIS_AZURE_CACHE = 26; + DISCOVER_RESOURCE_DATABASE_REDIS_CLUSTER_SELF_HOSTED = 27; + + DISCOVER_RESOURCE_DATABASE_MYSQL_AZURE = 28; + DISCOVER_RESOURCE_DATABASE_SQLSERVER_AZURE = 29; + DISCOVER_RESOURCE_DATABASE_SQLSERVER_MICROSOFT = 30; + DISCOVER_RESOURCE_DATABASE_COCKROACHDB_SELF_HOSTED = 31; + DISCOVER_RESOURCE_DATABASE_MONGODB_ATLAS = 32; + DISCOVER_RESOURCE_DATABASE_SNOWFLAKE = 33; + + DISCOVER_RESOURCE_DOC_DATABASE_RDS_PROXY = 34; + DISCOVER_RESOURCE_DOC_DATABASE_HIGH_AVAILABILITY = 35; + DISCOVER_RESOURCE_DOC_DATABASE_DYNAMIC_REGISTRATION = 36; } // DiscoverResourceMetadata contains common metadata identifying resource type being added. diff --git a/api/types/database.go b/api/types/database.go index 793430fff48eb..e0206f4862402 100644 --- a/api/types/database.go +++ b/api/types/database.go @@ -511,6 +511,7 @@ func (d *DatabaseV3) CheckAndSetDefaults() error { if err := d.Metadata.CheckAndSetDefaults(); err != nil { return trace.Wrap(err) } + for key := range d.Spec.DynamicLabels { if !IsValidLabelKey(key) { return trace.BadParameter("database %q invalid label key: %q", d.GetName(), key) diff --git a/api/types/device.go b/api/types/device.go new file mode 100644 index 0000000000000..ca0979ba14af8 --- /dev/null +++ b/api/types/device.go @@ -0,0 +1,256 @@ +// Copyright 2023 Gravitational, Inc +// +// 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. + +package types + +import ( + "encoding/json" + "time" + + "github.com/google/uuid" + "github.com/gravitational/trace" + "google.golang.org/protobuf/types/known/timestamppb" + + devicepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1" +) + +// CheckAndSetDefaults checks DeviceV1 fields to catch simple errors, and sets +// default values for all fields with defaults. +func (d *DeviceV1) CheckAndSetDefaults() error { + if d == nil { + return trace.BadParameter("device is nil") + } + + // Assign defaults: + // - Kind = device + // - Metadata.Name = UUID + // - Spec.EnrollStatus = unspecified + if d.Kind == "" { + d.Kind = KindDevice + } else if d.Kind != KindDevice { // sanity check + return trace.BadParameter("unexpected resource kind %q, must be %q", d.Kind, KindDevice) + } + if d.Metadata.Name == "" { + d.Metadata.Name = uuid.NewString() + } + if d.Spec.EnrollStatus == "" { + d.Spec.EnrollStatus = ResourceEnrollStatusToString(devicepb.DeviceEnrollStatus_DEVICE_ENROLL_STATUS_UNSPECIFIED) + } + + // Validate Header/Metadata. + if err := d.ResourceHeader.CheckAndSetDefaults(); err != nil { + return trace.Wrap(err) + } + + // Validate "simple" fields. + switch { + case d.Spec.OsType == "": + return trace.BadParameter("missing OS type") + case d.Spec.AssetTag == "": + return trace.BadParameter("missing asset tag") + } + + // Validate enum conversions. + if _, err := ResourceOSTypeFromString(d.Spec.OsType); err != nil { + return trace.Wrap(err) + } + if _, err := ResourceEnrollStatusFromString(d.Spec.EnrollStatus); err != nil { + return trace.Wrap(err) + } + + return nil +} + +// UnmarshalDevice unmarshals a DeviceV1 resource and runs CheckAndSetDefaults. +func UnmarshalDevice(raw []byte) (*DeviceV1, error) { + dev := &DeviceV1{} + if err := json.Unmarshal(raw, dev); err != nil { + return nil, trace.Wrap(err) + } + return dev, trace.Wrap(dev.CheckAndSetDefaults()) +} + +// DeviceFromResource converts a resource DeviceV1 to an API devicepb.Device. +func DeviceFromResource(res *DeviceV1) (*devicepb.Device, error) { + if res == nil { + return nil, trace.BadParameter("device is nil") + } + + toTimePB := func(t *time.Time) *timestamppb.Timestamp { + if t == nil { + return nil + } + return timestamppb.New(*t) + } + + osType, err := ResourceOSTypeFromString(res.Spec.OsType) + if err != nil { + return nil, trace.Wrap(err) + } + + enrollStatus, err := ResourceEnrollStatusFromString(res.Spec.EnrollStatus) + if err != nil { + return nil, trace.Wrap(err) + } + + var cred *devicepb.DeviceCredential + if res.Spec.Credential != nil { + cred = &devicepb.DeviceCredential{ + Id: res.Spec.Credential.Id, + PublicKeyDer: res.Spec.Credential.PublicKeyDer, + } + } + + collectedData := make([]*devicepb.DeviceCollectedData, len(res.Spec.CollectedData)) + for i, d := range res.Spec.CollectedData { + dataOSType, err := ResourceOSTypeFromString(d.OsType) + if err != nil { + return nil, trace.Wrap(err) + } + + collectedData[i] = &devicepb.DeviceCollectedData{ + CollectTime: toTimePB(d.CollectTime), + RecordTime: toTimePB(d.RecordTime), + OsType: dataOSType, + SerialNumber: d.SerialNumber, + } + } + + return &devicepb.Device{ + ApiVersion: res.Version, + Id: res.Metadata.Name, + OsType: osType, + AssetTag: res.Spec.AssetTag, + CreateTime: toTimePB(res.Spec.CreateTime), + UpdateTime: toTimePB(res.Spec.UpdateTime), + EnrollStatus: enrollStatus, + Credential: cred, + CollectedData: collectedData, + }, nil +} + +// DeviceToResource converts an API devicepb.Device to a resource DeviceV1 and +// assigns all default fields. +func DeviceToResource(dev *devicepb.Device) *DeviceV1 { + if dev == nil { + return nil + } + + toTimePtr := func(pb *timestamppb.Timestamp) *time.Time { + if pb == nil { + return nil + } + t := pb.AsTime() + return &t + } + + var cred *DeviceCredential + if dev.Credential != nil { + cred = &DeviceCredential{ + Id: dev.Credential.Id, + PublicKeyDer: dev.Credential.PublicKeyDer, + } + } + + collectedData := make([]*DeviceCollectedData, len(dev.CollectedData)) + for i, d := range dev.CollectedData { + collectedData[i] = &DeviceCollectedData{ + CollectTime: toTimePtr(d.CollectTime), + RecordTime: toTimePtr(d.RecordTime), + OsType: ResourceOSTypeToString(d.OsType), + SerialNumber: d.SerialNumber, + } + } + + res := &DeviceV1{ + ResourceHeader: ResourceHeader{ + Kind: KindDevice, + Version: dev.ApiVersion, + Metadata: Metadata{ + Name: dev.Id, + }, + }, + Spec: &DeviceSpec{ + OsType: ResourceOSTypeToString(dev.OsType), + AssetTag: dev.AssetTag, + CreateTime: toTimePtr(dev.CreateTime), + UpdateTime: toTimePtr(dev.UpdateTime), + EnrollStatus: ResourceEnrollStatusToString(dev.EnrollStatus), + Credential: cred, + CollectedData: collectedData, + }, + } + _ = res.CheckAndSetDefaults() // assign default fields + return res +} + +// ResourceOSTypeToString converts OSType to a string representation suitable +// for use in resource fields. +func ResourceOSTypeToString(osType devicepb.OSType) string { + switch osType { + case devicepb.OSType_OS_TYPE_LINUX: + return "linux" + case devicepb.OSType_OS_TYPE_MACOS: + return "macos" + case devicepb.OSType_OS_TYPE_WINDOWS: + return "windows" + default: + return osType.String() + } +} + +// ResourceOSTypeFromString converts a string representation of OSType suitable +// for resource fields to OSType. +func ResourceOSTypeFromString(osType string) (devicepb.OSType, error) { + switch osType { + case "linux": + return devicepb.OSType_OS_TYPE_LINUX, nil + case "macos": + return devicepb.OSType_OS_TYPE_MACOS, nil + case "windows": + return devicepb.OSType_OS_TYPE_WINDOWS, nil + default: + return devicepb.OSType_OS_TYPE_UNSPECIFIED, trace.BadParameter("unknown os type %q", osType) + } +} + +// ResourceEnrollStatusToString converts DeviceEnrollStatus to a string +// representation suitable for use in resource fields. +func ResourceEnrollStatusToString(enrollStatus devicepb.DeviceEnrollStatus) string { + switch enrollStatus { + case devicepb.DeviceEnrollStatus_DEVICE_ENROLL_STATUS_ENROLLED: + return "enrolled" + case devicepb.DeviceEnrollStatus_DEVICE_ENROLL_STATUS_NOT_ENROLLED: + return "not_enrolled" + case devicepb.DeviceEnrollStatus_DEVICE_ENROLL_STATUS_UNSPECIFIED: + return "unspecified" + default: + return enrollStatus.String() + } +} + +// ResourceEnrollStatusFromString converts a string representation of +// DeviceEnrollStatus suitable for resource fields to DeviceEnrollStatus. +func ResourceEnrollStatusFromString(enrollStatus string) (devicepb.DeviceEnrollStatus, error) { + switch enrollStatus { + case "enrolled": + return devicepb.DeviceEnrollStatus_DEVICE_ENROLL_STATUS_ENROLLED, nil + case "not_enrolled": + return devicepb.DeviceEnrollStatus_DEVICE_ENROLL_STATUS_NOT_ENROLLED, nil + case "unspecified": + return devicepb.DeviceEnrollStatus_DEVICE_ENROLL_STATUS_UNSPECIFIED, nil + default: + return devicepb.DeviceEnrollStatus_DEVICE_ENROLL_STATUS_UNSPECIFIED, trace.BadParameter("unknown enroll status %q", enrollStatus) + } +} diff --git a/api/types/device.pb.go b/api/types/device.pb.go new file mode 100644 index 0000000000000..fc30219db17c9 --- /dev/null +++ b/api/types/device.pb.go @@ -0,0 +1,1436 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: teleport/legacy/types/device.proto + +package types + +import ( + fmt "fmt" + _ "github.com/gogo/protobuf/gogoproto" + proto "github.com/gogo/protobuf/proto" + github_com_gogo_protobuf_types "github.com/gogo/protobuf/types" + _ "google.golang.org/protobuf/types/known/timestamppb" + io "io" + math "math" + math_bits "math/bits" + time "time" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf +var _ = time.Kitchen + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// DeviceV1 is the resource representation of teleport.devicetrust.v1.Device. +type DeviceV1 struct { + // Header is the common resource header. + // + // - Kind is always "device". + // - SubKind is unused. + // - Version is equivalent to teleport.devicetrust.v1.Device.api_version. + // - Metadata.Name is equivalent to teleport.devicetrust.v1.Device.Id. + ResourceHeader `protobuf:"bytes,1,opt,name=Header,proto3,embedded=Header" json:""` + // Specification of the device. + Spec *DeviceSpec `protobuf:"bytes,5,opt,name=spec,proto3" json:"spec"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *DeviceV1) Reset() { *m = DeviceV1{} } +func (m *DeviceV1) String() string { return proto.CompactTextString(m) } +func (*DeviceV1) ProtoMessage() {} +func (*DeviceV1) Descriptor() ([]byte, []int) { + return fileDescriptor_aceaef1b58496e7d, []int{0} +} +func (m *DeviceV1) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *DeviceV1) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_DeviceV1.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *DeviceV1) XXX_Merge(src proto.Message) { + xxx_messageInfo_DeviceV1.Merge(m, src) +} +func (m *DeviceV1) XXX_Size() int { + return m.Size() +} +func (m *DeviceV1) XXX_DiscardUnknown() { + xxx_messageInfo_DeviceV1.DiscardUnknown(m) +} + +var xxx_messageInfo_DeviceV1 proto.InternalMessageInfo + +// DeviceSpec is a device specification. +// Roughly matches teleport.devicetrust.v1.Device, with some fields changed for +// better UX. +type DeviceSpec struct { + OsType string `protobuf:"bytes,1,opt,name=os_type,json=osType,proto3" json:"os_type"` + AssetTag string `protobuf:"bytes,2,opt,name=asset_tag,json=assetTag,proto3" json:"asset_tag"` + CreateTime *time.Time `protobuf:"bytes,3,opt,name=create_time,json=createTime,proto3,stdtime" json:"create_time"` + UpdateTime *time.Time `protobuf:"bytes,4,opt,name=update_time,json=updateTime,proto3,stdtime" json:"update_time"` + EnrollStatus string `protobuf:"bytes,5,opt,name=enroll_status,json=enrollStatus,proto3" json:"enroll_status"` + Credential *DeviceCredential `protobuf:"bytes,6,opt,name=credential,proto3" json:"credential,omitempty"` + CollectedData []*DeviceCollectedData `protobuf:"bytes,7,rep,name=collected_data,json=collectedData,proto3" json:"collected_data,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *DeviceSpec) Reset() { *m = DeviceSpec{} } +func (m *DeviceSpec) String() string { return proto.CompactTextString(m) } +func (*DeviceSpec) ProtoMessage() {} +func (*DeviceSpec) Descriptor() ([]byte, []int) { + return fileDescriptor_aceaef1b58496e7d, []int{1} +} +func (m *DeviceSpec) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *DeviceSpec) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_DeviceSpec.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *DeviceSpec) XXX_Merge(src proto.Message) { + xxx_messageInfo_DeviceSpec.Merge(m, src) +} +func (m *DeviceSpec) XXX_Size() int { + return m.Size() +} +func (m *DeviceSpec) XXX_DiscardUnknown() { + xxx_messageInfo_DeviceSpec.DiscardUnknown(m) +} + +var xxx_messageInfo_DeviceSpec proto.InternalMessageInfo + +// DeviceCredential is the resource representation of +// teleport.devicetrust.v1.DeviceCredential. +type DeviceCredential struct { + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id"` + PublicKeyDer []byte `protobuf:"bytes,2,opt,name=public_key_der,json=publicKeyDer,proto3" json:"public_key_der"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *DeviceCredential) Reset() { *m = DeviceCredential{} } +func (m *DeviceCredential) String() string { return proto.CompactTextString(m) } +func (*DeviceCredential) ProtoMessage() {} +func (*DeviceCredential) Descriptor() ([]byte, []int) { + return fileDescriptor_aceaef1b58496e7d, []int{2} +} +func (m *DeviceCredential) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *DeviceCredential) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_DeviceCredential.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *DeviceCredential) XXX_Merge(src proto.Message) { + xxx_messageInfo_DeviceCredential.Merge(m, src) +} +func (m *DeviceCredential) XXX_Size() int { + return m.Size() +} +func (m *DeviceCredential) XXX_DiscardUnknown() { + xxx_messageInfo_DeviceCredential.DiscardUnknown(m) +} + +var xxx_messageInfo_DeviceCredential proto.InternalMessageInfo + +// DeviceCollectedData is the resource representation of +// teleport.devicetrust.v1.DeviceCollectedData. +type DeviceCollectedData struct { + CollectTime *time.Time `protobuf:"bytes,1,opt,name=collect_time,json=collectTime,proto3,stdtime" json:"collect_time"` + RecordTime *time.Time `protobuf:"bytes,2,opt,name=record_time,json=recordTime,proto3,stdtime" json:"record_time"` + OsType string `protobuf:"bytes,3,opt,name=os_type,json=osType,proto3" json:"os_type"` + SerialNumber string `protobuf:"bytes,4,opt,name=serial_number,json=serialNumber,proto3" json:"serial_number,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *DeviceCollectedData) Reset() { *m = DeviceCollectedData{} } +func (m *DeviceCollectedData) String() string { return proto.CompactTextString(m) } +func (*DeviceCollectedData) ProtoMessage() {} +func (*DeviceCollectedData) Descriptor() ([]byte, []int) { + return fileDescriptor_aceaef1b58496e7d, []int{3} +} +func (m *DeviceCollectedData) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *DeviceCollectedData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_DeviceCollectedData.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *DeviceCollectedData) XXX_Merge(src proto.Message) { + xxx_messageInfo_DeviceCollectedData.Merge(m, src) +} +func (m *DeviceCollectedData) XXX_Size() int { + return m.Size() +} +func (m *DeviceCollectedData) XXX_DiscardUnknown() { + xxx_messageInfo_DeviceCollectedData.DiscardUnknown(m) +} + +var xxx_messageInfo_DeviceCollectedData proto.InternalMessageInfo + +func init() { + proto.RegisterType((*DeviceV1)(nil), "types.DeviceV1") + proto.RegisterType((*DeviceSpec)(nil), "types.DeviceSpec") + proto.RegisterType((*DeviceCredential)(nil), "types.DeviceCredential") + proto.RegisterType((*DeviceCollectedData)(nil), "types.DeviceCollectedData") +} + +func init() { + proto.RegisterFile("teleport/legacy/types/device.proto", fileDescriptor_aceaef1b58496e7d) +} + +var fileDescriptor_aceaef1b58496e7d = []byte{ + // 615 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x54, 0xd1, 0x6e, 0xd3, 0x30, + 0x14, 0x5d, 0xda, 0xad, 0x5b, 0xdd, 0x76, 0x62, 0xde, 0x60, 0xd1, 0x40, 0xf5, 0xa8, 0x78, 0x40, + 0x80, 0x1a, 0x01, 0x12, 0x42, 0x42, 0x48, 0x28, 0xec, 0x01, 0x09, 0x09, 0x41, 0x36, 0xf1, 0xc0, + 0x4b, 0xe4, 0x3a, 0x97, 0x60, 0x91, 0xd6, 0x91, 0xe3, 0x4c, 0xf4, 0x2f, 0xf8, 0x08, 0x3e, 0x66, + 0x8f, 0xfb, 0x02, 0x03, 0x7b, 0xf4, 0x03, 0xdf, 0x80, 0x62, 0xb7, 0x5b, 0x32, 0x0d, 0xb1, 0x97, + 0x28, 0xf7, 0xdc, 0x73, 0x8e, 0x6f, 0xee, 0xbd, 0x31, 0x1a, 0x29, 0xc8, 0x20, 0x17, 0x52, 0x05, + 0x19, 0xa4, 0x94, 0xcd, 0x03, 0x35, 0xcf, 0xa1, 0x08, 0x12, 0x38, 0xe6, 0x0c, 0xc6, 0xb9, 0x14, + 0x4a, 0xe0, 0x35, 0x8b, 0xed, 0xed, 0xa4, 0x22, 0x15, 0x16, 0x09, 0xaa, 0x37, 0x97, 0xdc, 0x23, + 0xa9, 0x10, 0x69, 0x06, 0x81, 0x8d, 0x26, 0xe5, 0xe7, 0x40, 0xf1, 0x29, 0x14, 0x8a, 0x4e, 0xf3, + 0x05, 0xe1, 0xee, 0xd5, 0x27, 0xd8, 0xa7, 0xa3, 0x8c, 0xbe, 0xa1, 0x8d, 0x03, 0x7b, 0xe0, 0xc7, + 0xc7, 0xf8, 0x05, 0xea, 0xbc, 0x01, 0x9a, 0x80, 0xf4, 0xbd, 0x7d, 0xef, 0x7e, 0xef, 0xc9, 0xcd, + 0xb1, 0x63, 0x46, 0x50, 0x88, 0x52, 0x32, 0x70, 0xc9, 0xb0, 0x7f, 0xa2, 0xc9, 0xca, 0xa9, 0x26, + 0x9e, 0xd1, 0x64, 0x25, 0x5a, 0x48, 0x70, 0x80, 0x56, 0x8b, 0x1c, 0x98, 0xbf, 0x66, 0xa5, 0x5b, + 0x0b, 0xa9, 0xf3, 0x3e, 0xcc, 0x81, 0x85, 0x1b, 0x46, 0x13, 0x4b, 0x89, 0xec, 0x73, 0xf4, 0xa7, + 0x8d, 0xd0, 0x45, 0x1a, 0xdf, 0x43, 0xeb, 0xa2, 0x88, 0x2b, 0x95, 0x3d, 0xbd, 0x1b, 0xf6, 0x8c, + 0x26, 0x4b, 0x28, 0xea, 0x88, 0xe2, 0x68, 0x9e, 0x03, 0x7e, 0x80, 0xba, 0xb4, 0x28, 0x40, 0xc5, + 0x8a, 0xa6, 0x7e, 0xcb, 0xf2, 0x06, 0x46, 0x93, 0x0b, 0x30, 0xda, 0xb0, 0xaf, 0x47, 0x34, 0xc5, + 0xef, 0x51, 0x8f, 0x49, 0xa0, 0x0a, 0xe2, 0xaa, 0x2f, 0x7e, 0xdb, 0x16, 0xb6, 0x37, 0x76, 0x4d, + 0x1b, 0x2f, 0x9b, 0x36, 0x3e, 0x5a, 0x36, 0x2d, 0xdc, 0x36, 0x9a, 0xd4, 0x25, 0xdf, 0x7f, 0x12, + 0x2f, 0x42, 0x0e, 0xa8, 0x58, 0x95, 0x63, 0x99, 0x27, 0xe7, 0x8e, 0xab, 0xd7, 0x73, 0xac, 0x49, + 0x9c, 0xa3, 0x03, 0xac, 0xe3, 0x33, 0x34, 0x80, 0x99, 0x14, 0x59, 0x16, 0x17, 0x8a, 0xaa, 0xb2, + 0xb0, 0xed, 0xeb, 0x86, 0x5b, 0x46, 0x93, 0x66, 0x22, 0xea, 0xbb, 0xf0, 0xd0, 0x46, 0xf8, 0x03, + 0xaa, 0xea, 0x4a, 0x60, 0xa6, 0x38, 0xcd, 0xfc, 0x8e, 0x2d, 0x64, 0xb7, 0xd1, 0xf3, 0xd7, 0xe7, + 0xe9, 0xd0, 0x37, 0x9a, 0xec, 0x5c, 0xd0, 0x1f, 0x89, 0x29, 0x57, 0x30, 0xcd, 0xd5, 0x3c, 0xaa, + 0x99, 0xe0, 0x18, 0x6d, 0x32, 0x91, 0x65, 0xc0, 0x14, 0x24, 0x71, 0x42, 0x15, 0xf5, 0xd7, 0xf7, + 0xdb, 0xf6, 0xfb, 0x1a, 0xb6, 0x4b, 0xca, 0x01, 0x55, 0x34, 0xbc, 0x63, 0x34, 0xf1, 0x9b, 0xaa, + 0x9a, 0xfb, 0x80, 0xd5, 0xc9, 0xa3, 0x04, 0xdd, 0xb8, 0x5c, 0x1a, 0xbe, 0x85, 0x5a, 0x3c, 0x59, + 0x0c, 0xbc, 0x63, 0x34, 0x69, 0xf1, 0x24, 0x6a, 0xf1, 0x04, 0x3f, 0x47, 0x9b, 0x79, 0x39, 0xc9, + 0x38, 0x8b, 0xbf, 0xc2, 0x3c, 0xae, 0x56, 0xb2, 0x1a, 0x76, 0x3f, 0xc4, 0x46, 0x93, 0x4b, 0x99, + 0xa8, 0xef, 0xe2, 0xb7, 0x30, 0x3f, 0x00, 0x39, 0xfa, 0xd1, 0x42, 0xdb, 0x57, 0x94, 0x8a, 0x0f, + 0x51, 0x7f, 0x51, 0x8e, 0x1b, 0x9e, 0xf7, 0xdf, 0xe1, 0xed, 0x18, 0x4d, 0x1a, 0x1a, 0x3b, 0xbd, + 0xde, 0x02, 0x59, 0x2e, 0x84, 0x04, 0x26, 0x64, 0xe2, 0x3c, 0x5b, 0xd7, 0x5b, 0x88, 0x9a, 0xc4, + 0x2d, 0x84, 0x03, 0xac, 0x63, 0xed, 0x37, 0x68, 0xff, 0xfb, 0x37, 0x78, 0x85, 0x06, 0x05, 0x48, + 0x4e, 0xb3, 0x78, 0x56, 0x4e, 0x27, 0x20, 0xed, 0x2a, 0x76, 0xc3, 0xdb, 0x46, 0x93, 0xdd, 0x46, + 0xa2, 0x36, 0x8d, 0xbe, 0x4b, 0xbc, 0xb3, 0x78, 0xf8, 0xf2, 0xe4, 0xf7, 0x70, 0xe5, 0xe4, 0x6c, + 0xe8, 0x9d, 0x9e, 0x0d, 0xbd, 0x5f, 0x67, 0x43, 0xef, 0xd3, 0xc3, 0x94, 0xab, 0x2f, 0xe5, 0x64, + 0xcc, 0xc4, 0x34, 0x48, 0x25, 0x3d, 0xe6, 0x8a, 0x2a, 0x2e, 0x66, 0x34, 0x0b, 0xce, 0x6f, 0x11, + 0x9a, 0x73, 0x77, 0x79, 0x4c, 0x3a, 0xf6, 0xdb, 0x9e, 0xfe, 0x0d, 0x00, 0x00, 0xff, 0xff, 0x8b, + 0x1a, 0x20, 0x63, 0xc4, 0x04, 0x00, 0x00, +} + +func (m *DeviceV1) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DeviceV1) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *DeviceV1) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if m.Spec != nil { + { + size, err := m.Spec.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintDevice(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + } + { + size, err := m.ResourceHeader.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintDevice(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *DeviceSpec) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DeviceSpec) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *DeviceSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if len(m.CollectedData) > 0 { + for iNdEx := len(m.CollectedData) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.CollectedData[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintDevice(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x3a + } + } + if m.Credential != nil { + { + size, err := m.Credential.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintDevice(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + } + if len(m.EnrollStatus) > 0 { + i -= len(m.EnrollStatus) + copy(dAtA[i:], m.EnrollStatus) + i = encodeVarintDevice(dAtA, i, uint64(len(m.EnrollStatus))) + i-- + dAtA[i] = 0x2a + } + if m.UpdateTime != nil { + n4, err4 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.UpdateTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.UpdateTime):]) + if err4 != nil { + return 0, err4 + } + i -= n4 + i = encodeVarintDevice(dAtA, i, uint64(n4)) + i-- + dAtA[i] = 0x22 + } + if m.CreateTime != nil { + n5, err5 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.CreateTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.CreateTime):]) + if err5 != nil { + return 0, err5 + } + i -= n5 + i = encodeVarintDevice(dAtA, i, uint64(n5)) + i-- + dAtA[i] = 0x1a + } + if len(m.AssetTag) > 0 { + i -= len(m.AssetTag) + copy(dAtA[i:], m.AssetTag) + i = encodeVarintDevice(dAtA, i, uint64(len(m.AssetTag))) + i-- + dAtA[i] = 0x12 + } + if len(m.OsType) > 0 { + i -= len(m.OsType) + copy(dAtA[i:], m.OsType) + i = encodeVarintDevice(dAtA, i, uint64(len(m.OsType))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *DeviceCredential) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DeviceCredential) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *DeviceCredential) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if len(m.PublicKeyDer) > 0 { + i -= len(m.PublicKeyDer) + copy(dAtA[i:], m.PublicKeyDer) + i = encodeVarintDevice(dAtA, i, uint64(len(m.PublicKeyDer))) + i-- + dAtA[i] = 0x12 + } + if len(m.Id) > 0 { + i -= len(m.Id) + copy(dAtA[i:], m.Id) + i = encodeVarintDevice(dAtA, i, uint64(len(m.Id))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *DeviceCollectedData) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DeviceCollectedData) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *DeviceCollectedData) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if len(m.SerialNumber) > 0 { + i -= len(m.SerialNumber) + copy(dAtA[i:], m.SerialNumber) + i = encodeVarintDevice(dAtA, i, uint64(len(m.SerialNumber))) + i-- + dAtA[i] = 0x22 + } + if len(m.OsType) > 0 { + i -= len(m.OsType) + copy(dAtA[i:], m.OsType) + i = encodeVarintDevice(dAtA, i, uint64(len(m.OsType))) + i-- + dAtA[i] = 0x1a + } + if m.RecordTime != nil { + n6, err6 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.RecordTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.RecordTime):]) + if err6 != nil { + return 0, err6 + } + i -= n6 + i = encodeVarintDevice(dAtA, i, uint64(n6)) + i-- + dAtA[i] = 0x12 + } + if m.CollectTime != nil { + n7, err7 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.CollectTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.CollectTime):]) + if err7 != nil { + return 0, err7 + } + i -= n7 + i = encodeVarintDevice(dAtA, i, uint64(n7)) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintDevice(dAtA []byte, offset int, v uint64) int { + offset -= sovDevice(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *DeviceV1) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ResourceHeader.Size() + n += 1 + l + sovDevice(uint64(l)) + if m.Spec != nil { + l = m.Spec.Size() + n += 1 + l + sovDevice(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *DeviceSpec) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.OsType) + if l > 0 { + n += 1 + l + sovDevice(uint64(l)) + } + l = len(m.AssetTag) + if l > 0 { + n += 1 + l + sovDevice(uint64(l)) + } + if m.CreateTime != nil { + l = github_com_gogo_protobuf_types.SizeOfStdTime(*m.CreateTime) + n += 1 + l + sovDevice(uint64(l)) + } + if m.UpdateTime != nil { + l = github_com_gogo_protobuf_types.SizeOfStdTime(*m.UpdateTime) + n += 1 + l + sovDevice(uint64(l)) + } + l = len(m.EnrollStatus) + if l > 0 { + n += 1 + l + sovDevice(uint64(l)) + } + if m.Credential != nil { + l = m.Credential.Size() + n += 1 + l + sovDevice(uint64(l)) + } + if len(m.CollectedData) > 0 { + for _, e := range m.CollectedData { + l = e.Size() + n += 1 + l + sovDevice(uint64(l)) + } + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *DeviceCredential) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Id) + if l > 0 { + n += 1 + l + sovDevice(uint64(l)) + } + l = len(m.PublicKeyDer) + if l > 0 { + n += 1 + l + sovDevice(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *DeviceCollectedData) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.CollectTime != nil { + l = github_com_gogo_protobuf_types.SizeOfStdTime(*m.CollectTime) + n += 1 + l + sovDevice(uint64(l)) + } + if m.RecordTime != nil { + l = github_com_gogo_protobuf_types.SizeOfStdTime(*m.RecordTime) + n += 1 + l + sovDevice(uint64(l)) + } + l = len(m.OsType) + if l > 0 { + n += 1 + l + sovDevice(uint64(l)) + } + l = len(m.SerialNumber) + if l > 0 { + n += 1 + l + sovDevice(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func sovDevice(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozDevice(x uint64) (n int) { + return sovDevice(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *DeviceV1) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceV1: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceV1: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ResourceHeader", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthDevice + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ResourceHeader.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Spec", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthDevice + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Spec == nil { + m.Spec = &DeviceSpec{} + } + if err := m.Spec.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipDevice(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthDevice + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeviceSpec) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceSpec: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceSpec: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field OsType", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthDevice + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.OsType = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AssetTag", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthDevice + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AssetTag = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CreateTime", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthDevice + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.CreateTime == nil { + m.CreateTime = new(time.Time) + } + if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(m.CreateTime, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UpdateTime", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthDevice + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.UpdateTime == nil { + m.UpdateTime = new(time.Time) + } + if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(m.UpdateTime, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field EnrollStatus", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthDevice + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.EnrollStatus = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Credential", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthDevice + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Credential == nil { + m.Credential = &DeviceCredential{} + } + if err := m.Credential.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CollectedData", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthDevice + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CollectedData = append(m.CollectedData, &DeviceCollectedData{}) + if err := m.CollectedData[len(m.CollectedData)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipDevice(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthDevice + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeviceCredential) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceCredential: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceCredential: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthDevice + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Id = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PublicKeyDer", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthDevice + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PublicKeyDer = append(m.PublicKeyDer[:0], dAtA[iNdEx:postIndex]...) + if m.PublicKeyDer == nil { + m.PublicKeyDer = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipDevice(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthDevice + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeviceCollectedData) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceCollectedData: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceCollectedData: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CollectTime", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthDevice + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.CollectTime == nil { + m.CollectTime = new(time.Time) + } + if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(m.CollectTime, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RecordTime", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthDevice + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.RecordTime == nil { + m.RecordTime = new(time.Time) + } + if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(m.RecordTime, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field OsType", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthDevice + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.OsType = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SerialNumber", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthDevice + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SerialNumber = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipDevice(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthDevice + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipDevice(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowDevice + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowDevice + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowDevice + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthDevice + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupDevice + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthDevice + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthDevice = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowDevice = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupDevice = fmt.Errorf("proto: unexpected end of group") +) diff --git a/api/types/device_test.go b/api/types/device_test.go new file mode 100644 index 0000000000000..b31b054d44206 --- /dev/null +++ b/api/types/device_test.go @@ -0,0 +1,150 @@ +// Copyright 2023 Gravitational, Inc +// +// 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. + +package types + +import ( + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/gravitational/teleport/api/defaults" + devicepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1" +) + +// TestUnmarshalDevice tests that devices can be successfully +// unmarshalled from YAML and JSON. +func TestUnmarshalDevice(t *testing.T) { + for _, tc := range []struct { + desc string + input string + errorContains string + expected *DeviceV1 + }{ + { + desc: "success", + input: ` +{ + "kind": "device", + "version": "v1", + "metadata": { + "name": "xaa" + }, + "spec": { + "asset_tag": "mymachine", + "os_type": "macos", + "enroll_status": "enrolled" + } +}`, + expected: &DeviceV1{ + ResourceHeader: ResourceHeader{ + Kind: KindDevice, + Version: "v1", + Metadata: Metadata{ + Namespace: defaults.Namespace, + Name: "xaa", + }, + }, + Spec: &DeviceSpec{ + OsType: "macos", + AssetTag: "mymachine", + EnrollStatus: "enrolled", + }, + }, + }, + { + desc: "fail string as num", + errorContains: `cannot unmarshal number`, + input: ` +{ + "kind": "device", + "version": "v1", + "metadata": { + "name": "secretid" + }, + "spec": { + "asset_tag": 4, + "os_type": "macos", + "enroll_status": "enrolled" + } +}`, + }, + } { + t.Run(tc.desc, func(t *testing.T) { + out, err := UnmarshalDevice([]byte(tc.input)) + if tc.errorContains != "" { + require.ErrorContains(t, err, tc.errorContains, "error from UnmarshalDevice does not contain the expected string") + return + } + require.NoError(t, err, "UnmarshalDevice returned unexpected error") + require.Equal(t, tc.expected, out, "unmarshalled device does not match what was expected") + }) + } +} + +func TestDeviceConversions_toAndFrom(t *testing.T) { + t1 := time.UnixMilli(1680276526972000) // Fri Mar 31 2023 15:28:46 UTC + t11 := t1.Add(100 * time.Millisecond) + t2 := t1.Add(1 * time.Second) + t22 := t1.Add(100 * time.Millisecond) + + const osType = devicepb.OSType_OS_TYPE_MACOS + const assetTag = "llama14" + dev := &devicepb.Device{ + ApiVersion: "v1", + Id: "0af7c335-5f2c-4756-8266-9965a47ccbd3", + OsType: osType, + AssetTag: assetTag, + CreateTime: timestamppb.New(t1), + UpdateTime: timestamppb.New(t2), + EnrollStatus: devicepb.DeviceEnrollStatus_DEVICE_ENROLL_STATUS_ENROLLED, + Credential: &devicepb.DeviceCredential{ + Id: "557762f0-4cd4-4b75-aaee-575c57237c0b", + PublicKeyDer: []byte("insert public key here"), + }, + CollectedData: []*devicepb.DeviceCollectedData{ + { + CollectTime: timestamppb.New(t1), + RecordTime: timestamppb.New(t11), + OsType: osType, + SerialNumber: assetTag, + }, + { + CollectTime: timestamppb.New(t2), + RecordTime: timestamppb.New(t22), + OsType: osType, + SerialNumber: assetTag, + }, + }, + } + + gotRes := DeviceToResource(dev) + // Assert some of the more "unusual" or missing fields. + // We know other information isn't lost because of the conversion below, + // therefore it must be present in the resource. + assert.Equal(t, dev.ApiVersion, gotRes.Version, "resource.Version is not the ApiVersion") + assert.Equal(t, dev.Id, gotRes.Metadata.Name, "resource.Metadata.Name is not the Id") + assert.NotEmpty(t, gotRes.Metadata.Namespace, "resource.Metadata.Namespace") + + gotDev, err := DeviceFromResource(gotRes) + require.NoError(t, err, "DeviceFromResource failed") + if diff := cmp.Diff(dev, gotDev, protocmp.Transform()); diff != "" { + t.Errorf("DeviceFromResource mismatch (-want +got)\n%s", diff) + } +} diff --git a/api/types/installers/installer.sh.tmpl b/api/types/installers/installer.sh.tmpl index 3232f69d11132..d2b880410c5e6 100644 --- a/api/types/installers/installer.sh.tmpl +++ b/api/types/installers/installer.sh.tmpl @@ -1,5 +1,7 @@ #!/bin/sh +set -eu + on_ec2() { EC2_STATUS=$(curl -o /dev/null -w "%{http_code}" -m5 -sS "http://169.254.169.254/latest/meta-data") # EC2 metadata sometimes requires token access, so a successful hit may diff --git a/api/types/types.pb.go b/api/types/types.pb.go index 5371c39aa53b5..52b9642750a9e 100644 --- a/api/types/types.pb.go +++ b/api/types/types.pb.go @@ -766,6 +766,41 @@ func (OktaAssignmentActionTargetV1_OktaAssignmentActionTargetType) EnumDescripto return fileDescriptor_9198ee693835762e, []int{255, 0} } +// IntegrationStatus contains the current integration status. +type IntegrationSpecV1_IntegrationStatus int32 + +const ( + IntegrationSpecV1_INTEGRATION_STATUS_UNSPECIFIED IntegrationSpecV1_IntegrationStatus = 0 + // Integration is not running because it was just created or was disabled by the user. + IntegrationSpecV1_INTEGRATION_STATUS_PAUSED IntegrationSpecV1_IntegrationStatus = 1 + // Integration is running. + IntegrationSpecV1_INTEGRATION_STATUS_RUNNING IntegrationSpecV1_IntegrationStatus = 2 + // Integration has an error and that should be fixed before receiving requests. + IntegrationSpecV1_INTEGRATION_STATUS_ERROR IntegrationSpecV1_IntegrationStatus = 3 +) + +var IntegrationSpecV1_IntegrationStatus_name = map[int32]string{ + 0: "INTEGRATION_STATUS_UNSPECIFIED", + 1: "INTEGRATION_STATUS_PAUSED", + 2: "INTEGRATION_STATUS_RUNNING", + 3: "INTEGRATION_STATUS_ERROR", +} + +var IntegrationSpecV1_IntegrationStatus_value = map[string]int32{ + "INTEGRATION_STATUS_UNSPECIFIED": 0, + "INTEGRATION_STATUS_PAUSED": 1, + "INTEGRATION_STATUS_RUNNING": 2, + "INTEGRATION_STATUS_ERROR": 3, +} + +func (x IntegrationSpecV1_IntegrationStatus) String() string { + return proto.EnumName(IntegrationSpecV1_IntegrationStatus_name, int32(x)) +} + +func (IntegrationSpecV1_IntegrationStatus) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_9198ee693835762e, []int{257, 0} +} + type KeepAlive struct { // Name of the resource to keep alive. Name string `protobuf:"bytes,1,opt,name=Name,proto3" json:"server_name"` @@ -13995,6 +14030,173 @@ func (m *OktaAssignmentActionTargetV1) XXX_DiscardUnknown() { var xxx_messageInfo_OktaAssignmentActionTargetV1 proto.InternalMessageInfo +// IntegrationV1 represents a connection between Teleport and some other 3rd party system. +// This connection allows API access to that service from Teleport. +// Each Integration instance must have a SubKind defined which identifies the external system. +type IntegrationV1 struct { + // Header is the resource header. + ResourceHeader `protobuf:"bytes,1,opt,name=Header,proto3,embedded=Header" json:""` + // Spec is an Integration specification. + Spec IntegrationSpecV1 `protobuf:"bytes,2,opt,name=Spec,proto3" json:"spec"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *IntegrationV1) Reset() { *m = IntegrationV1{} } +func (*IntegrationV1) ProtoMessage() {} +func (*IntegrationV1) Descriptor() ([]byte, []int) { + return fileDescriptor_9198ee693835762e, []int{256} +} +func (m *IntegrationV1) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *IntegrationV1) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_IntegrationV1.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *IntegrationV1) XXX_Merge(src proto.Message) { + xxx_messageInfo_IntegrationV1.Merge(m, src) +} +func (m *IntegrationV1) XXX_Size() int { + return m.Size() +} +func (m *IntegrationV1) XXX_DiscardUnknown() { + xxx_messageInfo_IntegrationV1.DiscardUnknown(m) +} + +var xxx_messageInfo_IntegrationV1 proto.InternalMessageInfo + +// IntegrationSpecV1 contains properties of all the supported integrations. +type IntegrationSpecV1 struct { + // Types that are valid to be assigned to SubKindSpec: + // + // *IntegrationSpecV1_AWSOIDC + SubKindSpec isIntegrationSpecV1_SubKindSpec `protobuf_oneof:"SubKindSpec"` + // Status contains the current Integration Status + Status IntegrationSpecV1_IntegrationStatus `protobuf:"varint,2,opt,name=status,proto3,enum=types.IntegrationSpecV1_IntegrationStatus" json:"status,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *IntegrationSpecV1) Reset() { *m = IntegrationSpecV1{} } +func (m *IntegrationSpecV1) String() string { return proto.CompactTextString(m) } +func (*IntegrationSpecV1) ProtoMessage() {} +func (*IntegrationSpecV1) Descriptor() ([]byte, []int) { + return fileDescriptor_9198ee693835762e, []int{257} +} +func (m *IntegrationSpecV1) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *IntegrationSpecV1) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_IntegrationSpecV1.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *IntegrationSpecV1) XXX_Merge(src proto.Message) { + xxx_messageInfo_IntegrationSpecV1.Merge(m, src) +} +func (m *IntegrationSpecV1) XXX_Size() int { + return m.Size() +} +func (m *IntegrationSpecV1) XXX_DiscardUnknown() { + xxx_messageInfo_IntegrationSpecV1.DiscardUnknown(m) +} + +var xxx_messageInfo_IntegrationSpecV1 proto.InternalMessageInfo + +type isIntegrationSpecV1_SubKindSpec interface { + isIntegrationSpecV1_SubKindSpec() + MarshalTo([]byte) (int, error) + Size() int +} + +type IntegrationSpecV1_AWSOIDC struct { + AWSOIDC *AWSOIDCIntegrationSpecV1 `protobuf:"bytes,1,opt,name=AWSOIDC,proto3,oneof" json:"aws_oidc,omitempty"` +} + +func (*IntegrationSpecV1_AWSOIDC) isIntegrationSpecV1_SubKindSpec() {} + +func (m *IntegrationSpecV1) GetSubKindSpec() isIntegrationSpecV1_SubKindSpec { + if m != nil { + return m.SubKindSpec + } + return nil +} + +func (m *IntegrationSpecV1) GetAWSOIDC() *AWSOIDCIntegrationSpecV1 { + if x, ok := m.GetSubKindSpec().(*IntegrationSpecV1_AWSOIDC); ok { + return x.AWSOIDC + } + return nil +} + +// XXX_OneofWrappers is for the internal use of the proto package. +func (*IntegrationSpecV1) XXX_OneofWrappers() []interface{} { + return []interface{}{ + (*IntegrationSpecV1_AWSOIDC)(nil), + } +} + +// AWSOIDCIntegrationSpecV1 contains the spec properties for the AWS OIDC SubKind Integration. +type AWSOIDCIntegrationSpecV1 struct { + // RoleARN contains the Role ARN used to set up the Integration. + // This is the AWS Role that Teleport will use to issue tokens for API Calls. + RoleARN string `protobuf:"bytes,1,opt,name=RoleARN,proto3" json:"role_arn,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *AWSOIDCIntegrationSpecV1) Reset() { *m = AWSOIDCIntegrationSpecV1{} } +func (m *AWSOIDCIntegrationSpecV1) String() string { return proto.CompactTextString(m) } +func (*AWSOIDCIntegrationSpecV1) ProtoMessage() {} +func (*AWSOIDCIntegrationSpecV1) Descriptor() ([]byte, []int) { + return fileDescriptor_9198ee693835762e, []int{258} +} +func (m *AWSOIDCIntegrationSpecV1) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *AWSOIDCIntegrationSpecV1) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_AWSOIDCIntegrationSpecV1.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *AWSOIDCIntegrationSpecV1) XXX_Merge(src proto.Message) { + xxx_messageInfo_AWSOIDCIntegrationSpecV1.Merge(m, src) +} +func (m *AWSOIDCIntegrationSpecV1) XXX_Size() int { + return m.Size() +} +func (m *AWSOIDCIntegrationSpecV1) XXX_DiscardUnknown() { + xxx_messageInfo_AWSOIDCIntegrationSpecV1.DiscardUnknown(m) +} + +var xxx_messageInfo_AWSOIDCIntegrationSpecV1 proto.InternalMessageInfo + // HeadlessAuthentication holds data for an ongoing headless authentication attempt. type HeadlessAuthentication struct { // Header is the resource header. @@ -14018,7 +14220,7 @@ func (m *HeadlessAuthentication) Reset() { *m = HeadlessAuthentication{} func (m *HeadlessAuthentication) String() string { return proto.CompactTextString(m) } func (*HeadlessAuthentication) ProtoMessage() {} func (*HeadlessAuthentication) Descriptor() ([]byte, []int) { - return fileDescriptor_9198ee693835762e, []int{256} + return fileDescriptor_9198ee693835762e, []int{259} } func (m *HeadlessAuthentication) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -14068,6 +14270,7 @@ func init() { proto.RegisterEnum("types.ConnectionDiagnosticTrace_StatusType", ConnectionDiagnosticTrace_StatusType_name, ConnectionDiagnosticTrace_StatusType_value) proto.RegisterEnum("types.OktaAssignmentActionV1_OktaAssignmentActionStatus", OktaAssignmentActionV1_OktaAssignmentActionStatus_name, OktaAssignmentActionV1_OktaAssignmentActionStatus_value) proto.RegisterEnum("types.OktaAssignmentActionTargetV1_OktaAssignmentActionTargetType", OktaAssignmentActionTargetV1_OktaAssignmentActionTargetType_name, OktaAssignmentActionTargetV1_OktaAssignmentActionTargetType_value) + proto.RegisterEnum("types.IntegrationSpecV1_IntegrationStatus", IntegrationSpecV1_IntegrationStatus_name, IntegrationSpecV1_IntegrationStatus_value) proto.RegisterType((*KeepAlive)(nil), "types.KeepAlive") proto.RegisterType((*Metadata)(nil), "types.Metadata") proto.RegisterMapType((map[string]string)(nil), "types.Metadata.LabelsEntry") @@ -14347,1227 +14550,1240 @@ func init() { proto.RegisterType((*OktaAssignmentSpecV1)(nil), "types.OktaAssignmentSpecV1") proto.RegisterType((*OktaAssignmentActionV1)(nil), "types.OktaAssignmentActionV1") proto.RegisterType((*OktaAssignmentActionTargetV1)(nil), "types.OktaAssignmentActionTargetV1") + proto.RegisterType((*IntegrationV1)(nil), "types.IntegrationV1") + proto.RegisterType((*IntegrationSpecV1)(nil), "types.IntegrationSpecV1") + proto.RegisterType((*AWSOIDCIntegrationSpecV1)(nil), "types.AWSOIDCIntegrationSpecV1") proto.RegisterType((*HeadlessAuthentication)(nil), "types.HeadlessAuthentication") } func init() { proto.RegisterFile("teleport/legacy/types/types.proto", fileDescriptor_9198ee693835762e) } var fileDescriptor_9198ee693835762e = []byte{ - // 19413 bytes of a gzipped FileDescriptorProto + // 19577 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0xbd, 0x6d, 0x8c, 0x1c, 0x49, - 0x76, 0x20, 0xc6, 0xac, 0xea, 0x8f, 0xea, 0xd7, 0x5f, 0xd5, 0xd1, 0x4d, 0xb2, 0xc9, 0xf9, 0x68, - 0x4e, 0xce, 0x0c, 0x87, 0xe4, 0xcc, 0x90, 0xcb, 0xe6, 0x0e, 0x77, 0x67, 0xe7, 0xb3, 0xba, 0xaa, - 0xc8, 0xae, 0x61, 0x7f, 0xd4, 0x64, 0xf5, 0xc7, 0xce, 0x7e, 0x28, 0x37, 0xbb, 0x2a, 0xba, 0x3b, - 0x97, 0x55, 0x95, 0xa5, 0xcc, 0x2c, 0x92, 0xbd, 0x3a, 0xe1, 0x4e, 0x77, 0xa7, 0x5b, 0xcb, 0x3a, - 0x69, 0x25, 0x7b, 0x25, 0xed, 0xdd, 0xe9, 0x2c, 0x41, 0xf0, 0xd9, 0xf2, 0xf9, 0x24, 0x1c, 0xa4, - 0x1f, 0x3a, 0xe3, 0xe0, 0xf3, 0x09, 0x30, 0x84, 0xb5, 0x0d, 0xfb, 0x64, 0xc0, 0x86, 0xe1, 0x3d, - 0xa3, 0x6d, 0x49, 0x86, 0x61, 0xf0, 0xc7, 0xc1, 0x86, 0x60, 0x1b, 0xb7, 0x86, 0x0c, 0x23, 0x5e, - 0x44, 0x64, 0x46, 0x64, 0x65, 0x55, 0x57, 0x0f, 0x39, 0xbe, 0xe3, 0xc0, 0x7f, 0xc8, 0xae, 0x17, - 0xef, 0xbd, 0x88, 0x8c, 0x78, 0xf1, 0xe2, 0xc5, 0x8b, 0x17, 0x2f, 0xe0, 0xa5, 0x90, 0x36, 0x69, - 0xc7, 0xf3, 0xc3, 0x1b, 0x4d, 0x7a, 0xe0, 0xd4, 0x8f, 0x6e, 0x84, 0x47, 0x1d, 0x1a, 0xf0, 0x7f, - 0xaf, 0x77, 0x7c, 0x2f, 0xf4, 0xc8, 0x28, 0xfe, 0xb8, 0xb8, 0x70, 0xe0, 0x1d, 0x78, 0x08, 0xb9, - 0xc1, 0xfe, 0xe2, 0x85, 0x17, 0x97, 0x0e, 0x3c, 0xef, 0xa0, 0x49, 0x6f, 0xe0, 0xaf, 0xbd, 0xee, - 0xfe, 0x8d, 0xd0, 0x6d, 0xd1, 0x20, 0x74, 0x5a, 0x1d, 0x81, 0x70, 0x35, 0xaa, 0xc0, 0x09, 0x43, - 0x56, 0x12, 0xba, 0x5e, 0xfb, 0xc6, 0x83, 0x9b, 0xea, 0x4f, 0x81, 0xfa, 0x66, 0x7a, 0x5b, 0x1e, - 0xfa, 0x4e, 0xa7, 0x43, 0xfd, 0xf8, 0x0f, 0x8e, 0x6e, 0xfe, 0x41, 0x16, 0x26, 0xee, 0x51, 0xda, - 0x29, 0x34, 0xdd, 0x07, 0x94, 0xbc, 0x0c, 0x23, 0x1b, 0x4e, 0x8b, 0x2e, 0x1a, 0x97, 0x8c, 0x2b, - 0x13, 0x2b, 0xb3, 0x8f, 0x8f, 0x97, 0x26, 0x03, 0xea, 0x3f, 0xa0, 0xbe, 0xdd, 0x76, 0x5a, 0xd4, - 0xc2, 0x42, 0xf2, 0x3a, 0x4c, 0xb0, 0xff, 0x83, 0x8e, 0x53, 0xa7, 0x8b, 0x19, 0xc4, 0x9c, 0x7e, - 0x7c, 0xbc, 0x34, 0xd1, 0x96, 0x40, 0x2b, 0x2e, 0x27, 0x97, 0x61, 0x7c, 0x8d, 0x3a, 0x01, 0xad, - 0x94, 0x16, 0xb3, 0x97, 0x8c, 0x2b, 0xd9, 0x95, 0xa9, 0xc7, 0xc7, 0x4b, 0xb9, 0x26, 0x03, 0xd9, - 0x6e, 0xc3, 0x92, 0x85, 0xa4, 0x02, 0xe3, 0xe5, 0x47, 0x1d, 0xd7, 0xa7, 0xc1, 0xe2, 0xc8, 0x25, - 0xe3, 0xca, 0xe4, 0xf2, 0xc5, 0xeb, 0xbc, 0x53, 0xae, 0xcb, 0x4e, 0xb9, 0xbe, 0x25, 0x3b, 0x65, - 0x65, 0xfe, 0x87, 0xc7, 0x4b, 0x67, 0x1e, 0x1f, 0x2f, 0x8d, 0x53, 0x4e, 0xf2, 0x4b, 0xff, 0xe3, - 0x92, 0x61, 0x49, 0x7a, 0xf2, 0x2e, 0x8c, 0x6c, 0x1d, 0x75, 0xe8, 0xe2, 0xc4, 0x25, 0xe3, 0xca, - 0xcc, 0xf2, 0x8b, 0xd7, 0xf9, 0x30, 0x44, 0x1f, 0x19, 0xff, 0xc5, 0xb0, 0x56, 0x72, 0x8f, 0x8f, - 0x97, 0x46, 0x18, 0x8a, 0x85, 0x54, 0xe4, 0x4d, 0x18, 0x5b, 0xf5, 0x82, 0xb0, 0x52, 0x5a, 0x04, - 0xfc, 0xb4, 0xb3, 0x8f, 0x8f, 0x97, 0xe6, 0x0e, 0xbd, 0x20, 0xb4, 0xdd, 0xc6, 0x1b, 0x5e, 0xcb, - 0x0d, 0x69, 0xab, 0x13, 0x1e, 0x59, 0x02, 0xc9, 0x7c, 0x04, 0xd3, 0x1a, 0x3f, 0x32, 0x09, 0xe3, - 0xdb, 0x1b, 0xf7, 0x36, 0x36, 0x77, 0x37, 0xf2, 0x67, 0x48, 0x0e, 0x46, 0x36, 0x36, 0x4b, 0xe5, - 0xbc, 0x41, 0xc6, 0x21, 0x5b, 0xa8, 0x56, 0xf3, 0x19, 0x32, 0x05, 0xb9, 0x52, 0x61, 0xab, 0xb0, - 0x52, 0xa8, 0x95, 0xf3, 0x59, 0x32, 0x0f, 0xb3, 0xbb, 0x95, 0x8d, 0xd2, 0xe6, 0x6e, 0xcd, 0x2e, - 0x95, 0x6b, 0xf7, 0xb6, 0x36, 0xab, 0xf9, 0x11, 0x32, 0x03, 0x70, 0x6f, 0x7b, 0xa5, 0x6c, 0x6d, - 0x94, 0xb7, 0xca, 0xb5, 0xfc, 0x28, 0x59, 0x80, 0xbc, 0x24, 0xb1, 0x6b, 0x65, 0x6b, 0xa7, 0x52, - 0x2c, 0xe7, 0xc7, 0xcc, 0xef, 0x66, 0x21, 0xb7, 0x4e, 0x43, 0xa7, 0xe1, 0x84, 0x0e, 0x79, 0x5e, - 0x1b, 0x38, 0xfc, 0x26, 0x65, 0xc4, 0x5e, 0xee, 0x1d, 0xb1, 0xd1, 0xc7, 0xc7, 0x4b, 0xc6, 0x9b, - 0xea, 0x48, 0xbd, 0x03, 0x93, 0x25, 0x1a, 0xd4, 0x7d, 0xb7, 0xc3, 0xa4, 0x09, 0x47, 0x6b, 0x62, - 0xe5, 0xc2, 0xe3, 0xe3, 0xa5, 0xb3, 0x8d, 0x18, 0xac, 0xf4, 0x80, 0x8a, 0x4d, 0x2a, 0x30, 0xb6, - 0xe6, 0xec, 0xd1, 0x66, 0xb0, 0x38, 0x7a, 0x29, 0x7b, 0x65, 0x72, 0xf9, 0x39, 0xd1, 0xeb, 0xb2, - 0x81, 0xd7, 0x79, 0x69, 0xb9, 0x1d, 0xfa, 0x47, 0x2b, 0x0b, 0x8f, 0x8f, 0x97, 0xf2, 0x4d, 0x04, - 0xa8, 0x3d, 0xca, 0x51, 0x48, 0x2d, 0x96, 0x84, 0xb1, 0x13, 0x25, 0xe1, 0x85, 0x1f, 0x1e, 0x2f, - 0x19, 0x6c, 0x84, 0x84, 0x24, 0xc4, 0xfc, 0x74, 0x99, 0xb8, 0x04, 0x99, 0x4a, 0x69, 0x71, 0x1c, - 0x25, 0x30, 0xff, 0xf8, 0x78, 0x69, 0x4a, 0x1b, 0xcc, 0x4c, 0xa5, 0x74, 0xf1, 0x6d, 0x98, 0x54, - 0xda, 0x48, 0xf2, 0x90, 0xbd, 0x4f, 0x8f, 0x78, 0x7f, 0x5a, 0xec, 0x4f, 0xb2, 0x00, 0xa3, 0x0f, - 0x9c, 0x66, 0x57, 0x74, 0xa0, 0xc5, 0x7f, 0x7c, 0x25, 0xf3, 0x65, 0xc3, 0xfc, 0xb7, 0x46, 0x20, - 0x67, 0x79, 0x7c, 0x16, 0x92, 0xab, 0x30, 0x5a, 0x0b, 0x9d, 0x50, 0x0e, 0xc5, 0xfc, 0xe3, 0xe3, - 0xa5, 0x59, 0x36, 0x43, 0xa9, 0x52, 0x1f, 0xc7, 0x60, 0xa8, 0xd5, 0x43, 0x27, 0x90, 0x43, 0x82, - 0xa8, 0x1d, 0x06, 0x50, 0x51, 0x11, 0x83, 0x5c, 0x86, 0x91, 0x75, 0xaf, 0x41, 0xc5, 0xa8, 0x90, - 0xc7, 0xc7, 0x4b, 0x33, 0x2d, 0xaf, 0xa1, 0x22, 0x62, 0x39, 0x79, 0x03, 0x26, 0x8a, 0x5d, 0xdf, - 0xa7, 0x6d, 0x26, 0xc0, 0x23, 0x88, 0x3c, 0xf3, 0xf8, 0x78, 0x09, 0xea, 0x1c, 0xc8, 0xa6, 0x5c, - 0x8c, 0xc0, 0xba, 0xba, 0x16, 0x3a, 0x7e, 0x48, 0x1b, 0x8b, 0xa3, 0x43, 0x75, 0x35, 0x9b, 0x74, - 0x73, 0x01, 0x27, 0x49, 0x76, 0xb5, 0xe0, 0x44, 0x56, 0x61, 0xf2, 0xae, 0xef, 0xd4, 0x69, 0x95, - 0xfa, 0xae, 0xd7, 0xc0, 0x31, 0xcc, 0xae, 0x5c, 0x7e, 0x7c, 0xbc, 0x74, 0xee, 0x80, 0x81, 0xed, - 0x0e, 0xc2, 0x63, 0xea, 0x1f, 0x1f, 0x2f, 0xe5, 0x4a, 0x5d, 0x1f, 0x7b, 0xcf, 0x52, 0x49, 0xc9, - 0xb7, 0xd8, 0x90, 0x04, 0x21, 0x76, 0x2d, 0x6d, 0xe0, 0xe8, 0x0d, 0x6e, 0xa2, 0x29, 0x9a, 0x78, - 0xae, 0xe9, 0x04, 0xa1, 0xed, 0x73, 0xba, 0x44, 0x3b, 0x55, 0x96, 0x64, 0x13, 0x72, 0xb5, 0xfa, - 0x21, 0x6d, 0x74, 0x9b, 0x74, 0x31, 0x87, 0xec, 0xcf, 0x0b, 0xc1, 0x95, 0xe3, 0x29, 0x8b, 0x57, - 0x2e, 0x0a, 0xde, 0x24, 0x10, 0x10, 0xa5, 0xef, 0x23, 0x26, 0x5f, 0xc9, 0xfd, 0xe0, 0x37, 0x97, - 0xce, 0xfc, 0x95, 0xff, 0xe1, 0xd2, 0x19, 0xf3, 0x0f, 0x32, 0x90, 0x4f, 0x32, 0x21, 0xfb, 0x30, - 0xbd, 0xdd, 0x69, 0x38, 0x21, 0x2d, 0x36, 0x5d, 0xda, 0x0e, 0x03, 0x14, 0x92, 0xc1, 0xdf, 0xf4, - 0x8a, 0xa8, 0x77, 0xb1, 0x8b, 0x84, 0x76, 0x9d, 0x53, 0x26, 0xbe, 0x4a, 0x67, 0x1b, 0xd7, 0x53, - 0x43, 0xed, 0x1d, 0xa0, 0x84, 0x9d, 0xae, 0x1e, 0xae, 0xf7, 0xfb, 0xd4, 0x23, 0xd8, 0x0a, 0x01, - 0x6a, 0x37, 0xf6, 0x8e, 0x50, 0x32, 0x87, 0x17, 0x20, 0x46, 0x92, 0x22, 0x40, 0x0c, 0x6c, 0xfe, - 0xcf, 0x06, 0xcc, 0x58, 0x34, 0xf0, 0xba, 0x7e, 0x9d, 0xae, 0x52, 0xa7, 0x41, 0x7d, 0x26, 0xfe, - 0xf7, 0xdc, 0x76, 0x43, 0xcc, 0x29, 0x14, 0xff, 0xfb, 0x6e, 0x5b, 0x9d, 0xc2, 0x58, 0x4e, 0xbe, - 0x00, 0xe3, 0xb5, 0xee, 0x1e, 0xa2, 0xf2, 0x39, 0x75, 0x0e, 0x47, 0xac, 0xbb, 0x67, 0x27, 0xd0, - 0x25, 0x1a, 0xb9, 0x01, 0xe3, 0x3b, 0xd4, 0x0f, 0x62, 0x8d, 0x87, 0xfa, 0xfe, 0x01, 0x07, 0xa9, - 0x04, 0x02, 0x8b, 0xdc, 0x8d, 0xb5, 0xae, 0x58, 0xa9, 0x66, 0x13, 0xba, 0x2e, 0x16, 0x95, 0x96, - 0x80, 0xa8, 0xa2, 0x22, 0xb1, 0xcc, 0x5f, 0xce, 0x40, 0xbe, 0xe4, 0x84, 0xce, 0x9e, 0x13, 0x88, - 0xfe, 0xdc, 0xb9, 0xc5, 0xf4, 0xb8, 0xf2, 0xa1, 0xa8, 0xc7, 0x59, 0xcb, 0x3f, 0xf5, 0xe7, 0xbd, - 0x9a, 0xfc, 0xbc, 0x49, 0xb6, 0x6c, 0x8a, 0xcf, 0x8b, 0x3f, 0xea, 0xbd, 0x93, 0x3f, 0x2a, 0x2f, - 0x3e, 0x2a, 0x27, 0x3f, 0x2a, 0xfe, 0x14, 0xf2, 0x1e, 0x8c, 0xd4, 0x3a, 0xb4, 0x2e, 0x94, 0x88, - 0xd4, 0xfd, 0xfa, 0xc7, 0x31, 0x84, 0x9d, 0x5b, 0x2b, 0x53, 0x82, 0xcd, 0x48, 0xd0, 0xa1, 0x75, - 0x0b, 0xc9, 0x94, 0x49, 0xf3, 0x9f, 0x8d, 0xc1, 0x42, 0x1a, 0x19, 0x79, 0x4f, 0x5f, 0x9c, 0x78, - 0xf7, 0x3c, 0xd7, 0x77, 0x71, 0x5a, 0x34, 0xf4, 0xe5, 0xe9, 0x1a, 0xe4, 0xaa, 0x4c, 0x20, 0xeb, - 0x5e, 0x53, 0xf4, 0x1c, 0xd3, 0x8a, 0xb9, 0x8e, 0x84, 0x19, 0x56, 0x54, 0x4e, 0x9e, 0x83, 0xec, - 0xb6, 0x55, 0x11, 0xdd, 0x35, 0xf1, 0xf8, 0x78, 0x29, 0xdb, 0xf5, 0xdd, 0x45, 0xc3, 0x62, 0x50, - 0x72, 0x03, 0xc6, 0x8a, 0x85, 0x22, 0xf5, 0x43, 0xec, 0xa6, 0xa9, 0x95, 0xf3, 0x4c, 0x5a, 0xea, - 0x8e, 0x5d, 0xa7, 0x7e, 0xa8, 0x55, 0x2f, 0xd0, 0xc8, 0xeb, 0x90, 0x2d, 0xec, 0xd6, 0x44, 0xcf, - 0x80, 0xe8, 0x99, 0xc2, 0x6e, 0x6d, 0x65, 0x5a, 0x74, 0x44, 0xd6, 0x79, 0x18, 0x30, 0xee, 0x85, - 0xdd, 0x9a, 0x3a, 0x5a, 0x63, 0x03, 0x46, 0xeb, 0x0a, 0xe4, 0x98, 0xf5, 0xc1, 0x16, 0x78, 0x54, - 0x8a, 0x13, 0xdc, 0xa8, 0x3a, 0x14, 0x30, 0x2b, 0x2a, 0x25, 0x2f, 0x47, 0xc6, 0x4c, 0x2e, 0xe6, - 0x27, 0x8c, 0x19, 0x69, 0xc2, 0x90, 0x47, 0x30, 0x5d, 0x3a, 0x6a, 0x3b, 0x2d, 0xb7, 0x2e, 0x96, - 0xf0, 0x09, 0x5c, 0xc2, 0xaf, 0x0f, 0x18, 0xc6, 0xeb, 0x1a, 0x01, 0x5f, 0xd5, 0xa5, 0xf2, 0x5d, - 0x6c, 0xf0, 0x32, 0x3b, 0xb9, 0xc2, 0x2f, 0x1a, 0x96, 0x5e, 0x11, 0x9b, 0x4b, 0x52, 0x45, 0xa2, - 0xb5, 0x15, 0x8b, 0x9d, 0x04, 0xc7, 0x73, 0xc9, 0x17, 0x10, 0x75, 0x2e, 0x45, 0x8b, 0xee, 0x7b, - 0x90, 0xbd, 0x5b, 0xac, 0x2e, 0x4e, 0x22, 0x0f, 0x22, 0x78, 0xdc, 0x2d, 0x56, 0x8b, 0x4d, 0xaf, - 0xdb, 0xa8, 0x7d, 0xbc, 0xb6, 0x72, 0x5e, 0xb0, 0x99, 0x3e, 0xa8, 0x77, 0xb4, 0x16, 0x31, 0x3a, - 0x52, 0x86, 0x9c, 0xfc, 0xca, 0xc5, 0x29, 0xe4, 0x31, 0x97, 0xf8, 0xf8, 0x9d, 0x5b, 0x7c, 0xae, - 0x35, 0xc4, 0x6f, 0xb5, 0x15, 0x12, 0x87, 0xdc, 0x42, 0x29, 0x7b, 0x74, 0x54, 0x29, 0x05, 0x8b, - 0xd3, 0x97, 0xb2, 0x57, 0x26, 0x50, 0x3c, 0xe6, 0x3b, 0x0c, 0x66, 0xbb, 0x0d, 0xd5, 0xd8, 0x89, - 0x10, 0x2f, 0xee, 0x02, 0xe9, 0xed, 0xcc, 0x14, 0xf3, 0xe3, 0x75, 0xd5, 0xfc, 0x98, 0x5c, 0x3e, - 0x2b, 0x1a, 0x58, 0xf4, 0x5a, 0x2d, 0xa7, 0xdd, 0x40, 0xda, 0x9d, 0x65, 0xd5, 0x2a, 0x29, 0xc0, - 0x4c, 0xdc, 0xfa, 0x35, 0x37, 0x08, 0xc9, 0x0d, 0x98, 0x90, 0x10, 0xb6, 0xf2, 0x64, 0x53, 0xbf, - 0xd3, 0x8a, 0x71, 0xcc, 0x3f, 0xca, 0x00, 0xc4, 0x25, 0xcf, 0xa8, 0x72, 0xfa, 0x92, 0xa6, 0x9c, - 0xce, 0x26, 0xa5, 0xba, 0xaf, 0x5a, 0x22, 0x1f, 0xc0, 0x18, 0xb3, 0xd3, 0xba, 0xd2, 0x0e, 0x3d, - 0x9f, 0x24, 0xc5, 0xc2, 0x9d, 0x5b, 0x2b, 0x33, 0x82, 0x78, 0x2c, 0x40, 0x88, 0x25, 0xc8, 0x14, - 0xbd, 0xf6, 0x7b, 0xa3, 0xf1, 0x60, 0x08, 0x8d, 0x76, 0x45, 0x51, 0x49, 0x46, 0x3c, 0x89, 0xa5, - 0x4a, 0x52, 0x14, 0xd2, 0x05, 0xae, 0x90, 0x78, 0xa7, 0x8e, 0x0b, 0x85, 0x94, 0x54, 0x47, 0xbc, - 0x03, 0x4f, 0x54, 0x47, 0x9d, 0xe4, 0x5c, 0x1f, 0x41, 0x31, 0xb8, 0x92, 0xda, 0x2b, 0x69, 0xb3, - 0xfc, 0xd2, 0x49, 0xb3, 0x3c, 0x39, 0xc7, 0x6f, 0xf5, 0x53, 0x80, 0x67, 0xe5, 0x94, 0x74, 0x1e, - 0xaa, 0xe4, 0xa8, 0x08, 0xdf, 0xe1, 0xf3, 0x79, 0xac, 0xef, 0x7c, 0x3e, 0x9b, 0x3a, 0x9f, 0xf9, - 0x6c, 0x7e, 0x07, 0x46, 0x0b, 0xdf, 0xe9, 0xfa, 0x54, 0x18, 0x8c, 0x53, 0xb2, 0x4e, 0x06, 0x8b, - 0x14, 0xc1, 0xac, 0xc3, 0x7e, 0xaa, 0x86, 0x36, 0x96, 0xb3, 0x9a, 0xb7, 0xd6, 0x6a, 0xc2, 0x18, - 0x24, 0x89, 0x6e, 0xd9, 0x5a, 0x53, 0x9a, 0x1d, 0x6a, 0x5f, 0xcd, 0xa8, 0xc8, 0x0d, 0xc8, 0x14, - 0x4a, 0xb8, 0xef, 0x9c, 0x5c, 0x9e, 0x90, 0xd5, 0x96, 0x56, 0x16, 0x04, 0xc9, 0x94, 0xa3, 0x6d, - 0x3a, 0x0a, 0x25, 0xb2, 0x02, 0xa3, 0xeb, 0x47, 0xb5, 0x8f, 0xd7, 0x84, 0xf6, 0x9b, 0x97, 0x72, - 0xcd, 0x60, 0x9b, 0xb8, 0x74, 0x05, 0x71, 0x8b, 0x5b, 0x47, 0xc1, 0x4f, 0x36, 0xd5, 0x16, 0x23, - 0xda, 0x67, 0xa7, 0x40, 0xfe, 0x5d, 0xd5, 0x40, 0x11, 0xb2, 0xce, 0xb6, 0xc7, 0x42, 0xe2, 0x8c, - 0xd8, 0x5c, 0xea, 0x91, 0xb8, 0x48, 0xde, 0xae, 0xf2, 0xd1, 0xcf, 0xf4, 0x8c, 0xfe, 0xa4, 0xb2, - 0xfc, 0xf1, 0x31, 0x8f, 0xfa, 0x22, 0xfb, 0xa9, 0xfb, 0x82, 0x7c, 0x00, 0x53, 0xeb, 0x4e, 0xdb, - 0x39, 0xa0, 0x8d, 0xed, 0x80, 0x99, 0xbd, 0x23, 0xa8, 0x85, 0x99, 0x9d, 0x70, 0xbe, 0xc5, 0xe1, - 0x76, 0x37, 0xd0, 0xac, 0x5a, 0x4b, 0x23, 0x20, 0x37, 0xa5, 0xec, 0x8c, 0xa6, 0xc8, 0x8e, 0x5c, - 0xb2, 0x47, 0x51, 0x76, 0x84, 0xc4, 0x98, 0x3f, 0x18, 0xc3, 0x6f, 0x24, 0x6f, 0xc0, 0x98, 0x45, - 0x0f, 0x62, 0xeb, 0x04, 0x77, 0xb9, 0x3e, 0x42, 0xd4, 0x8e, 0xe1, 0x38, 0xb8, 0xf4, 0xd1, 0x46, - 0x70, 0xe8, 0xee, 0x87, 0xa2, 0x77, 0xa2, 0xa5, 0x4f, 0x80, 0x95, 0xa5, 0x4f, 0x40, 0xb4, 0xa5, - 0x4f, 0xc0, 0xd8, 0xfc, 0xb2, 0x4a, 0x35, 0xd1, 0x69, 0xb2, 0x87, 0xad, 0x92, 0x22, 0xa8, 0xbe, - 0xb6, 0xf2, 0x30, 0x6c, 0x72, 0x1b, 0x26, 0x0a, 0xf5, 0xba, 0xd7, 0x55, 0xb6, 0x89, 0x8b, 0x8f, - 0x8f, 0x97, 0x16, 0x1c, 0x0e, 0xd4, 0x5d, 0x1d, 0x31, 0x2a, 0xa9, 0xc1, 0x64, 0x99, 0xed, 0xad, - 0xdc, 0xa2, 0x53, 0x3f, 0x94, 0x9d, 0x24, 0x67, 0x89, 0x52, 0x12, 0xd9, 0xfa, 0x67, 0x29, 0x02, - 0xeb, 0x0c, 0xa8, 0xfa, 0x0e, 0x14, 0x5c, 0xb2, 0x05, 0x93, 0x35, 0x5a, 0xf7, 0x69, 0x58, 0x0b, - 0x3d, 0x9f, 0x26, 0x26, 0xbd, 0x52, 0xb2, 0xf2, 0xa2, 0xdc, 0xde, 0x05, 0x08, 0xb4, 0x03, 0x06, - 0x55, 0xb9, 0x2a, 0xc8, 0xdc, 0x4e, 0x6f, 0x79, 0xfe, 0x51, 0x69, 0x45, 0x28, 0x82, 0x78, 0xd5, - 0xe0, 0x60, 0xd5, 0x4e, 0x67, 0x90, 0xc6, 0x9e, 0x6e, 0xa7, 0x73, 0x2c, 0x1c, 0xa9, 0x52, 0x0d, - 0xd7, 0x6b, 0xa1, 0x16, 0x66, 0xe3, 0x5e, 0x46, 0xb0, 0x32, 0x52, 0x8d, 0x00, 0x57, 0x7b, 0x6d, - 0xa4, 0x04, 0x16, 0xe9, 0x00, 0x91, 0xa3, 0xc6, 0x6d, 0xa9, 0x26, 0x0d, 0x02, 0xa1, 0x2d, 0x2e, - 0x24, 0x06, 0x3f, 0x46, 0x58, 0x79, 0x55, 0x30, 0x7f, 0x41, 0x8a, 0x81, 0xd8, 0x9a, 0xb1, 0x42, - 0xa5, 0x9e, 0x14, 0xde, 0xe4, 0x6d, 0x80, 0xf2, 0xa3, 0x90, 0xfa, 0x6d, 0xa7, 0x19, 0xf9, 0xb3, - 0xd0, 0xa3, 0x43, 0x05, 0x54, 0x1f, 0x68, 0x05, 0x99, 0x14, 0x61, 0xba, 0x10, 0x04, 0xdd, 0x16, - 0xb5, 0xbc, 0x26, 0x2d, 0x58, 0x1b, 0x68, 0x5b, 0x4d, 0xac, 0xbc, 0xf0, 0xf8, 0x78, 0xe9, 0x82, - 0x83, 0x05, 0xb6, 0xef, 0x35, 0xa9, 0xed, 0xf8, 0xaa, 0x74, 0xeb, 0x34, 0xe6, 0x4f, 0x69, 0x23, - 0xcb, 0xa4, 0xee, 0x1e, 0x3d, 0xaa, 0xfa, 0x74, 0xdf, 0x7d, 0x24, 0x26, 0x09, 0x4a, 0xdd, 0x7d, - 0x7a, 0x64, 0x77, 0x10, 0xaa, 0x4a, 0x5d, 0x84, 0x4a, 0xbe, 0x08, 0xb9, 0x7b, 0xeb, 0xb5, 0x7b, - 0xf4, 0xa8, 0x52, 0x12, 0xab, 0x20, 0x27, 0x6b, 0x05, 0x36, 0x23, 0xd5, 0xbe, 0x21, 0xc2, 0x34, - 0x57, 0xe2, 0x19, 0xc6, 0x6a, 0x2e, 0x36, 0xbb, 0x41, 0x48, 0xfd, 0x4a, 0x49, 0xad, 0xb9, 0xce, - 0x81, 0x09, 0x79, 0x8f, 0x50, 0xcd, 0x7f, 0x6e, 0xe0, 0xec, 0x62, 0x1d, 0x59, 0x69, 0xb3, 0x6d, - 0x6b, 0x9d, 0x46, 0x0c, 0xb0, 0x23, 0x5d, 0x01, 0x4d, 0x74, 0x64, 0x8c, 0xac, 0x57, 0x9d, 0x19, - 0xba, 0x6a, 0x56, 0xa5, 0xdc, 0x04, 0x0b, 0xdf, 0xa9, 0xa8, 0xd2, 0x17, 0xd0, 0x44, 0x95, 0x31, - 0x32, 0xb9, 0x0c, 0xe3, 0x95, 0xc2, 0x7a, 0xa1, 0x1b, 0x1e, 0xe2, 0xdc, 0xce, 0x71, 0xcb, 0xc2, - 0x75, 0x5a, 0xb6, 0xd3, 0x0d, 0x0f, 0x2d, 0x59, 0x68, 0xfe, 0xa1, 0x11, 0x8b, 0x36, 0xdb, 0x62, - 0x2b, 0x1e, 0x44, 0xdc, 0x62, 0xb3, 0x2d, 0x84, 0xba, 0xc5, 0x46, 0x5f, 0xa2, 0x05, 0xa4, 0xd8, - 0x0d, 0x42, 0xaf, 0x55, 0x6e, 0x37, 0x3a, 0x9e, 0xdb, 0x0e, 0x91, 0x8a, 0x7f, 0x98, 0xf9, 0xf8, - 0x78, 0xe9, 0xc5, 0x3a, 0x96, 0xda, 0x54, 0x14, 0xdb, 0x09, 0x2e, 0x29, 0xd4, 0x4f, 0xf0, 0xad, - 0xe6, 0x7f, 0x9e, 0xd1, 0x54, 0x12, 0x6b, 0x9e, 0x45, 0x3b, 0x4d, 0xb7, 0x8e, 0x1b, 0x83, 0xbb, - 0xbe, 0xd7, 0xed, 0x44, 0x23, 0x86, 0xcd, 0xf3, 0xe3, 0x52, 0xfb, 0x80, 0x15, 0xeb, 0xbc, 0x53, - 0xa8, 0xc9, 0x87, 0x30, 0xc5, 0x56, 0x07, 0xf1, 0x33, 0x58, 0xcc, 0xe0, 0xaa, 0xf2, 0x3c, 0x3a, - 0x4b, 0x02, 0xea, 0x47, 0x6c, 0xb4, 0x65, 0x45, 0xa5, 0x20, 0x0d, 0x58, 0xdc, 0xf2, 0x9d, 0x76, - 0xe0, 0x86, 0xe5, 0x76, 0xdd, 0x3f, 0xc2, 0xd5, 0xac, 0xdc, 0x76, 0xf6, 0x9a, 0xb4, 0x81, 0x9f, - 0x9b, 0x5b, 0xb9, 0xf2, 0xf8, 0x78, 0xe9, 0x95, 0x90, 0xe3, 0xd8, 0x34, 0x42, 0xb2, 0x29, 0xc7, - 0x52, 0x38, 0xf7, 0xe5, 0xc4, 0x56, 0x3f, 0xd9, 0xad, 0xe8, 0x00, 0x1f, 0x89, 0x76, 0xc9, 0xe7, - 0xa3, 0xd1, 0x60, 0x6a, 0x46, 0x6d, 0xa6, 0x4a, 0x60, 0xfe, 0x9f, 0x46, 0xac, 0x34, 0xc9, 0xbb, - 0x30, 0x29, 0xa4, 0x51, 0x91, 0x8b, 0x8b, 0x4c, 0xfd, 0x4a, 0xd1, 0x4d, 0x8c, 0xac, 0x8a, 0xce, - 0x76, 0x03, 0x85, 0xe2, 0x9a, 0x22, 0x1b, 0xb8, 0x1b, 0x70, 0xea, 0xcd, 0x24, 0x95, 0x44, 0x63, - 0x42, 0xb0, 0xb5, 0x56, 0xd3, 0x7b, 0x05, 0x85, 0x20, 0x6c, 0x06, 0x29, 0xdd, 0xa0, 0x20, 0x3f, - 0xf9, 0x87, 0xff, 0x77, 0x46, 0x9a, 0x6e, 0x26, 0x2b, 0x30, 0xbd, 0xeb, 0xf9, 0xf7, 0x71, 0x7c, - 0x95, 0x4e, 0xc0, 0x91, 0x7f, 0x28, 0x0b, 0x92, 0x1f, 0xa4, 0x93, 0xa8, 0x6d, 0x53, 0x7a, 0x43, - 0x6f, 0x5b, 0x82, 0x83, 0x46, 0xc0, 0xc6, 0x21, 0xe2, 0x18, 0xcd, 0x0e, 0x1c, 0x87, 0xb8, 0x09, - 0x9a, 0x08, 0xab, 0xe8, 0xe6, 0x5f, 0x31, 0x60, 0x52, 0x31, 0x9c, 0x99, 0x3a, 0xaa, 0xfa, 0xde, - 0xb7, 0x69, 0x3d, 0xd4, 0x35, 0x61, 0x87, 0x03, 0x13, 0xea, 0x28, 0x42, 0x4d, 0x68, 0xc0, 0xcc, - 0x29, 0x34, 0xa0, 0xf9, 0xbf, 0x1b, 0xc2, 0xa8, 0x1a, 0x5a, 0xc7, 0xe8, 0xfa, 0x20, 0x73, 0x1a, - 0xdd, 0xf7, 0x21, 0x8c, 0x5a, 0xb4, 0xe1, 0x06, 0xc2, 0x20, 0x9a, 0x53, 0x0d, 0x38, 0x2c, 0x88, - 0x6d, 0x48, 0x9f, 0xfd, 0x54, 0x6d, 0x48, 0x2c, 0x67, 0x2b, 0x5f, 0x25, 0xb8, 0xd3, 0xa4, 0x8f, - 0x5c, 0x2e, 0x09, 0x42, 0x87, 0xe2, 0xca, 0xe7, 0x06, 0xf6, 0x3e, 0x2b, 0x11, 0x4b, 0xb0, 0x3a, - 0xea, 0x1a, 0x8d, 0xf9, 0x09, 0x40, 0x5c, 0x25, 0xb9, 0x07, 0x79, 0x31, 0x37, 0xdc, 0xf6, 0x41, - 0xd5, 0x6b, 0xba, 0x75, 0x61, 0x99, 0xaf, 0x2c, 0x3d, 0x3e, 0x5e, 0x7a, 0xae, 0x1e, 0x95, 0xd9, - 0x1d, 0x2c, 0x54, 0xf8, 0xf6, 0x10, 0x9a, 0xff, 0x7e, 0x86, 0xed, 0x32, 0x58, 0x1f, 0xdd, 0xa3, - 0x47, 0xa1, 0xb3, 0x77, 0xc7, 0x6d, 0x52, 0x75, 0x49, 0xba, 0x8f, 0x50, 0x7b, 0xdf, 0xd5, 0x5c, - 0xd4, 0x0a, 0x32, 0xb9, 0x05, 0xb9, 0x7b, 0xfe, 0xde, 0x5b, 0x48, 0x98, 0x89, 0xf6, 0x8d, 0xf3, - 0xf7, 0xfd, 0xbd, 0xb7, 0x92, 0x64, 0x11, 0x22, 0x31, 0x61, 0xac, 0xe4, 0xb5, 0x1c, 0x57, 0xee, - 0xd5, 0x81, 0x6d, 0x78, 0x1b, 0x08, 0xb1, 0x44, 0x09, 0xdb, 0xa9, 0xd6, 0xaa, 0x1b, 0x62, 0xfa, - 0xe1, 0x4e, 0x35, 0xe8, 0xb4, 0x2d, 0x06, 0x63, 0x75, 0xae, 0x95, 0x0a, 0x55, 0xdc, 0x39, 0x8c, - 0xc6, 0x75, 0x36, 0x1b, 0x4e, 0x27, 0xb9, 0x77, 0x88, 0x10, 0xc9, 0x7b, 0x30, 0x79, 0xaf, 0x54, - 0x5c, 0xf5, 0x02, 0x3e, 0x75, 0xc6, 0xe2, 0xa9, 0x73, 0xbf, 0x51, 0xb7, 0xd1, 0x8f, 0x95, 0xd4, - 0x41, 0x0a, 0xbe, 0xf9, 0x3b, 0x06, 0x4c, 0x2a, 0x5b, 0x37, 0xf2, 0x45, 0x71, 0x88, 0x62, 0xe0, - 0xc1, 0xe0, 0xb9, 0xde, 0xcd, 0x1d, 0x2b, 0xe5, 0x7e, 0x8d, 0x96, 0xd7, 0xa0, 0xe2, 0x48, 0x25, - 0xde, 0xf1, 0x64, 0x86, 0xd9, 0xf1, 0xbc, 0x0d, 0xc0, 0x65, 0x00, 0x9b, 0xac, 0xac, 0x65, 0xca, - 0x41, 0xaa, 0x3a, 0x2e, 0x31, 0xb2, 0x69, 0xc1, 0x94, 0xba, 0xdb, 0x61, 0xea, 0x47, 0x38, 0x86, - 0x85, 0x97, 0x44, 0x51, 0x3f, 0x82, 0x5b, 0xaf, 0xa3, 0x5a, 0x27, 0x31, 0xff, 0xaa, 0x11, 0x4f, - 0xdc, 0x9d, 0x9b, 0xe4, 0x1d, 0x18, 0xe3, 0x2e, 0x75, 0x71, 0xf2, 0x70, 0x36, 0xb2, 0x3b, 0x55, - 0x7f, 0x3b, 0x77, 0x87, 0xfc, 0x31, 0x3f, 0x5a, 0x3b, 0x63, 0x09, 0x92, 0xc8, 0x93, 0xa2, 0x6f, - 0x20, 0x25, 0x77, 0xf4, 0x19, 0xdc, 0x4c, 0xf3, 0xa4, 0x98, 0xff, 0x4e, 0x16, 0x66, 0x74, 0x34, - 0xd5, 0xef, 0x6e, 0x0c, 0xe5, 0x77, 0xff, 0x10, 0x72, 0xec, 0xcb, 0xdc, 0x3a, 0x95, 0x0b, 0xf0, - 0x2b, 0xe8, 0x5f, 0x12, 0x30, 0x41, 0x12, 0x76, 0x8e, 0x7e, 0x7c, 0xbc, 0x04, 0xb5, 0xa3, 0x20, - 0xa4, 0x2d, 0x66, 0x86, 0x5a, 0x11, 0x15, 0x59, 0x56, 0xdc, 0xa6, 0xd9, 0x78, 0x4d, 0x92, 0x6e, - 0x53, 0x55, 0x02, 0x23, 0x07, 0xea, 0x9b, 0x30, 0xc6, 0x4c, 0xa5, 0x68, 0x97, 0x84, 0xad, 0x64, - 0x56, 0x54, 0xe2, 0x34, 0x98, 0x23, 0x91, 0x5d, 0xc8, 0xad, 0x39, 0x41, 0x58, 0xa3, 0xb4, 0x3d, - 0xc4, 0x89, 0xda, 0x92, 0xe8, 0xaa, 0x79, 0x3c, 0xae, 0x0a, 0x28, 0x6d, 0x27, 0x8e, 0x44, 0x22, - 0x66, 0xe4, 0x9b, 0x00, 0x45, 0xaf, 0x1d, 0xfa, 0x5e, 0x73, 0xcd, 0x3b, 0x58, 0x1c, 0x43, 0xa7, - 0xcd, 0x8b, 0x89, 0x01, 0x88, 0x11, 0xb8, 0xab, 0x26, 0xda, 0x83, 0xd5, 0x79, 0x81, 0xdd, 0xf4, - 0x0e, 0x54, 0xc9, 0x8b, 0xf1, 0xcd, 0x1f, 0x67, 0xe0, 0x7c, 0x1f, 0x36, 0x4c, 0x69, 0xe3, 0xa2, - 0xaa, 0x28, 0xed, 0xc4, 0x5a, 0xca, 0x0f, 0xce, 0xf9, 0x11, 0x2b, 0x93, 0x8d, 0x91, 0xf4, 0x23, - 0x56, 0xf2, 0x11, 0x8c, 0xb0, 0x8f, 0x1f, 0xe2, 0xa8, 0x48, 0x6e, 0xa8, 0x66, 0x42, 0x57, 0x1d, - 0x18, 0xec, 0x14, 0xe4, 0x41, 0xbe, 0x08, 0xd9, 0xad, 0xad, 0x35, 0x1c, 0x95, 0x2c, 0x1a, 0x76, - 0xd3, 0x61, 0xd8, 0xd4, 0x0e, 0x15, 0xa7, 0x19, 0xed, 0xf5, 0xe8, 0x64, 0x91, 0xa1, 0x93, 0xaf, - 0x26, 0x8e, 0xa9, 0xaf, 0x0d, 0xee, 0xc2, 0xe1, 0x4f, 0xad, 0x9f, 0xe4, 0xf8, 0xd8, 0x8f, 0x27, - 0xc7, 0x1d, 0xb7, 0x19, 0x52, 0x9f, 0x5c, 0xe4, 0xb2, 0x1e, 0xef, 0x56, 0xac, 0xe8, 0x37, 0x59, - 0x8c, 0x27, 0x0e, 0xe7, 0x14, 0xcd, 0x90, 0x6b, 0xca, 0x0c, 0xc9, 0xe2, 0x0c, 0x99, 0xe9, 0x37, - 0x17, 0xcc, 0x9f, 0xc9, 0xc8, 0x2a, 0x76, 0x96, 0x9f, 0x51, 0xbf, 0xee, 0x5b, 0x9a, 0x5f, 0x77, - 0x3e, 0xf2, 0x17, 0x44, 0xa7, 0x14, 0xcb, 0x27, 0x1c, 0x36, 0xbd, 0x0d, 0x53, 0xb2, 0x0b, 0xd0, - 0x3d, 0x7e, 0x15, 0xc6, 0xe5, 0x71, 0x29, 0x77, 0x8e, 0xcf, 0x6a, 0x3c, 0x77, 0x96, 0x2d, 0x59, - 0x6e, 0xfe, 0xed, 0x51, 0x49, 0xcb, 0x6b, 0x62, 0x5d, 0x58, 0x68, 0x34, 0x7c, 0xb5, 0x0b, 0x9d, - 0x46, 0xc3, 0xb7, 0x10, 0xca, 0xd6, 0x84, 0x6a, 0x77, 0xaf, 0xe9, 0xd6, 0x11, 0x47, 0xb1, 0x67, - 0x3a, 0x08, 0xb5, 0x19, 0xaa, 0x3a, 0x33, 0x63, 0x64, 0xed, 0xac, 0x27, 0x3b, 0xf0, 0xac, 0xe7, - 0x27, 0x60, 0xa2, 0xd8, 0x6a, 0x68, 0x6e, 0x5d, 0x33, 0xa5, 0x53, 0xae, 0x47, 0x48, 0x5c, 0xac, - 0x9f, 0x17, 0x7d, 0xb4, 0x50, 0x6f, 0x35, 0x7a, 0x9d, 0xb9, 0x31, 0x4b, 0xed, 0xb0, 0x66, 0xf4, - 0x49, 0x0e, 0x6b, 0x6e, 0xc3, 0xc4, 0x76, 0x40, 0xb7, 0xba, 0xed, 0x36, 0x6d, 0xe2, 0x9a, 0x9e, - 0xe3, 0x26, 0x68, 0x37, 0xa0, 0x76, 0x88, 0x50, 0xb5, 0x01, 0x11, 0xaa, 0x2a, 0x56, 0xe3, 0x03, - 0xc4, 0xea, 0x8b, 0x30, 0x52, 0xe8, 0x74, 0xe4, 0x29, 0x56, 0xe4, 0x73, 0xec, 0x74, 0x70, 0x06, - 0xcf, 0x38, 0x9d, 0x8e, 0x7e, 0x26, 0x85, 0xd8, 0x78, 0x76, 0x43, 0xa9, 0x8f, 0x03, 0x34, 0x19, - 0xdb, 0x27, 0x1d, 0x4a, 0xfd, 0xe4, 0xf0, 0x44, 0x88, 0xda, 0x81, 0xcf, 0xd4, 0xb0, 0x07, 0x3e, - 0x35, 0x98, 0xd1, 0x87, 0xe0, 0x29, 0xf8, 0x6a, 0x3f, 0x1a, 0xc9, 0xe5, 0xf2, 0x13, 0x1f, 0x8d, - 0xe4, 0x20, 0x3f, 0x69, 0x7e, 0x37, 0x03, 0x93, 0x85, 0x4e, 0xe7, 0x19, 0x3f, 0x53, 0xfe, 0xb2, - 0x36, 0xbd, 0xcf, 0xc5, 0xc3, 0x78, 0x8a, 0xe3, 0xe4, 0xdf, 0xcd, 0xc0, 0x6c, 0x82, 0x42, 0x6d, - 0xbd, 0x31, 0xe4, 0x19, 0x6b, 0x66, 0xc8, 0x33, 0xd6, 0x6c, 0xff, 0x33, 0x56, 0x75, 0xf2, 0x8c, - 0x3c, 0xc9, 0xe4, 0x79, 0x0d, 0xb2, 0x85, 0x4e, 0x27, 0xe9, 0x9e, 0xee, 0x74, 0x76, 0x6e, 0x71, - 0x83, 0xdb, 0xe9, 0x74, 0x2c, 0x86, 0xa1, 0xc9, 0xe6, 0xd8, 0x90, 0xb2, 0x69, 0xbe, 0x09, 0x13, - 0xc8, 0x0b, 0xf5, 0xe1, 0x25, 0x31, 0x91, 0xb8, 0x32, 0xd4, 0xea, 0xe2, 0x93, 0xc6, 0xfc, 0xbf, - 0xd9, 0xce, 0x8e, 0xfd, 0x7e, 0x46, 0x65, 0x6c, 0x59, 0x93, 0xb1, 0xbc, 0x22, 0x63, 0xc3, 0x48, - 0xd7, 0x2f, 0x8f, 0x60, 0x6f, 0x09, 0xb9, 0x12, 0xa7, 0x74, 0x46, 0xca, 0x29, 0xdd, 0x13, 0xa8, - 0xff, 0xfb, 0xc9, 0xf3, 0xba, 0x2c, 0x0e, 0xc6, 0xcb, 0xc9, 0xa6, 0x3e, 0x95, 0xa3, 0xba, 0x55, - 0x20, 0x95, 0x76, 0x40, 0xeb, 0x5d, 0x9f, 0xd6, 0xee, 0xbb, 0x9d, 0x1d, 0xea, 0xbb, 0xfb, 0x47, - 0x62, 0xfb, 0x8b, 0x1a, 0xda, 0x15, 0xa5, 0x76, 0x70, 0xdf, 0xed, 0xb0, 0xbd, 0x87, 0xbb, 0x7f, - 0x64, 0xa5, 0xd0, 0x90, 0x0f, 0x60, 0xdc, 0xa2, 0x0f, 0x7d, 0x37, 0x94, 0x67, 0x04, 0x33, 0xd1, - 0x3e, 0x03, 0xa1, 0xdc, 0x8e, 0xf6, 0xf9, 0x0f, 0x75, 0xfc, 0x45, 0x39, 0x59, 0xe6, 0xe7, 0x46, - 0xfc, 0x2c, 0x60, 0x3a, 0xfe, 0xda, 0xc2, 0x6e, 0x6d, 0x65, 0xae, 0xcf, 0xa1, 0xe1, 0x55, 0x18, - 0x45, 0x37, 0x87, 0x58, 0x1d, 0x30, 0x9c, 0xae, 0xce, 0x00, 0xea, 0x1e, 0x1f, 0x31, 0x3e, 0xbb, - 0x33, 0xb3, 0xef, 0x8f, 0xe2, 0xfc, 0x3c, 0x21, 0x1e, 0x73, 0xc0, 0x89, 0xae, 0x2e, 0x2b, 0xd9, - 0xd3, 0xc8, 0xca, 0x0e, 0x4c, 0xd5, 0x98, 0x96, 0xd0, 0x8f, 0x76, 0x9f, 0x8f, 0x3b, 0xef, 0xba, - 0x5a, 0x3c, 0xc8, 0xa8, 0xd5, 0xf8, 0x10, 0x3b, 0x29, 0x83, 0xdc, 0x76, 0x7e, 0x41, 0x61, 0x9c, - 0x22, 0x7d, 0x91, 0x3a, 0xab, 0xf3, 0xce, 0x3a, 0xb5, 0xdc, 0x8d, 0x3d, 0x99, 0xdc, 0x8d, 0x7f, - 0x2a, 0xb9, 0x4b, 0x04, 0xc1, 0xe6, 0x4e, 0x13, 0x04, 0x7b, 0xf1, 0x03, 0x98, 0xeb, 0xe9, 0xe1, - 0xd3, 0xec, 0x04, 0x3e, 0x3b, 0xb1, 0xfc, 0x69, 0x50, 0x66, 0x56, 0xce, 0xa2, 0x0d, 0xd7, 0xa7, - 0xf5, 0x10, 0x35, 0xbb, 0x50, 0xc6, 0xbe, 0x80, 0x25, 0xce, 0x18, 0x11, 0x46, 0xde, 0x87, 0x71, - 0xee, 0x02, 0xe0, 0x5b, 0xef, 0x78, 0x46, 0x0a, 0x77, 0x01, 0x8f, 0x91, 0xe6, 0x18, 0x6a, 0xaf, - 0x0a, 0x22, 0xf3, 0xae, 0xf4, 0x3a, 0x9c, 0x30, 0x2f, 0x96, 0x60, 0x74, 0x27, 0xee, 0x19, 0x0c, - 0xbe, 0xe2, 0x1f, 0x61, 0x71, 0xb8, 0xf9, 0xf3, 0x06, 0xcc, 0xe8, 0x5f, 0x49, 0xae, 0xc3, 0x98, - 0x88, 0x34, 0x35, 0x70, 0x2f, 0xc8, 0xbe, 0x66, 0x8c, 0xc7, 0x98, 0x6a, 0x91, 0xa5, 0x02, 0x8b, - 0xad, 0x2c, 0x82, 0x83, 0x70, 0x23, 0xe0, 0xca, 0x22, 0x84, 0xd4, 0x92, 0x65, 0xc4, 0x84, 0x31, - 0x8b, 0x06, 0xdd, 0x66, 0xa8, 0xba, 0xbb, 0x7c, 0x84, 0x58, 0xa2, 0xc4, 0x2c, 0xc2, 0x18, 0x57, - 0x49, 0x89, 0x83, 0x36, 0xe3, 0x14, 0x07, 0x6d, 0xe6, 0xb1, 0x01, 0x50, 0xab, 0xad, 0xde, 0xa3, - 0x47, 0x55, 0xc7, 0xf5, 0xd1, 0x3f, 0x8b, 0x53, 0xfa, 0x9e, 0x18, 0xf2, 0x29, 0xe1, 0x9f, 0xe5, - 0xd3, 0xff, 0x3e, 0x3d, 0xd2, 0xfc, 0xb3, 0x12, 0x15, 0xf5, 0x86, 0xef, 0x3e, 0x70, 0x42, 0xca, - 0x08, 0x33, 0x48, 0xc8, 0xf5, 0x06, 0x87, 0x26, 0x28, 0x15, 0x64, 0xf2, 0x4d, 0x98, 0x89, 0x7f, - 0xe1, 0x56, 0x3f, 0x8b, 0x0e, 0x32, 0x29, 0x56, 0x7a, 0xe1, 0xca, 0x8b, 0x8f, 0x8f, 0x97, 0x2e, - 0x2a, 0x5c, 0x93, 0x9e, 0xf5, 0x04, 0x33, 0xf3, 0xb7, 0x0c, 0x74, 0xec, 0xcb, 0x0f, 0xbc, 0x0c, - 0x23, 0x51, 0xf8, 0xc0, 0x14, 0x77, 0x27, 0x24, 0x3c, 0x69, 0x58, 0x4e, 0x5e, 0x86, 0x6c, 0xfc, - 0x25, 0xa8, 0xf2, 0xf5, 0x2f, 0x60, 0xa5, 0xe4, 0x2e, 0x8c, 0x0f, 0xd5, 0x66, 0x14, 0xf1, 0x94, - 0xb6, 0x4a, 0x6a, 0x1c, 0x85, 0x8f, 0x76, 0xb7, 0x3e, 0xbf, 0xa3, 0xf0, 0xbd, 0x0c, 0xcc, 0xb2, - 0x7e, 0x2d, 0x74, 0xc3, 0x43, 0xcf, 0x77, 0xc3, 0xa3, 0x67, 0x76, 0xdf, 0xff, 0xae, 0x66, 0xb4, - 0x5d, 0x94, 0xba, 0x4f, 0xfd, 0xb6, 0xa1, 0xb6, 0xff, 0x7f, 0x34, 0x0a, 0xf3, 0x29, 0x54, 0xe4, - 0x0d, 0xcd, 0xdf, 0xb5, 0x28, 0xaf, 0x87, 0xfc, 0xf8, 0x78, 0x69, 0x4a, 0xa2, 0x6f, 0xc5, 0xd7, - 0x45, 0x96, 0xf5, 0x53, 0x32, 0xde, 0x53, 0xe8, 0xfe, 0x52, 0x4f, 0xc9, 0xf4, 0xb3, 0xb1, 0xab, - 0x30, 0x6a, 0x79, 0x4d, 0xca, 0x17, 0x52, 0x61, 0xa8, 0xf8, 0x0c, 0xa0, 0x1d, 0x46, 0x30, 0x00, - 0x59, 0x85, 0x71, 0xf6, 0xc7, 0xba, 0xd3, 0x11, 0x4e, 0x3f, 0x12, 0x6d, 0x1b, 0x10, 0xda, 0x71, - 0xdb, 0x07, 0xea, 0xce, 0xa1, 0x49, 0xed, 0x96, 0xd3, 0xd1, 0x56, 0x36, 0x8e, 0xa8, 0xed, 0x40, - 0x72, 0xfd, 0x77, 0x20, 0xc6, 0x89, 0x3b, 0x90, 0x06, 0x40, 0xcd, 0x3d, 0x68, 0xbb, 0xed, 0x83, - 0x42, 0xf3, 0x40, 0x5c, 0xb2, 0xb9, 0xda, 0x7f, 0x14, 0xae, 0xc7, 0xc8, 0x28, 0xb8, 0xdc, 0x17, - 0xce, 0x61, 0xb6, 0xd3, 0xd4, 0x3c, 0x92, 0x31, 0x2a, 0xd9, 0x00, 0x28, 0xd4, 0x43, 0xf7, 0x01, - 0x13, 0xe0, 0x40, 0x04, 0x76, 0xca, 0x06, 0x17, 0x0b, 0xf7, 0xe8, 0x51, 0x8d, 0x86, 0xb1, 0x87, - 0xd3, 0x41, 0x54, 0x36, 0x0f, 0xd4, 0x3e, 0x54, 0x38, 0x90, 0x0e, 0x9c, 0x2d, 0x34, 0x1a, 0x2e, - 0xfb, 0x02, 0xa7, 0xb9, 0xe5, 0xb3, 0xc1, 0x68, 0x20, 0xeb, 0xa9, 0x74, 0xd6, 0x57, 0x05, 0xeb, - 0x97, 0x9c, 0x88, 0xca, 0x0e, 0x39, 0x59, 0xb2, 0x9a, 0x74, 0xc6, 0xe6, 0x26, 0xcc, 0xe8, 0x9f, - 0xae, 0x5f, 0x0d, 0x9a, 0x82, 0x9c, 0x55, 0x2b, 0xd8, 0xb5, 0xd5, 0xc2, 0xcd, 0xbc, 0x41, 0xf2, - 0x30, 0x25, 0x7e, 0x2d, 0xdb, 0xcb, 0x6f, 0xdd, 0xce, 0x67, 0x34, 0xc8, 0x5b, 0x37, 0x97, 0xf3, - 0xd9, 0x8f, 0x46, 0x72, 0xd9, 0xfc, 0xc8, 0x47, 0x23, 0xb9, 0x91, 0xfc, 0xe8, 0x47, 0x23, 0xb9, - 0xf1, 0x7c, 0x4e, 0xec, 0xf7, 0x7f, 0xcf, 0x80, 0x9c, 0x6c, 0x37, 0xb9, 0x0d, 0xd9, 0x5a, 0x6d, - 0x35, 0x11, 0xdd, 0x19, 0xaf, 0x2f, 0x5c, 0x93, 0x06, 0xc1, 0xa1, 0xaa, 0x49, 0x6b, 0xb5, 0x55, - 0x46, 0xb7, 0xb5, 0x56, 0x13, 0xcb, 0xbb, 0xa4, 0x8b, 0xd5, 0x36, 0xa7, 0x4b, 0x09, 0x79, 0xbb, - 0x0d, 0xd9, 0x8f, 0x76, 0xb7, 0xc4, 0xb6, 0x44, 0xd2, 0xc5, 0x9a, 0x94, 0xd3, 0x7d, 0xfb, 0xa1, - 0xaa, 0xdf, 0x19, 0x81, 0x69, 0xc1, 0xa4, 0x22, 0xc2, 0x7c, 0xb9, 0x6d, 0x79, 0xd1, 0xb5, 0x19, - 0xb1, 0xdc, 0x32, 0x88, 0x25, 0x4a, 0x98, 0x75, 0xb0, 0xe6, 0xd5, 0x9d, 0xa6, 0x58, 0xb7, 0xd1, - 0x3a, 0x68, 0x32, 0x80, 0xc5, 0xe1, 0xe6, 0x1f, 0x1a, 0x90, 0xaf, 0xfa, 0xde, 0x03, 0x97, 0xa9, - 0x99, 0x2d, 0xef, 0x3e, 0x6d, 0xef, 0xdc, 0x24, 0x6f, 0xca, 0xc9, 0x66, 0x44, 0x9b, 0xe0, 0x51, - 0x9c, 0x6c, 0x09, 0xdf, 0xa8, 0x98, 0x70, 0xca, 0xed, 0xa3, 0xcc, 0xf0, 0x37, 0x1a, 0x4e, 0xb8, - 0x7d, 0xb4, 0x04, 0xa3, 0xd8, 0x1c, 0x25, 0xa8, 0x7c, 0x34, 0x64, 0x00, 0x8b, 0xc3, 0xd5, 0x4d, - 0x65, 0xa6, 0xe7, 0x1b, 0x96, 0x3f, 0x57, 0xb7, 0x02, 0xf4, 0x8f, 0x1b, 0x4a, 0x53, 0x7f, 0x02, - 0x0b, 0xc9, 0x2e, 0x41, 0x07, 0x45, 0x01, 0x66, 0x75, 0xb8, 0xf4, 0x55, 0x9c, 0x4f, 0xad, 0x6b, - 0x67, 0xd9, 0x4a, 0xe2, 0x9b, 0x7f, 0x6a, 0xc0, 0x04, 0xfe, 0x69, 0x75, 0x9b, 0x78, 0xee, 0x5c, - 0xd8, 0xad, 0x89, 0x70, 0x37, 0xd5, 0x8c, 0x73, 0x1e, 0x06, 0xb6, 0x88, 0x8d, 0xd3, 0xf4, 0x4b, - 0x84, 0x2c, 0x48, 0x79, 0x70, 0x9f, 0x3c, 0xa0, 0x8a, 0x48, 0x79, 0x14, 0x60, 0x90, 0x20, 0x15, - 0xc8, 0x18, 0x2a, 0xb1, 0x5b, 0x63, 0xe2, 0xa7, 0x1e, 0x4b, 0x21, 0x9d, 0xd7, 0xd4, 0x43, 0x25, - 0x38, 0x1a, 0x9e, 0x4a, 0xed, 0xd6, 0x0a, 0xd6, 0x86, 0x76, 0x2a, 0xc5, 0xda, 0xa8, 0x45, 0x63, - 0x09, 0x24, 0xf3, 0x7f, 0x19, 0x4f, 0x76, 0xa0, 0x58, 0xea, 0x4e, 0x39, 0x37, 0xde, 0x81, 0xd1, - 0x42, 0xb3, 0xe9, 0x3d, 0x14, 0x5a, 0x42, 0xfa, 0x4b, 0xa2, 0xfe, 0xe3, 0x2b, 0x99, 0xc3, 0x50, - 0xb4, 0xc0, 0x5a, 0x06, 0x20, 0x45, 0x98, 0x28, 0xec, 0xd6, 0x2a, 0x95, 0xd2, 0xd6, 0xd6, 0x9a, - 0xb8, 0x0a, 0xfa, 0xaa, 0xec, 0x1f, 0xd7, 0x6d, 0xd8, 0xc9, 0xe3, 0x9b, 0xd8, 0x72, 0x8f, 0xe9, - 0xc8, 0x7b, 0x00, 0x1f, 0x79, 0x6e, 0x7b, 0x9d, 0x86, 0x87, 0x5e, 0x43, 0x7c, 0xfc, 0x0b, 0x8f, - 0x8f, 0x97, 0x26, 0xbf, 0xed, 0xb9, 0x6d, 0xbb, 0x85, 0x60, 0xd6, 0xf6, 0x18, 0xc9, 0x52, 0xfe, - 0x66, 0x3d, 0xbd, 0xe2, 0xf1, 0xb3, 0xe4, 0xd1, 0xb8, 0xa7, 0xf7, 0xbc, 0x9e, 0x63, 0x64, 0x89, - 0x46, 0x5a, 0x30, 0x5b, 0xeb, 0x1e, 0x1c, 0x50, 0xa6, 0xd5, 0xc5, 0xee, 0x77, 0x4c, 0xec, 0xb9, - 0xa2, 0x8b, 0xb4, 0x7c, 0x27, 0xc2, 0xf6, 0x27, 0xc1, 0xca, 0x1b, 0x4c, 0x90, 0x7f, 0x74, 0xbc, - 0x24, 0x8e, 0x85, 0x98, 0x91, 0x16, 0x48, 0xfa, 0x5e, 0xff, 0x4b, 0x92, 0x37, 0xd9, 0x84, 0xb1, - 0xbb, 0x6e, 0xb8, 0xda, 0xdd, 0x13, 0xdb, 0xd7, 0x97, 0x06, 0x4c, 0x1a, 0x8e, 0xc8, 0x77, 0xf0, - 0x07, 0x6e, 0x78, 0xd8, 0x55, 0xc3, 0x17, 0x05, 0x1b, 0xb2, 0x0b, 0xb9, 0xa2, 0xeb, 0xd7, 0x9b, - 0xb4, 0x58, 0x11, 0xab, 0xfe, 0xcb, 0x03, 0x58, 0x4a, 0x54, 0xde, 0x2f, 0x75, 0xfc, 0x55, 0x77, - 0x55, 0x2b, 0x40, 0x62, 0x90, 0x7f, 0xdb, 0x80, 0xe7, 0xa2, 0xd6, 0x17, 0x0e, 0x68, 0x3b, 0x5c, - 0x77, 0xc2, 0xfa, 0x21, 0xf5, 0xa3, 0x3b, 0x24, 0x03, 0x7a, 0xe9, 0x2b, 0x3d, 0xbd, 0x74, 0x25, - 0xee, 0x25, 0x87, 0x31, 0xb3, 0x5b, 0x9c, 0x5b, 0x6f, 0x9f, 0x0d, 0xaa, 0x95, 0xd8, 0x00, 0xf7, - 0xba, 0x7b, 0xd4, 0x6f, 0xd3, 0x90, 0x06, 0x22, 0xa8, 0xfa, 0xd5, 0x01, 0x1f, 0x1c, 0x23, 0x8b, - 0x90, 0xc2, 0xe8, 0xb7, 0x16, 0x3a, 0x11, 0x41, 0xc9, 0x3d, 0x19, 0x1f, 0xcc, 0x2d, 0x92, 0x4b, - 0x03, 0x78, 0xf3, 0x98, 0xe1, 0xf9, 0x01, 0xb1, 0xe6, 0x7c, 0xb4, 0xd7, 0x9c, 0x3d, 0x61, 0x84, - 0x9c, 0x30, 0xda, 0x6b, 0x4e, 0x3c, 0xda, 0x4d, 0x27, 0x39, 0xda, 0x6b, 0xce, 0x9e, 0xf9, 0x2f, - 0x46, 0xe0, 0x62, 0x7f, 0x51, 0x21, 0x1f, 0xcb, 0xf9, 0xcb, 0xb5, 0xe4, 0xe5, 0x13, 0x85, 0xeb, - 0xfa, 0x89, 0xb3, 0xfa, 0xab, 0xb0, 0x50, 0x6e, 0x87, 0xd4, 0xef, 0xf8, 0xae, 0xbc, 0x21, 0xb4, - 0xea, 0x05, 0x32, 0x54, 0xe2, 0x95, 0xc7, 0xc7, 0x4b, 0x97, 0x68, 0x54, 0x2e, 0xa2, 0x66, 0x30, - 0x70, 0x43, 0x61, 0x95, 0xca, 0xe1, 0xe2, 0x6f, 0x65, 0x61, 0x04, 0x95, 0xf2, 0xcb, 0x90, 0xad, - 0x75, 0xf7, 0x84, 0x36, 0xe6, 0xe6, 0x8b, 0x26, 0xea, 0xac, 0x94, 0x7c, 0x19, 0xc0, 0xa2, 0x1d, - 0x2f, 0x70, 0x43, 0xcf, 0x3f, 0x52, 0xc3, 0x2c, 0xfd, 0x08, 0xaa, 0x07, 0x0c, 0x49, 0x28, 0x59, - 0x85, 0xd9, 0xf8, 0xd7, 0xe6, 0xc3, 0x36, 0x95, 0x5e, 0x37, 0xdc, 0x61, 0xc5, 0xe4, 0xb6, 0xc7, - 0xca, 0xd4, 0xc9, 0x9b, 0x20, 0x23, 0xcb, 0x90, 0xdb, 0xf5, 0xfc, 0xfb, 0xfb, 0xac, 0x87, 0x47, - 0x62, 0xf5, 0xf2, 0x50, 0xc0, 0xd4, 0x69, 0x24, 0xf1, 0xc8, 0x3b, 0x30, 0x59, 0x6e, 0x3f, 0x70, - 0x7d, 0xaf, 0xdd, 0xa2, 0x6d, 0x19, 0x19, 0xc3, 0x3d, 0x07, 0x31, 0x58, 0x0b, 0x9c, 0x8e, 0xc1, - 0x6c, 0x1f, 0x51, 0xa8, 0x87, 0x9e, 0x2f, 0x02, 0x63, 0xf8, 0x38, 0x31, 0x80, 0x36, 0x4e, 0x0c, - 0xc0, 0x3a, 0xd1, 0xa2, 0xfb, 0xc2, 0x33, 0x8a, 0x9d, 0xe8, 0xd3, 0x7d, 0x2d, 0x2a, 0x9c, 0xee, - 0x33, 0xf5, 0x68, 0xd1, 0x7d, 0xdc, 0xfc, 0xe4, 0xe2, 0xf6, 0xfb, 0x74, 0xbf, 0x67, 0xdb, 0x2c, - 0xd0, 0xcc, 0xdf, 0xee, 0x2f, 0x70, 0x6b, 0xce, 0x69, 0x05, 0x6e, 0xcd, 0x19, 0x42, 0xe0, 0xde, - 0x88, 0xc2, 0x90, 0x32, 0x71, 0x94, 0x3d, 0x0f, 0x43, 0x52, 0x27, 0x04, 0xc7, 0xb9, 0xf8, 0x6f, - 0x9c, 0x4a, 0x88, 0x44, 0x27, 0x65, 0x86, 0xed, 0xa4, 0xec, 0x50, 0x9d, 0x44, 0x56, 0x60, 0x3a, - 0xba, 0x65, 0x5f, 0x75, 0x44, 0x50, 0xae, 0x08, 0xe3, 0x89, 0x72, 0x26, 0xd8, 0x1d, 0x27, 0x54, - 0x8d, 0x72, 0x9d, 0x84, 0xbc, 0x0b, 0x93, 0x22, 0x16, 0x0f, 0x39, 0x8c, 0xc6, 0x41, 0x80, 0x32, - 0x70, 0x2f, 0x41, 0xaf, 0xa2, 0x93, 0x32, 0xcc, 0x54, 0xdd, 0x0e, 0x6d, 0xba, 0x6d, 0x5a, 0xc3, - 0x30, 0x1f, 0x21, 0x31, 0x18, 0xd3, 0xd6, 0x11, 0x25, 0x36, 0x8f, 0x00, 0xd2, 0x7c, 0x08, 0x1a, - 0x51, 0x52, 0x58, 0xc7, 0x4f, 0x23, 0xac, 0xe6, 0xef, 0x65, 0xe0, 0xf9, 0x41, 0x6b, 0x0e, 0xa9, - 0xe9, 0xc2, 0x72, 0x65, 0x88, 0x75, 0xea, 0x64, 0x71, 0x29, 0xc3, 0xcc, 0xa6, 0x7f, 0xe0, 0xb4, - 0xdd, 0xef, 0xa0, 0x2d, 0x11, 0x45, 0x13, 0xe2, 0x97, 0x7b, 0x4a, 0x89, 0xee, 0xa0, 0x4b, 0x10, - 0x5d, 0x7c, 0x20, 0xc4, 0xe8, 0xd3, 0x46, 0x4f, 0xde, 0x86, 0x89, 0xa2, 0xd7, 0x0e, 0xe9, 0xa3, - 0x30, 0x11, 0x04, 0xce, 0x81, 0xc9, 0x20, 0x70, 0x89, 0x6a, 0xfe, 0x91, 0x01, 0x2f, 0x0e, 0x5e, - 0xb7, 0xc8, 0xb6, 0xde, 0x6d, 0xd7, 0x86, 0x5a, 0xed, 0x4e, 0xec, 0xb8, 0x8b, 0xeb, 0xe2, 0x8b, - 0xcb, 0x30, 0x23, 0x82, 0x46, 0x74, 0xb3, 0x18, 0x3b, 0x50, 0x04, 0x5f, 0xa5, 0x98, 0xc6, 0x09, - 0x22, 0xf3, 0x2f, 0x0c, 0xb8, 0xd0, 0x77, 0x91, 0x24, 0x55, 0xfd, 0x1b, 0x5e, 0x3d, 0x69, 0x55, - 0x3d, 0xb9, 0xf9, 0xbf, 0x60, 0x88, 0xf6, 0xbf, 0x0f, 0x53, 0xb5, 0xee, 0x5e, 0xf2, 0xe6, 0x30, - 0xce, 0x9c, 0x40, 0x81, 0x6b, 0x87, 0x1f, 0x0a, 0x9c, 0x7d, 0xbf, 0x8c, 0x8d, 0xc3, 0x68, 0x6e, - 0x69, 0xdb, 0xe3, 0xf7, 0x47, 0xe1, 0xa8, 0x18, 0x6d, 0xab, 0xda, 0x0c, 0x09, 0x22, 0xf3, 0x17, - 0x32, 0x30, 0xc3, 0xcf, 0x06, 0xf8, 0xc6, 0xe3, 0x99, 0xdd, 0xd4, 0xbd, 0xa3, 0x6d, 0xea, 0xe4, - 0xb5, 0x15, 0xf5, 0xd3, 0x86, 0xda, 0xd2, 0x1d, 0x02, 0xe9, 0xa5, 0x21, 0x96, 0x3c, 0xc1, 0x1a, - 0x66, 0x37, 0x77, 0x33, 0xbe, 0xe1, 0x84, 0x19, 0x70, 0xea, 0x36, 0x6e, 0xa9, 0x03, 0x4b, 0xe3, - 0x61, 0xfe, 0x7c, 0x06, 0xa6, 0x15, 0xe7, 0xdb, 0x33, 0xdb, 0xf1, 0x5f, 0xd1, 0x3a, 0x7e, 0x51, - 0xfa, 0xab, 0xe2, 0x2f, 0x1b, 0xaa, 0xdf, 0xbb, 0x30, 0xd7, 0x43, 0x92, 0xf4, 0x61, 0x1a, 0xc3, - 0xf8, 0x30, 0xdf, 0xe8, 0xbd, 0xd6, 0xc2, 0x13, 0x8d, 0x44, 0xd7, 0x5a, 0xd4, 0x7b, 0x34, 0xdf, - 0xcb, 0xc0, 0x82, 0xf8, 0x55, 0xe8, 0x36, 0xdc, 0xb0, 0xe8, 0xb5, 0xf7, 0xdd, 0x83, 0x67, 0x76, - 0x2c, 0x0a, 0xda, 0x58, 0x2c, 0xe9, 0x63, 0xa1, 0x7c, 0x60, 0xff, 0x21, 0x31, 0xff, 0x3a, 0xc0, - 0x62, 0x3f, 0x82, 0xa1, 0x43, 0x2e, 0xe3, 0x2b, 0x87, 0x99, 0x21, 0xae, 0x1c, 0xae, 0x41, 0x1e, - 0xab, 0xaa, 0xd1, 0x80, 0x75, 0x42, 0x10, 0x67, 0x39, 0xb8, 0xf4, 0xf8, 0x78, 0xe9, 0x79, 0x87, - 0x95, 0xd9, 0x81, 0x28, 0xb4, 0xbb, 0xbe, 0xba, 0xf1, 0xeb, 0xa1, 0x24, 0xbf, 0x65, 0xc0, 0x0c, - 0x02, 0xcb, 0x0f, 0x68, 0x3b, 0x44, 0x66, 0x23, 0x22, 0x54, 0x27, 0xda, 0xf3, 0xd5, 0x42, 0xdf, - 0x6d, 0x1f, 0x88, 0x4d, 0xdf, 0x9e, 0xd8, 0xf4, 0xbd, 0xcb, 0x37, 0xab, 0xd7, 0xeb, 0x5e, 0xeb, - 0xc6, 0x81, 0xef, 0x3c, 0x70, 0xb9, 0x5f, 0xd9, 0x69, 0xde, 0x88, 0x93, 0x5c, 0x75, 0xdc, 0x44, - 0xda, 0x2a, 0xc1, 0x0a, 0x37, 0xd4, 0xbc, 0xa1, 0x14, 0xab, 0x4d, 0x34, 0x33, 0xd1, 0x22, 0xf2, - 0x35, 0x38, 0xcf, 0xef, 0x88, 0xb0, 0x25, 0xd5, 0x6d, 0x77, 0xbd, 0x6e, 0xb0, 0xe2, 0xd4, 0xef, - 0x33, 0x35, 0xce, 0x8f, 0x97, 0xf1, 0xcb, 0xeb, 0x51, 0xa1, 0xbd, 0xc7, 0x4b, 0x15, 0x96, 0xfd, - 0x18, 0x90, 0x55, 0x98, 0xe3, 0x45, 0x85, 0x6e, 0xe8, 0xd5, 0xea, 0x4e, 0xd3, 0x6d, 0x1f, 0xa0, - 0x4d, 0x94, 0xe3, 0xcb, 0x8b, 0xd3, 0x0d, 0x3d, 0x3b, 0xe0, 0x70, 0x85, 0x5f, 0x2f, 0x11, 0xa9, - 0xb0, 0x2d, 0x88, 0xd3, 0x58, 0x77, 0x1e, 0x15, 0x9d, 0x8e, 0x53, 0x77, 0x43, 0x7e, 0xd1, 0x30, - 0xcb, 0x6f, 0x07, 0xf8, 0xd4, 0x69, 0xd8, 0x2d, 0xe7, 0x91, 0x5d, 0x17, 0x85, 0xfa, 0x1e, 0x44, - 0xa3, 0x8b, 0x58, 0xb9, 0xed, 0x88, 0xd5, 0x44, 0x92, 0x95, 0xdb, 0xee, 0xcf, 0x2a, 0xa6, 0x93, - 0xac, 0xb6, 0x1c, 0xff, 0x80, 0x86, 0xfc, 0x58, 0x96, 0x6d, 0xa8, 0x0d, 0x85, 0x55, 0x88, 0x65, - 0x36, 0x1e, 0xd1, 0x26, 0x59, 0x29, 0x74, 0x4c, 0xf2, 0x76, 0x7d, 0x37, 0xa4, 0xea, 0x17, 0x4e, - 0x62, 0xb3, 0xb0, 0xff, 0xf1, 0x60, 0xba, 0xdf, 0x27, 0xf6, 0x50, 0xc6, 0xdc, 0x94, 0x8f, 0x9c, - 0xea, 0xe1, 0x96, 0xfe, 0x95, 0x3d, 0x94, 0x11, 0x37, 0xf5, 0x3b, 0xa7, 0xf1, 0x3b, 0x15, 0x6e, - 0x7d, 0x3e, 0xb4, 0x87, 0x92, 0x6c, 0xb0, 0x4e, 0x0b, 0x69, 0x9b, 0x49, 0xb4, 0x38, 0x96, 0x9e, - 0xc1, 0xa6, 0xbd, 0x22, 0xce, 0x56, 0xf2, 0xbe, 0x2c, 0xb6, 0x53, 0x0e, 0xa9, 0x93, 0xc4, 0xe4, - 0x2f, 0xc1, 0xec, 0x76, 0x40, 0xef, 0x54, 0xaa, 0x35, 0x79, 0x27, 0x68, 0x71, 0x16, 0x4f, 0x5c, - 0x6e, 0x9e, 0xa0, 0x74, 0xae, 0xab, 0x34, 0x98, 0x6e, 0x8a, 0x8f, 0x5b, 0x37, 0xa0, 0xf6, 0xbe, - 0xdb, 0x09, 0xa2, 0xfb, 0x79, 0xea, 0xb8, 0x25, 0xaa, 0x32, 0x57, 0x61, 0xae, 0x87, 0x0d, 0x99, - 0x01, 0x60, 0x40, 0x7b, 0x7b, 0xa3, 0x56, 0xde, 0xca, 0x9f, 0x21, 0x79, 0x98, 0xc2, 0xdf, 0xe5, - 0x8d, 0xc2, 0xca, 0x5a, 0xb9, 0x94, 0x37, 0xc8, 0x1c, 0x4c, 0x23, 0xa4, 0x54, 0xa9, 0x71, 0x50, - 0xe6, 0xa3, 0x91, 0xdc, 0x68, 0x7e, 0xcc, 0xca, 0xf3, 0xa9, 0x1b, 0xb2, 0x09, 0x80, 0x6b, 0x8a, - 0xf9, 0xb7, 0x32, 0x70, 0x41, 0x2e, 0x2b, 0x34, 0x64, 0x5b, 0x65, 0xb7, 0x7d, 0xf0, 0x8c, 0xaf, - 0x0e, 0x77, 0xb4, 0xd5, 0xe1, 0x95, 0xc4, 0x4a, 0x9d, 0xf8, 0xca, 0x01, 0x4b, 0xc4, 0xaf, 0x8e, - 0xc3, 0x0b, 0x03, 0xa9, 0xc8, 0xc7, 0x6c, 0x35, 0x77, 0x69, 0x3b, 0xac, 0x34, 0x9a, 0x74, 0xcb, - 0x6d, 0x51, 0xaf, 0x1b, 0x8a, 0x30, 0x88, 0x97, 0x1f, 0x1f, 0x2f, 0xcd, 0xf3, 0x5c, 0x51, 0xb6, - 0xdb, 0x68, 0x52, 0x3b, 0xe4, 0xc5, 0x9a, 0xb8, 0xf5, 0x52, 0x33, 0x96, 0x51, 0x3e, 0xbb, 0x4a, - 0x3b, 0xa4, 0xfe, 0x03, 0x87, 0xa7, 0xcc, 0x11, 0x2c, 0xef, 0x53, 0xda, 0xb1, 0x1d, 0x56, 0x6a, - 0xbb, 0xa2, 0x58, 0x67, 0xd9, 0x43, 0x4d, 0xee, 0x28, 0x2c, 0x8b, 0x6c, 0x37, 0xb0, 0xee, 0x3c, - 0x12, 0x1e, 0x60, 0x71, 0xfd, 0x37, 0x62, 0xc9, 0x6f, 0x9f, 0xb7, 0x9c, 0x47, 0x56, 0x2f, 0x09, - 0xf9, 0x26, 0x9c, 0x15, 0x0b, 0x90, 0x08, 0xd3, 0x97, 0x5f, 0xcc, 0x2f, 0x01, 0xbc, 0xf6, 0xf8, - 0x78, 0xe9, 0xbc, 0x58, 0xbe, 0x6c, 0x79, 0xe5, 0x21, 0xed, 0xab, 0xd3, 0xb9, 0x90, 0x2d, 0xb6, - 0x20, 0x27, 0xba, 0x63, 0x9d, 0x06, 0x81, 0x73, 0x20, 0xbd, 0xc5, 0x3c, 0x16, 0x49, 0xe9, 0x4c, - 0xbb, 0xc5, 0xcb, 0xad, 0xbe, 0x94, 0x64, 0x15, 0x66, 0x76, 0xe9, 0x9e, 0x3a, 0x3e, 0x63, 0x91, - 0xaa, 0xca, 0x3f, 0xa4, 0x7b, 0xfd, 0x07, 0x27, 0x41, 0x47, 0x5c, 0x98, 0xc3, 0x38, 0xcd, 0x35, - 0x37, 0x08, 0x69, 0x9b, 0xfa, 0x78, 0x95, 0x69, 0x1c, 0x95, 0xc1, 0x62, 0x6c, 0x21, 0xeb, 0xe5, - 0x2b, 0x2f, 0x3d, 0x3e, 0x5e, 0x7a, 0x81, 0xc7, 0x7c, 0x36, 0x05, 0xdc, 0x4e, 0x24, 0x8e, 0xeb, - 0xe5, 0x4a, 0xbe, 0x05, 0xb3, 0x96, 0xd7, 0x0d, 0xdd, 0xf6, 0x41, 0x2d, 0xf4, 0x9d, 0x90, 0x1e, - 0xf0, 0x05, 0x29, 0xbe, 0x33, 0x95, 0x28, 0x15, 0xbe, 0x32, 0x0e, 0xb4, 0x03, 0x01, 0xd5, 0x56, - 0x04, 0x9d, 0x80, 0xfc, 0x04, 0xcc, 0xf0, 0xa8, 0xee, 0xa8, 0x82, 0x09, 0x2d, 0xc7, 0x8a, 0x5e, - 0xb8, 0x73, 0x93, 0xef, 0xb7, 0x78, 0x74, 0x78, 0x5a, 0x05, 0x09, 0x6e, 0xe4, 0xeb, 0xa2, 0xb3, - 0xaa, 0x6e, 0xfb, 0x20, 0x12, 0x63, 0xc0, 0x9e, 0x7f, 0x33, 0xee, 0x92, 0x0e, 0x6b, 0xae, 0x14, - 0xe3, 0x3e, 0xa7, 0x0f, 0xbd, 0x7c, 0xcc, 0x63, 0x03, 0xf2, 0xc9, 0x06, 0x92, 0xaf, 0xc2, 0x04, - 0x77, 0x48, 0xd3, 0xe0, 0x50, 0x5c, 0xae, 0x92, 0x17, 0x74, 0x22, 0xb8, 0x4e, 0x24, 0x52, 0x2e, - 0x70, 0x77, 0x37, 0x55, 0x8f, 0x64, 0x57, 0xcf, 0x58, 0x31, 0x33, 0xd2, 0x80, 0x29, 0xde, 0x06, - 0x8a, 0xd7, 0x00, 0xc5, 0xb9, 0xe4, 0x4b, 0xea, 0x98, 0x8b, 0xa2, 0x04, 0x7f, 0xbc, 0x29, 0x27, - 0xbe, 0x94, 0x23, 0x68, 0x55, 0x68, 0x5c, 0x57, 0x00, 0x72, 0x92, 0xd0, 0xbc, 0x00, 0xe7, 0xfb, - 0xb4, 0xd9, 0x7c, 0x80, 0x0e, 0xbf, 0x3e, 0x35, 0x92, 0xaf, 0xc2, 0x02, 0x12, 0x16, 0xbd, 0x76, - 0x9b, 0xd6, 0x43, 0x9c, 0x64, 0xd2, 0x67, 0x90, 0xe5, 0xee, 0x60, 0xfe, 0xbd, 0xf5, 0x08, 0xc1, - 0x4e, 0xba, 0x0e, 0x52, 0x39, 0x98, 0xbf, 0x96, 0x81, 0x45, 0x31, 0x6f, 0x2d, 0x5a, 0xf7, 0xfc, - 0xc6, 0xb3, 0xbf, 0x4e, 0x94, 0xb5, 0x75, 0xe2, 0xe5, 0xe8, 0xae, 0x46, 0xda, 0x47, 0x0e, 0x58, - 0x26, 0x7e, 0xd7, 0x80, 0xe7, 0x07, 0x11, 0xb1, 0xde, 0x89, 0xae, 0x3d, 0x4e, 0xf4, 0x5c, 0x6f, - 0xec, 0xc0, 0x3c, 0x0e, 0x68, 0xf1, 0x90, 0xd6, 0xef, 0x07, 0xab, 0x5e, 0x10, 0x62, 0x58, 0x44, - 0x46, 0x0b, 0x89, 0x5e, 0xf1, 0x3c, 0x7e, 0x76, 0x83, 0x07, 0x5c, 0xc6, 0x8f, 0x8e, 0x97, 0x80, - 0x81, 0xf8, 0x45, 0x45, 0xe1, 0x85, 0x7c, 0x74, 0x64, 0xd7, 0x91, 0x07, 0xbf, 0x98, 0x79, 0x9f, - 0x1e, 0x05, 0x56, 0x1a, 0x6b, 0x3c, 0xe2, 0x2e, 0x74, 0xc3, 0xc3, 0xaa, 0x4f, 0xf7, 0xa9, 0x4f, - 0xdb, 0x75, 0xfa, 0x39, 0x3b, 0xe2, 0xd6, 0x3f, 0x6e, 0xa8, 0x7d, 0xf9, 0x3f, 0x05, 0x58, 0x48, - 0x23, 0x63, 0xfd, 0xa2, 0x6c, 0x05, 0x93, 0xc9, 0x6a, 0xff, 0x9a, 0x01, 0x53, 0x35, 0x5a, 0xf7, - 0xda, 0x8d, 0x3b, 0xe8, 0xf8, 0x17, 0xbd, 0x63, 0xf3, 0xa5, 0x90, 0xc1, 0xed, 0xfd, 0xc4, 0x89, - 0xc0, 0x8f, 0x8f, 0x97, 0x3e, 0x1c, 0x6e, 0x07, 0x56, 0xf7, 0xf0, 0x56, 0x5a, 0x88, 0x49, 0x58, - 0xa2, 0x2a, 0x30, 0xf6, 0x49, 0xab, 0x94, 0xac, 0xc0, 0xb4, 0x98, 0xae, 0x9e, 0x7a, 0xeb, 0x15, - 0x1d, 0xdc, 0x75, 0x59, 0xd0, 0x73, 0x4d, 0x5e, 0x23, 0x21, 0xb7, 0x20, 0xbb, 0xbd, 0x7c, 0x47, - 0x8c, 0x81, 0xbc, 0xb4, 0xb3, 0xbd, 0x7c, 0x07, 0x9d, 0x3c, 0xcc, 0x70, 0x9e, 0xee, 0x2e, 0x6b, - 0xbe, 0xf8, 0xed, 0xe5, 0x3b, 0xe4, 0x2f, 0xc3, 0xd9, 0x92, 0x1b, 0x88, 0x2a, 0x78, 0xb0, 0x45, - 0x03, 0x83, 0x0b, 0xc7, 0xfa, 0x48, 0xef, 0x97, 0x52, 0xa5, 0xf7, 0xa5, 0x46, 0xc4, 0xc4, 0xe6, - 0x91, 0x1c, 0x8d, 0xe4, 0xed, 0xde, 0xf4, 0x7a, 0xc8, 0xb7, 0x61, 0x06, 0xfd, 0x8d, 0x18, 0x7f, - 0x82, 0x09, 0x37, 0xc6, 0xfb, 0xd4, 0xfc, 0x85, 0xd4, 0x9a, 0x2f, 0xa2, 0xfb, 0xd2, 0xc6, 0x28, - 0x16, 0x4c, 0xce, 0xa1, 0xed, 0x65, 0x35, 0xce, 0xe4, 0x23, 0x98, 0x15, 0x46, 0xc5, 0xe6, 0xfe, - 0xd6, 0x21, 0x2d, 0x39, 0x47, 0xe2, 0x94, 0x06, 0xf7, 0x29, 0xc2, 0x12, 0xb1, 0xbd, 0x7d, 0x3b, - 0x3c, 0xa4, 0x76, 0xc3, 0xd1, 0x96, 0xdf, 0x04, 0x21, 0xf9, 0x29, 0x98, 0x5c, 0xf3, 0xea, 0xcc, - 0x9e, 0x44, 0xcd, 0x30, 0x81, 0x7c, 0x3e, 0xc1, 0xcc, 0xa9, 0x1c, 0x9c, 0x30, 0x12, 0x7e, 0x7c, - 0xbc, 0xf4, 0xce, 0x69, 0x85, 0x46, 0xa9, 0xc0, 0x52, 0x6b, 0x23, 0x45, 0xc8, 0xed, 0xd2, 0x3d, - 0xf6, 0xb5, 0xc9, 0xac, 0x7f, 0x12, 0x2c, 0x0e, 0xce, 0xc4, 0x2f, 0xed, 0xe0, 0x4c, 0xc0, 0x88, - 0x0f, 0x73, 0xd8, 0x3f, 0x55, 0x27, 0x08, 0x1e, 0x7a, 0x7e, 0x03, 0x73, 0xe9, 0x4c, 0xf6, 0xe9, - 0xfc, 0xe5, 0xd4, 0xce, 0x7f, 0x9e, 0x77, 0x7e, 0x47, 0xe1, 0xa0, 0x9a, 0x45, 0x3d, 0xec, 0xc9, - 0xb7, 0x60, 0xc6, 0xa2, 0x3f, 0xd9, 0x75, 0x7d, 0xba, 0x7e, 0xa7, 0x80, 0xb3, 0x72, 0x4a, 0x0b, - 0xd1, 0xd4, 0x0b, 0xb9, 0xed, 0xe5, 0x73, 0x98, 0xf4, 0xab, 0xd8, 0xad, 0x7d, 0x47, 0x77, 0x13, - 0xab, 0x24, 0xa4, 0x0a, 0x93, 0x25, 0xfa, 0xc0, 0xad, 0x53, 0x0c, 0x24, 0xc3, 0x7d, 0xa8, 0x92, - 0x85, 0x2c, 0x2e, 0xe1, 0x1e, 0x86, 0x06, 0x02, 0x78, 0x58, 0x9a, 0x1e, 0x93, 0x1e, 0x21, 0x92, - 0xdb, 0x90, 0xad, 0x94, 0xaa, 0xb8, 0x09, 0x8d, 0xe3, 0xb3, 0x2a, 0x8d, 0xaa, 0xcc, 0xa8, 0x85, - 0xc7, 0x5c, 0x6e, 0x43, 0x4b, 0xa2, 0x56, 0x29, 0x55, 0xc9, 0x3e, 0x4c, 0x63, 0x07, 0xac, 0x52, - 0x87, 0xf7, 0xed, 0x6c, 0x9f, 0xbe, 0xbd, 0x9e, 0xda, 0xb7, 0x8b, 0xbc, 0x6f, 0x0f, 0x05, 0xb5, - 0x96, 0x22, 0x48, 0x65, 0x4b, 0x56, 0x60, 0xac, 0x10, 0x04, 0x6e, 0x10, 0x2e, 0xe6, 0xb1, 0x82, - 0x05, 0xa9, 0x43, 0x11, 0x28, 0x5b, 0x89, 0x8e, 0x2d, 0x07, 0x41, 0x5a, 0x7c, 0x0b, 0x42, 0xf8, - 0xe6, 0xd2, 0x7c, 0x1f, 0x33, 0x16, 0xc5, 0x44, 0x18, 0x25, 0x53, 0xad, 0xc8, 0x08, 0x5e, 0x19, - 0x25, 0xd3, 0x71, 0x13, 0x41, 0xb8, 0x02, 0xc9, 0xfc, 0x8f, 0x0c, 0x54, 0x41, 0xe4, 0x1a, 0xde, - 0x69, 0x8a, 0xce, 0x77, 0x78, 0xd5, 0x9d, 0x44, 0x8e, 0x0d, 0x8e, 0x42, 0xde, 0x80, 0xb1, 0x3b, - 0x4e, 0x9d, 0x86, 0xf2, 0x54, 0x00, 0x91, 0xf7, 0x11, 0xa2, 0xd6, 0xc0, 0x71, 0x98, 0x75, 0xc4, - 0x87, 0xa6, 0x10, 0x67, 0x6d, 0x2f, 0x16, 0xe4, 0x65, 0x5d, 0xb4, 0x8e, 0xc4, 0x90, 0x2a, 0x69, - 0xdd, 0xed, 0xba, 0xa3, 0xf2, 0x4a, 0xe5, 0x60, 0xfe, 0x6f, 0x46, 0x3c, 0xa7, 0xc8, 0x6b, 0x30, - 0x62, 0x55, 0xa3, 0xf6, 0xf3, 0xe8, 0xd2, 0x44, 0xf3, 0x11, 0x81, 0x7c, 0x1d, 0xce, 0x2a, 0x7c, - 0x70, 0x5c, 0x68, 0x83, 0x35, 0x88, 0x7f, 0xcc, 0xab, 0x18, 0xfe, 0xa8, 0xb4, 0xc4, 0xe1, 0x18, - 0x89, 0x16, 0xa5, 0xf3, 0x40, 0x53, 0x30, 0x2e, 0x28, 0xd1, 0xb6, 0xcb, 0x79, 0x2b, 0x1f, 0xab, - 0xf2, 0x6e, 0x20, 0x42, 0xf2, 0x63, 0xd3, 0x38, 0xf0, 0x08, 0x48, 0xf3, 0x2d, 0x6d, 0xaa, 0x44, - 0x09, 0xb2, 0x8d, 0xc1, 0x09, 0xb2, 0xcd, 0x7f, 0x69, 0x28, 0xb9, 0xd0, 0x9f, 0x51, 0xab, 0xe3, - 0xb6, 0x66, 0x75, 0xc8, 0x19, 0x13, 0x7d, 0x15, 0x2b, 0x4b, 0xb5, 0x14, 0x67, 0x95, 0x83, 0x68, - 0x04, 0x7c, 0x37, 0x03, 0x93, 0xdb, 0x01, 0xf5, 0xf9, 0x81, 0xca, 0xe7, 0xeb, 0xa6, 0x68, 0xf4, - 0x5d, 0x43, 0xdd, 0xe5, 0xfb, 0x13, 0x03, 0x1d, 0x6d, 0x2a, 0x05, 0xeb, 0x0d, 0x06, 0x52, 0x7b, - 0xa3, 0x1b, 0x50, 0xdf, 0x42, 0x28, 0xbf, 0xc3, 0xb5, 0xa6, 0xdf, 0xe1, 0x6a, 0x5a, 0x0c, 0x46, - 0x3e, 0x84, 0xd1, 0x6d, 0x74, 0x1b, 0xe8, 0x11, 0xfc, 0x11, 0x7f, 0x2c, 0xe4, 0x13, 0xb3, 0xcb, - 0xfe, 0x54, 0xf5, 0x0a, 0x96, 0x91, 0x1a, 0x8c, 0x17, 0x7d, 0x8a, 0x59, 0xcf, 0x47, 0x86, 0x8f, - 0x42, 0xad, 0x73, 0x92, 0x64, 0x14, 0xaa, 0xe0, 0x64, 0xfe, 0x4a, 0x06, 0x48, 0xfc, 0x8d, 0x98, - 0x97, 0x2d, 0x78, 0x66, 0x07, 0xfd, 0x03, 0x6d, 0xd0, 0x5f, 0xe8, 0x19, 0x74, 0xfe, 0x79, 0x43, - 0x8d, 0xfd, 0x1f, 0x1a, 0x70, 0x2e, 0x9d, 0x90, 0xbc, 0x0c, 0x63, 0x9b, 0x5b, 0xd5, 0x78, 0x09, - 0xc1, 0x4f, 0xf1, 0x3a, 0xb8, 0xbb, 0xb1, 0x44, 0x11, 0x5b, 0x67, 0x3e, 0xb6, 0x8a, 0x4c, 0xf9, - 0x28, 0x09, 0x62, 0x7e, 0xd2, 0xb7, 0xeb, 0xba, 0xfe, 0x11, 0x48, 0xea, 0xd8, 0x66, 0x9f, 0xda, - 0xd8, 0x7e, 0x2f, 0x03, 0xb3, 0x85, 0x7a, 0x9d, 0x06, 0x01, 0xb3, 0x28, 0x68, 0x10, 0x3e, 0xb3, - 0x03, 0x9b, 0x7e, 0xbd, 0x43, 0xfb, 0xb6, 0xa1, 0x46, 0xf5, 0x8f, 0x0c, 0x38, 0x2b, 0xa9, 0x1e, - 0xb8, 0xf4, 0xe1, 0xd6, 0xa1, 0x4f, 0x83, 0x43, 0xaf, 0xd9, 0x18, 0x3a, 0x0b, 0x15, 0x5b, 0xdc, - 0x31, 0x1f, 0x87, 0x7a, 0xba, 0xb6, 0x8f, 0x10, 0x6d, 0x71, 0xe7, 0x39, 0x3b, 0x6e, 0xc0, 0x78, - 0xa1, 0xd3, 0xf1, 0xbd, 0x07, 0x7c, 0xda, 0x4f, 0x4b, 0x73, 0x03, 0x41, 0x5a, 0x10, 0x2f, 0x07, - 0xb1, 0x66, 0x94, 0x68, 0x9b, 0xdf, 0xaf, 0x9d, 0xe6, 0xcd, 0x68, 0xd0, 0xb6, 0x6a, 0x99, 0x60, - 0xb9, 0xf9, 0x8b, 0x23, 0x30, 0xa5, 0x7e, 0x08, 0x31, 0x79, 0x4e, 0x1a, 0xcf, 0x57, 0x63, 0xe5, - 0x1d, 0x84, 0x58, 0xa2, 0x24, 0xbe, 0x62, 0x92, 0x39, 0xf1, 0x8a, 0xc9, 0x2e, 0x4c, 0x57, 0x7d, - 0xaf, 0xe3, 0x05, 0xb4, 0xc1, 0x1f, 0xae, 0xe0, 0x5a, 0x6b, 0x5e, 0x31, 0x6a, 0x59, 0x9f, 0xe3, - 0x11, 0x02, 0x6e, 0xe9, 0x3a, 0x02, 0xdb, 0x4e, 0x3e, 0x6b, 0xa1, 0xf3, 0xe1, 0xa7, 0x93, 0x4e, - 0x20, 0x6e, 0xbc, 0x47, 0xa7, 0x93, 0x0c, 0xa2, 0x9f, 0x4e, 0x32, 0x88, 0x3a, 0x2d, 0x46, 0x9f, - 0xd6, 0xb4, 0x20, 0xbf, 0x62, 0xc0, 0x64, 0xa1, 0xdd, 0x16, 0x57, 0x57, 0x4e, 0x88, 0xdd, 0xfd, - 0x86, 0x38, 0xa0, 0x7c, 0xe7, 0x53, 0x1d, 0x50, 0x6e, 0xf9, 0x8e, 0x1b, 0x06, 0x18, 0xd1, 0x1c, - 0x57, 0xa8, 0x5a, 0xe5, 0x4a, 0x3b, 0xc8, 0x3b, 0x90, 0x8f, 0xe4, 0xb1, 0xd2, 0x6e, 0xd0, 0x47, - 0x34, 0x58, 0x1c, 0xbf, 0x94, 0xbd, 0x32, 0xcd, 0xdf, 0xdc, 0xd1, 0x4e, 0x5e, 0x93, 0x88, 0xe6, - 0xf7, 0x0c, 0x38, 0xa7, 0x0a, 0x44, 0xad, 0xbb, 0xd7, 0x72, 0x71, 0x77, 0x41, 0xae, 0xc3, 0x84, - 0x18, 0xaf, 0xc8, 0xfe, 0xeb, 0x4d, 0xc5, 0x13, 0xa3, 0x90, 0x32, 0x1b, 0x22, 0xc6, 0x43, 0xf8, - 0x7b, 0xe6, 0x13, 0xd3, 0x8d, 0x15, 0xad, 0x2c, 0x8a, 0xce, 0xce, 0xfb, 0xf8, 0x5b, 0x1f, 0x3b, - 0x06, 0x31, 0xdf, 0x87, 0x39, 0xbd, 0x95, 0x35, 0x8a, 0xe9, 0x54, 0xe4, 0xa7, 0x19, 0xe9, 0x9f, - 0x26, 0xcb, 0xcd, 0x5d, 0x20, 0x3d, 0xf4, 0x01, 0x9e, 0xb2, 0xd3, 0x50, 0x46, 0x81, 0x48, 0x1f, - 0x77, 0x0f, 0x62, 0xf4, 0x1a, 0xd0, 0xa4, 0xda, 0xdd, 0x48, 0x6a, 0xfe, 0x17, 0x93, 0x30, 0x9f, - 0xa2, 0x3a, 0x4e, 0x58, 0xda, 0x97, 0xf4, 0xc9, 0x33, 0x11, 0x85, 0xc5, 0xcb, 0x29, 0xf3, 0xbe, - 0x7c, 0xe3, 0x65, 0xc0, 0x54, 0x19, 0xf4, 0xf0, 0xcb, 0x67, 0xb1, 0xbc, 0xab, 0x37, 0x57, 0x46, - 0x9f, 0xda, 0xcd, 0x95, 0x15, 0x98, 0x16, 0x5f, 0x25, 0xa6, 0xf2, 0x58, 0xec, 0xda, 0xf1, 0x79, - 0x81, 0xdd, 0x33, 0xa5, 0x75, 0x12, 0xce, 0x23, 0xf0, 0x9a, 0x0f, 0xa8, 0xe0, 0x31, 0xae, 0xf2, - 0xc0, 0x82, 0x54, 0x1e, 0x0a, 0x09, 0xf9, 0x0f, 0x31, 0x41, 0x23, 0x42, 0xd4, 0xf9, 0x9c, 0x1b, - 0x34, 0x9f, 0x1b, 0x4f, 0x67, 0x3e, 0xbf, 0x20, 0xdb, 0x98, 0x3e, 0xaf, 0x53, 0x9a, 0x45, 0x7e, - 0xdb, 0x80, 0x39, 0x7e, 0x7d, 0x42, 0x6d, 0xec, 0xc0, 0x90, 0xf8, 0xfa, 0xd3, 0x69, 0xec, 0xf3, - 0x01, 0x56, 0xdb, 0xa7, 0xad, 0xbd, 0x8d, 0x22, 0x5f, 0x03, 0x88, 0x66, 0x54, 0xb0, 0x08, 0x7a, - 0xca, 0x80, 0xb4, 0xe5, 0x33, 0x4e, 0x18, 0x14, 0x46, 0x74, 0x5a, 0x5a, 0xce, 0x08, 0x4a, 0xfe, - 0x32, 0x2c, 0xb0, 0xf9, 0x12, 0x41, 0xc4, 0x65, 0xaf, 0xc5, 0x49, 0xac, 0xe5, 0x8b, 0xfd, 0x97, - 0xf6, 0xeb, 0x69, 0x64, 0x3c, 0xad, 0x40, 0x9c, 0xd5, 0x3a, 0x6c, 0xa9, 0x3b, 0xc5, 0x34, 0x0a, - 0xbc, 0x3d, 0x89, 0xad, 0xe7, 0xe9, 0x79, 0xfa, 0xe8, 0xb7, 0x0b, 0x72, 0x2e, 0x70, 0xfd, 0x16, - 0xe8, 0x31, 0xbc, 0x08, 0x22, 0x1f, 0x03, 0x89, 0xee, 0x1d, 0x70, 0x18, 0xf5, 0xe5, 0x23, 0x0f, - 0xe8, 0xe7, 0x89, 0xef, 0x2f, 0xf8, 0xb2, 0x58, 0x15, 0x92, 0x5e, 0x62, 0x42, 0x61, 0x41, 0x7c, - 0x34, 0x83, 0xca, 0x04, 0x96, 0xc1, 0xe2, 0x8c, 0x76, 0x95, 0x2e, 0x2e, 0x89, 0xd3, 0x5f, 0x2b, - 0x59, 0x30, 0xb5, 0xdd, 0x72, 0x1a, 0x3b, 0x72, 0x1b, 0x26, 0xd6, 0xbc, 0x03, 0xb7, 0xbd, 0x2a, - 0x63, 0x07, 0xc4, 0x39, 0x66, 0x93, 0x01, 0xed, 0x43, 0x3d, 0x02, 0x20, 0x46, 0x65, 0x56, 0x6d, - 0xc9, 0x3f, 0xb2, 0xba, 0x6d, 0x74, 0xcc, 0xe4, 0xb8, 0x39, 0xd3, 0xf0, 0x8f, 0x6c, 0xbf, 0xab, - 0x47, 0x5a, 0x23, 0xd2, 0xc5, 0x3d, 0xb8, 0xd0, 0x77, 0xd0, 0x52, 0x32, 0x18, 0xdc, 0xd0, 0x33, - 0x18, 0x5c, 0xe8, 0xa7, 0xdc, 0x03, 0x35, 0x8b, 0xc1, 0x6f, 0x18, 0x09, 0x6d, 0x2e, 0x4c, 0x2f, - 0x9e, 0x79, 0xae, 0xdf, 0x72, 0x97, 0xc1, 0x8c, 0xc8, 0x5c, 0xdf, 0x67, 0x62, 0x93, 0x8f, 0xe9, - 0x7b, 0x75, 0xbd, 0x40, 0xcd, 0xff, 0x84, 0x8a, 0xdd, 0xfc, 0xd5, 0x0c, 0x10, 0xde, 0xc2, 0xa2, - 0xd3, 0x71, 0xf6, 0xdc, 0xa6, 0x1b, 0xba, 0x78, 0x6b, 0x24, 0x2f, 0x58, 0x38, 0x7b, 0x4d, 0xaa, - 0x5e, 0xb9, 0x12, 0xb1, 0x34, 0x51, 0x99, 0x9d, 0x34, 0xd2, 0x7a, 0x08, 0xfb, 0x88, 0x62, 0xe6, - 0x49, 0x44, 0xf1, 0x5b, 0xf0, 0x5c, 0xa1, 0x83, 0x69, 0x8f, 0x65, 0x2d, 0x77, 0x3c, 0x5f, 0x0a, - 0x91, 0x74, 0xd9, 0xe0, 0x19, 0xaf, 0x13, 0xa1, 0xf5, 0xb4, 0x74, 0x10, 0x0b, 0xf3, 0x1f, 0x65, - 0xe0, 0x42, 0x6f, 0xc7, 0x88, 0x6f, 0x8b, 0x86, 0xc7, 0x38, 0x61, 0x78, 0xd2, 0xfa, 0x31, 0x83, - 0xd2, 0xf9, 0xd4, 0xfa, 0x91, 0xe7, 0x1d, 0xfe, 0x94, 0xfd, 0x58, 0x83, 0x49, 0x75, 0x26, 0x8f, - 0x7c, 0xda, 0x99, 0xac, 0x72, 0x31, 0xff, 0x63, 0x43, 0xcd, 0x86, 0x4b, 0xde, 0x4c, 0x0b, 0xd6, - 0xe4, 0x39, 0x2c, 0x38, 0x58, 0x8f, 0xd3, 0x94, 0x9b, 0xc0, 0x4c, 0xea, 0x26, 0x50, 0xa6, 0xe3, - 0xc8, 0xa6, 0xa6, 0xe3, 0x28, 0xc1, 0x6c, 0xad, 0xbb, 0x27, 0xeb, 0x46, 0xc4, 0x11, 0x2d, 0x7c, - 0xda, 0x96, 0xed, 0xd7, 0xef, 0xb4, 0x69, 0x24, 0xe6, 0xcf, 0x65, 0x60, 0xaa, 0xda, 0xec, 0x1e, - 0xb8, 0xed, 0x92, 0x13, 0x3a, 0xcf, 0xec, 0xbe, 0xf4, 0x6d, 0x6d, 0x5f, 0x1a, 0xc5, 0x24, 0x47, - 0x1f, 0x36, 0xd4, 0xa6, 0xf4, 0xfb, 0x06, 0xcc, 0xc6, 0x24, 0x5c, 0x39, 0xae, 0xc2, 0x08, 0xfb, - 0x21, 0xcc, 0xdc, 0x4b, 0x3d, 0x8c, 0x79, 0x26, 0xca, 0xe8, 0x2f, 0xb1, 0x53, 0xd4, 0x5f, 0x15, - 0x43, 0x0e, 0x17, 0xbf, 0xc4, 0xdf, 0xf7, 0x39, 0x7d, 0x06, 0xca, 0xdf, 0x37, 0x20, 0x9f, 0xfc, - 0x12, 0x72, 0x0f, 0xc6, 0x19, 0x27, 0x37, 0x7a, 0x2b, 0xe8, 0x95, 0x3e, 0xdf, 0x7c, 0x5d, 0xa0, - 0xf1, 0xe6, 0x61, 0xe7, 0x53, 0x0e, 0xb1, 0x24, 0x87, 0x8b, 0x16, 0x4c, 0xa9, 0x58, 0x29, 0xad, - 0x7b, 0x43, 0x5f, 0x11, 0xce, 0xa5, 0xf7, 0x83, 0xda, 0xea, 0x5f, 0xd7, 0x5a, 0x2d, 0xd6, 0x82, - 0x61, 0x5f, 0x8a, 0xc3, 0x34, 0x38, 0x5c, 0x4a, 0x55, 0x39, 0x4b, 0x11, 0xe8, 0x08, 0x8f, 0x6d, - 0x68, 0x79, 0x7d, 0x42, 0xce, 0x70, 0x43, 0xdb, 0x41, 0x88, 0xba, 0x22, 0x72, 0x1c, 0xf3, 0xef, - 0x66, 0xe1, 0x5c, 0xdc, 0x3c, 0xfe, 0x6e, 0x5e, 0xd5, 0xf1, 0x9d, 0x56, 0x70, 0xc2, 0x0c, 0xb8, - 0xd2, 0xd3, 0x34, 0x4c, 0x3d, 0x27, 0x9b, 0xa6, 0x34, 0xc8, 0x4c, 0x34, 0x08, 0x3d, 0x01, 0xbc, - 0x41, 0xb2, 0x19, 0xe4, 0x1e, 0x64, 0x6b, 0x34, 0x14, 0xba, 0xe8, 0x72, 0x4f, 0xaf, 0xaa, 0xed, - 0xba, 0x5e, 0xa3, 0x21, 0x1f, 0x44, 0x7e, 0x43, 0x8a, 0x6a, 0xb7, 0xf6, 0xd9, 0x9e, 0x6e, 0x17, - 0xc6, 0xca, 0x8f, 0x3a, 0xb4, 0x1e, 0x8a, 0x1c, 0x50, 0x57, 0x07, 0xf3, 0xe3, 0xb8, 0x4a, 0xa6, - 0x29, 0x8a, 0x00, 0xb5, 0xb3, 0x38, 0xca, 0xc5, 0xdb, 0x90, 0x93, 0x95, 0x9f, 0x2a, 0x63, 0xd2, - 0xdb, 0x30, 0xa9, 0x54, 0x72, 0x2a, 0xa1, 0xff, 0x0b, 0x03, 0xc6, 0xd8, 0x4a, 0xb0, 0x73, 0xfb, - 0x19, 0xd5, 0x48, 0xb7, 0x34, 0x8d, 0x34, 0xa7, 0x24, 0x06, 0xc1, 0x79, 0x79, 0xfb, 0x04, 0x5d, - 0x74, 0xcc, 0xd6, 0x95, 0x08, 0x99, 0xdc, 0x85, 0x71, 0x71, 0x70, 0x26, 0xa2, 0x97, 0xd4, 0x4c, - 0x23, 0xf2, 0x1c, 0x2e, 0x32, 0x95, 0xbd, 0x4e, 0x72, 0x6f, 0x21, 0xa9, 0x49, 0x29, 0xbe, 0x25, - 0xae, 0x65, 0x98, 0xf6, 0x30, 0x88, 0x9a, 0x67, 0xca, 0x50, 0xb2, 0xb0, 0xf7, 0xb9, 0xb8, 0x55, - 0x10, 0xde, 0xb1, 0xec, 0x20, 0x26, 0xe7, 0x64, 0x9a, 0xe0, 0x54, 0xc7, 0xd9, 0xcf, 0xe6, 0x79, - 0x8e, 0x09, 0xd9, 0xb0, 0xf7, 0x60, 0xea, 0x8e, 0xe7, 0x3f, 0x74, 0x7c, 0x7e, 0x73, 0x18, 0x3f, - 0x93, 0x3f, 0x31, 0x30, 0xbd, 0xcf, 0xe1, 0xfc, 0xee, 0xf1, 0x8f, 0x8f, 0x97, 0x46, 0x56, 0x3c, - 0xaf, 0x69, 0x69, 0xe8, 0x64, 0x13, 0xa6, 0xd7, 0x9d, 0x47, 0x22, 0xd2, 0x66, 0x6b, 0x6b, 0x4d, - 0x44, 0x45, 0x5e, 0x7d, 0x7c, 0xbc, 0x74, 0xa1, 0xe5, 0x3c, 0x8a, 0x4e, 0x78, 0xfb, 0x5f, 0x64, - 0xd7, 0xe9, 0x89, 0x0b, 0x33, 0x55, 0xcf, 0x0f, 0x45, 0x25, 0x6c, 0x63, 0x94, 0xed, 0x73, 0xc6, - 0x7a, 0x23, 0xf5, 0x8c, 0xf5, 0x02, 0xdb, 0x0d, 0xda, 0xfb, 0x11, 0xb9, 0x76, 0x73, 0x4f, 0x63, - 0x4c, 0xde, 0x83, 0xb9, 0x22, 0xf5, 0x43, 0x77, 0xdf, 0xad, 0x3b, 0x21, 0xbd, 0xe3, 0xf9, 0x2d, - 0x27, 0x14, 0x6b, 0x39, 0x7a, 0x65, 0xea, 0x94, 0x73, 0x6a, 0x39, 0xa1, 0xd5, 0x8b, 0x49, 0xbe, - 0x9e, 0x16, 0x67, 0x3a, 0x1a, 0x47, 0xd3, 0xa5, 0xc4, 0x99, 0xf6, 0x8b, 0xa6, 0xeb, 0x8d, 0x38, - 0x3d, 0x18, 0x14, 0xc4, 0x91, 0x5b, 0xb9, 0x29, 0xe2, 0x3f, 0x4e, 0x0e, 0xd2, 0x88, 0xc6, 0xad, - 0x4f, 0xb0, 0xc6, 0x32, 0x64, 0x57, 0xaa, 0x77, 0xd0, 0xcf, 0x26, 0x82, 0x26, 0x68, 0xfb, 0xd0, - 0x69, 0xd7, 0xd1, 0xc0, 0x13, 0x81, 0x53, 0xaa, 0xc2, 0x5b, 0xa9, 0xde, 0x21, 0x0e, 0xcc, 0x57, - 0xa9, 0xdf, 0x72, 0xc3, 0xaf, 0xde, 0xbc, 0xa9, 0x0c, 0x54, 0x0e, 0x9b, 0x76, 0x43, 0x34, 0x6d, - 0xa9, 0x83, 0x28, 0xf6, 0xa3, 0x9b, 0x37, 0x53, 0x87, 0x23, 0x6a, 0x58, 0x1a, 0x2f, 0x52, 0x86, - 0x99, 0x75, 0xe7, 0x51, 0x1c, 0xef, 0x16, 0x88, 0x88, 0xfd, 0x17, 0xa4, 0x60, 0xc5, 0xb1, 0x72, - 0xda, 0x0d, 0x33, 0x9d, 0x88, 0xbc, 0x0b, 0x93, 0xb1, 0x78, 0x05, 0x22, 0xd6, 0x11, 0x0d, 0x35, - 0x45, 0x38, 0x35, 0x23, 0x53, 0x41, 0x27, 0xdb, 0x91, 0x9f, 0x87, 0x5b, 0xe9, 0x22, 0x07, 0xee, - 0x0d, 0xd5, 0xcf, 0xe3, 0x60, 0x89, 0xf6, 0x59, 0xb3, 0xd1, 0xce, 0x88, 0x07, 0x00, 0x5a, 0x3a, - 0x17, 0xc5, 0x7d, 0x54, 0xf5, 0xbd, 0x56, 0x27, 0xc4, 0x80, 0x89, 0x84, 0xfb, 0xa8, 0x83, 0x25, - 0x29, 0xee, 0x23, 0x4e, 0x42, 0x28, 0x8c, 0xac, 0x79, 0xf5, 0xfb, 0x18, 0xc2, 0x30, 0xb1, 0xf2, - 0x31, 0x9b, 0xee, 0x4d, 0xaf, 0x7e, 0xff, 0xe9, 0x05, 0xa7, 0x20, 0x7b, 0xb2, 0xc1, 0x9a, 0xca, - 0xa4, 0x40, 0xf4, 0x89, 0x08, 0x78, 0x58, 0x88, 0xac, 0x77, 0xa5, 0x8c, 0xdb, 0x15, 0x5c, 0x68, - 0x64, 0xd7, 0x5a, 0x3a, 0x39, 0xa1, 0x90, 0x2f, 0xd1, 0xe0, 0x7e, 0xe8, 0x75, 0x8a, 0x4d, 0xb7, - 0xb3, 0xe7, 0x39, 0x7e, 0x43, 0x84, 0x38, 0xf4, 0xce, 0xef, 0xd7, 0x52, 0xe7, 0xf7, 0x5c, 0x83, - 0xd3, 0xdb, 0x75, 0xc9, 0xc0, 0xea, 0x61, 0x49, 0xbe, 0x0e, 0x33, 0x4c, 0xb8, 0xcb, 0x8f, 0x42, - 0xda, 0xe6, 0x23, 0x3f, 0x87, 0x2b, 0xf3, 0x82, 0x92, 0x91, 0x29, 0x2a, 0xe4, 0x32, 0x85, 0x93, - 0x9d, 0x46, 0x04, 0xaa, 0x4c, 0xe9, 0xac, 0x48, 0x03, 0x16, 0xd7, 0x9d, 0x47, 0xf1, 0xb5, 0x51, - 0x55, 0x48, 0x09, 0x0a, 0x18, 0x3e, 0x5b, 0xc3, 0x04, 0x2c, 0xce, 0x9c, 0xd0, 0x47, 0x5e, 0xfb, - 0x72, 0x22, 0x3f, 0x05, 0xe7, 0xc5, 0x67, 0x95, 0x30, 0xdd, 0xa0, 0xe7, 0x1f, 0xd5, 0x0e, 0x1d, - 0x0c, 0x75, 0x9d, 0x3f, 0x9d, 0x42, 0x94, 0x1d, 0xd6, 0x90, 0x7c, 0xec, 0x80, 0x33, 0xb2, 0xfa, - 0xd5, 0x40, 0xbe, 0x05, 0x33, 0xdc, 0x81, 0xba, 0xea, 0x05, 0x21, 0x6e, 0x42, 0x17, 0xfa, 0xd4, - 0x79, 0x39, 0xb5, 0xce, 0x3c, 0xf7, 0xca, 0xf2, 0x98, 0x47, 0xf4, 0x21, 0x27, 0xf8, 0x91, 0x77, - 0x60, 0xb2, 0xea, 0xb6, 0xf9, 0x15, 0xea, 0x4a, 0x75, 0xf1, 0x6c, 0xbc, 0xea, 0x74, 0xdc, 0xb6, - 0x2d, 0xf7, 0x7f, 0x9d, 0x48, 0x49, 0xa8, 0xd8, 0x64, 0x17, 0x26, 0x6b, 0xb5, 0xd5, 0x3b, 0x2e, - 0x5b, 0xf6, 0x3a, 0x47, 0x8b, 0xe7, 0xfa, 0xb4, 0xed, 0xe5, 0xd4, 0xb6, 0x4d, 0x07, 0xc1, 0x21, - 0x3e, 0xe8, 0x61, 0xd7, 0xbd, 0xce, 0x91, 0xa5, 0x72, 0x4a, 0x89, 0x65, 0x3a, 0xff, 0x94, 0x63, - 0x99, 0x2a, 0x30, 0xab, 0x04, 0x68, 0x60, 0x70, 0xc6, 0x62, 0xfc, 0xe6, 0x89, 0x1a, 0xbb, 0x94, - 0x8c, 0x48, 0x4f, 0xd2, 0xc9, 0x20, 0xa6, 0x0b, 0xa7, 0x0c, 0x62, 0xfa, 0x68, 0x24, 0x37, 0x9d, - 0x9f, 0x31, 0xff, 0x69, 0x26, 0x31, 0xb5, 0x49, 0x05, 0xc6, 0x85, 0x3c, 0x08, 0x5b, 0xa7, 0xb7, - 0x47, 0x5f, 0x48, 0xed, 0xd1, 0x71, 0x21, 0x61, 0x96, 0xa4, 0x27, 0x0f, 0x19, 0xab, 0x7d, 0xa7, - 0xdb, 0x94, 0x39, 0x2f, 0xbe, 0xc9, 0x67, 0x2e, 0x82, 0x34, 0x1d, 0x55, 0x3a, 0x7d, 0xd4, 0xa5, - 0x1e, 0xd4, 0x8b, 0xca, 0x4a, 0xd6, 0x46, 0xee, 0xf3, 0x44, 0x5f, 0xd9, 0x28, 0x74, 0x4f, 0xcf, - 0xea, 0xf5, 0xd4, 0x2a, 0x64, 0xb5, 0x98, 0xff, 0xc4, 0x80, 0x69, 0x4d, 0x37, 0x90, 0xdb, 0x4a, - 0x5c, 0x6a, 0x7c, 0x01, 0x41, 0xc3, 0x41, 0xc1, 0x49, 0x46, 0xac, 0xde, 0x16, 0x71, 0x3a, 0x99, - 0xfe, 0x74, 0xa9, 0xaf, 0xb0, 0x0c, 0xf6, 0x54, 0x44, 0x89, 0x43, 0x47, 0xfa, 0x24, 0x0e, 0xfd, - 0xc1, 0x1c, 0xcc, 0xe8, 0xc6, 0x23, 0xdb, 0xcd, 0xa1, 0xb7, 0x53, 0xba, 0xe2, 0x78, 0x2a, 0x5c, - 0x84, 0x68, 0xef, 0x3b, 0x20, 0x84, 0xbc, 0x0a, 0x10, 0xc5, 0xce, 0x48, 0x6f, 0xdb, 0xe8, 0xe3, - 0xe3, 0x25, 0xe3, 0x4d, 0x4b, 0x29, 0x20, 0x3f, 0x01, 0xb0, 0xe1, 0x35, 0x68, 0x94, 0xac, 0x79, - 0x80, 0xc7, 0xff, 0xb5, 0x9e, 0x24, 0x38, 0x67, 0xdb, 0x5e, 0x83, 0xf6, 0x66, 0xbc, 0x51, 0x38, - 0x92, 0xaf, 0xc0, 0xa8, 0xd5, 0x6d, 0x52, 0xe9, 0x5b, 0x9a, 0x94, 0xb3, 0xb5, 0xdb, 0x54, 0x9e, - 0x36, 0xf5, 0xbb, 0xc9, 0x83, 0x5e, 0x06, 0x20, 0x1f, 0xf0, 0xe4, 0x38, 0xe2, 0x1a, 0xfb, 0x68, - 0xec, 0x7f, 0x54, 0x74, 0x77, 0xcf, 0x45, 0x76, 0x85, 0x84, 0x6c, 0xc2, 0xb8, 0x58, 0x9a, 0xc5, - 0x41, 0xea, 0x8b, 0x69, 0x2e, 0x7c, 0xc5, 0x3e, 0x17, 0xd9, 0x76, 0x11, 0xac, 0x7b, 0xd5, 0xb9, - 0xdf, 0xef, 0x5d, 0x98, 0x60, 0xec, 0xf9, 0x5b, 0x9d, 0xe3, 0xb1, 0x97, 0x51, 0x69, 0x50, 0xf2, - 0xb9, 0xce, 0x98, 0x80, 0x7c, 0x1d, 0xd3, 0x6f, 0x8b, 0xae, 0x1e, 0x78, 0x12, 0x74, 0xb9, 0xa7, - 0xab, 0x17, 0x9c, 0x4e, 0x27, 0xe5, 0xb5, 0x83, 0x88, 0x1f, 0x39, 0x88, 0x6e, 0x8d, 0x0f, 0x93, - 0xd0, 0xe8, 0x5a, 0x4f, 0x05, 0x8b, 0xf2, 0x22, 0x74, 0x6f, 0xd2, 0x6d, 0x8d, 0x2f, 0xe9, 0x40, - 0x3e, 0x5e, 0x16, 0x45, 0x5d, 0x30, 0xa8, 0xae, 0x37, 0x7b, 0xea, 0x52, 0x07, 0xb0, 0xa7, 0xba, - 0x1e, 0xee, 0xa4, 0x11, 0xbf, 0x45, 0x2c, 0xea, 0x9b, 0x1c, 0x54, 0xdf, 0xab, 0x3d, 0xf5, 0xcd, - 0x37, 0xf6, 0x7a, 0xeb, 0x49, 0xf0, 0x24, 0xef, 0xc2, 0xb4, 0x84, 0xe0, 0xfc, 0x10, 0x0f, 0x24, - 0xf0, 0x57, 0xb4, 0xf7, 0x30, 0x1a, 0x5c, 0x4f, 0x09, 0xad, 0x22, 0xab, 0xd4, 0x5c, 0x3a, 0xa6, - 0x35, 0xea, 0xa4, 0x54, 0xe8, 0xc8, 0xe4, 0x13, 0x98, 0xac, 0xb4, 0xd8, 0x87, 0x78, 0x6d, 0x27, - 0xa4, 0x22, 0xf8, 0x55, 0x9e, 0x6a, 0x29, 0x25, 0x8a, 0xa8, 0xf2, 0xb7, 0xcc, 0xe2, 0x22, 0xd5, - 0x50, 0x56, 0x28, 0x58, 0xe7, 0x71, 0x7f, 0xaf, 0x90, 0x61, 0x19, 0x18, 0xfb, 0x42, 0xca, 0xc9, - 0x92, 0xc2, 0x5e, 0xa4, 0x8b, 0x60, 0x50, 0x5b, 0x4c, 0x88, 0x44, 0xba, 0x08, 0x95, 0x27, 0x79, - 0x0f, 0x26, 0x45, 0xae, 0xb7, 0x82, 0xb5, 0x11, 0x2c, 0xe6, 0xe3, 0x67, 0x6c, 0x65, 0x5a, 0x38, - 0xdb, 0xf1, 0x13, 0xe1, 0x05, 0x31, 0x3e, 0xf9, 0x2a, 0x2c, 0xec, 0xba, 0xed, 0x86, 0xf7, 0x30, - 0x10, 0xcb, 0x94, 0x50, 0x74, 0x73, 0x71, 0xec, 0xe5, 0x43, 0x5e, 0x6e, 0x4b, 0x93, 0xa9, 0x47, - 0xf1, 0xa5, 0x72, 0x20, 0x3f, 0xdd, 0xc3, 0x99, 0x4b, 0x10, 0x19, 0x24, 0x41, 0xcb, 0x3d, 0x12, - 0xd4, 0x5b, 0x7d, 0x52, 0x9c, 0x52, 0xab, 0x21, 0x1e, 0x10, 0x61, 0x65, 0x88, 0xb5, 0xea, 0x23, - 0xcf, 0x6d, 0x2f, 0xce, 0xa3, 0x2e, 0x7c, 0x2e, 0x79, 0x81, 0x06, 0xf1, 0xf8, 0xa3, 0x69, 0xf2, - 0xe9, 0x47, 0xdd, 0x7e, 0xf9, 0xb6, 0xa7, 0x39, 0xee, 0x52, 0x58, 0x93, 0x4f, 0x60, 0x8a, 0xfd, - 0x1f, 0x6d, 0xab, 0x16, 0xb4, 0x58, 0x04, 0x05, 0x53, 0xd4, 0x83, 0x63, 0x84, 0xc9, 0xe8, 0x52, - 0x76, 0x5c, 0x1a, 0x2b, 0xf2, 0x36, 0x00, 0xb3, 0x11, 0x85, 0x3a, 0x3e, 0x1b, 0x67, 0x0c, 0x44, - 0x53, 0xb2, 0x57, 0x11, 0xc7, 0xc8, 0x6c, 0xaf, 0xc7, 0x7e, 0xd5, 0xba, 0x0d, 0x8f, 0xcd, 0x8d, - 0x73, 0x48, 0x8b, 0x7b, 0x3d, 0xa4, 0x0d, 0x38, 0x5c, 0x95, 0x0e, 0x05, 0x9d, 0xac, 0xc2, 0x2c, - 0x66, 0x51, 0xa9, 0x34, 0x68, 0x3b, 0xc4, 0x73, 0x98, 0xc5, 0xf3, 0xca, 0x09, 0x0f, 0x2b, 0xb2, - 0xdd, 0xa8, 0x4c, 0xb5, 0xcb, 0x12, 0x64, 0x24, 0x80, 0xf9, 0x58, 0xbb, 0xc4, 0xe7, 0x45, 0x8b, - 0xd8, 0x49, 0xf2, 0x4c, 0xaf, 0x17, 0x83, 0xeb, 0x63, 0x36, 0x22, 0x8a, 0xe2, 0x92, 0xee, 0x4d, - 0xb5, 0xc2, 0x34, 0xee, 0xc4, 0x02, 0x72, 0xb7, 0x58, 0xd5, 0xf3, 0xcb, 0x04, 0x8b, 0x17, 0xf0, - 0x0b, 0x70, 0x98, 0x0f, 0xea, 0x1d, 0x3b, 0x91, 0x99, 0x46, 0x3b, 0xb8, 0xe9, 0xa5, 0x26, 0xdf, - 0x81, 0xb3, 0xd1, 0x4b, 0xd7, 0xbc, 0x48, 0xc8, 0xf5, 0xc5, 0x53, 0x6a, 0xe2, 0xc6, 0x5e, 0x54, - 0x75, 0x8f, 0x48, 0xa7, 0x57, 0x61, 0xfe, 0x1d, 0x03, 0x48, 0xef, 0x77, 0x0e, 0xed, 0xc8, 0x7e, - 0x4b, 0x89, 0x67, 0x56, 0x5f, 0xf3, 0x8b, 0x32, 0x4b, 0xa9, 0xeb, 0x5b, 0x1c, 0xf9, 0x7c, 0x59, - 0xb3, 0xa7, 0xfa, 0x06, 0xc1, 0x99, 0x7f, 0x66, 0xc0, 0x42, 0xda, 0x8c, 0x3a, 0x21, 0x93, 0xbb, - 0x99, 0x88, 0x9d, 0x43, 0xcf, 0x34, 0x8f, 0x9d, 0x8b, 0x22, 0xe6, 0x96, 0x60, 0x94, 0x7d, 0x81, - 0x3c, 0x5f, 0x44, 0xa3, 0x8d, 0x7d, 0x62, 0x60, 0x71, 0x38, 0x43, 0xe0, 0xd7, 0x07, 0x99, 0x55, - 0x37, 0xca, 0x11, 0x70, 0xc0, 0x2c, 0x0e, 0x67, 0x08, 0xcc, 0x38, 0x94, 0xc6, 0x0c, 0x22, 0x30, - 0x9b, 0x31, 0xb0, 0x38, 0x9c, 0x5c, 0x86, 0xf1, 0xcd, 0xf6, 0x1a, 0x75, 0x1e, 0xc8, 0x8c, 0x57, - 0xe8, 0x49, 0xf7, 0xda, 0x76, 0x93, 0xc1, 0x2c, 0x59, 0x68, 0x7e, 0xdf, 0x80, 0xb9, 0x9e, 0xc9, - 0x7c, 0x72, 0xb2, 0xfa, 0xc1, 0x51, 0x42, 0xc3, 0x7c, 0x1f, 0x6f, 0xfe, 0x48, 0x7a, 0xf3, 0xcd, - 0xdf, 0x1d, 0x81, 0xf3, 0x7d, 0x6c, 0xab, 0x38, 0xc2, 0xcf, 0x38, 0x31, 0xc2, 0xef, 0x1b, 0xcc, - 0x96, 0x71, 0xdc, 0x56, 0xb0, 0xe5, 0xc5, 0x2d, 0x8e, 0x83, 0x21, 0xb0, 0x4c, 0xe6, 0x92, 0x7e, - 0x49, 0x4c, 0xcd, 0x0b, 0x75, 0xa4, 0xb0, 0x43, 0xaf, 0xe7, 0x28, 0x55, 0x67, 0xd6, 0x13, 0x63, - 0x97, 0xfd, 0xd7, 0x24, 0xc6, 0x4e, 0x8f, 0x6c, 0x19, 0x79, 0xaa, 0x91, 0x2d, 0xe9, 0x67, 0xc7, - 0xa3, 0x4f, 0x72, 0x06, 0x5f, 0x84, 0xe9, 0x1a, 0x75, 0xfc, 0xfa, 0x61, 0x21, 0xe0, 0x83, 0x34, - 0x16, 0xe7, 0x99, 0x0a, 0xb0, 0xc0, 0x76, 0x82, 0xde, 0xb1, 0xd0, 0x68, 0xcc, 0xef, 0x67, 0xf4, - 0xd0, 0xc0, 0x7f, 0x1d, 0xe5, 0xe5, 0x2a, 0x8c, 0xee, 0x1e, 0x52, 0x5f, 0xaa, 0x1e, 0x6c, 0xc8, - 0x43, 0x06, 0x50, 0x1b, 0x82, 0x18, 0xe4, 0x0e, 0xcc, 0x54, 0x79, 0xff, 0xc9, 0x4e, 0x19, 0x89, - 0x17, 0xaa, 0x8e, 0x30, 0xa7, 0x52, 0x7a, 0x25, 0x41, 0x65, 0xfe, 0x14, 0x4c, 0xa9, 0x8d, 0x46, - 0xc5, 0xc2, 0x7e, 0x8b, 0x99, 0xcd, 0x15, 0x0b, 0x03, 0x58, 0x1c, 0x7e, 0xe2, 0x43, 0x14, 0x71, - 0x6f, 0x66, 0x4f, 0xea, 0x4d, 0x56, 0x39, 0xca, 0xad, 0x52, 0x39, 0xfe, 0x56, 0x2b, 0x0f, 0x19, - 0xc0, 0xe2, 0xf0, 0xa7, 0x5a, 0xf9, 0x7f, 0x2a, 0xf3, 0xa0, 0xbd, 0x05, 0x13, 0xf1, 0x02, 0x1d, - 0xa7, 0xfb, 0x9d, 0x4f, 0x5b, 0x76, 0x63, 0x4c, 0x56, 0xd5, 0x0e, 0xf5, 0xf7, 0xb4, 0x38, 0xe2, - 0x07, 0x0c, 0xa0, 0x56, 0x85, 0x18, 0xa7, 0x19, 0xd7, 0x1b, 0x30, 0x5e, 0x10, 0x6e, 0x44, 0x3e, - 0xa0, 0x3c, 0x56, 0xba, 0xc7, 0x67, 0x28, 0xb1, 0xcc, 0x1f, 0x18, 0x70, 0x36, 0xd5, 0x6e, 0x67, - 0xb5, 0xf2, 0x0d, 0x82, 0x22, 0xd6, 0xc9, 0xdd, 0x01, 0xc7, 0x38, 0x4d, 0x4c, 0xf4, 0xf0, 0xdf, - 0x62, 0xbe, 0x04, 0x13, 0x91, 0xd7, 0x88, 0x2c, 0xc8, 0xa1, 0xc3, 0xb3, 0x25, 0xe9, 0x7c, 0xf8, - 0x0b, 0x03, 0xc6, 0x58, 0x13, 0x9e, 0xd9, 0x6b, 0xce, 0xe9, 0x27, 0x8d, 0xec, 0x93, 0x86, 0xba, - 0xdc, 0xfc, 0x5b, 0x63, 0x00, 0x31, 0x32, 0xd9, 0x83, 0x99, 0xcd, 0x4a, 0xa9, 0xa8, 0x98, 0x9f, - 0x7a, 0x9e, 0xb7, 0xe8, 0x71, 0x14, 0x8e, 0x70, 0x14, 0xeb, 0x18, 0xcf, 0x6d, 0xd4, 0xd3, 0x4d, - 0xd3, 0x04, 0x47, 0x56, 0x47, 0xad, 0xb0, 0xbe, 0xa6, 0xd4, 0x91, 0x19, 0xb2, 0x8e, 0xc0, 0x69, - 0x35, 0xfb, 0xd4, 0xa1, 0x73, 0x24, 0x87, 0x90, 0xbf, 0x8b, 0xab, 0x98, 0x52, 0x4b, 0x76, 0x70, - 0x2d, 0x2f, 0x8b, 0x5a, 0x9e, 0xe3, 0xcb, 0x5f, 0x7a, 0x3d, 0x3d, 0x5c, 0x63, 0xc9, 0x1d, 0x39, - 0x51, 0x72, 0xff, 0x86, 0x01, 0x63, 0x7c, 0x99, 0x14, 0xa3, 0xd5, 0x67, 0x21, 0xde, 0x7d, 0x3a, - 0x0b, 0x71, 0x1e, 0x35, 0x97, 0xe6, 0x30, 0xe3, 0x65, 0xa4, 0x04, 0x63, 0xb5, 0xd0, 0x09, 0xbb, - 0x32, 0xe8, 0x5e, 0x1e, 0x27, 0xe3, 0x46, 0x92, 0x97, 0xc4, 0x91, 0xe5, 0x01, 0xfe, 0x56, 0xb9, - 0x70, 0x0c, 0x52, 0x89, 0x83, 0x9a, 0xc7, 0x4f, 0x0c, 0x6a, 0x96, 0x81, 0xe0, 0xe3, 0x22, 0xa8, - 0x59, 0x0f, 0x65, 0x5e, 0x83, 0x09, 0x11, 0x2a, 0xbd, 0x72, 0x24, 0xdc, 0x45, 0xd2, 0xed, 0x1b, - 0xc1, 0x95, 0xd7, 0x30, 0x39, 0xc8, 0xde, 0xd3, 0x5e, 0x7a, 0x89, 0x10, 0xc9, 0x26, 0x4c, 0xc4, - 0x97, 0xbe, 0xf5, 0xdc, 0x25, 0x11, 0x5c, 0xdc, 0x25, 0x92, 0xf1, 0x96, 0x29, 0x77, 0xbc, 0x63, - 0x1e, 0xe6, 0x2f, 0x1a, 0x90, 0x4f, 0xca, 0x0b, 0xbe, 0xc1, 0x2f, 0xaf, 0xc9, 0x47, 0x21, 0x8e, - 0xfc, 0x0d, 0xfe, 0xe8, 0x5e, 0xbd, 0xfe, 0xf6, 0xbb, 0x82, 0x4e, 0x96, 0x21, 0xc7, 0xa6, 0x5d, - 0x3b, 0xf1, 0x08, 0x7f, 0x57, 0xc0, 0xd4, 0x18, 0x17, 0x89, 0xa7, 0xcc, 0xda, 0x7f, 0x96, 0x85, - 0x49, 0x65, 0xb0, 0xc8, 0x55, 0xc8, 0x55, 0x82, 0x35, 0xaf, 0x7e, 0x9f, 0x36, 0xc4, 0xd1, 0xf9, - 0xf4, 0xe3, 0xe3, 0xa5, 0x09, 0x37, 0xb0, 0x9b, 0x08, 0xb4, 0xa2, 0x62, 0xb2, 0x02, 0xd3, 0xfc, - 0x2f, 0x99, 0x3f, 0x27, 0x13, 0x1f, 0xfb, 0x71, 0x64, 0x99, 0x39, 0x47, 0x35, 0x13, 0x34, 0x12, - 0xf2, 0x4d, 0x00, 0x0e, 0x18, 0xf2, 0xc9, 0x60, 0x39, 0x81, 0xcf, 0x8a, 0x0a, 0x52, 0x5e, 0x0e, - 0x56, 0x18, 0x92, 0x6f, 0xf1, 0x1b, 0xf0, 0x52, 0xb8, 0x4e, 0x0e, 0xc3, 0x37, 0x65, 0xcc, 0x1e, - 0xe3, 0x6f, 0xa7, 0x87, 0xcd, 0xab, 0x2c, 0xc9, 0xf7, 0x0c, 0xb8, 0x68, 0xd1, 0xba, 0xf7, 0x80, - 0xfa, 0x47, 0x85, 0x10, 0xb1, 0xd4, 0x1a, 0x4f, 0x8e, 0xd1, 0xbf, 0x25, 0x6a, 0x7c, 0xcd, 0x17, - 0x5c, 0xf0, 0x5a, 0x70, 0xab, 0x13, 0xda, 0x03, 0x9a, 0x30, 0xa0, 0x4a, 0xf3, 0xbf, 0x37, 0x94, - 0x29, 0x40, 0x36, 0x30, 0x27, 0x2d, 0x17, 0x16, 0x71, 0x0c, 0x12, 0x59, 0x78, 0x12, 0x6e, 0xd1, - 0xfd, 0x95, 0xe7, 0xc4, 0x29, 0xf7, 0x7c, 0x24, 0x72, 0x89, 0x5c, 0xb5, 0x1c, 0x48, 0x3e, 0x14, - 0xaf, 0x3b, 0x9f, 0xfc, 0x6c, 0x86, 0x5c, 0x6a, 0x46, 0xd8, 0x18, 0x29, 0x6f, 0x3a, 0x7f, 0x41, - 0x84, 0x81, 0x66, 0xb5, 0xa7, 0xdb, 0x18, 0x88, 0xb5, 0x23, 0x5a, 0x63, 0xe2, 0x9b, 0x1a, 0x8a, - 0xb4, 0xfe, 0x6a, 0x06, 0xf2, 0xc9, 0x89, 0x47, 0x3e, 0x80, 0x29, 0x79, 0x81, 0x7f, 0xd5, 0x11, - 0x69, 0x79, 0xa6, 0x44, 0x5a, 0x1c, 0x01, 0xb7, 0x0f, 0x1d, 0xed, 0x31, 0x14, 0x8d, 0x80, 0x2d, - 0xc8, 0x5b, 0xe2, 0x02, 0xa0, 0x32, 0x81, 0x42, 0x2f, 0xec, 0x24, 0x2e, 0x91, 0x4b, 0x34, 0xf2, - 0x16, 0x64, 0xd7, 0xef, 0x14, 0x44, 0x78, 0x94, 0xd4, 0x2f, 0xeb, 0x77, 0x0a, 0xfc, 0x48, 0x8b, - 0x9f, 0x54, 0xe9, 0xe7, 0x66, 0x0c, 0x9f, 0xac, 0x29, 0x39, 0x11, 0xc6, 0xb4, 0x0c, 0x9f, 0x12, - 0x1c, 0x7d, 0xdc, 0xc9, 0xc9, 0x11, 0xd4, 0x97, 0x62, 0xcc, 0xff, 0x20, 0x0b, 0x13, 0x51, 0xfd, - 0x84, 0x00, 0xda, 0x1b, 0x22, 0x42, 0x0a, 0xff, 0x26, 0x17, 0x20, 0x27, 0x4d, 0x0c, 0xf9, 0xa4, - 0x74, 0x20, 0xcc, 0x8b, 0x45, 0x90, 0xb6, 0x04, 0x37, 0x2f, 0x2c, 0xf9, 0x93, 0xdc, 0x84, 0xc8, - 0x50, 0xe8, 0x67, 0x51, 0x8c, 0xb0, 0x01, 0xb3, 0x22, 0x34, 0x32, 0x03, 0x19, 0x97, 0x5f, 0xee, - 0x9a, 0xb0, 0x32, 0x6e, 0x83, 0x7c, 0x00, 0x39, 0xa7, 0xd1, 0xa0, 0x0d, 0xdb, 0x91, 0xe7, 0x09, - 0x83, 0x84, 0x26, 0xc7, 0xb8, 0x71, 0x8d, 0x8e, 0x54, 0x85, 0x90, 0x14, 0x60, 0x02, 0x5f, 0x4e, - 0xef, 0x06, 0xb4, 0x31, 0xc4, 0xf2, 0x10, 0x73, 0xc8, 0x31, 0xb2, 0xed, 0x80, 0x36, 0xc8, 0x6b, - 0x30, 0xc2, 0x46, 0x53, 0xac, 0x07, 0xd1, 0xbb, 0x37, 0x9b, 0x5b, 0x55, 0xde, 0x61, 0xab, 0x67, - 0x2c, 0x44, 0x20, 0xaf, 0x40, 0xb6, 0xbb, 0xbc, 0x2f, 0x34, 0x7d, 0x3e, 0xce, 0x4f, 0x12, 0xa1, - 0xb1, 0x62, 0x72, 0x0b, 0x72, 0x0f, 0xf5, 0xd4, 0x16, 0x67, 0x13, 0xc3, 0x18, 0xe1, 0x47, 0x88, - 0x2b, 0x39, 0x18, 0xe3, 0xa7, 0xa1, 0xe6, 0x8b, 0x00, 0x71, 0xd5, 0xbd, 0xc1, 0x6c, 0xe6, 0x37, - 0x61, 0x22, 0xaa, 0x92, 0xbc, 0x00, 0x70, 0x9f, 0x1e, 0xd9, 0x87, 0x4e, 0xbb, 0xd1, 0xe4, 0x06, - 0xe7, 0x94, 0x35, 0x71, 0x9f, 0x1e, 0xad, 0x22, 0x80, 0x9c, 0x87, 0xf1, 0x0e, 0x1b, 0x55, 0xf9, - 0x0e, 0x99, 0x35, 0xd6, 0xe9, 0xee, 0x31, 0x09, 0x5d, 0x84, 0x71, 0x74, 0xa2, 0x88, 0x89, 0x36, - 0x6d, 0xc9, 0x9f, 0xe6, 0x6f, 0x64, 0x30, 0x59, 0x99, 0xd2, 0x4e, 0xf2, 0x32, 0x4c, 0xd7, 0x7d, - 0x8a, 0xcb, 0x11, 0x3e, 0x62, 0x27, 0xea, 0x99, 0x8a, 0x81, 0x95, 0x06, 0xb9, 0x0c, 0xb3, 0xf1, - 0xc3, 0x68, 0x76, 0x7d, 0x4f, 0xe4, 0xa1, 0x99, 0xb2, 0xa6, 0x3b, 0xf2, 0x65, 0xb4, 0xe2, 0x1e, - 0x5e, 0x4a, 0xcc, 0xab, 0x57, 0xfe, 0xc3, 0x28, 0x87, 0xba, 0x35, 0xab, 0xc0, 0xf1, 0x98, 0xf0, - 0x1c, 0x8c, 0x39, 0xce, 0x41, 0xd7, 0xe5, 0x17, 0xa4, 0xa6, 0x2c, 0xf1, 0x8b, 0xbc, 0x0e, 0x73, - 0x81, 0x7b, 0xd0, 0x76, 0xc2, 0xae, 0x2f, 0xb2, 0xc5, 0x51, 0x1f, 0x45, 0x6a, 0xda, 0xca, 0x47, - 0x05, 0x45, 0x0e, 0x27, 0x6f, 0x02, 0x51, 0xeb, 0xf3, 0xf6, 0xbe, 0x4d, 0xeb, 0x5c, 0xd4, 0xa6, - 0xac, 0x39, 0xa5, 0x64, 0x13, 0x0b, 0xc8, 0x4b, 0x30, 0xe5, 0xd3, 0x00, 0x4d, 0x32, 0xec, 0x36, - 0xcc, 0xe5, 0x69, 0x4d, 0x4a, 0xd8, 0x3d, 0x7a, 0x64, 0xae, 0xc0, 0x5c, 0xcf, 0x7c, 0x24, 0x6f, - 0x72, 0xeb, 0x5e, 0xac, 0xcf, 0x53, 0x7c, 0x33, 0xc3, 0x94, 0x94, 0xbe, 0x34, 0x0b, 0x24, 0xb3, - 0x0d, 0x53, 0xaa, 0x7e, 0x3d, 0x21, 0xc3, 0xcf, 0xb9, 0xe8, 0x55, 0xfd, 0x89, 0x95, 0xb1, 0xc7, - 0xc7, 0x4b, 0x19, 0xb7, 0x81, 0x37, 0x1a, 0xae, 0x40, 0x4e, 0x5a, 0x09, 0xea, 0xbb, 0xe0, 0xc2, - 0xa0, 0x3c, 0xb2, 0xa2, 0x52, 0xf3, 0x35, 0x18, 0x17, 0x2a, 0x74, 0xb0, 0x43, 0xcb, 0xfc, 0xd9, - 0x0c, 0xcc, 0x5a, 0x94, 0x4d, 0x70, 0xf1, 0xe2, 0xf6, 0xe7, 0xec, 0x89, 0x38, 0xed, 0xdb, 0x06, - 0x24, 0xd4, 0xfa, 0x1d, 0x03, 0xe6, 0x53, 0x70, 0x3f, 0x55, 0x9a, 0xe4, 0xdb, 0x30, 0x51, 0x72, - 0x9d, 0x66, 0xa1, 0xd1, 0x88, 0xee, 0x68, 0xa0, 0x35, 0xd8, 0x60, 0xd3, 0xc9, 0x61, 0x50, 0x75, - 0x31, 0x8d, 0x50, 0xc9, 0x35, 0x21, 0x14, 0xf1, 0x13, 0x03, 0xf2, 0x11, 0x3a, 0xe0, 0x6d, 0x8a, - 0x9f, 0xa0, 0xc3, 0x7b, 0xfd, 0x1c, 0x18, 0x47, 0xd5, 0x3c, 0xb3, 0x43, 0x97, 0x7e, 0xaf, 0x3f, - 0xf9, 0x79, 0x43, 0x6d, 0x3b, 0x7f, 0x31, 0x03, 0xe7, 0xd2, 0x09, 0x3f, 0x6d, 0xc6, 0x6b, 0xcc, - 0x66, 0xa6, 0xbc, 0xf3, 0x87, 0x19, 0xaf, 0x79, 0xea, 0x33, 0xc4, 0x8f, 0x11, 0xc8, 0x3e, 0x4c, - 0xaf, 0x39, 0x41, 0xb8, 0x4a, 0x1d, 0x3f, 0xdc, 0xa3, 0x4e, 0x38, 0x84, 0x05, 0xfb, 0x8a, 0x7c, - 0xaf, 0x19, 0x17, 0xb5, 0x43, 0x49, 0x99, 0x30, 0xf0, 0x74, 0xb6, 0x91, 0xa0, 0x8c, 0x0c, 0x21, - 0x28, 0x3f, 0x09, 0xb3, 0x35, 0xda, 0x72, 0x3a, 0x87, 0x9e, 0x4f, 0x85, 0x0f, 0xfe, 0x3a, 0x4c, - 0x47, 0xa0, 0x54, 0x69, 0xd1, 0x8b, 0x35, 0x7c, 0xa5, 0x23, 0x62, 0x55, 0xa2, 0x17, 0x9b, 0x7f, - 0x2b, 0x03, 0xe7, 0x0b, 0x75, 0x71, 0xac, 0x26, 0x0a, 0xe4, 0xe9, 0xff, 0x67, 0x5c, 0x37, 0xb9, - 0x01, 0x13, 0xeb, 0xce, 0xa3, 0x35, 0xea, 0x04, 0x34, 0x10, 0xf9, 0x46, 0xb9, 0xf9, 0xe5, 0x3c, - 0x8a, 0x4f, 0x9b, 0xac, 0x18, 0x47, 0xdd, 0x6c, 0x8e, 0x3c, 0xe1, 0x66, 0xd3, 0x84, 0xb1, 0x55, - 0xaf, 0xd9, 0x10, 0x8b, 0x93, 0x38, 0xff, 0x38, 0x44, 0x88, 0x25, 0x4a, 0xcc, 0x3f, 0x33, 0x60, - 0x26, 0x6a, 0x31, 0x36, 0xe1, 0x33, 0xef, 0x92, 0xcb, 0x30, 0x8e, 0x15, 0x45, 0x8f, 0xd5, 0xe3, - 0xa2, 0xd1, 0x64, 0x20, 0xdb, 0x6d, 0x58, 0xb2, 0x50, 0xed, 0x89, 0xd1, 0x27, 0xeb, 0x09, 0xf3, - 0xef, 0xe3, 0xd1, 0x8a, 0xfa, 0x95, 0x6c, 0x25, 0x52, 0x1a, 0x62, 0x0c, 0xd9, 0x90, 0xcc, 0x53, - 0x1b, 0x92, 0x6c, 0xdf, 0x21, 0xf9, 0x6e, 0x06, 0x26, 0xa3, 0xc6, 0x7e, 0xce, 0x12, 0xe2, 0x44, - 0xdf, 0x35, 0xd4, 0x4d, 0xa5, 0x9a, 0xa2, 0x2b, 0xc4, 0x85, 0xa0, 0x0f, 0x61, 0x4c, 0x4c, 0x26, - 0x23, 0x71, 0x0a, 0x9e, 0x18, 0xdd, 0x95, 0x19, 0xc1, 0x7a, 0x0c, 0x07, 0x34, 0xb0, 0x04, 0x1d, - 0x5e, 0x05, 0xdb, 0xa5, 0x7b, 0xe2, 0xa4, 0xed, 0x99, 0x5d, 0xa3, 0xd2, 0xaf, 0x82, 0xc5, 0x1f, - 0x36, 0xd4, 0xea, 0x74, 0x3c, 0x0a, 0xf9, 0x24, 0xc9, 0xc9, 0x29, 0x87, 0xaa, 0xdd, 0x3d, 0xf1, - 0xac, 0x30, 0xa6, 0x1c, 0xea, 0x74, 0xf7, 0x2c, 0x06, 0x23, 0x97, 0x61, 0xa4, 0xea, 0xbb, 0x0f, - 0xf0, 0xab, 0xc5, 0xab, 0xca, 0x1d, 0xdf, 0x7d, 0xa0, 0x1e, 0xe7, 0xb2, 0x72, 0xdc, 0xd0, 0xae, - 0xd5, 0x30, 0xbc, 0x1e, 0x0d, 0x6b, 0xb1, 0xa1, 0x6d, 0x06, 0xc9, 0x84, 0x87, 0x12, 0x8d, 0x2d, - 0x95, 0x2b, 0xd4, 0xf1, 0x45, 0x7a, 0x1c, 0xa1, 0xce, 0x70, 0xa9, 0xdc, 0x43, 0x30, 0x7f, 0x75, - 0xc3, 0x52, 0x91, 0x48, 0x13, 0x88, 0xf2, 0x53, 0x4e, 0xe0, 0x93, 0xf7, 0x78, 0x97, 0xa4, 0xf7, - 0x4d, 0x65, 0x6d, 0xab, 0xb3, 0x39, 0x85, 0xef, 0xd3, 0xf4, 0x11, 0x56, 0xc5, 0x65, 0x69, 0x74, - 0x64, 0xe4, 0x4e, 0x64, 0x26, 0xef, 0x9f, 0x00, 0xbf, 0x4c, 0x1d, 0xb9, 0x33, 0x62, 0x26, 0xe4, - 0x7d, 0x98, 0x54, 0x2f, 0x4d, 0xf0, 0xd0, 0xfe, 0xe7, 0xf9, 0x25, 0xe5, 0x3e, 0x89, 0x9f, 0x55, - 0x02, 0xb2, 0x07, 0xe7, 0x8b, 0x5e, 0x3b, 0xe8, 0xb6, 0x68, 0x43, 0x3b, 0x09, 0xae, 0x94, 0x70, - 0x83, 0x39, 0xc1, 0x23, 0xb0, 0xeb, 0x02, 0x45, 0xc4, 0xe8, 0xcb, 0x10, 0x23, 0x7d, 0x03, 0xd2, - 0x8f, 0x11, 0xd9, 0x82, 0xc9, 0x5a, 0x61, 0x7d, 0x4d, 0x06, 0xbe, 0x4f, 0xea, 0x6a, 0x23, 0x2e, - 0x29, 0xb1, 0x89, 0xc1, 0xef, 0x7e, 0x3a, 0xad, 0xa6, 0x8c, 0x70, 0x51, 0xbd, 0x8f, 0x0a, 0xb2, - 0xf9, 0x05, 0x55, 0xbe, 0x85, 0xb9, 0x31, 0x50, 0xbe, 0xcd, 0xff, 0x76, 0x0c, 0x66, 0x13, 0xd5, - 0x89, 0xfd, 0x8f, 0xd1, 0xb3, 0xff, 0xa9, 0x01, 0x70, 0x57, 0xd6, 0x90, 0x3e, 0x27, 0x19, 0x1c, - 0x39, 0x29, 0xe2, 0xb5, 0xa3, 0xb1, 0x52, 0xd8, 0x30, 0xa6, 0x5c, 0x12, 0x86, 0xf4, 0x39, 0x46, - 0x4c, 0xb9, 0x30, 0x29, 0x4c, 0x63, 0x36, 0x64, 0x09, 0x46, 0xf1, 0x36, 0xbb, 0x1a, 0x9b, 0xea, - 0x32, 0x80, 0xc5, 0xe1, 0xe4, 0x65, 0x18, 0x63, 0x8b, 0x73, 0xa5, 0x24, 0x26, 0x17, 0xea, 0x2c, - 0xb6, 0x7a, 0xb3, 0x95, 0x50, 0x14, 0x91, 0xdb, 0x30, 0xc5, 0xff, 0x12, 0x97, 0x77, 0xc6, 0xf4, - 0xb8, 0x0d, 0xdb, 0x6d, 0xc8, 0xfb, 0x3b, 0x1a, 0x1e, 0xb3, 0x5a, 0x6b, 0xdd, 0x3d, 0xf1, 0x62, - 0xd5, 0x78, 0x6c, 0xb5, 0x06, 0x1c, 0x88, 0xef, 0xb4, 0x44, 0x08, 0x6c, 0x8d, 0x14, 0xf1, 0x48, - 0x39, 0xdc, 0xab, 0xe0, 0x1a, 0xc9, 0x43, 0x91, 0x2c, 0x51, 0x42, 0xae, 0x72, 0xaf, 0x32, 0x9a, - 0x1b, 0x3c, 0x71, 0x28, 0xfa, 0x81, 0x71, 0xc3, 0x8b, 0x36, 0x47, 0x54, 0xcc, 0x2a, 0x67, 0x7f, - 0x97, 0x5b, 0x8e, 0xdb, 0x14, 0xe2, 0x8a, 0x95, 0x23, 0x2e, 0x65, 0x50, 0x2b, 0x46, 0x20, 0xef, - 0xc2, 0x0c, 0xfb, 0x51, 0xf4, 0x5a, 0x2d, 0xaf, 0x8d, 0xec, 0x27, 0xe3, 0x6b, 0x96, 0x48, 0x52, - 0xc7, 0x22, 0x5e, 0x4b, 0x02, 0x97, 0xe9, 0x29, 0x3c, 0x63, 0xea, 0x72, 0x7f, 0xf7, 0x54, 0xac, - 0xa7, 0x90, 0x34, 0xe0, 0x70, 0x4b, 0x45, 0x22, 0x6f, 0xc3, 0x34, 0xfb, 0x79, 0xd7, 0x7d, 0x40, - 0x79, 0x85, 0xd3, 0xf1, 0x71, 0x1f, 0x52, 0x1d, 0xb0, 0x12, 0x5e, 0x9f, 0x8e, 0x49, 0x3e, 0x86, - 0xb3, 0xc8, 0xa9, 0xee, 0x75, 0x68, 0xa3, 0xb0, 0xbf, 0xef, 0x36, 0x5d, 0xfe, 0xb6, 0x36, 0xbf, - 0xa6, 0x82, 0x3e, 0x46, 0x5e, 0x31, 0x62, 0xd8, 0x4e, 0x8c, 0x62, 0xa5, 0x53, 0x92, 0x5d, 0xc8, - 0x17, 0xbb, 0x41, 0xe8, 0xb5, 0x0a, 0x61, 0xe8, 0xbb, 0x7b, 0xdd, 0x90, 0x06, 0x8b, 0xb3, 0xda, - 0x65, 0x0e, 0x36, 0x39, 0xa2, 0x42, 0xee, 0x67, 0xa8, 0x23, 0x85, 0xed, 0x44, 0x24, 0x56, 0x0f, - 0x13, 0xf3, 0xbf, 0x31, 0x60, 0x5a, 0x23, 0x25, 0x6f, 0xc1, 0xd4, 0x1d, 0xdf, 0xa5, 0xed, 0x46, - 0xf3, 0x48, 0xd9, 0x00, 0xa1, 0x75, 0xbc, 0x2f, 0xe0, 0xfc, 0xab, 0x35, 0xb4, 0xc8, 0x7f, 0x90, - 0x49, 0x0d, 0x88, 0xb9, 0xc1, 0x43, 0xa4, 0x85, 0x80, 0x66, 0xe3, 0xdb, 0x65, 0x28, 0xa0, 0x42, - 0x3a, 0x15, 0x14, 0xf2, 0x1e, 0x8c, 0xf1, 0xd3, 0x28, 0x11, 0x9d, 0x71, 0x21, 0xed, 0x33, 0x79, - 0x38, 0x3e, 0x0a, 0x22, 0x1e, 0x82, 0x07, 0x96, 0x20, 0x32, 0x7f, 0xc5, 0x00, 0xd2, 0x8b, 0x7a, - 0x82, 0x3f, 0xe5, 0xc4, 0xc3, 0xf5, 0x0f, 0xa3, 0xd9, 0x98, 0xd5, 0x7c, 0x82, 0xac, 0x26, 0x5e, - 0xc0, 0x3b, 0x5e, 0xcc, 0x3a, 0xd5, 0xc1, 0xc3, 0x8b, 0xcd, 0xbf, 0x9e, 0x01, 0x88, 0xb1, 0xc9, - 0x97, 0x79, 0xd6, 0xc5, 0x8f, 0xbb, 0x4e, 0xd3, 0xdd, 0x77, 0xf5, 0x2c, 0x07, 0xc8, 0xe4, 0x27, - 0x65, 0x89, 0xa5, 0x23, 0x92, 0x0f, 0x60, 0xb6, 0x56, 0xd5, 0x69, 0x95, 0x0c, 0x73, 0x41, 0xc7, - 0x4e, 0x90, 0x27, 0xb1, 0x31, 0x0a, 0x4b, 0x1d, 0x0d, 0x1e, 0x85, 0xc5, 0x07, 0x42, 0x94, 0x30, - 0xc5, 0x52, 0xab, 0xe2, 0xd3, 0x5a, 0x0d, 0xda, 0xa8, 0x94, 0x84, 0x96, 0xc2, 0xd6, 0x05, 0x1d, - 0xbb, 0xc3, 0x0b, 0xf0, 0x3d, 0x27, 0x0d, 0x2f, 0xee, 0xc8, 0xd1, 0x3e, 0x21, 0xf7, 0xbf, 0x86, - 0xee, 0xa4, 0x96, 0x17, 0x52, 0xb1, 0x8b, 0x7e, 0x66, 0xed, 0xe9, 0xf8, 0x28, 0x73, 0x54, 0x8b, - 0x24, 0xd6, 0xbe, 0x8e, 0x63, 0xec, 0xdc, 0x8a, 0x8d, 0x5f, 0x7e, 0xa8, 0x29, 0x8f, 0x32, 0x15, - 0xd3, 0xef, 0xef, 0x19, 0x70, 0x36, 0x95, 0x96, 0x5c, 0x07, 0x88, 0x7d, 0x15, 0xa2, 0x97, 0xf8, - 0xb3, 0x5a, 0x11, 0xd4, 0x52, 0x30, 0xc8, 0x37, 0x92, 0x5e, 0x86, 0x93, 0x17, 0xc2, 0x8b, 0xf2, - 0xce, 0xac, 0xee, 0x65, 0x48, 0xf1, 0x2d, 0x98, 0xbf, 0x93, 0x85, 0x39, 0xe5, 0xca, 0x16, 0x6f, - 0xeb, 0x09, 0x51, 0x71, 0xf7, 0xe5, 0x13, 0x6e, 0x22, 0x80, 0x32, 0xa3, 0xbd, 0x4a, 0xd8, 0xc3, - 0xed, 0xba, 0x8a, 0xcc, 0x2f, 0x8a, 0xa3, 0xea, 0x14, 0x2f, 0xba, 0xf5, 0xc4, 0x50, 0x6a, 0xcc, - 0x49, 0x00, 0xd3, 0xa5, 0xa3, 0xb6, 0xd3, 0x8a, 0x6a, 0xe3, 0xc7, 0xef, 0xaf, 0xf7, 0xad, 0x4d, - 0xc3, 0xe6, 0xd5, 0x49, 0x9b, 0x73, 0xb1, 0xc1, 0xcb, 0x52, 0x02, 0xf6, 0x35, 0xaa, 0x8b, 0x1f, - 0xc0, 0x5c, 0x4f, 0xa3, 0x4f, 0x75, 0x67, 0x7d, 0x17, 0x48, 0x6f, 0x3b, 0x52, 0x38, 0xbc, 0xae, - 0x67, 0x44, 0x38, 0x1b, 0x1d, 0xce, 0xb5, 0x5a, 0x4e, 0x9b, 0xbf, 0x18, 0xbd, 0xb3, 0xac, 0xde, - 0x68, 0xff, 0xb5, 0x8c, 0x1a, 0x8f, 0xfb, 0xac, 0xcf, 0xba, 0x0f, 0xb5, 0x5d, 0xd6, 0x8b, 0xfd, - 0xc6, 0x74, 0xa8, 0xdd, 0xec, 0x8f, 0xb2, 0x70, 0xbe, 0x0f, 0x25, 0x39, 0x4a, 0x0a, 0x11, 0xdf, - 0xdd, 0xde, 0x1c, 0x5c, 0xe1, 0xd3, 0x10, 0x25, 0xf2, 0x65, 0x7e, 0x23, 0xa7, 0x8e, 0xaf, 0x35, - 0x88, 0x7d, 0x5d, 0xf4, 0x0e, 0x35, 0x87, 0x26, 0xaf, 0xe2, 0x70, 0x28, 0xf9, 0x40, 0xbe, 0x43, - 0xad, 0x5f, 0x19, 0x67, 0x18, 0xfc, 0xdd, 0xe9, 0xf8, 0x7e, 0x7d, 0xfa, 0xdb, 0xd3, 0x5f, 0x82, - 0x6c, 0x61, 0xb7, 0x26, 0xc6, 0x65, 0x46, 0x25, 0xdf, 0xad, 0xc5, 0x49, 0xb6, 0x1c, 0x2d, 0x1b, - 0x16, 0xa3, 0x60, 0x84, 0x77, 0x8b, 0x55, 0x31, 0x2a, 0x2a, 0xe1, 0xdd, 0x62, 0x35, 0x26, 0x3c, - 0xa8, 0x6b, 0x97, 0xf1, 0xee, 0x16, 0xab, 0x9f, 0x9d, 0xd8, 0xff, 0xcd, 0x0c, 0xbf, 0x46, 0xc4, - 0x3f, 0xec, 0x03, 0x98, 0xd2, 0x32, 0xd6, 0x18, 0xb1, 0x3d, 0x16, 0x65, 0xdb, 0x49, 0x44, 0x3f, - 0x68, 0x04, 0x32, 0x5d, 0x5d, 0xf4, 0x78, 0xa7, 0x1a, 0xbc, 0xa0, 0x3f, 0xf8, 0x99, 0x4c, 0x57, - 0x17, 0x91, 0x90, 0x5b, 0x90, 0xdb, 0xa2, 0x6d, 0xa7, 0x1d, 0x46, 0x8e, 0x36, 0x0c, 0xb6, 0x0b, - 0x11, 0xa6, 0x5b, 0x0d, 0x11, 0x22, 0x3e, 0xb5, 0xaa, 0x3c, 0x3d, 0x1a, 0xad, 0xc5, 0x3c, 0x04, - 0x54, 0x29, 0x49, 0xbc, 0x55, 0xab, 0x13, 0x99, 0xbf, 0x66, 0xc0, 0xb8, 0x18, 0x48, 0xe5, 0x81, - 0x40, 0x63, 0x88, 0x07, 0x02, 0x6f, 0xc3, 0x84, 0x88, 0x88, 0xd7, 0x5f, 0xa9, 0x15, 0x41, 0xf4, - 0x89, 0x57, 0x6a, 0x23, 0xd4, 0xa1, 0x63, 0xc9, 0xff, 0xae, 0x68, 0xd9, 0xdd, 0x62, 0x95, 0x2c, - 0x43, 0x6e, 0xcd, 0xab, 0x3b, 0xca, 0x3a, 0x87, 0x6a, 0xa7, 0x29, 0x60, 0x6a, 0x07, 0x49, 0x3c, - 0xfd, 0xf5, 0xdd, 0xcc, 0xf0, 0xaf, 0xef, 0x0e, 0xdb, 0x3e, 0x9a, 0xa2, 0x24, 0x76, 0x6e, 0xad, - 0xb9, 0x41, 0x48, 0x3e, 0x52, 0x63, 0xf4, 0x45, 0x91, 0xd4, 0x14, 0x17, 0xfb, 0x69, 0x8a, 0x9d, - 0x5b, 0x56, 0x0a, 0x15, 0x9e, 0xd7, 0xc4, 0x60, 0xfe, 0xe4, 0xf9, 0xe7, 0x2c, 0x0f, 0x73, 0xf2, - 0xf3, 0x86, 0x52, 0xd2, 0xff, 0x2c, 0x03, 0xe7, 0xd2, 0x09, 0xd5, 0x6f, 0x31, 0x06, 0x7c, 0xcb, - 0x15, 0xc8, 0xad, 0x7a, 0x41, 0xa8, 0x04, 0x3c, 0xa1, 0x5b, 0xf9, 0x50, 0xc0, 0xac, 0xa8, 0x94, - 0xed, 0xb9, 0xd9, 0xdf, 0xd1, 0xf4, 0x44, 0x7e, 0x78, 0x79, 0x86, 0xed, 0xb9, 0x79, 0x11, 0xb9, - 0x0b, 0x39, 0x4b, 0x84, 0x93, 0x27, 0xba, 0x46, 0x82, 0x23, 0x6b, 0x8a, 0xf8, 0x02, 0xa2, 0x25, - 0x0e, 0x12, 0x30, 0x52, 0x80, 0x71, 0x31, 0xfa, 0x89, 0x23, 0xc9, 0x14, 0x91, 0xd1, 0x73, 0x79, - 0x49, 0x3a, 0xa6, 0x51, 0xf0, 0x70, 0xa9, 0x52, 0x92, 0x91, 0xe1, 0xa8, 0x51, 0xf8, 0xe1, 0x93, - 0x9e, 0x3d, 0x2c, 0x42, 0x34, 0x7f, 0x36, 0x03, 0xb0, 0x4b, 0xf7, 0x9e, 0xed, 0xf4, 0xee, 0x5f, - 0xd2, 0x24, 0x4c, 0x89, 0xa7, 0x18, 0x3e, 0xbb, 0xfb, 0x26, 0xc6, 0x35, 0x0c, 0x9f, 0xdb, 0x7d, - 0x09, 0x46, 0xb9, 0xb7, 0x53, 0xd9, 0x24, 0x72, 0x37, 0x27, 0x87, 0x9b, 0x7b, 0xb0, 0x70, 0x97, - 0x86, 0xb1, 0x7b, 0x4b, 0x1e, 0x69, 0x0d, 0x66, 0xfb, 0x06, 0x4c, 0x08, 0x7c, 0xfd, 0xcd, 0x5c, - 0x79, 0x1f, 0x0d, 0x7d, 0x31, 0x12, 0x81, 0x69, 0xa3, 0x12, 0x6d, 0xd2, 0x90, 0x7e, 0xb6, 0xd5, - 0xd4, 0x80, 0xf0, 0x4f, 0xc1, 0x2f, 0x1b, 0xae, 0x86, 0x13, 0xfb, 0x67, 0x07, 0xce, 0x46, 0x6d, - 0x7f, 0x9a, 0x7c, 0x6f, 0xb0, 0x2d, 0xa5, 0x48, 0x83, 0x15, 0x73, 0x1c, 0x10, 0xd3, 0xf0, 0x08, - 0x2e, 0x4a, 0x82, 0x5d, 0x37, 0x0a, 0x0c, 0x1b, 0x8a, 0x96, 0xbc, 0x0b, 0x93, 0x0a, 0x8d, 0x48, - 0x34, 0x88, 0xee, 0xcf, 0x87, 0x6e, 0x78, 0x68, 0x07, 0x1c, 0xae, 0xba, 0x3f, 0x15, 0x74, 0xf3, - 0xeb, 0xf0, 0x5c, 0x14, 0x46, 0x9f, 0x52, 0x75, 0x82, 0xb9, 0x71, 0x3a, 0xe6, 0x1b, 0xf1, 0x67, - 0x55, 0xda, 0xd1, 0xa5, 0x2e, 0xc9, 0x9b, 0xa8, 0x9f, 0x25, 0x3e, 0xe6, 0xf9, 0x9e, 0x6b, 0x62, - 0xca, 0x6d, 0x30, 0xf3, 0x1d, 0xa5, 0xb1, 0x29, 0x0c, 0x35, 0x62, 0x23, 0x49, 0xfc, 0xb3, 0x19, - 0x98, 0xdd, 0xac, 0x94, 0x8a, 0x51, 0x54, 0xcb, 0xe7, 0x2c, 0xf7, 0xbc, 0xf6, 0x6d, 0xfd, 0xf5, - 0x8d, 0xb9, 0x0d, 0xf3, 0x89, 0x6e, 0x40, 0xd3, 0xe1, 0x7d, 0x1e, 0xee, 0x1e, 0x81, 0xa5, 0xd9, - 0x70, 0x2e, 0x8d, 0xfd, 0xce, 0x2d, 0x2b, 0x81, 0x6d, 0xfe, 0xc3, 0x5c, 0x82, 0xaf, 0x50, 0x61, - 0x6f, 0xc0, 0x44, 0x25, 0x08, 0xba, 0xd4, 0xdf, 0xb6, 0xd6, 0x54, 0x57, 0x81, 0x8b, 0x40, 0xbb, - 0xeb, 0x37, 0xad, 0x18, 0x81, 0x5c, 0x85, 0x9c, 0x48, 0xbd, 0x24, 0x75, 0x02, 0x7a, 0x6d, 0xa3, - 0xcc, 0x4d, 0x56, 0x54, 0x4c, 0xde, 0x82, 0x29, 0xfe, 0x37, 0x97, 0x36, 0xd1, 0xe1, 0xe8, 0x1c, - 0x14, 0xe8, 0x5c, 0x3a, 0x2d, 0x0d, 0x8d, 0x5c, 0x83, 0x6c, 0xa1, 0x68, 0xa9, 0x4f, 0x65, 0x3a, - 0x75, 0x9f, 0x3f, 0x99, 0xab, 0x6f, 0x22, 0x8a, 0x16, 0xb3, 0xfe, 0x84, 0x2b, 0xc9, 0x17, 0x9e, - 0x6c, 0x94, 0x00, 0xe9, 0x6d, 0x4a, 0x2c, 0x66, 0x08, 0x23, 0x37, 0x60, 0xbc, 0xe4, 0x06, 0x9d, - 0xa6, 0x73, 0x24, 0xfc, 0xd8, 0x3c, 0x23, 0x2d, 0x07, 0xa9, 0x32, 0x23, 0xb0, 0xc8, 0x55, 0x18, - 0x45, 0x27, 0xab, 0xf0, 0x65, 0xf3, 0xbc, 0xad, 0x0c, 0xa0, 0xe5, 0x6d, 0x65, 0x00, 0xcc, 0xec, - 0xc7, 0x13, 0x14, 0x4d, 0x28, 0x99, 0xfd, 0x92, 0x89, 0x89, 0x04, 0x4e, 0xef, 0xfd, 0x28, 0x78, - 0x9a, 0xf7, 0xa3, 0xf6, 0xe0, 0xfc, 0x5d, 0xf4, 0xde, 0xe8, 0x97, 0x54, 0xb7, 0xad, 0x8a, 0xf0, - 0x87, 0xe3, 0x89, 0x0f, 0x77, 0xf0, 0x24, 0xef, 0xb9, 0x26, 0x9e, 0xc3, 0xee, 0xc7, 0x88, 0x7c, - 0x15, 0x16, 0xd2, 0x8a, 0x84, 0xd7, 0x1c, 0x2f, 0x88, 0xa7, 0x57, 0xa0, 0xde, 0xd0, 0x4e, 0xe3, - 0x40, 0xd6, 0x20, 0xcf, 0xe1, 0x85, 0x46, 0xcb, 0x6d, 0x73, 0xcf, 0xff, 0x74, 0xfc, 0x4c, 0x99, - 0xe0, 0xea, 0xb0, 0x42, 0x7e, 0x02, 0xa0, 0x5d, 0x7c, 0x48, 0x50, 0x92, 0x5f, 0x32, 0xd8, 0x6e, - 0x8e, 0xa7, 0xf3, 0xd9, 0xb6, 0xd6, 0x02, 0x71, 0x95, 0xbf, 0xdf, 0x13, 0xe3, 0x5b, 0x4f, 0xe9, - 0x89, 0xf1, 0x29, 0x5f, 0xd4, 0x89, 0xb3, 0x48, 0x6b, 0x01, 0xbe, 0x6b, 0xd4, 0x6c, 0x7a, 0x0f, - 0xb7, 0xdb, 0x0f, 0xa8, 0xef, 0xee, 0xbb, 0xb4, 0xc1, 0x3f, 0x72, 0x16, 0x35, 0x38, 0x7f, 0xd7, - 0x08, 0xdf, 0xbe, 0xea, 0x46, 0x08, 0x3d, 0x1f, 0x9a, 0xca, 0x81, 0x6d, 0x3c, 0x65, 0x18, 0x3e, - 0xbf, 0x9d, 0x96, 0x8f, 0x37, 0x9e, 0x32, 0x66, 0xdf, 0x46, 0x31, 0x52, 0x85, 0x47, 0x23, 0x11, - 0x31, 0xbf, 0xff, 0x75, 0x8e, 0x6b, 0xe4, 0x42, 0x37, 0x3c, 0x94, 0x3a, 0x7c, 0x39, 0xed, 0x2a, - 0x01, 0x0f, 0x79, 0x52, 0xae, 0x12, 0xe8, 0x17, 0x08, 0xa4, 0x2b, 0x3d, 0x93, 0xea, 0x4a, 0x7f, - 0x03, 0x26, 0xf0, 0x65, 0xc7, 0x28, 0x66, 0x3b, 0x27, 0x7c, 0x95, 0x0c, 0xc8, 0x73, 0x21, 0xc5, - 0x08, 0xe4, 0x06, 0x00, 0xe6, 0x48, 0xe6, 0x0b, 0xbc, 0x92, 0x7b, 0x0e, 0x53, 0x29, 0x8b, 0x53, - 0x64, 0x05, 0x05, 0xd9, 0xd7, 0xac, 0x3b, 0xea, 0xb1, 0x33, 0x67, 0x1f, 0xf8, 0xfb, 0x02, 0x3d, - 0x46, 0x60, 0x9f, 0xa7, 0x0c, 0x93, 0x50, 0x2a, 0xf9, 0x9e, 0xb1, 0x54, 0x91, 0x30, 0xa2, 0x4b, - 0x06, 0xa8, 0xa2, 0x4e, 0x99, 0x12, 0x11, 0x5d, 0x51, 0x30, 0xab, 0x15, 0x23, 0x90, 0x2f, 0xc1, - 0x78, 0x91, 0xfa, 0xe1, 0xd6, 0xd6, 0x9a, 0x78, 0x9d, 0x9d, 0xed, 0xcb, 0x73, 0x98, 0x4c, 0x2b, - 0x0c, 0x9b, 0x3f, 0x3e, 0x5e, 0x9a, 0x0e, 0xdd, 0x16, 0xbd, 0x1e, 0x1d, 0xe3, 0x4a, 0x6c, 0xb2, - 0x02, 0x79, 0x7e, 0xc6, 0x18, 0x1b, 0x72, 0xa8, 0x66, 0x72, 0x5c, 0xe9, 0x89, 0x03, 0xc9, 0x87, - 0x74, 0x2f, 0x4a, 0x25, 0xd6, 0x83, 0x4f, 0xca, 0x32, 0x03, 0x9f, 0xfa, 0x91, 0x10, 0x7b, 0x16, - 0x84, 0x62, 0xd6, 0xbe, 0xb5, 0x97, 0x82, 0x14, 0x60, 0xba, 0xe8, 0xb5, 0x3a, 0x4e, 0xe8, 0x62, - 0x0a, 0xe6, 0x23, 0xa1, 0x51, 0xd0, 0x3b, 0x52, 0x57, 0x0b, 0xf4, 0x87, 0x1a, 0x95, 0x02, 0x72, - 0x07, 0x66, 0x2c, 0xaf, 0xcb, 0x06, 0x49, 0x6e, 0x69, 0xb8, 0xd2, 0x88, 0x9e, 0x00, 0x66, 0x63, - 0x69, 0x8b, 0xfd, 0x8b, 0x96, 0xe2, 0x42, 0xa3, 0x22, 0x1b, 0x29, 0xbe, 0x65, 0x55, 0x53, 0xa8, - 0x09, 0xc5, 0x7a, 0x98, 0xa5, 0xb8, 0xa5, 0x6f, 0xc1, 0x64, 0xad, 0xb6, 0xb9, 0x45, 0x83, 0xf0, - 0x4e, 0xd3, 0x7b, 0x88, 0x8a, 0x22, 0x27, 0xf2, 0x98, 0x06, 0x9e, 0x1d, 0xd2, 0x20, 0xb4, 0xf7, - 0x9b, 0xde, 0x43, 0x4b, 0xc5, 0x22, 0x3f, 0xa1, 0xbc, 0x5c, 0x89, 0x2b, 0xff, 0xec, 0x89, 0x2b, - 0x7f, 0xe2, 0x55, 0x4b, 0xb6, 0xfe, 0xa7, 0xbe, 0x6a, 0xc9, 0xd0, 0xf1, 0x02, 0x02, 0xdb, 0x8c, - 0x15, 0x1a, 0x0d, 0x9f, 0x06, 0x81, 0x98, 0xd1, 0xca, 0xbb, 0xbc, 0x0e, 0x2f, 0xd0, 0x2e, 0x20, - 0x28, 0x04, 0xe4, 0xbb, 0x06, 0x9c, 0x55, 0x63, 0x98, 0x71, 0xb2, 0xb4, 0x68, 0x3b, 0x5c, 0x9c, - 0xc3, 0x96, 0xbe, 0x79, 0x5d, 0x6a, 0xb4, 0xeb, 0x0a, 0xda, 0xf5, 0x07, 0x37, 0xaf, 0x2b, 0x4f, - 0xa7, 0xd5, 0x24, 0x11, 0x3e, 0xe1, 0xbd, 0x94, 0xca, 0x4f, 0xd5, 0x4e, 0x4e, 0x0a, 0x29, 0x5a, - 0x79, 0xb5, 0xc2, 0xfa, 0x5a, 0x6c, 0xaa, 0x7c, 0xbe, 0xa2, 0x83, 0xb5, 0x6f, 0x1b, 0x10, 0x1d, - 0xbc, 0x0d, 0xf3, 0x89, 0x6e, 0x90, 0x56, 0x9e, 0x06, 0x4e, 0x5a, 0x79, 0x09, 0x1a, 0x2b, 0x81, - 0x6d, 0xfe, 0x83, 0xf1, 0x04, 0x5f, 0x11, 0x11, 0x64, 0xc2, 0x18, 0x37, 0xe2, 0xd4, 0xc7, 0x7e, - 0xb8, 0x89, 0x67, 0x89, 0x12, 0x72, 0x01, 0xb2, 0xb5, 0xda, 0xa6, 0xfa, 0x14, 0x59, 0x10, 0x78, - 0x16, 0x83, 0xb1, 0x11, 0xc2, 0x60, 0x1f, 0x25, 0x6d, 0x16, 0xd3, 0x58, 0x16, 0x42, 0x59, 0x7f, - 0x4b, 0x93, 0x6a, 0x24, 0xee, 0x6f, 0x61, 0x52, 0xc5, 0x86, 0x54, 0x11, 0x16, 0x0b, 0x41, 0x40, - 0x7d, 0xfe, 0x1a, 0x33, 0xc6, 0x90, 0xf8, 0x62, 0xd9, 0x17, 0x8a, 0x19, 0x2b, 0x75, 0xea, 0x81, - 0xd5, 0x17, 0x91, 0x5c, 0x81, 0x5c, 0xa1, 0xdb, 0x70, 0x69, 0xbb, 0xae, 0xe5, 0x62, 0x70, 0x04, - 0xcc, 0x8a, 0x4a, 0xc9, 0xc7, 0x70, 0x56, 0x10, 0x49, 0xdb, 0x4f, 0xf4, 0xc0, 0x78, 0x3c, 0x7b, - 0xa4, 0x59, 0x12, 0x9f, 0x4f, 0xf2, 0x2e, 0x49, 0xa7, 0x24, 0x05, 0xc8, 0x97, 0x31, 0x1a, 0xbe, - 0x44, 0xb9, 0xab, 0xd4, 0xf3, 0xc5, 0xe3, 0xa9, 0x68, 0x44, 0xf2, 0x48, 0x79, 0xbb, 0x11, 0x15, - 0x5a, 0x3d, 0xe8, 0xe4, 0x1e, 0xcc, 0x27, 0x61, 0x4c, 0x07, 0x73, 0x7b, 0x11, 0xf3, 0xb6, 0xf4, - 0x70, 0x41, 0x2d, 0x9c, 0x46, 0x45, 0xf6, 0x60, 0x2e, 0x3e, 0x9f, 0xd7, 0xad, 0x48, 0x19, 0x4e, - 0x16, 0x95, 0x4b, 0x4b, 0xf2, 0x39, 0x21, 0x8c, 0xf3, 0xf1, 0x59, 0x7f, 0x64, 0x4d, 0x5a, 0xbd, - 0xec, 0x48, 0x03, 0x66, 0x6a, 0xee, 0x41, 0xdb, 0x6d, 0x1f, 0xdc, 0xa3, 0x47, 0x55, 0xc7, 0xf5, - 0x45, 0x60, 0xcf, 0x62, 0xf4, 0xc2, 0xe6, 0x51, 0xab, 0x45, 0x43, 0x1f, 0x57, 0x37, 0x56, 0x8e, - 0x57, 0xdc, 0x0c, 0xa6, 0xc6, 0x03, 0x4e, 0x87, 0xd7, 0x39, 0x3a, 0x8e, 0xab, 0xa9, 0x71, 0x9d, - 0xa7, 0x66, 0xc9, 0x4f, 0x0d, 0x69, 0xc9, 0x37, 0x61, 0xae, 0xdc, 0xae, 0xfb, 0x47, 0xe8, 0xb1, - 0x96, 0x8d, 0x9b, 0x3e, 0xa1, 0x71, 0xaf, 0x88, 0xc6, 0x3d, 0xef, 0x48, 0x09, 0x4b, 0x6b, 0x5e, - 0x2f, 0x63, 0x52, 0x13, 0x2f, 0xc5, 0x56, 0x4a, 0xd5, 0x4a, 0xdb, 0x0d, 0x5d, 0x7c, 0x76, 0x87, - 0x2f, 0x0f, 0xaf, 0x0a, 0x9e, 0x2f, 0x70, 0x8b, 0xcd, 0x6d, 0x74, 0x6c, 0x57, 0xa2, 0xf4, 0x3c, - 0x05, 0xab, 0xd2, 0x9b, 0xff, 0xd7, 0x38, 0xd7, 0x86, 0xaa, 0x85, 0xd5, 0x2f, 0x54, 0x29, 0x61, - 0x79, 0x65, 0x4e, 0x63, 0x79, 0x65, 0x4f, 0xb6, 0xbc, 0x46, 0x4e, 0xb2, 0xbc, 0x12, 0xa6, 0xd1, - 0xe8, 0xa9, 0x4d, 0xa3, 0xb1, 0x53, 0x98, 0x46, 0xe3, 0xa7, 0x32, 0x8d, 0x34, 0x1b, 0x2f, 0x77, - 0x92, 0x8d, 0xf7, 0xff, 0x1b, 0x52, 0xcf, 0xaa, 0x21, 0x95, 0xb6, 0xb8, 0x9e, 0xca, 0x90, 0xea, - 0x6f, 0x07, 0xe5, 0xff, 0x3f, 0xb6, 0x83, 0xfe, 0x12, 0xe4, 0x93, 0xaa, 0xf9, 0xe4, 0x44, 0x41, - 0x4f, 0x2d, 0x9f, 0x07, 0x5b, 0x38, 0x92, 0xaa, 0x91, 0x6d, 0xad, 0xaa, 0xbe, 0xfb, 0xc0, 0x09, - 0x69, 0xfc, 0x52, 0x25, 0x6e, 0xad, 0x3a, 0x1c, 0x8a, 0xd3, 0x55, 0x41, 0x89, 0xac, 0x82, 0x4c, - 0x9a, 0x55, 0x60, 0xfe, 0x5c, 0x06, 0xe6, 0x78, 0x0a, 0x82, 0x67, 0xdf, 0xa3, 0xf7, 0xbe, 0x66, - 0xeb, 0xc9, 0xc0, 0x9d, 0xc4, 0xd7, 0x0d, 0xf0, 0xe9, 0x7d, 0x13, 0xce, 0xf6, 0x74, 0x05, 0xda, - 0x7b, 0x25, 0x99, 0xfc, 0xa1, 0xc7, 0xe2, 0x5b, 0x4c, 0xaf, 0x64, 0xe7, 0x96, 0xd5, 0x43, 0x61, - 0xfe, 0x8b, 0x6c, 0x0f, 0x7f, 0xe1, 0xdd, 0x53, 0xfd, 0x75, 0xc6, 0xe9, 0xfc, 0x75, 0x99, 0xe1, - 0xfc, 0x75, 0x89, 0x65, 0x21, 0x3b, 0xcc, 0xb2, 0xf0, 0x31, 0x4c, 0x6f, 0x51, 0xa7, 0x15, 0x6c, - 0x79, 0x22, 0xa3, 0x20, 0x0f, 0xdc, 0x93, 0xb9, 0x1d, 0x58, 0x99, 0x34, 0x57, 0xa2, 0x00, 0x84, - 0x90, 0x11, 0x30, 0x55, 0xc6, 0x53, 0x0c, 0x5a, 0x3a, 0x07, 0xd5, 0x06, 0x1d, 0x1d, 0x60, 0x83, - 0xd6, 0x60, 0x4a, 0xd0, 0xc5, 0xd9, 0x91, 0x62, 0x63, 0x89, 0x15, 0x21, 0x5c, 0xd6, 0x1e, 0xbd, - 0x0c, 0x10, 0xd5, 0xce, 0xed, 0x24, 0x8d, 0x09, 0xeb, 0x82, 0x72, 0xbb, 0xd1, 0xf1, 0xdc, 0x36, - 0x76, 0xc1, 0x78, 0xdc, 0x05, 0x54, 0x80, 0x79, 0x17, 0x28, 0x48, 0xe4, 0x5d, 0x98, 0x29, 0x54, - 0x2b, 0x2a, 0x59, 0x4e, 0x7d, 0x27, 0xdc, 0xb5, 0x35, 0xd2, 0x04, 0xae, 0xf9, 0x8f, 0x73, 0x72, - 0x6e, 0x7d, 0xb6, 0xbe, 0x19, 0xdd, 0xdb, 0x92, 0x3d, 0xa5, 0xb7, 0x65, 0xe4, 0xa4, 0x95, 0x58, - 0x33, 0x0f, 0x46, 0x4f, 0x61, 0x1e, 0x8c, 0x3d, 0xb1, 0xe7, 0x64, 0xfc, 0x94, 0x0b, 0x7e, 0x42, - 0xcc, 0x73, 0xc3, 0x88, 0x79, 0xaa, 0x91, 0x30, 0xf1, 0xe4, 0x46, 0x02, 0x9c, 0xda, 0x48, 0x50, - 0x1e, 0x75, 0x9c, 0x1c, 0xea, 0x51, 0x47, 0x63, 0x88, 0x47, 0x1d, 0x3f, 0x57, 0x96, 0xc7, 0xb7, - 0xd2, 0x2d, 0x8f, 0xc1, 0xaa, 0xfe, 0x19, 0xb5, 0x3d, 0x7c, 0xec, 0xa0, 0x5d, 0xc7, 0x67, 0x3b, - 0xb0, 0x80, 0xdc, 0x80, 0x71, 0x99, 0x60, 0xc5, 0x88, 0x37, 0xb3, 0xbd, 0x99, 0x55, 0x24, 0x16, - 0xdb, 0xac, 0x49, 0x62, 0x71, 0x19, 0x99, 0xe7, 0x92, 0x10, 0x30, 0x2d, 0x97, 0x84, 0x80, 0x99, - 0xff, 0xde, 0x88, 0x9c, 0x84, 0x6c, 0x33, 0x21, 0x1e, 0x30, 0x5a, 0x51, 0x3a, 0x5d, 0xb1, 0x7c, - 0x12, 0xdd, 0x9a, 0x08, 0xb2, 0xd1, 0x49, 0x3e, 0x4d, 0x76, 0x1a, 0x25, 0x67, 0x77, 0x76, 0x88, - 0x9c, 0xdd, 0x6f, 0x6b, 0x09, 0xaf, 0x47, 0xe2, 0x0c, 0xab, 0x4c, 0x30, 0x07, 0xa7, 0xba, 0xbe, - 0xad, 0x66, 0xa6, 0x1e, 0x8d, 0xef, 0x6d, 0x23, 0xe5, 0x80, 0x9c, 0xd4, 0x91, 0x29, 0x37, 0x76, - 0x9a, 0x4c, 0x4d, 0xe3, 0xff, 0x4a, 0x33, 0x35, 0x95, 0x01, 0x94, 0x57, 0x6d, 0xb8, 0x6f, 0xfb, - 0x55, 0xd6, 0x4d, 0x27, 0xbf, 0x68, 0xa3, 0x10, 0x9a, 0xff, 0x72, 0x0e, 0xe6, 0x6a, 0xb5, 0xcd, - 0x92, 0xeb, 0x1c, 0xb4, 0xbd, 0x20, 0x74, 0xeb, 0x95, 0xf6, 0xbe, 0xc7, 0xec, 0x98, 0x68, 0x42, - 0x2b, 0x59, 0x83, 0xe2, 0xc9, 0x1c, 0x15, 0x33, 0x3b, 0xb9, 0xec, 0xfb, 0x9e, 0xaf, 0xda, 0xc9, - 0x94, 0x01, 0x2c, 0x0e, 0x67, 0xa6, 0x42, 0xad, 0xcb, 0x9f, 0x27, 0xe1, 0xc7, 0x0d, 0x68, 0x2a, - 0x04, 0x1c, 0x64, 0xc9, 0x32, 0x42, 0x7b, 0x05, 0x56, 0x98, 0x8e, 0xe7, 0xb5, 0x7c, 0x4f, 0x71, - 0x31, 0x57, 0x57, 0x62, 0x39, 0xc1, 0x5b, 0x1b, 0x1d, 0x84, 0xab, 0x67, 0x53, 0x3d, 0x73, 0xe0, - 0x08, 0xce, 0x6a, 0xb7, 0x0f, 0x86, 0xf5, 0xe3, 0x5c, 0x13, 0xa6, 0x89, 0x89, 0x97, 0xa8, 0x52, - 0x9c, 0x39, 0x6a, 0xca, 0xd8, 0xd4, 0x1a, 0xc8, 0xcf, 0x19, 0xf0, 0x42, 0x6a, 0x49, 0x34, 0xbb, - 0x27, 0xb5, 0x9c, 0x5b, 0x8a, 0xd2, 0xc0, 0x27, 0x5d, 0x5e, 0xef, 0x57, 0xb5, 0x9d, 0xa2, 0x0a, - 0x06, 0xd7, 0x44, 0xfe, 0xb1, 0x01, 0xe7, 0x35, 0x8c, 0x48, 0x5d, 0x05, 0xb8, 0xac, 0xf4, 0x95, - 0xeb, 0x6f, 0x3f, 0x1d, 0xb9, 0x7e, 0x59, 0xff, 0x96, 0x58, 0x9b, 0xaa, 0xdf, 0xd0, 0xaf, 0x85, - 0xe4, 0x01, 0xcc, 0x61, 0x91, 0xf4, 0x29, 0x31, 0x99, 0x15, 0xae, 0xa8, 0x85, 0xb8, 0xd9, 0xfc, - 0x46, 0x0d, 0xbe, 0x67, 0xb0, 0xfc, 0xa3, 0xe3, 0xa5, 0x69, 0x0d, 0x1d, 0xd3, 0x7d, 0x62, 0x1b, - 0x22, 0xc7, 0x94, 0xdb, 0xde, 0xf7, 0xb4, 0x67, 0x77, 0x93, 0x55, 0x90, 0xff, 0xc4, 0x80, 0x45, - 0x06, 0xe5, 0x9f, 0x71, 0xc7, 0xf7, 0x5a, 0x51, 0xb9, 0x3c, 0xe4, 0xec, 0xd3, 0x6d, 0xcd, 0xa7, - 0xd3, 0x6d, 0xaf, 0x62, 0x93, 0xb9, 0x4e, 0xb0, 0xf7, 0x7d, 0xaf, 0x15, 0x37, 0x5f, 0x7b, 0xb5, - 0xa5, 0x5f, 0x23, 0xc9, 0xcf, 0x18, 0x70, 0x41, 0xdb, 0xd6, 0xab, 0x49, 0x2e, 0xc5, 0xbd, 0x25, - 0x79, 0x22, 0xae, 0x16, 0xad, 0x5c, 0x17, 0xf2, 0x7f, 0x19, 0x5b, 0x10, 0xaf, 0x16, 0xd8, 0x16, - 0xbb, 0xc5, 0xb1, 0x94, 0x26, 0xf4, 0xaf, 0x85, 0xb8, 0x30, 0x87, 0x47, 0x34, 0xda, 0x61, 0xfc, - 0x42, 0xff, 0xc3, 0xf8, 0x28, 0xef, 0x34, 0x26, 0x12, 0xec, 0x7f, 0x22, 0xdf, 0xcb, 0x95, 0xfc, - 0x34, 0x5c, 0xe8, 0x01, 0x46, 0xb3, 0xed, 0x6c, 0xdf, 0xd9, 0xf6, 0xfa, 0xe3, 0xe3, 0xa5, 0xd7, - 0xd2, 0x6a, 0x4b, 0x9b, 0x69, 0xfd, 0x6b, 0x20, 0x0e, 0x40, 0x5c, 0x28, 0x9e, 0x81, 0x49, 0x17, - 0xd0, 0xd7, 0x85, 0x7c, 0x28, 0xf8, 0x4c, 0x97, 0x2b, 0x6d, 0x50, 0x97, 0xbc, 0x18, 0x89, 0x50, - 0x98, 0x52, 0x92, 0x28, 0x1e, 0xe1, 0x7b, 0x30, 0x7d, 0x2b, 0xf9, 0xd1, 0xf1, 0x92, 0x86, 0xcd, - 0x4c, 0x5a, 0x35, 0x3b, 0xa3, 0x6a, 0xd2, 0x6a, 0x88, 0xe4, 0xf7, 0x0d, 0x58, 0x60, 0x80, 0x58, - 0xa8, 0xc4, 0x47, 0x2d, 0x0e, 0x92, 0xfa, 0xc3, 0xa7, 0x23, 0xf5, 0x2f, 0x61, 0x1b, 0x55, 0xa9, - 0xef, 0xe9, 0x92, 0xd4, 0xc6, 0xa1, 0xb4, 0x6b, 0xa7, 0x81, 0x9a, 0xb4, 0x5f, 0x18, 0x42, 0xda, - 0xf9, 0x00, 0x9c, 0x2c, 0xed, 0x7d, 0x6b, 0x21, 0x5b, 0x30, 0x25, 0xac, 0x59, 0xde, 0x61, 0x2f, - 0x6a, 0x39, 0xdb, 0xd4, 0x22, 0xbe, 0xc5, 0x10, 0x39, 0x26, 0x7b, 0xbe, 0x50, 0xe3, 0x42, 0xda, - 0x30, 0xcf, 0x7f, 0xeb, 0x1b, 0xfb, 0xa5, 0xbe, 0x1b, 0xfb, 0x2b, 0xe2, 0x8b, 0x2e, 0x09, 0xfe, - 0x89, 0xfd, 0xbd, 0x9a, 0xbe, 0x3d, 0x85, 0x31, 0xe9, 0x00, 0xd1, 0xc0, 0x7c, 0xd2, 0x5e, 0x1a, - 0xbc, 0x9d, 0x7f, 0x4d, 0xd4, 0xb9, 0x94, 0xac, 0x33, 0x39, 0x73, 0x53, 0x78, 0x13, 0x07, 0x66, - 0x05, 0x94, 0xed, 0x5d, 0x51, 0xc3, 0xbf, 0xa4, 0x5d, 0x71, 0x4e, 0x94, 0xf2, 0x47, 0x51, 0x64, - 0x4d, 0x78, 0x05, 0x3d, 0xa1, 0xd0, 0x93, 0xfc, 0xcc, 0xef, 0x1a, 0x3d, 0x75, 0xb0, 0x3d, 0x32, - 0xfe, 0x50, 0x2e, 0x23, 0xe2, 0x1e, 0x99, 0x73, 0xc4, 0xbd, 0x7a, 0x8c, 0xc0, 0x6c, 0x1b, 0x35, - 0xd1, 0x45, 0x56, 0xbc, 0xa3, 0xca, 0x41, 0xf1, 0xd6, 0x6d, 0x49, 0xc6, 0x34, 0x65, 0x63, 0x1b, - 0x09, 0x63, 0x9a, 0x44, 0x24, 0x93, 0xf9, 0x33, 0x19, 0x5d, 0x4a, 0xc8, 0x15, 0xc5, 0xcc, 0x56, - 0x52, 0x6d, 0x48, 0x33, 0x5b, 0x31, 0xae, 0xff, 0x9e, 0x01, 0xf3, 0x9b, 0xfe, 0x81, 0xd3, 0x76, - 0xbf, 0xc3, 0x13, 0x71, 0x79, 0xd8, 0x8d, 0xd1, 0x3d, 0x8c, 0xcf, 0x34, 0xa1, 0xb7, 0xa7, 0x54, - 0xcc, 0x06, 0x16, 0x47, 0xd8, 0x4a, 0x6b, 0x0f, 0x46, 0x89, 0x62, 0xc3, 0x94, 0xbc, 0xea, 0x1c, - 0x9d, 0xc3, 0xcd, 0x5f, 0xcc, 0xc0, 0xa4, 0x22, 0xb1, 0xe4, 0x8b, 0x30, 0xa5, 0xf2, 0x51, 0xfd, - 0x2b, 0x6a, 0xb5, 0x96, 0x86, 0x85, 0x0e, 0x16, 0xea, 0xb4, 0x34, 0x07, 0x0b, 0x93, 0x4b, 0x84, - 0x9e, 0x72, 0x27, 0xf2, 0x41, 0xca, 0x4e, 0xe4, 0x54, 0x4f, 0xef, 0xbc, 0xdb, 0xbb, 0x1f, 0x19, - 0xfe, 0xa5, 0x1c, 0xf3, 0x97, 0x0d, 0xc8, 0x27, 0xe7, 0xd4, 0x67, 0xd2, 0x2b, 0xa7, 0xf0, 0x64, - 0xff, 0x42, 0x06, 0xf2, 0xf8, 0xc2, 0x17, 0x6d, 0xc8, 0xd8, 0xf7, 0x67, 0x35, 0xa0, 0xe0, 0x3d, - 0xcd, 0xc9, 0xfc, 0x5c, 0xb4, 0x0c, 0xa8, 0x1f, 0x37, 0x20, 0x27, 0xc8, 0xc8, 0x0f, 0x7e, 0x73, - 0xe9, 0x8c, 0xf9, 0x09, 0x2c, 0x24, 0xbb, 0x03, 0x1d, 0xcd, 0x05, 0x98, 0xd5, 0xe1, 0xc9, 0x74, - 0xc9, 0x49, 0x2a, 0x2b, 0x89, 0x6f, 0xfe, 0x71, 0x26, 0xc9, 0x5b, 0x04, 0x17, 0x30, 0xa5, 0xd3, - 0x76, 0xf6, 0x9a, 0x51, 0x46, 0x57, 0xf1, 0x78, 0x33, 0x82, 0x2c, 0x59, 0x76, 0x9a, 0xc4, 0xd9, - 0x51, 0x04, 0x77, 0x36, 0x3d, 0x82, 0x9b, 0xdc, 0x4e, 0x44, 0xc4, 0x28, 0xd7, 0x8d, 0x1f, 0xd2, - 0x3d, 0x3b, 0x8e, 0x8a, 0x49, 0x04, 0xc2, 0x14, 0x61, 0x41, 0xcb, 0xc9, 0x26, 0xe9, 0x47, 0x63, - 0xd7, 0x66, 0x88, 0x05, 0x9c, 0x38, 0x15, 0x99, 0xac, 0xc2, 0x38, 0x6b, 0xe6, 0xba, 0xd3, 0x11, - 0xfe, 0x63, 0xf5, 0x8d, 0x5b, 0xb9, 0xd6, 0x28, 0x57, 0x3a, 0x9a, 0x94, 0xad, 0xd0, 0xda, 0xcb, - 0x55, 0x1c, 0xd1, 0xfc, 0x73, 0x83, 0xcd, 0xff, 0xfa, 0xfd, 0xcf, 0x59, 0x4a, 0x6f, 0xf6, 0x49, - 0x03, 0x62, 0x5f, 0xfe, 0x24, 0xc3, 0x93, 0xf2, 0x0a, 0xf1, 0x79, 0x1b, 0xc6, 0xb6, 0x1c, 0xff, - 0x80, 0x86, 0x22, 0x7d, 0xac, 0xca, 0x85, 0x17, 0xc4, 0x97, 0xa1, 0x43, 0xfc, 0x6d, 0x09, 0x02, - 0xd5, 0x75, 0x95, 0x19, 0xca, 0x75, 0xa5, 0x38, 0x42, 0xb3, 0x4f, 0xcd, 0x11, 0xfa, 0xb5, 0x28, - 0x1f, 0x6e, 0x21, 0x1c, 0x22, 0xe5, 0xd7, 0xa5, 0x64, 0x72, 0xe8, 0x9e, 0xe4, 0x6c, 0x31, 0x3b, - 0x72, 0x5b, 0x4d, 0x37, 0xad, 0x04, 0x45, 0x9f, 0x90, 0x58, 0xda, 0xfc, 0xfb, 0x59, 0xde, 0xc7, - 0xa2, 0xa3, 0x2e, 0x6b, 0x17, 0x26, 0x70, 0x9e, 0x30, 0x45, 0xaf, 0xde, 0x5d, 0xc3, 0x43, 0xfe, - 0xcb, 0x30, 0xc2, 0x64, 0x53, 0xf4, 0x26, 0x7f, 0x9f, 0xd4, 0x6b, 0x6a, 0x77, 0xdc, 0x58, 0x39, - 0x9b, 0xcb, 0xb8, 0x26, 0xa9, 0x99, 0xed, 0x71, 0xd9, 0x52, 0xe7, 0x32, 0x62, 0xe0, 0xb5, 0x39, - 0xaf, 0x41, 0xd5, 0x29, 0xda, 0xd6, 0x1f, 0x6a, 0xc4, 0x72, 0xf6, 0xa5, 0x51, 0x82, 0x59, 0xf5, - 0x4b, 0x5b, 0xfb, 0x8e, 0xcd, 0x13, 0x9b, 0xaa, 0x5f, 0x1a, 0xe7, 0xa2, 0x2d, 0xc3, 0x8c, 0xfe, - 0xc8, 0x93, 0x88, 0x0b, 0xc2, 0xfb, 0x8e, 0x89, 0x07, 0xa2, 0x54, 0x2f, 0xb4, 0x4e, 0x44, 0x56, - 0x60, 0x5a, 0x4b, 0x68, 0x23, 0x8e, 0x71, 0xd0, 0x17, 0xa9, 0xa7, 0xc3, 0x51, 0x7d, 0x91, 0x1a, - 0x09, 0x5b, 0xcd, 0x45, 0xfb, 0x95, 0xc3, 0x9c, 0x9e, 0xb6, 0x0b, 0x1c, 0xe5, 0x62, 0xd1, 0x17, - 0x20, 0x2f, 0x74, 0x4b, 0xf4, 0x24, 0x01, 0x1e, 0xae, 0x56, 0x4a, 0x96, 0xaa, 0x0f, 0xea, 0x6e, - 0xc3, 0xb7, 0x10, 0x6a, 0x7e, 0xdf, 0x80, 0x0b, 0x1b, 0x34, 0x7c, 0xe8, 0xf9, 0xf7, 0x2d, 0x1a, - 0x84, 0xbe, 0xcb, 0x5f, 0x38, 0xc0, 0x19, 0xf5, 0x45, 0xf2, 0xae, 0x7c, 0x3b, 0x5b, 0x57, 0xf1, - 0xc9, 0x3a, 0x56, 0xa6, 0x85, 0x24, 0x8e, 0x62, 0xe0, 0x8a, 0x7c, 0x33, 0xfb, 0x6d, 0xf1, 0x66, - 0x76, 0x66, 0x30, 0x71, 0x34, 0xb3, 0x1b, 0xb4, 0x2d, 0xdf, 0xca, 0xfe, 0xe5, 0x0c, 0x9c, 0x4d, - 0x69, 0xd6, 0xce, 0x17, 0x9f, 0x51, 0xf5, 0xb6, 0xa2, 0xa9, 0xb7, 0x4b, 0x82, 0xb4, 0x6f, 0xc7, - 0xa7, 0x6a, 0xbb, 0x5f, 0x37, 0xe0, 0xbc, 0x2e, 0x6b, 0x22, 0xb8, 0x6c, 0xe7, 0x16, 0x79, 0x07, - 0xc6, 0x56, 0xa9, 0xd3, 0xa0, 0x32, 0x73, 0x76, 0xfc, 0x6c, 0x2a, 0xbf, 0x45, 0xc3, 0x0b, 0x39, - 0xdb, 0x3f, 0xe6, 0xca, 0xe8, 0x8c, 0x25, 0x48, 0x48, 0x49, 0x34, 0x8e, 0x1b, 0xd6, 0xa6, 0xbc, - 0xd1, 0x96, 0x56, 0xd5, 0x80, 0xa3, 0xe9, 0x1f, 0x19, 0xf0, 0xdc, 0x00, 0x1a, 0x36, 0x70, 0x6c, - 0xe8, 0xd5, 0x81, 0xc3, 0xa5, 0x11, 0xa1, 0xe4, 0x7d, 0x98, 0xdd, 0x12, 0x86, 0xb9, 0x1c, 0x8e, - 0x4c, 0x2c, 0xfa, 0xd2, 0x66, 0xb7, 0xe5, 0xb8, 0x24, 0x91, 0xb5, 0xab, 0x96, 0xd9, 0x81, 0x57, - 0x2d, 0xd5, 0x9b, 0x8b, 0x23, 0xc3, 0xde, 0x5c, 0xfc, 0x24, 0xf9, 0xce, 0x9c, 0x48, 0x20, 0x15, - 0xdf, 0xdb, 0x34, 0xfa, 0xdf, 0xdb, 0x1c, 0x98, 0xa6, 0x06, 0x13, 0xed, 0xeb, 0xbc, 0x9f, 0x74, - 0x3c, 0xdf, 0xd3, 0xc6, 0xf3, 0xb9, 0xf4, 0xf1, 0xec, 0x3f, 0x90, 0xbf, 0x61, 0x24, 0x3f, 0x76, - 0xa8, 0x11, 0x34, 0x61, 0xac, 0xe4, 0xb5, 0x1c, 0xb7, 0xad, 0x3e, 0xb1, 0xd5, 0x40, 0x88, 0x25, - 0x4a, 0x86, 0xbb, 0xe6, 0x7a, 0x09, 0x46, 0x37, 0xbc, 0x76, 0xa1, 0x24, 0x22, 0xc9, 0x90, 0x4f, - 0xdb, 0x6b, 0xdb, 0x4e, 0xc3, 0xe2, 0x05, 0xe6, 0xcf, 0x8d, 0xc2, 0x05, 0x8b, 0x1e, 0xb8, 0xcc, - 0x74, 0xdc, 0x0e, 0xdc, 0xf6, 0x81, 0x76, 0xa7, 0xcf, 0x4c, 0x8c, 0x89, 0x48, 0xac, 0xc8, 0x20, - 0x51, 0x1d, 0x57, 0x21, 0xc7, 0xd6, 0x0a, 0x65, 0x58, 0xf0, 0x18, 0x00, 0x1f, 0x3c, 0xe5, 0xf2, - 0x22, 0x8b, 0xc9, 0x35, 0xb1, 0x92, 0x29, 0xa9, 0x6f, 0xd9, 0x4a, 0xf6, 0xe3, 0xe3, 0x25, 0xa8, - 0x1d, 0x05, 0x21, 0xc5, 0x5d, 0x8c, 0x58, 0xcd, 0x22, 0x73, 0x73, 0xa4, 0x8f, 0xb9, 0xb9, 0x0e, - 0x0b, 0x85, 0x06, 0xd7, 0x7b, 0x4e, 0xb3, 0xea, 0xbb, 0xed, 0xba, 0xdb, 0x71, 0x9a, 0x72, 0x0b, - 0x85, 0x87, 0x41, 0x4e, 0x54, 0x6e, 0x77, 0x22, 0x04, 0x2b, 0x95, 0x8c, 0x7d, 0x46, 0x69, 0xa3, - 0xc6, 0xdf, 0xb3, 0xe4, 0x27, 0x3c, 0xf8, 0x19, 0x8d, 0x76, 0xc0, 0x1f, 0xb4, 0xb4, 0xa2, 0x62, - 0x34, 0x74, 0xf1, 0x04, 0x7c, 0x6b, 0xad, 0x16, 0xdf, 0x2f, 0xe0, 0x99, 0xf9, 0xf8, 0x29, 0x79, - 0xd8, 0x0c, 0xf0, 0xa4, 0x5c, 0xc3, 0x8b, 0xe9, 0x6a, 0xb5, 0x55, 0x46, 0x97, 0xeb, 0xa1, 0x0b, - 0x82, 0x43, 0x95, 0x8e, 0xe3, 0x91, 0x1b, 0x00, 0x3c, 0xa7, 0x0c, 0x8a, 0xcc, 0x44, 0x6c, 0x16, - 0xfb, 0x08, 0xe5, 0x66, 0xb1, 0x82, 0x42, 0xde, 0x85, 0xf9, 0x72, 0x71, 0x59, 0xfa, 0xe5, 0x4a, - 0x5e, 0xbd, 0x8b, 0x67, 0x9a, 0x80, 0xf5, 0xe1, 0x18, 0xd2, 0xfa, 0x32, 0x93, 0x93, 0x34, 0x34, - 0x72, 0x19, 0xc6, 0x2b, 0x25, 0xde, 0xf7, 0x93, 0x6a, 0xfa, 0x69, 0x11, 0x2b, 0x20, 0x0b, 0xc9, - 0x66, 0x6c, 0xb7, 0x4d, 0x9d, 0x68, 0x60, 0x5d, 0x38, 0xd9, 0x66, 0x13, 0x59, 0xaa, 0xf9, 0x1b, - 0x07, 0x45, 0xaf, 0x41, 0x83, 0x9d, 0x9b, 0x9f, 0xb3, 0x2c, 0xd5, 0xca, 0xb7, 0xa1, 0x22, 0xb8, - 0x99, 0xaa, 0x35, 0xfe, 0x36, 0x66, 0xa9, 0xee, 0xc1, 0x25, 0x5f, 0x86, 0x51, 0xfc, 0x29, 0x4c, - 0x88, 0xf9, 0x14, 0xb6, 0xb1, 0xf9, 0x50, 0xe7, 0xaf, 0xcd, 0x21, 0x01, 0xa9, 0xc0, 0xb8, 0x30, - 0x43, 0x4f, 0x93, 0x6b, 0x55, 0xd8, 0xb3, 0x7c, 0x90, 0x04, 0xbd, 0xd9, 0x80, 0x29, 0xb5, 0x42, - 0x26, 0x9c, 0xab, 0x4e, 0x70, 0x48, 0x1b, 0xec, 0x97, 0x48, 0x93, 0x8e, 0xc2, 0x79, 0x88, 0x50, - 0x9b, 0xb5, 0xc3, 0x52, 0x50, 0x98, 0xe2, 0xaa, 0x04, 0xdb, 0x81, 0x68, 0x8a, 0xd8, 0x98, 0xba, - 0xe8, 0xe4, 0x68, 0x58, 0xa2, 0xc8, 0xfc, 0x1a, 0x2c, 0x6c, 0x74, 0x9b, 0x4d, 0xb6, 0x49, 0x95, - 0x69, 0x34, 0x43, 0x27, 0xa4, 0x64, 0x05, 0x46, 0xf1, 0x0f, 0xf1, 0x38, 0xf5, 0xbc, 0xfe, 0x5a, - 0x27, 0x16, 0x61, 0x78, 0x92, 0x81, 0x37, 0x02, 0x43, 0xfd, 0xb5, 0x57, 0x4e, 0x6a, 0xfe, 0x30, - 0x7e, 0xf7, 0x70, 0xcb, 0x77, 0xea, 0xf7, 0xa9, 0x2f, 0x56, 0xa0, 0x61, 0xdf, 0x65, 0xfc, 0x48, - 0x36, 0x42, 0x5f, 0x15, 0xd2, 0x1a, 0x7c, 0x52, 0x63, 0xc8, 0xbb, 0x30, 0x29, 0x56, 0x06, 0x25, - 0x8f, 0x05, 0x5e, 0x16, 0x96, 0xef, 0xa7, 0x26, 0x4e, 0xcc, 0x55, 0x74, 0x5c, 0xf0, 0xf4, 0x4f, - 0xd9, 0xb9, 0xf9, 0x59, 0x2c, 0x78, 0x7a, 0x1d, 0x03, 0x44, 0xf7, 0x67, 0x21, 0xd9, 0xb7, 0x42, - 0x76, 0x6f, 0xab, 0x37, 0xd7, 0x8d, 0x78, 0x27, 0x11, 0xdf, 0x5c, 0x57, 0x77, 0x12, 0x11, 0x6a, - 0x34, 0x26, 0x99, 0x13, 0xc6, 0xe4, 0x7d, 0x39, 0x26, 0xd9, 0xfe, 0x82, 0x31, 0x3f, 0x60, 0x1c, - 0x6a, 0xf1, 0x0c, 0x19, 0x19, 0x6a, 0x13, 0x7a, 0x06, 0x53, 0xf4, 0x71, 0x92, 0xa4, 0x42, 0x13, - 0x9c, 0xd4, 0x9d, 0xed, 0xe8, 0xf0, 0x4c, 0x4f, 0xd8, 0xd9, 0x7e, 0x05, 0xa6, 0x0a, 0x61, 0xe8, - 0xd4, 0x0f, 0x69, 0xa3, 0xc4, 0xd4, 0x93, 0x72, 0xc9, 0xd6, 0x11, 0x70, 0xf5, 0x44, 0x40, 0xc5, - 0xe5, 0x49, 0x63, 0x9c, 0x40, 0xc4, 0x5a, 0x45, 0x49, 0x63, 0x18, 0x44, 0x4f, 0x1a, 0xc3, 0x20, - 0x6c, 0x27, 0x5f, 0x69, 0x3f, 0x70, 0x59, 0x9f, 0xe4, 0xe2, 0xb7, 0xdc, 0x5c, 0x0e, 0x52, 0x95, - 0xab, 0xc0, 0x22, 0x6f, 0x2b, 0x96, 0xe3, 0x44, 0xbc, 0xe1, 0xe3, 0x0e, 0x02, 0x5b, 0x1a, 0x90, - 0xaa, 0x55, 0x18, 0x99, 0x92, 0xb7, 0x61, 0x5c, 0xfa, 0x7d, 0x20, 0xde, 0xe4, 0x09, 0xca, 0xde, - 0xab, 0x54, 0x12, 0x19, 0x9f, 0x51, 0x52, 0xd2, 0xbd, 0x4f, 0x2a, 0xcf, 0x28, 0x29, 0xe9, 0xde, - 0xb5, 0x67, 0x94, 0x94, 0xc4, 0xef, 0xd1, 0x96, 0x79, 0xea, 0xc4, 0x2d, 0xf3, 0x0e, 0x4c, 0x55, - 0x1d, 0x3f, 0x74, 0x99, 0xb9, 0xd0, 0x0e, 0xf9, 0xab, 0xd5, 0xb1, 0x97, 0x49, 0x29, 0x5a, 0x79, - 0x51, 0x3e, 0x27, 0xd4, 0x51, 0xf0, 0xf5, 0x77, 0x68, 0x62, 0x78, 0x7a, 0xa4, 0xd5, 0xcc, 0x93, - 0x44, 0x5a, 0x61, 0xa7, 0xa2, 0x67, 0x61, 0x36, 0x8e, 0x6b, 0x43, 0xcb, 0x30, 0xe1, 0x5e, 0x88, - 0x10, 0xc9, 0x37, 0x60, 0x8a, 0xfd, 0x8d, 0x8f, 0xa3, 0xba, 0x94, 0xbf, 0x4a, 0x1d, 0x27, 0xe6, - 0xd2, 0x27, 0x34, 0x7f, 0x41, 0xb5, 0x46, 0x43, 0x3e, 0x81, 0x91, 0x71, 0xd2, 0x65, 0xa8, 0x71, - 0x23, 0x1f, 0xc0, 0x94, 0xfa, 0x04, 0x38, 0xde, 0x6f, 0x13, 0xb1, 0x72, 0x0d, 0x01, 0xef, 0xc9, - 0xdb, 0xa4, 0x12, 0xb0, 0x65, 0xbe, 0xd0, 0xe1, 0x0a, 0x92, 0x28, 0xd2, 0xde, 0xe9, 0x51, 0x8e, - 0x12, 0x8d, 0x7c, 0x08, 0x53, 0x85, 0x4e, 0x27, 0xd6, 0x38, 0xf3, 0x8a, 0xe3, 0xa0, 0xd3, 0xb1, - 0x53, 0xb5, 0x8e, 0x46, 0x91, 0x54, 0xcc, 0x0b, 0xa7, 0x53, 0xcc, 0x7f, 0x66, 0xc0, 0xf9, 0x3e, - 0xdd, 0x16, 0xe5, 0x2c, 0x32, 0x06, 0xe7, 0x2c, 0x62, 0xd3, 0x4f, 0xdf, 0xbf, 0xe1, 0xf4, 0x13, - 0xa6, 0x8a, 0xfa, 0xd1, 0xd2, 0x68, 0x49, 0x7f, 0x42, 0x3b, 0xfb, 0x99, 0x3d, 0xa1, 0x6d, 0x1e, - 0x1b, 0x30, 0xa9, 0x08, 0x33, 0xb9, 0xa4, 0x5c, 0x93, 0xc9, 0xf3, 0x44, 0xc8, 0x0a, 0x87, 0x0c, - 0x57, 0xe7, 0x28, 0x99, 0x99, 0x93, 0x7d, 0x5e, 0xeb, 0xcc, 0x9e, 0x50, 0xf2, 0x3a, 0xb5, 0x12, - 0x0e, 0x2a, 0x56, 0x8e, 0x4f, 0x89, 0x39, 0x41, 0x58, 0xa8, 0x87, 0xee, 0x03, 0x3a, 0x84, 0xe6, - 0x8e, 0x9f, 0x12, 0x73, 0x82, 0xd0, 0x76, 0x90, 0xac, 0xe7, 0x29, 0xb1, 0x88, 0xa1, 0xf9, 0x57, - 0x0d, 0x80, 0xed, 0x4a, 0x11, 0x13, 0xb3, 0x3d, 0xe9, 0xca, 0x9a, 0x9e, 0xec, 0x46, 0x72, 0x1f, - 0xb0, 0xa6, 0x56, 0x61, 0x46, 0xc7, 0x62, 0x3b, 0xfc, 0x5a, 0xdd, 0xf7, 0x9a, 0xcd, 0x3d, 0xa7, - 0x7e, 0x7f, 0xcd, 0x6d, 0x53, 0x9e, 0x65, 0x64, 0x94, 0xeb, 0xf3, 0x20, 0x2a, 0xb2, 0x9b, 0xac, - 0xcc, 0x4a, 0x22, 0x9b, 0x7f, 0x61, 0xc0, 0x64, 0xa5, 0x1d, 0x84, 0x4e, 0xb3, 0x89, 0x16, 0xc3, - 0xe7, 0x29, 0x17, 0x7e, 0xf4, 0x5d, 0x03, 0x7a, 0xf4, 0x2d, 0x98, 0x4d, 0xa0, 0xb1, 0x9d, 0x6e, - 0x0d, 0x6f, 0xf1, 0xa9, 0x3b, 0x5d, 0x7e, 0xaf, 0xcf, 0x12, 0x25, 0x66, 0x59, 0x21, 0xdb, 0xb9, - 0x89, 0x47, 0x38, 0xcb, 0x00, 0xae, 0x04, 0x49, 0xbb, 0x9c, 0x24, 0x5b, 0xb2, 0x73, 0xd3, 0x52, - 0xb0, 0xcc, 0x0d, 0x18, 0xab, 0x79, 0x7e, 0xb8, 0x72, 0xc4, 0x4d, 0xe1, 0x12, 0x0d, 0xea, 0xea, - 0x19, 0x8d, 0x8b, 0x9e, 0xd1, 0xba, 0x25, 0x8a, 0xd8, 0x46, 0xf8, 0x8e, 0x4b, 0x9b, 0x0d, 0x35, - 0x76, 0x6e, 0x9f, 0x01, 0x2c, 0x0e, 0x67, 0xdb, 0x85, 0x73, 0x71, 0xe2, 0xd0, 0x38, 0x48, 0xef, - 0x49, 0x05, 0xb6, 0xa8, 0xf5, 0xef, 0x4b, 0xfa, 0x03, 0x72, 0x5a, 0x4d, 0x03, 0xba, 0xfa, 0x1f, - 0x1a, 0x70, 0xb1, 0x3f, 0x89, 0x1a, 0xf7, 0x67, 0x0c, 0x88, 0xfb, 0x7b, 0x35, 0x79, 0xa6, 0x80, - 0x68, 0xe2, 0x4c, 0x21, 0x3e, 0x49, 0x28, 0x61, 0xd8, 0x65, 0x3d, 0x7a, 0xac, 0xf3, 0xd2, 0x80, - 0x36, 0x23, 0x22, 0x1f, 0xe6, 0x10, 0x69, 0x2c, 0x41, 0x6b, 0xfe, 0x93, 0x11, 0xb8, 0xd0, 0x97, - 0x82, 0xac, 0x2a, 0x39, 0x88, 0x67, 0xa2, 0xec, 0xa7, 0x7d, 0xf1, 0xaf, 0xe3, 0xbf, 0x18, 0x59, - 0x93, 0x0c, 0xe4, 0xdf, 0x8c, 0x72, 0xcf, 0x66, 0x90, 0xd7, 0xeb, 0x27, 0xf2, 0xe2, 0xe8, 0xc8, - 0x0c, 0x7a, 0xd3, 0xd0, 0xe2, 0x7d, 0x0b, 0x1a, 0x3a, 0x6e, 0x33, 0x50, 0xa7, 0x5d, 0x83, 0x83, - 0x2c, 0x59, 0x16, 0x07, 0x63, 0x8e, 0xa4, 0x07, 0x63, 0x9a, 0xff, 0x8f, 0x01, 0x13, 0x51, 0xb3, - 0xc9, 0x45, 0x38, 0xb7, 0x65, 0x15, 0x8a, 0x65, 0x7b, 0xeb, 0x93, 0x6a, 0xd9, 0xde, 0xde, 0xa8, - 0x55, 0xcb, 0xc5, 0xca, 0x9d, 0x4a, 0xb9, 0x94, 0x3f, 0x43, 0xe6, 0x60, 0x7a, 0x7b, 0xe3, 0xde, - 0xc6, 0xe6, 0xee, 0x86, 0x5d, 0xb6, 0xac, 0x4d, 0x2b, 0x6f, 0x90, 0x69, 0x98, 0xb0, 0x56, 0x0a, - 0x45, 0x7b, 0x63, 0xb3, 0x54, 0xce, 0x67, 0x48, 0x1e, 0xa6, 0x8a, 0x9b, 0x1b, 0x1b, 0xe5, 0xe2, - 0x56, 0x65, 0xa7, 0xb2, 0xf5, 0x49, 0x3e, 0x4b, 0x08, 0xcc, 0x20, 0x42, 0xd5, 0xaa, 0x6c, 0x14, - 0x2b, 0xd5, 0xc2, 0x5a, 0x7e, 0x84, 0xc1, 0x18, 0xbe, 0x02, 0x1b, 0x8d, 0x18, 0xdd, 0xdb, 0x5e, - 0x29, 0xe7, 0xc7, 0x18, 0x0a, 0xfb, 0x4b, 0x41, 0x19, 0x67, 0xd5, 0x23, 0x4a, 0xa9, 0xb0, 0x55, - 0x58, 0x29, 0xd4, 0xca, 0xf9, 0x1c, 0x39, 0x0f, 0xf3, 0x1a, 0xc8, 0x5e, 0xdb, 0xbc, 0x5b, 0xd9, - 0xc8, 0x4f, 0x90, 0x05, 0xc8, 0x47, 0xb0, 0xd2, 0x8a, 0xbd, 0x5d, 0x2b, 0x5b, 0x79, 0x48, 0x42, - 0x37, 0x0a, 0xeb, 0xe5, 0xfc, 0xa4, 0xf9, 0x1e, 0xbf, 0x62, 0xc1, 0xbb, 0x9a, 0x9c, 0x03, 0x52, - 0xdb, 0x2a, 0x6c, 0x6d, 0xd7, 0x12, 0x1f, 0x3f, 0x09, 0xe3, 0xb5, 0xed, 0x62, 0xb1, 0x5c, 0xab, - 0xe5, 0x0d, 0x02, 0x30, 0x76, 0xa7, 0x50, 0x59, 0x2b, 0x97, 0xf2, 0x19, 0xf3, 0x97, 0x0c, 0x98, - 0x93, 0xf6, 0x8b, 0xf4, 0x2b, 0x3f, 0xe1, 0x5c, 0x7c, 0x5f, 0xdb, 0x96, 0xc9, 0x08, 0xf8, 0x44, - 0x25, 0x03, 0xa6, 0xa1, 0x0f, 0x67, 0x53, 0x91, 0xc9, 0x27, 0x90, 0x97, 0x0d, 0x58, 0x77, 0xc2, - 0xfa, 0x61, 0xac, 0xc6, 0x5e, 0x4c, 0x54, 0x92, 0x40, 0xe3, 0xee, 0xb1, 0xf8, 0xb1, 0x9d, 0x1e, - 0x36, 0xe6, 0x4f, 0xc0, 0xf9, 0x3e, 0xb4, 0xa4, 0x08, 0x63, 0x51, 0x46, 0xd6, 0x01, 0x11, 0x28, - 0x0b, 0x3f, 0x3a, 0x5e, 0x12, 0x88, 0xf8, 0xe4, 0x08, 0xfe, 0x65, 0x09, 0x88, 0xf9, 0x37, 0x0c, - 0x98, 0x12, 0x56, 0x6f, 0xa1, 0x49, 0xfd, 0xf0, 0xc9, 0x7a, 0xf8, 0x6d, 0xad, 0x87, 0xa3, 0x70, - 0x62, 0x85, 0x3f, 0x2b, 0x4e, 0xed, 0xdc, 0xff, 0xca, 0x80, 0x7c, 0x12, 0x91, 0xbc, 0x0f, 0xb9, - 0x1a, 0x7d, 0x40, 0x7d, 0x37, 0x3c, 0x12, 0xba, 0x42, 0xa6, 0x7a, 0xe7, 0x38, 0xa2, 0x8c, 0x3b, - 0xd7, 0x02, 0xf1, 0xcb, 0x8a, 0x68, 0x86, 0x55, 0x79, 0xca, 0xbe, 0x35, 0xfb, 0xb4, 0xf6, 0xad, - 0xe6, 0x3f, 0xca, 0xc0, 0xf9, 0xbb, 0x34, 0x54, 0xbf, 0x29, 0x3a, 0x4f, 0xfb, 0xc2, 0x70, 0xdf, - 0xa5, 0x7c, 0xc9, 0x22, 0x8c, 0x63, 0x91, 0xbc, 0x20, 0x6d, 0xc9, 0x9f, 0x64, 0x25, 0x12, 0x83, - 0xac, 0x96, 0x4b, 0xba, 0x4f, 0xdd, 0xd7, 0x95, 0xec, 0xb2, 0x52, 0x0a, 0xc8, 0x65, 0x98, 0xc1, - 0xf4, 0x69, 0x5d, 0x26, 0x3d, 0xb4, 0x21, 0xf6, 0xef, 0x39, 0x2b, 0x01, 0x25, 0xd7, 0x20, 0xcf, - 0x20, 0x85, 0xfa, 0xfd, 0xb6, 0xf7, 0xb0, 0x49, 0x1b, 0x07, 0x94, 0x3f, 0x58, 0x99, 0xb3, 0x7a, - 0xe0, 0x17, 0xdf, 0x86, 0xc9, 0x4f, 0x99, 0x01, 0xda, 0xfc, 0xe7, 0x06, 0x2c, 0x60, 0xa3, 0x15, - 0x86, 0xe8, 0x55, 0xfd, 0x42, 0xdc, 0x0b, 0x4a, 0x52, 0x54, 0x87, 0x81, 0xf4, 0x6d, 0x48, 0xd4, - 0x3b, 0xf1, 0x66, 0x3d, 0x33, 0xc4, 0x66, 0xbd, 0x76, 0x9a, 0x17, 0xae, 0x86, 0xf4, 0x35, 0xf0, - 0xd7, 0x46, 0xe3, 0xa1, 0x34, 0xff, 0x5a, 0x06, 0xc6, 0x2d, 0x8a, 0x4f, 0xff, 0x90, 0xcb, 0x30, - 0xbe, 0xe1, 0x85, 0x34, 0x58, 0xd7, 0xde, 0x79, 0x6a, 0x33, 0x90, 0xdd, 0x6a, 0x58, 0xb2, 0x90, - 0x09, 0x72, 0xd5, 0xf7, 0x1a, 0xdd, 0x7a, 0xa8, 0x0a, 0x72, 0x87, 0x83, 0x2c, 0x59, 0x46, 0xde, - 0x80, 0x09, 0xc1, 0x39, 0x3a, 0xd2, 0xc0, 0x98, 0x3a, 0x9f, 0x46, 0x4f, 0x47, 0xc5, 0x08, 0x68, - 0xae, 0xf1, 0xb5, 0x73, 0x44, 0x31, 0xd7, 0x7a, 0x96, 0x43, 0x69, 0x85, 0x8e, 0x0e, 0xb0, 0x42, - 0xbf, 0x00, 0x63, 0x85, 0x20, 0xa0, 0xa1, 0xbc, 0x78, 0x38, 0x15, 0xe5, 0x29, 0x08, 0x68, 0xc8, - 0x19, 0x3b, 0x58, 0x6e, 0x09, 0x3c, 0xf3, 0xcf, 0x33, 0x30, 0x8a, 0x7f, 0xe2, 0x31, 0x8e, 0x5f, - 0x3f, 0xd4, 0x8e, 0x71, 0xfc, 0xfa, 0xa1, 0x85, 0x50, 0x72, 0x13, 0xb7, 0x90, 0x32, 0x7f, 0xaf, - 0xf8, 0x7a, 0xf4, 0x8d, 0x36, 0x62, 0xb0, 0xa5, 0xe2, 0x44, 0xe7, 0x5b, 0xd9, 0xd4, 0xeb, 0xc6, - 0xe7, 0x20, 0xb3, 0x59, 0x13, 0x5f, 0x8c, 0x69, 0x08, 0xbc, 0xc0, 0xca, 0x6c, 0xd6, 0xb0, 0x37, - 0x56, 0x0b, 0xcb, 0x6f, 0xdd, 0x56, 0x9f, 0x24, 0x0b, 0x0e, 0x9d, 0xe5, 0xb7, 0x6e, 0x5b, 0xa2, - 0x84, 0xf5, 0x2f, 0xb6, 0xb9, 0xe6, 0x7e, 0x87, 0x8a, 0xbb, 0x7a, 0xd8, 0xbf, 0xf8, 0x6d, 0x76, - 0xe0, 0x7e, 0x87, 0x5a, 0x31, 0x02, 0x59, 0x86, 0x49, 0x71, 0x3d, 0x13, 0xf1, 0x95, 0xeb, 0x93, - 0xe2, 0xfa, 0x26, 0xa7, 0x50, 0x91, 0xf8, 0x31, 0x85, 0x18, 0x20, 0xf9, 0xca, 0x88, 0x38, 0xa6, - 0x90, 0x43, 0x18, 0x58, 0x0a, 0x4a, 0x7c, 0xd5, 0x30, 0xbe, 0x83, 0xa7, 0x5e, 0x35, 0xc4, 0x34, - 0x77, 0x11, 0x82, 0xf9, 0xdb, 0x19, 0xc8, 0x55, 0x9b, 0xdd, 0x03, 0xb7, 0xbd, 0x73, 0xf3, 0x5f, - 0xe9, 0x63, 0xb7, 0x6f, 0x02, 0x2a, 0x7f, 0x61, 0x17, 0x4b, 0x4f, 0x23, 0x6f, 0x9a, 0x58, 0x82, - 0x39, 0x09, 0xa2, 0x91, 0x5b, 0x20, 0x04, 0x53, 0xbc, 0x92, 0x74, 0x56, 0x27, 0xe0, 0xef, 0x03, - 0x48, 0x12, 0x81, 0x4a, 0xde, 0x85, 0xc9, 0xf8, 0x1d, 0xd6, 0xf8, 0xf1, 0x23, 0x95, 0xb2, 0x18, - 0x97, 0xef, 0xdc, 0xb4, 0x54, 0x74, 0xf3, 0x0f, 0x0c, 0x98, 0x52, 0xdb, 0x43, 0x2c, 0x98, 0x0f, - 0x9a, 0x6c, 0x43, 0x28, 0xa2, 0x26, 0x3a, 0x58, 0x28, 0x96, 0xc9, 0x4b, 0x7a, 0x83, 0x18, 0x1e, - 0x0f, 0xa1, 0xa8, 0xd1, 0x30, 0x74, 0xdb, 0x07, 0xc1, 0xea, 0x19, 0x6b, 0x2e, 0x88, 0xc1, 0x1c, - 0x8f, 0x14, 0x20, 0xe7, 0x75, 0x82, 0x03, 0xda, 0x76, 0xa5, 0x23, 0xfc, 0x65, 0x8d, 0xd1, 0xa6, - 0x28, 0xec, 0xe1, 0x15, 0x91, 0x7d, 0x65, 0xe4, 0x7f, 0xfd, 0xcd, 0x25, 0x63, 0x05, 0x20, 0x17, - 0x88, 0x52, 0x73, 0x0d, 0x2e, 0xf4, 0x6d, 0x06, 0xb9, 0x0a, 0xf9, 0x7d, 0x47, 0xec, 0x6c, 0xeb, - 0x87, 0x4e, 0xbb, 0x4d, 0x9b, 0x42, 0x00, 0x66, 0x25, 0xbc, 0xc8, 0xc1, 0x9c, 0xb3, 0xf9, 0x9b, - 0x06, 0x3c, 0x3f, 0xa8, 0x31, 0x4c, 0x8c, 0x9c, 0xe8, 0x18, 0xd6, 0xc2, 0xbf, 0xc9, 0x45, 0xc8, - 0x75, 0x7c, 0xd7, 0xc3, 0x05, 0x8e, 0x8b, 0x51, 0xf4, 0x9b, 0xbc, 0x00, 0xc0, 0x35, 0x76, 0xe8, - 0x1c, 0x88, 0x48, 0x48, 0x6b, 0x02, 0x21, 0x5b, 0xce, 0x41, 0x40, 0x5e, 0x87, 0xb9, 0x06, 0xdd, - 0x77, 0xba, 0xcd, 0xd0, 0x0e, 0xea, 0x87, 0xb4, 0xd1, 0x8d, 0x1e, 0xf6, 0xb7, 0xf2, 0xa2, 0xa0, - 0x26, 0xe1, 0xa2, 0x89, 0xbf, 0x1e, 0x35, 0x71, 0xc5, 0xf3, 0xc2, 0x20, 0xf4, 0x9d, 0x8e, 0x36, - 0xb0, 0xa4, 0x05, 0x17, 0x3c, 0xa7, 0x1b, 0x1e, 0x2e, 0xe3, 0x03, 0xf3, 0x9e, 0x2f, 0x03, 0x69, - 0xeb, 0xf2, 0xb8, 0x65, 0x72, 0xf9, 0x86, 0xde, 0xef, 0x05, 0x86, 0x5d, 0x50, 0x91, 0x8b, 0x5e, - 0x83, 0x2a, 0x5c, 0x57, 0xcf, 0x58, 0xe7, 0x39, 0xcf, 0x1e, 0xac, 0x95, 0x69, 0x4d, 0xf0, 0xcc, - 0xbf, 0x04, 0x57, 0x86, 0xe5, 0x8a, 0x6f, 0xf2, 0xa6, 0x37, 0x71, 0xc2, 0x9a, 0x73, 0x92, 0x94, - 0xfc, 0x4d, 0xde, 0xe8, 0x96, 0xad, 0x2b, 0xfa, 0x7a, 0x52, 0xc2, 0xb6, 0x7d, 0xd7, 0x7c, 0x0f, - 0x66, 0xf4, 0x59, 0x42, 0x5e, 0x87, 0x91, 0x88, 0xeb, 0x4c, 0x64, 0xa5, 0xa9, 0x48, 0x8c, 0xb7, - 0x85, 0x48, 0xe6, 0xcf, 0x18, 0x30, 0x9f, 0x32, 0x57, 0xc8, 0xd7, 0x61, 0x5e, 0x76, 0x29, 0x9f, - 0x0e, 0x78, 0xb0, 0x29, 0x3a, 0xf3, 0x6a, 0x5a, 0x67, 0x22, 0x1a, 0x9e, 0x7a, 0xea, 0xdd, 0x38, - 0x27, 0xba, 0x31, 0x2e, 0x4f, 0x76, 0xe0, 0x3f, 0x30, 0xc0, 0x3c, 0x99, 0x15, 0xeb, 0x8c, 0x9e, - 0xb6, 0x4c, 0x58, 0x93, 0x4e, 0x8c, 0x4d, 0x5e, 0x86, 0x69, 0x9f, 0xee, 0xfb, 0x34, 0x38, 0x14, - 0x38, 0xbc, 0xc3, 0xa6, 0x04, 0x90, 0x23, 0xbd, 0x0f, 0x32, 0xb0, 0x7c, 0x08, 0x73, 0x50, 0x79, - 0x77, 0x5b, 0x10, 0x99, 0x6f, 0x4b, 0xc5, 0xb1, 0xe6, 0x06, 0xe1, 0xce, 0x4d, 0x72, 0x15, 0xc6, - 0xb9, 0xae, 0x90, 0xbb, 0x82, 0x59, 0xad, 0x7b, 0x76, 0x6e, 0x5a, 0xb2, 0xdc, 0xfc, 0x6d, 0x71, - 0xef, 0xa9, 0xd2, 0xa8, 0x26, 0x32, 0x35, 0x3d, 0xe9, 0xe6, 0xa7, 0xac, 0x99, 0xe6, 0x2f, 0x2b, - 0x89, 0x47, 0x7a, 0xeb, 0xea, 0xbf, 0x07, 0x52, 0x62, 0xbb, 0xfe, 0xa6, 0x01, 0xcf, 0x0f, 0x22, - 0x4f, 0x4d, 0x2a, 0x65, 0x9c, 0x2e, 0xa9, 0xd4, 0x55, 0xc8, 0x71, 0x98, 0x9e, 0x95, 0x55, 0x90, - 0xba, 0x0d, 0x2b, 0x2a, 0x36, 0x0b, 0x00, 0x95, 0x46, 0x75, 0xb3, 0xc3, 0x6f, 0x5f, 0xdd, 0x82, - 0x11, 0xd6, 0xb6, 0x44, 0x47, 0xb1, 0xa6, 0x16, 0xd6, 0xd7, 0x04, 0x12, 0xb7, 0x0c, 0x02, 0xa7, - 0xd5, 0xb4, 0x10, 0xd9, 0xdc, 0x85, 0x19, 0x1d, 0x83, 0x94, 0xf5, 0x00, 0xe0, 0xf8, 0x21, 0x89, - 0x15, 0xcf, 0xe3, 0xfb, 0xab, 0x95, 0x0b, 0x3f, 0x3a, 0x5e, 0x02, 0xf6, 0x93, 0xd3, 0xa4, 0x05, - 0x08, 0x9b, 0xdf, 0xcb, 0xc0, 0x42, 0x7c, 0xc8, 0x20, 0x87, 0xeb, 0x99, 0xf5, 0x19, 0x16, 0x34, - 0x9f, 0xd6, 0x52, 0x4f, 0xae, 0x76, 0xf9, 0x81, 0x03, 0xb6, 0xd2, 0x77, 0x61, 0xb1, 0x1f, 0x3e, - 0x79, 0xbd, 0x27, 0x9b, 0xb2, 0x08, 0x86, 0x89, 0xd2, 0x2e, 0x2b, 0xc9, 0x95, 0xb7, 0xf8, 0xb3, - 0x66, 0x78, 0x0d, 0xe0, 0x09, 0xa7, 0x88, 0x22, 0xdb, 0xff, 0xa6, 0x01, 0x0b, 0x9b, 0xf7, 0x43, - 0xa7, 0xd2, 0xea, 0x78, 0x7e, 0x68, 0x75, 0x9b, 0xb2, 0x6d, 0x57, 0x20, 0x57, 0x95, 0xeb, 0x1a, - 0xf7, 0x16, 0xa3, 0xc1, 0x2e, 0xd7, 0x36, 0x2b, 0x2a, 0x25, 0xab, 0x90, 0x13, 0x11, 0xd3, 0xf2, - 0x91, 0x1f, 0xe9, 0x0b, 0xd0, 0x19, 0x0b, 0x24, 0xd6, 0x4f, 0xd8, 0xdd, 0x82, 0xc6, 0x8a, 0xa8, - 0xcd, 0x3f, 0x37, 0xe0, 0x7c, 0x1f, 0x1a, 0xf2, 0x1e, 0x8c, 0xa2, 0x3b, 0x40, 0x28, 0x96, 0xe7, - 0xfb, 0x54, 0x11, 0xd6, 0x0f, 0x77, 0x6e, 0x72, 0x27, 0x55, 0x8b, 0xfd, 0xb0, 0x38, 0x15, 0xf9, - 0x3a, 0x4c, 0x14, 0x1a, 0x0d, 0xed, 0x29, 0xa2, 0x37, 0x07, 0xb7, 0xf2, 0x7a, 0x84, 0xcf, 0xdf, - 0x74, 0xe1, 0xe6, 0x6f, 0xa3, 0x21, 0xde, 0x72, 0xb1, 0x62, 0x7e, 0x17, 0xdf, 0x85, 0x19, 0x1d, - 0xf9, 0x54, 0x7b, 0xc0, 0x1f, 0x18, 0x90, 0xd7, 0xdb, 0xf0, 0xd9, 0x9c, 0xca, 0xa7, 0x0d, 0xf3, - 0x09, 0x9a, 0xef, 0xa7, 0xe0, 0x6c, 0x6a, 0x07, 0x93, 0x37, 0x61, 0xac, 0xd0, 0xe9, 0x30, 0xc3, - 0xdd, 0x88, 0x8f, 0x7a, 0x9d, 0x4e, 0x27, 0x11, 0xc6, 0x27, 0x90, 0xc8, 0x2d, 0xc8, 0xa1, 0xdc, - 0x32, 0x82, 0x4c, 0x1c, 0xf9, 0x87, 0x17, 0x5d, 0x92, 0x91, 0x7f, 0x12, 0x31, 0xea, 0x97, 0x42, - 0x10, 0xb8, 0x07, 0x6d, 0xb6, 0x2b, 0xfe, 0xec, 0xfa, 0x25, 0xae, 0x63, 0xa8, 0x7e, 0xf9, 0x0e, - 0x9f, 0x34, 0x49, 0xaa, 0x13, 0x92, 0xda, 0x97, 0x60, 0xbc, 0xc0, 0x63, 0x4d, 0x85, 0x04, 0xbe, - 0x90, 0xda, 0x02, 0x8e, 0xb3, 0x73, 0x93, 0xab, 0x34, 0x87, 0x53, 0x58, 0x92, 0xd4, 0xfc, 0x3f, - 0x32, 0x70, 0x2e, 0x9d, 0x80, 0x7c, 0x23, 0xda, 0x3b, 0x70, 0x83, 0xe7, 0xcb, 0x03, 0xf9, 0xa7, - 0x82, 0xb9, 0x55, 0xa4, 0x6f, 0x90, 0xc5, 0x26, 0xe3, 0x2e, 0x88, 0xd8, 0xfd, 0xc4, 0xca, 0x9a, - 0xc6, 0x86, 0xc7, 0xac, 0xef, 0xdc, 0x14, 0x1e, 0x73, 0x11, 0xf2, 0xcf, 0xff, 0x37, 0x7f, 0xdf, - 0x80, 0x8b, 0xfd, 0xeb, 0x26, 0x93, 0x30, 0x2e, 0xbc, 0xc4, 0xdc, 0x6b, 0x5a, 0x2d, 0x6f, 0x94, - 0x2a, 0x1b, 0x77, 0xf3, 0x06, 0x99, 0x01, 0x10, 0x2e, 0xd4, 0x3b, 0xdb, 0x6b, 0xf9, 0x8c, 0xe2, - 0x45, 0xcd, 0xb2, 0xb2, 0xe2, 0x5a, 0xb9, 0xb0, 0x51, 0x2e, 0xd9, 0xdb, 0x55, 0xee, 0x23, 0xc6, - 0xdf, 0xdb, 0x55, 0x5b, 0xe0, 0x8c, 0x32, 0x9c, 0xaa, 0xb5, 0xc9, 0xe8, 0x19, 0xbf, 0x31, 0x32, - 0x0f, 0xb3, 0x12, 0x47, 0x56, 0x32, 0x4e, 0xce, 0x01, 0x89, 0x80, 0x31, 0x72, 0xce, 0xfc, 0x33, - 0x03, 0x9e, 0x1f, 0xf4, 0xa9, 0xe4, 0x5b, 0x80, 0xee, 0x7a, 0xd1, 0xf7, 0x2b, 0x43, 0xf4, 0xce, - 0x80, 0xc2, 0xe4, 0x11, 0x40, 0xc8, 0x5d, 0xcb, 0x19, 0x57, 0xae, 0x90, 0x51, 0x5e, 0x41, 0xb7, - 0x61, 0x56, 0xe0, 0xc5, 0xc1, 0x9c, 0xf4, 0x3e, 0x9d, 0x85, 0xc9, 0x42, 0xb5, 0xba, 0x56, 0x29, - 0x16, 0xb6, 0x2a, 0x9b, 0x1b, 0x79, 0x83, 0x4c, 0xc0, 0xe8, 0x5d, 0x6b, 0x73, 0xbb, 0x9a, 0xcf, - 0x98, 0x7f, 0x27, 0x03, 0xe7, 0xd8, 0x6c, 0x69, 0xd2, 0x20, 0x60, 0x16, 0x28, 0x33, 0x40, 0xc4, - 0xa3, 0x3b, 0x5f, 0x82, 0xb1, 0xc3, 0x21, 0xa6, 0x5e, 0x4e, 0x4e, 0x3d, 0x4b, 0xa0, 0xb3, 0x6d, - 0x53, 0x37, 0x3a, 0xc5, 0xb5, 0xf0, 0x6f, 0xb6, 0x35, 0x8a, 0x53, 0x04, 0xf1, 0xc7, 0x8a, 0xad, - 0x89, 0x4e, 0x94, 0x28, 0xe8, 0xcb, 0x30, 0x8a, 0x91, 0x37, 0xb8, 0x68, 0xcf, 0x44, 0x91, 0xd1, - 0xe9, 0x2d, 0xc3, 0x90, 0x1c, 0x8b, 0x13, 0x90, 0x1b, 0x00, 0xf1, 0x75, 0x03, 0xb1, 0x70, 0x4b, - 0x5b, 0x26, 0xba, 0x71, 0x60, 0x4d, 0xb4, 0xf6, 0x1d, 0x71, 0xf9, 0xe0, 0x1a, 0xcc, 0xc9, 0x5c, - 0x5a, 0x1d, 0x19, 0x3a, 0xc2, 0xa3, 0x64, 0xac, 0x59, 0x5e, 0x50, 0xe9, 0x88, 0xf0, 0x91, 0x6b, - 0x1f, 0xc0, 0xac, 0xf4, 0x51, 0x6f, 0xad, 0xd5, 0xf0, 0x48, 0x79, 0x16, 0x26, 0x77, 0xca, 0x56, - 0xe5, 0xce, 0x27, 0xf6, 0x9d, 0xed, 0xb5, 0xb5, 0xfc, 0x19, 0x32, 0x0d, 0x13, 0x02, 0x50, 0x2c, - 0xe4, 0x0d, 0x32, 0x05, 0xb9, 0xca, 0x46, 0xad, 0x5c, 0xdc, 0xb6, 0xca, 0xf9, 0xcc, 0xb5, 0x65, - 0x98, 0x89, 0xb3, 0xaf, 0xe1, 0xc8, 0x8c, 0x43, 0xd6, 0x2a, 0xec, 0xe6, 0xcf, 0x30, 0x61, 0xae, - 0xde, 0x2b, 0xd6, 0x6e, 0xde, 0xcc, 0x1b, 0x6c, 0xb8, 0xee, 0x16, 0xab, 0xf6, 0xbd, 0xf5, 0x5a, - 0x3e, 0x73, 0xed, 0x0b, 0x30, 0x87, 0xd1, 0xd0, 0xcc, 0xc6, 0xa6, 0x6d, 0xea, 0x63, 0xb5, 0x53, - 0x90, 0xab, 0xd1, 0x8e, 0xe3, 0x3b, 0x21, 0xe5, 0x75, 0xae, 0x77, 0x9b, 0xa1, 0xdb, 0x69, 0xd2, - 0x47, 0x79, 0xe3, 0xda, 0xdb, 0x30, 0x6b, 0x79, 0x5d, 0xb6, 0x67, 0xad, 0x85, 0x0c, 0xe3, 0xe0, - 0x88, 0x9c, 0x85, 0xb9, 0xed, 0x8d, 0xc2, 0xfa, 0x4a, 0xe5, 0xee, 0xf6, 0xe6, 0x76, 0xcd, 0x5e, - 0x2f, 0x6c, 0x15, 0x57, 0xb9, 0x28, 0xac, 0x6f, 0xd6, 0xb6, 0x6c, 0xab, 0x5c, 0x2c, 0x6f, 0x6c, - 0xe5, 0x8d, 0x6b, 0x3f, 0x6f, 0xf0, 0xb7, 0x57, 0x71, 0x7f, 0xb0, 0x8d, 0x3e, 0xe3, 0x4b, 0xf0, - 0xfc, 0x76, 0xad, 0x6c, 0xd9, 0x5b, 0x9b, 0xf7, 0xca, 0x1b, 0xf6, 0x76, 0xad, 0x70, 0x37, 0x79, - 0xae, 0xb3, 0x04, 0xcf, 0x29, 0x18, 0x56, 0xb9, 0xb8, 0xb9, 0x53, 0xb6, 0xec, 0x6a, 0xa1, 0x56, - 0xdb, 0xdd, 0xb4, 0x4a, 0x79, 0x83, 0x5c, 0x84, 0x73, 0x29, 0x08, 0xeb, 0x77, 0x0a, 0xf9, 0x4c, - 0x4f, 0xd9, 0x46, 0x79, 0xb7, 0xb0, 0x66, 0xaf, 0x6c, 0x6e, 0xe5, 0xb3, 0xd7, 0xf0, 0x09, 0x2f, - 0x74, 0xe6, 0xf2, 0xb0, 0xab, 0x1c, 0x8c, 0x6c, 0x6c, 0x6e, 0x94, 0x93, 0x7a, 0x61, 0x0a, 0x72, - 0x85, 0x6a, 0xd5, 0xda, 0xdc, 0x29, 0x97, 0xb8, 0x56, 0x28, 0x95, 0x37, 0x58, 0xcb, 0xb2, 0xd7, - 0x4c, 0x98, 0x2b, 0x52, 0x3f, 0x2c, 0x3f, 0x0a, 0x69, 0x9b, 0x19, 0x84, 0xd8, 0x77, 0xd3, 0x30, - 0x51, 0xfe, 0xea, 0x56, 0x79, 0xa3, 0xc6, 0xa4, 0xff, 0xcc, 0xb5, 0xe7, 0x13, 0x38, 0x72, 0x58, - 0x6a, 0xb5, 0xd5, 0xfc, 0x99, 0x6b, 0xdf, 0x80, 0x29, 0x2d, 0xa4, 0xf0, 0x3c, 0xcc, 0xab, 0xbf, - 0xab, 0xb4, 0xdd, 0x70, 0xdb, 0x07, 0xf9, 0x33, 0xc9, 0x02, 0xab, 0xdb, 0x6e, 0xb3, 0x02, 0xfc, - 0x78, 0xb5, 0x60, 0x8b, 0xfa, 0x2d, 0xb7, 0xed, 0x84, 0xb4, 0x91, 0xcf, 0x5c, 0xbb, 0x0e, 0xd3, - 0x9a, 0x1f, 0x9c, 0xd5, 0xbb, 0xb6, 0x29, 0xc4, 0x61, 0xbd, 0x5c, 0xaa, 0x6c, 0xaf, 0xe7, 0x47, - 0xd9, 0x67, 0xaf, 0x56, 0xee, 0xae, 0xe6, 0xe1, 0xda, 0x37, 0x60, 0x46, 0x04, 0x56, 0xac, 0xdf, - 0x29, 0xc8, 0x86, 0x6e, 0xde, 0xb9, 0x23, 0xce, 0x97, 0x98, 0xb2, 0xc2, 0x19, 0xfd, 0x3c, 0x2c, - 0x8a, 0x1f, 0x76, 0x61, 0xa3, 0x64, 0xaf, 0x16, 0xac, 0xd2, 0x6e, 0xc1, 0x2a, 0xdb, 0xf7, 0xca, - 0x9f, 0xe4, 0x33, 0x4c, 0xc5, 0xa9, 0x10, 0x7b, 0x6b, 0x73, 0xbb, 0xb8, 0x9a, 0xcf, 0x5e, 0x73, - 0x21, 0x9f, 0xdc, 0x1b, 0xf7, 0x68, 0x63, 0x6b, 0x7b, 0x63, 0x83, 0xf7, 0xfa, 0x2c, 0x4c, 0x6e, - 0x6e, 0xad, 0x96, 0x2d, 0x71, 0x96, 0x87, 0x87, 0x77, 0xdb, 0x1b, 0x85, 0xed, 0xad, 0xd5, 0x4d, - 0xab, 0xf2, 0x35, 0x54, 0xca, 0x8b, 0xb0, 0x50, 0x5b, 0x2b, 0x14, 0xef, 0xd9, 0x1b, 0x9b, 0x5b, - 0x76, 0x65, 0xc3, 0x2e, 0xae, 0x16, 0x36, 0x36, 0xca, 0x6b, 0x79, 0xb8, 0xf6, 0x5f, 0x1a, 0xf0, - 0xdc, 0x80, 0xd9, 0x4c, 0xde, 0x84, 0xab, 0xab, 0xe5, 0x42, 0x69, 0xad, 0x5c, 0xab, 0xd9, 0x8c, - 0x65, 0x79, 0x63, 0x4b, 0xe8, 0x2b, 0xbb, 0xb6, 0x55, 0xd8, 0x4a, 0x4a, 0xe0, 0x55, 0x78, 0x75, - 0x30, 0x7a, 0x2c, 0x2c, 0x57, 0xe0, 0x95, 0xc1, 0xa8, 0x42, 0x78, 0x32, 0xe4, 0x1a, 0x5c, 0x1e, - 0x8c, 0x19, 0x09, 0x5d, 0x76, 0xe5, 0xbd, 0x1f, 0xfe, 0xc9, 0x8b, 0x67, 0x7e, 0xf8, 0xa7, 0x2f, - 0x1a, 0x7f, 0xfc, 0xa7, 0x2f, 0x1a, 0xff, 0xd3, 0x9f, 0xbe, 0x68, 0x7c, 0xed, 0xf5, 0x53, 0xdc, - 0x83, 0xde, 0x1b, 0xc3, 0xfd, 0xf6, 0xad, 0xff, 0x37, 0x00, 0x00, 0xff, 0xff, 0x70, 0x3a, 0xf0, - 0x0e, 0x40, 0x28, 0x01, 0x00, + 0x76, 0x20, 0xc6, 0xac, 0xea, 0x8f, 0xea, 0xd7, 0x5f, 0xd5, 0xd1, 0x4d, 0xb2, 0xc9, 0xe1, 0x4c, + 0x73, 0x72, 0x66, 0x38, 0x24, 0x67, 0x86, 0x5c, 0x36, 0x77, 0xb8, 0x3b, 0x3b, 0x9f, 0xd5, 0x55, + 0x45, 0x76, 0x0d, 0x9b, 0xdd, 0x35, 0x59, 0xfd, 0xb1, 0xb3, 0x1f, 0xca, 0xcd, 0xae, 0x8a, 0xee, + 0xce, 0x65, 0x55, 0x65, 0x6d, 0x66, 0x16, 0xc9, 0xde, 0x95, 0x70, 0xa7, 0xbb, 0xdb, 0x5b, 0xcb, + 0x3a, 0x69, 0xa5, 0xf3, 0x4a, 0xda, 0xbb, 0xd3, 0x59, 0x82, 0xe0, 0xb3, 0xe5, 0xf3, 0x49, 0x38, + 0x48, 0x3f, 0x74, 0xc6, 0xc1, 0xe7, 0x13, 0x60, 0x08, 0x6b, 0x1b, 0xf6, 0xc9, 0x86, 0x0d, 0xc3, + 0x7b, 0x46, 0xdb, 0x92, 0x0c, 0xc3, 0xe0, 0x8f, 0x83, 0x0d, 0xc1, 0x36, 0x6e, 0x0d, 0x19, 0x46, + 0xbc, 0x88, 0xc8, 0x8c, 0xc8, 0xca, 0xaa, 0xae, 0x1e, 0x72, 0x7c, 0xc7, 0x81, 0xff, 0x90, 0x5d, + 0x2f, 0xde, 0x7b, 0x11, 0x19, 0xf1, 0xe2, 0xc5, 0x8b, 0x17, 0x2f, 0x5e, 0xc0, 0x8b, 0x21, 0x6d, + 0xd2, 0x8e, 0xe7, 0x87, 0xd7, 0x9b, 0x74, 0xdf, 0xa9, 0x1f, 0x5e, 0x0f, 0x0f, 0x3b, 0x34, 0xe0, + 0xff, 0x5e, 0xeb, 0xf8, 0x5e, 0xe8, 0x91, 0x51, 0xfc, 0x71, 0x7e, 0x61, 0xdf, 0xdb, 0xf7, 0x10, + 0x72, 0x9d, 0xfd, 0xc5, 0x0b, 0xcf, 0x2f, 0xed, 0x7b, 0xde, 0x7e, 0x93, 0x5e, 0xc7, 0x5f, 0xbb, + 0xdd, 0xbd, 0xeb, 0xa1, 0xdb, 0xa2, 0x41, 0xe8, 0xb4, 0x3a, 0x02, 0xe1, 0x4a, 0x54, 0x81, 0x13, + 0x86, 0xac, 0x24, 0x74, 0xbd, 0xf6, 0xf5, 0x07, 0x37, 0xd4, 0x9f, 0x02, 0xf5, 0x8d, 0xf4, 0xb6, + 0x3c, 0xf4, 0x9d, 0x4e, 0x87, 0xfa, 0xf1, 0x1f, 0x1c, 0xdd, 0xfc, 0x83, 0x2c, 0x4c, 0xdc, 0xa5, + 0xb4, 0x53, 0x68, 0xba, 0x0f, 0x28, 0x79, 0x09, 0x46, 0xd6, 0x9d, 0x16, 0x5d, 0x34, 0x2e, 0x1a, + 0x97, 0x27, 0x56, 0x66, 0x1f, 0x1f, 0x2d, 0x4d, 0x06, 0xd4, 0x7f, 0x40, 0x7d, 0xbb, 0xed, 0xb4, + 0xa8, 0x85, 0x85, 0xe4, 0x35, 0x98, 0x60, 0xff, 0x07, 0x1d, 0xa7, 0x4e, 0x17, 0x33, 0x88, 0x39, + 0xfd, 0xf8, 0x68, 0x69, 0xa2, 0x2d, 0x81, 0x56, 0x5c, 0x4e, 0x2e, 0xc1, 0xf8, 0x1a, 0x75, 0x02, + 0x5a, 0x29, 0x2d, 0x66, 0x2f, 0x1a, 0x97, 0xb3, 0x2b, 0x53, 0x8f, 0x8f, 0x96, 0x72, 0x4d, 0x06, + 0xb2, 0xdd, 0x86, 0x25, 0x0b, 0x49, 0x05, 0xc6, 0xcb, 0x8f, 0x3a, 0xae, 0x4f, 0x83, 0xc5, 0x91, + 0x8b, 0xc6, 0xe5, 0xc9, 0xe5, 0xf3, 0xd7, 0x78, 0xa7, 0x5c, 0x93, 0x9d, 0x72, 0x6d, 0x53, 0x76, + 0xca, 0xca, 0xfc, 0x8f, 0x8e, 0x96, 0x4e, 0x3d, 0x3e, 0x5a, 0x1a, 0xa7, 0x9c, 0xe4, 0x97, 0xfe, + 0xc7, 0x25, 0xc3, 0x92, 0xf4, 0xe4, 0x1d, 0x18, 0xd9, 0x3c, 0xec, 0xd0, 0xc5, 0x89, 0x8b, 0xc6, + 0xe5, 0x99, 0xe5, 0x17, 0xae, 0xf1, 0x61, 0x88, 0x3e, 0x32, 0xfe, 0x8b, 0x61, 0xad, 0xe4, 0x1e, + 0x1f, 0x2d, 0x8d, 0x30, 0x14, 0x0b, 0xa9, 0xc8, 0x1b, 0x30, 0xb6, 0xea, 0x05, 0x61, 0xa5, 0xb4, + 0x08, 0xf8, 0x69, 0xa7, 0x1f, 0x1f, 0x2d, 0xcd, 0x1d, 0x78, 0x41, 0x68, 0xbb, 0x8d, 0xd7, 0xbd, + 0x96, 0x1b, 0xd2, 0x56, 0x27, 0x3c, 0xb4, 0x04, 0x92, 0xf9, 0x08, 0xa6, 0x35, 0x7e, 0x64, 0x12, + 0xc6, 0xb7, 0xd6, 0xef, 0xae, 0x6f, 0xec, 0xac, 0xe7, 0x4f, 0x91, 0x1c, 0x8c, 0xac, 0x6f, 0x94, + 0xca, 0x79, 0x83, 0x8c, 0x43, 0xb6, 0x50, 0xad, 0xe6, 0x33, 0x64, 0x0a, 0x72, 0xa5, 0xc2, 0x66, + 0x61, 0xa5, 0x50, 0x2b, 0xe7, 0xb3, 0x64, 0x1e, 0x66, 0x77, 0x2a, 0xeb, 0xa5, 0x8d, 0x9d, 0x9a, + 0x5d, 0x2a, 0xd7, 0xee, 0x6e, 0x6e, 0x54, 0xf3, 0x23, 0x64, 0x06, 0xe0, 0xee, 0xd6, 0x4a, 0xd9, + 0x5a, 0x2f, 0x6f, 0x96, 0x6b, 0xf9, 0x51, 0xb2, 0x00, 0x79, 0x49, 0x62, 0xd7, 0xca, 0xd6, 0x76, + 0xa5, 0x58, 0xce, 0x8f, 0x99, 0xdf, 0xcb, 0x42, 0xee, 0x1e, 0x0d, 0x9d, 0x86, 0x13, 0x3a, 0xe4, + 0x82, 0x36, 0x70, 0xf8, 0x4d, 0xca, 0x88, 0xbd, 0xd4, 0x3b, 0x62, 0xa3, 0x8f, 0x8f, 0x96, 0x8c, + 0x37, 0xd4, 0x91, 0x7a, 0x1b, 0x26, 0x4b, 0x34, 0xa8, 0xfb, 0x6e, 0x87, 0x49, 0x13, 0x8e, 0xd6, + 0xc4, 0xca, 0xb9, 0xc7, 0x47, 0x4b, 0xa7, 0x1b, 0x31, 0x58, 0xe9, 0x01, 0x15, 0x9b, 0x54, 0x60, + 0x6c, 0xcd, 0xd9, 0xa5, 0xcd, 0x60, 0x71, 0xf4, 0x62, 0xf6, 0xf2, 0xe4, 0xf2, 0x73, 0xa2, 0xd7, + 0x65, 0x03, 0xaf, 0xf1, 0xd2, 0x72, 0x3b, 0xf4, 0x0f, 0x57, 0x16, 0x1e, 0x1f, 0x2d, 0xe5, 0x9b, + 0x08, 0x50, 0x7b, 0x94, 0xa3, 0x90, 0x5a, 0x2c, 0x09, 0x63, 0xc7, 0x4a, 0xc2, 0xf3, 0x3f, 0x3a, + 0x5a, 0x32, 0xd8, 0x08, 0x09, 0x49, 0x88, 0xf9, 0xe9, 0x32, 0x71, 0x11, 0x32, 0x95, 0xd2, 0xe2, + 0x38, 0x4a, 0x60, 0xfe, 0xf1, 0xd1, 0xd2, 0x94, 0x36, 0x98, 0x99, 0x4a, 0xe9, 0xfc, 0x5b, 0x30, + 0xa9, 0xb4, 0x91, 0xe4, 0x21, 0x7b, 0x9f, 0x1e, 0xf2, 0xfe, 0xb4, 0xd8, 0x9f, 0x64, 0x01, 0x46, + 0x1f, 0x38, 0xcd, 0xae, 0xe8, 0x40, 0x8b, 0xff, 0xf8, 0x52, 0xe6, 0x8b, 0x86, 0xf9, 0x37, 0x47, + 0x20, 0x67, 0x79, 0x7c, 0x16, 0x92, 0x2b, 0x30, 0x5a, 0x0b, 0x9d, 0x50, 0x0e, 0xc5, 0xfc, 0xe3, + 0xa3, 0xa5, 0x59, 0x36, 0x43, 0xa9, 0x52, 0x1f, 0xc7, 0x60, 0xa8, 0xd5, 0x03, 0x27, 0x90, 0x43, + 0x82, 0xa8, 0x1d, 0x06, 0x50, 0x51, 0x11, 0x83, 0x5c, 0x82, 0x91, 0x7b, 0x5e, 0x83, 0x8a, 0x51, + 0x21, 0x8f, 0x8f, 0x96, 0x66, 0x5a, 0x5e, 0x43, 0x45, 0xc4, 0x72, 0xf2, 0x3a, 0x4c, 0x14, 0xbb, + 0xbe, 0x4f, 0xdb, 0x4c, 0x80, 0x47, 0x10, 0x79, 0xe6, 0xf1, 0xd1, 0x12, 0xd4, 0x39, 0x90, 0x4d, + 0xb9, 0x18, 0x81, 0x75, 0x75, 0x2d, 0x74, 0xfc, 0x90, 0x36, 0x16, 0x47, 0x87, 0xea, 0x6a, 0x36, + 0xe9, 0xe6, 0x02, 0x4e, 0x92, 0xec, 0x6a, 0xc1, 0x89, 0xac, 0xc2, 0xe4, 0x1d, 0xdf, 0xa9, 0xd3, + 0x2a, 0xf5, 0x5d, 0xaf, 0x81, 0x63, 0x98, 0x5d, 0xb9, 0xf4, 0xf8, 0x68, 0xe9, 0xcc, 0x3e, 0x03, + 0xdb, 0x1d, 0x84, 0xc7, 0xd4, 0x3f, 0x39, 0x5a, 0xca, 0x95, 0xba, 0x3e, 0xf6, 0x9e, 0xa5, 0x92, + 0x92, 0x6f, 0xb0, 0x21, 0x09, 0x42, 0xec, 0x5a, 0xda, 0xc0, 0xd1, 0x1b, 0xdc, 0x44, 0x53, 0x34, + 0xf1, 0x4c, 0xd3, 0x09, 0x42, 0xdb, 0xe7, 0x74, 0x89, 0x76, 0xaa, 0x2c, 0xc9, 0x06, 0xe4, 0x6a, + 0xf5, 0x03, 0xda, 0xe8, 0x36, 0xe9, 0x62, 0x0e, 0xd9, 0x9f, 0x15, 0x82, 0x2b, 0xc7, 0x53, 0x16, + 0xaf, 0x9c, 0x17, 0xbc, 0x49, 0x20, 0x20, 0x4a, 0xdf, 0x47, 0x4c, 0xbe, 0x94, 0xfb, 0xe1, 0x6f, + 0x2e, 0x9d, 0xfa, 0xcb, 0xff, 0xc3, 0xc5, 0x53, 0xe6, 0x1f, 0x64, 0x20, 0x9f, 0x64, 0x42, 0xf6, + 0x60, 0x7a, 0xab, 0xd3, 0x70, 0x42, 0x5a, 0x6c, 0xba, 0xb4, 0x1d, 0x06, 0x28, 0x24, 0x83, 0xbf, + 0xe9, 0x65, 0x51, 0xef, 0x62, 0x17, 0x09, 0xed, 0x3a, 0xa7, 0x4c, 0x7c, 0x95, 0xce, 0x36, 0xae, + 0xa7, 0x86, 0xda, 0x3b, 0x40, 0x09, 0x3b, 0x59, 0x3d, 0x5c, 0xef, 0xf7, 0xa9, 0x47, 0xb0, 0x15, + 0x02, 0xd4, 0x6e, 0xec, 0x1e, 0xa2, 0x64, 0x0e, 0x2f, 0x40, 0x8c, 0x24, 0x45, 0x80, 0x18, 0xd8, + 0xfc, 0x9f, 0x0d, 0x98, 0xb1, 0x68, 0xe0, 0x75, 0xfd, 0x3a, 0x5d, 0xa5, 0x4e, 0x83, 0xfa, 0x4c, + 0xfc, 0xef, 0xba, 0xed, 0x86, 0x98, 0x53, 0x28, 0xfe, 0xf7, 0xdd, 0xb6, 0x3a, 0x85, 0xb1, 0x9c, + 0x7c, 0x0e, 0xc6, 0x6b, 0xdd, 0x5d, 0x44, 0xe5, 0x73, 0xea, 0x0c, 0x8e, 0x58, 0x77, 0xd7, 0x4e, + 0xa0, 0x4b, 0x34, 0x72, 0x1d, 0xc6, 0xb7, 0xa9, 0x1f, 0xc4, 0x1a, 0x0f, 0xf5, 0xfd, 0x03, 0x0e, + 0x52, 0x09, 0x04, 0x16, 0xb9, 0x13, 0x6b, 0x5d, 0xb1, 0x52, 0xcd, 0x26, 0x74, 0x5d, 0x2c, 0x2a, + 0x2d, 0x01, 0x51, 0x45, 0x45, 0x62, 0x99, 0xbf, 0x9c, 0x81, 0x7c, 0xc9, 0x09, 0x9d, 0x5d, 0x27, + 0x10, 0xfd, 0xb9, 0x7d, 0x93, 0xe9, 0x71, 0xe5, 0x43, 0x51, 0x8f, 0xb3, 0x96, 0x7f, 0xe2, 0xcf, + 0x7b, 0x25, 0xf9, 0x79, 0x93, 0x6c, 0xd9, 0x14, 0x9f, 0x17, 0x7f, 0xd4, 0xbb, 0xc7, 0x7f, 0x54, + 0x5e, 0x7c, 0x54, 0x4e, 0x7e, 0x54, 0xfc, 0x29, 0xe4, 0x5d, 0x18, 0xa9, 0x75, 0x68, 0x5d, 0x28, + 0x11, 0xa9, 0xfb, 0xf5, 0x8f, 0x63, 0x08, 0xdb, 0x37, 0x57, 0xa6, 0x04, 0x9b, 0x91, 0xa0, 0x43, + 0xeb, 0x16, 0x92, 0x29, 0x93, 0xe6, 0x3f, 0x1d, 0x83, 0x85, 0x34, 0x32, 0xf2, 0xae, 0xbe, 0x38, + 0xf1, 0xee, 0x79, 0xae, 0xef, 0xe2, 0xb4, 0x68, 0xe8, 0xcb, 0xd3, 0x55, 0xc8, 0x55, 0x99, 0x40, + 0xd6, 0xbd, 0xa6, 0xe8, 0x39, 0xa6, 0x15, 0x73, 0x1d, 0x09, 0x33, 0xac, 0xa8, 0x9c, 0x3c, 0x07, + 0xd9, 0x2d, 0xab, 0x22, 0xba, 0x6b, 0xe2, 0xf1, 0xd1, 0x52, 0xb6, 0xeb, 0xbb, 0x8b, 0x86, 0xc5, + 0xa0, 0xe4, 0x3a, 0x8c, 0x15, 0x0b, 0x45, 0xea, 0x87, 0xd8, 0x4d, 0x53, 0x2b, 0x67, 0x99, 0xb4, + 0xd4, 0x1d, 0xbb, 0x4e, 0xfd, 0x50, 0xab, 0x5e, 0xa0, 0x91, 0xd7, 0x20, 0x5b, 0xd8, 0xa9, 0x89, + 0x9e, 0x01, 0xd1, 0x33, 0x85, 0x9d, 0xda, 0xca, 0xb4, 0xe8, 0x88, 0xac, 0xf3, 0x30, 0x60, 0xdc, + 0x0b, 0x3b, 0x35, 0x75, 0xb4, 0xc6, 0x06, 0x8c, 0xd6, 0x65, 0xc8, 0x31, 0xeb, 0x83, 0x2d, 0xf0, + 0xa8, 0x14, 0x27, 0xb8, 0x51, 0x75, 0x20, 0x60, 0x56, 0x54, 0x4a, 0x5e, 0x8a, 0x8c, 0x99, 0x5c, + 0xcc, 0x4f, 0x18, 0x33, 0xd2, 0x84, 0x21, 0x8f, 0x60, 0xba, 0x74, 0xd8, 0x76, 0x5a, 0x6e, 0x5d, + 0x2c, 0xe1, 0x13, 0xb8, 0x84, 0x5f, 0x1b, 0x30, 0x8c, 0xd7, 0x34, 0x02, 0xbe, 0xaa, 0x4b, 0xe5, + 0xbb, 0xd8, 0xe0, 0x65, 0x76, 0x72, 0x85, 0x5f, 0x34, 0x2c, 0xbd, 0x22, 0x36, 0x97, 0xa4, 0x8a, + 0x44, 0x6b, 0x2b, 0x16, 0x3b, 0x09, 0x8e, 0xe7, 0x92, 0x2f, 0x20, 0xea, 0x5c, 0x8a, 0x16, 0xdd, + 0x77, 0x21, 0x7b, 0xa7, 0x58, 0x5d, 0x9c, 0x44, 0x1e, 0x44, 0xf0, 0xb8, 0x53, 0xac, 0x16, 0x9b, + 0x5e, 0xb7, 0x51, 0xfb, 0x68, 0x6d, 0xe5, 0xac, 0x60, 0x33, 0xbd, 0x5f, 0xef, 0x68, 0x2d, 0x62, + 0x74, 0xa4, 0x0c, 0x39, 0xf9, 0x95, 0x8b, 0x53, 0xc8, 0x63, 0x2e, 0xf1, 0xf1, 0xdb, 0x37, 0xf9, + 0x5c, 0x6b, 0x88, 0xdf, 0x6a, 0x2b, 0x24, 0x0e, 0xb9, 0x89, 0x52, 0xf6, 0xe8, 0xb0, 0x52, 0x0a, + 0x16, 0xa7, 0x2f, 0x66, 0x2f, 0x4f, 0xa0, 0x78, 0xcc, 0x77, 0x18, 0xcc, 0x76, 0x1b, 0xaa, 0xb1, + 0x13, 0x21, 0x9e, 0xdf, 0x01, 0xd2, 0xdb, 0x99, 0x29, 0xe6, 0xc7, 0x6b, 0xaa, 0xf9, 0x31, 0xb9, + 0x7c, 0x5a, 0x34, 0xb0, 0xe8, 0xb5, 0x5a, 0x4e, 0xbb, 0x81, 0xb4, 0xdb, 0xcb, 0xaa, 0x55, 0x52, + 0x80, 0x99, 0xb8, 0xf5, 0x6b, 0x6e, 0x10, 0x92, 0xeb, 0x30, 0x21, 0x21, 0x6c, 0xe5, 0xc9, 0xa6, + 0x7e, 0xa7, 0x15, 0xe3, 0x98, 0x7f, 0x94, 0x01, 0x88, 0x4b, 0x9e, 0x51, 0xe5, 0xf4, 0x05, 0x4d, + 0x39, 0x9d, 0x4e, 0x4a, 0x75, 0x5f, 0xb5, 0x44, 0xde, 0x87, 0x31, 0x66, 0xa7, 0x75, 0xa5, 0x1d, + 0x7a, 0x36, 0x49, 0x8a, 0x85, 0xdb, 0x37, 0x57, 0x66, 0x04, 0xf1, 0x58, 0x80, 0x10, 0x4b, 0x90, + 0x29, 0x7a, 0xed, 0xf7, 0x46, 0xe3, 0xc1, 0x10, 0x1a, 0xed, 0xb2, 0xa2, 0x92, 0x8c, 0x78, 0x12, + 0x4b, 0x95, 0xa4, 0x28, 0xa4, 0x73, 0x5c, 0x21, 0xf1, 0x4e, 0x1d, 0x17, 0x0a, 0x29, 0xa9, 0x8e, + 0x78, 0x07, 0x1e, 0xab, 0x8e, 0x3a, 0xc9, 0xb9, 0x3e, 0x82, 0x62, 0x70, 0x39, 0xb5, 0x57, 0xd2, + 0x66, 0xf9, 0xc5, 0xe3, 0x66, 0x79, 0x72, 0x8e, 0xdf, 0xec, 0xa7, 0x00, 0x4f, 0xcb, 0x29, 0xe9, + 0x3c, 0x54, 0xc9, 0x51, 0x11, 0xbe, 0xcd, 0xe7, 0xf3, 0x58, 0xdf, 0xf9, 0x7c, 0x3a, 0x75, 0x3e, + 0xf3, 0xd9, 0xfc, 0x36, 0x8c, 0x16, 0xbe, 0xdd, 0xf5, 0xa9, 0x30, 0x18, 0xa7, 0x64, 0x9d, 0x0c, + 0x16, 0x29, 0x82, 0x59, 0x87, 0xfd, 0x54, 0x0d, 0x6d, 0x2c, 0x67, 0x35, 0x6f, 0xae, 0xd5, 0x84, + 0x31, 0x48, 0x12, 0xdd, 0xb2, 0xb9, 0xa6, 0x34, 0x3b, 0xd4, 0xbe, 0x9a, 0x51, 0x91, 0xeb, 0x90, + 0x29, 0x94, 0x70, 0xdf, 0x39, 0xb9, 0x3c, 0x21, 0xab, 0x2d, 0xad, 0x2c, 0x08, 0x92, 0x29, 0x47, + 0xdb, 0x74, 0x14, 0x4a, 0x64, 0x05, 0x46, 0xef, 0x1d, 0xd6, 0x3e, 0x5a, 0x13, 0xda, 0x6f, 0x5e, + 0xca, 0x35, 0x83, 0x6d, 0xe0, 0xd2, 0x15, 0xc4, 0x2d, 0x6e, 0x1d, 0x06, 0xdf, 0x6a, 0xaa, 0x2d, + 0x46, 0xb4, 0x4f, 0x4f, 0x81, 0xfc, 0x3b, 0xaa, 0x81, 0x22, 0x64, 0x9d, 0x6d, 0x8f, 0x85, 0xc4, + 0x19, 0xb1, 0xb9, 0xd4, 0x23, 0x71, 0x91, 0xbc, 0x5d, 0xe1, 0xa3, 0x9f, 0xe9, 0x19, 0xfd, 0x49, + 0x65, 0xf9, 0xe3, 0x63, 0x1e, 0xf5, 0x45, 0xf6, 0x13, 0xf7, 0x05, 0x79, 0x1f, 0xa6, 0xee, 0x39, + 0x6d, 0x67, 0x9f, 0x36, 0xb6, 0x02, 0x66, 0xf6, 0x8e, 0xa0, 0x16, 0x66, 0x76, 0xc2, 0xd9, 0x16, + 0x87, 0xdb, 0xdd, 0x40, 0xb3, 0x6a, 0x2d, 0x8d, 0x80, 0xdc, 0x90, 0xb2, 0x33, 0x9a, 0x22, 0x3b, + 0x72, 0xc9, 0x1e, 0x45, 0xd9, 0x11, 0x12, 0x63, 0xfe, 0x70, 0x0c, 0xbf, 0x91, 0xbc, 0x0e, 0x63, + 0x16, 0xdd, 0x8f, 0xad, 0x13, 0xdc, 0xe5, 0xfa, 0x08, 0x51, 0x3b, 0x86, 0xe3, 0xe0, 0xd2, 0x47, + 0x1b, 0xc1, 0x81, 0xbb, 0x17, 0x8a, 0xde, 0x89, 0x96, 0x3e, 0x01, 0x56, 0x96, 0x3e, 0x01, 0xd1, + 0x96, 0x3e, 0x01, 0x63, 0xf3, 0xcb, 0x2a, 0xd5, 0x44, 0xa7, 0xc9, 0x1e, 0xb6, 0x4a, 0x8a, 0xa0, + 0xfa, 0xda, 0xca, 0xc3, 0xb0, 0xc9, 0x2d, 0x98, 0x28, 0xd4, 0xeb, 0x5e, 0x57, 0xd9, 0x26, 0x2e, + 0x3e, 0x3e, 0x5a, 0x5a, 0x70, 0x38, 0x50, 0x77, 0x75, 0xc4, 0xa8, 0xa4, 0x06, 0x93, 0x65, 0xb6, + 0xb7, 0x72, 0x8b, 0x4e, 0xfd, 0x40, 0x76, 0x92, 0x9c, 0x25, 0x4a, 0x49, 0x64, 0xeb, 0x9f, 0xa6, + 0x08, 0xac, 0x33, 0xa0, 0xea, 0x3b, 0x50, 0x70, 0xc9, 0x26, 0x4c, 0xd6, 0x68, 0xdd, 0xa7, 0x61, + 0x2d, 0xf4, 0x7c, 0x9a, 0x98, 0xf4, 0x4a, 0xc9, 0xca, 0x0b, 0x72, 0x7b, 0x17, 0x20, 0xd0, 0x0e, + 0x18, 0x54, 0xe5, 0xaa, 0x20, 0x73, 0x3b, 0xbd, 0xe5, 0xf9, 0x87, 0xa5, 0x15, 0xa1, 0x08, 0xe2, + 0x55, 0x83, 0x83, 0x55, 0x3b, 0x9d, 0x41, 0x1a, 0xbb, 0xba, 0x9d, 0xce, 0xb1, 0x70, 0xa4, 0x4a, + 0x35, 0x5c, 0xaf, 0x85, 0x5a, 0x98, 0x8d, 0x7b, 0x19, 0xc1, 0xca, 0x48, 0x35, 0x02, 0x5c, 0xed, + 0xb5, 0x91, 0x12, 0x58, 0xa4, 0x03, 0x44, 0x8e, 0x1a, 0xb7, 0xa5, 0x9a, 0x34, 0x08, 0x84, 0xb6, + 0x38, 0x97, 0x18, 0xfc, 0x18, 0x61, 0xe5, 0x15, 0xc1, 0xfc, 0x79, 0x29, 0x06, 0x62, 0x6b, 0xc6, + 0x0a, 0x95, 0x7a, 0x52, 0x78, 0x93, 0xb7, 0x00, 0xca, 0x8f, 0x42, 0xea, 0xb7, 0x9d, 0x66, 0xe4, + 0xcf, 0x42, 0x8f, 0x0e, 0x15, 0x50, 0x7d, 0xa0, 0x15, 0x64, 0x52, 0x84, 0xe9, 0x42, 0x10, 0x74, + 0x5b, 0xd4, 0xf2, 0x9a, 0xb4, 0x60, 0xad, 0xa3, 0x6d, 0x35, 0xb1, 0xf2, 0xfc, 0xe3, 0xa3, 0xa5, + 0x73, 0x0e, 0x16, 0xd8, 0xbe, 0xd7, 0xa4, 0xb6, 0xe3, 0xab, 0xd2, 0xad, 0xd3, 0x98, 0xdf, 0xd1, + 0x46, 0x96, 0x49, 0xdd, 0x5d, 0x7a, 0x58, 0xf5, 0xe9, 0x9e, 0xfb, 0x48, 0x4c, 0x12, 0x94, 0xba, + 0xfb, 0xf4, 0xd0, 0xee, 0x20, 0x54, 0x95, 0xba, 0x08, 0x95, 0x7c, 0x1e, 0x72, 0x77, 0xef, 0xd5, + 0xee, 0xd2, 0xc3, 0x4a, 0x49, 0xac, 0x82, 0x9c, 0xac, 0x15, 0xd8, 0x8c, 0x54, 0xfb, 0x86, 0x08, + 0xd3, 0x5c, 0x89, 0x67, 0x18, 0xab, 0xb9, 0xd8, 0xec, 0x06, 0x21, 0xf5, 0x2b, 0x25, 0xb5, 0xe6, + 0x3a, 0x07, 0x26, 0xe4, 0x3d, 0x42, 0x35, 0xff, 0xb9, 0x81, 0xb3, 0x8b, 0x75, 0x64, 0xa5, 0xcd, + 0xb6, 0xad, 0x75, 0x1a, 0x31, 0xc0, 0x8e, 0x74, 0x05, 0x34, 0xd1, 0x91, 0x31, 0xb2, 0x5e, 0x75, + 0x66, 0xe8, 0xaa, 0x59, 0x95, 0x72, 0x13, 0x2c, 0x7c, 0xa7, 0xa2, 0x4a, 0x5f, 0x40, 0x13, 0x55, + 0xc6, 0xc8, 0xe4, 0x12, 0x8c, 0x57, 0x0a, 0xf7, 0x0a, 0xdd, 0xf0, 0x00, 0xe7, 0x76, 0x8e, 0x5b, + 0x16, 0xae, 0xd3, 0xb2, 0x9d, 0x6e, 0x78, 0x60, 0xc9, 0x42, 0xf3, 0x0f, 0x8d, 0x58, 0xb4, 0xd9, + 0x16, 0x5b, 0xf1, 0x20, 0xe2, 0x16, 0x9b, 0x6d, 0x21, 0xd4, 0x2d, 0x36, 0xfa, 0x12, 0x2d, 0x20, + 0xc5, 0x6e, 0x10, 0x7a, 0xad, 0x72, 0xbb, 0xd1, 0xf1, 0xdc, 0x76, 0x88, 0x54, 0xfc, 0xc3, 0xcc, + 0xc7, 0x47, 0x4b, 0x2f, 0xd4, 0xb1, 0xd4, 0xa6, 0xa2, 0xd8, 0x4e, 0x70, 0x49, 0xa1, 0x7e, 0x82, + 0x6f, 0x35, 0xff, 0xb3, 0x8c, 0xa6, 0x92, 0x58, 0xf3, 0x2c, 0xda, 0x69, 0xba, 0x75, 0xdc, 0x18, + 0xdc, 0xf1, 0xbd, 0x6e, 0x27, 0x1a, 0x31, 0x6c, 0x9e, 0x1f, 0x97, 0xda, 0xfb, 0xac, 0x58, 0xe7, + 0x9d, 0x42, 0x4d, 0x3e, 0x80, 0x29, 0xb6, 0x3a, 0x88, 0x9f, 0xc1, 0x62, 0x06, 0x57, 0x95, 0x0b, + 0xe8, 0x2c, 0x09, 0xa8, 0x1f, 0xb1, 0xd1, 0x96, 0x15, 0x95, 0x82, 0x34, 0x60, 0x71, 0xd3, 0x77, + 0xda, 0x81, 0x1b, 0x96, 0xdb, 0x75, 0xff, 0x10, 0x57, 0xb3, 0x72, 0xdb, 0xd9, 0x6d, 0xd2, 0x06, + 0x7e, 0x6e, 0x6e, 0xe5, 0xf2, 0xe3, 0xa3, 0xa5, 0x97, 0x43, 0x8e, 0x63, 0xd3, 0x08, 0xc9, 0xa6, + 0x1c, 0x4b, 0xe1, 0xdc, 0x97, 0x13, 0x5b, 0xfd, 0x64, 0xb7, 0xa2, 0x03, 0x7c, 0x24, 0xda, 0x25, + 0x9f, 0x8d, 0x46, 0x83, 0xa9, 0x19, 0xb5, 0x99, 0x2a, 0x81, 0xf9, 0x7f, 0x1a, 0xb1, 0xd2, 0x24, + 0xef, 0xc0, 0xa4, 0x90, 0x46, 0x45, 0x2e, 0xce, 0x33, 0xf5, 0x2b, 0x45, 0x37, 0x31, 0xb2, 0x2a, + 0x3a, 0xdb, 0x0d, 0x14, 0x8a, 0x6b, 0x8a, 0x6c, 0xe0, 0x6e, 0xc0, 0xa9, 0x37, 0x93, 0x54, 0x12, + 0x8d, 0x09, 0xc1, 0xe6, 0x5a, 0x4d, 0xef, 0x15, 0x14, 0x82, 0xb0, 0x19, 0xa4, 0x74, 0x83, 0x82, + 0xfc, 0xe4, 0x1f, 0xfe, 0xdf, 0x19, 0x69, 0xba, 0x99, 0xac, 0xc0, 0xf4, 0x8e, 0xe7, 0xdf, 0xc7, + 0xf1, 0x55, 0x3a, 0x01, 0x47, 0xfe, 0xa1, 0x2c, 0x48, 0x7e, 0x90, 0x4e, 0xa2, 0xb6, 0x4d, 0xe9, + 0x0d, 0xbd, 0x6d, 0x09, 0x0e, 0x1a, 0x01, 0x1b, 0x87, 0x88, 0x63, 0x34, 0x3b, 0x70, 0x1c, 0xe2, + 0x26, 0x68, 0x22, 0xac, 0xa2, 0x9b, 0x7f, 0xd9, 0x80, 0x49, 0xc5, 0x70, 0x66, 0xea, 0xa8, 0xea, + 0x7b, 0xdf, 0xa4, 0xf5, 0x50, 0xd7, 0x84, 0x1d, 0x0e, 0x4c, 0xa8, 0xa3, 0x08, 0x35, 0xa1, 0x01, + 0x33, 0x27, 0xd0, 0x80, 0xe6, 0xff, 0x6e, 0x08, 0xa3, 0x6a, 0x68, 0x1d, 0xa3, 0xeb, 0x83, 0xcc, + 0x49, 0x74, 0xdf, 0x07, 0x30, 0x6a, 0xd1, 0x86, 0x1b, 0x08, 0x83, 0x68, 0x4e, 0x35, 0xe0, 0xb0, + 0x20, 0xb6, 0x21, 0x7d, 0xf6, 0x53, 0xb5, 0x21, 0xb1, 0x9c, 0xad, 0x7c, 0x95, 0xe0, 0x76, 0x93, + 0x3e, 0x72, 0xb9, 0x24, 0x08, 0x1d, 0x8a, 0x2b, 0x9f, 0x1b, 0xd8, 0x7b, 0xac, 0x44, 0x2c, 0xc1, + 0xea, 0xa8, 0x6b, 0x34, 0xe6, 0xc7, 0x00, 0x71, 0x95, 0xe4, 0x2e, 0xe4, 0xc5, 0xdc, 0x70, 0xdb, + 0xfb, 0x55, 0xaf, 0xe9, 0xd6, 0x85, 0x65, 0xbe, 0xb2, 0xf4, 0xf8, 0x68, 0xe9, 0xb9, 0x7a, 0x54, + 0x66, 0x77, 0xb0, 0x50, 0xe1, 0xdb, 0x43, 0x68, 0xfe, 0x7b, 0x19, 0xb6, 0xcb, 0x60, 0x7d, 0x74, + 0x97, 0x1e, 0x86, 0xce, 0xee, 0x6d, 0xb7, 0x49, 0xd5, 0x25, 0xe9, 0x3e, 0x42, 0xed, 0x3d, 0x57, + 0x73, 0x51, 0x2b, 0xc8, 0xe4, 0x26, 0xe4, 0xee, 0xfa, 0xbb, 0x6f, 0x22, 0x61, 0x26, 0xda, 0x37, + 0xce, 0xdf, 0xf7, 0x77, 0xdf, 0x4c, 0x92, 0x45, 0x88, 0xc4, 0x84, 0xb1, 0x92, 0xd7, 0x72, 0x5c, + 0xb9, 0x57, 0x07, 0xb6, 0xe1, 0x6d, 0x20, 0xc4, 0x12, 0x25, 0x6c, 0xa7, 0x5a, 0xab, 0xae, 0x8b, + 0xe9, 0x87, 0x3b, 0xd5, 0xa0, 0xd3, 0xb6, 0x18, 0x8c, 0xd5, 0xb9, 0x56, 0x2a, 0x54, 0x71, 0xe7, + 0x30, 0x1a, 0xd7, 0xd9, 0x6c, 0x38, 0x9d, 0xe4, 0xde, 0x21, 0x42, 0x24, 0xef, 0xc2, 0xe4, 0xdd, + 0x52, 0x71, 0xd5, 0x0b, 0xf8, 0xd4, 0x19, 0x8b, 0xa7, 0xce, 0xfd, 0x46, 0xdd, 0x46, 0x3f, 0x56, + 0x52, 0x07, 0x29, 0xf8, 0xe6, 0xef, 0x18, 0x30, 0xa9, 0x6c, 0xdd, 0xc8, 0xe7, 0xc5, 0x21, 0x8a, + 0x81, 0x07, 0x83, 0x67, 0x7a, 0x37, 0x77, 0xac, 0x94, 0xfb, 0x35, 0x5a, 0x5e, 0x83, 0x8a, 0x23, + 0x95, 0x78, 0xc7, 0x93, 0x19, 0x66, 0xc7, 0xf3, 0x16, 0x00, 0x97, 0x01, 0x6c, 0xb2, 0xb2, 0x96, + 0x29, 0x07, 0xa9, 0xea, 0xb8, 0xc4, 0xc8, 0xa6, 0x05, 0x53, 0xea, 0x6e, 0x87, 0xa9, 0x1f, 0xe1, + 0x18, 0x16, 0x5e, 0x12, 0x45, 0xfd, 0x08, 0x6e, 0xbd, 0x8e, 0x6a, 0x9d, 0xc4, 0xfc, 0x2b, 0x46, + 0x3c, 0x71, 0xb7, 0x6f, 0x90, 0xb7, 0x61, 0x8c, 0xbb, 0xd4, 0xc5, 0xc9, 0xc3, 0xe9, 0xc8, 0xee, + 0x54, 0xfd, 0xed, 0xdc, 0x1d, 0xf2, 0xc7, 0xfc, 0x68, 0xed, 0x94, 0x25, 0x48, 0x22, 0x4f, 0x8a, + 0xbe, 0x81, 0x94, 0xdc, 0xd1, 0x67, 0x70, 0x23, 0xcd, 0x93, 0x62, 0xfe, 0xdb, 0x59, 0x98, 0xd1, + 0xd1, 0x54, 0xbf, 0xbb, 0x31, 0x94, 0xdf, 0xfd, 0x03, 0xc8, 0xb1, 0x2f, 0x73, 0xeb, 0x54, 0x2e, + 0xc0, 0x2f, 0xa3, 0x7f, 0x49, 0xc0, 0x04, 0x49, 0xd8, 0x39, 0xfc, 0xc9, 0xd1, 0x12, 0xd4, 0x0e, + 0x83, 0x90, 0xb6, 0x98, 0x19, 0x6a, 0x45, 0x54, 0x64, 0x59, 0x71, 0x9b, 0x66, 0xe3, 0x35, 0x49, + 0xba, 0x4d, 0x55, 0x09, 0x8c, 0x1c, 0xa8, 0x6f, 0xc0, 0x18, 0x33, 0x95, 0xa2, 0x5d, 0x12, 0xb6, + 0x92, 0x59, 0x51, 0x89, 0xd3, 0x60, 0x8e, 0x44, 0x76, 0x20, 0xb7, 0xe6, 0x04, 0x61, 0x8d, 0xd2, + 0xf6, 0x10, 0x27, 0x6a, 0x4b, 0xa2, 0xab, 0xe6, 0xf1, 0xb8, 0x2a, 0xa0, 0xb4, 0x9d, 0x38, 0x12, + 0x89, 0x98, 0x91, 0xaf, 0x03, 0x14, 0xbd, 0x76, 0xe8, 0x7b, 0xcd, 0x35, 0x6f, 0x7f, 0x71, 0x0c, + 0x9d, 0x36, 0x2f, 0x24, 0x06, 0x20, 0x46, 0xe0, 0xae, 0x9a, 0x68, 0x0f, 0x56, 0xe7, 0x05, 0x76, + 0xd3, 0xdb, 0x57, 0x25, 0x2f, 0xc6, 0x37, 0x7f, 0x92, 0x81, 0xb3, 0x7d, 0xd8, 0x30, 0xa5, 0x8d, + 0x8b, 0xaa, 0xa2, 0xb4, 0x13, 0x6b, 0x29, 0x3f, 0x38, 0xe7, 0x47, 0xac, 0x4c, 0x36, 0x46, 0xd2, + 0x8f, 0x58, 0xc9, 0x87, 0x30, 0xc2, 0x3e, 0x7e, 0x88, 0xa3, 0x22, 0xb9, 0xa1, 0x9a, 0x09, 0x5d, + 0x75, 0x60, 0xb0, 0x53, 0x90, 0x07, 0xf9, 0x3c, 0x64, 0x37, 0x37, 0xd7, 0x70, 0x54, 0xb2, 0x68, + 0xd8, 0x4d, 0x87, 0x61, 0x53, 0x3b, 0x54, 0x9c, 0x66, 0xb4, 0xd7, 0xa2, 0x93, 0x45, 0x86, 0x4e, + 0xbe, 0x9c, 0x38, 0xa6, 0xbe, 0x3a, 0xb8, 0x0b, 0x87, 0x3f, 0xb5, 0x7e, 0x92, 0xe3, 0x63, 0x3f, + 0x9e, 0x1c, 0xb7, 0xdd, 0x66, 0x48, 0x7d, 0x72, 0x9e, 0xcb, 0x7a, 0xbc, 0x5b, 0xb1, 0xa2, 0xdf, + 0x64, 0x31, 0x9e, 0x38, 0x9c, 0x53, 0x34, 0x43, 0xae, 0x2a, 0x33, 0x24, 0x8b, 0x33, 0x64, 0xa6, + 0xdf, 0x5c, 0x30, 0x7f, 0x36, 0x23, 0xab, 0xd8, 0x5e, 0x7e, 0x46, 0xfd, 0xba, 0x6f, 0x6a, 0x7e, + 0xdd, 0xf9, 0xc8, 0x5f, 0x10, 0x9d, 0x52, 0x2c, 0x1f, 0x73, 0xd8, 0xf4, 0x16, 0x4c, 0xc9, 0x2e, + 0x40, 0xf7, 0xf8, 0x15, 0x18, 0x97, 0xc7, 0xa5, 0xdc, 0x39, 0x3e, 0xab, 0xf1, 0xdc, 0x5e, 0xb6, + 0x64, 0xb9, 0xf9, 0xb7, 0x47, 0x25, 0x2d, 0xaf, 0x89, 0x75, 0x61, 0xa1, 0xd1, 0xf0, 0xd5, 0x2e, + 0x74, 0x1a, 0x0d, 0xdf, 0x42, 0x28, 0x5b, 0x13, 0xaa, 0xdd, 0xdd, 0xa6, 0x5b, 0x47, 0x1c, 0xc5, + 0x9e, 0xe9, 0x20, 0xd4, 0x66, 0xa8, 0xea, 0xcc, 0x8c, 0x91, 0xb5, 0xb3, 0x9e, 0xec, 0xc0, 0xb3, + 0x9e, 0x9f, 0x82, 0x89, 0x62, 0xab, 0xa1, 0xb9, 0x75, 0xcd, 0x94, 0x4e, 0xb9, 0x16, 0x21, 0x71, + 0xb1, 0xbe, 0x20, 0xfa, 0x68, 0xa1, 0xde, 0x6a, 0xf4, 0x3a, 0x73, 0x63, 0x96, 0xda, 0x61, 0xcd, + 0xe8, 0x93, 0x1c, 0xd6, 0xdc, 0x82, 0x89, 0xad, 0x80, 0x6e, 0x76, 0xdb, 0x6d, 0xda, 0xc4, 0x35, + 0x3d, 0xc7, 0x4d, 0xd0, 0x6e, 0x40, 0xed, 0x10, 0xa1, 0x6a, 0x03, 0x22, 0x54, 0x55, 0xac, 0xc6, + 0x07, 0x88, 0xd5, 0xe7, 0x61, 0xa4, 0xd0, 0xe9, 0xc8, 0x53, 0xac, 0xc8, 0xe7, 0xd8, 0xe9, 0xe0, + 0x0c, 0x9e, 0x71, 0x3a, 0x1d, 0xfd, 0x4c, 0x0a, 0xb1, 0xf1, 0xec, 0x86, 0x52, 0x1f, 0x07, 0x68, + 0x32, 0xb6, 0x4f, 0x3a, 0x94, 0xfa, 0xc9, 0xe1, 0x89, 0x10, 0xb5, 0x03, 0x9f, 0xa9, 0x61, 0x0f, + 0x7c, 0x6a, 0x30, 0xa3, 0x0f, 0xc1, 0x53, 0xf0, 0xd5, 0x7e, 0x38, 0x92, 0xcb, 0xe5, 0x27, 0x3e, + 0x1c, 0xc9, 0x41, 0x7e, 0xd2, 0xfc, 0x5e, 0x06, 0x26, 0x0b, 0x9d, 0xce, 0x33, 0x7e, 0xa6, 0xfc, + 0x45, 0x6d, 0x7a, 0x9f, 0x89, 0x87, 0xf1, 0x04, 0xc7, 0xc9, 0xbf, 0x9b, 0x81, 0xd9, 0x04, 0x85, + 0xda, 0x7a, 0x63, 0xc8, 0x33, 0xd6, 0xcc, 0x90, 0x67, 0xac, 0xd9, 0xfe, 0x67, 0xac, 0xea, 0xe4, + 0x19, 0x79, 0x92, 0xc9, 0xf3, 0x2a, 0x64, 0x0b, 0x9d, 0x4e, 0xd2, 0x3d, 0xdd, 0xe9, 0x6c, 0xdf, + 0xe4, 0x06, 0xb7, 0xd3, 0xe9, 0x58, 0x0c, 0x43, 0x93, 0xcd, 0xb1, 0x21, 0x65, 0xd3, 0x7c, 0x03, + 0x26, 0x90, 0x17, 0xea, 0xc3, 0x8b, 0x62, 0x22, 0x71, 0x65, 0xa8, 0xd5, 0xc5, 0x27, 0x8d, 0xf9, + 0x7f, 0xb3, 0x9d, 0x1d, 0xfb, 0xfd, 0x8c, 0xca, 0xd8, 0xb2, 0x26, 0x63, 0x79, 0x45, 0xc6, 0x86, + 0x91, 0xae, 0x5f, 0x1e, 0xc1, 0xde, 0x12, 0x72, 0x25, 0x4e, 0xe9, 0x8c, 0x94, 0x53, 0xba, 0x27, + 0x50, 0xff, 0xf7, 0x93, 0xe7, 0x75, 0x59, 0x1c, 0x8c, 0x97, 0x92, 0x4d, 0x7d, 0x2a, 0x47, 0x75, + 0xab, 0x40, 0x2a, 0xed, 0x80, 0xd6, 0xbb, 0x3e, 0xad, 0xdd, 0x77, 0x3b, 0xdb, 0xd4, 0x77, 0xf7, + 0x0e, 0xc5, 0xf6, 0x17, 0x35, 0xb4, 0x2b, 0x4a, 0xed, 0xe0, 0xbe, 0xdb, 0x61, 0x7b, 0x0f, 0x77, + 0xef, 0xd0, 0x4a, 0xa1, 0x21, 0xef, 0xc3, 0xb8, 0x45, 0x1f, 0xfa, 0x6e, 0x28, 0xcf, 0x08, 0x66, + 0xa2, 0x7d, 0x06, 0x42, 0xb9, 0x1d, 0xed, 0xf3, 0x1f, 0xea, 0xf8, 0x8b, 0x72, 0xb2, 0xcc, 0xcf, + 0x8d, 0xf8, 0x59, 0xc0, 0x74, 0xfc, 0xb5, 0x85, 0x9d, 0xda, 0xca, 0x5c, 0x9f, 0x43, 0xc3, 0x2b, + 0x30, 0x8a, 0x6e, 0x0e, 0xb1, 0x3a, 0x60, 0x38, 0x5d, 0x9d, 0x01, 0xd4, 0x3d, 0x3e, 0x62, 0x7c, + 0x7a, 0x67, 0x66, 0x3f, 0x18, 0xc5, 0xf9, 0x79, 0x4c, 0x3c, 0xe6, 0x80, 0x13, 0x5d, 0x5d, 0x56, + 0xb2, 0x27, 0x91, 0x95, 0x6d, 0x98, 0xaa, 0x31, 0x2d, 0xa1, 0x1f, 0xed, 0x5e, 0x88, 0x3b, 0xef, + 0x9a, 0x5a, 0x3c, 0xc8, 0xa8, 0xd5, 0xf8, 0x10, 0x3b, 0x29, 0x83, 0xdc, 0x76, 0x7e, 0x5e, 0x61, + 0x9c, 0x22, 0x7d, 0x91, 0x3a, 0xab, 0xf3, 0xce, 0x3a, 0xb1, 0xdc, 0x8d, 0x3d, 0x99, 0xdc, 0x8d, + 0x7f, 0x22, 0xb9, 0x4b, 0x04, 0xc1, 0xe6, 0x4e, 0x12, 0x04, 0x7b, 0xfe, 0x7d, 0x98, 0xeb, 0xe9, + 0xe1, 0x93, 0xec, 0x04, 0x3e, 0x3d, 0xb1, 0xfc, 0x19, 0x50, 0x66, 0x56, 0xce, 0xa2, 0x0d, 0xd7, + 0xa7, 0xf5, 0x10, 0x35, 0xbb, 0x50, 0xc6, 0xbe, 0x80, 0x25, 0xce, 0x18, 0x11, 0x46, 0xde, 0x83, + 0x71, 0xee, 0x02, 0xe0, 0x5b, 0xef, 0x78, 0x46, 0x0a, 0x77, 0x01, 0x8f, 0x91, 0xe6, 0x18, 0x6a, + 0xaf, 0x0a, 0x22, 0xf3, 0x8e, 0xf4, 0x3a, 0x1c, 0x33, 0x2f, 0x96, 0x60, 0x74, 0x3b, 0xee, 0x19, + 0x0c, 0xbe, 0xe2, 0x1f, 0x61, 0x71, 0xb8, 0xf9, 0xf3, 0x06, 0xcc, 0xe8, 0x5f, 0x49, 0xae, 0xc1, + 0x98, 0x88, 0x34, 0x35, 0x70, 0x2f, 0xc8, 0xbe, 0x66, 0x8c, 0xc7, 0x98, 0x6a, 0x91, 0xa5, 0x02, + 0x8b, 0xad, 0x2c, 0x82, 0x83, 0x70, 0x23, 0xe0, 0xca, 0x22, 0x84, 0xd4, 0x92, 0x65, 0xc4, 0x84, + 0x31, 0x8b, 0x06, 0xdd, 0x66, 0xa8, 0xba, 0xbb, 0x7c, 0x84, 0x58, 0xa2, 0xc4, 0x2c, 0xc2, 0x18, + 0x57, 0x49, 0x89, 0x83, 0x36, 0xe3, 0x04, 0x07, 0x6d, 0xe6, 0x91, 0x01, 0x50, 0xab, 0xad, 0xde, + 0xa5, 0x87, 0x55, 0xc7, 0xf5, 0xd1, 0x3f, 0x8b, 0x53, 0xfa, 0xae, 0x18, 0xf2, 0x29, 0xe1, 0x9f, + 0xe5, 0xd3, 0xff, 0x3e, 0x3d, 0xd4, 0xfc, 0xb3, 0x12, 0x15, 0xf5, 0x86, 0xef, 0x3e, 0x70, 0x42, + 0xca, 0x08, 0x33, 0x48, 0xc8, 0xf5, 0x06, 0x87, 0x26, 0x28, 0x15, 0x64, 0xf2, 0x75, 0x98, 0x89, + 0x7f, 0xe1, 0x56, 0x3f, 0x8b, 0x0e, 0x32, 0x29, 0x56, 0x7a, 0xe1, 0xca, 0x0b, 0x8f, 0x8f, 0x96, + 0xce, 0x2b, 0x5c, 0x93, 0x9e, 0xf5, 0x04, 0x33, 0xf3, 0xb7, 0x0c, 0x74, 0xec, 0xcb, 0x0f, 0xbc, + 0x04, 0x23, 0x51, 0xf8, 0xc0, 0x14, 0x77, 0x27, 0x24, 0x3c, 0x69, 0x58, 0x4e, 0x5e, 0x82, 0x6c, + 0xfc, 0x25, 0xa8, 0xf2, 0xf5, 0x2f, 0x60, 0xa5, 0xe4, 0x0e, 0x8c, 0x0f, 0xd5, 0x66, 0x14, 0xf1, + 0x94, 0xb6, 0x4a, 0x6a, 0x1c, 0x85, 0x0f, 0x77, 0x36, 0x3f, 0xbb, 0xa3, 0xf0, 0xfd, 0x0c, 0xcc, + 0xb2, 0x7e, 0x2d, 0x74, 0xc3, 0x03, 0xcf, 0x77, 0xc3, 0xc3, 0x67, 0x76, 0xdf, 0xff, 0x8e, 0x66, + 0xb4, 0x9d, 0x97, 0xba, 0x4f, 0xfd, 0xb6, 0xa1, 0xb6, 0xff, 0x7f, 0x34, 0x0a, 0xf3, 0x29, 0x54, + 0xe4, 0x75, 0xcd, 0xdf, 0xb5, 0x28, 0xaf, 0x87, 0xfc, 0xe4, 0x68, 0x69, 0x4a, 0xa2, 0x6f, 0xc6, + 0xd7, 0x45, 0x96, 0xf5, 0x53, 0x32, 0xde, 0x53, 0xe8, 0xfe, 0x52, 0x4f, 0xc9, 0xf4, 0xb3, 0xb1, + 0x2b, 0x30, 0x6a, 0x79, 0x4d, 0xca, 0x17, 0x52, 0x61, 0xa8, 0xf8, 0x0c, 0xa0, 0x1d, 0x46, 0x30, + 0x00, 0x59, 0x85, 0x71, 0xf6, 0xc7, 0x3d, 0xa7, 0x23, 0x9c, 0x7e, 0x24, 0xda, 0x36, 0x20, 0xb4, + 0xe3, 0xb6, 0xf7, 0xd5, 0x9d, 0x43, 0x93, 0xda, 0x2d, 0xa7, 0xa3, 0xad, 0x6c, 0x1c, 0x51, 0xdb, + 0x81, 0xe4, 0xfa, 0xef, 0x40, 0x8c, 0x63, 0x77, 0x20, 0x0d, 0x80, 0x9a, 0xbb, 0xdf, 0x76, 0xdb, + 0xfb, 0x85, 0xe6, 0xbe, 0xb8, 0x64, 0x73, 0xa5, 0xff, 0x28, 0x5c, 0x8b, 0x91, 0x51, 0x70, 0xb9, + 0x2f, 0x9c, 0xc3, 0x6c, 0xa7, 0xa9, 0x79, 0x24, 0x63, 0x54, 0xb2, 0x0e, 0x50, 0xa8, 0x87, 0xee, + 0x03, 0x26, 0xc0, 0x81, 0x08, 0xec, 0x94, 0x0d, 0x2e, 0x16, 0xee, 0xd2, 0xc3, 0x1a, 0x0d, 0x63, + 0x0f, 0xa7, 0x83, 0xa8, 0x6c, 0x1e, 0xa8, 0x7d, 0xa8, 0x70, 0x20, 0x1d, 0x38, 0x5d, 0x68, 0x34, + 0x5c, 0xf6, 0x05, 0x4e, 0x73, 0xd3, 0x67, 0x83, 0xd1, 0x40, 0xd6, 0x53, 0xe9, 0xac, 0xaf, 0x08, + 0xd6, 0x2f, 0x3a, 0x11, 0x95, 0x1d, 0x72, 0xb2, 0x64, 0x35, 0xe9, 0x8c, 0xcd, 0x0d, 0x98, 0xd1, + 0x3f, 0x5d, 0xbf, 0x1a, 0x34, 0x05, 0x39, 0xab, 0x56, 0xb0, 0x6b, 0xab, 0x85, 0x1b, 0x79, 0x83, + 0xe4, 0x61, 0x4a, 0xfc, 0x5a, 0xb6, 0x97, 0xdf, 0xbc, 0x95, 0xcf, 0x68, 0x90, 0x37, 0x6f, 0x2c, + 0xe7, 0xb3, 0x1f, 0x8e, 0xe4, 0xb2, 0xf9, 0x91, 0x0f, 0x47, 0x72, 0x23, 0xf9, 0xd1, 0x0f, 0x47, + 0x72, 0xe3, 0xf9, 0x9c, 0xd8, 0xef, 0xff, 0x9e, 0x01, 0x39, 0xd9, 0x6e, 0x72, 0x0b, 0xb2, 0xb5, + 0xda, 0x6a, 0x22, 0xba, 0x33, 0x5e, 0x5f, 0xb8, 0x26, 0x0d, 0x82, 0x03, 0x55, 0x93, 0xd6, 0x6a, + 0xab, 0x8c, 0x6e, 0x73, 0xad, 0x26, 0x96, 0x77, 0x49, 0x17, 0xab, 0x6d, 0x4e, 0x97, 0x12, 0xf2, + 0x76, 0x0b, 0xb2, 0x1f, 0xee, 0x6c, 0x8a, 0x6d, 0x89, 0xa4, 0x8b, 0x35, 0x29, 0xa7, 0xfb, 0xe6, + 0x43, 0x55, 0xbf, 0x33, 0x02, 0xd3, 0x82, 0x49, 0x45, 0x84, 0xf9, 0x72, 0xdb, 0xf2, 0xa2, 0x6b, + 0x33, 0x62, 0xb9, 0x65, 0x10, 0x4b, 0x94, 0x30, 0xeb, 0x60, 0xcd, 0xab, 0x3b, 0x4d, 0xb1, 0x6e, + 0xa3, 0x75, 0xd0, 0x64, 0x00, 0x8b, 0xc3, 0xcd, 0x3f, 0x34, 0x20, 0x5f, 0xf5, 0xbd, 0x07, 0x2e, + 0x53, 0x33, 0x9b, 0xde, 0x7d, 0xda, 0xde, 0xbe, 0x41, 0xde, 0x90, 0x93, 0xcd, 0x88, 0x36, 0xc1, + 0xa3, 0x38, 0xd9, 0x12, 0xbe, 0x51, 0x31, 0xe1, 0x94, 0xdb, 0x47, 0x99, 0xe1, 0x6f, 0x34, 0x1c, + 0x73, 0xfb, 0x68, 0x09, 0x46, 0xb1, 0x39, 0x4a, 0x50, 0xf9, 0x68, 0xc8, 0x00, 0x16, 0x87, 0xab, + 0x9b, 0xca, 0x4c, 0xcf, 0x37, 0x2c, 0x7f, 0xa6, 0x6e, 0x05, 0xe8, 0x1f, 0x37, 0x94, 0xa6, 0xfe, + 0x18, 0x16, 0x92, 0x5d, 0x82, 0x0e, 0x8a, 0x02, 0xcc, 0xea, 0x70, 0xe9, 0xab, 0x38, 0x9b, 0x5a, + 0xd7, 0xf6, 0xb2, 0x95, 0xc4, 0x37, 0xff, 0xd4, 0x80, 0x09, 0xfc, 0xd3, 0xea, 0x36, 0xf1, 0xdc, + 0xb9, 0xb0, 0x53, 0x13, 0xe1, 0x6e, 0xaa, 0x19, 0xe7, 0x3c, 0x0c, 0x6c, 0x11, 0x1b, 0xa7, 0xe9, + 0x97, 0x08, 0x59, 0x90, 0xf2, 0xe0, 0x3e, 0x79, 0x40, 0x15, 0x91, 0xf2, 0x28, 0xc0, 0x20, 0x41, + 0x2a, 0x90, 0x31, 0x54, 0x62, 0xa7, 0xc6, 0xc4, 0x4f, 0x3d, 0x96, 0x42, 0x3a, 0xaf, 0xa9, 0x87, + 0x4a, 0x70, 0x34, 0x3c, 0x95, 0xda, 0xa9, 0x15, 0xac, 0x75, 0xed, 0x54, 0x8a, 0xb5, 0x51, 0x8b, + 0xc6, 0x12, 0x48, 0xe6, 0xff, 0x32, 0x9e, 0xec, 0x40, 0xb1, 0xd4, 0x9d, 0x70, 0x6e, 0xbc, 0x0d, + 0xa3, 0x85, 0x66, 0xd3, 0x7b, 0x28, 0xb4, 0x84, 0xf4, 0x97, 0x44, 0xfd, 0xc7, 0x57, 0x32, 0x87, + 0xa1, 0x68, 0x81, 0xb5, 0x0c, 0x40, 0x8a, 0x30, 0x51, 0xd8, 0xa9, 0x55, 0x2a, 0xa5, 0xcd, 0xcd, + 0x35, 0x71, 0x15, 0xf4, 0x15, 0xd9, 0x3f, 0xae, 0xdb, 0xb0, 0x93, 0xc7, 0x37, 0xb1, 0xe5, 0x1e, + 0xd3, 0x91, 0x77, 0x01, 0x3e, 0xf4, 0xdc, 0xf6, 0x3d, 0x1a, 0x1e, 0x78, 0x0d, 0xf1, 0xf1, 0xcf, + 0x3f, 0x3e, 0x5a, 0x9a, 0xfc, 0xa6, 0xe7, 0xb6, 0xed, 0x16, 0x82, 0x59, 0xdb, 0x63, 0x24, 0x4b, + 0xf9, 0x9b, 0xf5, 0xf4, 0x8a, 0xc7, 0xcf, 0x92, 0x47, 0xe3, 0x9e, 0xde, 0xf5, 0x7a, 0x8e, 0x91, + 0x25, 0x1a, 0x69, 0xc1, 0x6c, 0xad, 0xbb, 0xbf, 0x4f, 0x99, 0x56, 0x17, 0xbb, 0xdf, 0x31, 0xb1, + 0xe7, 0x8a, 0x2e, 0xd2, 0xf2, 0x9d, 0x08, 0xdb, 0x9f, 0x04, 0x2b, 0xaf, 0x33, 0x41, 0xfe, 0xf1, + 0xd1, 0x92, 0x38, 0x16, 0x62, 0x46, 0x5a, 0x20, 0xe9, 0x7b, 0xfd, 0x2f, 0x49, 0xde, 0x64, 0x03, + 0xc6, 0xee, 0xb8, 0xe1, 0x6a, 0x77, 0x57, 0x6c, 0x5f, 0x5f, 0x1c, 0x30, 0x69, 0x38, 0x22, 0xdf, + 0xc1, 0xef, 0xbb, 0xe1, 0x41, 0x57, 0x0d, 0x5f, 0x14, 0x6c, 0xc8, 0x0e, 0xe4, 0x8a, 0xae, 0x5f, + 0x6f, 0xd2, 0x62, 0x45, 0xac, 0xfa, 0x2f, 0x0d, 0x60, 0x29, 0x51, 0x79, 0xbf, 0xd4, 0xf1, 0x57, + 0xdd, 0x55, 0xad, 0x00, 0x89, 0x41, 0xfe, 0x2d, 0x03, 0x9e, 0x8b, 0x5a, 0x5f, 0xd8, 0xa7, 0xed, + 0xf0, 0x9e, 0x13, 0xd6, 0x0f, 0xa8, 0x1f, 0xdd, 0x21, 0x19, 0xd0, 0x4b, 0x5f, 0xea, 0xe9, 0xa5, + 0xcb, 0x71, 0x2f, 0x39, 0x8c, 0x99, 0xdd, 0xe2, 0xdc, 0x7a, 0xfb, 0x6c, 0x50, 0xad, 0xc4, 0x06, + 0xb8, 0xdb, 0xdd, 0xa5, 0x7e, 0x9b, 0x86, 0x34, 0x10, 0x41, 0xd5, 0xaf, 0x0c, 0xf8, 0xe0, 0x18, + 0x59, 0x84, 0x14, 0x46, 0xbf, 0xb5, 0xd0, 0x89, 0x08, 0x4a, 0xee, 0xca, 0xf8, 0x60, 0x6e, 0x91, + 0x5c, 0x1c, 0xc0, 0x9b, 0xc7, 0x0c, 0xcf, 0x0f, 0x88, 0x35, 0xe7, 0xa3, 0xbd, 0xe6, 0xec, 0x0a, + 0x23, 0xe4, 0x98, 0xd1, 0x5e, 0x73, 0xe2, 0xd1, 0x6e, 0x3a, 0xc9, 0xd1, 0x5e, 0x73, 0x76, 0xcd, + 0x7f, 0x31, 0x02, 0xe7, 0xfb, 0x8b, 0x0a, 0xf9, 0x48, 0xce, 0x5f, 0xae, 0x25, 0x2f, 0x1d, 0x2b, + 0x5c, 0xd7, 0x8e, 0x9d, 0xd5, 0x5f, 0x86, 0x85, 0x72, 0x3b, 0xa4, 0x7e, 0xc7, 0x77, 0xe5, 0x0d, + 0xa1, 0x55, 0x2f, 0x90, 0xa1, 0x12, 0x2f, 0x3f, 0x3e, 0x5a, 0xba, 0x48, 0xa3, 0x72, 0x11, 0x35, + 0x83, 0x81, 0x1b, 0x0a, 0xab, 0x54, 0x0e, 0xe7, 0x7f, 0x2b, 0x0b, 0x23, 0xa8, 0x94, 0x5f, 0x82, + 0x6c, 0xad, 0xbb, 0x2b, 0xb4, 0x31, 0x37, 0x5f, 0x34, 0x51, 0x67, 0xa5, 0xe4, 0x8b, 0x00, 0x16, + 0xed, 0x78, 0x81, 0x1b, 0x7a, 0xfe, 0xa1, 0x1a, 0x66, 0xe9, 0x47, 0x50, 0x3d, 0x60, 0x48, 0x42, + 0xc9, 0x2a, 0xcc, 0xc6, 0xbf, 0x36, 0x1e, 0xb6, 0xa9, 0xf4, 0xba, 0xe1, 0x0e, 0x2b, 0x26, 0xb7, + 0x3d, 0x56, 0xa6, 0x4e, 0xde, 0x04, 0x19, 0x59, 0x86, 0xdc, 0x8e, 0xe7, 0xdf, 0xdf, 0x63, 0x3d, + 0x3c, 0x12, 0xab, 0x97, 0x87, 0x02, 0xa6, 0x4e, 0x23, 0x89, 0x47, 0xde, 0x86, 0xc9, 0x72, 0xfb, + 0x81, 0xeb, 0x7b, 0xed, 0x16, 0x6d, 0xcb, 0xc8, 0x18, 0xee, 0x39, 0x88, 0xc1, 0x5a, 0xe0, 0x74, + 0x0c, 0x66, 0xfb, 0x88, 0x42, 0x3d, 0xf4, 0x7c, 0x11, 0x18, 0xc3, 0xc7, 0x89, 0x01, 0xb4, 0x71, + 0x62, 0x00, 0xd6, 0x89, 0x16, 0xdd, 0x13, 0x9e, 0x51, 0xec, 0x44, 0x9f, 0xee, 0x69, 0x51, 0xe1, + 0x74, 0x8f, 0xa9, 0x47, 0x8b, 0xee, 0xe1, 0xe6, 0x27, 0x17, 0xb7, 0xdf, 0xa7, 0x7b, 0x3d, 0xdb, + 0x66, 0x81, 0x66, 0xfe, 0x76, 0x7f, 0x81, 0x5b, 0x73, 0x4e, 0x2a, 0x70, 0x6b, 0xce, 0x10, 0x02, + 0xf7, 0x7a, 0x14, 0x86, 0x94, 0x89, 0xa3, 0xec, 0x79, 0x18, 0x92, 0x3a, 0x21, 0x38, 0xce, 0xf9, + 0x7f, 0xe3, 0x44, 0x42, 0x24, 0x3a, 0x29, 0x33, 0x6c, 0x27, 0x65, 0x87, 0xea, 0x24, 0xb2, 0x02, + 0xd3, 0xd1, 0x2d, 0xfb, 0xaa, 0x23, 0x82, 0x72, 0x45, 0x18, 0x4f, 0x94, 0x33, 0xc1, 0xee, 0x38, + 0xa1, 0x6a, 0x94, 0xeb, 0x24, 0xe4, 0x1d, 0x98, 0x14, 0xb1, 0x78, 0xc8, 0x61, 0x34, 0x0e, 0x02, + 0x94, 0x81, 0x7b, 0x09, 0x7a, 0x15, 0x9d, 0x94, 0x61, 0xa6, 0xea, 0x76, 0x68, 0xd3, 0x6d, 0xd3, + 0x1a, 0x86, 0xf9, 0x08, 0x89, 0xc1, 0x98, 0xb6, 0x8e, 0x28, 0xb1, 0x79, 0x04, 0x90, 0xe6, 0x43, + 0xd0, 0x88, 0x92, 0xc2, 0x3a, 0x7e, 0x12, 0x61, 0x35, 0x7f, 0x2f, 0x03, 0x17, 0x06, 0xad, 0x39, + 0xa4, 0xa6, 0x0b, 0xcb, 0xe5, 0x21, 0xd6, 0xa9, 0xe3, 0xc5, 0xa5, 0x0c, 0x33, 0x1b, 0xfe, 0xbe, + 0xd3, 0x76, 0xbf, 0x8d, 0xb6, 0x44, 0x14, 0x4d, 0x88, 0x5f, 0xee, 0x29, 0x25, 0xba, 0x83, 0x2e, + 0x41, 0x74, 0xfe, 0x81, 0x10, 0xa3, 0x4f, 0x1a, 0x3d, 0x79, 0x0b, 0x26, 0x8a, 0x5e, 0x3b, 0xa4, + 0x8f, 0xc2, 0x44, 0x10, 0x38, 0x07, 0x26, 0x83, 0xc0, 0x25, 0xaa, 0xf9, 0x47, 0x06, 0xbc, 0x30, + 0x78, 0xdd, 0x22, 0x5b, 0x7a, 0xb7, 0x5d, 0x1d, 0x6a, 0xb5, 0x3b, 0xb6, 0xe3, 0xce, 0xdf, 0x13, + 0x5f, 0x5c, 0x86, 0x19, 0x11, 0x34, 0xa2, 0x9b, 0xc5, 0xd8, 0x81, 0x22, 0xf8, 0x2a, 0xc5, 0x34, + 0x4e, 0x10, 0x99, 0x7f, 0x61, 0xc0, 0xb9, 0xbe, 0x8b, 0x24, 0xa9, 0xea, 0xdf, 0xf0, 0xca, 0x71, + 0xab, 0xea, 0xf1, 0xcd, 0xff, 0x05, 0x43, 0xb4, 0xff, 0x3d, 0x98, 0xaa, 0x75, 0x77, 0x93, 0x37, + 0x87, 0x71, 0xe6, 0x04, 0x0a, 0x5c, 0x3b, 0xfc, 0x50, 0xe0, 0xec, 0xfb, 0x65, 0x6c, 0x1c, 0x46, + 0x73, 0x4b, 0xdb, 0x1e, 0xbf, 0x3f, 0x0a, 0x47, 0xc5, 0x68, 0x5b, 0xd5, 0x66, 0x48, 0x10, 0x99, + 0xbf, 0x90, 0x81, 0x19, 0x7e, 0x36, 0xc0, 0x37, 0x1e, 0xcf, 0xec, 0xa6, 0xee, 0x6d, 0x6d, 0x53, + 0x27, 0xaf, 0xad, 0xa8, 0x9f, 0x36, 0xd4, 0x96, 0xee, 0x00, 0x48, 0x2f, 0x0d, 0xb1, 0xe4, 0x09, + 0xd6, 0x30, 0xbb, 0xb9, 0x1b, 0xf1, 0x0d, 0x27, 0xcc, 0x80, 0x53, 0xb7, 0x71, 0x4b, 0x1d, 0x58, + 0x1a, 0x0f, 0xf3, 0xe7, 0x33, 0x30, 0xad, 0x38, 0xdf, 0x9e, 0xd9, 0x8e, 0xff, 0x92, 0xd6, 0xf1, + 0x8b, 0xd2, 0x5f, 0x15, 0x7f, 0xd9, 0x50, 0xfd, 0xde, 0x85, 0xb9, 0x1e, 0x92, 0xa4, 0x0f, 0xd3, + 0x18, 0xc6, 0x87, 0xf9, 0x7a, 0xef, 0xb5, 0x16, 0x9e, 0x68, 0x24, 0xba, 0xd6, 0xa2, 0xde, 0xa3, + 0xf9, 0x7e, 0x06, 0x16, 0xc4, 0xaf, 0x42, 0xb7, 0xe1, 0x86, 0x45, 0xaf, 0xbd, 0xe7, 0xee, 0x3f, + 0xb3, 0x63, 0x51, 0xd0, 0xc6, 0x62, 0x49, 0x1f, 0x0b, 0xe5, 0x03, 0xfb, 0x0f, 0x89, 0xf9, 0xd7, + 0x00, 0x16, 0xfb, 0x11, 0x0c, 0x1d, 0x72, 0x19, 0x5f, 0x39, 0xcc, 0x0c, 0x71, 0xe5, 0x70, 0x0d, + 0xf2, 0x58, 0x55, 0x8d, 0x06, 0xac, 0x13, 0x82, 0x38, 0xcb, 0xc1, 0xc5, 0xc7, 0x47, 0x4b, 0x17, + 0x1c, 0x56, 0x66, 0x07, 0xa2, 0xd0, 0xee, 0xfa, 0xea, 0xc6, 0xaf, 0x87, 0x92, 0xfc, 0x96, 0x01, + 0x33, 0x08, 0x2c, 0x3f, 0xa0, 0xed, 0x10, 0x99, 0x8d, 0x88, 0x50, 0x9d, 0x68, 0xcf, 0x57, 0x0b, + 0x7d, 0xb7, 0xbd, 0x2f, 0x36, 0x7d, 0xbb, 0x62, 0xd3, 0xf7, 0x0e, 0xdf, 0xac, 0x5e, 0xab, 0x7b, + 0xad, 0xeb, 0xfb, 0xbe, 0xf3, 0xc0, 0xe5, 0x7e, 0x65, 0xa7, 0x79, 0x3d, 0x4e, 0x72, 0xd5, 0x71, + 0x13, 0x69, 0xab, 0x04, 0x2b, 0xdc, 0x50, 0xf3, 0x86, 0x52, 0xac, 0x36, 0xd1, 0xcc, 0x44, 0x8b, + 0xc8, 0x57, 0xe0, 0x2c, 0xbf, 0x23, 0xc2, 0x96, 0x54, 0xb7, 0xdd, 0xf5, 0xba, 0xc1, 0x8a, 0x53, + 0xbf, 0xcf, 0xd4, 0x38, 0x3f, 0x5e, 0xc6, 0x2f, 0xaf, 0x47, 0x85, 0xf6, 0x2e, 0x2f, 0x55, 0x58, + 0xf6, 0x63, 0x40, 0x56, 0x61, 0x8e, 0x17, 0x15, 0xba, 0xa1, 0x57, 0xab, 0x3b, 0x4d, 0xb7, 0xbd, + 0x8f, 0x36, 0x51, 0x8e, 0x2f, 0x2f, 0x4e, 0x37, 0xf4, 0xec, 0x80, 0xc3, 0x15, 0x7e, 0xbd, 0x44, + 0xa4, 0xc2, 0xb6, 0x20, 0x4e, 0xe3, 0x9e, 0xf3, 0xa8, 0xe8, 0x74, 0x9c, 0xba, 0x1b, 0xf2, 0x8b, + 0x86, 0x59, 0x7e, 0x3b, 0xc0, 0xa7, 0x4e, 0xc3, 0x6e, 0x39, 0x8f, 0xec, 0xba, 0x28, 0xd4, 0xf7, + 0x20, 0x1a, 0x5d, 0xc4, 0xca, 0x6d, 0x47, 0xac, 0x26, 0x92, 0xac, 0xdc, 0x76, 0x7f, 0x56, 0x31, + 0x9d, 0x64, 0xb5, 0xe9, 0xf8, 0xfb, 0x34, 0xe4, 0xc7, 0xb2, 0x6c, 0x43, 0x6d, 0x28, 0xac, 0x42, + 0x2c, 0xb3, 0xf1, 0x88, 0x36, 0xc9, 0x4a, 0xa1, 0x63, 0x92, 0xb7, 0xe3, 0xbb, 0x21, 0x55, 0xbf, + 0x70, 0x12, 0x9b, 0x85, 0xfd, 0x8f, 0x07, 0xd3, 0xfd, 0x3e, 0xb1, 0x87, 0x32, 0xe6, 0xa6, 0x7c, + 0xe4, 0x54, 0x0f, 0xb7, 0xf4, 0xaf, 0xec, 0xa1, 0x8c, 0xb8, 0xa9, 0xdf, 0x39, 0x8d, 0xdf, 0xa9, + 0x70, 0xeb, 0xf3, 0xa1, 0x3d, 0x94, 0x64, 0x9d, 0x75, 0x5a, 0x48, 0xdb, 0x4c, 0xa2, 0xc5, 0xb1, + 0xf4, 0x0c, 0x36, 0xed, 0x65, 0x71, 0xb6, 0x92, 0xf7, 0x65, 0xb1, 0x9d, 0x72, 0x48, 0x9d, 0x24, + 0x26, 0x3f, 0x0d, 0xb3, 0x5b, 0x01, 0xbd, 0x5d, 0xa9, 0xd6, 0xe4, 0x9d, 0xa0, 0xc5, 0x59, 0x3c, + 0x71, 0xb9, 0x71, 0x8c, 0xd2, 0xb9, 0xa6, 0xd2, 0x60, 0xba, 0x29, 0x3e, 0x6e, 0xdd, 0x80, 0xda, + 0x7b, 0x6e, 0x27, 0x88, 0xee, 0xe7, 0xa9, 0xe3, 0x96, 0xa8, 0xca, 0x5c, 0x85, 0xb9, 0x1e, 0x36, + 0x64, 0x06, 0x80, 0x01, 0xed, 0xad, 0xf5, 0x5a, 0x79, 0x33, 0x7f, 0x8a, 0xe4, 0x61, 0x0a, 0x7f, + 0x97, 0xd7, 0x0b, 0x2b, 0x6b, 0xe5, 0x52, 0xde, 0x20, 0x73, 0x30, 0x8d, 0x90, 0x52, 0xa5, 0xc6, + 0x41, 0x99, 0x0f, 0x47, 0x72, 0xa3, 0xf9, 0x31, 0x2b, 0xcf, 0xa7, 0x6e, 0xc8, 0x26, 0x00, 0xae, + 0x29, 0xe6, 0xdf, 0xca, 0xc0, 0x39, 0xb9, 0xac, 0xd0, 0x90, 0x6d, 0x95, 0xdd, 0xf6, 0xfe, 0x33, + 0xbe, 0x3a, 0xdc, 0xd6, 0x56, 0x87, 0x97, 0x13, 0x2b, 0x75, 0xe2, 0x2b, 0x07, 0x2c, 0x11, 0xbf, + 0x3a, 0x0e, 0xcf, 0x0f, 0xa4, 0x22, 0x1f, 0xb1, 0xd5, 0xdc, 0xa5, 0xed, 0xb0, 0xd2, 0x68, 0xd2, + 0x4d, 0xb7, 0x45, 0xbd, 0x6e, 0x28, 0xc2, 0x20, 0x5e, 0x7a, 0x7c, 0xb4, 0x34, 0xcf, 0x73, 0x45, + 0xd9, 0x6e, 0xa3, 0x49, 0xed, 0x90, 0x17, 0x6b, 0xe2, 0xd6, 0x4b, 0xcd, 0x58, 0x46, 0xf9, 0xec, + 0x2a, 0xed, 0x90, 0xfa, 0x0f, 0x1c, 0x9e, 0x32, 0x47, 0xb0, 0xbc, 0x4f, 0x69, 0xc7, 0x76, 0x58, + 0xa9, 0xed, 0x8a, 0x62, 0x9d, 0x65, 0x0f, 0x35, 0xb9, 0xad, 0xb0, 0x2c, 0xb2, 0xdd, 0xc0, 0x3d, + 0xe7, 0x91, 0xf0, 0x00, 0x8b, 0xeb, 0xbf, 0x11, 0x4b, 0x7e, 0xfb, 0xbc, 0xe5, 0x3c, 0xb2, 0x7a, + 0x49, 0xc8, 0xd7, 0xe1, 0xb4, 0x58, 0x80, 0x44, 0x98, 0xbe, 0xfc, 0x62, 0x7e, 0x09, 0xe0, 0xd5, + 0xc7, 0x47, 0x4b, 0x67, 0xc5, 0xf2, 0x65, 0xcb, 0x2b, 0x0f, 0x69, 0x5f, 0x9d, 0xce, 0x85, 0x6c, + 0xb2, 0x05, 0x39, 0xd1, 0x1d, 0xf7, 0x68, 0x10, 0x38, 0xfb, 0xd2, 0x5b, 0xcc, 0x63, 0x91, 0x94, + 0xce, 0xb4, 0x5b, 0xbc, 0xdc, 0xea, 0x4b, 0x49, 0x56, 0x61, 0x66, 0x87, 0xee, 0xaa, 0xe3, 0x33, + 0x16, 0xa9, 0xaa, 0xfc, 0x43, 0xba, 0xdb, 0x7f, 0x70, 0x12, 0x74, 0xc4, 0x85, 0x39, 0x8c, 0xd3, + 0x5c, 0x73, 0x83, 0x90, 0xb6, 0xa9, 0x8f, 0x57, 0x99, 0xc6, 0x51, 0x19, 0x2c, 0xc6, 0x16, 0xb2, + 0x5e, 0xbe, 0xf2, 0xe2, 0xe3, 0xa3, 0xa5, 0xe7, 0x79, 0xcc, 0x67, 0x53, 0xc0, 0xed, 0x44, 0xe2, + 0xb8, 0x5e, 0xae, 0xe4, 0x1b, 0x30, 0x6b, 0x79, 0xdd, 0xd0, 0x6d, 0xef, 0xd7, 0x42, 0xdf, 0x09, + 0xe9, 0x3e, 0x5f, 0x90, 0xe2, 0x3b, 0x53, 0x89, 0x52, 0xe1, 0x2b, 0xe3, 0x40, 0x3b, 0x10, 0x50, + 0x6d, 0x45, 0xd0, 0x09, 0xc8, 0x4f, 0xc1, 0x0c, 0x8f, 0xea, 0x8e, 0x2a, 0x98, 0xd0, 0x72, 0xac, + 0xe8, 0x85, 0xdb, 0x37, 0xf8, 0x7e, 0x8b, 0x47, 0x87, 0xa7, 0x55, 0x90, 0xe0, 0x46, 0xbe, 0x2a, + 0x3a, 0xab, 0xea, 0xb6, 0xf7, 0x23, 0x31, 0x06, 0xec, 0xf9, 0x37, 0xe2, 0x2e, 0xe9, 0xb0, 0xe6, + 0x4a, 0x31, 0xee, 0x73, 0xfa, 0xd0, 0xcb, 0xc7, 0x3c, 0x32, 0x20, 0x9f, 0x6c, 0x20, 0xf9, 0x32, + 0x4c, 0x70, 0x87, 0x34, 0x0d, 0x0e, 0xc4, 0xe5, 0x2a, 0x79, 0x41, 0x27, 0x82, 0xeb, 0x44, 0x22, + 0xe5, 0x02, 0x77, 0x77, 0x53, 0xf5, 0x48, 0x76, 0xf5, 0x94, 0x15, 0x33, 0x23, 0x0d, 0x98, 0xe2, + 0x6d, 0xa0, 0x78, 0x0d, 0x50, 0x9c, 0x4b, 0xbe, 0xa8, 0x8e, 0xb9, 0x28, 0x4a, 0xf0, 0xc7, 0x9b, + 0x72, 0xe2, 0x4b, 0x39, 0x82, 0x56, 0x85, 0xc6, 0x75, 0x05, 0x20, 0x27, 0x09, 0xcd, 0x73, 0x70, + 0xb6, 0x4f, 0x9b, 0xcd, 0x07, 0xe8, 0xf0, 0xeb, 0x53, 0x23, 0xf9, 0x32, 0x2c, 0x20, 0x61, 0xd1, + 0x6b, 0xb7, 0x69, 0x3d, 0xc4, 0x49, 0x26, 0x7d, 0x06, 0x59, 0xee, 0x0e, 0xe6, 0xdf, 0x5b, 0x8f, + 0x10, 0xec, 0xa4, 0xeb, 0x20, 0x95, 0x83, 0xf9, 0x6b, 0x19, 0x58, 0x14, 0xf3, 0xd6, 0xa2, 0x75, + 0xcf, 0x6f, 0x3c, 0xfb, 0xeb, 0x44, 0x59, 0x5b, 0x27, 0x5e, 0x8a, 0xee, 0x6a, 0xa4, 0x7d, 0xe4, + 0x80, 0x65, 0xe2, 0x77, 0x0d, 0xb8, 0x30, 0x88, 0x88, 0xf5, 0x4e, 0x74, 0xed, 0x71, 0xa2, 0xe7, + 0x7a, 0x63, 0x07, 0xe6, 0x71, 0x40, 0x8b, 0x07, 0xb4, 0x7e, 0x3f, 0x58, 0xf5, 0x82, 0x10, 0xc3, + 0x22, 0x32, 0x5a, 0x48, 0xf4, 0x8a, 0xe7, 0xf1, 0xb3, 0x1b, 0x3c, 0xe0, 0x32, 0x7e, 0x7c, 0xb4, + 0x04, 0x0c, 0xc4, 0x2f, 0x2a, 0x0a, 0x2f, 0xe4, 0xa3, 0x43, 0xbb, 0x8e, 0x3c, 0xf8, 0xc5, 0xcc, + 0xfb, 0xf4, 0x30, 0xb0, 0xd2, 0x58, 0xe3, 0x11, 0x77, 0xa1, 0x1b, 0x1e, 0x54, 0x7d, 0xba, 0x47, + 0x7d, 0xda, 0xae, 0xd3, 0xcf, 0xd8, 0x11, 0xb7, 0xfe, 0x71, 0x43, 0xed, 0xcb, 0xff, 0x29, 0xc0, + 0x42, 0x1a, 0x19, 0xeb, 0x17, 0x65, 0x2b, 0x98, 0x4c, 0x56, 0xfb, 0x57, 0x0d, 0x98, 0xaa, 0xd1, + 0xba, 0xd7, 0x6e, 0xdc, 0x46, 0xc7, 0xbf, 0xe8, 0x1d, 0x9b, 0x2f, 0x85, 0x0c, 0x6e, 0xef, 0x25, + 0x4e, 0x04, 0x7e, 0x72, 0xb4, 0xf4, 0xc1, 0x70, 0x3b, 0xb0, 0xba, 0x87, 0xb7, 0xd2, 0x42, 0x4c, + 0xc2, 0x12, 0x55, 0x81, 0xb1, 0x4f, 0x5a, 0xa5, 0x64, 0x05, 0xa6, 0xc5, 0x74, 0xf5, 0xd4, 0x5b, + 0xaf, 0xe8, 0xe0, 0xae, 0xcb, 0x82, 0x9e, 0x6b, 0xf2, 0x1a, 0x09, 0xb9, 0x09, 0xd9, 0xad, 0xe5, + 0xdb, 0x62, 0x0c, 0xe4, 0xa5, 0x9d, 0xad, 0xe5, 0xdb, 0xe8, 0xe4, 0x61, 0x86, 0xf3, 0x74, 0x77, + 0x59, 0xf3, 0xc5, 0x6f, 0x2d, 0xdf, 0x26, 0x7f, 0x09, 0x4e, 0x97, 0xdc, 0x40, 0x54, 0xc1, 0x83, + 0x2d, 0x1a, 0x18, 0x5c, 0x38, 0xd6, 0x47, 0x7a, 0xbf, 0x90, 0x2a, 0xbd, 0x2f, 0x36, 0x22, 0x26, + 0x36, 0x8f, 0xe4, 0x68, 0x24, 0x6f, 0xf7, 0xa6, 0xd7, 0x43, 0xbe, 0x09, 0x33, 0xe8, 0x6f, 0xc4, + 0xf8, 0x13, 0x4c, 0xb8, 0x31, 0xde, 0xa7, 0xe6, 0xcf, 0xa5, 0xd6, 0x7c, 0x1e, 0xdd, 0x97, 0x36, + 0x46, 0xb1, 0x60, 0x72, 0x0e, 0x6d, 0x2f, 0xab, 0x71, 0x26, 0x1f, 0xc2, 0xac, 0x30, 0x2a, 0x36, + 0xf6, 0x36, 0x0f, 0x68, 0xc9, 0x39, 0x14, 0xa7, 0x34, 0xb8, 0x4f, 0x11, 0x96, 0x88, 0xed, 0xed, + 0xd9, 0xe1, 0x01, 0xb5, 0x1b, 0x8e, 0xb6, 0xfc, 0x26, 0x08, 0xc9, 0x77, 0x60, 0x72, 0xcd, 0xab, + 0x33, 0x7b, 0x12, 0x35, 0xc3, 0x04, 0xf2, 0xf9, 0x18, 0x33, 0xa7, 0x72, 0x70, 0xc2, 0x48, 0xf8, + 0xc9, 0xd1, 0xd2, 0xdb, 0x27, 0x15, 0x1a, 0xa5, 0x02, 0x4b, 0xad, 0x8d, 0x14, 0x21, 0xb7, 0x43, + 0x77, 0xd9, 0xd7, 0x26, 0xb3, 0xfe, 0x49, 0xb0, 0x38, 0x38, 0x13, 0xbf, 0xb4, 0x83, 0x33, 0x01, + 0x23, 0x3e, 0xcc, 0x61, 0xff, 0x54, 0x9d, 0x20, 0x78, 0xe8, 0xf9, 0x0d, 0xcc, 0xa5, 0x33, 0xd9, + 0xa7, 0xf3, 0x97, 0x53, 0x3b, 0xff, 0x02, 0xef, 0xfc, 0x8e, 0xc2, 0x41, 0x35, 0x8b, 0x7a, 0xd8, + 0x93, 0x6f, 0xc0, 0x8c, 0x45, 0xbf, 0xd5, 0x75, 0x7d, 0x7a, 0xef, 0x76, 0x01, 0x67, 0xe5, 0x94, + 0x16, 0xa2, 0xa9, 0x17, 0x72, 0xdb, 0xcb, 0xe7, 0x30, 0xe9, 0x57, 0xb1, 0x5b, 0x7b, 0x8e, 0xee, + 0x26, 0x56, 0x49, 0x48, 0x15, 0x26, 0x4b, 0xf4, 0x81, 0x5b, 0xa7, 0x18, 0x48, 0x86, 0xfb, 0x50, + 0x25, 0x0b, 0x59, 0x5c, 0xc2, 0x3d, 0x0c, 0x0d, 0x04, 0xf0, 0xb0, 0x34, 0x3d, 0x26, 0x3d, 0x42, + 0x24, 0xb7, 0x20, 0x5b, 0x29, 0x55, 0x71, 0x13, 0x1a, 0xc7, 0x67, 0x55, 0x1a, 0x55, 0x99, 0x51, + 0x0b, 0x8f, 0xb9, 0xdc, 0x86, 0x96, 0x44, 0xad, 0x52, 0xaa, 0x92, 0x3d, 0x98, 0xc6, 0x0e, 0x58, + 0xa5, 0x0e, 0xef, 0xdb, 0xd9, 0x3e, 0x7d, 0x7b, 0x2d, 0xb5, 0x6f, 0x17, 0x79, 0xdf, 0x1e, 0x08, + 0x6a, 0x2d, 0x45, 0x90, 0xca, 0x96, 0xac, 0xc0, 0x58, 0x21, 0x08, 0xdc, 0x20, 0x5c, 0xcc, 0x63, + 0x05, 0x0b, 0x52, 0x87, 0x22, 0x50, 0xb6, 0x12, 0x1d, 0x5b, 0x0e, 0x82, 0xb4, 0xf8, 0x16, 0x84, + 0xf0, 0xcd, 0xa5, 0xf9, 0x1e, 0x66, 0x2c, 0x8a, 0x89, 0x30, 0x4a, 0xa6, 0x5a, 0x91, 0x11, 0xbc, + 0x32, 0x4a, 0xa6, 0xe3, 0x26, 0x82, 0x70, 0x05, 0x92, 0xf9, 0x1f, 0x1a, 0xa8, 0x82, 0xc8, 0x55, + 0xbc, 0xd3, 0x14, 0x9d, 0xef, 0xf0, 0xaa, 0x3b, 0x89, 0x1c, 0x1b, 0x1c, 0x85, 0xbc, 0x0e, 0x63, + 0xb7, 0x9d, 0x3a, 0x0d, 0xe5, 0xa9, 0x00, 0x22, 0xef, 0x21, 0x44, 0xad, 0x81, 0xe3, 0x30, 0xeb, + 0x88, 0x0f, 0x4d, 0x21, 0xce, 0xda, 0x5e, 0x2c, 0xc8, 0xcb, 0xba, 0x68, 0x1d, 0x89, 0x21, 0x55, + 0xd2, 0xba, 0xdb, 0x75, 0x47, 0xe5, 0x95, 0xca, 0xc1, 0xfc, 0xdf, 0x8c, 0x78, 0x4e, 0x91, 0x57, + 0x61, 0xc4, 0xaa, 0x46, 0xed, 0xe7, 0xd1, 0xa5, 0x89, 0xe6, 0x23, 0x02, 0xf9, 0x2a, 0x9c, 0x56, + 0xf8, 0xe0, 0xb8, 0xd0, 0x06, 0x6b, 0x10, 0xff, 0x98, 0x57, 0x30, 0xfc, 0x51, 0x69, 0x89, 0xc3, + 0x31, 0x12, 0x2d, 0x4a, 0xe7, 0x81, 0xa6, 0x60, 0x5c, 0x50, 0xa2, 0x6d, 0x97, 0xf3, 0x56, 0x3e, + 0x56, 0xe5, 0xdd, 0x40, 0x84, 0xe4, 0xc7, 0xa6, 0x71, 0xe0, 0x11, 0x90, 0xe6, 0x9b, 0xda, 0x54, + 0x89, 0x12, 0x64, 0x1b, 0x83, 0x13, 0x64, 0x9b, 0xff, 0xd2, 0x50, 0x72, 0xa1, 0x3f, 0xa3, 0x56, + 0xc7, 0x2d, 0xcd, 0xea, 0x90, 0x33, 0x26, 0xfa, 0x2a, 0x56, 0x96, 0x6a, 0x29, 0xce, 0x2a, 0x07, + 0xd1, 0x08, 0xf8, 0x5e, 0x06, 0x26, 0xb7, 0x02, 0xea, 0xf3, 0x03, 0x95, 0xcf, 0xd6, 0x4d, 0xd1, + 0xe8, 0xbb, 0x86, 0xba, 0xcb, 0xf7, 0x27, 0x06, 0x3a, 0xda, 0x54, 0x0a, 0xd6, 0x1b, 0x0c, 0xa4, + 0xf6, 0x46, 0x37, 0xa0, 0xbe, 0x85, 0x50, 0x7e, 0x87, 0x6b, 0x4d, 0xbf, 0xc3, 0xd5, 0xb4, 0x18, + 0x8c, 0x7c, 0x00, 0xa3, 0x5b, 0xe8, 0x36, 0xd0, 0x23, 0xf8, 0x23, 0xfe, 0x58, 0xc8, 0x27, 0x66, + 0x97, 0xfd, 0xa9, 0xea, 0x15, 0x2c, 0x23, 0x35, 0x18, 0x2f, 0xfa, 0x14, 0xb3, 0x9e, 0x8f, 0x0c, + 0x1f, 0x85, 0x5a, 0xe7, 0x24, 0xc9, 0x28, 0x54, 0xc1, 0xc9, 0xfc, 0x95, 0x0c, 0x90, 0xf8, 0x1b, + 0x31, 0x2f, 0x5b, 0xf0, 0xcc, 0x0e, 0xfa, 0xfb, 0xda, 0xa0, 0x3f, 0xdf, 0x33, 0xe8, 0xfc, 0xf3, + 0x86, 0x1a, 0xfb, 0x3f, 0x34, 0xe0, 0x4c, 0x3a, 0x21, 0x79, 0x09, 0xc6, 0x36, 0x36, 0xab, 0xf1, + 0x12, 0x82, 0x9f, 0xe2, 0x75, 0x70, 0x77, 0x63, 0x89, 0x22, 0xb6, 0xce, 0x7c, 0x64, 0x15, 0x99, + 0xf2, 0x51, 0x12, 0xc4, 0x7c, 0xcb, 0xb7, 0xeb, 0xba, 0xfe, 0x11, 0x48, 0xea, 0xd8, 0x66, 0x9f, + 0xda, 0xd8, 0x7e, 0x3f, 0x03, 0xb3, 0x85, 0x7a, 0x9d, 0x06, 0x01, 0xb3, 0x28, 0x68, 0x10, 0x3e, + 0xb3, 0x03, 0x9b, 0x7e, 0xbd, 0x43, 0xfb, 0xb6, 0xa1, 0x46, 0xf5, 0x8f, 0x0c, 0x38, 0x2d, 0xa9, + 0x1e, 0xb8, 0xf4, 0xe1, 0xe6, 0x81, 0x4f, 0x83, 0x03, 0xaf, 0xd9, 0x18, 0x3a, 0x0b, 0x15, 0x5b, + 0xdc, 0x31, 0x1f, 0x87, 0x7a, 0xba, 0xb6, 0x87, 0x10, 0x6d, 0x71, 0xe7, 0x39, 0x3b, 0xae, 0xc3, + 0x78, 0xa1, 0xd3, 0xf1, 0xbd, 0x07, 0x7c, 0xda, 0x4f, 0x4b, 0x73, 0x03, 0x41, 0x5a, 0x10, 0x2f, + 0x07, 0xb1, 0x66, 0x94, 0x68, 0x9b, 0xdf, 0xaf, 0x9d, 0xe6, 0xcd, 0x68, 0xd0, 0xb6, 0x6a, 0x99, + 0x60, 0xb9, 0xf9, 0x8b, 0x23, 0x30, 0xa5, 0x7e, 0x08, 0x31, 0x79, 0x4e, 0x1a, 0xcf, 0x57, 0x63, + 0xe5, 0x1d, 0x84, 0x58, 0xa2, 0x24, 0xbe, 0x62, 0x92, 0x39, 0xf6, 0x8a, 0xc9, 0x0e, 0x4c, 0x57, + 0x7d, 0xaf, 0xe3, 0x05, 0xb4, 0xc1, 0x1f, 0xae, 0xe0, 0x5a, 0x6b, 0x5e, 0x31, 0x6a, 0x59, 0x9f, + 0xe3, 0x11, 0x02, 0x6e, 0xe9, 0x3a, 0x02, 0xdb, 0x4e, 0x3e, 0x6b, 0xa1, 0xf3, 0xe1, 0xa7, 0x93, + 0x4e, 0x20, 0x6e, 0xbc, 0x47, 0xa7, 0x93, 0x0c, 0xa2, 0x9f, 0x4e, 0x32, 0x88, 0x3a, 0x2d, 0x46, + 0x9f, 0xd6, 0xb4, 0x20, 0xbf, 0x62, 0xc0, 0x64, 0xa1, 0xdd, 0x16, 0x57, 0x57, 0x8e, 0x89, 0xdd, + 0xfd, 0x9a, 0x38, 0xa0, 0x7c, 0xfb, 0x13, 0x1d, 0x50, 0x6e, 0xfa, 0x8e, 0x1b, 0x06, 0x18, 0xd1, + 0x1c, 0x57, 0xa8, 0x5a, 0xe5, 0x4a, 0x3b, 0xc8, 0xdb, 0x90, 0x8f, 0xe4, 0xb1, 0xd2, 0x6e, 0xd0, + 0x47, 0x34, 0x58, 0x1c, 0xbf, 0x98, 0xbd, 0x3c, 0xcd, 0xdf, 0xdc, 0xd1, 0x4e, 0x5e, 0x93, 0x88, + 0xe6, 0xf7, 0x0d, 0x38, 0xa3, 0x0a, 0x44, 0xad, 0xbb, 0xdb, 0x72, 0x71, 0x77, 0x41, 0xae, 0xc1, + 0x84, 0x18, 0xaf, 0xc8, 0xfe, 0xeb, 0x4d, 0xc5, 0x13, 0xa3, 0x90, 0x32, 0x1b, 0x22, 0xc6, 0x43, + 0xf8, 0x7b, 0xe6, 0x13, 0xd3, 0x8d, 0x15, 0xad, 0x2c, 0x8a, 0xce, 0xce, 0xfb, 0xf8, 0x5b, 0x1f, + 0x3b, 0x06, 0x31, 0xdf, 0x83, 0x39, 0xbd, 0x95, 0x35, 0x8a, 0xe9, 0x54, 0xe4, 0xa7, 0x19, 0xe9, + 0x9f, 0x26, 0xcb, 0xcd, 0x1d, 0x20, 0x3d, 0xf4, 0x01, 0x9e, 0xb2, 0xd3, 0x50, 0x46, 0x81, 0x48, + 0x1f, 0x77, 0x0f, 0x62, 0xf4, 0x1a, 0xd0, 0xa4, 0xda, 0xdd, 0x48, 0x6a, 0xfe, 0xe7, 0x93, 0x30, + 0x9f, 0xa2, 0x3a, 0x8e, 0x59, 0xda, 0x97, 0xf4, 0xc9, 0x33, 0x11, 0x85, 0xc5, 0xcb, 0x29, 0xf3, + 0x9e, 0x7c, 0xe3, 0x65, 0xc0, 0x54, 0x19, 0xf4, 0xf0, 0xcb, 0xa7, 0xb1, 0xbc, 0xab, 0x37, 0x57, + 0x46, 0x9f, 0xda, 0xcd, 0x95, 0x15, 0x98, 0x16, 0x5f, 0x25, 0xa6, 0xf2, 0x58, 0xec, 0xda, 0xf1, + 0x79, 0x81, 0xdd, 0x33, 0xa5, 0x75, 0x12, 0xce, 0x23, 0xf0, 0x9a, 0x0f, 0xa8, 0xe0, 0x31, 0xae, + 0xf2, 0xc0, 0x82, 0x54, 0x1e, 0x0a, 0x09, 0xf9, 0x0f, 0x30, 0x41, 0x23, 0x42, 0xd4, 0xf9, 0x9c, + 0x1b, 0x34, 0x9f, 0x1b, 0x4f, 0x67, 0x3e, 0x3f, 0x2f, 0xdb, 0x98, 0x3e, 0xaf, 0x53, 0x9a, 0x45, + 0x7e, 0xdb, 0x80, 0x39, 0x7e, 0x7d, 0x42, 0x6d, 0xec, 0xc0, 0x90, 0xf8, 0xfa, 0xd3, 0x69, 0xec, + 0x85, 0x00, 0xab, 0xed, 0xd3, 0xd6, 0xde, 0x46, 0x91, 0xaf, 0x00, 0x44, 0x33, 0x2a, 0x58, 0x04, + 0x3d, 0x65, 0x40, 0xda, 0xf2, 0x19, 0x27, 0x0c, 0x0a, 0x23, 0x3a, 0x2d, 0x2d, 0x67, 0x04, 0x25, + 0x7f, 0x09, 0x16, 0xd8, 0x7c, 0x89, 0x20, 0xe2, 0xb2, 0xd7, 0xe2, 0x24, 0xd6, 0xf2, 0xf9, 0xfe, + 0x4b, 0xfb, 0xb5, 0x34, 0x32, 0x9e, 0x56, 0x20, 0xce, 0x6a, 0x1d, 0xb6, 0xd4, 0x9d, 0x62, 0x1a, + 0x05, 0xde, 0x9e, 0xc4, 0xd6, 0xf3, 0xf4, 0x3c, 0x7d, 0xf4, 0xdb, 0x39, 0x39, 0x17, 0xb8, 0x7e, + 0x0b, 0xf4, 0x18, 0x5e, 0x04, 0x91, 0x8f, 0x80, 0x44, 0xf7, 0x0e, 0x38, 0x8c, 0xfa, 0xf2, 0x91, + 0x07, 0xf4, 0xf3, 0xc4, 0xf7, 0x17, 0x7c, 0x59, 0xac, 0x0a, 0x49, 0x2f, 0x31, 0xa1, 0xb0, 0x20, + 0x3e, 0x9a, 0x41, 0x65, 0x02, 0xcb, 0x60, 0x71, 0x46, 0xbb, 0x4a, 0x17, 0x97, 0xc4, 0xe9, 0xaf, + 0x95, 0x2c, 0x98, 0xda, 0x6e, 0x39, 0x8d, 0x1d, 0xb9, 0x05, 0x13, 0x6b, 0xde, 0xbe, 0xdb, 0x5e, + 0x95, 0xb1, 0x03, 0xe2, 0x1c, 0xb3, 0xc9, 0x80, 0xf6, 0x81, 0x1e, 0x01, 0x10, 0xa3, 0x32, 0xab, + 0xb6, 0xe4, 0x1f, 0x5a, 0xdd, 0x36, 0x3a, 0x66, 0x72, 0xdc, 0x9c, 0x69, 0xf8, 0x87, 0xb6, 0xdf, + 0xd5, 0x23, 0xad, 0x11, 0xe9, 0xfc, 0x2e, 0x9c, 0xeb, 0x3b, 0x68, 0x29, 0x19, 0x0c, 0xae, 0xeb, + 0x19, 0x0c, 0xce, 0xf5, 0x53, 0xee, 0x81, 0x9a, 0xc5, 0xe0, 0x37, 0x8c, 0x84, 0x36, 0x17, 0xa6, + 0x17, 0xcf, 0x3c, 0xd7, 0x6f, 0xb9, 0xcb, 0x60, 0x46, 0x64, 0xae, 0xef, 0x33, 0xb1, 0xc9, 0xc7, + 0xf4, 0xbd, 0xba, 0x5e, 0xa0, 0xe6, 0x7f, 0x42, 0xc5, 0x6e, 0xfe, 0x6a, 0x06, 0x08, 0x6f, 0x61, + 0xd1, 0xe9, 0x38, 0xbb, 0x6e, 0xd3, 0x0d, 0x5d, 0xbc, 0x35, 0x92, 0x17, 0x2c, 0x9c, 0xdd, 0x26, + 0x55, 0xaf, 0x5c, 0x89, 0x58, 0x9a, 0xa8, 0xcc, 0x4e, 0x1a, 0x69, 0x3d, 0x84, 0x7d, 0x44, 0x31, + 0xf3, 0x24, 0xa2, 0xf8, 0x0d, 0x78, 0xae, 0xd0, 0xc1, 0xb4, 0xc7, 0xb2, 0x96, 0xdb, 0x9e, 0x2f, + 0x85, 0x48, 0xba, 0x6c, 0xf0, 0x8c, 0xd7, 0x89, 0xd0, 0x7a, 0x5a, 0x3a, 0x88, 0x85, 0xf9, 0x8f, + 0x32, 0x70, 0xae, 0xb7, 0x63, 0xc4, 0xb7, 0x45, 0xc3, 0x63, 0x1c, 0x33, 0x3c, 0x69, 0xfd, 0x98, + 0x41, 0xe9, 0x7c, 0x6a, 0xfd, 0xc8, 0xf3, 0x0e, 0x7f, 0xc2, 0x7e, 0xac, 0xc1, 0xa4, 0x3a, 0x93, + 0x47, 0x3e, 0xe9, 0x4c, 0x56, 0xb9, 0x98, 0xff, 0x91, 0xa1, 0x66, 0xc3, 0x25, 0x6f, 0xa4, 0x05, + 0x6b, 0xf2, 0x1c, 0x16, 0x1c, 0xac, 0xc7, 0x69, 0xca, 0x4d, 0x60, 0x26, 0x75, 0x13, 0x28, 0xd3, + 0x71, 0x64, 0x53, 0xd3, 0x71, 0x94, 0x60, 0xb6, 0xd6, 0xdd, 0x95, 0x75, 0x23, 0xe2, 0x88, 0x16, + 0x3e, 0x6d, 0xcb, 0xf6, 0xeb, 0x77, 0xda, 0x34, 0x12, 0xf3, 0xe7, 0x32, 0x30, 0x55, 0x6d, 0x76, + 0xf7, 0xdd, 0x76, 0xc9, 0x09, 0x9d, 0x67, 0x76, 0x5f, 0xfa, 0x96, 0xb6, 0x2f, 0x8d, 0x62, 0x92, + 0xa3, 0x0f, 0x1b, 0x6a, 0x53, 0xfa, 0x03, 0x03, 0x66, 0x63, 0x12, 0xae, 0x1c, 0x57, 0x61, 0x84, + 0xfd, 0x10, 0x66, 0xee, 0xc5, 0x1e, 0xc6, 0x3c, 0x13, 0x65, 0xf4, 0x97, 0xd8, 0x29, 0xea, 0xaf, + 0x8a, 0x21, 0x87, 0xf3, 0x5f, 0xe0, 0xef, 0xfb, 0x9c, 0x3c, 0x03, 0xe5, 0xef, 0x1b, 0x90, 0x4f, + 0x7e, 0x09, 0xb9, 0x0b, 0xe3, 0x8c, 0x93, 0x1b, 0xbd, 0x15, 0xf4, 0x72, 0x9f, 0x6f, 0xbe, 0x26, + 0xd0, 0x78, 0xf3, 0xb0, 0xf3, 0x29, 0x87, 0x58, 0x92, 0xc3, 0x79, 0x0b, 0xa6, 0x54, 0xac, 0x94, + 0xd6, 0xbd, 0xae, 0xaf, 0x08, 0x67, 0xd2, 0xfb, 0x41, 0x6d, 0xf5, 0xaf, 0x6b, 0xad, 0x16, 0x6b, + 0xc1, 0xb0, 0x2f, 0xc5, 0x61, 0x1a, 0x1c, 0x2e, 0xa5, 0xaa, 0x9c, 0xa5, 0x08, 0x74, 0x84, 0xc7, + 0x36, 0xb4, 0xbc, 0x3e, 0x21, 0x67, 0xb8, 0xa1, 0xed, 0x20, 0x44, 0x5d, 0x11, 0x39, 0x8e, 0xf9, + 0x77, 0xb3, 0x70, 0x26, 0x6e, 0x1e, 0x7f, 0x37, 0xaf, 0xea, 0xf8, 0x4e, 0x2b, 0x38, 0x66, 0x06, + 0x5c, 0xee, 0x69, 0x1a, 0xa6, 0x9e, 0x93, 0x4d, 0x53, 0x1a, 0x64, 0x26, 0x1a, 0x84, 0x9e, 0x00, + 0xde, 0x20, 0xd9, 0x0c, 0x72, 0x17, 0xb2, 0x35, 0x1a, 0x0a, 0x5d, 0x74, 0xa9, 0xa7, 0x57, 0xd5, + 0x76, 0x5d, 0xab, 0xd1, 0x90, 0x0f, 0x22, 0xbf, 0x21, 0x45, 0xb5, 0x5b, 0xfb, 0x6c, 0x4f, 0xb7, + 0x03, 0x63, 0xe5, 0x47, 0x1d, 0x5a, 0x0f, 0x45, 0x0e, 0xa8, 0x2b, 0x83, 0xf9, 0x71, 0x5c, 0x25, + 0xd3, 0x14, 0x45, 0x80, 0xda, 0x59, 0x1c, 0xe5, 0xfc, 0x2d, 0xc8, 0xc9, 0xca, 0x4f, 0x94, 0x31, + 0xe9, 0x2d, 0x98, 0x54, 0x2a, 0x39, 0x91, 0xd0, 0xff, 0x85, 0x01, 0x63, 0x6c, 0x25, 0xd8, 0xbe, + 0xf5, 0x8c, 0x6a, 0xa4, 0x9b, 0x9a, 0x46, 0x9a, 0x53, 0x12, 0x83, 0xe0, 0xbc, 0xbc, 0x75, 0x8c, + 0x2e, 0x3a, 0x62, 0xeb, 0x4a, 0x84, 0x4c, 0xee, 0xc0, 0xb8, 0x38, 0x38, 0x13, 0xd1, 0x4b, 0x6a, + 0xa6, 0x11, 0x79, 0x0e, 0x17, 0x99, 0xca, 0x5e, 0x27, 0xb9, 0xb7, 0x90, 0xd4, 0xa4, 0x14, 0xdf, + 0x12, 0xd7, 0x32, 0x4c, 0x7b, 0x18, 0x44, 0xcd, 0x33, 0x65, 0x28, 0x59, 0xd8, 0xfb, 0x5c, 0xdc, + 0x2a, 0x08, 0xef, 0x58, 0x76, 0x10, 0x93, 0x33, 0x32, 0x4d, 0x70, 0xaa, 0xe3, 0xec, 0xbb, 0x79, + 0x9e, 0x63, 0x42, 0x36, 0xec, 0x5d, 0x98, 0xba, 0xed, 0xf9, 0x0f, 0x1d, 0x9f, 0xdf, 0x1c, 0xc6, + 0xcf, 0xe4, 0x4f, 0x0c, 0x4c, 0xef, 0x71, 0x38, 0xbf, 0x7b, 0xfc, 0x93, 0xa3, 0xa5, 0x91, 0x15, + 0xcf, 0x6b, 0x5a, 0x1a, 0x3a, 0xd9, 0x80, 0xe9, 0x7b, 0xce, 0x23, 0x11, 0x69, 0xb3, 0xb9, 0xb9, + 0x26, 0xa2, 0x22, 0xaf, 0x3c, 0x3e, 0x5a, 0x3a, 0xd7, 0x72, 0x1e, 0x45, 0x27, 0xbc, 0xfd, 0x2f, + 0xb2, 0xeb, 0xf4, 0xc4, 0x85, 0x99, 0xaa, 0xe7, 0x87, 0xa2, 0x12, 0xb6, 0x31, 0xca, 0xf6, 0x39, + 0x63, 0xbd, 0x9e, 0x7a, 0xc6, 0x7a, 0x8e, 0xed, 0x06, 0xed, 0xbd, 0x88, 0x5c, 0xbb, 0xb9, 0xa7, + 0x31, 0x26, 0xef, 0xc2, 0x5c, 0x91, 0xfa, 0xa1, 0xbb, 0xe7, 0xd6, 0x9d, 0x90, 0xde, 0xf6, 0xfc, + 0x96, 0x13, 0x8a, 0xb5, 0x1c, 0xbd, 0x32, 0x75, 0xca, 0x39, 0xb5, 0x9c, 0xd0, 0xea, 0xc5, 0x24, + 0x5f, 0x4d, 0x8b, 0x33, 0x1d, 0x8d, 0xa3, 0xe9, 0x52, 0xe2, 0x4c, 0xfb, 0x45, 0xd3, 0xf5, 0x46, + 0x9c, 0xee, 0x0f, 0x0a, 0xe2, 0xc8, 0xad, 0xdc, 0x10, 0xf1, 0x1f, 0xc7, 0x07, 0x69, 0x44, 0xe3, + 0xd6, 0x27, 0x58, 0x63, 0x19, 0xb2, 0x2b, 0xd5, 0xdb, 0xe8, 0x67, 0x13, 0x41, 0x13, 0xb4, 0x7d, + 0xe0, 0xb4, 0xeb, 0x68, 0xe0, 0x89, 0xc0, 0x29, 0x55, 0xe1, 0xad, 0x54, 0x6f, 0x13, 0x07, 0xe6, + 0xab, 0xd4, 0x6f, 0xb9, 0xe1, 0x97, 0x6f, 0xdc, 0x50, 0x06, 0x2a, 0x87, 0x4d, 0xbb, 0x2e, 0x9a, + 0xb6, 0xd4, 0x41, 0x14, 0xfb, 0xd1, 0x8d, 0x1b, 0xa9, 0xc3, 0x11, 0x35, 0x2c, 0x8d, 0x17, 0x29, + 0xc3, 0xcc, 0x3d, 0xe7, 0x51, 0x1c, 0xef, 0x16, 0x88, 0x88, 0xfd, 0xe7, 0xa5, 0x60, 0xc5, 0xb1, + 0x72, 0xda, 0x0d, 0x33, 0x9d, 0x88, 0xbc, 0x03, 0x93, 0xb1, 0x78, 0x05, 0x22, 0xd6, 0x11, 0x0d, + 0x35, 0x45, 0x38, 0x35, 0x23, 0x53, 0x41, 0x27, 0x5b, 0x91, 0x9f, 0x87, 0x5b, 0xe9, 0x22, 0x07, + 0xee, 0x75, 0xd5, 0xcf, 0xe3, 0x60, 0x89, 0xf6, 0x59, 0xb3, 0xd1, 0xce, 0x88, 0x07, 0x00, 0x5a, + 0x3a, 0x17, 0xc5, 0x7d, 0x54, 0xf5, 0xbd, 0x56, 0x27, 0xc4, 0x80, 0x89, 0x84, 0xfb, 0xa8, 0x83, + 0x25, 0x29, 0xee, 0x23, 0x4e, 0x42, 0x28, 0x8c, 0xac, 0x79, 0xf5, 0xfb, 0x18, 0xc2, 0x30, 0xb1, + 0xf2, 0x11, 0x9b, 0xee, 0x4d, 0xaf, 0x7e, 0xff, 0xe9, 0x05, 0xa7, 0x20, 0x7b, 0xb2, 0xce, 0x9a, + 0xca, 0xa4, 0x40, 0xf4, 0x89, 0x08, 0x78, 0x58, 0x88, 0xac, 0x77, 0xa5, 0x8c, 0xdb, 0x15, 0x5c, + 0x68, 0x64, 0xd7, 0x5a, 0x3a, 0x39, 0xa1, 0x90, 0x2f, 0xd1, 0xe0, 0x7e, 0xe8, 0x75, 0x8a, 0x4d, + 0xb7, 0xb3, 0xeb, 0x39, 0x7e, 0x43, 0x84, 0x38, 0xf4, 0xce, 0xef, 0x57, 0x53, 0xe7, 0xf7, 0x5c, + 0x83, 0xd3, 0xdb, 0x75, 0xc9, 0xc0, 0xea, 0x61, 0x49, 0xbe, 0x0a, 0x33, 0x4c, 0xb8, 0xcb, 0x8f, + 0x42, 0xda, 0xe6, 0x23, 0x3f, 0x87, 0x2b, 0xf3, 0x82, 0x92, 0x91, 0x29, 0x2a, 0xe4, 0x32, 0x85, + 0x93, 0x9d, 0x46, 0x04, 0xaa, 0x4c, 0xe9, 0xac, 0x48, 0x03, 0x16, 0xef, 0x39, 0x8f, 0xe2, 0x6b, + 0xa3, 0xaa, 0x90, 0x12, 0x14, 0x30, 0x7c, 0xb6, 0x86, 0x09, 0x58, 0x9c, 0x39, 0xa1, 0x8f, 0xbc, + 0xf6, 0xe5, 0x44, 0xbe, 0x03, 0x67, 0xc5, 0x67, 0x95, 0x30, 0xdd, 0xa0, 0xe7, 0x1f, 0xd6, 0x0e, + 0x1c, 0x0c, 0x75, 0x9d, 0x3f, 0x99, 0x42, 0x94, 0x1d, 0xd6, 0x90, 0x7c, 0xec, 0x80, 0x33, 0xb2, + 0xfa, 0xd5, 0x40, 0xbe, 0x01, 0x33, 0xdc, 0x81, 0xba, 0xea, 0x05, 0x21, 0x6e, 0x42, 0x17, 0xfa, + 0xd4, 0x79, 0x29, 0xb5, 0xce, 0x3c, 0xf7, 0xca, 0xf2, 0x98, 0x47, 0xf4, 0x21, 0x27, 0xf8, 0x91, + 0xb7, 0x61, 0xb2, 0xea, 0xb6, 0xf9, 0x15, 0xea, 0x4a, 0x75, 0xf1, 0x74, 0xbc, 0xea, 0x74, 0xdc, + 0xb6, 0x2d, 0xf7, 0x7f, 0x9d, 0x48, 0x49, 0xa8, 0xd8, 0x64, 0x07, 0x26, 0x6b, 0xb5, 0xd5, 0xdb, + 0x2e, 0x5b, 0xf6, 0x3a, 0x87, 0x8b, 0x67, 0xfa, 0xb4, 0xed, 0xa5, 0xd4, 0xb6, 0x4d, 0x07, 0xc1, + 0x01, 0x3e, 0xe8, 0x61, 0xd7, 0xbd, 0xce, 0xa1, 0xa5, 0x72, 0x4a, 0x89, 0x65, 0x3a, 0xfb, 0x94, + 0x63, 0x99, 0x2a, 0x30, 0xab, 0x04, 0x68, 0x60, 0x70, 0xc6, 0x62, 0xfc, 0xe6, 0x89, 0x1a, 0xbb, + 0x94, 0x8c, 0x48, 0x4f, 0xd2, 0xc9, 0x20, 0xa6, 0x73, 0x27, 0x0c, 0x62, 0xfa, 0x70, 0x24, 0x37, + 0x9d, 0x9f, 0x31, 0xff, 0x69, 0x26, 0x31, 0xb5, 0x49, 0x05, 0xc6, 0x85, 0x3c, 0x08, 0x5b, 0xa7, + 0xb7, 0x47, 0x9f, 0x4f, 0xed, 0xd1, 0x71, 0x21, 0x61, 0x96, 0xa4, 0x27, 0x0f, 0x19, 0xab, 0x3d, + 0xa7, 0xdb, 0x94, 0x39, 0x2f, 0xbe, 0xce, 0x67, 0x2e, 0x82, 0x34, 0x1d, 0x55, 0x3a, 0x79, 0xd4, + 0xa5, 0x1e, 0xd4, 0x8b, 0xca, 0x4a, 0xd6, 0x46, 0xee, 0xf3, 0x44, 0x5f, 0xd9, 0x28, 0x74, 0x4f, + 0xcf, 0xea, 0xf5, 0xd4, 0x2a, 0x64, 0xb5, 0x98, 0xff, 0xc4, 0x80, 0x69, 0x4d, 0x37, 0x90, 0x5b, + 0x4a, 0x5c, 0x6a, 0x7c, 0x01, 0x41, 0xc3, 0x41, 0xc1, 0x49, 0x46, 0xac, 0xde, 0x12, 0x71, 0x3a, + 0x99, 0xfe, 0x74, 0xa9, 0xaf, 0xb0, 0x0c, 0xf6, 0x54, 0x44, 0x89, 0x43, 0x47, 0xfa, 0x24, 0x0e, + 0xfd, 0xe1, 0x1c, 0xcc, 0xe8, 0xc6, 0x23, 0xdb, 0xcd, 0xa1, 0xb7, 0x53, 0xba, 0xe2, 0x78, 0x2a, + 0x5c, 0x84, 0x68, 0xef, 0x3b, 0x20, 0x84, 0xbc, 0x02, 0x10, 0xc5, 0xce, 0x48, 0x6f, 0xdb, 0xe8, + 0xe3, 0xa3, 0x25, 0xe3, 0x0d, 0x4b, 0x29, 0x20, 0x3f, 0x05, 0xb0, 0xee, 0x35, 0x68, 0x94, 0xac, + 0x79, 0x80, 0xc7, 0xff, 0xd5, 0x9e, 0x24, 0x38, 0xa7, 0xdb, 0x5e, 0x83, 0xf6, 0x66, 0xbc, 0x51, + 0x38, 0x92, 0x2f, 0xc1, 0xa8, 0xd5, 0x6d, 0x52, 0xe9, 0x5b, 0x9a, 0x94, 0xb3, 0xb5, 0xdb, 0x54, + 0x9e, 0x36, 0xf5, 0xbb, 0xc9, 0x83, 0x5e, 0x06, 0x20, 0xef, 0xf3, 0xe4, 0x38, 0xe2, 0x1a, 0xfb, + 0x68, 0xec, 0x7f, 0x54, 0x74, 0x77, 0xcf, 0x45, 0x76, 0x85, 0x84, 0x6c, 0xc0, 0xb8, 0x58, 0x9a, + 0xc5, 0x41, 0xea, 0x0b, 0x69, 0x2e, 0x7c, 0xc5, 0x3e, 0x17, 0xd9, 0x76, 0x11, 0xac, 0x7b, 0xd5, + 0xb9, 0xdf, 0xef, 0x1d, 0x98, 0x60, 0xec, 0xf9, 0x5b, 0x9d, 0xe3, 0xb1, 0x97, 0x51, 0x69, 0x50, + 0xf2, 0xb9, 0xce, 0x98, 0x80, 0x7c, 0x15, 0xd3, 0x6f, 0x8b, 0xae, 0x1e, 0x78, 0x12, 0x74, 0xa9, + 0xa7, 0xab, 0x17, 0x9c, 0x4e, 0x27, 0xe5, 0xb5, 0x83, 0x88, 0x1f, 0xd9, 0x8f, 0x6e, 0x8d, 0x0f, + 0x93, 0xd0, 0xe8, 0x6a, 0x4f, 0x05, 0x8b, 0xf2, 0x22, 0x74, 0x6f, 0xd2, 0x6d, 0x8d, 0x2f, 0xe9, + 0x40, 0x3e, 0x5e, 0x16, 0x45, 0x5d, 0x30, 0xa8, 0xae, 0x37, 0x7a, 0xea, 0x52, 0x07, 0xb0, 0xa7, + 0xba, 0x1e, 0xee, 0xa4, 0x11, 0xbf, 0x45, 0x2c, 0xea, 0x9b, 0x1c, 0x54, 0xdf, 0x2b, 0x3d, 0xf5, + 0xcd, 0x37, 0x76, 0x7b, 0xeb, 0x49, 0xf0, 0x24, 0xef, 0xc0, 0xb4, 0x84, 0xe0, 0xfc, 0x10, 0x0f, + 0x24, 0xf0, 0x57, 0xb4, 0x77, 0x31, 0x1a, 0x5c, 0x4f, 0x09, 0xad, 0x22, 0xab, 0xd4, 0x5c, 0x3a, + 0xa6, 0x35, 0xea, 0xa4, 0x54, 0xe8, 0xc8, 0xe4, 0x63, 0x98, 0xac, 0xb4, 0xd8, 0x87, 0x78, 0x6d, + 0x27, 0xa4, 0x22, 0xf8, 0x55, 0x9e, 0x6a, 0x29, 0x25, 0x8a, 0xa8, 0xf2, 0xb7, 0xcc, 0xe2, 0x22, + 0xd5, 0x50, 0x56, 0x28, 0x58, 0xe7, 0x71, 0x7f, 0xaf, 0x90, 0x61, 0x19, 0x18, 0xfb, 0x7c, 0xca, + 0xc9, 0x92, 0xc2, 0x5e, 0xa4, 0x8b, 0x60, 0x50, 0x5b, 0x4c, 0x88, 0x44, 0xba, 0x08, 0x95, 0x27, + 0x79, 0x17, 0x26, 0x45, 0xae, 0xb7, 0x82, 0xb5, 0x1e, 0x2c, 0xe6, 0xe3, 0x67, 0x6c, 0x65, 0x5a, + 0x38, 0xdb, 0xf1, 0x13, 0xe1, 0x05, 0x31, 0x3e, 0xf9, 0x32, 0x2c, 0xec, 0xb8, 0xed, 0x86, 0xf7, + 0x30, 0x10, 0xcb, 0x94, 0x50, 0x74, 0x73, 0x71, 0xec, 0xe5, 0x43, 0x5e, 0x6e, 0x4b, 0x93, 0xa9, + 0x47, 0xf1, 0xa5, 0x72, 0x20, 0x3f, 0xd3, 0xc3, 0x99, 0x4b, 0x10, 0x19, 0x24, 0x41, 0xcb, 0x3d, + 0x12, 0xd4, 0x5b, 0x7d, 0x52, 0x9c, 0x52, 0xab, 0x21, 0x1e, 0x10, 0x61, 0x65, 0x88, 0xb5, 0xea, + 0x43, 0xcf, 0x6d, 0x2f, 0xce, 0xa3, 0x2e, 0x7c, 0x2e, 0x79, 0x81, 0x06, 0xf1, 0xf8, 0xa3, 0x69, + 0xf2, 0xe9, 0x47, 0xdd, 0x7e, 0xf9, 0xa6, 0xa7, 0x39, 0xee, 0x52, 0x58, 0x93, 0x8f, 0x61, 0x8a, + 0xfd, 0x1f, 0x6d, 0xab, 0x16, 0xb4, 0x58, 0x04, 0x05, 0x53, 0xd4, 0x83, 0x63, 0x84, 0xc9, 0xe8, + 0x52, 0x76, 0x5c, 0x1a, 0x2b, 0xf2, 0x16, 0x00, 0xb3, 0x11, 0x85, 0x3a, 0x3e, 0x1d, 0x67, 0x0c, + 0x44, 0x53, 0xb2, 0x57, 0x11, 0xc7, 0xc8, 0x6c, 0xaf, 0xc7, 0x7e, 0xd5, 0xba, 0x0d, 0x8f, 0xcd, + 0x8d, 0x33, 0x48, 0x8b, 0x7b, 0x3d, 0xa4, 0x0d, 0x38, 0x5c, 0x95, 0x0e, 0x05, 0x9d, 0xac, 0xc2, + 0x2c, 0x66, 0x51, 0xa9, 0x34, 0x68, 0x3b, 0xc4, 0x73, 0x98, 0xc5, 0xb3, 0xca, 0x09, 0x0f, 0x2b, + 0xb2, 0xdd, 0xa8, 0x4c, 0xb5, 0xcb, 0x12, 0x64, 0x24, 0x80, 0xf9, 0x58, 0xbb, 0xc4, 0xe7, 0x45, + 0x8b, 0xd8, 0x49, 0xf2, 0x4c, 0xaf, 0x17, 0x83, 0xeb, 0x63, 0x36, 0x22, 0x8a, 0xe2, 0x92, 0xee, + 0x4d, 0xb5, 0xc2, 0x34, 0xee, 0xc4, 0x02, 0x72, 0xa7, 0x58, 0xd5, 0xf3, 0xcb, 0x04, 0x8b, 0xe7, + 0xf0, 0x0b, 0x70, 0x98, 0xf7, 0xeb, 0x1d, 0x3b, 0x91, 0x99, 0x46, 0x3b, 0xb8, 0xe9, 0xa5, 0x26, + 0xdf, 0x86, 0xd3, 0xd1, 0x4b, 0xd7, 0xbc, 0x48, 0xc8, 0xf5, 0xf9, 0x13, 0x6a, 0xe2, 0xc6, 0x6e, + 0x54, 0x75, 0x8f, 0x48, 0xa7, 0x57, 0x61, 0xfe, 0x1d, 0x03, 0x48, 0xef, 0x77, 0x0e, 0xed, 0xc8, + 0x7e, 0x53, 0x89, 0x67, 0x56, 0x5f, 0xf3, 0x8b, 0x32, 0x4b, 0xa9, 0xeb, 0x5b, 0x1c, 0xf9, 0x7c, + 0x49, 0xb3, 0xa7, 0xfa, 0x06, 0xc1, 0x99, 0x7f, 0x66, 0xc0, 0x42, 0xda, 0x8c, 0x3a, 0x26, 0x93, + 0xbb, 0x99, 0x88, 0x9d, 0x43, 0xcf, 0x34, 0x8f, 0x9d, 0x8b, 0x22, 0xe6, 0x96, 0x60, 0x94, 0x7d, + 0x81, 0x3c, 0x5f, 0x44, 0xa3, 0x8d, 0x7d, 0x62, 0x60, 0x71, 0x38, 0x43, 0xe0, 0xd7, 0x07, 0x99, + 0x55, 0x37, 0xca, 0x11, 0x70, 0xc0, 0x2c, 0x0e, 0x67, 0x08, 0xcc, 0x38, 0x94, 0xc6, 0x0c, 0x22, + 0x30, 0x9b, 0x31, 0xb0, 0x38, 0x9c, 0x5c, 0x82, 0xf1, 0x8d, 0xf6, 0x1a, 0x75, 0x1e, 0xc8, 0x8c, + 0x57, 0xe8, 0x49, 0xf7, 0xda, 0x76, 0x93, 0xc1, 0x2c, 0x59, 0x68, 0xfe, 0xc0, 0x80, 0xb9, 0x9e, + 0xc9, 0x7c, 0x7c, 0xb2, 0xfa, 0xc1, 0x51, 0x42, 0xc3, 0x7c, 0x1f, 0x6f, 0xfe, 0x48, 0x7a, 0xf3, + 0xcd, 0xdf, 0x1d, 0x81, 0xb3, 0x7d, 0x6c, 0xab, 0x38, 0xc2, 0xcf, 0x38, 0x36, 0xc2, 0xef, 0x6b, + 0xcc, 0x96, 0x71, 0xdc, 0x56, 0xb0, 0xe9, 0xc5, 0x2d, 0x8e, 0x83, 0x21, 0xb0, 0x4c, 0xe6, 0x92, + 0x7e, 0x51, 0x4c, 0xcd, 0x73, 0x75, 0xa4, 0xb0, 0x43, 0xaf, 0xe7, 0x28, 0x55, 0x67, 0xd6, 0x13, + 0x63, 0x97, 0xfd, 0xd7, 0x24, 0xc6, 0x4e, 0x8f, 0x6c, 0x19, 0x79, 0xaa, 0x91, 0x2d, 0xe9, 0x67, + 0xc7, 0xa3, 0x4f, 0x72, 0x06, 0x5f, 0x84, 0xe9, 0x1a, 0x75, 0xfc, 0xfa, 0x41, 0x21, 0xe0, 0x83, + 0x34, 0x16, 0xe7, 0x99, 0x0a, 0xb0, 0xc0, 0x76, 0x82, 0xde, 0xb1, 0xd0, 0x68, 0xcc, 0x1f, 0x64, + 0xf4, 0xd0, 0xc0, 0x7f, 0x1d, 0xe5, 0xe5, 0x0a, 0x8c, 0xee, 0x1c, 0x50, 0x5f, 0xaa, 0x1e, 0x6c, + 0xc8, 0x43, 0x06, 0x50, 0x1b, 0x82, 0x18, 0xe4, 0x36, 0xcc, 0x54, 0x79, 0xff, 0xc9, 0x4e, 0x19, + 0x89, 0x17, 0xaa, 0x8e, 0x30, 0xa7, 0x52, 0x7a, 0x25, 0x41, 0x65, 0x7e, 0x07, 0xa6, 0xd4, 0x46, + 0xa3, 0x62, 0x61, 0xbf, 0xc5, 0xcc, 0xe6, 0x8a, 0x85, 0x01, 0x2c, 0x0e, 0x3f, 0xf6, 0x21, 0x8a, + 0xb8, 0x37, 0xb3, 0xc7, 0xf5, 0x26, 0xab, 0x1c, 0xe5, 0x56, 0xa9, 0x1c, 0x7f, 0xab, 0x95, 0x87, + 0x0c, 0x60, 0x71, 0xf8, 0x53, 0xad, 0xfc, 0x3f, 0x91, 0x79, 0xd0, 0xde, 0x84, 0x89, 0x78, 0x81, + 0x8e, 0xd3, 0xfd, 0xce, 0xa7, 0x2d, 0xbb, 0x31, 0x26, 0xab, 0x6a, 0x9b, 0xfa, 0xbb, 0x5a, 0x1c, + 0xf1, 0x03, 0x06, 0x50, 0xab, 0x42, 0x8c, 0x93, 0x8c, 0xeb, 0x75, 0x18, 0x2f, 0x08, 0x37, 0x22, + 0x1f, 0x50, 0x1e, 0x2b, 0xdd, 0xe3, 0x33, 0x94, 0x58, 0xe6, 0x0f, 0x0d, 0x38, 0x9d, 0x6a, 0xb7, + 0xb3, 0x5a, 0xf9, 0x06, 0x41, 0x11, 0xeb, 0xe4, 0xee, 0x80, 0x63, 0x9c, 0x24, 0x26, 0x7a, 0xf8, + 0x6f, 0x31, 0x5f, 0x84, 0x89, 0xc8, 0x6b, 0x44, 0x16, 0xe4, 0xd0, 0xe1, 0xd9, 0x92, 0x74, 0x3e, + 0xfc, 0x85, 0x01, 0x63, 0xac, 0x09, 0xcf, 0xec, 0x35, 0xe7, 0xf4, 0x93, 0x46, 0xf6, 0x49, 0x43, + 0x5d, 0x6e, 0xfe, 0xad, 0x31, 0x80, 0x18, 0x99, 0xec, 0xc2, 0xcc, 0x46, 0xa5, 0x54, 0x54, 0xcc, + 0x4f, 0x3d, 0xcf, 0x5b, 0xf4, 0x38, 0x0a, 0x47, 0x38, 0x8c, 0x75, 0x8c, 0xe7, 0x36, 0xea, 0xe9, + 0xa6, 0x69, 0x82, 0x23, 0xab, 0xa3, 0x56, 0xb8, 0xb7, 0xa6, 0xd4, 0x91, 0x19, 0xb2, 0x8e, 0xc0, + 0x69, 0x35, 0xfb, 0xd4, 0xa1, 0x73, 0x24, 0x07, 0x90, 0xbf, 0x83, 0xab, 0x98, 0x52, 0x4b, 0x76, + 0x70, 0x2d, 0x2f, 0x89, 0x5a, 0x9e, 0xe3, 0xcb, 0x5f, 0x7a, 0x3d, 0x3d, 0x5c, 0x63, 0xc9, 0x1d, + 0x39, 0x56, 0x72, 0xff, 0xba, 0x01, 0x63, 0x7c, 0x99, 0x14, 0xa3, 0xd5, 0x67, 0x21, 0xde, 0x79, + 0x3a, 0x0b, 0x71, 0x1e, 0x35, 0x97, 0xe6, 0x30, 0xe3, 0x65, 0xa4, 0x04, 0x63, 0xb5, 0xd0, 0x09, + 0xbb, 0x32, 0xe8, 0x5e, 0x1e, 0x27, 0xe3, 0x46, 0x92, 0x97, 0xc4, 0x91, 0xe5, 0x01, 0xfe, 0x56, + 0xb9, 0x70, 0x0c, 0x52, 0x89, 0x83, 0x9a, 0xc7, 0x8f, 0x0d, 0x6a, 0x96, 0x81, 0xe0, 0xe3, 0x22, + 0xa8, 0x59, 0x0f, 0x65, 0x5e, 0x83, 0x09, 0x11, 0x2a, 0xbd, 0x72, 0x28, 0xdc, 0x45, 0xd2, 0xed, + 0x1b, 0xc1, 0x95, 0xd7, 0x30, 0x39, 0xc8, 0xde, 0xd5, 0x5e, 0x7a, 0x89, 0x10, 0xc9, 0x06, 0x4c, + 0xc4, 0x97, 0xbe, 0xf5, 0xdc, 0x25, 0x11, 0x5c, 0xdc, 0x25, 0x92, 0xf1, 0x96, 0x29, 0x77, 0xbc, + 0x63, 0x1e, 0xe6, 0x2f, 0x1a, 0x90, 0x4f, 0xca, 0x0b, 0xbe, 0xc1, 0x2f, 0xaf, 0xc9, 0x47, 0x21, + 0x8e, 0xfc, 0x0d, 0xfe, 0xe8, 0x5e, 0xbd, 0xfe, 0xf6, 0xbb, 0x82, 0x4e, 0x96, 0x21, 0xc7, 0xa6, + 0x5d, 0x3b, 0xf1, 0x08, 0x7f, 0x57, 0xc0, 0xd4, 0x18, 0x17, 0x89, 0xa7, 0xcc, 0xda, 0x7f, 0x96, + 0x85, 0x49, 0x65, 0xb0, 0xc8, 0x15, 0xc8, 0x55, 0x82, 0x35, 0xaf, 0x7e, 0x9f, 0x36, 0xc4, 0xd1, + 0xf9, 0xf4, 0xe3, 0xa3, 0xa5, 0x09, 0x37, 0xb0, 0x9b, 0x08, 0xb4, 0xa2, 0x62, 0xb2, 0x02, 0xd3, + 0xfc, 0x2f, 0x99, 0x3f, 0x27, 0x13, 0x1f, 0xfb, 0x71, 0x64, 0x99, 0x39, 0x47, 0x35, 0x13, 0x34, + 0x12, 0xf2, 0x75, 0x00, 0x0e, 0x18, 0xf2, 0xc9, 0x60, 0x39, 0x81, 0x4f, 0x8b, 0x0a, 0x52, 0x5e, + 0x0e, 0x56, 0x18, 0x92, 0x6f, 0xf0, 0x1b, 0xf0, 0x52, 0xb8, 0x8e, 0x0f, 0xc3, 0x37, 0x65, 0xcc, + 0x1e, 0xe3, 0x6f, 0xa7, 0x87, 0xcd, 0xab, 0x2c, 0xc9, 0xf7, 0x0d, 0x38, 0x6f, 0xd1, 0xba, 0xf7, + 0x80, 0xfa, 0x87, 0x85, 0x10, 0xb1, 0xd4, 0x1a, 0x8f, 0x8f, 0xd1, 0xbf, 0x29, 0x6a, 0x7c, 0xd5, + 0x17, 0x5c, 0xf0, 0x5a, 0x70, 0xab, 0x13, 0xda, 0x03, 0x9a, 0x30, 0xa0, 0x4a, 0xf3, 0xbf, 0x37, + 0x94, 0x29, 0x40, 0xd6, 0x31, 0x27, 0x2d, 0x17, 0x16, 0x71, 0x0c, 0x12, 0x59, 0x78, 0x12, 0x6e, + 0xd1, 0xbd, 0x95, 0xe7, 0xc4, 0x29, 0xf7, 0x7c, 0x24, 0x72, 0x89, 0x5c, 0xb5, 0x1c, 0x48, 0x3e, + 0x10, 0xaf, 0x3b, 0x1f, 0xff, 0x6c, 0x86, 0x5c, 0x6a, 0x46, 0xd8, 0x18, 0x29, 0x6f, 0x3a, 0x7f, + 0x4e, 0x84, 0x81, 0x66, 0xb5, 0xa7, 0xdb, 0x18, 0x88, 0xb5, 0x23, 0x5a, 0x63, 0xe2, 0x9b, 0x1a, + 0x8a, 0xb4, 0xfe, 0x6a, 0x06, 0xf2, 0xc9, 0x89, 0x47, 0xde, 0x87, 0x29, 0x79, 0x81, 0x7f, 0xd5, + 0x11, 0x69, 0x79, 0xa6, 0x44, 0x5a, 0x1c, 0x01, 0xb7, 0x0f, 0x1c, 0xed, 0x31, 0x14, 0x8d, 0x80, + 0x2d, 0xc8, 0x9b, 0xe2, 0x02, 0xa0, 0x32, 0x81, 0x42, 0x2f, 0xec, 0x24, 0x2e, 0x91, 0x4b, 0x34, + 0xf2, 0x26, 0x64, 0xef, 0xdd, 0x2e, 0x88, 0xf0, 0x28, 0xa9, 0x5f, 0xee, 0xdd, 0x2e, 0xf0, 0x23, + 0x2d, 0x7e, 0x52, 0xa5, 0x9f, 0x9b, 0x31, 0x7c, 0xb2, 0xa6, 0xe4, 0x44, 0x18, 0xd3, 0x32, 0x7c, + 0x4a, 0x70, 0xf4, 0x71, 0xc7, 0x27, 0x47, 0x50, 0x5f, 0x8a, 0x31, 0xff, 0xfd, 0x2c, 0x4c, 0x44, + 0xf5, 0x13, 0x02, 0x68, 0x6f, 0x88, 0x08, 0x29, 0xfc, 0x9b, 0x9c, 0x83, 0x9c, 0x34, 0x31, 0xe4, + 0x93, 0xd2, 0x81, 0x30, 0x2f, 0x16, 0x41, 0xda, 0x12, 0xdc, 0xbc, 0xb0, 0xe4, 0x4f, 0x72, 0x03, + 0x22, 0x43, 0xa1, 0x9f, 0x45, 0x31, 0xc2, 0x06, 0xcc, 0x8a, 0xd0, 0xc8, 0x0c, 0x64, 0x5c, 0x7e, + 0xb9, 0x6b, 0xc2, 0xca, 0xb8, 0x0d, 0xf2, 0x3e, 0xe4, 0x9c, 0x46, 0x83, 0x36, 0x6c, 0x47, 0x9e, + 0x27, 0x0c, 0x12, 0x9a, 0x1c, 0xe3, 0xc6, 0x35, 0x3a, 0x52, 0x15, 0x42, 0x52, 0x80, 0x09, 0x7c, + 0x39, 0xbd, 0x1b, 0xd0, 0xc6, 0x10, 0xcb, 0x43, 0xcc, 0x21, 0xc7, 0xc8, 0xb6, 0x02, 0xda, 0x20, + 0xaf, 0xc2, 0x08, 0x1b, 0x4d, 0xb1, 0x1e, 0x44, 0xef, 0xde, 0x6c, 0x6c, 0x56, 0x79, 0x87, 0xad, + 0x9e, 0xb2, 0x10, 0x81, 0xbc, 0x0c, 0xd9, 0xee, 0xf2, 0x9e, 0xd0, 0xf4, 0xf9, 0x38, 0x3f, 0x49, + 0x84, 0xc6, 0x8a, 0xc9, 0x4d, 0xc8, 0x3d, 0xd4, 0x53, 0x5b, 0x9c, 0x4e, 0x0c, 0x63, 0x84, 0x1f, + 0x21, 0xae, 0xe4, 0x60, 0x8c, 0x9f, 0x86, 0x9a, 0x2f, 0x00, 0xc4, 0x55, 0xf7, 0x06, 0xb3, 0x99, + 0x5f, 0x87, 0x89, 0xa8, 0x4a, 0xf2, 0x3c, 0xc0, 0x7d, 0x7a, 0x68, 0x1f, 0x38, 0xed, 0x46, 0x93, + 0x1b, 0x9c, 0x53, 0xd6, 0xc4, 0x7d, 0x7a, 0xb8, 0x8a, 0x00, 0x72, 0x16, 0xc6, 0x3b, 0x6c, 0x54, + 0xe5, 0x3b, 0x64, 0xd6, 0x58, 0xa7, 0xbb, 0xcb, 0x24, 0x74, 0x11, 0xc6, 0xd1, 0x89, 0x22, 0x26, + 0xda, 0xb4, 0x25, 0x7f, 0x9a, 0xbf, 0x91, 0xc1, 0x64, 0x65, 0x4a, 0x3b, 0xc9, 0x4b, 0x30, 0x5d, + 0xf7, 0x29, 0x2e, 0x47, 0xf8, 0x88, 0x9d, 0xa8, 0x67, 0x2a, 0x06, 0x56, 0x1a, 0xe4, 0x12, 0xcc, + 0xc6, 0x0f, 0xa3, 0xd9, 0xf5, 0x5d, 0x91, 0x87, 0x66, 0xca, 0x9a, 0xee, 0xc8, 0x97, 0xd1, 0x8a, + 0xbb, 0x78, 0x29, 0x31, 0xaf, 0x5e, 0xf9, 0x0f, 0xa3, 0x1c, 0xea, 0xd6, 0xac, 0x02, 0xc7, 0x63, + 0xc2, 0x33, 0x30, 0xe6, 0x38, 0xfb, 0x5d, 0x97, 0x5f, 0x90, 0x9a, 0xb2, 0xc4, 0x2f, 0xf2, 0x1a, + 0xcc, 0x05, 0xee, 0x7e, 0xdb, 0x09, 0xbb, 0xbe, 0xc8, 0x16, 0x47, 0x7d, 0x14, 0xa9, 0x69, 0x2b, + 0x1f, 0x15, 0x14, 0x39, 0x9c, 0xbc, 0x01, 0x44, 0xad, 0xcf, 0xdb, 0xfd, 0x26, 0xad, 0x73, 0x51, + 0x9b, 0xb2, 0xe6, 0x94, 0x92, 0x0d, 0x2c, 0x20, 0x2f, 0xc2, 0x94, 0x4f, 0x03, 0x34, 0xc9, 0xb0, + 0xdb, 0x30, 0x97, 0xa7, 0x35, 0x29, 0x61, 0x77, 0xe9, 0xa1, 0xb9, 0x02, 0x73, 0x3d, 0xf3, 0x91, + 0xbc, 0xc1, 0xad, 0x7b, 0xb1, 0x3e, 0x4f, 0xf1, 0xcd, 0x0c, 0x53, 0x52, 0xfa, 0xd2, 0x2c, 0x90, + 0xcc, 0x36, 0x4c, 0xa9, 0xfa, 0xf5, 0x98, 0x0c, 0x3f, 0x67, 0xa2, 0x57, 0xf5, 0x27, 0x56, 0xc6, + 0x1e, 0x1f, 0x2d, 0x65, 0xdc, 0x06, 0xde, 0x68, 0xb8, 0x0c, 0x39, 0x69, 0x25, 0xa8, 0xef, 0x82, + 0x0b, 0x83, 0xf2, 0xd0, 0x8a, 0x4a, 0xcd, 0x57, 0x61, 0x5c, 0xa8, 0xd0, 0xc1, 0x0e, 0x2d, 0xf3, + 0xbb, 0x19, 0x98, 0xb5, 0x28, 0x9b, 0xe0, 0xe2, 0xc5, 0xed, 0xcf, 0xd8, 0x13, 0x71, 0xda, 0xb7, + 0x0d, 0x48, 0xa8, 0xf5, 0x3b, 0x06, 0xcc, 0xa7, 0xe0, 0x7e, 0xa2, 0x34, 0xc9, 0xb7, 0x60, 0xa2, + 0xe4, 0x3a, 0xcd, 0x42, 0xa3, 0x11, 0xdd, 0xd1, 0x40, 0x6b, 0xb0, 0xc1, 0xa6, 0x93, 0xc3, 0xa0, + 0xea, 0x62, 0x1a, 0xa1, 0x92, 0xab, 0x42, 0x28, 0xe2, 0x27, 0x06, 0xe4, 0x23, 0x74, 0xc0, 0xdb, + 0x14, 0x3f, 0x41, 0x87, 0xf7, 0xfa, 0x39, 0x30, 0x8e, 0xaa, 0x79, 0x66, 0x87, 0x2e, 0xfd, 0x5e, + 0x7f, 0xf2, 0xf3, 0x86, 0xda, 0x76, 0xfe, 0x62, 0x06, 0xce, 0xa4, 0x13, 0x7e, 0xd2, 0x8c, 0xd7, + 0x98, 0xcd, 0x4c, 0x79, 0xe7, 0x0f, 0x33, 0x5e, 0xf3, 0xd4, 0x67, 0x88, 0x1f, 0x23, 0x90, 0x3d, + 0x98, 0x5e, 0x73, 0x82, 0x70, 0x95, 0x3a, 0x7e, 0xb8, 0x4b, 0x9d, 0x70, 0x08, 0x0b, 0xf6, 0x65, + 0xf9, 0x5e, 0x33, 0x2e, 0x6a, 0x07, 0x92, 0x32, 0x61, 0xe0, 0xe9, 0x6c, 0x23, 0x41, 0x19, 0x19, + 0x42, 0x50, 0xbe, 0x05, 0xb3, 0x35, 0xda, 0x72, 0x3a, 0x07, 0x9e, 0x4f, 0x85, 0x0f, 0xfe, 0x1a, + 0x4c, 0x47, 0xa0, 0x54, 0x69, 0xd1, 0x8b, 0x35, 0x7c, 0xa5, 0x23, 0x62, 0x55, 0xa2, 0x17, 0x9b, + 0x7f, 0x2b, 0x03, 0x67, 0x0b, 0x75, 0x71, 0xac, 0x26, 0x0a, 0xe4, 0xe9, 0xff, 0xa7, 0x5c, 0x37, + 0xb9, 0x0e, 0x13, 0xf7, 0x9c, 0x47, 0x6b, 0xd4, 0x09, 0x68, 0x20, 0xf2, 0x8d, 0x72, 0xf3, 0xcb, + 0x79, 0x14, 0x9f, 0x36, 0x59, 0x31, 0x8e, 0xba, 0xd9, 0x1c, 0x79, 0xc2, 0xcd, 0xa6, 0x09, 0x63, + 0xab, 0x5e, 0xb3, 0x21, 0x16, 0x27, 0x71, 0xfe, 0x71, 0x80, 0x10, 0x4b, 0x94, 0x98, 0x7f, 0x66, + 0xc0, 0x4c, 0xd4, 0x62, 0x6c, 0xc2, 0xa7, 0xde, 0x25, 0x97, 0x60, 0x1c, 0x2b, 0x8a, 0x1e, 0xab, + 0xc7, 0x45, 0xa3, 0xc9, 0x40, 0xb6, 0xdb, 0xb0, 0x64, 0xa1, 0xda, 0x13, 0xa3, 0x4f, 0xd6, 0x13, + 0xe6, 0xdf, 0xc7, 0xa3, 0x15, 0xf5, 0x2b, 0xd9, 0x4a, 0xa4, 0x34, 0xc4, 0x18, 0xb2, 0x21, 0x99, + 0xa7, 0x36, 0x24, 0xd9, 0xbe, 0x43, 0xf2, 0xbd, 0x0c, 0x4c, 0x46, 0x8d, 0xfd, 0x8c, 0x25, 0xc4, + 0x89, 0xbe, 0x6b, 0xa8, 0x9b, 0x4a, 0x35, 0x45, 0x57, 0x88, 0x0b, 0x41, 0x1f, 0xc0, 0x98, 0x98, + 0x4c, 0x46, 0xe2, 0x14, 0x3c, 0x31, 0xba, 0x2b, 0x33, 0x82, 0xf5, 0x18, 0x0e, 0x68, 0x60, 0x09, + 0x3a, 0xbc, 0x0a, 0xb6, 0x43, 0x77, 0xc5, 0x49, 0xdb, 0x33, 0xbb, 0x46, 0xa5, 0x5f, 0x05, 0x8b, + 0x3f, 0x6c, 0xa8, 0xd5, 0xe9, 0x68, 0x14, 0xf2, 0x49, 0x92, 0xe3, 0x53, 0x0e, 0x55, 0xbb, 0xbb, + 0xe2, 0x59, 0x61, 0x4c, 0x39, 0xd4, 0xe9, 0xee, 0x5a, 0x0c, 0x46, 0x2e, 0xc1, 0x48, 0xd5, 0x77, + 0x1f, 0xe0, 0x57, 0x8b, 0x57, 0x95, 0x3b, 0xbe, 0xfb, 0x40, 0x3d, 0xce, 0x65, 0xe5, 0xb8, 0xa1, + 0x5d, 0xab, 0x61, 0x78, 0x3d, 0x1a, 0xd6, 0x62, 0x43, 0xdb, 0x0c, 0x92, 0x09, 0x0f, 0x25, 0x1a, + 0x5b, 0x2a, 0x57, 0xa8, 0xe3, 0x8b, 0xf4, 0x38, 0x42, 0x9d, 0xe1, 0x52, 0xb9, 0x8b, 0x60, 0xfe, + 0xea, 0x86, 0xa5, 0x22, 0x91, 0x26, 0x10, 0xe5, 0xa7, 0x9c, 0xc0, 0xc7, 0xef, 0xf1, 0x2e, 0x4a, + 0xef, 0x9b, 0xca, 0xda, 0x56, 0x67, 0x73, 0x0a, 0xdf, 0xa7, 0xe9, 0x23, 0xac, 0x8a, 0xcb, 0xd2, + 0xe8, 0xc8, 0xc8, 0x1d, 0xcb, 0x4c, 0xde, 0x3f, 0x01, 0x7e, 0x99, 0x3a, 0x72, 0x67, 0xc4, 0x4c, + 0xc8, 0x7b, 0x30, 0xa9, 0x5e, 0x9a, 0xe0, 0xa1, 0xfd, 0x17, 0xf8, 0x25, 0xe5, 0x3e, 0x89, 0x9f, + 0x55, 0x02, 0xb2, 0x0b, 0x67, 0x8b, 0x5e, 0x3b, 0xe8, 0xb6, 0x68, 0x43, 0x3b, 0x09, 0xae, 0x94, + 0x70, 0x83, 0x39, 0xc1, 0x23, 0xb0, 0xeb, 0x02, 0x45, 0xc4, 0xe8, 0xcb, 0x10, 0x23, 0x7d, 0x03, + 0xd2, 0x8f, 0x11, 0xd9, 0x84, 0xc9, 0x5a, 0xe1, 0xde, 0x9a, 0x0c, 0x7c, 0x9f, 0xd4, 0xd5, 0x46, + 0x5c, 0x52, 0x62, 0x13, 0x83, 0xdf, 0xfd, 0x74, 0x5a, 0x4d, 0x19, 0xe1, 0xa2, 0x7a, 0x1f, 0x15, + 0x64, 0xf3, 0x73, 0xaa, 0x7c, 0x0b, 0x73, 0x63, 0xa0, 0x7c, 0x9b, 0xff, 0xed, 0x18, 0xcc, 0x26, + 0xaa, 0x13, 0xfb, 0x1f, 0xa3, 0x67, 0xff, 0x53, 0x03, 0xe0, 0xae, 0xac, 0x21, 0x7d, 0x4e, 0x32, + 0x38, 0x72, 0x52, 0xc4, 0x6b, 0x47, 0x63, 0xa5, 0xb0, 0x61, 0x4c, 0xb9, 0x24, 0x0c, 0xe9, 0x73, + 0x8c, 0x98, 0x72, 0x61, 0x52, 0x98, 0xc6, 0x6c, 0xc8, 0x12, 0x8c, 0xe2, 0x6d, 0x76, 0x35, 0x36, + 0xd5, 0x65, 0x00, 0x8b, 0xc3, 0xc9, 0x4b, 0x30, 0xc6, 0x16, 0xe7, 0x4a, 0x49, 0x4c, 0x2e, 0xd4, + 0x59, 0x6c, 0xf5, 0x66, 0x2b, 0xa1, 0x28, 0x22, 0xb7, 0x60, 0x8a, 0xff, 0x25, 0x2e, 0xef, 0x8c, + 0xe9, 0x71, 0x1b, 0xb6, 0xdb, 0x90, 0xf7, 0x77, 0x34, 0x3c, 0x66, 0xb5, 0xd6, 0xba, 0xbb, 0xe2, + 0xc5, 0xaa, 0xf1, 0xd8, 0x6a, 0x0d, 0x38, 0x10, 0xdf, 0x69, 0x89, 0x10, 0xd8, 0x1a, 0x29, 0xe2, + 0x91, 0x72, 0xb8, 0x57, 0xc1, 0x35, 0x92, 0x87, 0x22, 0x59, 0xa2, 0x84, 0x5c, 0xe1, 0x5e, 0x65, + 0x34, 0x37, 0x78, 0xe2, 0x50, 0xf4, 0x03, 0xe3, 0x86, 0x17, 0x6d, 0x8e, 0xa8, 0x98, 0x55, 0xce, + 0xfe, 0x2e, 0xb7, 0x1c, 0xb7, 0x29, 0xc4, 0x15, 0x2b, 0x47, 0x5c, 0xca, 0xa0, 0x56, 0x8c, 0x40, + 0xde, 0x81, 0x19, 0xf6, 0xa3, 0xe8, 0xb5, 0x5a, 0x5e, 0x1b, 0xd9, 0x4f, 0xc6, 0xd7, 0x2c, 0x91, + 0xa4, 0x8e, 0x45, 0xbc, 0x96, 0x04, 0x2e, 0xd3, 0x53, 0x78, 0xc6, 0xd4, 0xe5, 0xfe, 0xee, 0xa9, + 0x58, 0x4f, 0x21, 0x69, 0xc0, 0xe1, 0x96, 0x8a, 0x44, 0xde, 0x82, 0x69, 0xf6, 0xf3, 0x8e, 0xfb, + 0x80, 0xf2, 0x0a, 0xa7, 0xe3, 0xe3, 0x3e, 0xa4, 0xda, 0x67, 0x25, 0xbc, 0x3e, 0x1d, 0x93, 0x7c, + 0x04, 0xa7, 0x91, 0x53, 0xdd, 0xeb, 0xd0, 0x46, 0x61, 0x6f, 0xcf, 0x6d, 0xba, 0xfc, 0x6d, 0x6d, + 0x7e, 0x4d, 0x05, 0x7d, 0x8c, 0xbc, 0x62, 0xc4, 0xb0, 0x9d, 0x18, 0xc5, 0x4a, 0xa7, 0x24, 0x3b, + 0x90, 0x2f, 0x76, 0x83, 0xd0, 0x6b, 0x15, 0xc2, 0xd0, 0x77, 0x77, 0xbb, 0x21, 0x0d, 0x16, 0x67, + 0xb5, 0xcb, 0x1c, 0x6c, 0x72, 0x44, 0x85, 0xdc, 0xcf, 0x50, 0x47, 0x0a, 0xdb, 0x89, 0x48, 0xac, + 0x1e, 0x26, 0xe6, 0x7f, 0x63, 0xc0, 0xb4, 0x46, 0x4a, 0xde, 0x84, 0xa9, 0xdb, 0xbe, 0x4b, 0xdb, + 0x8d, 0xe6, 0xa1, 0xb2, 0x01, 0x42, 0xeb, 0x78, 0x4f, 0xc0, 0xf9, 0x57, 0x6b, 0x68, 0x91, 0xff, + 0x20, 0x93, 0x1a, 0x10, 0x73, 0x9d, 0x87, 0x48, 0x0b, 0x01, 0xcd, 0xc6, 0xb7, 0xcb, 0x50, 0x40, + 0x85, 0x74, 0x2a, 0x28, 0xe4, 0x5d, 0x18, 0xe3, 0xa7, 0x51, 0x22, 0x3a, 0xe3, 0x5c, 0xda, 0x67, + 0xf2, 0x70, 0x7c, 0x14, 0x44, 0x3c, 0x04, 0x0f, 0x2c, 0x41, 0x64, 0xfe, 0x8a, 0x01, 0xa4, 0x17, + 0xf5, 0x18, 0x7f, 0xca, 0xb1, 0x87, 0xeb, 0x1f, 0x44, 0xb3, 0x31, 0xab, 0xf9, 0x04, 0x59, 0x4d, + 0xbc, 0x80, 0x77, 0xbc, 0x98, 0x75, 0xaa, 0x83, 0x87, 0x17, 0x9b, 0x7f, 0x2d, 0x03, 0x10, 0x63, + 0x93, 0x2f, 0xf2, 0xac, 0x8b, 0x1f, 0x75, 0x9d, 0xa6, 0xbb, 0xe7, 0xea, 0x59, 0x0e, 0x90, 0xc9, + 0xb7, 0x64, 0x89, 0xa5, 0x23, 0x92, 0xf7, 0x61, 0xb6, 0x56, 0xd5, 0x69, 0x95, 0x0c, 0x73, 0x41, + 0xc7, 0x4e, 0x90, 0x27, 0xb1, 0x31, 0x0a, 0x4b, 0x1d, 0x0d, 0x1e, 0x85, 0xc5, 0x07, 0x42, 0x94, + 0x30, 0xc5, 0x52, 0xab, 0xe2, 0xd3, 0x5a, 0x0d, 0xda, 0xa8, 0x94, 0x84, 0x96, 0xc2, 0xd6, 0x05, + 0x1d, 0xbb, 0xc3, 0x0b, 0xf0, 0x3d, 0x27, 0x0d, 0x2f, 0xee, 0xc8, 0xd1, 0x3e, 0x21, 0xf7, 0xbf, + 0x86, 0xee, 0xa4, 0x96, 0x17, 0x52, 0xb1, 0x8b, 0x7e, 0x66, 0xed, 0xe9, 0xf8, 0x28, 0x73, 0x54, + 0x8b, 0x24, 0xd6, 0xbe, 0x8e, 0x63, 0x6c, 0xdf, 0x8c, 0x8d, 0x5f, 0x7e, 0xa8, 0x29, 0x8f, 0x32, + 0x15, 0xd3, 0xef, 0xef, 0x19, 0x70, 0x3a, 0x95, 0x96, 0x5c, 0x03, 0x88, 0x7d, 0x15, 0xa2, 0x97, + 0xf8, 0xb3, 0x5a, 0x11, 0xd4, 0x52, 0x30, 0xc8, 0xd7, 0x92, 0x5e, 0x86, 0xe3, 0x17, 0xc2, 0xf3, + 0xf2, 0xce, 0xac, 0xee, 0x65, 0x48, 0xf1, 0x2d, 0x98, 0xbf, 0x93, 0x85, 0x39, 0xe5, 0xca, 0x16, + 0x6f, 0xeb, 0x31, 0x51, 0x71, 0xf7, 0xe5, 0x13, 0x6e, 0x22, 0x80, 0x32, 0xa3, 0xbd, 0x4a, 0xd8, + 0xc3, 0xed, 0x9a, 0x8a, 0xcc, 0x2f, 0x8a, 0xa3, 0xea, 0x14, 0x2f, 0xba, 0xf5, 0xc4, 0x50, 0x6a, + 0xcc, 0x49, 0x00, 0xd3, 0xa5, 0xc3, 0xb6, 0xd3, 0x8a, 0x6a, 0xe3, 0xc7, 0xef, 0xaf, 0xf5, 0xad, + 0x4d, 0xc3, 0xe6, 0xd5, 0x49, 0x9b, 0x73, 0xb1, 0xc1, 0xcb, 0x52, 0x02, 0xf6, 0x35, 0xaa, 0xf3, + 0xef, 0xc3, 0x5c, 0x4f, 0xa3, 0x4f, 0x74, 0x67, 0x7d, 0x07, 0x48, 0x6f, 0x3b, 0x52, 0x38, 0xbc, + 0xa6, 0x67, 0x44, 0x38, 0x1d, 0x1d, 0xce, 0xb5, 0x5a, 0x4e, 0x9b, 0xbf, 0x18, 0xbd, 0xbd, 0xac, + 0xde, 0x68, 0xff, 0xb5, 0x8c, 0x1a, 0x8f, 0xfb, 0xac, 0xcf, 0xba, 0x0f, 0xb4, 0x5d, 0xd6, 0x0b, + 0xfd, 0xc6, 0x74, 0xa8, 0xdd, 0xec, 0x8f, 0xb3, 0x70, 0xb6, 0x0f, 0x25, 0x39, 0x4c, 0x0a, 0x11, + 0xdf, 0xdd, 0xde, 0x18, 0x5c, 0xe1, 0xd3, 0x10, 0x25, 0xf2, 0x45, 0x7e, 0x23, 0xa7, 0x8e, 0xaf, + 0x35, 0x88, 0x7d, 0x5d, 0xf4, 0x0e, 0x35, 0x87, 0x26, 0xaf, 0xe2, 0x70, 0x28, 0x79, 0x5f, 0xbe, + 0x43, 0xad, 0x5f, 0x19, 0x67, 0x18, 0xfc, 0xdd, 0xe9, 0xf8, 0x7e, 0x7d, 0xfa, 0xdb, 0xd3, 0x5f, + 0x80, 0x6c, 0x61, 0xa7, 0x26, 0xc6, 0x65, 0x46, 0x25, 0xdf, 0xa9, 0xc5, 0x49, 0xb6, 0x1c, 0x2d, + 0x1b, 0x16, 0xa3, 0x60, 0x84, 0x77, 0x8a, 0x55, 0x31, 0x2a, 0x2a, 0xe1, 0x9d, 0x62, 0x35, 0x26, + 0xdc, 0xaf, 0x6b, 0x97, 0xf1, 0xee, 0x14, 0xab, 0x9f, 0x9e, 0xd8, 0xff, 0x8d, 0x0c, 0xbf, 0x46, + 0xc4, 0x3f, 0xec, 0x7d, 0x98, 0xd2, 0x32, 0xd6, 0x18, 0xb1, 0x3d, 0x16, 0x65, 0xdb, 0x49, 0x44, + 0x3f, 0x68, 0x04, 0x32, 0x5d, 0x5d, 0xf4, 0x78, 0xa7, 0x1a, 0xbc, 0xa0, 0x3f, 0xf8, 0x99, 0x4c, + 0x57, 0x17, 0x91, 0x90, 0x9b, 0x90, 0xdb, 0xa4, 0x6d, 0xa7, 0x1d, 0x46, 0x8e, 0x36, 0x0c, 0xb6, + 0x0b, 0x11, 0xa6, 0x5b, 0x0d, 0x11, 0x22, 0x3e, 0xb5, 0xaa, 0x3c, 0x3d, 0x1a, 0xad, 0xc5, 0x3c, + 0x04, 0x54, 0x29, 0x49, 0xbc, 0x55, 0xab, 0x13, 0x99, 0xbf, 0x66, 0xc0, 0xb8, 0x18, 0x48, 0xe5, + 0x81, 0x40, 0x63, 0x88, 0x07, 0x02, 0x6f, 0xc1, 0x84, 0x88, 0x88, 0xd7, 0x5f, 0xa9, 0x15, 0x41, + 0xf4, 0x89, 0x57, 0x6a, 0x23, 0xd4, 0xa1, 0x63, 0xc9, 0xff, 0xae, 0x68, 0xd9, 0x9d, 0x62, 0x95, + 0x2c, 0x43, 0x6e, 0xcd, 0xab, 0x3b, 0xca, 0x3a, 0x87, 0x6a, 0xa7, 0x29, 0x60, 0x6a, 0x07, 0x49, + 0x3c, 0xfd, 0xf5, 0xdd, 0xcc, 0xf0, 0xaf, 0xef, 0x0e, 0xdb, 0x3e, 0x9a, 0xa2, 0x24, 0xb6, 0x6f, + 0xae, 0xb9, 0x41, 0x48, 0x3e, 0x54, 0x63, 0xf4, 0x45, 0x91, 0xd4, 0x14, 0xe7, 0xfb, 0x69, 0x8a, + 0xed, 0x9b, 0x56, 0x0a, 0x15, 0x9e, 0xd7, 0xc4, 0x60, 0xfe, 0xe4, 0xf9, 0x67, 0x2c, 0x0f, 0x73, + 0xf2, 0xf3, 0x86, 0x52, 0xd2, 0xff, 0x2c, 0x03, 0x67, 0xd2, 0x09, 0xd5, 0x6f, 0x31, 0x06, 0x7c, + 0xcb, 0x65, 0xc8, 0xad, 0x7a, 0x41, 0xa8, 0x04, 0x3c, 0xa1, 0x5b, 0xf9, 0x40, 0xc0, 0xac, 0xa8, + 0x94, 0xed, 0xb9, 0xd9, 0xdf, 0xd1, 0xf4, 0x44, 0x7e, 0x78, 0x79, 0x86, 0xed, 0xb9, 0x79, 0x11, + 0xb9, 0x03, 0x39, 0x4b, 0x84, 0x93, 0x27, 0xba, 0x46, 0x82, 0x23, 0x6b, 0x8a, 0xf8, 0x02, 0xa2, + 0x25, 0x0e, 0x12, 0x30, 0x52, 0x80, 0x71, 0x31, 0xfa, 0x89, 0x23, 0xc9, 0x14, 0x91, 0xd1, 0x73, + 0x79, 0x49, 0x3a, 0xa6, 0x51, 0xf0, 0x70, 0xa9, 0x52, 0x92, 0x91, 0xe1, 0xa8, 0x51, 0xf8, 0xe1, + 0x93, 0x9e, 0x3d, 0x2c, 0x42, 0x34, 0xbf, 0x9b, 0x01, 0xd8, 0xa1, 0xbb, 0xcf, 0x76, 0x7a, 0xf7, + 0x2f, 0x68, 0x12, 0xa6, 0xc4, 0x53, 0x0c, 0x9f, 0xdd, 0x7d, 0x03, 0xe3, 0x1a, 0x86, 0xcf, 0xed, + 0xbe, 0x04, 0xa3, 0xdc, 0xdb, 0xa9, 0x6c, 0x12, 0xb9, 0x9b, 0x93, 0xc3, 0xcd, 0x5d, 0x58, 0xb8, + 0x43, 0xc3, 0xd8, 0xbd, 0x25, 0x8f, 0xb4, 0x06, 0xb3, 0x7d, 0x1d, 0x26, 0x04, 0xbe, 0xfe, 0x66, + 0xae, 0xbc, 0x8f, 0x86, 0xbe, 0x18, 0x89, 0xc0, 0xb4, 0x51, 0x89, 0x36, 0x69, 0x48, 0x3f, 0xdd, + 0x6a, 0x6a, 0x40, 0xf8, 0xa7, 0xe0, 0x97, 0x0d, 0x57, 0xc3, 0xb1, 0xfd, 0xb3, 0x0d, 0xa7, 0xa3, + 0xb6, 0x3f, 0x4d, 0xbe, 0xd7, 0xd9, 0x96, 0x52, 0xa4, 0xc1, 0x8a, 0x39, 0x0e, 0x88, 0x69, 0x78, + 0x04, 0xe7, 0x25, 0xc1, 0x8e, 0x1b, 0x05, 0x86, 0x0d, 0x45, 0x4b, 0xde, 0x81, 0x49, 0x85, 0x46, + 0x24, 0x1a, 0x44, 0xf7, 0xe7, 0x43, 0x37, 0x3c, 0xb0, 0x03, 0x0e, 0x57, 0xdd, 0x9f, 0x0a, 0xba, + 0xf9, 0x55, 0x78, 0x2e, 0x0a, 0xa3, 0x4f, 0xa9, 0x3a, 0xc1, 0xdc, 0x38, 0x19, 0xf3, 0xf5, 0xf8, + 0xb3, 0x2a, 0xed, 0xe8, 0x52, 0x97, 0xe4, 0x4d, 0xd4, 0xcf, 0x12, 0x1f, 0x73, 0xa1, 0xe7, 0x9a, + 0x98, 0x72, 0x1b, 0xcc, 0x7c, 0x5b, 0x69, 0x6c, 0x0a, 0x43, 0x8d, 0xd8, 0x48, 0x12, 0x7f, 0x37, + 0x03, 0xb3, 0x1b, 0x95, 0x52, 0x31, 0x8a, 0x6a, 0xf9, 0x8c, 0xe5, 0x9e, 0xd7, 0xbe, 0xad, 0xbf, + 0xbe, 0x31, 0xb7, 0x60, 0x3e, 0xd1, 0x0d, 0x68, 0x3a, 0xbc, 0xc7, 0xc3, 0xdd, 0x23, 0xb0, 0x34, + 0x1b, 0xce, 0xa4, 0xb1, 0xdf, 0xbe, 0x69, 0x25, 0xb0, 0xcd, 0x7f, 0x98, 0x4b, 0xf0, 0x15, 0x2a, + 0xec, 0x75, 0x98, 0xa8, 0x04, 0x41, 0x97, 0xfa, 0x5b, 0xd6, 0x9a, 0xea, 0x2a, 0x70, 0x11, 0x68, + 0x77, 0xfd, 0xa6, 0x15, 0x23, 0x90, 0x2b, 0x90, 0x13, 0xa9, 0x97, 0xa4, 0x4e, 0x40, 0xaf, 0x6d, + 0x94, 0xb9, 0xc9, 0x8a, 0x8a, 0xc9, 0x9b, 0x30, 0xc5, 0xff, 0xe6, 0xd2, 0x26, 0x3a, 0x1c, 0x9d, + 0x83, 0x02, 0x9d, 0x4b, 0xa7, 0xa5, 0xa1, 0x91, 0xab, 0x90, 0x2d, 0x14, 0x2d, 0xf5, 0xa9, 0x4c, + 0xa7, 0xee, 0xf3, 0x27, 0x73, 0xf5, 0x4d, 0x44, 0xd1, 0x62, 0xd6, 0x9f, 0x70, 0x25, 0xf9, 0xc2, + 0x93, 0x8d, 0x12, 0x20, 0xbd, 0x4d, 0x89, 0xc5, 0x0c, 0x61, 0xe4, 0x3a, 0x8c, 0x97, 0xdc, 0xa0, + 0xd3, 0x74, 0x0e, 0x85, 0x1f, 0x9b, 0x67, 0xa4, 0xe5, 0x20, 0x55, 0x66, 0x04, 0x16, 0xb9, 0x02, + 0xa3, 0xe8, 0x64, 0x15, 0xbe, 0x6c, 0x9e, 0xb7, 0x95, 0x01, 0xb4, 0xbc, 0xad, 0x0c, 0x80, 0x99, + 0xfd, 0x78, 0x82, 0xa2, 0x09, 0x25, 0xb3, 0x5f, 0x32, 0x31, 0x91, 0xc0, 0xe9, 0xbd, 0x1f, 0x05, + 0x4f, 0xf3, 0x7e, 0xd4, 0x2e, 0x9c, 0xbd, 0x83, 0xde, 0x1b, 0xfd, 0x92, 0xea, 0x96, 0x55, 0x11, + 0xfe, 0x70, 0x3c, 0xf1, 0xe1, 0x0e, 0x9e, 0xe4, 0x3d, 0xd7, 0xc4, 0x73, 0xd8, 0xfd, 0x18, 0x91, + 0x2f, 0xc3, 0x42, 0x5a, 0x91, 0xf0, 0x9a, 0xe3, 0x05, 0xf1, 0xf4, 0x0a, 0xd4, 0x1b, 0xda, 0x69, + 0x1c, 0xc8, 0x1a, 0xe4, 0x39, 0xbc, 0xd0, 0x68, 0xb9, 0x6d, 0xee, 0xf9, 0x9f, 0x8e, 0x9f, 0x29, + 0x13, 0x5c, 0x1d, 0x56, 0xc8, 0x4f, 0x00, 0xb4, 0x8b, 0x0f, 0x09, 0x4a, 0xf2, 0x4b, 0x06, 0xdb, + 0xcd, 0xf1, 0x74, 0x3e, 0x5b, 0xd6, 0x5a, 0x20, 0xae, 0xf2, 0xf7, 0x7b, 0x62, 0x7c, 0xf3, 0x29, + 0x3d, 0x31, 0x3e, 0xe5, 0x8b, 0x3a, 0x71, 0x16, 0x69, 0x2d, 0xc0, 0x77, 0x8d, 0x9a, 0x4d, 0xef, + 0xe1, 0x56, 0xfb, 0x01, 0xf5, 0xdd, 0x3d, 0x97, 0x36, 0xf8, 0x47, 0xce, 0xa2, 0x06, 0xe7, 0xef, + 0x1a, 0xe1, 0xdb, 0x57, 0xdd, 0x08, 0xa1, 0xe7, 0x43, 0x53, 0x39, 0xb0, 0x8d, 0xa7, 0x0c, 0xc3, + 0xe7, 0xb7, 0xd3, 0xf2, 0xf1, 0xc6, 0x53, 0xc6, 0xec, 0xdb, 0x28, 0x46, 0xaa, 0xf0, 0x68, 0x24, + 0x22, 0xe6, 0xf7, 0xbf, 0xca, 0x71, 0x8d, 0x5c, 0xe8, 0x86, 0x07, 0x52, 0x87, 0x2f, 0xa7, 0x5d, + 0x25, 0xe0, 0x21, 0x4f, 0xca, 0x55, 0x02, 0xfd, 0x02, 0x81, 0x74, 0xa5, 0x67, 0x52, 0x5d, 0xe9, + 0xaf, 0xc3, 0x04, 0xbe, 0xec, 0x18, 0xc5, 0x6c, 0xe7, 0x84, 0xaf, 0x92, 0x01, 0x79, 0x2e, 0xa4, + 0x18, 0x81, 0x5c, 0x07, 0xc0, 0x1c, 0xc9, 0x7c, 0x81, 0x57, 0x72, 0xcf, 0x61, 0x2a, 0x65, 0x71, + 0x8a, 0xac, 0xa0, 0x20, 0xfb, 0x9a, 0x75, 0x5b, 0x3d, 0x76, 0xe6, 0xec, 0x03, 0x7f, 0x4f, 0xa0, + 0xc7, 0x08, 0xec, 0xf3, 0x94, 0x61, 0x12, 0x4a, 0x25, 0xdf, 0x33, 0x96, 0x2a, 0x12, 0x46, 0x74, + 0xc9, 0x00, 0x55, 0xd4, 0x29, 0x53, 0x22, 0xa2, 0x2b, 0x0a, 0x66, 0xb5, 0x62, 0x04, 0xf2, 0x05, + 0x18, 0x2f, 0x52, 0x3f, 0xdc, 0xdc, 0x5c, 0x13, 0xaf, 0xb3, 0xb3, 0x7d, 0x79, 0x0e, 0x93, 0x69, + 0x85, 0x61, 0xf3, 0x27, 0x47, 0x4b, 0xd3, 0xa1, 0xdb, 0xa2, 0xd7, 0xa2, 0x63, 0x5c, 0x89, 0x4d, + 0x56, 0x20, 0xcf, 0xcf, 0x18, 0x63, 0x43, 0x0e, 0xd5, 0x4c, 0x8e, 0x2b, 0x3d, 0x71, 0x20, 0xf9, + 0x90, 0xee, 0x46, 0xa9, 0xc4, 0x7a, 0xf0, 0x49, 0x59, 0x66, 0xe0, 0x53, 0x3f, 0x12, 0x62, 0xcf, + 0x82, 0x50, 0xcc, 0xda, 0xb7, 0xf6, 0x52, 0x90, 0x02, 0x4c, 0x17, 0xbd, 0x56, 0xc7, 0x09, 0x5d, + 0x4c, 0xc1, 0x7c, 0x28, 0x34, 0x0a, 0x7a, 0x47, 0xea, 0x6a, 0x81, 0xfe, 0x50, 0xa3, 0x52, 0x40, + 0x6e, 0xc3, 0x8c, 0xe5, 0x75, 0xd9, 0x20, 0xc9, 0x2d, 0x0d, 0x57, 0x1a, 0xd1, 0x13, 0xc0, 0x6c, + 0x2c, 0x6d, 0xb1, 0x7f, 0xd1, 0x52, 0x5c, 0x68, 0x54, 0x64, 0x3d, 0xc5, 0xb7, 0xac, 0x6a, 0x0a, + 0x35, 0xa1, 0x58, 0x0f, 0xb3, 0x14, 0xb7, 0xf4, 0x4d, 0x98, 0xac, 0xd5, 0x36, 0x36, 0x69, 0x10, + 0xde, 0x6e, 0x7a, 0x0f, 0x51, 0x51, 0xe4, 0x44, 0x1e, 0xd3, 0xc0, 0xb3, 0x43, 0x1a, 0x84, 0xf6, + 0x5e, 0xd3, 0x7b, 0x68, 0xa9, 0x58, 0xe4, 0xa7, 0x94, 0x97, 0x2b, 0x71, 0xe5, 0x9f, 0x3d, 0x76, + 0xe5, 0x4f, 0xbc, 0x6a, 0xc9, 0xd6, 0xff, 0xd4, 0x57, 0x2d, 0x19, 0x3a, 0x5e, 0x40, 0x60, 0x9b, + 0xb1, 0x42, 0xa3, 0xe1, 0xd3, 0x20, 0x10, 0x33, 0x5a, 0x79, 0x97, 0xd7, 0xe1, 0x05, 0xda, 0x05, + 0x04, 0x85, 0x80, 0x7c, 0xcf, 0x80, 0xd3, 0x6a, 0x0c, 0x33, 0x4e, 0x96, 0x16, 0x6d, 0x87, 0x8b, + 0x73, 0xd8, 0xd2, 0x37, 0xae, 0x49, 0x8d, 0x76, 0x4d, 0x41, 0xbb, 0xf6, 0xe0, 0xc6, 0x35, 0xe5, + 0xe9, 0xb4, 0x9a, 0x24, 0xc2, 0x27, 0xbc, 0x97, 0x52, 0xf9, 0xa9, 0xda, 0xc9, 0x49, 0x21, 0x45, + 0x2b, 0xaf, 0x56, 0xb8, 0xb7, 0x16, 0x9b, 0x2a, 0x9f, 0xad, 0xe8, 0x60, 0xed, 0xdb, 0x06, 0x44, + 0x07, 0x6f, 0xc1, 0x7c, 0xa2, 0x1b, 0xa4, 0x95, 0xa7, 0x81, 0x93, 0x56, 0x5e, 0x82, 0xc6, 0x4a, + 0x60, 0x9b, 0xff, 0x60, 0x3c, 0xc1, 0x57, 0x44, 0x04, 0x99, 0x30, 0xc6, 0x8d, 0x38, 0xf5, 0xb1, + 0x1f, 0x6e, 0xe2, 0x59, 0xa2, 0x84, 0x9c, 0x83, 0x6c, 0xad, 0xb6, 0xa1, 0x3e, 0x45, 0x16, 0x04, + 0x9e, 0xc5, 0x60, 0x6c, 0x84, 0x30, 0xd8, 0x47, 0x49, 0x9b, 0xc5, 0x34, 0x96, 0x85, 0x50, 0xd6, + 0xdf, 0xd2, 0xa4, 0x1a, 0x89, 0xfb, 0x5b, 0x98, 0x54, 0xb1, 0x21, 0x55, 0x84, 0xc5, 0x42, 0x10, + 0x50, 0x9f, 0xbf, 0xc6, 0x8c, 0x31, 0x24, 0xbe, 0x58, 0xf6, 0x85, 0x62, 0xc6, 0x4a, 0x9d, 0x7a, + 0x60, 0xf5, 0x45, 0x24, 0x97, 0x21, 0x57, 0xe8, 0x36, 0x5c, 0xda, 0xae, 0x6b, 0xb9, 0x18, 0x1c, + 0x01, 0xb3, 0xa2, 0x52, 0xf2, 0x11, 0x9c, 0x16, 0x44, 0xd2, 0xf6, 0x13, 0x3d, 0x30, 0x1e, 0xcf, + 0x1e, 0x69, 0x96, 0xc4, 0xe7, 0x93, 0xbc, 0x4b, 0xd2, 0x29, 0x49, 0x01, 0xf2, 0x65, 0x8c, 0x86, + 0x2f, 0x51, 0xee, 0x2a, 0xf5, 0x7c, 0xf1, 0x78, 0x2a, 0x1a, 0x91, 0x3c, 0x52, 0xde, 0x6e, 0x44, + 0x85, 0x56, 0x0f, 0x3a, 0xb9, 0x0b, 0xf3, 0x49, 0x18, 0xd3, 0xc1, 0xdc, 0x5e, 0xc4, 0xbc, 0x2d, + 0x3d, 0x5c, 0x50, 0x0b, 0xa7, 0x51, 0x91, 0x5d, 0x98, 0x8b, 0xcf, 0xe7, 0x75, 0x2b, 0x52, 0x86, + 0x93, 0x45, 0xe5, 0xd2, 0x92, 0x7c, 0x4e, 0x08, 0xe3, 0x7c, 0x7c, 0xd6, 0x1f, 0x59, 0x93, 0x56, + 0x2f, 0x3b, 0xd2, 0x80, 0x99, 0x9a, 0xbb, 0xdf, 0x76, 0xdb, 0xfb, 0x77, 0xe9, 0x61, 0xd5, 0x71, + 0x7d, 0x11, 0xd8, 0xb3, 0x18, 0xbd, 0xb0, 0x79, 0xd8, 0x6a, 0xd1, 0xd0, 0xc7, 0xd5, 0x8d, 0x95, + 0xe3, 0x15, 0x37, 0x83, 0xa9, 0xf1, 0x80, 0xd3, 0xe1, 0x75, 0x8e, 0x8e, 0xe3, 0x6a, 0x6a, 0x5c, + 0xe7, 0xa9, 0x59, 0xf2, 0x53, 0x43, 0x5a, 0xf2, 0x4d, 0x98, 0x2b, 0xb7, 0xeb, 0xfe, 0x21, 0x7a, + 0xac, 0x65, 0xe3, 0xa6, 0x8f, 0x69, 0xdc, 0xcb, 0xa2, 0x71, 0x17, 0x1c, 0x29, 0x61, 0x69, 0xcd, + 0xeb, 0x65, 0x4c, 0x6a, 0xe2, 0xa5, 0xd8, 0x4a, 0xa9, 0x5a, 0x69, 0xbb, 0xa1, 0x8b, 0xcf, 0xee, + 0xf0, 0xe5, 0xe1, 0x15, 0xc1, 0xf3, 0x79, 0x6e, 0xb1, 0xb9, 0x8d, 0x8e, 0xed, 0x4a, 0x94, 0x9e, + 0xa7, 0x60, 0x55, 0x7a, 0xf3, 0xff, 0x1a, 0xe7, 0xda, 0x50, 0xb5, 0xb0, 0xfa, 0x85, 0x2a, 0x25, + 0x2c, 0xaf, 0xcc, 0x49, 0x2c, 0xaf, 0xec, 0xf1, 0x96, 0xd7, 0xc8, 0x71, 0x96, 0x57, 0xc2, 0x34, + 0x1a, 0x3d, 0xb1, 0x69, 0x34, 0x76, 0x02, 0xd3, 0x68, 0xfc, 0x44, 0xa6, 0x91, 0x66, 0xe3, 0xe5, + 0x8e, 0xb3, 0xf1, 0xfe, 0x7f, 0x43, 0xea, 0x59, 0x35, 0xa4, 0xd2, 0x16, 0xd7, 0x13, 0x19, 0x52, + 0xfd, 0xed, 0xa0, 0xfc, 0xff, 0xc7, 0x76, 0xd0, 0x4f, 0x43, 0x3e, 0xa9, 0x9a, 0x8f, 0x4f, 0x14, + 0xf4, 0xd4, 0xf2, 0x79, 0xb0, 0x85, 0x23, 0xa9, 0x1a, 0xd9, 0xd6, 0xaa, 0xea, 0xbb, 0x0f, 0x9c, + 0x90, 0xc6, 0x2f, 0x55, 0xe2, 0xd6, 0xaa, 0xc3, 0xa1, 0x38, 0x5d, 0x15, 0x94, 0xc8, 0x2a, 0xc8, + 0xa4, 0x59, 0x05, 0xe6, 0xcf, 0x65, 0x60, 0x8e, 0xa7, 0x20, 0x78, 0xf6, 0x3d, 0x7a, 0xef, 0x69, + 0xb6, 0x9e, 0x0c, 0xdc, 0x49, 0x7c, 0xdd, 0x00, 0x9f, 0xde, 0xd7, 0xe1, 0x74, 0x4f, 0x57, 0xa0, + 0xbd, 0x57, 0x92, 0xc9, 0x1f, 0x7a, 0x2c, 0xbe, 0xc5, 0xf4, 0x4a, 0xb6, 0x6f, 0x5a, 0x3d, 0x14, + 0xe6, 0xbf, 0xc8, 0xf6, 0xf0, 0x17, 0xde, 0x3d, 0xd5, 0x5f, 0x67, 0x9c, 0xcc, 0x5f, 0x97, 0x19, + 0xce, 0x5f, 0x97, 0x58, 0x16, 0xb2, 0xc3, 0x2c, 0x0b, 0x1f, 0xc1, 0xf4, 0x26, 0x75, 0x5a, 0xc1, + 0xa6, 0x27, 0x32, 0x0a, 0xf2, 0xc0, 0x3d, 0x99, 0xdb, 0x81, 0x95, 0x49, 0x73, 0x25, 0x0a, 0x40, + 0x08, 0x19, 0x01, 0x53, 0x65, 0x3c, 0xc5, 0xa0, 0xa5, 0x73, 0x50, 0x6d, 0xd0, 0xd1, 0x01, 0x36, + 0x68, 0x0d, 0xa6, 0x04, 0x5d, 0x9c, 0x1d, 0x29, 0x36, 0x96, 0x58, 0x11, 0xc2, 0x65, 0xed, 0xd1, + 0xcb, 0x00, 0x51, 0xed, 0xdc, 0x4e, 0xd2, 0x98, 0xb0, 0x2e, 0x28, 0xb7, 0x1b, 0x1d, 0xcf, 0x6d, + 0x63, 0x17, 0x8c, 0xc7, 0x5d, 0x40, 0x05, 0x98, 0x77, 0x81, 0x82, 0x44, 0xde, 0x81, 0x99, 0x42, + 0xb5, 0xa2, 0x92, 0xe5, 0xd4, 0x77, 0xc2, 0x5d, 0x5b, 0x23, 0x4d, 0xe0, 0x9a, 0xff, 0x38, 0x27, + 0xe7, 0xd6, 0xa7, 0xeb, 0x9b, 0xd1, 0xbd, 0x2d, 0xd9, 0x13, 0x7a, 0x5b, 0x46, 0x8e, 0x5b, 0x89, + 0x35, 0xf3, 0x60, 0xf4, 0x04, 0xe6, 0xc1, 0xd8, 0x13, 0x7b, 0x4e, 0xc6, 0x4f, 0xb8, 0xe0, 0x27, + 0xc4, 0x3c, 0x37, 0x8c, 0x98, 0xa7, 0x1a, 0x09, 0x13, 0x4f, 0x6e, 0x24, 0xc0, 0x89, 0x8d, 0x04, + 0xe5, 0x51, 0xc7, 0xc9, 0xa1, 0x1e, 0x75, 0x34, 0x86, 0x78, 0xd4, 0xf1, 0x33, 0x65, 0x79, 0x7c, + 0x23, 0xdd, 0xf2, 0x18, 0xac, 0xea, 0x9f, 0x51, 0xdb, 0xc3, 0xc7, 0x0e, 0xda, 0x71, 0x7c, 0xb6, + 0x03, 0x0b, 0xc8, 0x75, 0x18, 0x97, 0x09, 0x56, 0x8c, 0x78, 0x33, 0xdb, 0x9b, 0x59, 0x45, 0x62, + 0xb1, 0xcd, 0x9a, 0x24, 0x16, 0x97, 0x91, 0x79, 0x2e, 0x09, 0x01, 0xd3, 0x72, 0x49, 0x08, 0x98, + 0xf9, 0xef, 0x8e, 0xc8, 0x49, 0xc8, 0x36, 0x13, 0xe2, 0x01, 0xa3, 0x15, 0xa5, 0xd3, 0x15, 0xcb, + 0x27, 0xd1, 0xad, 0x89, 0x20, 0x1b, 0x9d, 0xe4, 0x93, 0x64, 0xa7, 0x51, 0x72, 0x76, 0x67, 0x87, + 0xc8, 0xd9, 0xfd, 0x96, 0x96, 0xf0, 0x7a, 0x24, 0xce, 0xb0, 0xca, 0x04, 0x73, 0x70, 0xaa, 0xeb, + 0x5b, 0x6a, 0x66, 0xea, 0xd1, 0xf8, 0xde, 0x36, 0x52, 0x0e, 0xc8, 0x49, 0x1d, 0x99, 0x72, 0x63, + 0x27, 0xc9, 0xd4, 0x34, 0xfe, 0xaf, 0x34, 0x53, 0x53, 0x19, 0x40, 0x79, 0xd5, 0x86, 0xfb, 0xb6, + 0x5f, 0x61, 0xdd, 0x74, 0xfc, 0x8b, 0x36, 0x0a, 0xa1, 0xf9, 0x2f, 0xe7, 0x60, 0xae, 0x56, 0xdb, + 0x28, 0xb9, 0xce, 0x7e, 0xdb, 0x0b, 0x42, 0xb7, 0x5e, 0x69, 0xef, 0x79, 0xcc, 0x8e, 0x89, 0x26, + 0xb4, 0x92, 0x35, 0x28, 0x9e, 0xcc, 0x51, 0x31, 0xb3, 0x93, 0xcb, 0xbe, 0xef, 0xf9, 0xaa, 0x9d, + 0x4c, 0x19, 0xc0, 0xe2, 0x70, 0x66, 0x2a, 0xd4, 0xba, 0xfc, 0x79, 0x12, 0x7e, 0xdc, 0x80, 0xa6, + 0x42, 0xc0, 0x41, 0x96, 0x2c, 0x23, 0xb4, 0x57, 0x60, 0x85, 0xe9, 0x78, 0x56, 0xcb, 0xf7, 0x14, + 0x17, 0x73, 0x75, 0x25, 0x96, 0x13, 0xbc, 0xb5, 0xd1, 0x41, 0xb8, 0x7a, 0x36, 0xd5, 0x33, 0x07, + 0x0e, 0xe1, 0xb4, 0x76, 0xfb, 0x60, 0x58, 0x3f, 0xce, 0x55, 0x61, 0x9a, 0x98, 0x78, 0x89, 0x2a, + 0xc5, 0x99, 0xa3, 0xa6, 0x8c, 0x4d, 0xad, 0x81, 0xfc, 0x9c, 0x01, 0xcf, 0xa7, 0x96, 0x44, 0xb3, + 0x7b, 0x52, 0xcb, 0xb9, 0xa5, 0x28, 0x0d, 0x7c, 0xd2, 0xe5, 0xb5, 0x7e, 0x55, 0xdb, 0x29, 0xaa, + 0x60, 0x70, 0x4d, 0xe4, 0x1f, 0x1b, 0x70, 0x56, 0xc3, 0x88, 0xd4, 0x55, 0x80, 0xcb, 0x4a, 0x5f, + 0xb9, 0xfe, 0xe6, 0xd3, 0x91, 0xeb, 0x97, 0xf4, 0x6f, 0x89, 0xb5, 0xa9, 0xfa, 0x0d, 0xfd, 0x5a, + 0x48, 0x1e, 0xc0, 0x1c, 0x16, 0x49, 0x9f, 0x12, 0x93, 0x59, 0xe1, 0x8a, 0x5a, 0x88, 0x9b, 0xcd, + 0x6f, 0xd4, 0xe0, 0x7b, 0x06, 0xcb, 0x3f, 0x3e, 0x5a, 0x9a, 0xd6, 0xd0, 0x31, 0xdd, 0x27, 0xb6, + 0x21, 0x72, 0x4c, 0xb9, 0xed, 0x3d, 0x4f, 0x7b, 0x76, 0x37, 0x59, 0x05, 0xf9, 0x8f, 0x0d, 0x58, + 0x64, 0x50, 0xfe, 0x19, 0xb7, 0x7d, 0xaf, 0x15, 0x95, 0xcb, 0x43, 0xce, 0x3e, 0xdd, 0xd6, 0x7c, + 0x3a, 0xdd, 0xf6, 0x0a, 0x36, 0x99, 0xeb, 0x04, 0x7b, 0xcf, 0xf7, 0x5a, 0x71, 0xf3, 0xb5, 0x57, + 0x5b, 0xfa, 0x35, 0x92, 0xfc, 0xac, 0x01, 0xe7, 0xb4, 0x6d, 0xbd, 0x9a, 0xe4, 0x52, 0xdc, 0x5b, + 0x92, 0x27, 0xe2, 0x6a, 0xd1, 0xca, 0x35, 0x21, 0xff, 0x97, 0xb0, 0x05, 0xf1, 0x6a, 0x81, 0x6d, + 0xb1, 0x5b, 0x1c, 0x4b, 0x69, 0x42, 0xff, 0x5a, 0x88, 0x0b, 0x73, 0x78, 0x44, 0xa3, 0x1d, 0xc6, + 0x2f, 0xf4, 0x3f, 0x8c, 0x8f, 0xf2, 0x4e, 0x63, 0x22, 0xc1, 0xfe, 0x27, 0xf2, 0xbd, 0x5c, 0xc9, + 0xcf, 0xc0, 0xb9, 0x1e, 0x60, 0x34, 0xdb, 0x4e, 0xf7, 0x9d, 0x6d, 0xaf, 0x3d, 0x3e, 0x5a, 0x7a, + 0x35, 0xad, 0xb6, 0xb4, 0x99, 0xd6, 0xbf, 0x06, 0xe2, 0x00, 0xc4, 0x85, 0xe2, 0x19, 0x98, 0x74, + 0x01, 0x7d, 0x4d, 0xc8, 0x87, 0x82, 0xcf, 0x74, 0xb9, 0xd2, 0x06, 0x75, 0xc9, 0x8b, 0x91, 0x08, + 0x85, 0x29, 0x25, 0x89, 0xe2, 0x21, 0xbe, 0x07, 0xd3, 0xb7, 0x92, 0x1f, 0x1f, 0x2d, 0x69, 0xd8, + 0xcc, 0xa4, 0x55, 0xb3, 0x33, 0xaa, 0x26, 0xad, 0x86, 0x48, 0x7e, 0xdf, 0x80, 0x05, 0x06, 0x88, + 0x85, 0x4a, 0x7c, 0xd4, 0xe2, 0x20, 0xa9, 0x3f, 0x78, 0x3a, 0x52, 0xff, 0x22, 0xb6, 0x51, 0x95, + 0xfa, 0x9e, 0x2e, 0x49, 0x6d, 0x1c, 0x4a, 0xbb, 0x76, 0x1a, 0xa8, 0x49, 0xfb, 0xb9, 0x21, 0xa4, + 0x9d, 0x0f, 0xc0, 0xf1, 0xd2, 0xde, 0xb7, 0x16, 0xb2, 0x09, 0x53, 0xc2, 0x9a, 0xe5, 0x1d, 0xf6, + 0x82, 0x96, 0xb3, 0x4d, 0x2d, 0xe2, 0x5b, 0x0c, 0x91, 0x63, 0xb2, 0xe7, 0x0b, 0x35, 0x2e, 0xa4, + 0x0d, 0xf3, 0xfc, 0xb7, 0xbe, 0xb1, 0x5f, 0xea, 0xbb, 0xb1, 0xbf, 0x2c, 0xbe, 0xe8, 0xa2, 0xe0, + 0x9f, 0xd8, 0xdf, 0xab, 0xe9, 0xdb, 0x53, 0x18, 0x93, 0x0e, 0x10, 0x0d, 0xcc, 0x27, 0xed, 0xc5, + 0xc1, 0xdb, 0xf9, 0x57, 0x45, 0x9d, 0x4b, 0xc9, 0x3a, 0x93, 0x33, 0x37, 0x85, 0x37, 0x71, 0x60, + 0x56, 0x40, 0xd9, 0xde, 0x15, 0x35, 0xfc, 0x8b, 0xda, 0x15, 0xe7, 0x44, 0x29, 0x7f, 0x14, 0x45, + 0xd6, 0x84, 0x57, 0xd0, 0x13, 0x0a, 0x3d, 0xc9, 0xcf, 0xfc, 0x9e, 0xd1, 0x53, 0x07, 0xdb, 0x23, + 0xe3, 0x0f, 0xe5, 0x32, 0x22, 0xee, 0x91, 0x39, 0x47, 0xdc, 0xab, 0xc7, 0x08, 0xcc, 0xb6, 0x51, + 0x13, 0x5d, 0x64, 0xc5, 0x3b, 0xaa, 0x1c, 0x14, 0x6f, 0xdd, 0x96, 0x64, 0x4c, 0x53, 0x36, 0xb6, + 0x91, 0x30, 0xa6, 0x49, 0x44, 0x32, 0x99, 0x3f, 0x9b, 0xd1, 0xa5, 0x84, 0x5c, 0x56, 0xcc, 0x6c, + 0x25, 0xd5, 0x86, 0x34, 0xb3, 0x15, 0xe3, 0xfa, 0xef, 0x19, 0x30, 0xbf, 0xe1, 0xef, 0x3b, 0x6d, + 0xf7, 0xdb, 0x3c, 0x11, 0x97, 0x87, 0xdd, 0x18, 0xdd, 0xc3, 0xf8, 0x54, 0x13, 0x7a, 0x7b, 0x4a, + 0xc5, 0x6c, 0x60, 0x71, 0x84, 0xad, 0xb4, 0xf6, 0x60, 0x94, 0x28, 0x36, 0x4c, 0xc9, 0xab, 0xce, + 0xd1, 0x39, 0xdc, 0xfc, 0xc5, 0x0c, 0x4c, 0x2a, 0x12, 0x4b, 0x3e, 0x0f, 0x53, 0x2a, 0x1f, 0xd5, + 0xbf, 0xa2, 0x56, 0x6b, 0x69, 0x58, 0xe8, 0x60, 0xa1, 0x4e, 0x4b, 0x73, 0xb0, 0x30, 0xb9, 0x44, + 0xe8, 0x09, 0x77, 0x22, 0xef, 0xa7, 0xec, 0x44, 0x4e, 0xf4, 0xf4, 0xce, 0x3b, 0xbd, 0xfb, 0x91, + 0xe1, 0x5f, 0xca, 0x31, 0x7f, 0xd9, 0x80, 0x7c, 0x72, 0x4e, 0x7d, 0x2a, 0xbd, 0x72, 0x02, 0x4f, + 0xf6, 0x2f, 0x64, 0x20, 0x8f, 0x2f, 0x7c, 0xd1, 0x86, 0x8c, 0x7d, 0x7f, 0x56, 0x03, 0x0a, 0xde, + 0xd5, 0x9c, 0xcc, 0xcf, 0x45, 0xcb, 0x80, 0xfa, 0x71, 0x03, 0x72, 0x82, 0x8c, 0xfc, 0xf0, 0x37, + 0x97, 0x4e, 0x99, 0x1f, 0xc3, 0x42, 0xb2, 0x3b, 0xd0, 0xd1, 0x5c, 0x80, 0x59, 0x1d, 0x9e, 0x4c, + 0x97, 0x9c, 0xa4, 0xb2, 0x92, 0xf8, 0xe6, 0x1f, 0x67, 0x92, 0xbc, 0x45, 0x70, 0x01, 0x53, 0x3a, + 0x6d, 0x67, 0xb7, 0x19, 0x65, 0x74, 0x15, 0x8f, 0x37, 0x23, 0xc8, 0x92, 0x65, 0x27, 0x49, 0x9c, + 0x1d, 0x45, 0x70, 0x67, 0xd3, 0x23, 0xb8, 0xc9, 0xad, 0x44, 0x44, 0x8c, 0x72, 0xdd, 0xf8, 0x21, + 0xdd, 0xb5, 0xe3, 0xa8, 0x98, 0x44, 0x20, 0x4c, 0x11, 0x16, 0xb4, 0x9c, 0x6c, 0x92, 0x7e, 0x34, + 0x76, 0x6d, 0x86, 0x58, 0xc0, 0x89, 0x53, 0x91, 0xc9, 0x2a, 0x8c, 0xb3, 0x66, 0xde, 0x73, 0x3a, + 0xc2, 0x7f, 0xac, 0xbe, 0x71, 0x2b, 0xd7, 0x1a, 0xe5, 0x4a, 0x47, 0x93, 0xb2, 0x15, 0x5a, 0x7b, + 0xb9, 0x8a, 0x23, 0x9a, 0x7f, 0x6e, 0xb0, 0xf9, 0x5f, 0xbf, 0xff, 0x19, 0x4b, 0xe9, 0xcd, 0x3e, + 0x69, 0x40, 0xec, 0xcb, 0x9f, 0x64, 0x78, 0x52, 0x5e, 0x21, 0x3e, 0x6f, 0xc1, 0xd8, 0xa6, 0xe3, + 0xef, 0xd3, 0x50, 0xa4, 0x8f, 0x55, 0xb9, 0xf0, 0x82, 0xf8, 0x32, 0x74, 0x88, 0xbf, 0x2d, 0x41, + 0xa0, 0xba, 0xae, 0x32, 0x43, 0xb9, 0xae, 0x14, 0x47, 0x68, 0xf6, 0xa9, 0x39, 0x42, 0xbf, 0x12, + 0xe5, 0xc3, 0x2d, 0x84, 0x43, 0xa4, 0xfc, 0xba, 0x98, 0x4c, 0x0e, 0xdd, 0x93, 0x9c, 0x2d, 0x66, + 0x47, 0x6e, 0xa9, 0xe9, 0xa6, 0x95, 0xa0, 0xe8, 0x63, 0x12, 0x4b, 0x9b, 0x7f, 0x3f, 0xcb, 0xfb, + 0x58, 0x74, 0xd4, 0x25, 0xed, 0xc2, 0x04, 0xce, 0x13, 0xa6, 0xe8, 0xd5, 0xbb, 0x6b, 0x78, 0xc8, + 0x7f, 0x09, 0x46, 0x98, 0x6c, 0x8a, 0xde, 0xe4, 0xef, 0x93, 0x7a, 0x4d, 0xed, 0x8e, 0x1b, 0x2b, + 0x67, 0x73, 0x19, 0xd7, 0x24, 0x35, 0xb3, 0x3d, 0x2e, 0x5b, 0xea, 0x5c, 0x46, 0x0c, 0xbc, 0x36, + 0xe7, 0x35, 0xa8, 0x3a, 0x45, 0xdb, 0xfa, 0x43, 0x8d, 0x58, 0xce, 0xbe, 0x34, 0x4a, 0x30, 0xab, + 0x7e, 0x69, 0x6b, 0xcf, 0xb1, 0x79, 0x62, 0x53, 0xf5, 0x4b, 0xe3, 0x5c, 0xb4, 0x65, 0x98, 0xd1, + 0x1f, 0x79, 0x12, 0x71, 0x41, 0x78, 0xdf, 0x31, 0xf1, 0x40, 0x94, 0xea, 0x85, 0xd6, 0x89, 0xc8, + 0x0a, 0x4c, 0x6b, 0x09, 0x6d, 0xc4, 0x31, 0x0e, 0xfa, 0x22, 0xf5, 0x74, 0x38, 0xaa, 0x2f, 0x52, + 0x23, 0x61, 0xab, 0xb9, 0x68, 0xbf, 0x72, 0x98, 0xd3, 0xd3, 0x76, 0x81, 0xa3, 0x5c, 0x2c, 0xfa, + 0x1c, 0xe4, 0x85, 0x6e, 0x89, 0x9e, 0x24, 0xc0, 0xc3, 0xd5, 0x4a, 0xc9, 0x52, 0xf5, 0x41, 0xdd, + 0x6d, 0xf8, 0x16, 0x42, 0xcd, 0x1f, 0x18, 0x70, 0x6e, 0x9d, 0x86, 0x0f, 0x3d, 0xff, 0xbe, 0x45, + 0x83, 0xd0, 0x77, 0xf9, 0x0b, 0x07, 0x38, 0xa3, 0x3e, 0x4f, 0xde, 0x91, 0x6f, 0x67, 0xeb, 0x2a, + 0x3e, 0x59, 0xc7, 0xca, 0xb4, 0x90, 0xc4, 0x51, 0x0c, 0x5c, 0x91, 0x6f, 0x66, 0xbf, 0x25, 0xde, + 0xcc, 0xce, 0x0c, 0x26, 0x8e, 0x66, 0x76, 0x83, 0xb6, 0xe5, 0x5b, 0xd9, 0xbf, 0x9c, 0x81, 0xd3, + 0x29, 0xcd, 0xda, 0xfe, 0xfc, 0x33, 0xaa, 0xde, 0x56, 0x34, 0xf5, 0x76, 0x51, 0x90, 0xf6, 0xed, + 0xf8, 0x54, 0x6d, 0xf7, 0xeb, 0x06, 0x9c, 0xd5, 0x65, 0x4d, 0x04, 0x97, 0x6d, 0xdf, 0x24, 0x6f, + 0xc3, 0xd8, 0x2a, 0x75, 0x1a, 0x54, 0x66, 0xce, 0x8e, 0x9f, 0x4d, 0xe5, 0xb7, 0x68, 0x78, 0x21, + 0x67, 0xfb, 0xc7, 0x5c, 0x19, 0x9d, 0xb2, 0x04, 0x09, 0x29, 0x89, 0xc6, 0x71, 0xc3, 0xda, 0x94, + 0x37, 0xda, 0xd2, 0xaa, 0x1a, 0x70, 0x34, 0xfd, 0x63, 0x03, 0x9e, 0x1b, 0x40, 0xc3, 0x06, 0x8e, + 0x0d, 0xbd, 0x3a, 0x70, 0xb8, 0x34, 0x22, 0x94, 0xbc, 0x07, 0xb3, 0x9b, 0xc2, 0x30, 0x97, 0xc3, + 0x91, 0x89, 0x45, 0x5f, 0xda, 0xec, 0xb6, 0x1c, 0x97, 0x24, 0xb2, 0x76, 0xd5, 0x32, 0x3b, 0xf0, + 0xaa, 0xa5, 0x7a, 0x73, 0x71, 0x64, 0xd8, 0x9b, 0x8b, 0x1f, 0x27, 0xdf, 0x99, 0x13, 0x09, 0xa4, + 0xe2, 0x7b, 0x9b, 0x46, 0xff, 0x7b, 0x9b, 0x03, 0xd3, 0xd4, 0x60, 0xa2, 0x7d, 0x9d, 0xf7, 0x93, + 0x8e, 0xe7, 0xbb, 0xda, 0x78, 0x3e, 0x97, 0x3e, 0x9e, 0xfd, 0x07, 0xf2, 0x37, 0x8c, 0xe4, 0xc7, + 0x0e, 0x35, 0x82, 0x26, 0x8c, 0x95, 0xbc, 0x96, 0xe3, 0xb6, 0xd5, 0x27, 0xb6, 0x1a, 0x08, 0xb1, + 0x44, 0xc9, 0x70, 0xd7, 0x5c, 0x2f, 0xc2, 0xe8, 0xba, 0xd7, 0x2e, 0x94, 0x44, 0x24, 0x19, 0xf2, + 0x69, 0x7b, 0x6d, 0xdb, 0x69, 0x58, 0xbc, 0xc0, 0xfc, 0xb9, 0x51, 0x38, 0x67, 0xd1, 0x7d, 0x97, + 0x99, 0x8e, 0x5b, 0x81, 0xdb, 0xde, 0xd7, 0xee, 0xf4, 0x99, 0x89, 0x31, 0x11, 0x89, 0x15, 0x19, + 0x24, 0xaa, 0xe3, 0x0a, 0xe4, 0xd8, 0x5a, 0xa1, 0x0c, 0x0b, 0x1e, 0x03, 0xe0, 0x83, 0xa7, 0x5c, + 0x5e, 0x64, 0x31, 0xb9, 0x2a, 0x56, 0x32, 0x25, 0xf5, 0x2d, 0x5b, 0xc9, 0x7e, 0x72, 0xb4, 0x04, + 0xb5, 0xc3, 0x20, 0xa4, 0xb8, 0x8b, 0x11, 0xab, 0x59, 0x64, 0x6e, 0x8e, 0xf4, 0x31, 0x37, 0xef, + 0xc1, 0x42, 0xa1, 0xc1, 0xf5, 0x9e, 0xd3, 0xac, 0xfa, 0x6e, 0xbb, 0xee, 0x76, 0x9c, 0xa6, 0xdc, + 0x42, 0xe1, 0x61, 0x90, 0x13, 0x95, 0xdb, 0x9d, 0x08, 0xc1, 0x4a, 0x25, 0x63, 0x9f, 0x51, 0x5a, + 0xaf, 0xf1, 0xf7, 0x2c, 0xf9, 0x09, 0x0f, 0x7e, 0x46, 0xa3, 0x1d, 0xf0, 0x07, 0x2d, 0xad, 0xa8, + 0x18, 0x0d, 0x5d, 0x3c, 0x01, 0xdf, 0x5c, 0xab, 0xc5, 0xf7, 0x0b, 0x78, 0x66, 0x3e, 0x7e, 0x4a, + 0x1e, 0x36, 0x03, 0x3c, 0x29, 0xd7, 0xf0, 0x62, 0xba, 0x5a, 0x6d, 0x95, 0xd1, 0xe5, 0x7a, 0xe8, + 0x82, 0xe0, 0x40, 0xa5, 0xe3, 0x78, 0xe4, 0x3a, 0x00, 0xcf, 0x29, 0x83, 0x22, 0x33, 0x11, 0x9b, + 0xc5, 0x3e, 0x42, 0xb9, 0x59, 0xac, 0xa0, 0x90, 0x77, 0x60, 0xbe, 0x5c, 0x5c, 0x96, 0x7e, 0xb9, + 0x92, 0x57, 0xef, 0xe2, 0x99, 0x26, 0x60, 0x7d, 0x38, 0x86, 0xb4, 0xbe, 0xcc, 0xe4, 0x24, 0x0d, + 0x8d, 0x5c, 0x82, 0xf1, 0x4a, 0x89, 0xf7, 0xfd, 0xa4, 0x9a, 0x7e, 0x5a, 0xc4, 0x0a, 0xc8, 0x42, + 0xb2, 0x11, 0xdb, 0x6d, 0x53, 0xc7, 0x1a, 0x58, 0xe7, 0x8e, 0xb7, 0xd9, 0x44, 0x96, 0x6a, 0xfe, + 0xc6, 0x41, 0xd1, 0x6b, 0xd0, 0x60, 0xfb, 0xc6, 0x67, 0x2c, 0x4b, 0xb5, 0xf2, 0x6d, 0xa8, 0x08, + 0x6e, 0xa4, 0x6a, 0x8d, 0xbf, 0x8d, 0x59, 0xaa, 0x7b, 0x70, 0xc9, 0x17, 0x61, 0x14, 0x7f, 0x0a, + 0x13, 0x62, 0x3e, 0x85, 0x6d, 0x6c, 0x3e, 0xd4, 0xf9, 0x6b, 0x73, 0x48, 0x40, 0x2a, 0x30, 0x2e, + 0xcc, 0xd0, 0x93, 0xe4, 0x5a, 0x15, 0xf6, 0x2c, 0x1f, 0x24, 0x41, 0x6f, 0x36, 0x60, 0x4a, 0xad, + 0x90, 0x09, 0xe7, 0xaa, 0x13, 0x1c, 0xd0, 0x06, 0xfb, 0x25, 0xd2, 0xa4, 0xa3, 0x70, 0x1e, 0x20, + 0xd4, 0x66, 0xed, 0xb0, 0x14, 0x14, 0xa6, 0xb8, 0x2a, 0xc1, 0x56, 0x20, 0x9a, 0x22, 0x36, 0xa6, + 0x2e, 0x3a, 0x39, 0x1a, 0x96, 0x28, 0x32, 0xbf, 0x02, 0x0b, 0xeb, 0xdd, 0x66, 0x93, 0x6d, 0x52, + 0x65, 0x1a, 0xcd, 0xd0, 0x09, 0x29, 0x59, 0x81, 0x51, 0xfc, 0x43, 0x3c, 0x4e, 0x3d, 0xaf, 0xbf, + 0xd6, 0x89, 0x45, 0x18, 0x9e, 0x64, 0xe0, 0x8d, 0xc0, 0x50, 0x7f, 0xed, 0x95, 0x93, 0x9a, 0x3f, + 0x8a, 0xdf, 0x3d, 0xdc, 0xf4, 0x9d, 0xfa, 0x7d, 0xea, 0x8b, 0x15, 0x68, 0xd8, 0x77, 0x19, 0x3f, + 0x94, 0x8d, 0xd0, 0x57, 0x85, 0xb4, 0x06, 0x1f, 0xd7, 0x18, 0xf2, 0x0e, 0x4c, 0x8a, 0x95, 0x41, + 0xc9, 0x63, 0x81, 0x97, 0x85, 0xe5, 0xfb, 0xa9, 0x89, 0x13, 0x73, 0x15, 0x1d, 0x17, 0x3c, 0xfd, + 0x53, 0xb6, 0x6f, 0x7c, 0x1a, 0x0b, 0x9e, 0x5e, 0xc7, 0x00, 0xd1, 0xfd, 0x2e, 0x24, 0xfb, 0x56, + 0xc8, 0xee, 0x2d, 0xf5, 0xe6, 0xba, 0x11, 0xef, 0x24, 0xe2, 0x9b, 0xeb, 0xea, 0x4e, 0x22, 0x42, + 0x8d, 0xc6, 0x24, 0x73, 0xcc, 0x98, 0xbc, 0x27, 0xc7, 0x24, 0xdb, 0x5f, 0x30, 0xe6, 0x07, 0x8c, + 0x43, 0x2d, 0x9e, 0x21, 0x23, 0x43, 0x6d, 0x42, 0x4f, 0x61, 0x8a, 0x3e, 0x4e, 0x92, 0x54, 0x68, + 0x82, 0x93, 0xba, 0xb3, 0x1d, 0x1d, 0x9e, 0xe9, 0x31, 0x3b, 0xdb, 0x2f, 0xc1, 0x54, 0x21, 0x0c, + 0x9d, 0xfa, 0x01, 0x6d, 0x94, 0x98, 0x7a, 0x52, 0x2e, 0xd9, 0x3a, 0x02, 0xae, 0x9e, 0x08, 0xa8, + 0xb8, 0x3c, 0x69, 0x8c, 0x13, 0x88, 0x58, 0xab, 0x28, 0x69, 0x0c, 0x83, 0xe8, 0x49, 0x63, 0x18, + 0x84, 0xed, 0xe4, 0x2b, 0xed, 0x07, 0x2e, 0xeb, 0x93, 0x5c, 0xfc, 0x96, 0x9b, 0xcb, 0x41, 0xaa, + 0x72, 0x15, 0x58, 0xe4, 0x2d, 0xc5, 0x72, 0x9c, 0x88, 0x37, 0x7c, 0xdc, 0x41, 0x60, 0x4b, 0x03, + 0x52, 0xb5, 0x0a, 0x23, 0x53, 0xf2, 0x16, 0x8c, 0x4b, 0xbf, 0x0f, 0xc4, 0x9b, 0x3c, 0x41, 0xd9, + 0x7b, 0x95, 0x4a, 0x22, 0xe3, 0x33, 0x4a, 0x4a, 0xba, 0xf7, 0x49, 0xe5, 0x19, 0x25, 0x25, 0xdd, + 0xbb, 0xf6, 0x8c, 0x92, 0x92, 0xf8, 0x3d, 0xda, 0x32, 0x4f, 0x1d, 0xbb, 0x65, 0xde, 0x86, 0xa9, + 0xaa, 0xe3, 0x87, 0x2e, 0x33, 0x17, 0xda, 0x21, 0x7f, 0xb5, 0x3a, 0xf6, 0x32, 0x29, 0x45, 0x2b, + 0x2f, 0xc8, 0xe7, 0x84, 0x3a, 0x0a, 0xbe, 0xfe, 0x0e, 0x4d, 0x0c, 0x4f, 0x8f, 0xb4, 0x9a, 0x79, + 0x92, 0x48, 0x2b, 0xec, 0x54, 0xf4, 0x2c, 0xcc, 0xc6, 0x71, 0x6d, 0x68, 0x19, 0x26, 0xdc, 0x0b, + 0x11, 0x22, 0xf9, 0x1a, 0x4c, 0xb1, 0xbf, 0xf1, 0x71, 0x54, 0x97, 0xf2, 0x57, 0xa9, 0xe3, 0xc4, + 0x5c, 0xfa, 0x84, 0xe6, 0x2f, 0xa8, 0xd6, 0x68, 0xc8, 0x27, 0x30, 0x32, 0x4e, 0xba, 0x0c, 0x35, + 0x6e, 0xe4, 0x7d, 0x98, 0x52, 0x9f, 0x00, 0xc7, 0xfb, 0x6d, 0x22, 0x56, 0xae, 0x21, 0xe0, 0x3d, + 0x79, 0x9b, 0x54, 0x02, 0xb6, 0xcc, 0x17, 0x3a, 0x5c, 0x41, 0x12, 0x45, 0xda, 0x3b, 0x3d, 0xca, + 0x51, 0xa2, 0x91, 0x0f, 0x60, 0xaa, 0xd0, 0xe9, 0xc4, 0x1a, 0x67, 0x5e, 0x71, 0x1c, 0x74, 0x3a, + 0x76, 0xaa, 0xd6, 0xd1, 0x28, 0x92, 0x8a, 0x79, 0xe1, 0x64, 0x8a, 0xf9, 0xcf, 0x0c, 0x38, 0xdb, + 0xa7, 0xdb, 0xa2, 0x9c, 0x45, 0xc6, 0xe0, 0x9c, 0x45, 0x6c, 0xfa, 0xe9, 0xfb, 0x37, 0x9c, 0x7e, + 0xc2, 0x54, 0x51, 0x3f, 0x5a, 0x1a, 0x2d, 0xe9, 0x4f, 0x68, 0x67, 0x3f, 0xb5, 0x27, 0xb4, 0xcd, + 0x23, 0x03, 0x26, 0x15, 0x61, 0x26, 0x17, 0x95, 0x6b, 0x32, 0x79, 0x9e, 0x08, 0x59, 0xe1, 0x90, + 0xe1, 0xea, 0x1c, 0x25, 0x33, 0x73, 0xbc, 0xcf, 0xeb, 0x1e, 0xb3, 0x27, 0x94, 0xbc, 0x4e, 0xad, + 0x84, 0x83, 0x8a, 0x95, 0xe3, 0x53, 0x62, 0x4e, 0x10, 0x16, 0xea, 0xa1, 0xfb, 0x80, 0x0e, 0xa1, + 0xb9, 0xe3, 0xa7, 0xc4, 0x9c, 0x20, 0xb4, 0x1d, 0x24, 0xeb, 0x79, 0x4a, 0x2c, 0x62, 0x68, 0xfe, + 0x15, 0x03, 0x60, 0xab, 0x52, 0xc4, 0xc4, 0x6c, 0x4f, 0xba, 0xb2, 0xa6, 0x27, 0xbb, 0x91, 0xdc, + 0x07, 0xac, 0xa9, 0x55, 0x98, 0xd1, 0xb1, 0xd8, 0x0e, 0xbf, 0x56, 0xf7, 0xbd, 0x66, 0x73, 0xd7, + 0xa9, 0xdf, 0x5f, 0x73, 0xdb, 0x94, 0x67, 0x19, 0x19, 0xe5, 0xfa, 0x3c, 0x88, 0x8a, 0xec, 0x26, + 0x2b, 0xb3, 0x92, 0xc8, 0xe6, 0x5f, 0x18, 0x30, 0x59, 0x69, 0x07, 0xa1, 0xd3, 0x6c, 0xa2, 0xc5, + 0xf0, 0x59, 0xca, 0x85, 0x1f, 0x7d, 0xd7, 0x80, 0x1e, 0x7d, 0x13, 0x66, 0x13, 0x68, 0x6c, 0xa7, + 0x5b, 0xc3, 0x5b, 0x7c, 0xea, 0x4e, 0x97, 0xdf, 0xeb, 0xb3, 0x44, 0x89, 0x59, 0x56, 0xc8, 0xb6, + 0x6f, 0xe0, 0x11, 0xce, 0x32, 0x80, 0x2b, 0x41, 0xd2, 0x2e, 0x27, 0xc9, 0x96, 0x6c, 0xdf, 0xb0, + 0x14, 0x2c, 0x73, 0x1d, 0xc6, 0x6a, 0x9e, 0x1f, 0xae, 0x1c, 0x72, 0x53, 0xb8, 0x44, 0x83, 0xba, + 0x7a, 0x46, 0xe3, 0xa2, 0x67, 0xb4, 0x6e, 0x89, 0x22, 0xb6, 0x11, 0xbe, 0xed, 0xd2, 0x66, 0x43, + 0x8d, 0x9d, 0xdb, 0x63, 0x00, 0x8b, 0xc3, 0xd9, 0x76, 0xe1, 0x4c, 0x9c, 0x38, 0x34, 0x0e, 0xd2, + 0x7b, 0x52, 0x81, 0x2d, 0x6a, 0xfd, 0xfb, 0xa2, 0xfe, 0x80, 0x9c, 0x56, 0xd3, 0x80, 0xae, 0xfe, + 0x87, 0x06, 0x9c, 0xef, 0x4f, 0xa2, 0xc6, 0xfd, 0x19, 0x03, 0xe2, 0xfe, 0x5e, 0x49, 0x9e, 0x29, + 0x20, 0x9a, 0x38, 0x53, 0x88, 0x4f, 0x12, 0x4a, 0x18, 0x76, 0x59, 0x8f, 0x1e, 0xeb, 0xbc, 0x38, + 0xa0, 0xcd, 0x88, 0xc8, 0x87, 0x39, 0x44, 0x1a, 0x4b, 0xd0, 0x9a, 0xff, 0x64, 0x04, 0xce, 0xf5, + 0xa5, 0x20, 0xab, 0x4a, 0x0e, 0xe2, 0x99, 0x28, 0xfb, 0x69, 0x5f, 0xfc, 0x6b, 0xf8, 0x2f, 0x46, + 0xd6, 0x24, 0x03, 0xf9, 0x37, 0xa2, 0xdc, 0xb3, 0x19, 0xe4, 0xf5, 0xda, 0xb1, 0xbc, 0x38, 0x3a, + 0x32, 0x83, 0xde, 0x34, 0xb4, 0x78, 0xdf, 0x82, 0x86, 0x8e, 0xdb, 0x0c, 0xd4, 0x69, 0xd7, 0xe0, + 0x20, 0x4b, 0x96, 0xc5, 0xc1, 0x98, 0x23, 0xe9, 0xc1, 0x98, 0xe6, 0xff, 0x63, 0xc0, 0x44, 0xd4, + 0x6c, 0x72, 0x1e, 0xce, 0x6c, 0x5a, 0x85, 0x62, 0xd9, 0xde, 0xfc, 0xb8, 0x5a, 0xb6, 0xb7, 0xd6, + 0x6b, 0xd5, 0x72, 0xb1, 0x72, 0xbb, 0x52, 0x2e, 0xe5, 0x4f, 0x91, 0x39, 0x98, 0xde, 0x5a, 0xbf, + 0xbb, 0xbe, 0xb1, 0xb3, 0x6e, 0x97, 0x2d, 0x6b, 0xc3, 0xca, 0x1b, 0x64, 0x1a, 0x26, 0xac, 0x95, + 0x42, 0xd1, 0x5e, 0xdf, 0x28, 0x95, 0xf3, 0x19, 0x92, 0x87, 0xa9, 0xe2, 0xc6, 0xfa, 0x7a, 0xb9, + 0xb8, 0x59, 0xd9, 0xae, 0x6c, 0x7e, 0x9c, 0xcf, 0x12, 0x02, 0x33, 0x88, 0x50, 0xb5, 0x2a, 0xeb, + 0xc5, 0x4a, 0xb5, 0xb0, 0x96, 0x1f, 0x61, 0x30, 0x86, 0xaf, 0xc0, 0x46, 0x23, 0x46, 0x77, 0xb7, + 0x56, 0xca, 0xf9, 0x31, 0x86, 0xc2, 0xfe, 0x52, 0x50, 0xc6, 0x59, 0xf5, 0x88, 0x52, 0x2a, 0x6c, + 0x16, 0x56, 0x0a, 0xb5, 0x72, 0x3e, 0x47, 0xce, 0xc2, 0xbc, 0x06, 0xb2, 0xd7, 0x36, 0xee, 0x54, + 0xd6, 0xf3, 0x13, 0x64, 0x01, 0xf2, 0x11, 0xac, 0xb4, 0x62, 0x6f, 0xd5, 0xca, 0x56, 0x1e, 0x92, + 0xd0, 0xf5, 0xc2, 0xbd, 0x72, 0x7e, 0xd2, 0x7c, 0x97, 0x5f, 0xb1, 0xe0, 0x5d, 0x4d, 0xce, 0x00, + 0xa9, 0x6d, 0x16, 0x36, 0xb7, 0x6a, 0x89, 0x8f, 0x9f, 0x84, 0xf1, 0xda, 0x56, 0xb1, 0x58, 0xae, + 0xd5, 0xf2, 0x06, 0x01, 0x18, 0xbb, 0x5d, 0xa8, 0xac, 0x95, 0x4b, 0xf9, 0x8c, 0xf9, 0x4b, 0x06, + 0xcc, 0x49, 0xfb, 0x45, 0xfa, 0x95, 0x9f, 0x70, 0x2e, 0xbe, 0xa7, 0x6d, 0xcb, 0x64, 0x04, 0x7c, + 0xa2, 0x92, 0x01, 0xd3, 0xd0, 0x87, 0xd3, 0xa9, 0xc8, 0xe4, 0x63, 0xc8, 0xcb, 0x06, 0xdc, 0x73, + 0xc2, 0xfa, 0x41, 0xac, 0xc6, 0x5e, 0x48, 0x54, 0x92, 0x40, 0xe3, 0xee, 0xb1, 0xf8, 0xb1, 0x9d, + 0x1e, 0x36, 0xe6, 0x4f, 0xc1, 0xd9, 0x3e, 0xb4, 0xa4, 0x08, 0x63, 0x51, 0x46, 0xd6, 0x01, 0x11, + 0x28, 0x0b, 0x3f, 0x3e, 0x5a, 0x12, 0x88, 0xf8, 0xe4, 0x08, 0xfe, 0x65, 0x09, 0x88, 0xf9, 0xd7, + 0x0d, 0x98, 0x12, 0x56, 0x6f, 0xa1, 0x49, 0xfd, 0xf0, 0xc9, 0x7a, 0xf8, 0x2d, 0xad, 0x87, 0xa3, + 0x70, 0x62, 0x85, 0x3f, 0x2b, 0x4e, 0xed, 0xdc, 0xff, 0xd2, 0x80, 0x7c, 0x12, 0x91, 0xbc, 0x07, + 0xb9, 0x1a, 0x7d, 0x40, 0x7d, 0x37, 0x3c, 0x14, 0xba, 0x42, 0xa6, 0x7a, 0xe7, 0x38, 0xa2, 0x8c, + 0x3b, 0xd7, 0x02, 0xf1, 0xcb, 0x8a, 0x68, 0x86, 0x55, 0x79, 0xca, 0xbe, 0x35, 0xfb, 0xb4, 0xf6, + 0xad, 0xe6, 0x3f, 0xca, 0xc0, 0xd9, 0x3b, 0x34, 0x54, 0xbf, 0x29, 0x3a, 0x4f, 0xfb, 0xdc, 0x70, + 0xdf, 0xa5, 0x7c, 0xc9, 0x22, 0x8c, 0x63, 0x91, 0xbc, 0x20, 0x6d, 0xc9, 0x9f, 0x64, 0x25, 0x12, + 0x83, 0xac, 0x96, 0x4b, 0xba, 0x4f, 0xdd, 0xd7, 0x94, 0xec, 0xb2, 0x52, 0x0a, 0xc8, 0x25, 0x98, + 0xc1, 0xf4, 0x69, 0x5d, 0x26, 0x3d, 0xb4, 0x21, 0xf6, 0xef, 0x39, 0x2b, 0x01, 0x25, 0x57, 0x21, + 0xcf, 0x20, 0x85, 0xfa, 0xfd, 0xb6, 0xf7, 0xb0, 0x49, 0x1b, 0xfb, 0x94, 0x3f, 0x58, 0x99, 0xb3, + 0x7a, 0xe0, 0xe7, 0xdf, 0x82, 0xc9, 0x4f, 0x98, 0x01, 0xda, 0xfc, 0xe7, 0x06, 0x2c, 0x60, 0xa3, + 0x15, 0x86, 0xe8, 0x55, 0xfd, 0x5c, 0xdc, 0x0b, 0x4a, 0x52, 0x54, 0x87, 0x81, 0xf4, 0x6d, 0x48, + 0xd4, 0x3b, 0xf1, 0x66, 0x3d, 0x33, 0xc4, 0x66, 0xbd, 0x76, 0x92, 0x17, 0xae, 0x86, 0xf4, 0x35, + 0xf0, 0xd7, 0x46, 0xe3, 0xa1, 0x34, 0xff, 0x6a, 0x06, 0xc6, 0x2d, 0x8a, 0x4f, 0xff, 0x90, 0x4b, + 0x30, 0xbe, 0xee, 0x85, 0x34, 0xb8, 0xa7, 0xbd, 0xf3, 0xd4, 0x66, 0x20, 0xbb, 0xd5, 0xb0, 0x64, + 0x21, 0x13, 0xe4, 0xaa, 0xef, 0x35, 0xba, 0xf5, 0x50, 0x15, 0xe4, 0x0e, 0x07, 0x59, 0xb2, 0x8c, + 0xbc, 0x0e, 0x13, 0x82, 0x73, 0x74, 0xa4, 0x81, 0x31, 0x75, 0x3e, 0x8d, 0x9e, 0x8e, 0x8a, 0x11, + 0xd0, 0x5c, 0xe3, 0x6b, 0xe7, 0x88, 0x62, 0xae, 0xf5, 0x2c, 0x87, 0xd2, 0x0a, 0x1d, 0x1d, 0x60, + 0x85, 0x7e, 0x0e, 0xc6, 0x0a, 0x41, 0x40, 0x43, 0x79, 0xf1, 0x70, 0x2a, 0xca, 0x53, 0x10, 0xd0, + 0x90, 0x33, 0x76, 0xb0, 0xdc, 0x12, 0x78, 0xe6, 0x9f, 0x67, 0x60, 0x14, 0xff, 0xc4, 0x63, 0x1c, + 0xbf, 0x7e, 0xa0, 0x1d, 0xe3, 0xf8, 0xf5, 0x03, 0x0b, 0xa1, 0xe4, 0x06, 0x6e, 0x21, 0x65, 0xfe, + 0x5e, 0xf1, 0xf5, 0xe8, 0x1b, 0x6d, 0xc4, 0x60, 0x4b, 0xc5, 0x89, 0xce, 0xb7, 0xb2, 0xa9, 0xd7, + 0x8d, 0xcf, 0x40, 0x66, 0xa3, 0x26, 0xbe, 0x18, 0xd3, 0x10, 0x78, 0x81, 0x95, 0xd9, 0xa8, 0x61, + 0x6f, 0xac, 0x16, 0x96, 0xdf, 0xbc, 0xa5, 0x3e, 0x49, 0x16, 0x1c, 0x38, 0xcb, 0x6f, 0xde, 0xb2, + 0x44, 0x09, 0xeb, 0x5f, 0x6c, 0x73, 0xcd, 0xfd, 0x36, 0x15, 0x77, 0xf5, 0xb0, 0x7f, 0xf1, 0xdb, + 0xec, 0xc0, 0xfd, 0x36, 0xb5, 0x62, 0x04, 0xb2, 0x0c, 0x93, 0xe2, 0x7a, 0x26, 0xe2, 0x2b, 0xd7, + 0x27, 0xc5, 0xf5, 0x4d, 0x4e, 0xa1, 0x22, 0xf1, 0x63, 0x0a, 0x31, 0x40, 0xf2, 0x95, 0x11, 0x71, + 0x4c, 0x21, 0x87, 0x30, 0xb0, 0x14, 0x94, 0xf8, 0xaa, 0x61, 0x7c, 0x07, 0x4f, 0xbd, 0x6a, 0x88, + 0x69, 0xee, 0x22, 0x04, 0xf3, 0xb7, 0x33, 0x90, 0xab, 0x36, 0xbb, 0xfb, 0x6e, 0x7b, 0xfb, 0xc6, + 0xbf, 0xd2, 0xc7, 0x6e, 0xdf, 0x00, 0x54, 0xfe, 0xc2, 0x2e, 0x96, 0x9e, 0x46, 0xde, 0x34, 0xb1, + 0x04, 0x73, 0x12, 0x44, 0x23, 0x37, 0x41, 0x08, 0xa6, 0x78, 0x25, 0xe9, 0xb4, 0x4e, 0xc0, 0xdf, + 0x07, 0x90, 0x24, 0x02, 0x95, 0xbc, 0x03, 0x93, 0xf1, 0x3b, 0xac, 0xf1, 0xe3, 0x47, 0x2a, 0x65, + 0x31, 0x2e, 0xdf, 0xbe, 0x61, 0xa9, 0xe8, 0xe6, 0x1f, 0x18, 0x30, 0xa5, 0xb6, 0x87, 0x58, 0x30, + 0x1f, 0x34, 0xd9, 0x86, 0x50, 0x44, 0x4d, 0x74, 0xb0, 0x50, 0x2c, 0x93, 0x17, 0xf5, 0x06, 0x31, + 0x3c, 0x1e, 0x42, 0x51, 0xa3, 0x61, 0xe8, 0xb6, 0xf7, 0x83, 0xd5, 0x53, 0xd6, 0x5c, 0x10, 0x83, + 0x39, 0x1e, 0x29, 0x40, 0xce, 0xeb, 0x04, 0xfb, 0xb4, 0xed, 0x4a, 0x47, 0xf8, 0x4b, 0x1a, 0xa3, + 0x0d, 0x51, 0xd8, 0xc3, 0x2b, 0x22, 0xfb, 0xd2, 0xc8, 0xff, 0xfa, 0x9b, 0x4b, 0xc6, 0x0a, 0x40, + 0x2e, 0x10, 0xa5, 0xe6, 0x1a, 0x9c, 0xeb, 0xdb, 0x0c, 0x72, 0x05, 0xf2, 0x7b, 0x8e, 0xd8, 0xd9, + 0xd6, 0x0f, 0x9c, 0x76, 0x9b, 0x36, 0x85, 0x00, 0xcc, 0x4a, 0x78, 0x91, 0x83, 0x39, 0x67, 0xf3, + 0x37, 0x0d, 0xb8, 0x30, 0xa8, 0x31, 0x4c, 0x8c, 0x9c, 0xe8, 0x18, 0xd6, 0xc2, 0xbf, 0xc9, 0x79, + 0xc8, 0x75, 0x7c, 0xd7, 0xc3, 0x05, 0x8e, 0x8b, 0x51, 0xf4, 0x9b, 0x3c, 0x0f, 0xc0, 0x35, 0x76, + 0xe8, 0xec, 0x8b, 0x48, 0x48, 0x6b, 0x02, 0x21, 0x9b, 0xce, 0x7e, 0x40, 0x5e, 0x83, 0xb9, 0x06, + 0xdd, 0x73, 0xba, 0xcd, 0xd0, 0x0e, 0xea, 0x07, 0xb4, 0xd1, 0x8d, 0x1e, 0xf6, 0xb7, 0xf2, 0xa2, + 0xa0, 0x26, 0xe1, 0xa2, 0x89, 0xbf, 0x1e, 0x35, 0x71, 0xc5, 0xf3, 0xc2, 0x20, 0xf4, 0x9d, 0x8e, + 0x36, 0xb0, 0xa4, 0x05, 0xe7, 0x3c, 0xa7, 0x1b, 0x1e, 0x2c, 0xe3, 0x03, 0xf3, 0x9e, 0x2f, 0x03, + 0x69, 0xeb, 0xf2, 0xb8, 0x65, 0x72, 0xf9, 0xba, 0xde, 0xef, 0x05, 0x86, 0x5d, 0x50, 0x91, 0x8b, + 0x5e, 0x83, 0x2a, 0x5c, 0x57, 0x4f, 0x59, 0x67, 0x39, 0xcf, 0x1e, 0xac, 0x95, 0x69, 0x4d, 0xf0, + 0xcc, 0x9f, 0x86, 0xcb, 0xc3, 0x72, 0xc5, 0x37, 0x79, 0xd3, 0x9b, 0x38, 0x61, 0xcd, 0x39, 0x49, + 0x4a, 0xfe, 0x26, 0x6f, 0x74, 0xcb, 0xd6, 0x15, 0x7d, 0x3d, 0x29, 0x61, 0x5b, 0xbe, 0x6b, 0xbe, + 0x0b, 0x33, 0xfa, 0x2c, 0x21, 0xaf, 0xc1, 0x48, 0xc4, 0x75, 0x26, 0xb2, 0xd2, 0x54, 0x24, 0xc6, + 0xdb, 0x42, 0x24, 0xf3, 0x67, 0x0d, 0x98, 0x4f, 0x99, 0x2b, 0xe4, 0xab, 0x30, 0x2f, 0xbb, 0x94, + 0x4f, 0x07, 0x3c, 0xd8, 0x14, 0x9d, 0x79, 0x25, 0xad, 0x33, 0x11, 0x0d, 0x4f, 0x3d, 0xf5, 0x6e, + 0x9c, 0x13, 0xdd, 0x18, 0x97, 0x27, 0x3b, 0xf0, 0x1f, 0x18, 0x60, 0x1e, 0xcf, 0x8a, 0x75, 0x46, + 0x4f, 0x5b, 0x26, 0xac, 0x49, 0x27, 0xc6, 0x26, 0x2f, 0xc1, 0xb4, 0x4f, 0xf7, 0x7c, 0x1a, 0x1c, + 0x08, 0x1c, 0xde, 0x61, 0x53, 0x02, 0xc8, 0x91, 0xde, 0x03, 0x19, 0x58, 0x3e, 0x84, 0x39, 0xa8, + 0xbc, 0xbb, 0x2d, 0x88, 0xcc, 0xb7, 0xa4, 0xe2, 0x58, 0x73, 0x83, 0x70, 0xfb, 0x06, 0xb9, 0x02, + 0xe3, 0x5c, 0x57, 0xc8, 0x5d, 0xc1, 0xac, 0xd6, 0x3d, 0xdb, 0x37, 0x2c, 0x59, 0x6e, 0xfe, 0xb6, + 0xb8, 0xf7, 0x54, 0x69, 0x54, 0x13, 0x99, 0x9a, 0x9e, 0x74, 0xf3, 0x53, 0xd6, 0x4c, 0xf3, 0x97, + 0x94, 0xc4, 0x23, 0xbd, 0x75, 0xf5, 0xdf, 0x03, 0x29, 0xb1, 0x5d, 0x7f, 0xc3, 0x80, 0x0b, 0x83, + 0xc8, 0x53, 0x93, 0x4a, 0x19, 0x27, 0x4b, 0x2a, 0x75, 0x05, 0x72, 0x1c, 0xa6, 0x67, 0x65, 0x15, + 0xa4, 0x6e, 0xc3, 0x8a, 0x8a, 0xcd, 0x02, 0x40, 0xa5, 0x51, 0xdd, 0xe8, 0xf0, 0xdb, 0x57, 0x37, + 0x61, 0x84, 0xb5, 0x2d, 0xd1, 0x51, 0xac, 0xa9, 0x85, 0x7b, 0x6b, 0x02, 0x89, 0x5b, 0x06, 0x81, + 0xd3, 0x6a, 0x5a, 0x88, 0x6c, 0xee, 0xc0, 0x8c, 0x8e, 0x41, 0xca, 0x7a, 0x00, 0x70, 0xfc, 0x90, + 0xc4, 0x8a, 0xe7, 0xf1, 0xfd, 0xd5, 0xca, 0xb9, 0x1f, 0x1f, 0x2d, 0x01, 0xfb, 0xc9, 0x69, 0xd2, + 0x02, 0x84, 0xcd, 0xef, 0x67, 0x60, 0x21, 0x3e, 0x64, 0x90, 0xc3, 0xf5, 0xcc, 0xfa, 0x0c, 0x0b, + 0x9a, 0x4f, 0x6b, 0xa9, 0x27, 0x57, 0xbb, 0xfc, 0xc0, 0x01, 0x5b, 0xe9, 0x3b, 0xb0, 0xd8, 0x0f, + 0x9f, 0xbc, 0xd6, 0x93, 0x4d, 0x59, 0x04, 0xc3, 0x44, 0x69, 0x97, 0x95, 0xe4, 0xca, 0x9b, 0xfc, + 0x59, 0x33, 0xbc, 0x06, 0xf0, 0x84, 0x53, 0x44, 0x91, 0xed, 0x7f, 0xd3, 0x80, 0x85, 0x8d, 0xfb, + 0xa1, 0x53, 0x69, 0x75, 0x3c, 0x3f, 0xb4, 0xba, 0x4d, 0xd9, 0xb6, 0xcb, 0x90, 0xab, 0xca, 0x75, + 0x8d, 0x7b, 0x8b, 0xd1, 0x60, 0x97, 0x6b, 0x9b, 0x15, 0x95, 0x92, 0x55, 0xc8, 0x89, 0x88, 0x69, + 0xf9, 0xc8, 0x8f, 0xf4, 0x05, 0xe8, 0x8c, 0x05, 0x12, 0xeb, 0x27, 0xec, 0x6e, 0x41, 0x63, 0x45, + 0xd4, 0xe6, 0x9f, 0x1b, 0x70, 0xb6, 0x0f, 0x0d, 0x79, 0x17, 0x46, 0xd1, 0x1d, 0x20, 0x14, 0xcb, + 0x85, 0x3e, 0x55, 0x84, 0xf5, 0x83, 0xed, 0x1b, 0xdc, 0x49, 0xd5, 0x62, 0x3f, 0x2c, 0x4e, 0x45, + 0xbe, 0x0a, 0x13, 0x85, 0x46, 0x43, 0x7b, 0x8a, 0xe8, 0x8d, 0xc1, 0xad, 0xbc, 0x16, 0xe1, 0xf3, + 0x37, 0x5d, 0xb8, 0xf9, 0xdb, 0x68, 0x88, 0xb7, 0x5c, 0xac, 0x98, 0xdf, 0xf9, 0x77, 0x60, 0x46, + 0x47, 0x3e, 0xd1, 0x1e, 0xf0, 0x87, 0x06, 0xe4, 0xf5, 0x36, 0x7c, 0x3a, 0xa7, 0xf2, 0x69, 0xc3, + 0x7c, 0x8c, 0xe6, 0xfb, 0x0e, 0x9c, 0x4e, 0xed, 0x60, 0xf2, 0x06, 0x8c, 0x15, 0x3a, 0x1d, 0x66, + 0xb8, 0x1b, 0xf1, 0x51, 0xaf, 0xd3, 0xe9, 0x24, 0xc2, 0xf8, 0x04, 0x12, 0xb9, 0x09, 0x39, 0x94, + 0x5b, 0x46, 0x90, 0x89, 0x23, 0xff, 0xf0, 0xa2, 0x4b, 0x32, 0xf2, 0x4f, 0x22, 0x46, 0xfd, 0x52, + 0x08, 0x02, 0x77, 0xbf, 0xcd, 0x76, 0xc5, 0x9f, 0x5e, 0xbf, 0xc4, 0x75, 0x0c, 0xd5, 0x2f, 0xdf, + 0xe6, 0x93, 0x26, 0x49, 0x75, 0x4c, 0x52, 0xfb, 0x12, 0x8c, 0x17, 0x78, 0xac, 0xa9, 0x90, 0xc0, + 0xe7, 0x53, 0x5b, 0xc0, 0x71, 0xb6, 0x6f, 0x70, 0x95, 0xe6, 0x70, 0x0a, 0x4b, 0x92, 0x9a, 0xff, + 0x47, 0x06, 0xce, 0xa4, 0x13, 0x90, 0xaf, 0x45, 0x7b, 0x07, 0x6e, 0xf0, 0x7c, 0x71, 0x20, 0xff, + 0x54, 0x30, 0xb7, 0x8a, 0xf4, 0x0d, 0xb2, 0xd8, 0x64, 0xdc, 0x01, 0x11, 0xbb, 0x9f, 0x58, 0x59, + 0xd3, 0xd8, 0xf0, 0x98, 0xf5, 0xed, 0x1b, 0xc2, 0x63, 0x2e, 0x42, 0xfe, 0xf9, 0xff, 0xe6, 0xef, + 0x1b, 0x70, 0xbe, 0x7f, 0xdd, 0x64, 0x12, 0xc6, 0x85, 0x97, 0x98, 0x7b, 0x4d, 0xab, 0xe5, 0xf5, + 0x52, 0x65, 0xfd, 0x4e, 0xde, 0x20, 0x33, 0x00, 0xc2, 0x85, 0x7a, 0x7b, 0x6b, 0x2d, 0x9f, 0x51, + 0xbc, 0xa8, 0x59, 0x56, 0x56, 0x5c, 0x2b, 0x17, 0xd6, 0xcb, 0x25, 0x7b, 0xab, 0xca, 0x7d, 0xc4, + 0xf8, 0x7b, 0xab, 0x6a, 0x0b, 0x9c, 0x51, 0x86, 0x53, 0xb5, 0x36, 0x18, 0x3d, 0xe3, 0x37, 0x46, + 0xe6, 0x61, 0x56, 0xe2, 0xc8, 0x4a, 0xc6, 0xc9, 0x19, 0x20, 0x11, 0x30, 0x46, 0xce, 0x99, 0x7f, + 0x66, 0xc0, 0x85, 0x41, 0x9f, 0x4a, 0xbe, 0x01, 0xe8, 0xae, 0x17, 0x7d, 0xbf, 0x32, 0x44, 0xef, + 0x0c, 0x28, 0x4c, 0x1e, 0x01, 0x84, 0xdc, 0xb5, 0x9c, 0x71, 0xe5, 0x0a, 0x19, 0xe5, 0x15, 0x74, + 0x1b, 0x66, 0x05, 0x5e, 0x18, 0xcc, 0x49, 0xef, 0xd3, 0x59, 0x98, 0x2c, 0x54, 0xab, 0x6b, 0x95, + 0x62, 0x61, 0xb3, 0xb2, 0xb1, 0x9e, 0x37, 0xc8, 0x04, 0x8c, 0xde, 0xb1, 0x36, 0xb6, 0xaa, 0xf9, + 0x8c, 0xf9, 0x37, 0x0d, 0x98, 0xae, 0xb4, 0x43, 0xba, 0xcf, 0xf3, 0x3a, 0x3c, 0xe9, 0x8c, 0xfb, + 0x92, 0x36, 0xe3, 0x16, 0xa3, 0xa3, 0xae, 0xa8, 0x82, 0xa1, 0xa6, 0xdb, 0x7f, 0x9d, 0x81, 0xb9, + 0x1e, 0x1a, 0x52, 0x83, 0xf1, 0xc2, 0x4e, 0x6d, 0xa3, 0x52, 0x2a, 0x8a, 0x96, 0xc9, 0xf5, 0x59, + 0x40, 0x7b, 0x6b, 0xe1, 0x3e, 0xb4, 0x87, 0x81, 0xed, 0xb9, 0x0d, 0x25, 0xcd, 0xcb, 0xea, 0x29, + 0x4b, 0x72, 0x22, 0x2b, 0xd1, 0x14, 0xca, 0x68, 0x27, 0x36, 0x3d, 0xcc, 0x34, 0x88, 0x36, 0x51, + 0xcc, 0x1f, 0x18, 0x7a, 0x73, 0xb9, 0x58, 0x9b, 0xf0, 0x42, 0x65, 0x7d, 0xb3, 0x7c, 0xc7, 0xc2, + 0x5e, 0xb7, 0x53, 0xcf, 0x08, 0x9e, 0x87, 0x73, 0x29, 0x38, 0xd5, 0xc2, 0x56, 0xad, 0x5c, 0xca, + 0x1b, 0xe4, 0x05, 0x38, 0x9f, 0x52, 0x6c, 0x6d, 0xad, 0xaf, 0x33, 0x11, 0xcd, 0x90, 0x0b, 0xb0, + 0x98, 0x52, 0xce, 0x8f, 0x5a, 0xb2, 0x6c, 0xab, 0x21, 0xac, 0x27, 0x34, 0x4c, 0xd6, 0x60, 0xb1, + 0x5f, 0x47, 0x31, 0x7b, 0xcc, 0xf2, 0x9a, 0xb4, 0x60, 0xad, 0xab, 0xde, 0x47, 0xbc, 0x06, 0xe5, + 0xf8, 0xed, 0xe4, 0x35, 0xa8, 0x82, 0xb5, 0x6e, 0xfe, 0x9d, 0x0c, 0x9c, 0x61, 0x63, 0xde, 0xa4, + 0x41, 0xc0, 0x76, 0x2e, 0xcc, 0x70, 0x15, 0x8f, 0x35, 0x7d, 0x01, 0xc6, 0x0e, 0x86, 0x10, 0xa0, + 0x9c, 0x14, 0x20, 0x4b, 0xa0, 0xb3, 0xed, 0x76, 0x37, 0x3a, 0xfd, 0xb7, 0xf0, 0x6f, 0xb6, 0xa5, + 0x8e, 0x53, 0x4b, 0xf1, 0x47, 0xae, 0xad, 0x89, 0x4e, 0x94, 0x60, 0xea, 0x8b, 0x30, 0x8a, 0x11, + 0x5b, 0x68, 0xec, 0xcd, 0x44, 0x11, 0xf5, 0xe9, 0x2d, 0xc3, 0x50, 0x2e, 0x8b, 0x13, 0x90, 0xeb, + 0x00, 0xf1, 0x35, 0x15, 0x61, 0xf0, 0x49, 0x1b, 0x38, 0xba, 0xa9, 0x62, 0x4d, 0xb4, 0xf6, 0x1c, + 0x71, 0x69, 0xe5, 0x2a, 0xcc, 0xc9, 0x1c, 0x6c, 0x1d, 0x19, 0x72, 0xc4, 0xa3, 0xab, 0xac, 0x59, + 0x5e, 0x50, 0xe9, 0x88, 0xb0, 0xa3, 0xab, 0xef, 0xc3, 0xac, 0x3c, 0xdb, 0xd8, 0x5c, 0xab, 0x61, + 0x28, 0xc2, 0x2c, 0x4c, 0x6e, 0x97, 0xad, 0xca, 0xed, 0x8f, 0xed, 0xdb, 0x5b, 0x6b, 0x6b, 0xf9, + 0x53, 0x64, 0x1a, 0x26, 0x04, 0xa0, 0x58, 0xc8, 0x1b, 0x64, 0x0a, 0x72, 0x95, 0xf5, 0x5a, 0xb9, + 0xb8, 0x65, 0x95, 0xf3, 0x99, 0xab, 0xcb, 0x30, 0x13, 0x67, 0xed, 0xc3, 0x19, 0x3d, 0x0e, 0x59, + 0xab, 0xb0, 0x93, 0x3f, 0xc5, 0x94, 0x60, 0xf5, 0x6e, 0xb1, 0x76, 0xe3, 0x46, 0xde, 0x60, 0xd3, + 0xfc, 0x4e, 0xb1, 0x6a, 0xdf, 0xbd, 0x57, 0xcb, 0x67, 0xae, 0x7e, 0x0e, 0xe6, 0x30, 0x8a, 0x9e, + 0xed, 0xcd, 0x68, 0x9b, 0xfa, 0x58, 0xed, 0x14, 0xe4, 0x6a, 0xb4, 0xe3, 0xf8, 0x4e, 0x48, 0x79, + 0x9d, 0xf7, 0xba, 0xcd, 0xd0, 0xed, 0x34, 0xe9, 0xa3, 0xbc, 0x71, 0xf5, 0x2d, 0x98, 0xb5, 0xbc, + 0x6e, 0xe8, 0xb6, 0xf7, 0x6b, 0x21, 0xc3, 0xd8, 0x3f, 0x24, 0xa7, 0x61, 0x6e, 0x6b, 0xbd, 0x70, + 0x6f, 0xa5, 0x72, 0x67, 0x6b, 0x63, 0xab, 0x66, 0xdf, 0x2b, 0x6c, 0x16, 0x57, 0xb9, 0x0a, 0xb9, + 0xb7, 0x51, 0xdb, 0xb4, 0xad, 0x72, 0xb1, 0xbc, 0xbe, 0x99, 0x37, 0xae, 0xfe, 0xbc, 0xc1, 0xdf, + 0xec, 0xc5, 0x7d, 0xe5, 0x16, 0x9e, 0x35, 0x5c, 0x84, 0x0b, 0x5b, 0xb5, 0xb2, 0x65, 0x6f, 0x6e, + 0xdc, 0x2d, 0xaf, 0xdb, 0x5b, 0xb5, 0xc2, 0x9d, 0xe4, 0x79, 0xe0, 0x12, 0x3c, 0xa7, 0x60, 0x58, + 0xe5, 0xe2, 0xc6, 0x76, 0xd9, 0xb2, 0xab, 0x85, 0x5a, 0x6d, 0x67, 0xc3, 0x62, 0x02, 0x7f, 0x1e, + 0xce, 0xa4, 0x20, 0xdc, 0xbb, 0x5d, 0xc8, 0x67, 0x7a, 0xca, 0xd6, 0xcb, 0x3b, 0x85, 0x35, 0x7b, + 0x65, 0x63, 0x33, 0x9f, 0xbd, 0x8a, 0x4f, 0xbf, 0xe1, 0x21, 0x00, 0x0f, 0xd7, 0xcb, 0xc1, 0xc8, + 0xfa, 0xc6, 0x7a, 0x39, 0xb9, 0x9e, 0x4c, 0x41, 0xae, 0x50, 0xad, 0x5a, 0x1b, 0xdb, 0xe5, 0x12, + 0x5f, 0x4d, 0x4a, 0xe5, 0x75, 0xd6, 0xb2, 0xec, 0x55, 0x13, 0xe6, 0x8a, 0xd4, 0x0f, 0xcb, 0x8f, + 0x42, 0xda, 0x66, 0x1b, 0x09, 0xec, 0xbb, 0x69, 0x98, 0x28, 0x7f, 0x79, 0xb3, 0xbc, 0x5e, 0x63, + 0x5a, 0xf3, 0xd4, 0xd5, 0x0b, 0x09, 0x1c, 0x39, 0x2c, 0xb5, 0xda, 0x6a, 0xfe, 0xd4, 0xd5, 0xaf, + 0xc1, 0x94, 0x16, 0x8a, 0x7a, 0x16, 0xe6, 0xd5, 0xdf, 0x55, 0xda, 0x6e, 0xb8, 0xed, 0xfd, 0xfc, + 0xa9, 0x64, 0x81, 0xd5, 0x6d, 0xb7, 0x59, 0x01, 0x7e, 0xbc, 0x5a, 0xb0, 0x49, 0xfd, 0x96, 0xdb, + 0x76, 0x42, 0xda, 0xc8, 0x67, 0xae, 0x5e, 0x83, 0x69, 0xed, 0xfc, 0x84, 0xd5, 0xbb, 0xb6, 0x21, + 0xc4, 0xe1, 0x5e, 0xb9, 0x54, 0xd9, 0xba, 0x97, 0x1f, 0x65, 0x9f, 0xbd, 0x5a, 0xb9, 0xb3, 0x9a, + 0x87, 0xab, 0x5f, 0x83, 0x19, 0x11, 0x90, 0x73, 0xef, 0x76, 0x41, 0x36, 0x74, 0xe3, 0xf6, 0x6d, + 0x71, 0x2e, 0xc9, 0x16, 0x39, 0x5c, 0x09, 0x2e, 0xc0, 0xa2, 0xf8, 0x61, 0x17, 0xd6, 0x4b, 0xf6, + 0x6a, 0xc1, 0x2a, 0xed, 0x14, 0xac, 0xb2, 0x7d, 0xb7, 0xfc, 0x71, 0x3e, 0xc3, 0x96, 0x46, 0x15, + 0x62, 0x6f, 0x6e, 0x6c, 0x15, 0x57, 0xf3, 0xd9, 0xab, 0x2e, 0xe4, 0x93, 0x3e, 0x95, 0x9e, 0x55, + 0x5c, 0x6a, 0x29, 0x83, 0xc9, 0xce, 0xc6, 0xe6, 0x6a, 0xd9, 0x12, 0x8a, 0x09, 0x0f, 0x7d, 0xb7, + 0xd6, 0x0b, 0x5b, 0x9b, 0xab, 0x1b, 0x56, 0xe5, 0x2b, 0xb8, 0x98, 0x2f, 0xc2, 0x42, 0x6d, 0xad, + 0x50, 0xbc, 0x6b, 0xaf, 0x6f, 0x6c, 0xda, 0x95, 0x75, 0xbb, 0xb8, 0x5a, 0x58, 0x5f, 0x2f, 0xaf, + 0xe5, 0xe1, 0xea, 0x7f, 0x61, 0xc0, 0x73, 0x03, 0x66, 0x33, 0x79, 0x03, 0xae, 0xac, 0x96, 0x0b, + 0xa5, 0xb5, 0x72, 0xad, 0x66, 0x33, 0x96, 0xe5, 0xf5, 0x4d, 0xb1, 0xce, 0xa1, 0x3a, 0x4c, 0x4a, + 0xe0, 0x15, 0x78, 0x65, 0x30, 0x7a, 0x2c, 0x2c, 0x97, 0xe1, 0xe5, 0xc1, 0xa8, 0x42, 0x78, 0x32, + 0xe4, 0x2a, 0x5c, 0x1a, 0x8c, 0x19, 0x09, 0x5d, 0x76, 0xe5, 0xdd, 0x1f, 0xfd, 0xc9, 0x0b, 0xa7, + 0x7e, 0xf4, 0xa7, 0x2f, 0x18, 0x7f, 0xfc, 0xa7, 0x2f, 0x18, 0xff, 0xd3, 0x9f, 0xbe, 0x60, 0x7c, + 0xe5, 0xb5, 0x13, 0xdc, 0x9f, 0xdf, 0x1d, 0x43, 0x3f, 0xcd, 0xcd, 0xff, 0x37, 0x00, 0x00, 0xff, + 0xff, 0x23, 0xe0, 0xaf, 0xb6, 0x78, 0x2a, 0x01, 0x00, } func (this *PluginSpecV1) Equal(that interface{}) bool { @@ -32511,6 +32727,149 @@ func (m *OktaAssignmentActionTargetV1) MarshalToSizedBuffer(dAtA []byte) (int, e return len(dAtA) - i, nil } +func (m *IntegrationV1) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *IntegrationV1) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *IntegrationV1) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + { + size, err := m.Spec.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTypes(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + { + size, err := m.ResourceHeader.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTypes(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *IntegrationSpecV1) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *IntegrationSpecV1) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *IntegrationSpecV1) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if m.Status != 0 { + i = encodeVarintTypes(dAtA, i, uint64(m.Status)) + i-- + dAtA[i] = 0x10 + } + if m.SubKindSpec != nil { + { + size := m.SubKindSpec.Size() + i -= size + if _, err := m.SubKindSpec.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + } + } + return len(dAtA) - i, nil +} + +func (m *IntegrationSpecV1_AWSOIDC) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *IntegrationSpecV1_AWSOIDC) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + if m.AWSOIDC != nil { + { + size, err := m.AWSOIDC.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTypes(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} +func (m *AWSOIDCIntegrationSpecV1) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *AWSOIDCIntegrationSpecV1) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *AWSOIDCIntegrationSpecV1) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.XXX_unrecognized != nil { + i -= len(m.XXX_unrecognized) + copy(dAtA[i:], m.XXX_unrecognized) + } + if len(m.RoleARN) > 0 { + i -= len(m.RoleARN) + copy(dAtA[i:], m.RoleARN) + i = encodeVarintTypes(dAtA, i, uint64(len(m.RoleARN))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *HeadlessAuthentication) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -40116,6 +40475,68 @@ func (m *OktaAssignmentActionTargetV1) Size() (n int) { return n } +func (m *IntegrationV1) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ResourceHeader.Size() + n += 1 + l + sovTypes(uint64(l)) + l = m.Spec.Size() + n += 1 + l + sovTypes(uint64(l)) + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *IntegrationSpecV1) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.SubKindSpec != nil { + n += m.SubKindSpec.Size() + } + if m.Status != 0 { + n += 1 + sovTypes(uint64(m.Status)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *IntegrationSpecV1_AWSOIDC) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.AWSOIDC != nil { + l = m.AWSOIDC.Size() + n += 1 + l + sovTypes(uint64(l)) + } + return n +} +func (m *AWSOIDCIntegrationSpecV1) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.RoleARN) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + func (m *HeadlessAuthentication) Size() (n int) { if m == nil { return 0 @@ -89947,6 +90368,311 @@ func (m *OktaAssignmentActionTargetV1) Unmarshal(dAtA []byte) error { } return nil } +func (m *IntegrationV1) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: IntegrationV1: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: IntegrationV1: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ResourceHeader", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ResourceHeader.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Spec", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Spec.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTypes(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTypes + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *IntegrationSpecV1) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: IntegrationSpecV1: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: IntegrationSpecV1: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AWSOIDC", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &AWSOIDCIntegrationSpecV1{} + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.SubKindSpec = &IntegrationSpecV1_AWSOIDC{v} + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + } + m.Status = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Status |= IntegrationSpecV1_IntegrationStatus(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipTypes(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTypes + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *AWSOIDCIntegrationSpecV1) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: AWSOIDCIntegrationSpecV1: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AWSOIDCIntegrationSpecV1: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RoleARN", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RoleARN = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTypes(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTypes + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *HeadlessAuthentication) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/api/types/user.go b/api/types/user.go index 78d5573701432..48ec73908a1ce 100644 --- a/api/types/user.go +++ b/api/types/user.go @@ -26,6 +26,16 @@ import ( "github.com/gravitational/teleport/api/utils" ) +// UserType is the user's types that indicates where it was created. +type UserType string + +const ( + // UserTypeSSO identifies a user that was created from an SSO provider. + UserTypeSSO UserType = "sso" + // UserTypeLocal identifies a user that was created in Teleport itself and has no connection to an external identity. + UserTypeLocal UserType = "local" +) + // User represents teleport embedded user or external user. type User interface { // ResourceWithSecrets provides common resource properties @@ -99,6 +109,8 @@ type User interface { GetCreatedBy() CreatedBy // SetCreatedBy sets created by information SetCreatedBy(CreatedBy) + // GetUserType indicates if the User was created by an SSO Provider or locally. + GetUserType() UserType // GetTraits gets the trait map for this user used to populate role variables. GetTraits() map[string][]string // SetTraits sets the trait map for this user used to populate role variables. @@ -404,6 +416,15 @@ func (u UserV2) GetGCPServiceAccounts() []string { return u.getTrait(constants.TraitGCPServiceAccounts) } +// GetUserType indicates if the User was created by an SSO Provider or locally. +func (u UserV2) GetUserType() UserType { + if u.GetCreatedBy().Connector == nil { + return UserTypeLocal + } + + return UserTypeSSO +} + func (u *UserV2) String() string { return fmt.Sprintf("User(name=%v, roles=%v, identities=%v)", u.Metadata.Name, u.Spec.Roles, u.Spec.OIDCIdentities) } diff --git a/api/utils/grpc/stream/stream.go b/api/utils/grpc/stream/stream.go index bd9c40e31c954..54021755d273d 100644 --- a/api/utils/grpc/stream/stream.go +++ b/api/utils/grpc/stream/stream.go @@ -113,7 +113,7 @@ func (c *ReadWriter) Write(b []byte) (int, error) { } if err := c.source.Send(chunk); err != nil { - return sent, trace.ConnectionProblem(trail.FromGRPC(err), "failed to send on source") + return sent, trace.ConnectionProblem(trail.FromGRPC(err), "failed to send on source: %v", err) } sent += len(chunk) diff --git a/api/utils/keypaths/keypaths.go b/api/utils/keypaths/keypaths.go index 9c1fbd96f1eec..763e49e21338f 100644 --- a/api/utils/keypaths/keypaths.go +++ b/api/utils/keypaths/keypaths.go @@ -62,6 +62,8 @@ const ( profileFileExt = ".yaml" // fileLocalCA is the filename where a self-signed localhost CA cert is stored. fileLocalCA = "localca.pem" + // oracleWalletDirSuffix is the suffix of the oracle wallet database directory. + oracleWalletDirSuffix = "-wallet" ) // Here's the file layout of all these keypaths. @@ -90,9 +92,11 @@ const ( // │ ├── foo-db --> App access certs for user "foo" // │ │ ├── root --> App access certs for cluster "root" // │ │ │ ├── dbA-x509.pem --> TLS cert for database service "dbA" -// │ │ │ └── dbB-x509.pem --> TLS cert for database service "dbB" -// │ │ └── leaf --> App access certs for cluster "leaf" -// │ │ └── dbC-x509.pem --> TLS cert for database service "dbC" +// │ │ │ ├── dbB-x509.pem --> TLS cert for database service "dbB" +// │ │ │ └── dbC-wallet --> Oracle Client wallet Configuration directory. +// │ │ ├── leaf --> App access certs for cluster "leaf" +// │ │ │ └── dbC-x509.pem --> TLS cert for database service "dbC" +// │ │ └── proxy-localca.pem --> Self-signed TLS Routing local proxy CA // │ ├── foo-kube --> Kubernetes certs for user "foo" // │ | ├── root --> Kubernetes certs for Teleport cluster "root" // │ | │ ├── kubeA-kubeconfig --> standalone kubeconfig for Kubernetes cluster "kubeA" @@ -278,6 +282,13 @@ func DatabaseCertPath(baseDir, proxy, username, cluster, dbname string) string { return filepath.Join(DatabaseCertDir(baseDir, proxy, username, cluster), dbname+fileExtTLSCert) } +// DatabaseOracleWalletDirectory returns the path to the user's Oracle Wallet configuration directory. +// for the given proxy, cluster and database. +// /keys//-db//dbname-wallet/ +func DatabaseOracleWalletDirectory(baseDir, proxy, username, cluster, dbname string) string { + return filepath.Join(DatabaseCertDir(baseDir, proxy, username, cluster), dbname+oracleWalletDirSuffix) +} + // KubeDir returns the path to the user's kube directory // for the given proxy. // diff --git a/api/utils/pingconn/pingconn.go b/api/utils/pingconn/pingconn.go new file mode 100644 index 0000000000000..1578a1110b90c --- /dev/null +++ b/api/utils/pingconn/pingconn.go @@ -0,0 +1,132 @@ +/* +Copyright 2023 Gravitational, Inc. + +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. +*/ + +package pingconn + +import ( + "crypto/tls" + "encoding/binary" + "math" + "sync" + + "github.com/gravitational/trace" +) + +// New returns a ping connection wrapping the provided net.Conn. +func New(conn *tls.Conn) *PingConn { + return &PingConn{Conn: conn} +} + +// PingConn wraps a *tls.Conn and add ping capabilities to it, including the +// `WritePing` function and `Read` (which excludes ping packets). +// +// When using this connection, the packets written will contain an initial data: +// the packet size. When reading, this information is taken into account, but it +// is not returned to the caller. +// +// Ping messages have a packet size of zero and are produced only when +// `WritePing` is called. On `Read`, any Ping packet is discarded. +type PingConn struct { + //net.Conn + *tls.Conn + + muRead sync.Mutex + muWrite sync.Mutex + + // currentSize size of bytes of the current packet. + currentSize uint32 +} + +// Read reads content from the underlaying connection, discarding any ping +// messages it finds. +func (c *PingConn) Read(p []byte) (int, error) { + c.muRead.Lock() + defer c.muRead.Unlock() + + err := c.discardPingReads() + if err != nil { + return 0, err + } + + // Check if the current size is larger than the provided buffer. + readSize := c.currentSize + if c.currentSize > uint32(len(p)) { + readSize = uint32(len(p)) + } + + n, err := c.Conn.Read(p[:readSize]) + c.currentSize -= uint32(n) + + return n, err +} + +// WritePing writes the ping packet to the connection. +func (c *PingConn) WritePing() error { + c.muWrite.Lock() + defer c.muWrite.Unlock() + + return binary.Write(c.Conn, binary.BigEndian, uint32(0)) +} + +// discardPingReads reads from the wrapped net.Conn until it encounters a +// non-ping packet. +func (c *PingConn) discardPingReads() error { + for c.currentSize == 0 { + err := binary.Read(c.Conn, binary.BigEndian, &c.currentSize) + if err != nil { + return err + } + } + + return nil +} + +// Write writes provided content to the underlying connection with proper +// protocol fields. +func (c *PingConn) Write(p []byte) (int, error) { + c.muWrite.Lock() + defer c.muWrite.Unlock() + + // Avoid overflow when casting data length. It is only present to avoid + // panicking if the size cannot be cast. Callers should handle packet length + // limits, such as protocol implementations and audits. + if uint64(len(p)) > math.MaxUint32 { + return 0, trace.BadParameter("invalid content size, max size permitted is %d", uint64(math.MaxUint32)) + } + + size := uint32(len(p)) + if size == 0 { + return 0, nil + } + + // Write packet size. + if err := binary.Write(c.Conn, binary.BigEndian, size); err != nil { + return 0, trace.Wrap(err) + } + + // Iterate until everything is written. + var written int + for written < len(p) { + n, err := c.Conn.Write(p) + written += n + + if err != nil { + return written, trace.Wrap(err) + } + } + + return written, nil +} diff --git a/lib/srv/alpnproxy/conn_test.go b/api/utils/pingconn/pingconn_test.go similarity index 96% rename from lib/srv/alpnproxy/conn_test.go rename to api/utils/pingconn/pingconn_test.go index d91175d628a22..69a6a32cc5c47 100644 --- a/lib/srv/alpnproxy/conn_test.go +++ b/api/utils/pingconn/pingconn_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package alpnproxy +package pingconn import ( "bytes" @@ -26,6 +26,8 @@ import ( "time" "github.com/stretchr/testify/require" + + "github.com/gravitational/teleport/api/fixtures" ) func TestPingConnection(t *testing.T) { @@ -273,7 +275,7 @@ func makePingConn(t *testing.T) (*PingConn, *PingConn) { writer, reader := net.Pipe() tlsWriter, tlsReader := makeTLSConn(t, writer, reader) - return NewPingConn(tlsWriter), NewPingConn(tlsReader) + return New(tlsWriter), New(tlsReader) } // makeBufferedPingConn creates connections to have asynchronous writes. @@ -321,7 +323,7 @@ func makeBufferedPingConn(t *testing.T) (*PingConn, *PingConn) { } tlsConnA, tlsConnB := makeTLSConn(t, connSlice[0], connSlice[1]) - return NewPingConn(tlsConnA), NewPingConn(tlsConnB) + return New(tlsConnA), New(tlsConnB) } // makeTLSConn take two connections (client and server) and wrap them into TLS @@ -334,10 +336,13 @@ func makeTLSConn(t *testing.T, server, client net.Conn) (*tls.Conn, *tls.Conn) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() + cert, err := tls.X509KeyPair([]byte(fixtures.TLSCACertPEM), []byte(fixtures.TLSCAKeyPEM)) + require.NoError(t, err) + // Server go func() { tlsConn := tls.Server(server, &tls.Config{ - Certificates: []tls.Certificate{mustGenCertSignedWithCA(t, mustGenSelfSignedCert(t))}, + Certificates: []tls.Certificate{cert}, }) tlsConnChan <- struct { *tls.Conn diff --git a/build.assets/Dockerfile b/build.assets/Dockerfile index 58c7635808609..4e99516ba0412 100644 --- a/build.assets/Dockerfile +++ b/build.assets/Dockerfile @@ -38,16 +38,26 @@ RUN git clone --depth=1 https://github.com/PJK/libcbor.git -b v0.10.2 && \ make install && \ make clean +# Install openssl. +# install_sw install only binaries, skips docs. +RUN git clone --depth=1 https://github.com/openssl/openssl.git -b openssl-3.0.8 && \ + cd openssl && \ + [ "$(git rev-parse HEAD)" = '31157bc0b46e04227b8468d3e6915e4d0332777c' ] && \ + ./config --release --libdir=/usr/local/lib && \ + make && \ + make install_sw + # Install libfido2. -# Depends on libcbor, libssl-dev, zlib1g-dev and libudev. -RUN git clone --depth=1 https://github.com/Yubico/libfido2.git -b 1.12.0 && \ +# Depends on libcbor, libcrypto (OpenSSL 3.x), libudev and zlib1g-dev. +RUN git clone --depth=1 https://github.com/Yubico/libfido2.git -b 1.13.0 && \ cd libfido2 && \ - [ "$(git rev-parse HEAD)" = '659a02679f99fd34a44e06e35dce90794f6ecc86' ] && \ + [ "$(git rev-parse HEAD)" = '486a8f8667e42f55cee2bba301b41433cacec830' ] && \ CFLAGS=-pthread cmake \ -DBUILD_EXAMPLES=OFF \ -DBUILD_MANPAGES=OFF \ -DBUILD_TOOLS=OFF \ -DCMAKE_BUILD_TYPE=Release . && \ + grep 'CRYPTO_VERSION:INTERNAL=3\.0\.' CMakeCache.txt && \ make && \ make install && \ make clean @@ -298,16 +308,24 @@ COPY --from=libbpf /opt/libbpf/usr /usr/libbpf-${LIBBPF_VERSION} # Copy libfido2 libraries. # Do this near the end to take better advantage of the multi-stage build. COPY --from=libfido2 /usr/local/include/ /usr/local/include/ +COPY --from=libfido2 /usr/local/lib/engines-3/ /usr/local/lib/engines-3/ +COPY --from=libfido2 /usr/local/lib/ossl-modules/ /usr/local/lib/ossl-modules/ COPY --from=libfido2 /usr/local/lib/pkgconfig/ /usr/local/lib/pkgconfig/ COPY --from=libfido2 \ /usr/local/lib/libcbor.a \ + /usr/local/lib/libcrypto.a \ + /usr/local/lib/libcrypto.so.3 \ /usr/local/lib/libfido2.a \ - /usr/local/lib/libfido2.so.1.12.0 \ + /usr/local/lib/libfido2.so.1.13.0 \ + /usr/local/lib/libssl.a \ + /usr/local/lib/libssl.so.3 \ /usr/local/lib/libudev.a \ /usr/local/lib/ RUN cd /usr/local/lib && \ - ln -s libfido2.so.1.12.0 libfido2.so.1 && \ + ln -s libcrypto.so.3 libcrypto.so && \ + ln -s libfido2.so.1.13.0 libfido2.so.1 && \ ln -s libfido2.so.1 libfido2.so && \ + ln -s libssl.so.3 libssl.so && \ ldconfig COPY pkgconfig/buildbox/ / diff --git a/build.assets/Dockerfile-centos7 b/build.assets/Dockerfile-centos7 index 16b29b078f944..049180bbb63ff 100644 --- a/build.assets/Dockerfile-centos7 +++ b/build.assets/Dockerfile-centos7 @@ -53,6 +53,7 @@ RUN yum groupinstall -y 'Development Tools' && \ ${DEVTOOLSET}-gcc* \ git \ libudev-devel \ + perl-IPC-Cmd \ zlib-devel && \ yum clean all @@ -63,16 +64,6 @@ RUN git clone --depth=1 https://github.com/illiliti/libudev-zero.git -b 1.0.1 && [ "$(git rev-parse HEAD)" = '4154cf252c17297f98a8ca33693ead003b4509da' ] && \ make install-static LIBDIR='$(PREFIX)/lib64' -# Instal openssl. -# Pulled from source because repository versions are too old. -# install_sw install only binaries, skips docs. -RUN git clone --depth=1 https://github.com/openssl/openssl.git -b OpenSSL_1_1_1t && \ - cd openssl && \ - [ "$(git rev-parse HEAD)" = '830bf8e1e4749ad65c51b6a1d0d769ae689404ba' ] && \ - ./config --release --libdir=/usr/local/lib64 && \ - make && \ - make install_sw - # Install libcbor. RUN git clone --depth=1 https://github.com/PJK/libcbor.git -b v0.10.2 && \ cd libcbor && \ @@ -84,18 +75,30 @@ RUN git clone --depth=1 https://github.com/PJK/libcbor.git -b v0.10.2 && \ make && \ make install +# Install openssl. +# install_sw install only binaries, skips docs. +RUN git clone --depth=1 https://github.com/openssl/openssl.git -b openssl-3.0.8 && \ + cd openssl && \ + [ "$(git rev-parse HEAD)" = '31157bc0b46e04227b8468d3e6915e4d0332777c' ] && \ + ./config --release --libdir=/usr/local/lib64 && \ + make && \ + make install_sw +# Necessary for libfido2 to find the correct libcrypto. +ENV PKG_CONFIG_PATH="/usr/local/lib64/pkgconfig" + # Install libfido2. -# Depends on libcbor, openssl, zlib-devel and libudev. +# Depends on libcbor, libcrypto (OpenSSL 3.x), libudev and zlib-devel. # Linked so `make build/tsh` finds the library where it expects it. -RUN git clone --depth=1 https://github.com/Yubico/libfido2.git -b 1.12.0 && \ +RUN git clone --depth=1 https://github.com/Yubico/libfido2.git -b 1.13.0 && \ cd libfido2 && \ - [ "$(git rev-parse HEAD)" = '659a02679f99fd34a44e06e35dce90794f6ecc86' ] && \ + [ "$(git rev-parse HEAD)" = '486a8f8667e42f55cee2bba301b41433cacec830' ] && \ scl enable ${DEVTOOLSET} "\ - cmake3 \ + CFLAGS=-pthread cmake3 \ -DBUILD_EXAMPLES=OFF \ -DBUILD_MANPAGES=OFF \ -DBUILD_TOOLS=OFF \ -DCMAKE_BUILD_TYPE=Release . && \ + grep 'CRYPTO_VERSION:INTERNAL=3\.0\.' CMakeCache.txt && \ make" && \ make install && \ make clean @@ -255,23 +258,25 @@ RUN curl --proto '=https' --tlsv1.2 -fsSL https://sh.rustup.rs | sh -s -- -y --p # Do this last to take better advantage of the multi-stage build. USER root COPY --from=libfido2 /usr/local/include/ /usr/local/include/ +COPY --from=libfido2 /usr/local/lib64/engines-3/ /usr/local/lib64/engines-3/ +COPY --from=libfido2 /usr/local/lib64/ossl-modules/ /usr/local/lib64/ossl-modules/ COPY --from=libfido2 /usr/local/lib64/pkgconfig/ /usr/local/lib64/pkgconfig/ COPY --from=libfido2 \ /usr/local/lib64/libcbor.a \ /usr/local/lib64/libcrypto.a \ - /usr/local/lib64/libcrypto.so.1.1 \ + /usr/local/lib64/libcrypto.so.3 \ /usr/local/lib64/libfido2.a \ - /usr/local/lib64/libfido2.so.1.12.0 \ + /usr/local/lib64/libfido2.so.1.13.0 \ /usr/local/lib64/libssl.a \ - /usr/local/lib64/libssl.so.1.1 \ + /usr/local/lib64/libssl.so.3 \ /usr/local/lib64/libudev.a \ /usr/local/lib64/ # Re-create usual lib64 links. RUN cd /usr/local/lib64 && \ - ln -s libcrypto.so.1.1 libcrypto.so && \ - ln -s libfido2.so.1.12.0 libfido2.so.1 && \ + ln -s libcrypto.so.3 libcrypto.so && \ + ln -s libfido2.so.1.13.0 libfido2.so.1 && \ ln -s libfido2.so.1 libfido2.so && \ - ln -s libssl.so.1.1 libssl.so && \ + ln -s libssl.so.3 libssl.so && \ # Update ld. echo /usr/local/lib64 > /etc/ld.so.conf.d/libfido2.conf && \ ldconfig diff --git a/build.assets/Dockerfile-multiarch b/build.assets/Dockerfile-multiarch index 159169b7a6a48..a0c332fe4cdea 100644 --- a/build.assets/Dockerfile-multiarch +++ b/build.assets/Dockerfile-multiarch @@ -27,7 +27,7 @@ RUN groupadd ci --gid=$GID -o && \ RUN install --directory --mode=0700 --owner=ci --group=ci /var/lib/teleport ## LIBPCSCLITE ################################################################ -# +# FROM gcc AS libpcsclite ARG LIBPCSCLITE_VERSION @@ -55,16 +55,6 @@ RUN git clone --depth=1 https://github.com/illiliti/libudev-zero.git -b 1.0.1 && [ "$(git rev-parse HEAD)" = '4154cf252c17297f98a8ca33693ead003b4509da' ] && \ make install-static LIBDIR='$(PREFIX)/lib64' -# Install openssl. -# Pulled from source because repository versions are too old. -# install_sw install only binaries, skips docs. -RUN git clone --depth=1 https://github.com/openssl/openssl.git -b OpenSSL_1_1_1t && \ - cd openssl && \ - [ "$(git rev-parse HEAD)" = '830bf8e1e4749ad65c51b6a1d0d769ae689404ba' ] && \ - ./config --release --libdir=/usr/local/lib64 && \ - make && \ - make install_sw - # Install libcbor. RUN git clone --depth=1 https://github.com/PJK/libcbor.git -b v0.10.2 && \ cd libcbor && \ @@ -77,18 +67,29 @@ RUN git clone --depth=1 https://github.com/PJK/libcbor.git -b v0.10.2 && \ make && \ make install +# Install openssl. +# install_sw install only binaries, skips docs. +RUN git clone --depth=1 https://github.com/openssl/openssl.git -b openssl-3.0.8 && \ + cd openssl && \ + [ "$(git rev-parse HEAD)" = '31157bc0b46e04227b8468d3e6915e4d0332777c' ] && \ + ./config --release --libdir=/usr/local/lib64 && \ + make && \ + make install_sw +# Necessary for libfido2 to find the correct libcrypto. +ENV PKG_CONFIG_PATH="/usr/local/lib64/pkgconfig" + # Install libfido2. -# Depends on libcbor, openssl, zlib-devel and libudev. -# Linked so `make build/tsh` finds the library where it expects it. -RUN git clone --depth=1 https://github.com/Yubico/libfido2.git -b 1.12.0 && \ +# Depends on libcbor, libcrypto (OpenSSL 3.x), libudev and zlib1g-dev. +RUN git clone --depth=1 https://github.com/Yubico/libfido2.git -b 1.13.0 && \ cd libfido2 && \ - [ "$(git rev-parse HEAD)" = '659a02679f99fd34a44e06e35dce90794f6ecc86' ] && \ - LDFLAGS="-lpthread" cmake3 \ - -DBUILD_EXAMPLES=OFF \ - -DBUILD_MANPAGES=OFF \ - -DBUILD_TOOLS=OFF \ - -DCMAKE_BUILD_TYPE=Release . && \ - make && \ + [ "$(git rev-parse HEAD)" = '486a8f8667e42f55cee2bba301b41433cacec830' ] && \ + CFLAGS="-lpthread" cmake3 \ + -DBUILD_EXAMPLES=OFF \ + -DBUILD_MANPAGES=OFF \ + -DBUILD_TOOLS=OFF \ + -DCMAKE_BUILD_TYPE=Release . && \ + grep 'CRYPTO_VERSION:INTERNAL=3\.0\.' CMakeCache.txt && \ + make && \ make install && \ make clean @@ -129,32 +130,35 @@ RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --pr cargo --version && \ rustc --version && \ rustup target add ${RUST_ARCH} - + USER root -# Copy dependencies +# Copy libfido2 libraries. +# Do this near the end to take better advantage of the multi-stage build. COPY --from=libfido2 /usr/local/include/ /usr/local/include/ +COPY --from=libfido2 /usr/local/lib64/engines-3/ /usr/local/lib64/engines-3/ +COPY --from=libfido2 /usr/local/lib64/ossl-modules/ /usr/local/lib64/ossl-modules/ COPY --from=libfido2 /usr/local/lib64/pkgconfig/ /usr/local/lib64/pkgconfig/ COPY --from=libfido2 \ /usr/local/lib64/libcbor.a \ /usr/local/lib64/libcrypto.a \ - /usr/local/lib64/libcrypto.so.1.1 \ + /usr/local/lib64/libcrypto.so.3 \ /usr/local/lib64/libfido2.a \ - /usr/local/lib64/libfido2.so.1.12.0 \ + /usr/local/lib64/libfido2.so.1.13.0 \ /usr/local/lib64/libssl.a \ - /usr/local/lib64/libssl.so.1.1 \ + /usr/local/lib64/libssl.so.3 \ /usr/local/lib64/libudev.a \ /usr/local/lib64/ # Re-create usual lib64 links. RUN cd /usr/local/lib64 && \ - ln -s libcrypto.so.1.1 libcrypto.so && \ - ln -s libfido2.so.1.12.0 libfido2.so.1 && \ + ln -s libcrypto.so.3 libcrypto.so && \ + ln -s libfido2.so.1.13.0 libfido2.so.1 && \ ln -s libfido2.so.1 libfido2.so && \ - ln -s libssl.so.1.1 libssl.so && \ + ln -s libssl.so.3 libssl.so && \ # Update ld. echo /usr/local/lib64 > /etc/ld.so.conf.d/libfido2.conf && \ ldconfig - +# Configure pkg-config. COPY pkgconfig/centos7/ / ENV PKG_CONFIG_PATH="/usr/local/lib64/pkgconfig" diff --git a/build.assets/Makefile b/build.assets/Makefile index f943cf10e0cea..8f96e962d02d6 100644 --- a/build.assets/Makefile +++ b/build.assets/Makefile @@ -19,7 +19,7 @@ TEST_KUBE ?= OS ?= linux ARCH ?= amd64 -GOLANG_VERSION ?= go1.20.2 +GOLANG_VERSION ?= go1.20.3 NODE_VERSION ?= 16.18.1 diff --git a/build.assets/build-fido2-macos.sh b/build.assets/build-fido2-macos.sh index e7ed1c5d73763..a82c1383b68ef 100755 --- a/build.assets/build-fido2-macos.sh +++ b/build.assets/build-fido2-macos.sh @@ -16,10 +16,10 @@ readonly MACOS_VERSION_MIN=10.13 # Note: versions are the same as the corresponding git tags for each repo. readonly CBOR_VERSION=v0.10.2 readonly CBOR_COMMIT=efa6c0886bae46bdaef9b679f61f4b9d8bc296ae -readonly CRYPTO_VERSION=OpenSSL_1_1_1t -readonly CRYPTO_COMMIT=830bf8e1e4749ad65c51b6a1d0d769ae689404ba -readonly FIDO2_VERSION=1.12.0 -readonly FIDO2_COMMIT=659a02679f99fd34a44e06e35dce90794f6ecc86 +readonly CRYPTO_VERSION=openssl-3.0.8 +readonly CRYPTO_COMMIT=31157bc0b46e04227b8468d3e6915e4d0332777c +readonly FIDO2_VERSION=1.13.0 +readonly FIDO2_COMMIT=486a8f8667e42f55cee2bba301b41433cacec830 readonly LIB_CACHE="/tmp/teleport-fido2-cache" readonly PKGFILE_DIR="$LIB_CACHE/fido2-${FIDO2_VERSION}_cbor-${CBOR_VERSION}_crypto-${CRYPTO_VERSION}" @@ -108,7 +108,6 @@ crypto_build() { ./config \ -mmacosx-version-min="$MACOS_VERSION_MIN" \ --prefix="$dest" \ - --openssldir="$dest/openssl@1.1" \ no-shared \ no-zlib # Build and copy only what we need instead of 'make && make install'. @@ -143,6 +142,7 @@ fido2_build() { -DCMAKE_OSX_DEPLOYMENT_TARGET="$MACOS_VERSION_MIN" \ -G "Unix Makefiles" \ . + grep 'CRYPTO_VERSION:INTERNAL=3\.0\.' CMakeCache.txt # double-check OpenSSL make make install } diff --git a/build.assets/build-package.sh b/build.assets/build-package.sh index 5e89ce80dc9ce..77d347b9518b3 100755 --- a/build.assets/build-package.sh +++ b/build.assets/build-package.sh @@ -2,7 +2,7 @@ set -e usage() { - echo "Usage: $(basename $0) [-t ] [-v ] [-p ] <-a [amd64/x86_64]|[386/i386]|arm|arm64> <-r fips> <-s tarball source dir>" 1>&2 + echo "Usage: $(basename $0) [-t ] [-v ] [-p ] [-b ] <-a [amd64/x86_64]|[386/i386]|arm|arm64> <-r fips> <-s tarball source dir>" 1>&2 exit 1 } @@ -11,7 +11,7 @@ usage() { #shellcheck disable=SC1091 . "$(dirname "$0")/build-common.sh" -while getopts ":t:v:p:a:r:s:n" o; do +while getopts ":t:v:p:a:r:s:b:n" o; do case "${o}" in t) t=${OPTARG} @@ -35,6 +35,9 @@ while getopts ":t:v:p:a:r:s:n" o; do s) s=${OPTARG} ;; + b) + b=${OPTARG} + ;; n) # Dry-run mode. # Only affects parts of the script, use at your own peril! @@ -120,6 +123,11 @@ else usage fi + if [[ -n "${b:-}" ]]; then + echo "bundle ID parameter can only be used for OS X packages" + exit 6 + fi + # set docker image appropriately if [[ "${PACKAGE_TYPE}" == "deb" ]]; then DOCKER_IMAGE="public.ecr.aws/gravitational/fpm:debian8" @@ -197,7 +205,7 @@ fi if [[ "${PACKAGE_TYPE}" == "pkg" ]]; then SIGN_PKG="true" FILE_LIST="${TAR_PATH}/tsh ${TAR_PATH}/tctl ${TAR_PATH}/teleport ${TAR_PATH}/tbot" - BUNDLE_ID="com.gravitational.teleport" + BUNDLE_ID="${b:-com.gravitational.teleport}" if [[ "${TELEPORT_TYPE}" == "ent" ]]; then PKG_FILENAME="teleport-ent-${TELEPORT_VERSION}.${PACKAGE_TYPE}" else diff --git a/build.assets/build-pkg-tsh.sh b/build.assets/build-pkg-tsh.sh index 3704a276980f3..125ca4080ebd0 100755 --- a/build.assets/build-pkg-tsh.sh +++ b/build.assets/build-pkg-tsh.sh @@ -5,9 +5,10 @@ set -eu TELEPORT_TYPE='' # -t, oss or ent TELEPORT_VERSION='' # -v, version, without leading 'v' TARBALL_DIRECTORY='' # -s +BUNDLEID="${TSH_BUNDLEID}" usage() { - log "Usage: $0 -t oss|eng -v version [-s tarball_directory] [-n]" + log "Usage: $0 -t oss|eng -v version [-s tarball_directory] [-b bundle_id] [-n]" } # make_non_relocatable_plist changes the default component plist of the $root @@ -34,7 +35,7 @@ main() { . "$buildassets/build-common.sh" local opt='' - while getopts "t:v:s:n" opt; do + while getopts "t:v:s:b:n" opt; do case "$opt" in t) if [[ "$OPTARG" != "oss" && "$OPTARG" != "ent" ]]; then @@ -54,6 +55,9 @@ main() { fi TARBALL_DIRECTORY="$OPTARG" ;; + b) + BUNDLEID="$OPTARG" + ;; n) DRY_RUN_PREFIX='echo + ' # declared by build-common.sh ;; @@ -75,6 +79,12 @@ main() { exit 1 fi + if [[ -z "${BUNDLEID}" ]]; then + echo "No bundle ID specified. Either set TSH_BUNDLEID or use -b bundle_id" + usage + exit 1 + fi + # Verify environment varibles. if [[ "${APPLE_USERNAME:-}" == "" ]]; then echo "\ @@ -89,6 +99,20 @@ password created by APPLE_USERNAME" exit 1 fi + if [[ -z "${DEVELOPER_ID_APPLICATION}" ]]; then + echo "\ +The DEVELOPER_ID_APPLICATION environment variable needs to be set to the hash\ +of the key to sign applications" + exit 1 + fi + + if [[ -z "${DEVELOPER_ID_INSTALLER}" ]]; then + echo "\ +The DEVELOPER_ID_INSTALLER environment variable needs to be set to the hash\ +of the key to sign packages" + exit 1 + fi + # Use similar find-or-download logic as build-package.sh for compatibility # purposes. local ent='' @@ -134,7 +158,7 @@ password created by APPLE_USERNAME" $DRY_RUN_PREFIX codesign -f \ -o kill,hard,runtime \ -s "$DEVELOPER_ID_APPLICATION" \ - -i "$TSH_BUNDLEID" \ + -i "$BUNDLEID" \ --entitlements "$skel"/tsh*.entitlements \ --timestamp \ "$target" @@ -149,7 +173,7 @@ password created by APPLE_USERNAME" pkgbuild \ --root "$pkg_root" \ --component-plist "$pkg_component_plist" \ - --identifier "$TSH_BUNDLEID" \ + --identifier "$BUNDLEID" \ --version "v$TELEPORT_VERSION" \ --install-location /Applications \ --scripts "$pkg_scripts" \ @@ -166,7 +190,7 @@ password created by APPLE_USERNAME" fi # Notarize. - notarize "$target" "$TEAMID" "$TSH_BUNDLEID" + notarize "$target" "$TEAMID" "$BUNDLEID" # Copy resulting package to $PWD, generate hashes. mv "$target" . diff --git a/build.assets/genproto.sh b/build.assets/genproto.sh index aa903a9fcf42e..9c9cbc5fdd72f 100755 --- a/build.assets/genproto.sh +++ b/build.assets/genproto.sh @@ -21,6 +21,7 @@ main() { # Add your protos to the list if you can. buf generate --template=buf-go.gen.yaml \ --path=api/proto/teleport/devicetrust/ \ + --path=api/proto/teleport/integration/ \ --path=api/proto/teleport/kube/ \ --path=api/proto/teleport/loginrule/ \ --path=api/proto/teleport/okta/ \ diff --git a/build.assets/keychain-setup.sh b/build.assets/keychain-setup.sh index d3f0ae440f67a..6e7962d9799dd 100755 --- a/build.assets/keychain-setup.sh +++ b/build.assets/keychain-setup.sh @@ -75,14 +75,18 @@ create_keychain() { } # Add a key from a file ($1) protected with a passphrase ($2) to a keychain ($3) -# protected with a password ($4). This is to allow `/usr/bin/codesign` to access -# the key. If the key file name is empty, add_key returns without doing anything. +# protected with a password ($4). This is to allow `/usr/bin/codesign` and +# `/usr/bin/productsign` to access the key. +# If the key file name is empty, add_key returns without doing anything. add_key() { local keyfile="$1" passphrase="$2" keychain="$3" keychain_password="$4" if [[ -z "${keyfile}" ]]; then return 0 fi - run security import "${keyfile}" -k "${keychain}" -P "${passphrase}" -T /usr/bin/codesign + run security import "${keyfile}" \ + -k "${keychain}" -P "${passphrase}" \ + -T /usr/bin/codesign \ + -T /usr/bin/productsign # Set ACLs so the key can be used for code signing. # Note: This selects all the signing keys (-s) in the keychain to be usable # for code signing. Not a problem because the keychain is just for that only diff --git a/build.assets/pkgconfig/buildbox/usr/lib/x86_64-linux-gnu/pkgconfig/libcrypto-static.pc b/build.assets/pkgconfig/buildbox/usr/local/lib/pkgconfig/libcrypto-static.pc similarity index 59% rename from build.assets/pkgconfig/buildbox/usr/lib/x86_64-linux-gnu/pkgconfig/libcrypto-static.pc rename to build.assets/pkgconfig/buildbox/usr/local/lib/pkgconfig/libcrypto-static.pc index 615ad01155071..a50be3fb61c50 100644 --- a/build.assets/pkgconfig/buildbox/usr/lib/x86_64-linux-gnu/pkgconfig/libcrypto-static.pc +++ b/build.assets/pkgconfig/buildbox/usr/local/lib/pkgconfig/libcrypto-static.pc @@ -1,11 +1,12 @@ -prefix=/usr +prefix=/usr/local exec_prefix=${prefix} -libdir=${exec_prefix}/lib/x86_64-linux-gnu +libdir=${exec_prefix}/lib includedir=${prefix}/include -enginesdir=${libdir}/engines-1.1 +enginesdir=${libdir}/engines-3 +modulesdir=${libdir}/ossl-modules Name: OpenSSL-libcrypto Description: OpenSSL cryptography library -Version: 1.1.1 +Version: 3.0.8 Libs: ${libdir}/libcrypto.a -ldl -pthread Cflags: -I${includedir} diff --git a/build.assets/pkgconfig/buildbox/usr/local/lib/pkgconfig/libfido2-static.pc b/build.assets/pkgconfig/buildbox/usr/local/lib/pkgconfig/libfido2-static.pc index 8a283f9728751..7c0e44547e85e 100644 --- a/build.assets/pkgconfig/buildbox/usr/local/lib/pkgconfig/libfido2-static.pc +++ b/build.assets/pkgconfig/buildbox/usr/local/lib/pkgconfig/libfido2-static.pc @@ -6,7 +6,7 @@ includedir=${prefix}/include Name: libfido2 Description: A FIDO2 library URL: https://github.com/yubico/libfido2 -Version: 1.12.0 +Version: 1.13.0 Requires: libcrypto-static # libfido2, libcbor and libudev combined here for simplicity. Libs: ${libdir}/libfido2.a ${libdir}/libcbor.a ${libdir}/libudev.a -pthread diff --git a/build.assets/pkgconfig/centos7/usr/local/lib64/pkgconfig/libcrypto-static.pc b/build.assets/pkgconfig/centos7/usr/local/lib64/pkgconfig/libcrypto-static.pc index 466eeb2745889..dd5588c1a6961 100644 --- a/build.assets/pkgconfig/centos7/usr/local/lib64/pkgconfig/libcrypto-static.pc +++ b/build.assets/pkgconfig/centos7/usr/local/lib64/pkgconfig/libcrypto-static.pc @@ -2,10 +2,11 @@ prefix=/usr/local exec_prefix=${prefix} libdir=${exec_prefix}/lib64 includedir=${prefix}/include -enginesdir=${libdir}/engines-1.1 +enginesdir=${libdir}/engines-3 +modulesdir=${libdir}/ossl-modules Name: OpenSSL-libcrypto Description: OpenSSL cryptography library -Version: 1.1.1t +Version: 3.0.8 Libs: ${libdir}/libcrypto.a -ldl -pthread Cflags: -I${includedir} diff --git a/build.assets/pkgconfig/centos7/usr/local/lib64/pkgconfig/libfido2-static.pc b/build.assets/pkgconfig/centos7/usr/local/lib64/pkgconfig/libfido2-static.pc index 2bf7c769b79ed..02ad75e049e69 100644 --- a/build.assets/pkgconfig/centos7/usr/local/lib64/pkgconfig/libfido2-static.pc +++ b/build.assets/pkgconfig/centos7/usr/local/lib64/pkgconfig/libfido2-static.pc @@ -6,7 +6,7 @@ includedir=${prefix}/include Name: libfido2 Description: A FIDO2 library URL: https://github.com/yubico/libfido2 -Version: 1.12.0 +Version: 1.13.0 Requires: libcrypto-static # libfido2, libcbor and libudev combined here for simplicity. Libs: ${libdir}/libfido2.a ${libdir}/libcbor.a ${libdir}/libudev.a -pthread diff --git a/constants.go b/constants.go index 86640f05a9c9f..e21f46bda3423 100644 --- a/constants.go +++ b/constants.go @@ -261,6 +261,9 @@ const ( // ComponentUsageReporting is the component responsible for reporting usage metrics. ComponentUsageReporting = "usage-reporting" + // ComponentAthena represents athena clients. + ComponentAthena = "athena" + // VerboseLogEnvVar forces all logs to be verbose (down to DEBUG level) VerboseLogsEnvVar = "TELEPORT_DEBUG" @@ -311,11 +314,11 @@ const ( // DirMaskSharedGroup is the mask for a directory accessible // by the owner and group - DirMaskSharedGroup = 0770 + DirMaskSharedGroup = 0o770 // FileMaskOwnerOnly is the file mask that allows read write access // to owers only - FileMaskOwnerOnly = 0600 + FileMaskOwnerOnly = 0o600 // On means mode is on On = "on" @@ -619,10 +622,10 @@ const ( const ( // SharedDirMode is a mode for a directory shared with group - SharedDirMode = 0750 + SharedDirMode = 0o750 // PrivateDirMode is a mode for private directories - PrivateDirMode = 0700 + PrivateDirMode = 0o700 ) const ( @@ -801,13 +804,14 @@ const UserSingleUseCertTTL = time.Minute const StandardHTTPSPort = 443 const ( - // WebAPIConnUpgrade is the HTTP web API to make the connection upgrade - // call. - WebAPIConnUpgrade = "/webapi/connectionupgrade" - // WebAPIConnUpgradeHeader is the header used to indicate the requested - // connection upgrade types in the connection upgrade API. - WebAPIConnUpgradeHeader = "Upgrade" - // WebAPIConnUpgradeTypeALPN is a connection upgrade type that specifies - // the upgraded connection should be handled by the ALPN handler. - WebAPIConnUpgradeTypeALPN = "alpn" + // KubeSessionDisplayParticipantRequirementsQueryParam is the query parameter used to + // indicate that the client wants to display the participant requirements + // for the given session. + KubeSessionDisplayParticipantRequirementsQueryParam = "displayParticipantRequirements" + // KubeSessionReasonQueryParam is the query parameter used to indicate the reason + // for the session request. + KubeSessionReasonQueryParam = "reason" + // KubeSessionInvitedQueryParam is the query parameter used to indicate the users + // to invite to the session. + KubeSessionInvitedQueryParam = "invite" ) diff --git a/docs/config.json b/docs/config.json index b568307fcb4e5..80f2529dd6cf7 100644 --- a/docs/config.json +++ b/docs/config.json @@ -263,6 +263,10 @@ "slug": "/access-controls/guides/device-trust/", "forScopes": ["enterprise", "cloud"] }, + { + "title": "Headless WebAuthn (Preview)", + "slug": "/access-controls/guides/headless/" + }, { "title": "IP Pinning (Preview)", "slug": "/access-controls/guides/ip-pinning/", @@ -440,10 +444,6 @@ { "title": "Reference", "slug": "/access-controls/reference/" - }, - { - "title": "FAQ", - "slug": "/access-controls/faq/" } ] }, @@ -803,6 +803,10 @@ "title": "EC2 Instance Discovery (Preview)", "slug": "/server-access/guides/ec2-discovery/" }, + { + "title": "Azure Instance Discovery (Preview)", + "slug": "/server-access/guides/azure-discovery/" + }, { "title": "Using Teleport with Ansible", "slug": "/server-access/guides/ansible/" @@ -1138,6 +1142,10 @@ "title": "GitHub Actions & Kubernetes", "slug": "/machine-id/guides/github-actions-kubernetes/" }, + { + "title": "GitLab CI", + "slug": "/machine-id/guides/gitlab/" + }, { "title": "Jenkins", "slug": "/machine-id/guides/jenkins/" @@ -1215,6 +1223,10 @@ { "title": "Automatically Register Teleport Agents", "slug": "/api/automatically-register-agents/" + }, + { + "title": "Automatically Generate Roles", + "slug": "/api/rbac/" } ] }, @@ -1401,8 +1413,8 @@ "version": "12.1.2", "major_version": "12", "sla": { - "monthly_percentage": "99.5%", - "monthly_downtime": "3 hours 40 minutes" + "monthly_percentage": "99.9%", + "monthly_downtime": "44 minutes" } }, "devicetrust": { @@ -2343,6 +2355,11 @@ "source": "/kubernetes-access/guides/", "destination": "/kubernetes-access/introduction/", "permanent": true + }, + { + "source": "/access-controls/faq/", + "destination": "/access-controls/introduction/", + "permanent": true } ] } diff --git a/docs/img/database-access/guides/azure/add-custom-role@2x.png b/docs/img/azure/add-custom-role@2x.png similarity index 100% rename from docs/img/database-access/guides/azure/add-custom-role@2x.png rename to docs/img/azure/add-custom-role@2x.png diff --git a/docs/img/database-access/guides/azure/app-registrations@2x.png b/docs/img/azure/app-registrations@2x.png similarity index 100% rename from docs/img/database-access/guides/azure/app-registrations@2x.png rename to docs/img/azure/app-registrations@2x.png diff --git a/docs/img/database-access/guides/azure/managed-identities@2x.png b/docs/img/azure/managed-identities@2x.png similarity index 100% rename from docs/img/database-access/guides/azure/managed-identities@2x.png rename to docs/img/azure/managed-identities@2x.png diff --git a/docs/img/database-access/guides/azure/registered-app-secrets@2x.png b/docs/img/azure/registered-app-secrets@2x.png similarity index 100% rename from docs/img/database-access/guides/azure/registered-app-secrets@2x.png rename to docs/img/azure/registered-app-secrets@2x.png diff --git a/docs/img/server-access/guides/azure/create-role-assignment@2x.png b/docs/img/server-access/guides/azure/create-role-assignment@2x.png new file mode 100644 index 0000000000000..ae103b08881e7 Binary files /dev/null and b/docs/img/server-access/guides/azure/create-role-assignment@2x.png differ diff --git a/docs/img/server-access/guides/azure/created-identity@2x.png b/docs/img/server-access/guides/azure/created-identity@2x.png new file mode 100644 index 0000000000000..304f2e5fb237d Binary files /dev/null and b/docs/img/server-access/guides/azure/created-identity@2x.png differ diff --git a/docs/img/server-access/guides/azure/new-identity@2x.png b/docs/img/server-access/guides/azure/new-identity@2x.png new file mode 100644 index 0000000000000..ba862f8d31a58 Binary files /dev/null and b/docs/img/server-access/guides/azure/new-identity@2x.png differ diff --git a/docs/img/server-access/guides/azure/registered-app@2x.png b/docs/img/server-access/guides/azure/registered-app@2x.png new file mode 100644 index 0000000000000..635a7bdee6fda Binary files /dev/null and b/docs/img/server-access/guides/azure/registered-app@2x.png differ diff --git a/docs/img/server-access/guides/azure/vm-create-role-from-json@2x.png b/docs/img/server-access/guides/azure/vm-create-role-from-json@2x.png new file mode 100644 index 0000000000000..35190ac411008 Binary files /dev/null and b/docs/img/server-access/guides/azure/vm-create-role-from-json@2x.png differ diff --git a/docs/img/server-access/guides/azure/vm-identity@2x.png b/docs/img/server-access/guides/azure/vm-identity@2x.png new file mode 100644 index 0000000000000..3f1bb21121eac Binary files /dev/null and b/docs/img/server-access/guides/azure/vm-identity@2x.png differ diff --git a/docs/pages/access-controls/access-request-plugins/ssh-approval-discord.mdx b/docs/pages/access-controls/access-request-plugins/ssh-approval-discord.mdx index bdc2bb9820a69..c75b0e30e8166 100644 --- a/docs/pages/access-controls/access-request-plugins/ssh-approval-discord.mdx +++ b/docs/pages/access-controls/access-request-plugins/ssh-approval-discord.mdx @@ -16,8 +16,7 @@ compromising productivity. - Admin account on your Discord server. Installing a bot requires at least the "manager server" permission. - Either a Linux host or Kubernetes cluster where you will run the Discord plugin. - -(!/docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/8. Define RBAC resources @@ -93,7 +92,7 @@ and will require access to both the public internet and the Teleport Auth Servic ## Step 4/8. Export the access plugin identity -(!docs/pages/includes/plugins/identity-export.mdx!) +(!docs/pages/includes/plugins/identity-export.mdx user="access-plugin"!) The rest of this guide assumes that you have placed any files generated by this command into `/var/lib/teleport/plugins/discord` for later reference when diff --git a/docs/pages/access-controls/access-request-plugins/ssh-approval-email.mdx b/docs/pages/access-controls/access-request-plugins/ssh-approval-email.mdx index 6ee27df906c2f..8735c2a63f09a 100644 --- a/docs/pages/access-controls/access-request-plugins/ssh-approval-email.mdx +++ b/docs/pages/access-controls/access-request-plugins/ssh-approval-email.mdx @@ -25,7 +25,7 @@ regularly. -(!/docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/7. Define RBAC resources @@ -104,7 +104,7 @@ $ teleport-email version ## Step 4/7. Export the access plugin identity -(!docs/pages/includes/plugins/identity-export.mdx!) +(!docs/pages/includes/plugins/identity-export.mdx user="access-plugin"!) ## Step 5/7. Configure the plugin diff --git a/docs/pages/access-controls/access-request-plugins/ssh-approval-jira.mdx b/docs/pages/access-controls/access-request-plugins/ssh-approval-jira.mdx index 5aca6e6fa685b..0081e1f07fc84 100644 --- a/docs/pages/access-controls/access-request-plugins/ssh-approval-jira.mdx +++ b/docs/pages/access-controls/access-request-plugins/ssh-approval-jira.mdx @@ -14,7 +14,7 @@ Jira tickets. - Jira Server or Jira Cloud installation with an owner privileges, specifically to set up webhooks, issue types, and workflows -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/6. Create a user and role for access @@ -22,7 +22,7 @@ Jira tickets. ## Step 2/6. Export the access-plugin certificate -(!docs/pages/includes/plugins/identity-export.mdx!) +(!docs/pages/includes/plugins/identity-export.mdx user="access-plugin"!) We'll reference these files later when configuring the plugin. diff --git a/docs/pages/access-controls/access-request-plugins/ssh-approval-mattermost.mdx b/docs/pages/access-controls/access-request-plugins/ssh-approval-mattermost.mdx index eaccf38559df0..02c75c2944163 100644 --- a/docs/pages/access-controls/access-request-plugins/ssh-approval-mattermost.mdx +++ b/docs/pages/access-controls/access-request-plugins/ssh-approval-mattermost.mdx @@ -16,8 +16,7 @@ compromising productivity. - A Mattermost account with admin privileges. This plugin has been tested with Mattermost v7.0.1. - Either a Linux host or Kubernetes cluster where you will run the Mattermost plugin. - -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/8. Define RBAC resources @@ -79,7 +78,7 @@ Run `./install` from `teleport-mattermost` or place the executable in the approp ## Step 4/8. Export the access plugin identity -(!docs/pages/includes/plugins/identity-export.mdx!) +(!docs/pages/includes/plugins/identity-export.mdx user="access-plugin"!) ## Step 5/8. Register a Mattermost bot diff --git a/docs/pages/access-controls/access-request-plugins/ssh-approval-msteams.mdx b/docs/pages/access-controls/access-request-plugins/ssh-approval-msteams.mdx index 89612df5a5e3c..8f4be8308eb0b 100644 --- a/docs/pages/access-controls/access-request-plugins/ssh-approval-msteams.mdx +++ b/docs/pages/access-controls/access-request-plugins/ssh-approval-msteams.mdx @@ -85,7 +85,7 @@ and will require access to both the public internet and the Teleport Auth Servic ## Step 4/9. Export the access plugin identity -(!docs/pages/includes/plugins/identity-export.mdx!) +(!docs/pages/includes/plugins/identity-export.mdx user="access-plugin"!) The rest of this guide assumes that you have placed any files generated by this command into `/var/lib/teleport/plugins/msteams` for later reference when diff --git a/docs/pages/access-controls/access-request-plugins/ssh-approval-pagerduty.mdx b/docs/pages/access-controls/access-request-plugins/ssh-approval-pagerduty.mdx index 9900ceeadfed5..52e5eb1ccee03 100644 --- a/docs/pages/access-controls/access-request-plugins/ssh-approval-pagerduty.mdx +++ b/docs/pages/access-controls/access-request-plugins/ssh-approval-pagerduty.mdx @@ -45,7 +45,7 @@ Teleport Cloud tenant and PagerDuty. -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/8. Create services @@ -413,7 +413,7 @@ Run `./install` from `teleport-pagerduty`. ## Step 4/8. Export the access plugin identity -(!docs/pages/includes/plugins/identity-export.mdx!) +(!docs/pages/includes/plugins/identity-export.mdx user="access-plugin"!) ## Step 5/8. Set up a PagerDuty API key diff --git a/docs/pages/access-controls/access-request-plugins/ssh-approval-slack.mdx b/docs/pages/access-controls/access-request-plugins/ssh-approval-slack.mdx index dfb84503c6cbc..d21b116113536 100644 --- a/docs/pages/access-controls/access-request-plugins/ssh-approval-slack.mdx +++ b/docs/pages/access-controls/access-request-plugins/ssh-approval-slack.mdx @@ -107,7 +107,7 @@ and will require access to both the public internet and the Teleport Auth Servic ## Step 4/8. Export the access plugin identity -(!docs/pages/includes/plugins/identity-export.mdx!) +(!docs/pages/includes/plugins/identity-export.mdx user="access-plugin"!) The rest of this guide assumes that you have placed any files generated by this command into `/var/lib/teleport/plugins/slack` for later reference when diff --git a/docs/pages/access-controls/access-requests/resource-requests.mdx b/docs/pages/access-controls/access-requests/resource-requests.mdx index 03411d4917832..107053adfe985 100644 --- a/docs/pages/access-controls/access-requests/resource-requests.mdx +++ b/docs/pages/access-controls/access-requests/resource-requests.mdx @@ -1,7 +1,6 @@ --- title: Resource Access Requests description: Teleport allows users to request access to specific resources from the CLI or UI. Requests can be escalated via ChatOps or anywhere else via our flexible Authorization Workflow API. -h1: Teleport Resource Access Requests --- @@ -28,7 +27,7 @@ available in Teleport Enterprise. (!docs/pages/includes/commercial-prereqs-tabs.mdx!) -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) All `teleport` instances in the cluster must be running Teleport `v10.0.0` or diff --git a/docs/pages/access-controls/access-requests/role-requests.mdx b/docs/pages/access-controls/access-requests/role-requests.mdx index e703b6c7fc649..0a70c664b67b4 100644 --- a/docs/pages/access-controls/access-requests/role-requests.mdx +++ b/docs/pages/access-controls/access-requests/role-requests.mdx @@ -12,7 +12,7 @@ via ChatOps or anywhere else via our flexible Authorization Workflow API. (!docs/pages/includes/edition-prereqs-tabs.mdx!) -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## RBAC Setup diff --git a/docs/pages/access-controls/faq.mdx b/docs/pages/access-controls/faq.mdx deleted file mode 100644 index 34b05c0ea2fa7..0000000000000 --- a/docs/pages/access-controls/faq.mdx +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Access Controls FAQ -description: Frequently asked questions about Teleport RBAC ---- - -## What if a node has multiple labels? - -In this case, the access will be granted only if **all of the labels** defined in the -role are present. This effectively means Teleport uses an "AND" operator when evaluating -node-level access using labels. - -## Can I use node-level RBAC with OpenSSH servers? - -No. OpenSSH servers running `sshd` can't label themselves. This is a factor in deciding -to run the Teleport Node Service instead. - -## Why do I see a UUID instead of a hostname when reviewing access requests? - -Resource Access Requests embed the UUID of requested resources in order to ensure -that extra access isn't mistakenly granted due to overlapping hostnames. - -In order for Access Request reviewers to see the hostname, they must either: - -- Have permissions to access the requested server themselves, or -- Have [`preview_as_roles`](./access-requests/resource-requests.mdx#allow-reviewers-to-see-the-hostnames-of-ssh-nodes) - set with a role that can access the server - \ No newline at end of file diff --git a/docs/pages/access-controls/getting-started.mdx b/docs/pages/access-controls/getting-started.mdx index ab847056040d7..0e308d1cb921e 100644 --- a/docs/pages/access-controls/getting-started.mdx +++ b/docs/pages/access-controls/getting-started.mdx @@ -14,9 +14,9 @@ wrap up with creating your own role. (!docs/pages/includes/edition-prereqs-tabs.mdx!) -(!docs/pages/includes/permission-warning.mdx!) +- (!docs/pages/includes/tctl.mdx!) -(!docs/pages/includes/tctl.mdx!) +(!docs/pages/includes/permission-warning.mdx!) ## Step 1/3. Add local users with preset roles diff --git a/docs/pages/access-controls/guides/device-trust.mdx b/docs/pages/access-controls/guides/device-trust.mdx index 01ad5d1336ef3..d4e30b2250c81 100644 --- a/docs/pages/access-controls/guides/device-trust.mdx +++ b/docs/pages/access-controls/guides/device-trust.mdx @@ -7,7 +7,7 @@ videoBanner: gBQyj_X1LVw Device Trust is currently in Preview mode. -Device Trust requires Teleport Enterprise. +Device Trust requires Teleport Enterprise v12+. Device Trust allows Teleport admins to enforce the use of trusted devices. @@ -202,7 +202,7 @@ by the `device_trust.mode` authentication setting: - `off` - disables device trust. Device authentication is not performed and device-aware audit logs are absent. -- `optional` - enables device authentication and device-aware audit, but doesn't +- `optional` - enables device authentication and device-aware audit, but does not require a trusted device to access resources. - `required` - enables device authentication and device-aware audit. Additionally, it requires a trusted device for all SSH, Database and @@ -220,23 +220,8 @@ preview. Only `tsh` and Teleport Connect are able to fulfill device mode To enable device mode `required` update your configuration as follows: - - -Edit the Auth Server's `teleport.yaml` file: - -```diff -auth_service: - authentication: - type: local - second_factor: "on" - webauthn: - rp_id: example.com - device_trust: -+ mode: "required" # add this line -``` - - - + + Create a `cap.yaml` file or get the existing configuration using `tctl get cluster_auth_preference`: @@ -267,6 +252,21 @@ You can also edit this configuration directly: $ tctl edit cluster_auth_preference ``` + + +Edit the Auth Server's `teleport.yaml` file and restart all Auth Services: + +```diff +auth_service: + authentication: + type: local + second_factor: "on" + webauthn: + rp_id: example.com + device_trust: ++ mode: "required" # add this line +``` + diff --git a/docs/pages/access-controls/guides/dual-authz.mdx b/docs/pages/access-controls/guides/dual-authz.mdx index 52d97461d5759..abe12a9444150 100644 --- a/docs/pages/access-controls/guides/dual-authz.mdx +++ b/docs/pages/access-controls/guides/dual-authz.mdx @@ -41,7 +41,7 @@ of two team members for a privileged role `dbadmin`. -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/3. Set up a Teleport bot @@ -69,7 +69,7 @@ Create the bot and save the access token. ### Export the access-plugin identity files -(!docs/pages/includes/plugins/identity-export.mdx!) +(!docs/pages/includes/plugins/identity-export.mdx user="access-plugin"!) We'll reference the exported file(s) later when configuring the plugin. diff --git a/docs/pages/access-controls/guides/hardware-key-support.mdx b/docs/pages/access-controls/guides/hardware-key-support.mdx index 31d3be24d9be8..533ccadb4af6b 100644 --- a/docs/pages/access-controls/guides/hardware-key-support.mdx +++ b/docs/pages/access-controls/guides/hardware-key-support.mdx @@ -62,7 +62,7 @@ Additionally, this feature can be configured to require touch for every Teleport Teleport clients use PIV slot 9a for the `hardware_key` option and 9c for the `hardware_key_touch` option, and will overwrite other keys and certs in these slots as needed. This may interfere with other PIV applications, like `yubikey-agent`, so we recommend only using one PIV application at a time. -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/2. Enforce Hardware Key Support diff --git a/docs/pages/access-controls/guides/headless.mdx b/docs/pages/access-controls/guides/headless.mdx new file mode 100644 index 0000000000000..56d71d14f1c87 --- /dev/null +++ b/docs/pages/access-controls/guides/headless.mdx @@ -0,0 +1,154 @@ +--- +title: Headless WebAuthn (Preview) +description: Headless WebAuthn +--- + + + Headless WebAuthn is currently in Preview mode. + + +Headless WebAuthn provides a secure way to authenticate with WebAuthn from a +machine without access to a WebAuthn device. This enables the use of WebAuthn +features which are usually not usable in WebAuthn-incompatible environments. +For example: + +- Logging into Teleport with WebAuthn from a remote dev box +- Connecting to a Teleport SSH Service from a remote dev box with per-session MFA +- Performing `tsh scp` from one Teleport SSH Service to another with per-session MFA +- Logging into Teleport on a machine without a WebAuthn-compatible browser + + + Headless WebAuthn only supports the following `tsh` commands: + + - `tsh ls` + - `tsh ssh` + - `tsh scp` + + In the future, Headless WebAuthn will be extended to other `tsh` commands. + + +## Prerequisites + +- A v12.2+ Teleport cluster with WebAuthn configured. + See the [Second Factor: WebAuthn](./webauthn.mdx) guide. +- WebAuthn hardware device, such as YubiKey. +- A Web browser with [WebAuthn support]( + https://developers.yubico.com/WebAuthn/WebAuthn_Browser_Support/). + + +## Step 1/3. Configuration + +A v12.2+ Teleport cluster capable of WebAuthn is automatically capable of +Headless WebAuthn without any additional configuration. + +
+ +To make Headless WebAuthn the default authentication method for your Teleport +Cluster, add `connector_name: headless` to your cluster configuration. + +Create a `cap.yaml` file or get the existing configuration using +`tctl get cluster_auth_preference`: + +```yaml +kind: cluster_auth_preference +version: v2 +metadata: + name: cluster-auth-preference +spec: + type: local + second_factor: "on" + webauthn: + rp_id: example.com + connector_name: headless # headless by default +``` + +Update the configuration: + +```code +$ tctl create -f cap.yaml +# cluster auth preference has been updated +``` +
+ +
+ +Headless WebAuthn is enabled automatically when WebAuthn is configured. If you +want to forbid Headless WebAuthn in your cluster, add `headless: false` to your +configuration. + +Create a `cap.yaml` file or get the existing configuration using +`tctl get cluster_auth_preference`: + +```yaml +kind: cluster_auth_preference +version: v2 +metadata: + name: cluster-auth-preference +spec: + type: local + second_factor: "on" + webauthn: + rp_id: example.com + headless: false # disable Headless WebAuthn +``` + +Update the configuration: + +```code +$ tctl create -f cap.yaml +# cluster auth preference has been updated +``` + +
+ +## Step 2/3. Initiate Headless WebAuthn + +Run a headless `tsh` command with the `--headless` flag. This will initiate +headless authentication, printing a URL and `tsh` command. + +```code +$ tsh ls --headless --proxy=proxy.example.com --user=alice +# Complete headless authentication in your local web browser: +# +# https://proxy.example.com:3080/web/headless/86172f78-af7c-5935-a7c1-ed06b94f17dc +# +# or execute this command in your local terminal: +# +# tsh headless approve --user=alice --proxy=proxy.example.com 86172f78-af7c-5935-a7c1-ed06b94f17dc +``` + +## Step 3/3. Approve Headless WebAuthn + +To approve the headless authentication, click or copy+paste the URL printed by +`tsh` in your local web browser. You will be prompted to approve the log in with +WebAuthn verification. Once approved, your initial `tsh --headless ` +should continue as if you had logged in locally. + +Unlike a standard login session, headless sessions are only available for the +lifetime of a single `tsh` request. This means that for each `tsh --headless` +command, you will need to go through the Headless WebAuthn flow: + +```code +$ tsh ls --headless --proxy=proxy.example.com --user=alice +# Complete headless authentication in your local web browser: +# +# https://proxy.example.com:3080/web/headless/86172f78-af7c-5935-a7c1-ed06b94f17dc +# +# or execute this command in your local terminal: +# +# tsh headless approve --user=alice --proxy=proxy.example.com 86172f78-af7c-5935-a7c1-ed06b94f17dc +# # User approves through link +# Node Name Address Labels +# --------- -------------- ----------- +# server01 127.0.0.1:3022 arch=x86_64 +# +$ tsh ssh --headless --proxy=proxy.example.com --user=alice server01 +# Complete headless authentication in your local web browser: +# +# https://proxy.example.com:3080/web/headless/864cccd9-2425-46d9-a9f2-636387e66ebf +# +# or execute this command in your local terminal: +# +# tsh headless approve --user=alice --proxy=proxy.example.com 864cccd9-2425-46d9-a9f2-636387e66ebf +# # User approves through link +``` \ No newline at end of file diff --git a/docs/pages/access-controls/guides/impersonation.mdx b/docs/pages/access-controls/guides/impersonation.mdx index 2489761616817..9ee8285164a2e 100644 --- a/docs/pages/access-controls/guides/impersonation.mdx +++ b/docs/pages/access-controls/guides/impersonation.mdx @@ -15,7 +15,7 @@ non-interactive CI/CD user Jenkins and a security scanner. (!docs/pages/includes/edition-prereqs-tabs.mdx!) -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/3: Create a CI/CD user and corresponding role diff --git a/docs/pages/access-controls/guides/locking.mdx b/docs/pages/access-controls/guides/locking.mdx index 33658ae9e43df..e203a58630f69 100644 --- a/docs/pages/access-controls/guides/locking.mdx +++ b/docs/pages/access-controls/guides/locking.mdx @@ -39,7 +39,7 @@ A lock can target the following objects or attributes: (!docs/pages/includes/edition-prereqs-tabs.mdx!) -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/2. Create a lock diff --git a/docs/pages/access-controls/guides/moderated-sessions.mdx b/docs/pages/access-controls/guides/moderated-sessions.mdx index ec9645e44991c..86aca13d72cc5 100644 --- a/docs/pages/access-controls/guides/moderated-sessions.mdx +++ b/docs/pages/access-controls/guides/moderated-sessions.mdx @@ -170,9 +170,8 @@ A participant joining a session will always have one of three modes: When joining a session with `tsh join` or `tsh kube join`, a user can specify a -mode with the `--mode ` flag , where the mode is one of `peer`, -`moderator` or `observer`. By default, the mode is `peer` for SSH and -`moderator` for Kubernetes sessions. +participant mode with the `--mode ` flag , where the mode is one of `peer`, +`moderator` or `observer`. By default, the mode is `observer`. A participant may leave a session with the shortcut `^c` (Control + c) while in observer or moderator mode. When in moderator mode, a participant may also forcefully diff --git a/docs/pages/access-controls/guides/per-session-mfa.mdx b/docs/pages/access-controls/guides/per-session-mfa.mdx index fa03f4eebe1c0..4f9da7652788a 100644 --- a/docs/pages/access-controls/guides/per-session-mfa.mdx +++ b/docs/pages/access-controls/guides/per-session-mfa.mdx @@ -38,13 +38,11 @@ their on-disk Teleport certificates. Per-session MFA for Desktop Access was introduced in Teleport 9. - ## Prerequisites (!docs/pages/includes/edition-prereqs-tabs.mdx!) -(!docs/pages/includes/tctl.mdx!) - +- (!docs/pages/includes/tctl.mdx!) - [WebAuthn configured](webauthn.mdx) on this cluster - Second factor hardware device, such as YubiKey or SoloKey - A Web browser with [WebAuthn support]( @@ -64,6 +62,7 @@ teleport: webauthn: rp_id: teleport.example.com ``` + ## Configure per-session MFA diff --git a/docs/pages/access-controls/guides/role-templates.mdx b/docs/pages/access-controls/guides/role-templates.mdx index 1f3b7f0534ef5..96b26cc21bc5e 100644 --- a/docs/pages/access-controls/guides/role-templates.mdx +++ b/docs/pages/access-controls/guides/role-templates.mdx @@ -20,7 +20,7 @@ other policies. (!docs/pages/includes/edition-prereqs-tabs.mdx!) -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Local users diff --git a/docs/pages/access-controls/guides/webauthn.mdx b/docs/pages/access-controls/guides/webauthn.mdx index 153d5c7dbeaa8..91eebded132e7 100644 --- a/docs/pages/access-controls/guides/webauthn.mdx +++ b/docs/pages/access-controls/guides/webauthn.mdx @@ -20,8 +20,7 @@ UI). - WebAuthn hardware device, such as YubiKey or SoloKey - A Web browser with [WebAuthn support]( https://developers.yubico.com/WebAuthn/WebAuthn_Browser_Support/) - -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/3. Enable WebAuthn support diff --git a/docs/pages/access-controls/idps/saml-grafana.mdx b/docs/pages/access-controls/idps/saml-grafana.mdx index 6a730b6102417..6a1ba82e57700 100644 --- a/docs/pages/access-controls/idps/saml-grafana.mdx +++ b/docs/pages/access-controls/idps/saml-grafana.mdx @@ -17,7 +17,7 @@ not just those running behind the Teleport App Service. (!docs/pages/includes/commercial-prereqs-tabs.mdx!) -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/3. Configure a Teleport role with access to SAML service provider objects diff --git a/docs/pages/access-controls/idps/saml-guide.mdx b/docs/pages/access-controls/idps/saml-guide.mdx index ffaa46f0e1b0e..71ae4eb53c5d7 100644 --- a/docs/pages/access-controls/idps/saml-guide.mdx +++ b/docs/pages/access-controls/idps/saml-guide.mdx @@ -13,10 +13,10 @@ authenticate to external services. (!docs/pages/includes/commercial-prereqs-tabs.mdx!) -(!docs/pages/includes/tctl.mdx!) - +- (!docs/pages/includes/tctl.mdx!) - If you're new to SAML, consider reviewing our [SAML Identity Provider Reference](./saml-reference.mdx) before proceeding. + ## Example external application We'll be using [samltest.id](https://samltest.id/) to create a test consumer of diff --git a/docs/pages/access-controls/introduction.mdx b/docs/pages/access-controls/introduction.mdx index 4b2fbf3baae31..50c5802b14a25 100644 --- a/docs/pages/access-controls/introduction.mdx +++ b/docs/pages/access-controls/introduction.mdx @@ -74,7 +74,5 @@ achieve compliance with: ## Find out more -Find out more information on Teleport's RBAC features: - -- [Access Controls Reference](./reference.mdx) -- [Frequently Asked Questions](./faq.mdx) +Find out more information on Teleport's RBAC features by reading the [Access +Controls Reference](./reference.mdx). diff --git a/docs/pages/access-controls/login-rules/guide.mdx b/docs/pages/access-controls/login-rules/guide.mdx index 98f4362d488b1..7c9350ec2aae8 100644 --- a/docs/pages/access-controls/login-rules/guide.mdx +++ b/docs/pages/access-controls/login-rules/guide.mdx @@ -14,7 +14,7 @@ first Login Rule to your Teleport cluster. (!docs/pages/includes/commercial-prereqs-tabs.mdx!) -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) Before you get started you’ll need a running Teleport Enterprise or Cloud cluster on version `11.3.1` or greater. diff --git a/docs/pages/access-controls/sso/adfs.mdx b/docs/pages/access-controls/sso/adfs.mdx index 10178ac810a1c..7d3c75fd3183a 100644 --- a/docs/pages/access-controls/sso/adfs.mdx +++ b/docs/pages/access-controls/sso/adfs.mdx @@ -21,7 +21,7 @@ like: (!docs/pages/includes/commercial-prereqs-tabs.mdx!) -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) (!docs/pages/includes/enterprise/samlauthentication.mdx!) diff --git a/docs/pages/access-controls/sso/azuread.mdx b/docs/pages/access-controls/sso/azuread.mdx index 6bb637aa2faee..767bc741d3537 100644 --- a/docs/pages/access-controls/sso/azuread.mdx +++ b/docs/pages/access-controls/sso/azuread.mdx @@ -23,7 +23,7 @@ Before you get started you’ll need: (!docs/pages/includes/commercial-prereqs-tabs.mdx!) -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) (!docs/pages/includes/enterprise/samlauthentication.mdx!) diff --git a/docs/pages/access-controls/sso/github-sso.mdx b/docs/pages/access-controls/sso/github-sso.mdx index 11ee87b749df9..4f55164492ddd 100644 --- a/docs/pages/access-controls/sso/github-sso.mdx +++ b/docs/pages/access-controls/sso/github-sso.mdx @@ -17,7 +17,7 @@ from either GitHub Cloud or GitHub Enterprise Server. (!docs/pages/includes/edition-prereqs-tabs.mdx!) -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/3. Create a GitHub OAuth app diff --git a/docs/pages/access-controls/sso/gitlab.mdx b/docs/pages/access-controls/sso/gitlab.mdx index 71ef8261a99e5..160225fa6ef8e 100644 --- a/docs/pages/access-controls/sso/gitlab.mdx +++ b/docs/pages/access-controls/sso/gitlab.mdx @@ -22,7 +22,7 @@ like: (!docs/pages/includes/commercial-prereqs-tabs.mdx!) -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Enable default OIDC authentication diff --git a/docs/pages/access-controls/sso/google-workspace.mdx b/docs/pages/access-controls/sso/google-workspace.mdx index 6eb98480b0824..83d2d4b0ae681 100644 --- a/docs/pages/access-controls/sso/google-workspace.mdx +++ b/docs/pages/access-controls/sso/google-workspace.mdx @@ -24,7 +24,7 @@ Before you get started you’ll need: (!docs/pages/includes/commercial-prereqs-tabs.mdx!) -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/4. Enable default OIDC authentication diff --git a/docs/pages/access-controls/sso/oidc.mdx b/docs/pages/access-controls/sso/oidc.mdx index b39aed6f53268..af4f6e10fdd37 100644 --- a/docs/pages/access-controls/sso/oidc.mdx +++ b/docs/pages/access-controls/sso/oidc.mdx @@ -19,7 +19,7 @@ administrators to define policies like: (!docs/pages/includes/commercial-prereqs-tabs.mdx!) -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Enable default OIDC authentication diff --git a/docs/pages/access-controls/sso/okta.mdx b/docs/pages/access-controls/sso/okta.mdx index 5e454b21289ce..d0a9aa0b16623 100644 --- a/docs/pages/access-controls/sso/okta.mdx +++ b/docs/pages/access-controls/sso/okta.mdx @@ -23,8 +23,7 @@ Teleport administrators to define policies like: - A Teleport role with access to edit and maintain `saml` resources. This is available in the default `editor` role. - -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) (!docs/pages/includes/enterprise/samlauthentication.mdx!) diff --git a/docs/pages/access-controls/sso/one-login.mdx b/docs/pages/access-controls/sso/one-login.mdx index 37146b9293a75..fe5941589db6c 100644 --- a/docs/pages/access-controls/sso/one-login.mdx +++ b/docs/pages/access-controls/sso/one-login.mdx @@ -20,7 +20,7 @@ like: (!docs/pages/includes/commercial-prereqs-tabs.mdx!) -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) (!docs/pages/includes/enterprise/samlauthentication.mdx!) diff --git a/docs/pages/api/introduction.mdx b/docs/pages/api/introduction.mdx index d77f441e19ee3..72530df5e8fea 100644 --- a/docs/pages/api/introduction.mdx +++ b/docs/pages/api/introduction.mdx @@ -4,8 +4,8 @@ description: Introduction to the Teleport gRPC API. layout: tocless-doc --- -The Teleport Auth Service provides a gRPC API for remotely interacting with -your cluster. +The Teleport Auth Service provides a gRPC API for remotely interacting with your +cluster. In this section, we will show you how to use Teleport's API. ## Go client @@ -19,8 +19,8 @@ Here is what you can do with the Go Client: - Integrate with external tools, e.g., to write an [Access Request plugin](../access-controls/access-request-plugins/index.mdx). Teleport maintains Access Request plugins for tools like Slack, Jira, and Mattermost. - - Perform CRUD actions on resources, such as `roles`, `auth connectors`, and - `provisioning tokens`. + - Perform CRUD actions on resources, such as roles, authentication connectors, + and provisioning tokens. - Dynamically configure Teleport. ## Get started @@ -38,6 +38,9 @@ Teleport's API client libraries: - [Automatically Register Teleport Agents](./automatically-register-agents.mdx): Write a Teleport API client to reconcile the resources in your Teleport cluster with an external service discovery API. +- [Automatically generate Teleport roles](./rbac.mdx) from an external RBAC + system, making it easier to get started with Teleport-based RBAC and keep your + Teleport roles up to date. - [Write an Access Request Plugin](./access-plugin.mdx): Follow this guide for a minimal working example of a plugin that you can use to manage Access Requests through your organization's unique communication worfklows. diff --git a/docs/pages/api/rbac.mdx b/docs/pages/api/rbac.mdx new file mode 100644 index 0000000000000..5307468910c44 --- /dev/null +++ b/docs/pages/api/rbac.mdx @@ -0,0 +1,983 @@ +--- +title: Generate Teleport Roles from an External RBAC System +description: Use Teleport's API to automatically generate Teleport roles based on third-party RBAC policies +--- + +You can use the Teleport gRPC API to generate roles automatically based on an +external role-based access control (RBAC) system, such as GitHub or AWS Identity +and Access Management. + +This is especially useful for: + +- Setting up a new Teleport cluster, since you can preserve your existing + authorization levels or categories while letting Teleport handle access + control. +- Ensuring that your Teleport cluster stays up to date with the RBAC systems of + the infrastructure it manages access to. This way, Teleport roles do not + unexpectedly gain or lose permissions if your teams reconfigure your external + RBAC systems. + +In this guide, we will build a small demo application to show you how to +generate Teleport roles using Teleport's API client library. + + + +The program we will build in this guide is intended as a learning tool. **Do not +connect it to your production Teleport cluster.** Use a demo cluster instead. + + + +## Prerequisites + +(!docs/pages/includes/edition-prereqs-tabs.mdx!) + +- Go version (=teleport.golang=) or above installed on your workstation. See the + [Go download page](https://go.dev/dl/). You will not need to be familiar with + Go to complete this guide, though Go knowledge is required if you want to + build a production-ready Teleport client application. + +In a production scenario, you will already have a third-party RBAC solution to +use as a basis for generating Teleport roles. In this guide, we will simulate +this by deploying a local Kubernetes cluster using `minikube` and setting up +some RBAC resources. We will then use this Kubernetes cluster to generate +Teleport roles. + +To run the local demo environment, ensure that you have the following tools +installed on your workstation: + +| Tool | Purpose | Installation link | +|----------|----------------------------------|---------------------------------------------------------------| +| minikube | Local Kubernetes deployment tool | [Install minikube](https://minikube.sigs.k8s.io/docs/start/) | +| Helm | Kubernetes package manager | [Install Helm](https://helm.sh/docs/intro/install/) | +| kubectl | Kubernetes admin CLI | [Install kubectl](https://kubernetes.io/docs/tasks/tools/) | +| Docker | Required minikube driver | [Get Started With Docker](https://www.docker.com/get-started) | + + +Even if you do not plan to set up the demo project, you can follow this guide to +see which libraries, types, and functions you can use to automatically generate +Teleport roles based on an external RBAC system. + + + +- (!docs/pages/includes/tctl.mdx!) + +## Step 1/4. Set up your Kubernetes cluster + +In this step, we will launch a local Kubernetes cluster and set up role-based +access controls within it. We will then use this Kubernetes cluster as a basis +for generating Teleport roles. + +### Start minikube + +Start minikube with the Docker driver, which boots a local Kubernetes cluster on +a single Docker container: + +```code +$ minikube start --driver=docker +``` + +This command should start a local Kubernetes cluster and set your context (i.e., +the Kubernetes cluster you are currently interacting with) to `minikube`. To +verify this, run the following command: + +```code +$ kubectl config current-context +minikube +``` + +### Define demo Kubernetes RBAC resources + +Next, we will set up RBAC resources in your local `minikube` cluster to use as a +basis for generating Teleport roles. + +In Kubernetes, you can divide a cluster into logically isolated **namespaces**. +A **role** defines a set of permissions for manipulating resources in a specific +namespace. A **cluster role** is a role that applies to all namespaces in a +cluster. You can use a **role binding** or **cluster role binding** to attach a +role or cluster role to Kubernetes users and groups. + +Define a Kubernetes role and role binding that allows users in the +`app-developer` group to read and list pods in the `app` namespace. Add the +following to a file called `pod-reader.yaml`: + +```yaml +apiVersion: v1 +kind: Namespace +metadata: + name: app +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + namespace: app + name: pod-reader +rules: +- apiGroups: [""] + resources: ["pods"] + verbs: ["get", "list"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: read-pods + namespace: app + annotations: + 'create-teleport-role': 'true' +subjects: +- kind: Group + name: app-developer + apiGroup: rbac.authorization.k8s.io +roleRef: + kind: Role + name: pod-reader + apiGroup: rbac.authorization.k8s.io +``` + +Create the resources: + +```code +$ kubectl apply -f pod-reader.yaml +namespace/app created +role.rbac.authorization.k8s.io/pod-reader created +rolebinding.rbac.authorization.k8s.io/read-pods created +``` + +Next, define a cluster role and cluster role binding that allow users in the +`ops` group to read, create, and execute commands on pods in all namespaces. Add +the following to a file called `pod-ops.yaml`: + +```yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: pod-ops +rules: +- apiGroups: [""] + resources: ["pods"] + verbs: ["get", "watch", "list", "create", "exec", "logs"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: pod-ops + annotations: + 'create-teleport-role': 'true' +subjects: +- kind: Group + name: ops + apiGroup: rbac.authorization.k8s.io +roleRef: + kind: ClusterRole + name: pod-ops + apiGroup: rbac.authorization.k8s.io +``` + +Create the resources: + +```code +$ kubectl apply -f pod-ops.yaml +clusterrole.rbac.authorization.k8s.io/pod-ops created +clusterrolebinding.rbac.authorization.k8s.io/pod-ops created +``` + +Later in this guide, we will show you how to automatically generate Teleport +roles based on the Kubernetes RBAC resources you created. + +### Define RBAC resources for the client application + +Next, ensure that your API client can read the RBAC resources you created. +Create a file called `rbac-sync.yaml` with the following content: + +```yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: rbac-sync +rules: +- apiGroups: ["rbac.authorization.k8s.io"] + resources: ["roles", "clusterroles", "rolebindings", "clusterrolebindings"] + verbs: ["get", "list"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: rbac-sync +subjects: +- kind: User + name: sync-kubernetes-rbac + apiGroup: rbac.authorization.k8s.io +roleRef: + kind: ClusterRole + name: rbac-sync + apiGroup: rbac.authorization.k8s.io +``` + +Apply the changes: + +```code +$ kubectl apply -f rbac-sync.yaml +clusterrole.rbac.authorization.k8s.io/rbac-sync created +clusterrolebinding.rbac.authorization.k8s.io/rbac-sync created +``` + +## Step 2/4. Set up Teleport + +In this step, you will configure Teleport to enable your API client application +to interact with your Kubernetes cluster. + +### Create a user and role for the client application + +Give the client application a Teleport user and role that can retrieve +information about a Kubernetes cluster that is registered with Teleport, +authenticate to the cluster, and create or update Teleport roles. + +Create a file called `sync-kubernetes-rbac.yaml` with the following content: + +```yaml +kind: role +version: v6 +metadata: + name: sync-kubernetes-rbac +spec: + allow: + kubernetes_labels: + '*': '*' + kubernetes_users: + - sync-kubernetes-rbac + kubernetes_resources: + - kind: pod + name: '*' + namespace: '*' + rules: + - resources: ['kubernetes_cluster'] + verbs: ['read'] + - resources: ['role'] + verbs: ['create', 'update'] +--- +kind: user +metadata: + name: sync-kubernetes-rbac +spec: + roles: ['sync-kubernetes-rbac'] +version: v2 +``` + +Create the user and role: + +```code +$ tctl create -f sync-kubernetes-rbac.yaml +role 'sync-kubernetes-rbac' has been created +user "sync-kubernetes-rbac" has been created +``` + +### Enable impersonation of the client application + +As with all Teleport users, the Teleport Auth Service authenticates the +`sync-kubernetes-rbac` user by issuing short-lived TLS credentials. In this +case, we will request the credentials manually by *impersonating* the +`sync-kubernetes-rbac` role and user. + +If you are using `tctl` from the Auth +Service host, you will already have impersonation privileges. + +To grant your user impersonation privileges for `sync-kubernetes-rbac`, define a role +called `sync-kubernetes-rbac-impersonator` by pasting the following YAML document into +a file called `sync-kubernetes-rbac-impersonator.yaml`: + +```yaml +kind: role +version: v5 +metadata: + name: sync-kubernetes-rbac-impersonator +spec: + allow: + impersonate: + roles: + - sync-kubernetes-rbac + users: + - sync-kubernetes-rbac +``` + +Create the `sync-kubernetes-rbac-impersonator` role: + +```code +$ tctl create -f sync-kubernetes-rbac-impersonator.yaml +``` + +(!docs/pages/includes/add-role-to-user.mdx role="sync-kubernetes-rbac-impersonator"!) + +You will now be able to generate signed certificates for the `sync-kubernetes-rbac` +role and user. + +### Install the Teleport Kubernetes Service + +We will enable your client application to communicate with your Kubernetes +cluster via the Teleport Kubernetes Service, which forwards requests after +authorizing them. While this step is not strictly necessary with a local +`minikube` cluster, it demonstrates one way to use Teleport to securely access +your external RBAC system's API. + +(!docs/pages/kubernetes-access/helm/includes/helm-repo-add.mdx!) + +Request a token that the Kubernetes Service will use to join your Teleport +cluster: + +```code +$ tctl nodes add --roles=kube --ttl=10000h --format=json +``` + +Copy this token so you can use it when running the Teleport Kubernetes Service. + +Ensure that you are connected to the right Kubernetes cluster (logging into +Teleport earlier will have changed your Kubernetes context): + +```code +$ kubectl config use-context minikube +Switched to context "minikube". +``` + +Install the Teleport Kubernetes Service in your cluster, assigning to the host **and port** of your Teleport Proxy Service +(e.g., `mytenant.teleport.sh:443`) and to the token you +requested earlier: + +```code +$ helm install teleport-agent teleport/teleport-kube-agent \ + --set kubeClusterName=minikube \ + --set proxyAddr= \ + --set authToken= \ + --create-namespace \ + --namespace=teleport \ + --set labels.environment=demo \ + --version (=teleport.version=) +``` + +After a few seconds, verify that you have deployed the Teleport Kubernetes +Service by running the following command: + +```code +$ kubectl -n teleport get pods +``` + +This should show that the Kubernetes Service is running: + +```text +NAME READY STATUS RESTARTS AGE +teleport-agent-0 1/1 Running 0 22s +``` + +`tsh` should indicate that the cluster has registered with Teleport: + +```code +$ tsh kube ls +Kube Cluster Name Labels Selected +----------------- ---------------- -------- +minikube environment=demo +``` + +## Step 3/4. Write the client application + +At this point, we have set up an external RBAC system to use for generating +Teleport roles and configured Teleport to allow our API client to interact with +our Kubernetes cluster and Teleport cluster. In this step, we will write our +client application. + +### Set up your Go project + +Download the source code for the API client application: + +```code +$ git clone --depth=1 https://github.com/gravitational/teleport +$ cd teleport/examples/api-sync-roles +``` + +For the rest of this guide, we will show you how to set up the client +application and explore the ways it uses Teleport's API to automatically +generate Teleport roles. + +### Export identity files for the client application + +The `sync-kubernetes-rbac` user needs signed credentials in order to connect to +your Teleport cluster as well as your Kubernetes cluster. You will use the `tctl +auth sign` command to request these credentials for your API client. + +#### Connecting to your Teleport cluster + +The following `tctl auth sign` command impersonates the `sync-kubernetes-rbac` +user, generates signed credentials, and writes an identity file to the local +directory: + +```code +$ tctl auth sign --user=sync-kubernetes-rbac --out=auth.pem +``` + +The identity file, `auth.pem`, includes both TLS and SSH credentials. Your +client application uses the SSH credentials to connect to the Proxy Service, +which establishes a reverse tunnel connection to the Auth Service. The client +application uses this reverse tunnel, along with your TLS credentials, to +connect to the Auth Service's gRPC endpoint. + +#### Connecting to the Kubernetes cluster + +You will also need to give the client application a way to authenticate to your +Kubernetes cluster. To do this, use Teleport's certificate authority to sign +credentials for the `sync-kubernetes-rbac` user. Your API client will present +these credentials to authenticate to the Teleport Kubernetes Service, which will +proxy requests to the Kubernetes cluster. + +Run the following command, ensuring that includes +the host and port of your Proxy Service: + +```code +$ tctl auth sign --user=sync-kubernetes-rbac \ + --kube-cluster-name=minikube \ + --format=kubernetes \ + --proxy=https:// \ + --out=kubeconfig +``` + +### Imports + +In the `api-sync-roles` directory, open `main.go`, which contains the API client +program we demonstrate in this guide. + +Here are the packages our client application imports from Go's standard library: + +|Package|Description| +|---|---| +| `context` |Includes the `context.Context` type. `context.Context` is an abstraction for controlling long-running routines, such as connections to external services, that might fail or time out. Programs can cancel contexts or assign them timeouts and metadata.| +|`fmt`|Formatting data for printing, strings, or errors.| +|`io`|Dealing with I/O operations, e.g., reading files or network sockets.| +|`os`|Interacting with the operating system, e.g., to open files.| +|`time`|Dealing with time. We will use this to define a timeout for connecting to the Auth Service along with a ticker for executing our discovery logic in a loop.| + +The client imports the following third-party code: + +|Package|Description| +|---|---| +|`github.com/gravitational/``teleport/api/client`|A library for authenticating to the Auth Service's gRPC API and making requests, aliased as `teleport`.| +|`github.com/gravitational/``teleport/api/types`|Types used in the Auth Service API, e.g., Application Service records.| +|`github.com/gravitational/trace`|Presenting errors with more useful detail than the standard library provides.| +|`google.golang.org/grpc`|The gRPC client and server library.| +|`k8s.io/api/rbac/v1`|The Kubernetes RBAC API client library.| +|`k8s.io/apimachinery/``pkg/apis/meta/v1`|Code common to Kubernetes' API client libraries.| +|`k8s.io/client-go/kubernetes`|Setting up an general-purpose Kubernetes client.| +|`k8s.io/client-go/kubernetes/typed/rbac/v1`|Types for the Kubernetes RBAC API.| +|`k8s.io/client-go/tools/clientcmd`|Another general-purpose Kubernetes client library.| + +### Constants + +The program defines constants in a visible location so, later on, it's easier to +make them configurable outside the program: + +```go +const ( + proxyAddr string = "" + initTimeout = time.Duration(30) * time.Second + identityFilePath string = "auth.pem" + kubeconfigPath string = "kubeconfig" + clusterName string = "minikube" + roleAnnotationKey string = "create-teleport-role" +) +``` + +We will use these constants later in the program. They define some values we may +want to change later, including: + +|Constant|Description| +|---|---| +|`proxyAddr`|The host and port of the Teleport Proxy Service, e.g., `mytenant.teleport.sh:443`, which we will use to connect the client to your cluster. **Assign this to your own Proxy Service's host and port:** | +|`initTimeout`|The timeout for connecting to the Teleport cluster. We have defined this as 30 seconds.| +|`identityFilePath`|The path to the Teleport identity file you created earlier.| +|`clusterName`|The name of the Kubernetes cluster you will fetch RBAC resources from. In this guide, the cluster's name is `minikube`.| +|`roleAnnotationKey`|In Kubernetes, annotations are arbitrary key/value pairs that you can add to resources. The role and cluster role bindings we created earlier have the annotation key we specify here so our client application can fetch them.| + +### Initializing a Kubernetes RBAC client + +To contact the Kubernetes API, we will need to set up an HTTP client. The client +authenticates to the API using mutual TLS, loading a client certificate, +certificate authority, and private key from the file at `kubeconfigPath`. +Earlier in the guide, we requested this from the Teleport Auth Service. + +The program sets up a Kubernetes API client with the `getRBBACClient` function: + +```go +func getRBACClient() (v1.RbacV1Interface, error) { + f, err := os.Open(kubeconfigPath) + if err != nil { + return nil, trace.Wrap(err) + } + + kc, err := io.ReadAll(f) + if err != nil { + return nil, trace.Wrap(err) + } + n, err := clientcmd.RESTConfigFromKubeConfig(kc) + if err != nil { + return nil, trace.Wrap(err) + } + + c, err := kubernetes.NewForConfig(n) + if err != nil { + return nil, trace.Wrap(err) + } + + return c.RbacV1(), nil +} +``` + +`getRBBACClient` opens and reads the Kubernetes credentials file at +`kubeconfigPath`, then uses the file to set up a Kubernetes API client +configuration (`clientcmd.RESTConfigFromKubeConfig(kc)`) and, with that, an HTTP +client (`kubernetes.NewForConfig(n)`). + +Finally, it returns an interface to the Kubernetes API dedicated to role-based +access controls, which the rest of the program uses to interact with your +Kubernetes cluster. + +### Creating a Teleport role from a Kubernetes cluster role binding + +The `createTeleportRoleFromClusterRoleBinding` function creates a Teleport role +from a Kubernetes cluster role binding by populating fields in the former based +on fields in latter: + +```go +func createTeleportRoleFromClusterRoleBinding(teleport *client.Client, k types.KubeCluster, r rbacv1.ClusterRoleBinding) error { + if e, ok := r.Annotations[roleAnnotationKey]; !ok || e != "true" { + return nil + } + + role := types.RoleV6{} + role.SetMetadata(types.Metadata{ + Name: k.GetName() + "-" + r.RoleRef.Name + "-" + "cluster", + }) + + b := k.GetStaticLabels() + labels := make(types.Labels) + for k, v := range b { + labels[k] = []string{v} + } + role.SetKubernetesLabels(types.Allow, labels) + role.SetKubeResources(types.Allow, []types.KubernetesResource{ + types.KubernetesResource{ + Kind: "pod", + Namespace: "*", + Name: "*", + }, + }) + + var g []string + var u []string + for _, s := range r.Subjects { + if s.Kind == "User" || s.Kind == "ServiceAccount" { + u = append(u, s.Name) + continue + } + if s.Kind == "Group" { + g = append(g, s.Name) + continue + } + } + role.SetKubeGroups(types.Allow, g) + role.SetKubeUsers(types.Allow, u) + if err := teleport.UpsertRole( + context.Background(), + &role, + ); err != nil { + return trace.Wrap(err) + } + fmt.Println("Upserted Teleport role:", role.GetName()) + return nil +} +``` + +To avoid unexpected behavior, this function ignores Kubernetes-managed roles and +roles for internal systems like the Teleport Kubernetes Service. This function +checks the cluster role binding's metatada for an annotation with a specific +key, `roleAnnotationKey`, and ignores any resource where this key is not set to +`"true"`. + +We also want a quick way to identify roles we created with this program. To do +so, this function names all roles it generates based on cluster role bindings +according to the following attributes: + +- Kubernetes cluster name +- Kubernetes role name +- The suffix `-cluster`. + +In our demo application, this function will create a Teleport role called +`minikube-pod-ops-cluster`. + +The rest of the function assigns fields to a `types.RoleV6`, the Teleport API +client's role type, based on the cluster role binding: + +|Role field|Purpose|How we assign it| +|---|---|---| +|`allow.kubernetes_labels`|Labels for Teleport-registered Kubernetes clusters that a user with this role is allowed to access.|Based on the Teleport-registered Kubernetes cluster that the cluster role binding belongs to.| +|`allow.kubernetes_resources`|Kubernetes pods in specific namespaces that that a user with this role is allowed to access.|Allow access to all namespaces, since cluster role bindings are not restricted by namespace.| +|`allow.kubernetes_users` and `allow.kubernetes_groups`|The Kubernetes groups and users that a Teleport user with this role will assume when interacting with the Kubernetes cluster.|Supply the names of any users or groups connected to the cluster role binding.| + +### Creating a Teleport role from a Kubernetes role binding + +As with cluster role bindings, this program will also create Teleport roles +based on Kubernetes role bindings: + +```go +func createTeleportRoleFromRoleBinding(teleport *client.Client, k types.KubeCluster, r rbacv1.RoleBinding) error { + if e, ok := r.Annotations[roleAnnotationKey]; !ok || e != "true" { + return nil + } + + role := types.RoleV6{} + role.SetMetadata(types.Metadata{ + Name: k.GetName() + "-" + r.RoleRef.Name + "-" + r.Namespace, + }) + + b := k.GetStaticLabels() + labels := make(types.Labels) + for k, v := range b { + labels[k] = []string{v} + } + role.SetKubernetesLabels(types.Allow, labels) + role.SetKubeResources(types.Allow, []types.KubernetesResource{ + types.KubernetesResource{ + Kind: "pod", + Namespace: r.Namespace, + Name: "*", + }, + }) + var g []string + var u []string + for _, s := range r.Subjects { + if s.Kind == "User" || s.Kind == "ServiceAccount" { + u = append(u, s.Name) + continue + } + if s.Kind == "Group" { + g = append(g, s.Name) + continue + } + } + role.SetKubeGroups(types.Allow, g) + role.SetKubeUsers(types.Allow, u) + + if err := teleport.UpsertRole( + context.Background(), + &role, + ); err != nil { + return trace.Wrap(err) + } + fmt.Println("Upserted Teleport role:", role.GetName()) + return nil +} +``` + +While the overall behavior of this function is the same as +`createTeleportRoleFromClusterRoleBinding`, Kubernetes role bindings require +some differences in how we assign fields to Teleport roles: + +- When setting the name of the role, we use the role binding's namespace as the + suffix, rather than `-cluster`, to indicate the namespace that this role + applies to. +- In the role's `kubernetes_resources` field, the value has the same namespace + as the role binding, rather than applying to all namespaces. + +### Creating Teleport roles based on Kubernetes resources + +Now that we have functions to create Teleport roles based on individual +Kubernetes RBAC resources, we can fetch all RBAC resources from our Kubernetes +cluster and call these functions: + +```go +func createTeleportRolesForKubeCluster(teleport *client.Client, k types.KubeCluster) error { + rbac, err := getRBACClient() + if err != nil { + return trace.Wrap(err) + } + + crb, err := rbac.ClusterRoleBindings().List( + context.Background(), + metav1.ListOptions{}, + ) + if err != nil { + return trace.Wrap(err) + } + + for _, i := range crb.Items { + if err := createTeleportRoleFromClusterRoleBinding(teleport, k, i); err != nil { + return trace.Wrap(err) + } + } + + rb, err := rbac.RoleBindings("").List( + context.Background(), + metav1.ListOptions{}, + ) + if err != nil { + return trace.Wrap(err) + } + + for _, i := range rb.Items { + if err := createTeleportRoleFromRoleBinding(teleport, k, i); err != nil { + return trace.Wrap(err) + } + } + return nil +} +``` + +`createTeleportRolesForKubeCluster` takes a Teleport client and a +Teleport-registered Kubernetes cluster. It calls the `getRBACClient` function we +defined earlier to set up a client for the Kubernetes cluster. It then: + +- Lists Kubernetes cluster role bindings and creates a Teleport role for each one. +- Lists Kubernetes role bindings and creates a Teleport role for each one. + +### Initializing clients and start the application + +The functions we declared earlier require a Teleport API client and a +Teleport-registered Kubernetes cluster, and we initialize these in the +entrypoint of the program, the `main` function: + +```go +func main() { + ctx, cancel := context.WithTimeout(context.Background(), initTimeout) + defer cancel() + creds := client.LoadIdentityFile(identityFilePath) + + teleport, err := client.New(ctx, client.Config{ + Addrs: []string{proxyAddr}, + Credentials: []client.Credentials{creds}, + DialOpts: []grpc.DialOption{ + grpc.WithReturnConnectionError(), + }, + }) + if err != nil { + panic(err) + } + fmt.Println("Connected to Teleport") + + ks, err := teleport.GetKubernetesServers(context.Background()) + if err != nil { + panic(err) + } + for _, k := range ks { + if k.GetCluster().GetName() != clusterName { + continue + } + fmt.Println("Retrieved Kubernetes cluster", clusterName) + + if err := createTeleportRolesForKubeCluster(teleport, k.GetCluster()); err != nil { + panic(err) + } + fmt.Println("Created roles for Kubernetes cluster", clusterName) + return + } + panic("Unable to locate a Kubernetes Service instance for " + clusterName) +} +``` + +`client` is Teleport's library for setting up an API client. Our program +initializes a Teleport client by calling `client.LoadIdentityFile` to obtain a +`client.Credentials`. It then uses the `client.Credentials` to call +`client.New`, which connects to the Teleport Proxy Service specified in the +`Addrs` field using the provided identity file. + +In this example, we are passing the `grpc.WithReturnConnectionError()` function +call to `client.New`, which instructs the gRPC client to return more detailed +connection errors. + + + +This program does not validate your credentials or Teleport cluster address. +Make sure that: + +- The identity file you exported earlier does not have an expired TTL +- The value you supplied to the `Addrs` field in `teleport.Config` includes both + the host **and** the web port of your Teleport Proxy Service, e.g., + `mytenant.teleport.sh:443` + + + +After initializing a Teleport client, the `main` function fetches all Kubernetes +servers registered with Teleport (`teleport.GetKubernetesServers`) and checks if +there is a registered Kubernetes cluster that matches the one you specified. + +If a matching Kubernetes cluster exists, the code calls the +`createTeleportRolesForKubeClusters` function we defined earlier. If not, the +program prints an error message and a stack trace by calling Go's built-in +`panic` function. + +## Step 4/4. Test your client application + +To test the client application, start it up from within its project directory: + +```code +$ go run main.go +``` + +You should see the following output: + +```text +Connected to Teleport +Retrieved Kubernetes cluster minikube +Upserted Teleport role: minikube-pod-ops-cluster +Upserted Teleport role: minikube-pod-reader-app +Created roles for Kubernetes cluster minikube +``` + +Examine the new `minikube-pod-ops-cluster` role by running the command below: + +```code +$ tctl get roles/minikube-pod-ops-cluster +``` + +You should see output similar to the following: + +```yaml +kind: role +metadata: + id: 1678732494974032643 + name: minikube-pod-ops-cluster +spec: + allow: + kubernetes_groups: + - ops + kubernetes_labels: + environment: demo + kubernetes_resources: + - kind: pod + name: '*' + namespace: '*' + deny: {} + options: + cert_format: standard + create_host_user: false + desktop_clipboard: true + desktop_directory_sharing: true + enhanced_recording: + - command + - network + forward_agent: false + idp: + saml: + enabled: true + max_session_ttl: 30h0m0s + pin_source_ip: false + port_forwarding: true + record_session: + default: best_effort + desktop: true + ssh_file_copy: true +version: v6 +``` + +Compare this with the `minikube-pod-reader-app` role, which you can retrieve +with the following command: + +```code +$ tctl get roles/minikube-pod-reader-app +``` + +Here is the role we created: + +```yaml +kind: role +metadata: + id: 1678732495284493075 + name: minikube-pod-reader-app +spec: + allow: + kubernetes_groups: + - app-developer + kubernetes_labels: + environment: demo + kubernetes_resources: + - kind: pod + name: '*' + namespace: app + deny: {} + options: + cert_format: standard + create_host_user: false + desktop_clipboard: true + desktop_directory_sharing: true + enhanced_recording: + - command + - network + forward_agent: false + idp: + saml: + enabled: true + max_session_ttl: 30h0m0s + pin_source_ip: false + port_forwarding: true + record_session: + default: best_effort + desktop: true + ssh_file_copy: true +version: v6 +``` + +Since role bindings are namespaced, this role only allows access to pods in the +`app` namespace, where this role binding was applied. The Kubernetes Service +forwards traffic from users with this role using the `app-developer` Kubernetes +group. + +## Next steps + +We have implemented a Teleport API client that generates Teleport roles based on +the Kubernetes RBAC system. You can use Teleport's API to build similar +applications that interact with other RBAC systems, such as GitHub teams or +groups within your database management system. + +Here are some starting points for building out your client application. + +### Learn more about Teleport roles + +To write an effective client application that generates Teleport roles from an +external RBAC solution, you should understand the role fields that apply to +infrastructure resources you want to manage access to. + +See the links below for guides to fields related to different infrastructure +resources: + +- [Servers](../server-access/rbac.mdx) +- [Databases](../database-access/rbac.mdx) +- [Kubernetes clusters](../kubernetes-access/manage-access/rbac.mdx) +- [Windows Desktops](../desktop-access/rbac.mdx) +- [Applications](../application-access/controls.mdx) + +For general guidance, read our [Access Controls +Reference](../access-controls/reference.mdx). + +### Register your cloud provider with Teleport + +You can protect cloud provider APIs with Teleport and instruct your API client +applications to connect to these APIs via the Teleport Application Service. +Using Teleport-protected cloud provider APIs, you can generate Teleport roles +based on your cloud provider's RBAC solution. + +Read our guides for how to set up the Teleport Application Service for cloud +provider APIs: + +- [AWS](../application-access/cloud-apis/aws-console.mdx) +- [Google Cloud](../application-access/cloud-apis/google-cloud.mdx) +- [Azure](../application-access/cloud-apis/azure.mdx) + +### Consult examples + +The +[`gravitational/teleport-plugins`](https://github.com/gravitational/teleport-plugins) +repository contains examples of production-ready Teleport API clients. While we +currently do not maintain plugins that generate Teleport roles, you can use +these examples to see how to implement configuration parsing, retries, and other +tasks. + +### Provision the client application with short-lived credentials + +In this example, we used the `tctl auth sign` command to fetch credentials for +the program you wrote. For production usage, we recommend provisioning +short-lived credentials via Machine ID, which reduces the risk of these +credentials becoming stolen. View our [Machine ID +documentation](../machine-id/introduction.mdx) to learn more. diff --git a/docs/pages/application-access/cloud-apis/azure.mdx b/docs/pages/application-access/cloud-apis/azure.mdx index b81718bfce4db..2e10d39ec88fe 100644 --- a/docs/pages/application-access/cloud-apis/azure.mdx +++ b/docs/pages/application-access/cloud-apis/azure.mdx @@ -48,7 +48,7 @@ prevent unauthorized access to your organization's Azure identities. -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/4. Grant an identity to your VM diff --git a/docs/pages/application-access/cloud-apis/google-cloud.mdx b/docs/pages/application-access/cloud-apis/google-cloud.mdx index 74c9340c21f04..627308636184c 100644 --- a/docs/pages/application-access/cloud-apis/google-cloud.mdx +++ b/docs/pages/application-access/cloud-apis/google-cloud.mdx @@ -55,7 +55,7 @@ guide. -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/4. Configure Google Cloud diff --git a/docs/pages/application-access/getting-started.mdx b/docs/pages/application-access/getting-started.mdx index 7b844b7c4dad3..102807b06fd49 100644 --- a/docs/pages/application-access/getting-started.mdx +++ b/docs/pages/application-access/getting-started.mdx @@ -21,8 +21,7 @@ Let's connect to Grafana using Teleport Application Access in three steps: (!docs/pages/includes/edition-prereqs-tabs.mdx!) -(!docs/pages/includes/tctl.mdx!) - +- (!docs/pages/includes/tctl.mdx!) - A Docker installation, which we will use to launch Grafana in a container. Alternatively, if you have another web application you'd like to protect with Application Access, you can use that instead. - A host where you will run the Teleport Application Service. diff --git a/docs/pages/application-access/guides/dynamodb.mdx b/docs/pages/application-access/guides/dynamodb.mdx index 708e26434efb3..ffcf5b3ef4091 100644 --- a/docs/pages/application-access/guides/dynamodb.mdx +++ b/docs/pages/application-access/guides/dynamodb.mdx @@ -37,7 +37,7 @@ This guide will help you to: (!docs/pages/includes/edition-prereqs-tabs.mdx!) -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) If you have not yet deployed the Auth Service and Proxy Service, you should follow one of our [getting started guides](../getting-started.mdx) or try our Teleport Application Access [interactive learning track](https://play.instruqt.com/teleport/invite/rgvuva4gzkon). diff --git a/docs/pages/application-access/guides/tcp.mdx b/docs/pages/application-access/guides/tcp.mdx index 93546c082650b..c6853dd4a3fbb 100644 --- a/docs/pages/application-access/guides/tcp.mdx +++ b/docs/pages/application-access/guides/tcp.mdx @@ -11,8 +11,7 @@ servers or databases not yet natively supported in Database Access. (!docs/pages/includes/edition-prereqs-tabs.mdx!) -(!docs/pages/includes/tctl.mdx!) - +- (!docs/pages/includes/tctl.mdx!) - TCP application to connect to. In this guide we'll use a PostgreSQL running in Docker as an example. You can also use any TCP-based application you may already have. diff --git a/docs/pages/contributing/documentation/style-guide.mdx b/docs/pages/contributing/documentation/style-guide.mdx index 67b91461a75eb..21992871272e6 100644 --- a/docs/pages/contributing/documentation/style-guide.mdx +++ b/docs/pages/contributing/documentation/style-guide.mdx @@ -111,7 +111,6 @@ Specific kinds of architecture guides include: - Should be easy to navigate with a browser's search functionality. List all content on the same page. - Should avoid prose and be expressed with brevity. - ## General style rules Please refer to this style guide when determining how address questions about @@ -119,6 +118,14 @@ English grammar, usage, and so on. Since many of these rules have equally justifiable alternatives, a style guide prevents debates over these questions from getting in the way of documenting Teleport. + + +If a commonly debated style question does not have a resolution in this guide +(e.g., the Oxford comma), all we ask is that you keep your style consistent +within a particular page to maintain a professional polish. + + + ### Voice The documentation should be technically precise and directed toward a technical diff --git a/docs/pages/database-access/getting-started.mdx b/docs/pages/database-access/getting-started.mdx index abe9ec1913206..97fc01248e671 100644 --- a/docs/pages/database-access/getting-started.mdx +++ b/docs/pages/database-access/getting-started.mdx @@ -35,7 +35,7 @@ release. - A host, e.g., an EC2 instance, where you will run the Teleport Database Service. -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/4. Set up Aurora diff --git a/docs/pages/database-access/guides/aws-cassandra-keyspaces.mdx b/docs/pages/database-access/guides/aws-cassandra-keyspaces.mdx index c14a5a0dd0564..77d22104ebce6 100644 --- a/docs/pages/database-access/guides/aws-cassandra-keyspaces.mdx +++ b/docs/pages/database-access/guides/aws-cassandra-keyspaces.mdx @@ -35,7 +35,7 @@ This guide will help you to: - The `cqlsh` Cassandra client installed and added to your system's `PATH` environment variable. - A host, e.g., an Amazon EC2 instance, where you will run the Teleport Database Service. -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/5. Set up the Teleport Database Service diff --git a/docs/pages/database-access/guides/aws-dynamodb.mdx b/docs/pages/database-access/guides/aws-dynamodb.mdx index 9309b50b56067..8a18bcf50b7de 100644 --- a/docs/pages/database-access/guides/aws-dynamodb.mdx +++ b/docs/pages/database-access/guides/aws-dynamodb.mdx @@ -32,7 +32,7 @@ This guide will help you to: This guide assumes an EC2 instance when creating and applying IAM roles, and must be adjusted accordingly for custom configurations. -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) This guide provides an example configuration of IAM access roles as a model, diff --git a/docs/pages/database-access/guides/azure-postgres-mysql.mdx b/docs/pages/database-access/guides/azure-postgres-mysql.mdx index 5c02d75656448..2f077ef0c6191 100644 --- a/docs/pages/database-access/guides/azure-postgres-mysql.mdx +++ b/docs/pages/database-access/guides/azure-postgres-mysql.mdx @@ -214,7 +214,7 @@ Go to the [Subscriptions](https://portal.azure.com/#view/Microsoft_Azure_Billing Click on *Access control (IAM)* in the subscription and select *Add > Add custom role*:
-![IAM custom role](../../../img/database-access/guides/azure/add-custom-role@2x.png) +![IAM custom role](../../../img/azure/add-custom-role@2x.png)
In the custom role creation page, click the *JSON* tab and click *Edit*, then paste the JSON example diff --git a/docs/pages/database-access/guides/azure-redis.mdx b/docs/pages/database-access/guides/azure-redis.mdx index aac0fbcf60519..4f7bcdbbdb2ae 100644 --- a/docs/pages/database-access/guides/azure-redis.mdx +++ b/docs/pages/database-access/guides/azure-redis.mdx @@ -28,8 +28,7 @@ This guide will help you to: Service. - `redis-cli` version `6.2` or newer installed and added to your system's `PATH` environment variable. - -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/5. Create a Teleport user @@ -132,7 +131,7 @@ Now go to the [Subscriptions](https://portal.azure.com/#view/Microsoft_Azure_Bil Click on *Access control (IAM)* in the subscription and select *Add > Add custom role*:
-![IAM custom role](../../../img/database-access/guides/azure/add-custom-role@2x.png) +![IAM custom role](../../../img/azure/add-custom-role@2x.png)
In the custom role creation page, click the *JSON* tab and click *Edit*, then paste the JSON example diff --git a/docs/pages/database-access/guides/azure-sql-server-ad.mdx b/docs/pages/database-access/guides/azure-sql-server-ad.mdx index 215d8c802b414..467151ad7f34f 100644 --- a/docs/pages/database-access/guides/azure-sql-server-ad.mdx +++ b/docs/pages/database-access/guides/azure-sql-server-ad.mdx @@ -38,8 +38,7 @@ This guide will help you to: - SQL Server running on Azure. - The Teleport Database Service running on an Azure virtual instance. - -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/8. Create a Teleport user @@ -113,7 +112,7 @@ page and select a subscription. Click on **Access control (IAM)** in the subscription and select **Add** > **Add custom role**: -![IAM custom role](../../../img/database-access/guides/azure/add-custom-role@2x.png) +![IAM custom role](../../../img/azure/add-custom-role@2x.png) In the custom role creation page, click the **JSON** tab and click **Edit**, then paste the JSON example and replace the subscription in `assignableScopes` diff --git a/docs/pages/database-access/guides/cassandra-self-hosted.mdx b/docs/pages/database-access/guides/cassandra-self-hosted.mdx index 7edc53d919258..2613ab7fe6c09 100644 --- a/docs/pages/database-access/guides/cassandra-self-hosted.mdx +++ b/docs/pages/database-access/guides/cassandra-self-hosted.mdx @@ -33,8 +33,7 @@ This guide will help you to: - Self-hosted Cassandra or ScyllaDB instance. - The `cqlsh` Cassandra client installed and added to your system's `PATH` environment variable. - -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/5. Set up the Teleport Database Service diff --git a/docs/pages/database-access/guides/cockroachdb-self-hosted.mdx b/docs/pages/database-access/guides/cockroachdb-self-hosted.mdx index 26fdc0032aa78..739134650910b 100644 --- a/docs/pages/database-access/guides/cockroachdb-self-hosted.mdx +++ b/docs/pages/database-access/guides/cockroachdb-self-hosted.mdx @@ -19,7 +19,6 @@ This guide will help you to: 2. Configure mutual TLS authentication between Teleport and your CockroachDB cluster. 3. Connect to your CockroachDB cluster via Teleport. - ![Teleport Database Access CockroachDB Self-Hosted](../../../img/database-access/guides/cockroachdb_selfhosted.png) @@ -34,8 +33,7 @@ This guide will help you to: - CockroachDB cluster. - A host, e.g., an Amazon EC2 instance, where you will run the Teleport Database Service. - -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/4. Set up the Teleport Database Service diff --git a/docs/pages/database-access/guides/dynamic-registration.mdx b/docs/pages/database-access/guides/dynamic-registration.mdx index 8da5713d3548d..3373419c38f00 100644 --- a/docs/pages/database-access/guides/dynamic-registration.mdx +++ b/docs/pages/database-access/guides/dynamic-registration.mdx @@ -91,7 +91,7 @@ To create a database resource, run: $ tctl create database.yaml ``` -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) After the resource has been created, it will appear among the list of available databases (in `tsh db ls` or UI) as long as at least one Database Service diff --git a/docs/pages/database-access/guides/elastic.mdx b/docs/pages/database-access/guides/elastic.mdx index 437dc96af3b82..44113c78f4785 100644 --- a/docs/pages/database-access/guides/elastic.mdx +++ b/docs/pages/database-access/guides/elastic.mdx @@ -28,7 +28,7 @@ This guide will help you to configure secured access to an Elasticsearch databas See [Installation](../../installation.mdx) for details. -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/5. Set up the Teleport Database Service diff --git a/docs/pages/database-access/guides/mongodb-atlas.mdx b/docs/pages/database-access/guides/mongodb-atlas.mdx index e2d4e46f4a8fd..febab1dd80657 100644 --- a/docs/pages/database-access/guides/mongodb-atlas.mdx +++ b/docs/pages/database-access/guides/mongodb-atlas.mdx @@ -24,8 +24,7 @@ In this guide you will: - [MongoDB Atlas](https://www.mongodb.com/cloud/atlas) cluster. - A host, e.g., an Amazon EC2 instance, where you will run the Teleport Database Service. - -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/4. Set up the Teleport Database Service @@ -85,7 +84,8 @@ db_service: - name: "mongodb-atlas" protocol: "mongodb" uri: "mongodb+srv://cluster0.abcde.mongodb.net" - ca_cert_file: "/path/to/letsencrypt/isrgrootx1.pem" + tls: + ca_cert_file: "/path/to/letsencrypt/isrgrootx1.pem" static_labels: env: "dev" @@ -133,7 +133,8 @@ db_service: - name: "mongodb-atlas" protocol: "mongodb" uri: "mongodb+srv://cluster0.abcde.mongodb.net" - ca_cert_file: "/path/to/letsencrypt/isrgrootx1.pem" + tls: + ca_cert_file: "/path/to/letsencrypt/isrgrootx1.pem" static_labels: env: "dev" ``` diff --git a/docs/pages/database-access/guides/mongodb-self-hosted.mdx b/docs/pages/database-access/guides/mongodb-self-hosted.mdx index 87dc20a7f7ebb..7141e4875ac63 100644 --- a/docs/pages/database-access/guides/mongodb-self-hosted.mdx +++ b/docs/pages/database-access/guides/mongodb-self-hosted.mdx @@ -31,7 +31,7 @@ In this guide you will: April 2021 so if you're still using an older version, consider upgrading.
-(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/3. Install and configure Teleport diff --git a/docs/pages/database-access/guides/mysql-cloudsql.mdx b/docs/pages/database-access/guides/mysql-cloudsql.mdx index 977b750e581dd..93afe819803f2 100644 --- a/docs/pages/database-access/guides/mysql-cloudsql.mdx +++ b/docs/pages/database-access/guides/mysql-cloudsql.mdx @@ -30,8 +30,7 @@ Teleport Database Access for Cloud SQL MySQL is available starting from the - Google Cloud account - A host, e.g., a Compute Engine instance, where you will run the Teleport Database Service - -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/5. Create a service account for the Teleport Database Service diff --git a/docs/pages/database-access/guides/mysql-self-hosted.mdx b/docs/pages/database-access/guides/mysql-self-hosted.mdx index f963a5b9d0cc1..3ac02ef68606e 100644 --- a/docs/pages/database-access/guides/mysql-self-hosted.mdx +++ b/docs/pages/database-access/guides/mysql-self-hosted.mdx @@ -23,8 +23,7 @@ This guide will help you to: - A self-hosted MySQL or MariaDB instance. - A host, e.g., an Amazon EC2 instance, where you will run the Teleport Database Service. - -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/4. Set up the Teleport Database Service @@ -54,8 +53,8 @@ which you'll need to enable mutual TLS on your MySQL server. ## Step 3/4. Configure MySQL/MariaDB - - To configure MySQL to accept TLS connections, add the following to your + +To configure MySQL to accept TLS connections, add the following to your MySQL configuration file, `mysql.cnf`: ```conf @@ -65,9 +64,10 @@ ssl-ca=/path/to/server.cas ssl-cert=/path/to/server.crt ssl-key=/path/to/server.key ``` - - - To configure MariaDB to accept TLS connections, add the following to your + + + +To configure MariaDB to accept TLS connections, add the following to your MariaDB configuration file, `mysql.cnf`: ```conf @@ -77,29 +77,55 @@ ssl-ca=/path/to/server.cas ssl-cert=/path/to/server.crt ssl-key=/path/to/server.key ``` - + + Additionally, your MySQL/MariaDB database user accounts must be configured to require a -valid client certificate. If you're creating a new user: +valid client certificate. + + + +Create a new user: ```sql CREATE USER 'alice'@'%' REQUIRE SUBJECT '/CN=alice'; ``` -If you're updating an existing user: +By default, the created user may not have access to anything and won't be able +to connect, so let's grant it some permissions: + +```sql +GRANT ALL ON `%`.* TO 'alice'@'%'; +``` + + +This is an example command that grants database-wide permissions to a user. +In a production environment you should follow the principle of least privilege + + + + +Because Teleport uses certificates to authenticate database users, the user must +not have a password set. Note that removing an existing user's password may break +existing integrations. Consider using a new Database user specifically for Teleport +access. + +Update the existing user to require a valid certificate: ```sql ALTER USER 'alice'@'%' REQUIRE SUBJECT '/CN=alice'; ``` -By default, the created user may not have access to anything and won't be able -to connect, so let's grant it some permissions: +Remove the password from the user: ```sql -GRANT ALL ON `%`.* TO 'alice'@'%'; +SET PASSWORD FOR 'alice'@'%' = PASSWORD(""); ``` + + + See [Configuring MySQL to Use Encrypted Connections](https://dev.mysql.com/doc/refman/8.0/en/using-encrypted-connections.html) in the MySQL documentation or diff --git a/docs/pages/database-access/guides/postgres-cloudsql.mdx b/docs/pages/database-access/guides/postgres-cloudsql.mdx index b2855b29b5e41..6ae1868421b9e 100644 --- a/docs/pages/database-access/guides/postgres-cloudsql.mdx +++ b/docs/pages/database-access/guides/postgres-cloudsql.mdx @@ -25,8 +25,7 @@ This guide will help you to: - Command-line client `psql` installed and added to your system's `PATH` environment variable. - A host, e.g., a Compute Engine instance, where you will run the Teleport Database Service - -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/7. Enable Cloud SQL IAM authentication diff --git a/docs/pages/database-access/guides/postgres-redshift.mdx b/docs/pages/database-access/guides/postgres-redshift.mdx index 8f8ec94754325..520758c197b94 100644 --- a/docs/pages/database-access/guides/postgres-redshift.mdx +++ b/docs/pages/database-access/guides/postgres-redshift.mdx @@ -26,8 +26,7 @@ This guide will help you to: - Command-line client `psql` installed and added to your system's `PATH` environment variable. - A host, e.g., an EC2 instance, where you will run the Teleport Database Service. - -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/5. Create a Teleport user diff --git a/docs/pages/database-access/guides/postgres-self-hosted.mdx b/docs/pages/database-access/guides/postgres-self-hosted.mdx index 795a3d5080abc..c40f278203a2e 100644 --- a/docs/pages/database-access/guides/postgres-self-hosted.mdx +++ b/docs/pages/database-access/guides/postgres-self-hosted.mdx @@ -24,8 +24,7 @@ This guide will help you to: - Command-line client `psql` installed and added to your system's `PATH` environment variable. - A host, e.g., an Amazon EC2 instance, where you will run the Teleport Database Service. - -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/5. Set up the Teleport Database Service diff --git a/docs/pages/database-access/guides/rds-proxy.mdx b/docs/pages/database-access/guides/rds-proxy.mdx index c03f3715f57df..b6ade327a6de9 100644 --- a/docs/pages/database-access/guides/rds-proxy.mdx +++ b/docs/pages/database-access/guides/rds-proxy.mdx @@ -25,8 +25,7 @@ Teleport currently supports RDS Proxy instances with engine family - Any RDS Proxy instances intended for connection through Teleport must have TLS enabled. - A host, e.g., an EC2 instance, where you will run the Teleport Database Service. - -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/5. Create a Database Service configuration diff --git a/docs/pages/database-access/guides/rds.mdx b/docs/pages/database-access/guides/rds.mdx index dbb9ac6204952..608577f327530 100644 --- a/docs/pages/database-access/guides/rds.mdx +++ b/docs/pages/database-access/guides/rds.mdx @@ -35,8 +35,7 @@ This guide will help you to: IAM policies. - A host, e.g., an EC2 instance, where you will run the Teleport Database Service. - -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/6. Create a Teleport user diff --git a/docs/pages/database-access/guides/redis-aws.mdx b/docs/pages/database-access/guides/redis-aws.mdx index 69f755c536e9d..b0b8d935cd6de 100644 --- a/docs/pages/database-access/guides/redis-aws.mdx +++ b/docs/pages/database-access/guides/redis-aws.mdx @@ -27,7 +27,7 @@ This guide will help you to: - A host, e.g., an EC2 instance, where you will run the Teleport Database Service. -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/6. Create a Teleport user diff --git a/docs/pages/database-access/guides/redis-cluster.mdx b/docs/pages/database-access/guides/redis-cluster.mdx index 078f573b51b97..c9ca66e3332ff 100644 --- a/docs/pages/database-access/guides/redis-cluster.mdx +++ b/docs/pages/database-access/guides/redis-cluster.mdx @@ -40,7 +40,7 @@ This guide will help you to: Redis `7.0` and RESP3 (REdis Serialization Protocol) are currently not supported.
-(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/6. Set up the Teleport Database Service diff --git a/docs/pages/database-access/guides/redis.mdx b/docs/pages/database-access/guides/redis.mdx index 2f6a8d96e346d..3bebb8c7dc41b 100644 --- a/docs/pages/database-access/guides/redis.mdx +++ b/docs/pages/database-access/guides/redis.mdx @@ -45,7 +45,7 @@ This guide will help you to: Redis `7.0` and RESP3 (REdis Serialization Protocol) are currently not supported. -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/5. Set up the Teleport Database Service diff --git a/docs/pages/database-access/guides/snowflake.mdx b/docs/pages/database-access/guides/snowflake.mdx index 8825a60d8dee4..f0b542ca787b1 100644 --- a/docs/pages/database-access/guides/snowflake.mdx +++ b/docs/pages/database-access/guides/snowflake.mdx @@ -38,7 +38,7 @@ This guide will help you to: See [Installation](../../installation.mdx) for details. -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/5. Set up the Teleport Database Service diff --git a/docs/pages/database-access/guides/sql-server-ad.mdx b/docs/pages/database-access/guides/sql-server-ad.mdx index 92743aff71ed9..bafe5d90e66ed 100644 --- a/docs/pages/database-access/guides/sql-server-ad.mdx +++ b/docs/pages/database-access/guides/sql-server-ad.mdx @@ -44,8 +44,7 @@ Directory authentication. - A Windows machine joined to the same Active Directory domain as the database. - A Linux node joined to the same Active Directory domain as the database. This guide will walk you through the joining steps if you don't have one. - -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/7. Create a Teleport user diff --git a/docs/pages/desktop-access/active-directory-manual.mdx b/docs/pages/desktop-access/active-directory-manual.mdx index aeb50786be3c8..ed61d54b6191d 100644 --- a/docs/pages/desktop-access/active-directory-manual.mdx +++ b/docs/pages/desktop-access/active-directory-manual.mdx @@ -189,14 +189,12 @@ These steps will need to be repeated if Teleport's user certificate authority is -1. Get the Teleport user CA certificate by running: +Get the Teleport user CA certificate by running the following in the Windows machine where you can manage your group policy, assigning to the address of your Teleport Proxy Service: ```code -$ tctl auth export --type=windows > user-ca.cer +$ curl 'https:///webapi/auth/export?type=windows' > user-ca.cer ``` -2. Transfer the `user-ca.cer` file to a Windows machine where you can manage your group policy. - Take note of the path to the `user-ca.cer` file, as you will need this in the next step. diff --git a/docs/pages/desktop-access/directory-sharing.mdx b/docs/pages/desktop-access/directory-sharing.mdx index 8200ed73c2539..d3a87e50e8f6b 100644 --- a/docs/pages/desktop-access/directory-sharing.mdx +++ b/docs/pages/desktop-access/directory-sharing.mdx @@ -36,7 +36,7 @@ after the session ends. You can see a full compatibility table in the [Mozilla Developer Network documentation](https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API#browser_compatibility). -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/4. Share a directory diff --git a/docs/pages/desktop-access/getting-started.mdx b/docs/pages/desktop-access/getting-started.mdx index b238be628060a..de5e8f388ea26 100644 --- a/docs/pages/desktop-access/getting-started.mdx +++ b/docs/pages/desktop-access/getting-started.mdx @@ -37,8 +37,7 @@ an [Active Directory domain](./active-directory.mdx). You can reuse an existing server running any other Teleport instance. - A server or virtual machine running a Windows operating system with Remote Desktop enabled and the RDP port available to the Linux server. - -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/4. Prepare Windows @@ -47,14 +46,12 @@ to your Windows system, and prepare it for passwordless access through Teleport. ### Import the Teleport root certificate -Use `tctl` to export the Teleport user certificate authority: +Export the Teleport user certificate authority by running the following from your Windows system: ```code -$ tctl auth export --type=windows > teleport.cer +$ curl 'https://teleport-proxy.example.com:443/webapi/auth/export?type=windows' > teleport.cer ``` -Copy this certificate to your Windows system, if you didn't run `tctl` from there. - ### Install the Teleport service for Windows From the Windows system, download the [Teleport Windows Auth diff --git a/docs/pages/desktop-access/reference/cli.mdx b/docs/pages/desktop-access/reference/cli.mdx index a244da40c8353..474c0da1e16ff 100644 --- a/docs/pages/desktop-access/reference/cli.mdx +++ b/docs/pages/desktop-access/reference/cli.mdx @@ -5,7 +5,7 @@ description: CLI reference for Teleport Desktop Access. The following `tctl` commands are used to manage Teleport Desktop Access. -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) Generate a join token for a Windows Desktop Service: diff --git a/docs/pages/desktop-access/troubleshooting.mdx b/docs/pages/desktop-access/troubleshooting.mdx index c572bf1b96127..551b6e561b156 100644 --- a/docs/pages/desktop-access/troubleshooting.mdx +++ b/docs/pages/desktop-access/troubleshooting.mdx @@ -56,29 +56,12 @@ This means that the host does not trust the Teleport CA. First, make sure that you [import the Teleport CA into Group Policy](./active-directory-manual.mdx#create-another-gpo-and-import-the-teleport-ca). Note that if the Teleport CA was rotated since the last import, you will have to fetch the -new CA using the following command: - - - -```code -# Log in to your cluster with tsh so you can use tctl from your local machine. -# You can also run tctl on your Auth Service host without running "tsh login" -# first. -$ tsh login --proxy=teleport.example.com --user=myuser -$ tctl auth export --type=windows >user-ca.cer -``` - - - +new CA using the following command, assigning to the address of your Teleport Proxy Service: ```code -# Log in to your Teleport cluster so you can use tctl remotely. -$ tsh login --proxy=mytenant.teleport.sh --user=myuser -$ tctl auth export --type=windows >user-ca.cer +$ curl 'https:///webapi/auth/export?type=windows' > user-ca.cer ``` - - If that doesn't help, log into the target host directly, open PowerShell and run `gpupdate.exe /force`. This forces a Group Policy sync and should pick up the new CA. diff --git a/docs/pages/includes/access-control-guides.mdx b/docs/pages/includes/access-control-guides.mdx index 80efb8b52fcfc..01c1439da73e0 100644 --- a/docs/pages/includes/access-control-guides.mdx +++ b/docs/pages/includes/access-control-guides.mdx @@ -8,4 +8,5 @@ - [Moderated Sessions](../access-controls/guides/moderated-sessions.mdx): Require session auditors and allow fine-grained live session access. - [Hardware Key Support (Preview)](../access-controls/guides/hardware-key-support.mdx): Enforce the use of hardware-based private keys. - [Device Trust (Preview)](../access-controls/guides/device-trust.mdx): Register and enforce trusted devices. -- [IP Pinning (Preview)](../access-controls/guides/ip-pinning.mdx): Pin a user's certificates to a login IP address. \ No newline at end of file +- [Headless WebAuthn (Preview)](../access-controls/guides/headless.mdx): Login with Webauthn from a remote device. +- [IP Pinning (Preview)](../access-controls/guides/ip-pinning.mdx): Pin a user's certificates to a login IP address. diff --git a/docs/pages/includes/config-reference/auth-service.yaml b/docs/pages/includes/config-reference/auth-service.yaml index e557c81b2527a..d47ebc0551989 100644 --- a/docs/pages/includes/config-reference/auth-service.yaml +++ b/docs/pages/includes/config-reference/auth-service.yaml @@ -141,16 +141,23 @@ auth_service: # - 'otp' and 'webauthn' require the corresponding second factor. second_factor: otp - # Sets whether passwordless authorization is allowed. + # Sets whether passwordless authentication is allowed. # Passwordless requires WebAuthn. # Defaults to "true". #passwordless: true - # Sets the authenticator connector for SSO (Enterprise) or the default - # connector for "local" authentication. - # See SSO for Enterprise (https://goteleport.com/docs/enterprise/sso/). - # See Passwordless for local - # (http://goteleport.com/docs/access-controls/guides/passwordless/#optional-enable-passwordless-by-default). + # Sets whether headless authentication is allowed. + # Headless authentication requires WebAuthn. + # Defaults to "true". + #headless: true + + # Sets the default authentication connector for the cluster: + # - 'local' for local authentication (password, WebAuthn, etc.) + # - 'passwordless' for passwordless authentication + # (http://goteleport.com/docs/access-controls/guides/passwordless/#optional-enable-passwordless-by-default) + # - 'headless' for headless authentication + # (http://goteleport.com/docs/access-controls/guides/headless-login/#optional-enable-passwordless-by-default) + # - A specific SSO connector name - see https://goteleport.com/docs/access-controls/sso/ for details. # Defaults to "local". #connector_name: local diff --git a/docs/pages/includes/database-access/azure-configure-service-principal.mdx b/docs/pages/includes/database-access/azure-configure-service-principal.mdx index bc88fb5323526..18c95d343ae04 100644 --- a/docs/pages/includes/database-access/azure-configure-service-principal.mdx +++ b/docs/pages/includes/database-access/azure-configure-service-principal.mdx @@ -15,7 +15,7 @@ resources: page in your Azure portal and click *Create* to create a new user-assigned managed identity: - ![Managed identities](../../../img/database-access/guides/azure/managed-identities@2x.png) + ![Managed identities](../../../img/azure/managed-identities@2x.png) Pick a name and resource group for the new identity and create it: @@ -44,7 +44,7 @@ resources: Go the the [App registrations](https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredApps) page of your Azure Active Directory and click on *New registration*: - ![App registrations](../../../img/database-access/guides/azure/app-registrations@2x.png) + ![App registrations](../../../img/azure/app-registrations@2x.png) Pick a name (e.g. *DatabaseService*) and register a new application. Once the app has been created, take note of its *Application (client) ID* and click on @@ -55,7 +55,7 @@ resources: Create a new client secret that the Database Service agent will use to authenticate with the Azure API: - ![Registered app secrets](../../../img/database-access/guides/azure/registered-app-secrets@2x.png) + ![Registered app secrets](../../../img/azure/registered-app-secrets@2x.png) The Teleport Database Service uses Azure SDK's default credential provider chain to look for credentials. Refer to [Azure SDK Authorization](https://docs.microsoft.com/en-us/azure/developer/go/azure-sdk-authorization) diff --git a/docs/pages/includes/machine-id/guides.mdx b/docs/pages/includes/machine-id/guides.mdx index 494bd86247d08..933d1c85f4eb6 100644 --- a/docs/pages/includes/machine-id/guides.mdx +++ b/docs/pages/includes/machine-id/guides.mdx @@ -6,3 +6,4 @@ - [Using Machine ID with CircleCI](../../machine-id/guides/circleci.mdx): How to use Machine ID to to access Teleport resources from CircleCI. - [Using Machine ID with Jenkins](../../machine-id/guides/jenkins.mdx): How to integrate Machine ID with Jenkins. - [Using Machine ID with Kubernetes](../../machine-id/guides/kubernetes.mdx): How to use Machine ID to connect automated services to Kubernetes clusters. +- [Using Machine ID with GitLab CI](../../machine-id/guides/gitlab.mdx): How to use Machine ID to SSH into Teleport nodes from GitLab CI. diff --git a/docs/pages/includes/machine-id/kubernetes-machineidnote.mdx b/docs/pages/includes/machine-id/kubernetes-machineidnote.mdx new file mode 100644 index 0000000000000..e83c2e80ee57c --- /dev/null +++ b/docs/pages/includes/machine-id/kubernetes-machineidnote.mdx @@ -0,0 +1,30 @@ + +For self-hosted Teleport Clusters that have non-TLS routing the Kubernetes public +address must be set for Machine ID Kubernetes connections. + +To confirm the TLS routing mode check the `proxy.tls_routing_enabled` from this +command with your proxy address: + +```code +$ curl https://teleport.example.com:443/webapi/ping | jq +``` + +The optional tool [`jq`](https://stedolan.github.io/jq/) is used here to help display the JSON output. +If the value `proxy.tls_routing_enabled` is `false` then non-TLS routing +is set and a Kubernetes public address is required so Machine ID +will connect to the right port. You can confirm the Kubernetes public +address is set if `proxy.kube.public_addr` is populated. + +The `kube_public_addr` is set within the `proxy_service` by Teleport administrators: + +```yaml +proxy_service: + enabled: true + kube_listen_addr: 0.0.0.0:3026 + kube_public_addr: teleport.example.com:3026 +``` + + diff --git a/docs/pages/includes/plugins/identity-export.mdx b/docs/pages/includes/plugins/identity-export.mdx index 1024b9ae160d5..350ab8b7d775d 100644 --- a/docs/pages/includes/plugins/identity-export.mdx +++ b/docs/pages/includes/plugins/identity-export.mdx @@ -1,23 +1,22 @@ -Like all Teleport users, `access-plugin` needs signed credentials in +Like all Teleport users, `{{ user }}` needs signed credentials in order to connect to your Teleport cluster. You will use the `tctl auth sign` command to request these credentials for your plugin. -The following `tctl auth sign` command impersonates the `access-plugin` user, +The following `tctl auth sign` command impersonates the `{{ user }}` user, generates signed credentials, and writes an identity file to the local directory: ```code -$ tctl auth sign --user=access-plugin --out=auth.pem +$ tctl auth sign --user={{ user }} --out=auth.pem ``` -Teleport's Access Request plugins listen for new and updated Access Requests by -connecting to the Teleport Auth Service's gRPC endpoint over TLS. +The plugin connects to the Teleport Auth Service's gRPC endpoint over TLS. -The identity file, `auth.pem`, includes both TLS and SSH credentials. Your -Access Request plugin uses the SSH credentials to connect to the Proxy Service, -which establishes a reverse tunnel connection to the Auth Service. The plugin -uses this reverse tunnel, along with your TLS credentials, to connect to the -Auth Service's gRPC endpoint. +The identity file, `auth.pem`, includes both TLS and SSH credentials. The plugin +uses the SSH credentials to connect to the Proxy Service, which establishes a +reverse tunnel connection to the Auth Service. The plugin uses this reverse +tunnel, along with your TLS credentials, to connect to the Auth Service's gRPC +endpoint. You will refer to this file later when configuring the plugin. diff --git a/docs/pages/includes/server-access/azure-assign-service-principal.mdx b/docs/pages/includes/server-access/azure-assign-service-principal.mdx new file mode 100644 index 0000000000000..d4149625858ce --- /dev/null +++ b/docs/pages/includes/server-access/azure-assign-service-principal.mdx @@ -0,0 +1,17 @@ +To grant Teleport permissions, the custom role you created must be assigned to the Teleport service principal - +either the managed identity or the app registration you created earlier. + +Navigate to the resource scope where you want to make the role assignment. Click *Access control (IAM)* and +select *Add > Add role assignment*. Choose the custom role you created as the role and the Teleport +service principal as a member. + +
+![Assign role](../../../img/server-access/guides/azure/create-role-assignment@2x.png) +
+ + +The role assignment should be at a high enough scope to allow the Teleport Discovery Service to discover +all matching virtual machines. See +[Identify the needed scope](https://docs.microsoft.com/en-us/azure/role-based-access-control/role-assignments-steps#step-3-identify-the-needed-scope) +for more information about Azure scopes and creating role assignments. + diff --git a/docs/pages/includes/start-teleport.mdx b/docs/pages/includes/start-teleport.mdx index a74bbba483ef8..1ad822cde19bb 100644 --- a/docs/pages/includes/start-teleport.mdx +++ b/docs/pages/includes/start-teleport.mdx @@ -7,9 +7,10 @@ creating a systemd service for it. The instructions depend on how you installed -On the host where you will run {{ service }}, start Teleport: +On the host where you will run {{ service }}, enable and start Teleport: ```code +$ sudo systemctl enable teleport $ sudo systemctl start teleport ``` diff --git a/docs/pages/includes/tctl.mdx b/docs/pages/includes/tctl.mdx index 4b697ba811210..d759381f6a341 100644 --- a/docs/pages/includes/tctl.mdx +++ b/docs/pages/includes/tctl.mdx @@ -1,9 +1,4 @@ -
+ To connect to Teleport, log in to your cluster using `tsh`, then use `tctl` remotely: @@ -20,13 +15,8 @@ You can run subsequent `tctl` commands in this guide on your local machine. For full privileges, you can also run `tctl` commands on your Auth Service host. -
-
+ + To connect to Teleport, log in to your cluster using `tsh`, then use `tctl` remotely: @@ -41,4 +31,4 @@ $ tctl status You must run subsequent `tctl` commands in this guide on your local machine. -
+ diff --git a/docs/pages/kubernetes-access/discovery/google-cloud.mdx b/docs/pages/kubernetes-access/discovery/google-cloud.mdx index 73c379521e506..1befef790c74f 100644 --- a/docs/pages/kubernetes-access/discovery/google-cloud.mdx +++ b/docs/pages/kubernetes-access/discovery/google-cloud.mdx @@ -31,7 +31,7 @@ Auto-Discovery for GKE. services. You can run this host on any cloud provider or even use a local machine. -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/3. Obtain Google Cloud credentials diff --git a/docs/pages/kubernetes-access/getting-started.mdx b/docs/pages/kubernetes-access/getting-started.mdx index 39b311bb2bd92..b981dc3d0d0f9 100644 --- a/docs/pages/kubernetes-access/getting-started.mdx +++ b/docs/pages/kubernetes-access/getting-started.mdx @@ -27,7 +27,7 @@ Standalone Teleport Cluster](./register-clusters/static-kubeconfig.mdx). (!docs/pages/includes/kubernetes-access/helm-k8s.mdx!) -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Deployment overview diff --git a/docs/pages/kubernetes-access/manage-access/rbac.mdx b/docs/pages/kubernetes-access/manage-access/rbac.mdx index fe69c5d6b4416..8326f84ea7fb3 100644 --- a/docs/pages/kubernetes-access/manage-access/rbac.mdx +++ b/docs/pages/kubernetes-access/manage-access/rbac.mdx @@ -20,7 +20,7 @@ Kubernetes clusers, groups, users, and resources. (!docs/pages/includes/edition-prereqs-tabs.mdx!) -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) To run the local demo environment, ensure that you have the following tools installed on your workstation: diff --git a/docs/pages/kubernetes-access/register-clusters/dynamic-registration.mdx b/docs/pages/kubernetes-access/register-clusters/dynamic-registration.mdx index 391abe1b304c0..0833f82c45fe3 100644 --- a/docs/pages/kubernetes-access/register-clusters/dynamic-registration.mdx +++ b/docs/pages/kubernetes-access/register-clusters/dynamic-registration.mdx @@ -32,7 +32,7 @@ registration, then create, list, update, and delete Kubernetes clusters via permissions to create namespaces, secrets, service accounts, cluster roles, and cluster role bindings in the cluster. -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/3. Set up the Teleport Kubernetes Service diff --git a/docs/pages/kubernetes-access/register-clusters/iam-joining.mdx b/docs/pages/kubernetes-access/register-clusters/iam-joining.mdx index 1546b70f94e38..dccfea7922e9d 100644 --- a/docs/pages/kubernetes-access/register-clusters/iam-joining.mdx +++ b/docs/pages/kubernetes-access/register-clusters/iam-joining.mdx @@ -27,7 +27,7 @@ to automatically join the cluster on subsequent restarts. (!docs/pages/includes/helm.mdx!) -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/3. Create a Kubernetes service account with an IAM identity diff --git a/docs/pages/kubernetes-access/register-clusters/register-via-deployment.mdx b/docs/pages/kubernetes-access/register-clusters/register-via-deployment.mdx index d5a974e821d17..f936b94eeefcf 100644 --- a/docs/pages/kubernetes-access/register-clusters/register-via-deployment.mdx +++ b/docs/pages/kubernetes-access/register-clusters/register-via-deployment.mdx @@ -27,7 +27,7 @@ Teleport Kubernetes Service on each cluster you want to register. (!docs/pages/includes/helm.mdx!) -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Connecting clusters diff --git a/docs/pages/kubernetes-access/register-clusters/static-kubeconfig.mdx b/docs/pages/kubernetes-access/register-clusters/static-kubeconfig.mdx index 66860352c8056..aced14c6e5fef 100644 --- a/docs/pages/kubernetes-access/register-clusters/static-kubeconfig.mdx +++ b/docs/pages/kubernetes-access/register-clusters/static-kubeconfig.mdx @@ -23,8 +23,7 @@ authenticate to the API server of your chosen Kubernetes cluster. Kubernetes Service. This can run outside of your Kubernetes cluster. - The [`kubectl`](https://kubernetes.io/docs/reference/kubectl/) command line tool installed on your workstation. - -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/4. Generate a kubeconfig file diff --git a/docs/pages/machine-id/guides/applications.mdx b/docs/pages/machine-id/guides/applications.mdx index abfe5908fc5ae..ac760e0cbab32 100644 --- a/docs/pages/machine-id/guides/applications.mdx +++ b/docs/pages/machine-id/guides/applications.mdx @@ -26,7 +26,7 @@ If you're not already familiar with Machine ID, follow the [Getting Started Guide](../getting-started.mdx) to familiarize yourself with Machine ID. You'll also need `tctl` access to initially configure the bot. -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) Lastly, ensure the `tbot` binary is installed on your Machine ID client system. The client system is any system from which you want to access your Teleport diff --git a/docs/pages/machine-id/guides/circleci.mdx b/docs/pages/machine-id/guides/circleci.mdx index 81ee923c56cd8..62a93877b454e 100644 --- a/docs/pages/machine-id/guides/circleci.mdx +++ b/docs/pages/machine-id/guides/circleci.mdx @@ -25,8 +25,7 @@ control. (!docs/pages/includes/edition-prereqs-tabs.mdx!) -(!docs/pages/includes/tctl.mdx!) - +- (!docs/pages/includes/tctl.mdx!) - A running instance of the Teleport SSH Service that you have registered with your Teleport cluster. For instructions on setting this up, see the [Getting Started Guide](../../server-access/introduction.mdx). The SSH node must diff --git a/docs/pages/machine-id/guides/databases.mdx b/docs/pages/machine-id/guides/databases.mdx index 97741e505e5fe..361e6aa7eafbb 100644 --- a/docs/pages/machine-id/guides/databases.mdx +++ b/docs/pages/machine-id/guides/databases.mdx @@ -34,7 +34,7 @@ If you have not already set up Machine ID, follow the [Machine ID Getting Started Guide](../getting-started.mdx) to familiarize yourself with Machine ID. You'll need `tctl` access to initially configure the bot. -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) Lastly, ensure both the `tbot` and `tsh` executables are available on your application host. See [Installation](../../installation.mdx) for details. diff --git a/docs/pages/machine-id/guides/github-actions-kubernetes.mdx b/docs/pages/machine-id/guides/github-actions-kubernetes.mdx index d9adf320eb035..5af4508bd67e8 100644 --- a/docs/pages/machine-id/guides/github-actions-kubernetes.mdx +++ b/docs/pages/machine-id/guides/github-actions-kubernetes.mdx @@ -27,7 +27,7 @@ Actions runners as well as GitHub Enterprise Server. (!docs/pages/includes/edition-prereqs-tabs.mdx!) -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) - A Kubernetes cluster connected to your Teleport cluster. If you do not already have one configured, try our @@ -38,7 +38,9 @@ In our examples, this Kubernetes cluster will be named `my-kubernetes-cluster`. `gravitational/example`, and this value should be replaced with your own unique repository. -### 1/4. Create a join token for GitHub Actions +(!docs/pages/includes/machine-id/kubernetes-machineidnote.mdx!) + +## Step 1/4. Create a join token for GitHub Actions (!docs/pages/includes/machine-id/github-actions-create-token.mdx!) diff --git a/docs/pages/machine-id/guides/github-actions.mdx b/docs/pages/machine-id/guides/github-actions.mdx index 2449e2a9bed50..c6a1cbdbd1dc7 100644 --- a/docs/pages/machine-id/guides/github-actions.mdx +++ b/docs/pages/machine-id/guides/github-actions.mdx @@ -27,8 +27,7 @@ Actions runners as well as GitHub Enterprise Server. (!docs/pages/includes/edition-prereqs-tabs.mdx!) -(!docs/pages/includes/tctl.mdx!) - +- (!docs/pages/includes/tctl.mdx!) - A node that is a part of the Teleport cluster with [Server Access](https://goteleport.com/docs/server-access/introduction/). - Your user should have the privileges to create token resources. - A GitHub repository with GitHub Actions enabled. This guide uses the example `gravitational/example` @@ -40,17 +39,17 @@ repo, however this value should be replaced with your own unique repo. ## Step 2/3. Create your Machine ID bot -With the join token for GitHub Actions created, the next step is to create the Machine ID bot, -and ensure that it is configured to use the newly created token. +With the join token created, the next step is to create the Machine ID bot that +the token will grant access to. -The Machine ID bot created in this example will be used to access a specific node on the cluster -via `tsh ssh`, and will therefore require a role that can access the cluster as needed. This example -configuration will apply the `access` role, however care should be taken to either create or apply -a role according to the principle of least privilege in production environments. Additionally, it -should have explicit access to the cluster using a username created specifically for the bot user alone. +In this example, the bot is given the preset `access` role. In a production +environment, the principle of least privilege should be applied and you should +create a role that grants the bot access to the precise resources that will be +needed in your CI/CD workflow. -Enter the following command from your workstation with `tsh` access, replacing the `username` value with -a Linux user on the host that you want your GitHub Actions flow to be able to connect to: +From your workstation enter the following command, replacing the`username` +value with a Linux user on the host that you want your GitHub Actions flow to +be able to connect to: ```code $ tctl bots add github-demo --roles=access --token=github-token --logins=username diff --git a/docs/pages/machine-id/guides/gitlab.mdx b/docs/pages/machine-id/guides/gitlab.mdx new file mode 100644 index 0000000000000..09254c5964ce3 --- /dev/null +++ b/docs/pages/machine-id/guides/gitlab.mdx @@ -0,0 +1,160 @@ +--- +title: Using Machine ID with GitLab CI +description: How to use Machine ID to SSH into Teleport nodes from GitLab CI +--- + +
+ Machine ID for GitLab is available starting from Teleport `v12.2`. +
+ +In this guide, you will use Teleport Machine ID to allow a GitLab pipeline to +securely connect to a Teleport SSH node without the need for long-lived secrets. + +Machine ID for GitLab works with GitLab's cloud-hosted option and with +self-hosted GitLab installations. **The minimum supported GitLab version is +15.7**. + +This mitigates the risk of long-lived secrets such as passwords or SSH private +keys being exfiltrated from your GitLab organization and provides many of +the other benefits of Teleport such as auditing and finely-grained access +control. + +## Prerequisites + +(!docs/pages/includes/edition-prereqs-tabs.mdx!) + +- (!docs/pages/includes/tctl.mdx!) +- A running instance of the Teleport SSH Service registered with your Teleport +cluster. For instructions on setting this up, see the [Getting Started +Guide](../../server-access/introduction.mdx). The SSH node must include a user +you want to grant access to. In this guide, we will call the SSH node `my-node` +and the user `ci-user`. Replace these with values appropriate to your setup. +- A GitLab project to connect to Teleport. This can either be on GitLab's +cloud-hosted offering (gitlab.com) or on a self-hosted GitLab instance. **When +using a self-hosted GitLab instance, your Teleport Auth Server must be able to +connect to your GitLab instance and your GitLab instance must be configured with +a valid TLS certificate.** + +## Step 1/3. Create a join token + +To allow GitLab CI to authenticate to your Teleport cluster, you'll first need +to create a join token. A GitLab join token contains allow rules that describe +which pipelines can use that token in order to join the Teleport cluster. A rule +can contain multiple fields, and any pipeline that matches all of the fields +within a single rule is granted access. + +In this example, you will create a token with a rule that grants access to any +GitLab CI job within a specific GitLab project. Determine the fully qualified +path of your GitLab project. This will include your username (or group) and the +name of your project, e.g `my-user/my-project`. + +Create a file named `gitlab-token.yaml`. Ensure you substitute any values as +suggested by the comments in this example: + +```yaml +kind: token +version: v2 +metadata: + name: gitlab-demo +spec: + # The Bot role indicates that this token grants access to a bot user, rather + # than allowing a node to join. This role is built in to Teleport. + roles: [Bot] + join_method: gitlab + # The bot_name indicates which bot user this token grants access to. This + # should match the name of the bot that you create in step 2. + bot_name: gitlab-demo + gitlab: + # domain should be the domain of your GitLab instance. If you are using + # GitLab's cloud hosted offering, omit this field entirely. + domain: gitlab.example.com + # allow specifies rules that control which GitLab tokens will be accepted + # by Teleport. Tokens not matching any allow rule will be denied. + allow: + # project_path should be the fully qualified path of your GitLab + # project that you determined earlier. This will grant access to any + # GitLab CI run in that project. + - project_path: my-user/my-project +``` + +You can find a full list of the token configuration options for GitLab joining +on the +[GitLab CI reference page.](../reference/gitlab.mdx#gitlab-join-token) + +Apply this to your Teleport cluster using `tctl`: + +```code +$ tctl create -f gitlab-token.yaml +``` + +## Step 2/3. Create a Machine ID bot + +With the join token created, the next step is to create the Machine ID bot that +the token will grant access to. + +In this example, the bot is given the preset `access` role. In a production +environment, the principle of least privilege should be applied and you should +create a role that grants the bot access to the precise resources that will be +needed in your CI/CD pipeline. + +From your workstation enter the following command, replacing the`ci-user` +value with a Linux user on the host that you want your GitHub Actions flow to +be able to connect to: + +```code +$ tctl bots add gitlab-demo --roles=access --token=gitlab-demo --logins=ci-user +``` + +## Step 3/3. Configure a GitLab Pipeline + +With the bot and join token created, you can now create a GitLab pipeline that +uses these to access a node in your cluster. + +Create a file called `.gitlab-ci.yml` in the root of your repository. Replace: +- `teleport.example.com` with the name of your Teleport cluster +- `teleport.example.com:443` with the public address and port of your Teleport + Proxy +- `my-node` and `ci-user` with the user and host you wish to connect to + +```yaml +stages: + - deploy + +deploy-job: + stage: deploy + id_tokens: + # See https://docs.gitlab.com/ee/ci/secrets/id_token_authentication.html + # for further explanation of the id_tokens configuration. + TBOT_GITLAB_JWT: + # An environment variable named TBOT_GITLAB_JWT must exist and contain + # an ID token with an audience that matches your Teleport cluster's name. + aud: teleport.example.com + script: + - cd /tmp + - 'curl -O https://cdn.teleport.dev/teleport-v(=teleport.version=)-linux-amd64-bin.tar.gz' + - tar -xvf teleport-v(=teleport.version=)-linux-amd64-bin.tar.gz + - sudo ./teleport/install + - 'tbot start --token=gitlab-demo --destination-dir=/tmp/tbot-user --data-dir=/tmp/tbot-data --auth-server=teleport.example.com:443 --join-method=gitlab --oneshot' + - 'tsh -i /tmp/tbot-user/identity --proxy teleport.example.com:443 ssh ci-user@my-node "echo $CI_JOB_ID >> ~/gitlab_run_log"' +``` + +Commit and push this to the repository. Check your GitLab CI status, and examine +the log results from the commit for failure. If the job has succeeded, you +should see a file in the home directory of `ci-user` called `gitlab_run_log` +that includes the ID of the CI run. + +## Further steps + +For more information about GitLab itself, read +[their documentation](https://docs.gitlab.com/ee/ci/). + +For more information about GitLab joining, read the +[GitLab CI reference page.](../reference/gitlab.mdx#gitlab-join-token) + +[More information about `TELEPORT_ANONYMOUS_TELEMETRY`.](../reference/telemetry.mdx) diff --git a/docs/pages/machine-id/guides/jenkins.mdx b/docs/pages/machine-id/guides/jenkins.mdx index a1ad3bd3342ab..35d2ad6c3f3cc 100644 --- a/docs/pages/machine-id/guides/jenkins.mdx +++ b/docs/pages/machine-id/guides/jenkins.mdx @@ -34,7 +34,7 @@ proxy_service: ``` -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Architecture diff --git a/docs/pages/machine-id/guides/kubernetes.mdx b/docs/pages/machine-id/guides/kubernetes.mdx index 86ca7659eae11..09033798e1ad9 100644 --- a/docs/pages/machine-id/guides/kubernetes.mdx +++ b/docs/pages/machine-id/guides/kubernetes.mdx @@ -22,11 +22,13 @@ You will need a running Teleport cluster version 10.1.0 or later. If you have not already connected your Kubernetes cluster to Teleport, follow the [Kubernetes Access Getting Started Guide](../../kubernetes-access/getting-started.mdx). +(!docs/pages/includes/machine-id/kubernetes-machineidnote.mdx!) + If you're not already familiar with Machine ID, follow the [Getting Started Guide](../getting-started.mdx) to familiarize yourself with Machine ID. You'll also need `tctl` access to initially configure the bot. -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) Next, ensure the `tbot` binary is installed on your Machine ID client system. The client system is any system from which you want to access your Teleport diff --git a/docs/pages/machine-id/reference.mdx b/docs/pages/machine-id/reference.mdx index 82d56d25af241..bd98d518e01d6 100644 --- a/docs/pages/machine-id/reference.mdx +++ b/docs/pages/machine-id/reference.mdx @@ -6,5 +6,6 @@ description: Configuration and CLI reference for Teleport Machine ID. - [Configuration](./reference/configuration.mdx) - [GitHub Actions](./reference/github-actions.mdx) +- [GitLab CI](./reference/gitlab.mdx) - [CLI](../reference/cli.mdx#tbot) - [Telemetry](./reference/telemetry.mdx) diff --git a/docs/pages/machine-id/reference/gitlab.mdx b/docs/pages/machine-id/reference/gitlab.mdx new file mode 100644 index 0000000000000..fef1cec223f37 --- /dev/null +++ b/docs/pages/machine-id/reference/gitlab.mdx @@ -0,0 +1,91 @@ +--- +title: GitLab CI +description: Reference for GitLab joining +--- + +This document acts a reference for GitLab CI and Machine ID. You will find +links to in-depth guides as well as a full description of the configuration +options available when using the GitLab join method. + +## Guides + +You can read step-by-step guides on using Machine ID and GitHub Actions: + +- [Using Machine ID with GitLab](../guides/gitlab.mdx): How to + use Machine ID to SSH into Teleport nodes from GitLab CI. + +## GitLab join token + +A GitLab join token contains allow rules that describe which pipelines can +use that token in order to join the Teleport cluster. A rule can contain +multiple fields, and any pipeline that matches all of the fields within a +single rule is granted access. + +The following constraints exist: +- `sub`: a string uniquely identifying the CI run's source. It follows the + following format: + + +```yaml +kind: token +version: v2 +metadata: + # name identifies the token. When configuring a bot or node to join using this + # token, this name should be specified. + name: gitlab-demo +spec: + # The Bot role indicates that this token grants access to a bot user, rather + # than allowing a node to join. + roles: [Bot] + # join_method for GitLab joining will always be "gitlab". + join_method: gitlab + + # bot_name specifies the name of the bot that this token will grant access to + # when it is used. + bot_name: gitlab-demo + + gitlab: + # domain should be the domain of your GitLab instance. If you are using + # GitLab's cloud hosted offering, omit this field entirely. + domain: gitlab.example.com + # allow is an array of rule configurations for what GitLab CI jobs should be + # allowed to join. All options configured within one allow entry + # must be satisfied for the GitLab CI run to be allowed to join. Where + # multiple allow entries are specified, any job which satisfies all of the + # options within a single entry will be allowed to join. + # + # An allow entry must include at least one of: + # - project_path + # - namespace_path + # - sub + # This ensures that GitLab CI runs in other GitLab user's projects are not + # able to access your Teleport cluster. + allow: + # project_path restricts joins to jobs that originate within the + # specified project. + - project_path: my-user/my-project + # namespace_path restricts joins to any run within project that exists + # within the specified namespace. A namespace will either be a username + # or the name of a group. + namespace_path: my-user + # pipeline_source restricts joins to jobs triggered by certain criteria, + # e.g triggered through the web interface. + pipeline_source: web + # environment restricts joins to jobs that are associated with the + # specified environment + environment: production + # ref_type restricts joins to jobs that were triggered by a specific + # type of git reference. Either `branch` or `tag`. + ref_type: branch + # ref restricts joins to jobs that were triggered by a specific git + # reference. Combine this with `ref_type` to create allow rules that + # can only be triggered by a specific branch or tag. + ref: main + # sub is a single string that concatenates the project_path, ref_type + # and ref. This can be used to restrict joins using a single string, + # whilst also describing a specific project and git ref. + # + # It is better to use the individual fields, as it is easy to mis-format + # the sub string. + sub: project_path:my-user/my-project:ref_type:branch:ref:main +``` \ No newline at end of file diff --git a/docs/pages/machine-id/troubleshooting.mdx b/docs/pages/machine-id/troubleshooting.mdx index 5fa6eb2890e58..91ec8656a83b3 100644 --- a/docs/pages/machine-id/troubleshooting.mdx +++ b/docs/pages/machine-id/troubleshooting.mdx @@ -292,3 +292,51 @@ flag: $ tctl bots rm example $ tctl bots add example --roles=foo,bar,machine-id-db ``` + +## Kubernetes connections are failing with `Unable to connect to the server: x509: certificate signed by unknown authority` + +### Symptoms + +A self-hosted Teleport cluster is connecting Machine ID to Kubernetes clusters +with the following errors. This can happen for non-TLS configured Teleport clusters. + +```bash +E0322 22:53:31.653051 1699 memcache.go:265] couldn't get current server API group list: Get "https://teleport.example.com:443/api?timeout=32s": x509: certificate signed by unknown authority +``` + +To confirm the TLS routing mode check the value of the `proxy.tls_routing_enabled` +key with this command, substituting your proxy address: + +```bash +curl https://teleport.example.com:443/webapi/ping | jq +``` + +If the value is `false` then this is a non-TLS routing configuration. + +### Explanation + +Proxies configured with non-TLS routing use specific ports for various types +of traffic. That requires that a Kubernetes +connection use its designated port. Currently Machine ID requires that the Kubernetes +public address is set to use the correct port. Otherwise it will use the Proxy web port +which can cause these type of errors. + +### Resolution + +The Kubernetes public address is via the `kube_public_addr` within the +Teleport `proxy_service` configuration by administrators. The proxy will +require a restart after the configuration is updated. + + ```yaml + proxy_service: + enabled: true + kube_listen_addr: 0.0.0.0:3026 + kube_public_addr: teleport.example.com:3026 + ``` + +Retrieve the configuration listing from the proxy web address to confirm the +Kubernetes public address is populated in `proxy.kube.public_addr`. + +```bash +curl https://teleport.example.com:443/webapi/ping | jq +``` diff --git a/docs/pages/management/admin/adding-nodes.mdx b/docs/pages/management/admin/adding-nodes.mdx index 090b2797425d7..43fce9d49a953 100644 --- a/docs/pages/management/admin/adding-nodes.mdx +++ b/docs/pages/management/admin/adding-nodes.mdx @@ -10,8 +10,7 @@ This guide explains how to add Teleport Nodes to your cluster. (!docs/pages/includes/edition-prereqs-tabs.mdx!) - A Linux server that you will use to host your Teleport Node. - -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/3. Install Teleport on your Node @@ -45,7 +44,7 @@ print a warning message. The CA pin becomes invalid if a Teleport administrator performs the CA rotation by executing [`tctl auth rotate`](../../reference/cli.mdx#tctl-auth-rotate). - + On you local machine, retrieve the CA pin of the Auth Service -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/3. Enable verbose logging diff --git a/docs/pages/management/admin/users.mdx b/docs/pages/management/admin/users.mdx index 97008bc2c0614..7bb98f1115798 100644 --- a/docs/pages/management/admin/users.mdx +++ b/docs/pages/management/admin/users.mdx @@ -20,7 +20,7 @@ This guide shows you how to: (!docs/pages/includes/edition-prereqs-tabs.mdx!) -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Adding local users diff --git a/docs/pages/management/export-audit-events/datadog.mdx b/docs/pages/management/export-audit-events/datadog.mdx index 364ebd6049886..cf4a7a6246799 100644 --- a/docs/pages/management/export-audit-events/datadog.mdx +++ b/docs/pages/management/export-audit-events/datadog.mdx @@ -63,7 +63,7 @@ d-->h(Datadog) The instructions below demonstrate a local test of the Event Handler plugin on your workstation. You will need to adjust paths, ports, and domains for other environments. -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/6. Install the Event Handler plugin @@ -158,29 +158,15 @@ command. -```code -$ tctl auth sign --user=teleport-event-handler --out=identity -``` -This command creates one PEM-encoded file, `identity`. The identity file -includes both TLS and SSH credentials. The Event Handler plugin uses the SSH -credentials to connect to the Proxy Service, which establishes a reverse tunnel -connection to the Auth Service. The plugin uses this reverse tunnel, along with -your TLS credentials, to connect to the Auth Service's gRPC endpoint. +(!docs/pages/includes/plugins/identity-export.mdx user="teleport-event-handler"!) -If you are planning to use the Helm Chart, you'll need to generate the keys -with the `file` format, then create a secret in Kubernetes. - -Create the identity using the following command: - -```code -$ tctl auth sign --format=file --user=teleport-event-handler --out=identity -``` +(!docs/pages/includes/plugins/identity-export.mdx user="teleport-event-handler"!) -Then create the Kubernetes secret: +Next, create a Kubernetes secret for the Teleport identity file: ```code $ kubectl create secret generic teleport-event-handler-identity --from-file=auth_id=identity @@ -390,7 +376,7 @@ Teleport Cluster, ensure that: `--ttl` flag in the `tctl auth sign` command, which is 12 hours by default. - Ensure that in your Teleport Event Handler configuration file (`teleport-event-handler.toml`), you have provided the correct host *and* port - for the Teleport Proxy Service or Auth Service. + for the Teleport Proxy Service. ## Next steps diff --git a/docs/pages/management/export-audit-events/elastic-stack.mdx b/docs/pages/management/export-audit-events/elastic-stack.mdx index 67bdd8ebfda4b..822e009bbf350 100644 --- a/docs/pages/management/export-audit-events/elastic-stack.mdx +++ b/docs/pages/management/export-audit-events/elastic-stack.mdx @@ -1,5 +1,5 @@ --- -title: "Export Teleport Audit Events with to Elastic Stack" +title: "Export Teleport Audit Events to the Elastic Stack" description: "How to configure Teleport's Event Handler plugin to send audit events to the Elastic Stack" --- @@ -144,95 +144,7 @@ $ tctl create teleport-event-handler-impersonator.yaml ### Export the access plugin identity -Like all Teleport users, `teleport-event-handler` needs signed credentials in -order to connect to your Teleport cluster. You will use the `tctl auth sign` -command to request these credentials for the plugin. - - - -The format of the credentials depends on whether you have set up your network to -give the plugin direct access to the Teleport Auth Service, or if all Teleport -clients and services connect to the Teleport Proxy Service instead. - - - - -The following `tctl auth sign` command impersonates the `teleport-event-handler` -user, generates signed credentials, and writes an identity file to the local -directory: - -```code -$ tctl auth sign --user=teleport-event-handler --out=auth.pem -``` - -The Event Handler plugin listens for audit events by connecting to the Teleport -Auth Service's gRPC endpoint over TLS. - -The identity file, `auth.pem`, includes both TLS and SSH credentials. Your -Event Handler plugin uses the SSH credentials to connect to the Proxy Service, -which establishes a reverse tunnel connection to the Auth Service. The plugin -uses this reverse tunnel, along with your TLS credentials, to connect to the -Auth Service's gRPC endpoint. - -You will refer to this file later when configuring the plugin. - - - - -If your network allows your plugin to access the Auth Service directly, e.g., -you are running the plugin on the Auth Service host, the plugin uses TLS -credentials to connect to the Auth Service's gRPC endpoint and listen for audit -events. - -You can generate TLS credentials with the following command: - -```code -$ tctl auth sign --format=tls --user=teleport-event-handler --out=auth -``` - -This command should result in three PEM-encoded files: `auth.crt`, -`auth.key`, and `auth.cas` (certificate, private key, and CA certs -respectively). Later, you will configure the plugin to use these credentials to -connect to the Auth Service. - - - - - - - - -The following `tctl auth sign` command impersonates the `teleport-event-handler` -user, generates signed credentials, and writes an identity file to the local -directory: - -```code -$ tctl auth sign --user=teleport-event-handler --out=auth.pem -``` - -Teleport's Event Handler plugin listens for new and updated audit events by -connecting to the Teleport Auth Service's gRPC endpoint over TLS. - -The identity file, `auth.pem`, includes both TLS and SSH credentials. The Event -Handler plugin uses the SSH credentials to connect to the Proxy Service, which -establishes a reverse tunnel connection to the Auth Service. The plugin uses -this reverse tunnel, along with your TLS credentials, to connect to the Auth -Service's gRPC endpoint. - -You will refer to this file later when configuring the plugin. - - - - - - By default, `tctl auth sign` produces certificates with a relatively short - lifetime. For production deployments, you can use the `--ttl` flag to ensure a - more practical certificate lifetime, e.g., `--ttl=8760h` to export a one-year - certificate. - - +(!docs/pages/includes/plugins/identity-export.mdx user="teleport-event-handler"!) ## Step 2/4. Configure a Logstash pipeline @@ -557,21 +469,7 @@ Change `teleport.addr` to the host and port of your Teleport Proxy Service, or the Auth Service if you have configured the Event Handler to connect to it directly, e.g., `mytenant.teleport.sh:443`. - - - -Assign `teleport.identity` to a path to the identity file you exported earlier, -e.g., `/home/auth.pem`. - - - - -Assign `teleport.ca`, `teleport.cert`, and `teleport.key` to the paths of the -TLS credentials you generated earlier. Respectively, these are the certificate -authority, certificate, and private key. - - - +(!docs/pages/includes/plugins/config-toml-teleport.mdx!) ### Start the Event Handler diff --git a/docs/pages/management/export-audit-events/fluentd.mdx b/docs/pages/management/export-audit-events/fluentd.mdx index 95320910136b3..c281bfd56f432 100644 --- a/docs/pages/management/export-audit-events/fluentd.mdx +++ b/docs/pages/management/export-audit-events/fluentd.mdx @@ -44,9 +44,10 @@ to integrate with your infrastructure. (!docs/pages/includes/edition-prereqs-tabs.mdx!) - Fluentd version v(=fluentd.version=) or greater. + - Docker version v(=docker.version=). -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) - On your workstation, create a folder called `event-handler`, to hold configuration files and plugin state: diff --git a/docs/pages/management/export-audit-events/splunk.mdx b/docs/pages/management/export-audit-events/splunk.mdx index 09a08ffb0b41a..567ddd13d969c 100644 --- a/docs/pages/management/export-audit-events/splunk.mdx +++ b/docs/pages/management/export-audit-events/splunk.mdx @@ -38,7 +38,7 @@ visualization and alerting. - On Splunk Enterprise, port `8088` should be open to traffic from the host running the Teleport Event Handler and Universal Forwarder. -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/4. Set up the Teleport Event Handler plugin @@ -141,89 +141,9 @@ $ tctl create -f teleport-event-handler-impersonator.yaml Log out of your Teleport cluster and log in again to assume the new role. -### Export the access plugin identity +### Export the plugin identity -Like all Teleport users, `teleport-event-handler` needs signed credentials in -order to connect to your Teleport cluster. You will use the `tctl auth sign` -command to request these credentials for the plugin. - - - -The format of the credentials depends on whether you have set up your Teleport -cluster so clients and services connect to the Teleport Proxy Service or to the -Teleport Auth Service instead. - - - - -The following `tctl auth sign` command impersonates the `teleport-event-handler` -user, generates signed credentials, and writes an identity file to the local -directory. It uses the `--ttl` flag to request a certificate with a lifetime of -10 days: - -```code -$ tctl auth sign --user=teleport-event-handler --out=auth.pem --ttl=240h -``` - -The Event Handler plugin listens for audit logs by connecting to the Teleport -Auth Service's gRPC endpoint over TLS. - -The identity file, `auth.pem`, includes both TLS and SSH credentials. Your -Event Handler plugin uses the SSH credentials to connect to the Proxy Service, -which establishes a reverse tunnel connection to the Auth Service. The plugin -uses this reverse tunnel, along with your TLS credentials, to connect to the -Auth Service's gRPC endpoint. - -You will refer to this file later when configuring the plugin. - - - - -If your network allows your plugin to access the Auth Service directly, e.g., -you are running the plugin on the Auth Service host, the plugin uses TLS -credentials to connect to the Auth Service's gRPC endpoint and listen for audit -events. - -You can generate TLS credentials with the following command, which uses the -`--ttl` flag to request a certificate with a lifetime of 10 days: - -```code -$ tctl auth sign --format=tls --user=teleport-event-handler --out=auth --ttl=240h -``` - -This command should result in three PEM-encoded files: `auth.crt`, -`auth.key`, and `auth.cas` (certificate, private key, and CA certs -respectively). Later, you will configure the plugin to use these credentials to -connect to the Auth Service. - - - - - - - - -The following `tctl auth sign` command impersonates the `teleport-event-handler` -user, generates signed credentials, and writes an identity file to the local -directory. It uses the `--ttl` flag to request a certificate with a lifetime of -10 days: - -```code -$ tctl auth sign --user=teleport-event-handler --out=auth.pem --ttl=240h -``` - -Teleport's Event Handler plugin listens for new and updated audit logs by -connecting to the Teleport Auth Service's gRPC endpoint over TLS. - -The identity file, `auth.pem`, includes both TLS and SSH credentials. The Event -Handler plugin uses the SSH credentials to connect to the Proxy Service, which -establishes a reverse tunnel connection to the Auth Service. The plugin uses -this reverse tunnel, along with your TLS credentials, to connect to the Auth -Service's gRPC endpoint. - -You will refer to this file later when configuring the plugin. - - +(!docs/pages/includes/plugins/identity-export.mdx user="teleport-event-handler"!) Move the credentials you generated to the host where you are running the Teleport Event Handler plugin. @@ -498,38 +418,16 @@ Adding the `noop` query parameter causes the Teleport Event Handler to append the routing information as the parameter's value so the Universal Forwarder can discard it. -Change `teleport.addr` to the host and port of your Teleport Proxy Service, or -the Auth Service if you have configured the Teleport Event Handler to connect to -it directly, e.g., `mytenant.teleport.sh:443`. - - - +Next, edit the `teleport` section of the configuration as follows: -Assign `teleport.identity` to a path to the identity file you exported earlier, -e.g., `/home/auth.pem`. +(!docs/pages/includes/plugins/config-toml-teleport.mdx!) -Ensure that the Teleport Event Handler can read these credentials: +Ensure that the Teleport Event Handler can read the identity file: ```code $ chmod +r auth.pem ``` - - - -Assign `teleport.ca`, `teleport.cert`, and `teleport.key` to the paths of the -TLS credentials you generated earlier. Respectively, these are the certificate -authority, certificate, and private key. - -Ensure that the Teleport Event Handler can read these credentials: - -```code -$ chmod +r auth.cas auth.crt auth.key -``` - - - - ### Start the Teleport Event Handler Start the Teleport Teleport Event Handler as a daemon. To do so, create a @@ -622,7 +520,7 @@ Teleport Cluster, ensure that: `--ttl` flag in the `tctl auth sign` command, which is 12 hours by default. - Ensure that in your Teleport Event Handler configuration file (`teleport-event-handler.toml`), you have provided the correct host *and* port - for the Teleport Proxy Service or Auth Service. + for the Teleport Proxy Service. ## Next steps diff --git a/docs/pages/management/guides/joining-nodes-aws-ec2.mdx b/docs/pages/management/guides/joining-nodes-aws-ec2.mdx index 3de48af8da2b7..9fb5c447813ec 100644 --- a/docs/pages/management/guides/joining-nodes-aws-ec2.mdx +++ b/docs/pages/management/guides/joining-nodes-aws-ec2.mdx @@ -89,7 +89,7 @@ more in the following guide: -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/4. Set up AWS IAM credentials diff --git a/docs/pages/management/guides/joining-nodes-aws-iam.mdx b/docs/pages/management/guides/joining-nodes-aws-iam.mdx index 8ff4c11a32d1d..35deae6191c9b 100644 --- a/docs/pages/management/guides/joining-nodes-aws-iam.mdx +++ b/docs/pages/management/guides/joining-nodes-aws-iam.mdx @@ -64,10 +64,10 @@ connecting directly to the Auth Service. - An AWS EC2 instance to act as a Teleport Node, with the Teleport binary installed. - -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/4. Set up AWS IAM credentials + Every Node or Proxy using the IAM method to join your Teleport cluster needs AWS IAM credentials in order to call the `sts:GetCallerIdentity` API. No specific IAM policy or permissions are needed. Any IAM user or role can call this API. diff --git a/docs/pages/management/guides/joining-nodes-azure.mdx b/docs/pages/management/guides/joining-nodes-azure.mdx index a1bc1000a766a..37195e9c2a238 100644 --- a/docs/pages/management/guides/joining-nodes-azure.mdx +++ b/docs/pages/management/guides/joining-nodes-azure.mdx @@ -40,10 +40,10 @@ connecting directly to the Auth Service. installed. The Virtual Machine must have a [Managed Identity](https://learn.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview) assigned to it with permission to read virtual machine info. - -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/4. Set up a Managed Identity + Every virtual machine hosting a Node or Proxy using the Azure method to join your Teleport cluster needs a Managed Identity assigned to it. The identity requires the `Microsoft.Compute/virtualMachines/read` permission so Teleport @@ -348,6 +348,7 @@ metadata: spec: # use the minimal set of roles required roles: [Node] + join_method: azure azure: allow: # specify the Azure subscription which Nodes may join from diff --git a/docs/pages/management/guides/ssh-key-extensions.mdx b/docs/pages/management/guides/ssh-key-extensions.mdx index 37bc899a7212e..805af70e10ce4 100644 --- a/docs/pages/management/guides/ssh-key-extensions.mdx +++ b/docs/pages/management/guides/ssh-key-extensions.mdx @@ -13,29 +13,12 @@ Teleport supports exporting user SSH certificates with configurable key extensio ## Step 1/3. Import the Teleport CA into GitHub -In order to export the Teleport CA, execute the following command: - - - -```code -# Log in to your cluster with tsh so you can use tctl from your local machine. -# You can also run tctl on your Auth Service host without running "tsh login" -# first. -$ tsh login --proxy=teleport.example.com --user=myuser -$ tctl auth export --type=user | sed 's/^cert-authority //g' -``` - - - +In order to export the Teleport CA, execute the following command, assigning to the address of your Teleport Proxy Service: ```code -# Log in to your Teleport cluster so you can use tctl remotely. -$ tsh login --proxy=mytenant.teleport.sh --user=myuser -$ tctl auth export --type=user | sed 's/^cert-authority //g' +$ curl 'https:///webapi/auth/export?type=user' | sed 's/^cert-authority //g' ``` - - Next, follow the instructions in the guide below to import your Teleport CA into GitHub: [Managing your organization's SSH certificate authorities](https://docs.github.com/en/organizations/managing-git-access-to-your-organizations-repositories/managing-your-organizations-ssh-certificate-authorities) diff --git a/docs/pages/management/guides/terraform-provider.mdx b/docs/pages/management/guides/terraform-provider.mdx index 6340ae44991cf..6573397552e4c 100644 --- a/docs/pages/management/guides/terraform-provider.mdx +++ b/docs/pages/management/guides/terraform-provider.mdx @@ -20,7 +20,7 @@ This guide will explain how to: # Terraform v(=terraform.version=) ``` -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) Create a folder called `teleport-terraform` to hold some temporary files: diff --git a/docs/pages/management/operations/ca-rotation.mdx b/docs/pages/management/operations/ca-rotation.mdx index 00a3feefe1981..4a4e51830972d 100644 --- a/docs/pages/management/operations/ca-rotation.mdx +++ b/docs/pages/management/operations/ca-rotation.mdx @@ -7,7 +7,7 @@ description: How to rotate Teleport's certificate authority (!docs/pages/includes/edition-prereqs-tabs.mdx!) -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Certificate Authority rotation diff --git a/docs/pages/management/security/reduce-blast-radius.mdx b/docs/pages/management/security/reduce-blast-radius.mdx index 9ef1b896b3b83..357e36bde2fbb 100644 --- a/docs/pages/management/security/reduce-blast-radius.mdx +++ b/docs/pages/management/security/reduce-blast-radius.mdx @@ -11,10 +11,10 @@ Teleport encourages users to practice defense in depth so that every component o - [Automatically prevent some roles from requesting others](#automatically-prevent-some-roles-from-requesting-others) - [Restrict role requests based on user traits](#restrict-role-requests-based-on-user-traits) - [Set up your RBAC without admin roles](#set-up-your-rbac-without-admin-roles) - -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Make MFA mandatory for `tsh login` + If a user sets up an account to authenticate to their Teleport cluster with only a password, an adversary can gain access to the password using brute-force attacks, person-in-the-middle attacks, or phishing. But even if a user's password is compromised, you can stop an attacker from authenticating with it when they run `tsh login`. Teleport lets you make it mandatory for a user to enroll an MFA device when they create an account, and to authenticate using that device when they begin a new Teleport session. diff --git a/docs/pages/reference/cli.mdx b/docs/pages/reference/cli.mdx index 7afabe71c29c7..441594ae7e115 100644 --- a/docs/pages/reference/cli.mdx +++ b/docs/pages/reference/cli.mdx @@ -147,6 +147,7 @@ information about the cluster. | `--skip-version-check` | none | none | Skip version checking between server and client. | | `-d, --debug` | none | none | Verbose logging to stdout | | `-J, --jumphost` | none | A jump host | SSH jumphost | +| `--headless` | none | none | Use Headless WebAuthn for authentication | ### tsh help @@ -1270,6 +1271,7 @@ Environment variables configure your tsh client and can help you avoid using fla | TELEPORT_ADD_KEYS_TO_AGENT | Specifies if the user certificate should be stored on the running SSH agent | yes, no, auto, only | | TELEPORT_USE_LOCAL_SSH_AGENT | Disable or enable local SSH agent integration | true, false | | TELEPORT_GLOBAL_TSH_CONFIG | Override location of global `tsh` config file from default `/etc/tsh.yaml` | /opt/teleport/tsh.yaml | +| TELEPORT_HEADLESS | Use headless authentication | true, false, 1, 0 | ### tsh configuration files diff --git a/docs/pages/reference/config.mdx b/docs/pages/reference/config.mdx index d376145c27295..93d35e0fc5d51 100644 --- a/docs/pages/reference/config.mdx +++ b/docs/pages/reference/config.mdx @@ -82,7 +82,7 @@ proxy_service: ## Reference configurations These example configurations include all possible configuration options in YAML -format to demonstrate proper use of indentation. +format to demonstrate proper use of indentation. Choose a Teleport service to view the application configuration options: @@ -168,3 +168,51 @@ These settings apply to the Windows Desktop Service: (!docs/pages/includes/config-reference/desktop-config.yaml!) ``` +## Configuration versions + +In order to avoid breaking existing configurations, Teleport's configuration is +versioned. The newer configuration version is `v3`. If a `version` is not +specified in the configuration file, `v1` is assumed. + +Some new Teleport features require users to opt-in by explicitly upgrading their +configuration to a newer version. + +### Config `v1` + +`v1` is the original version of Teleport's file configuration. It is still supported +today, but most new users should start with the latest configuration version. + +### Config `v2` + +Configuration version `v2` was introduced in Teleport 8 as part of Teleport's +TLS routing feature. With TLS routing, Teleport's proxy listens on a single port +and uses ALPN and SNI to route incoming traffic to the correct Teleport service +rather than listening on multiple protocol-specific ports. + +For backwards compatibility, configuration version `v1` always listens on these +protocol-specific ports. When Teleport is using configuration version `v2`, the +individual protocol-specific ports are not opened unless explicitly set. + +### Config `v3` + +Configuration version `v3` was introduced with Teleport 11. In version 3, the +`auth_servers` field is no longer supported, and agents must specify one of +`auth_server` or `proxy_server` to indicate which endpoint to use for joining a +Teleport cluster. + +Previous versions of Teleport allowed for `auth_servers` to point to Auth +Servers or Proxy Servers. As a result, Teleport would try to connect in multiple +different modes, resulting in confusing error messages. With config version 3, +Teleport only attempts to connect in a single mode, which is both more efficient +and easier to troubleshoot. + +For example, this excerpt from a `v2` config can be converted to `v3` with the +following changes. + +```diff +-version: v2 ++version: v3 +teleport: +- auth_servers: [ teleport.example.com:443 ] ++ proxy_server: teleport.example.com:443 +``` diff --git a/docs/pages/reference/helm-reference/teleport-cluster.mdx b/docs/pages/reference/helm-reference/teleport-cluster.mdx index a9993d65becc0..0370555bc28ac 100644 --- a/docs/pages/reference/helm-reference/teleport-cluster.mdx +++ b/docs/pages/reference/helm-reference/teleport-cluster.mdx @@ -1956,6 +1956,30 @@ Allows the `imagePullPolicy` for any pods created by the chart to be overridden.
+## `imagePullSecrets` + +| Type | Default value | +|--------|---------------| +| `list` | `[]` | + +[Kubernetes reference](https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod) + +A list of secrets containing authorization tokens which can be optionally used to access a private Docker registry. + + + + ```yaml + imagePullSecrets: + - name: my-docker-registry-key + ``` + + + ```shell + --set "imagePullSecrets[0].name=my-docker-registry-key" + ``` + + + ## `initContainers` | Type | Default value | diff --git a/docs/pages/reference/resources.mdx b/docs/pages/reference/resources.mdx index d9524a0896d77..7fa009bbdf959 100644 --- a/docs/pages/reference/resources.mdx +++ b/docs/pages/reference/resources.mdx @@ -26,7 +26,7 @@ users. -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ### `tctl` concepts diff --git a/docs/pages/server-access/getting-started.mdx b/docs/pages/server-access/getting-started.mdx index 4cc16d9057a42..e899e6c91decf 100644 --- a/docs/pages/server-access/getting-started.mdx +++ b/docs/pages/server-access/getting-started.mdx @@ -32,8 +32,7 @@ pattern** so that only a single Node can be accessed publicly. - One host running a Linux environment (such as Ubuntu 20.04, CentOS 8.0, or Debian 10). This will serve as a Teleport Node. - -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) (!docs/pages/includes/permission-warning.mdx!) diff --git a/docs/pages/server-access/guides.mdx b/docs/pages/server-access/guides.mdx index f441a4cca446f..34f32bc91a029 100644 --- a/docs/pages/server-access/guides.mdx +++ b/docs/pages/server-access/guides.mdx @@ -14,5 +14,6 @@ layout: tocless-doc - [Host User Creation](./guides/host-user-creation.mdx): How to configure Teleport to automatically create transient host users. - [Linux Auditing System](./guides/auditd.mdx): How to integrate Teleport with the Linux Auditing System (auditd). - [EC2 Instance Discovery (Preview)](./guides/ec2-discovery.mdx): How to configure Teleport to automatically enroll EC2 instances. +- [Azure Instance Discovery (Preview)](./guides/azure-discovery.mdx): How to configure Teleport to automatically enroll Azure virtual machines. - [Using Teleport with Ansible](./guides/ansible.mdx): How to use Ansible with Teleport-issued SSH credentials. diff --git a/docs/pages/server-access/guides/auditd.mdx b/docs/pages/server-access/guides/auditd.mdx index 7f8dd3c79d428..a3c46559bb158 100644 --- a/docs/pages/server-access/guides/auditd.mdx +++ b/docs/pages/server-access/guides/auditd.mdx @@ -13,8 +13,7 @@ You can configure Teleport's SSH Service to integrate with the Linux Auditing Sy - A running Teleport Node. See the [Server Access Getting Started Guide](../getting-started.mdx) for how to add a Node to your Teleport cluster. On the Node, `teleport` must be running as a systemd service with root permissions. - Linux kernel 2.6.6+ compiled with `CONFIG_AUDIT`. Most Linux distributions have this option enabled by default. - `auditctl` to check auditd status (optional). - -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/4. Check system configuration @@ -36,6 +35,7 @@ backlog_wait_time 60000 backlog_wait_time_actual 0 loginuid_immutable 0 unlocked ``` + The first line `enabled 1` indicates that auditd is enabled, and Teleport will send events. All events are generated on a Teleport Node. diff --git a/docs/pages/server-access/guides/azure-discovery.mdx b/docs/pages/server-access/guides/azure-discovery.mdx new file mode 100644 index 0000000000000..022136deb4ad1 --- /dev/null +++ b/docs/pages/server-access/guides/azure-discovery.mdx @@ -0,0 +1,344 @@ +--- +title: Automatically Discover Azure Virtual Machines (Preview) +description: How to configure Teleport to automatically enroll Azure virtual machines. +--- + +The Teleport Discovery Service can connect to Azure and automatically +discover and enroll virtual machines matching configured labels. It will then +execute a script on these discovered instances that will install Teleport, +start it and join the cluster. + +## Prerequisites + +(!docs/pages/includes/edition-prereqs-tabs.mdx!) + +- Azure subscription with virtual machines and permissions to create and attach +managed identities. +- Azure virtual machines to join the Teleport cluster, running +Ubuntu/Debian/RHEL if making use of the default Teleport install script. (For +other Linux distributions, you can install Teleport manually.) +- (!docs/pages/includes/tctl.mdx!) + +## Step 1/5. Create an Azure invite token + +When discovering Azure virtual machines, Teleport makes use of Azure invite tokens for +authenticating joining SSH Service instances. + +Create a file called `token.yaml`: + +```yaml +# token.yaml +kind: token +version: v2 +metadata: + # the token name is not a secret because instances must prove that they are + # running in your Azure subscription to use this token + name: azure-discovery-token + # set a long expiry time, as the default for tokens is only 30 minutes + expires: "3000-01-01T00:00:00Z" +spec: + # use the minimal set of roles required + roles: [Node] + + # set the join method allowed for this token + join_method: azure + + azure: + allow: + # specify the Azure subscription which Nodes may join from + - subscription: "123456789" +``` + +Assign the `subscription` field to your Azure subscription ID. +Add the token to the Teleport cluster with: + +```code +$ tctl create -f token.yaml +``` + +## Step 2/5. Configure IAM permissions for Teleport + +The Teleport Discovery Service needs Azure IAM permissions to discover and register Azure virtual machines. + +### Configure an Azure service principal + +There are a couple of ways for the Teleport Discovery Service to access Azure +resources: + +- The Discovery Service can run on an Azure VM with attached managed identity. This + is the recommended way of deploying the Discovery Service in production since + it eliminates the need to manage Azure credentials. +- The Discovery Service can be registered as an Azure AD application (via AD's "App + registrations") and configured with its credentials. This is only recommended + for development and testing purposes since it requires Azure credentials to + be present in the Discovery Service's environment. + + + + Go to the [Managed Identities](https://portal.azure.com/#blade/HubsExtension/BrowseResource/resourceType/Microsoft.ManagedIdentity%2FuserAssignedIdentities) + page in your Azure portal and click *Create* to create a new user-assigned + managed identity: + + ![Managed identities](../../../img/azure/managed-identities@2x.png) + + Pick a name and resource group for the new identity and create it: + + ![New identity](../../../img/server-access/guides/azure/new-identity@2x.png) + + Take note of the created identity's *Client ID*: + + ![Created identity](../../../img/server-access/guides/azure/created-identity@2x.png) + + Next, navigate to the Azure VM that will run your Discovery Service instance and + add the identity you've just created to it: + + ![VM identity](../../../img/server-access/guides/azure/vm-identity@2x.png) + + Attach this identity to all Azure VMs that will be running the Discovery + Service. + + + + Registering the Discovery Service as Azure AD application is suitable for + test and development scenarios, or if your Discovery Service does not run on + an Azure VM. For production scenarios prefer to use the managed identity + approach. + + + Go the the [App registrations](https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredApps) + page of your Azure Active Directory and click on *New registration*: + + ![App registrations](../../../img/azure/app-registrations@2x.png) + + Pick a name (e.g. *DiscoveryService*) and register a new application. Once the + app has been created, take note of its *Application (client) ID* and click on + *Add a certificate or secret*: + + ![Registered app](../../../img/server-access/guides/azure/registered-app@2x.png) + + Create a new client secret that the Discovery Service agent will use to + authenticate with the Azure API: + + ![Registered app secrets](../../../img/azure/registered-app-secrets@2x.png) + + The Teleport Discovery Service uses Azure SDK's default credential provider chain to + look for credentials. Refer to [Azure SDK Authorization](https://docs.microsoft.com/en-us/azure/developer/go/azure-sdk-authorization) + to pick a method suitable for your use-case. For example, to use + environment-based authentication with a client secret, the Discovery Service should + have the following environment variables set: + + ```text + export AZURE_TENANT_ID= + export AZURE_CLIENT_ID= + export AZURE_CLIENT_SECRET= + ``` + + + +### Create a custom role + +Teleport requires `Microsoft.Compute/virtualMachines/read` permissions for discovery and +`Microsoft.Compute/virtualMachines/runCommand/action` permissions for installation. + +Here is a sample role definition allowing Teleport to read and run commands on Azure +virtual machines: +```json +{ + "properties": { + "roleName": "TeleportDiscovery", + "description": "Allows Teleport to discover Azure virtual machines", + "assignableScopes": [ + "/subscriptions/11111111-2222-3333-4444-555555555555" + ], + "permissions": [ + { + "actions": [ + "Microsoft.Compute/virtualMachines/read`", + "Microsoft.Compute/virtualMachines/runCommand/action" + ], + "notActions": [], + "dataActions": [], + "notDataActions": [] + } + ] + } +} +``` +The `assignableScopes` field above includes a subscription +`/subscriptions/`, allowing the role to be assigned at any +resource scope within that subscription or the subscription scope itself. If +you want to further limit the `assignableScopes`, you can use a resource group +`/subscriptions//resourceGroups/` or a management group +`/providers/Microsoft.Management/managementGroups/` instead. + +Now go to the [Subscriptions](https://portal.azure.com/#view/Microsoft_Azure_Billing/SubscriptionsBlade) page and select a subscription. + +Click on *Access control (IAM)* in the subscription and select *Add > Add custom role*: +
+![IAM custom role](../../../img/azure/add-custom-role@2x.png) +
+ +In the custom role creation page, click the *JSON* tab and click *Edit*, then paste the JSON example +and replace the subscription in `assignableScopes` with your own subscription id: +
+![Create JSON role](../../../img/server-access/guides/azure/vm-create-role-from-json@2x.png) +
+ +### Create a role assignment for the Teleport Discovery Service principal + +(!docs/pages/includes/server-access/azure-assign-service-principal.mdx!) + +## Step 3/5. Install the Teleport Discovery Service + + + +If you plan on running the Discovery Service on a host that is already running +another Teleport service (Auth or Proxy, for example), you can skip this step. + + + +Install Teleport on the virtual machine that will run the Discovery Service: + +(!docs/pages/includes/install-linux.mdx!) + +## Step 4/5. Configure Teleport to discover Azure instances + +If you are running the Discovery Service on its own host, the service requires a +valid invite token to connect to the cluster. Generate one by running the +following command against your Teleport Auth Service: + +```code +$ tctl tokens add --type=discovery +``` + +Save the generated token in `/tmp/token` on the virtual machine that will run +the Discovery Service. + +In order to enable Azure instance discovery the `discovery_service.azure` section +of `teleport.yaml` must include at least one entry: + +```yaml +version: v2 +teleport: + join_params: + token_name: "/tmp/token" + method: token + auth_servers: + - "teleport.example.com:3080" +auth_service: + enabled: off +proxy_service: + enabled: off +ssh_service: + enabled: off +discovery_service: + enabled: "yes" + azure: + - types: ["vm"] + subscriptions: [] + resource_groups: [] + regions: [] + tags: + "env": "prod" # Match virtual machines where tag:env=prod +``` + +- Edit the `teleport.auth_servers` key to match your Auth Service or Proxy Service's URI + and port. +- Adjust the keys under `discovery_service.azure` to match your Azure environment, + specifically the regions and tags you want to associate with the Discovery + Service. + +## Step 5/5. [Optional] Customize the default installer script + +To customize the default installer script, execute the following command on +your workstation: + +```code +$ tctl get installer/default-installer > teleport-default-installer.yaml +``` + +The resulting `teleport-default-installer.yaml` can the be edited to +change what gets executed when enrolling discovered Azure instances. + +After making the desired changes to the default installer, the +resource can be updated by executing: + +```code +$ tctl create -f teleport-default-installer.yaml +``` + +Multiple `installer` resources can exist and be specified in the +`azure.install.script_name` section of a `discovery_service.azure` list item in +`teleport.yaml`: + +```yaml +discovery_service: + azure: + - types: ["vm"] + tags: + - "env": "prod" + install: # optional section when default-installer is used. + script_name: "default-installer" + - types: ["vm"] + tags: + - "env": "devel" + install: + script_name: "devel-installer" +``` + +--- + +The `installer` resource has the following templating options: + +- `{{ .MajorVersion }}`: the major version of Teleport to use when + installing from the repository. +- `{{ .PublicProxyAddr }}`: the public address of the Teleport Proxy Service to +connect to. + +These can be used as follows: + +```yaml +kind: installer +metadata: + name: default-installer +spec: + script: | + echo {{ .PublicProxyAddr }} + echo Teleport-{{ .MajorVersion }} +version: v1 +``` + +Which, when retrieved by the Systems Manager document will evaluate to a script +with the following contents: + +```sh +echo teleport.example.com +echo Teleport-(=teleport.version=) +``` + +The default installer will take the following actions: + +- Add an official Teleport repository to supported Linux distributions. +- Install Teleport via `apt` or `yum`. +- Generate the Teleport config file and write it to `/etc/teleport.yaml`. +- Enable and start the Teleport service. + +## Troubleshooting + +### No credential providers error + +If you see the error `DefaultAzureCredential: failed to acquire a token.` in Discovery Service logs then Teleport +is not detecting the required credentials to connect to the Azure SDK. Check whether +the credentials have been applied in the machine running the Teleport Discovery Service and restart +the Teleport Discovery Service. +Refer to [Azure SDK Authorization](https://docs.microsoft.com/en-us/azure/developer/go/azure-sdk-authorization) +for more information. + +## Next steps + +- Read [Joining Nodes via Azure Managed Identity](../../management/guides/joining-nodes-azure.mdx) + for more information on Azure tokens. +- Full documentation on Azure discovery configuration can be found through the [ + config file reference documentation](../../reference/config.mdx). +- The complete default installer can be found [with the Teleport source +](https://github.com/gravitational/teleport/blob/master/api/types/installers/installer.sh.tmpl). diff --git a/docs/pages/server-access/guides/bpf-session-recording.mdx b/docs/pages/server-access/guides/bpf-session-recording.mdx index 3bf0a5b028177..60206b5f490df 100644 --- a/docs/pages/server-access/guides/bpf-session-recording.mdx +++ b/docs/pages/server-access/guides/bpf-session-recording.mdx @@ -93,7 +93,7 @@ library preloading, and environment variables may not be captured in session rec -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/2. Configure a Teleport Node diff --git a/docs/pages/server-access/guides/ec2-discovery.mdx b/docs/pages/server-access/guides/ec2-discovery.mdx index 6819849dad1c7..c6b600ee97e67 100644 --- a/docs/pages/server-access/guides/ec2-discovery.mdx +++ b/docs/pages/server-access/guides/ec2-discovery.mdx @@ -21,8 +21,7 @@ policies. - EC2 instances running Ubuntu/Debian/RHEL/Amazon Linux 2 and SSM agent version 3.1 or greater if making use of the default Teleport install script. (For other Linux distributions, you can install Teleport manually.) - -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/7. Create an EC2 invite token diff --git a/docs/pages/server-access/guides/host-user-creation.mdx b/docs/pages/server-access/guides/host-user-creation.mdx index b54399f399c9c..e8dfb9242bf3d 100644 --- a/docs/pages/server-access/guides/host-user-creation.mdx +++ b/docs/pages/server-access/guides/host-user-creation.mdx @@ -29,7 +29,7 @@ since it must execute these commands in order to create transient users: - `getent` - `visudo` -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/2. Configure a role diff --git a/docs/pages/server-access/guides/openssh.mdx b/docs/pages/server-access/guides/openssh.mdx index ed76283e4517b..01bc5d32e13ee 100644 --- a/docs/pages/server-access/guides/openssh.mdx +++ b/docs/pages/server-access/guides/openssh.mdx @@ -26,8 +26,7 @@ We've outlined these reasons in [OpenSSH vs Teleport SSH for Servers?](https://g - A Linux host with the OpenSSH server `sshd` installed, but not Teleport. The SSH port on this host must be open to traffic from the Teleport Proxy Service host. - -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Step 1/4. Configure `sshd` to trust the Teleport CA @@ -38,21 +37,10 @@ certificates generated by the Teleport Auth Service. Start by exporting the Teleport CA public key. -On your local machine, print the Teleport certificate authority certificate to -stdout: - -```code -$ tctl auth export --type=user | sed "s/cert-authority\ //" -``` - -Copy the output. - -On the host where you are running `sshd`, run the following commands. - -Assign the output of the `tctl auth export` command to an environment variable: +On the host where you are running `sshd`, run the following commands, assigning to the address of your Teleport Proxy Service: ```code -$ export KEY="" +$ export KEY=$(curl 'https:///webapi/auth/export?type=user' | sed "s/cert-authority\ //") ``` Make the public key accessible to `sshd`: @@ -508,4 +496,4 @@ $ ssh -F ssh_config_teleport ${USER?}@node2.leafcluster.${CLUSTER} To revoke the current Teleport CA and generate a new one, run `tctl auth rotate`. Unless you've highly automated your infrastructure, we would suggest you proceed with caution as this will invalidate the user -and host CAs, meaning that the new CAs will need to be exported to every OpenSSH-based machine again using `tctl auth export` as above. +and host CAs, meaning that the new CAs will need to be exported to every OpenSSH-based machine again using `curl .../auth/export` as above. diff --git a/docs/pages/server-access/guides/recording-proxy-mode.mdx b/docs/pages/server-access/guides/recording-proxy-mode.mdx index b376ca3f6a5dd..bfb464f0b0afe 100644 --- a/docs/pages/server-access/guides/recording-proxy-mode.mdx +++ b/docs/pages/server-access/guides/recording-proxy-mode.mdx @@ -120,11 +120,10 @@ auth_service: by the Teleport User CA. Start by exporting the Teleport CA public key. On your Teleport Node, export the Teleport Certificate Authority certificate -into a file and update your SSH configuration to trust Teleport's CA: +into a file and update your SSH configuration to trust Teleport's CA. Assign to the address of your Teleport Proxy Service: ```code -# tctl needs to be run on the Auth Server. -$ sudo tctl auth export --type=user | sed s/cert-authority\ // > teleport_user_ca.pub +$ curl 'https:///webapi/auth/export?type=user' | sed s/cert-authority\ // > teleport_user_ca.pub $ sudo mv ./teleport_user_ca.pub /etc/ssh/teleport_user_ca.pub $ echo "TrustedUserCAKeys /etc/ssh/teleport_user_ca.pub" | sudo tee -a /etc/ssh/sshd_config ``` diff --git a/docs/pages/server-access/guides/restricted-session.mdx b/docs/pages/server-access/guides/restricted-session.mdx index 1f4b707c6ebd9..dd5227b28364f 100644 --- a/docs/pages/server-access/guides/restricted-session.mdx +++ b/docs/pages/server-access/guides/restricted-session.mdx @@ -37,7 +37,7 @@ Teleport supports network restrictions with more types coming in the future. -(!docs/pages/includes/tctl.mdx!) +- (!docs/pages/includes/tctl.mdx!) ## Network Restrictions diff --git a/docs/pages/server-access/rbac.mdx b/docs/pages/server-access/rbac.mdx index 58f361b51c399..d3706ef264a3d 100644 --- a/docs/pages/server-access/rbac.mdx +++ b/docs/pages/server-access/rbac.mdx @@ -3,7 +3,6 @@ title: Access Controls for Servers description: Role-based access control (RBAC) for Teleport Server Access. --- - You can use Teleport's role-based access control (RBAC) system to set up granular permissions for authenticating to Linux servers connected to Teleport. @@ -15,6 +14,14 @@ emergency.* For a more general description of Teleport roles and examples see our [Access Controls guides](../access-controls/introduction.mdx), as this section focuses on configuring RBAC for servers connected to Teleport. + + +Teleport's RBAC system does not extend to OpenSSH servers, as it is not possible +to apply labels to servers running `sshd`. You must enable access to these +servers via the Teleport SSH Service instead. + + + ## Role configuration Teleport's "role" resource provides the following instruments for restricting diff --git a/dronegen/aws.go b/dronegen/aws.go index f5c763ce27716..33c925d40ce2f 100644 --- a/dronegen/aws.go +++ b/dronegen/aws.go @@ -93,6 +93,7 @@ func kubernetesAssumeAwsRoleStep(s kubernetesRoleSettings) step { return step{ Name: s.name, Image: "amazon/aws-cli", + Pull: "if-not-exists", Environment: map[string]value{ "AWS_ACCESS_KEY_ID": s.awsAccessKeyID, "AWS_SECRET_ACCESS_KEY": s.awsSecretAccessKey, @@ -125,6 +126,7 @@ func kubernetesUploadToS3Step(s kubernetesS3Settings) step { return step{ Name: "Upload to S3", Image: "amazon/aws-cli", + Pull: "if-not-exists", Environment: map[string]value{ "AWS_S3_BUCKET": {fromSecret: "AWS_S3_BUCKET"}, "AWS_REGION": {raw: s.region}, diff --git a/dronegen/buildbox.go b/dronegen/buildbox.go index 75aa73db5e018..b4a696ca03feb 100644 --- a/dronegen/buildbox.go +++ b/dronegen/buildbox.go @@ -69,7 +69,8 @@ func buildboxPipelineStep(buildboxName string, fips bool) step { return step{ Name: "Build and push " + buildboxName, Image: "docker", - Volumes: []volumeRef{volumeRefDocker, volumeRefAwsConfig}, + Pull: "if-not-exists", + Volumes: []volumeRef{volumeRefAwsConfig, volumeRefDocker, volumeRefDockerConfig}, Commands: []string{ `apk add --no-cache make aws-cli`, `chown -R $UID:$GID /go`, @@ -101,7 +102,7 @@ func buildboxPipeline() pipeline { // only on master for now; add the release branch name when forking a new release series. p.Trigger = pushTriggerForBranch("master", "branch/*") p.Workspace = workspace{Path: "/go/src/github.com/gravitational/teleport"} - p.Volumes = []volume{volumeDocker, volumeAwsConfig} + p.Volumes = []volume{volumeAwsConfig, volumeDocker, volumeDockerConfig} p.Services = []service{ dockerService(), } diff --git a/dronegen/common.go b/dronegen/common.go index 63ac021f026fd..2039052aa63e2 100644 --- a/dronegen/common.go +++ b/dronegen/common.go @@ -86,6 +86,25 @@ var ( Name: "awsconfig", Path: "/root/.aws", } + + // volumeDockerConfig is a temporary volume for storing docker + // credentials for use with the Docker-in-Docker service we use + // to isolate the host machines docker daemon from the one used + // during the build. Mount this any time you use `volumeDocker` + // + // Drone claims to destroy the the temp volumes after a workflow + // has run, so it should be safe to write credentials etc. + volumeDockerConfig = volume{ + Name: "dockerconfig", + Temp: &volumeTemp{}, + } + + // volumeRefDockerConfig is how you reference the docker config + // volume in a workflow step + volumeRefDockerConfig = volumeRef{ + Name: "dockerconfig", + Path: "/root/.docker", + } ) var buildboxVersion value @@ -242,18 +261,6 @@ func dockerRegistryService() service { } } -// dockerVolumes returns a slice of volumes -// It includes the Docker socket volume by default, plus any extra volumes passed in -func dockerVolumes(v ...volume) []volume { - return append(v, volumeDocker) -} - -// dockerVolumeRefs returns a slice of volumeRefs -// It includes the Docker socket volumeRef as a default, plus any extra volumeRefs passed in -func dockerVolumeRefs(v ...volumeRef) []volumeRef { - return append(v, volumeRefDocker) -} - // releaseMakefileTarget gets the correct Makefile target for a given arch/fips/centos combo func releaseMakefileTarget(b buildType) string { makefileTarget := fmt.Sprintf("release-%s", b.arch) @@ -283,10 +290,16 @@ func waitForDockerStep() step { return step{ Name: "Wait for docker", Image: "docker", + Pull: "if-not-exists", Commands: []string{ `timeout 30s /bin/sh -c 'while [ ! -S /var/run/docker.sock ]; do sleep 1; done'`, + `printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin`, + }, + Volumes: []volumeRef{volumeRefDocker, volumeRefDockerConfig}, + Environment: map[string]value{ + "DOCKERHUB_USERNAME": {fromSecret: "DOCKERHUB_USERNAME"}, + "DOCKERHUB_PASSWORD": {fromSecret: "DOCKERHUB_READONLY_TOKEN"}, }, - Volumes: []volumeRef{volumeRefDocker}, } } @@ -295,6 +308,7 @@ func waitForDockerRegistryStep() step { return step{ Name: "Wait for docker registry", Image: "alpine", + Pull: "if-not-exists", Commands: []string{ "apk add curl", fmt.Sprintf(`timeout 30s /bin/sh -c 'while [ "$(curl -s -o /dev/null -w %%{http_code} http://%s/)" != "200" ]; do sleep 1; done'`, LocalRegistrySocket), @@ -306,6 +320,7 @@ func verifyTaggedStep() step { return step{ Name: "Verify build is tagged", Image: "alpine:latest", + Pull: "if-not-exists", Commands: []string{ "[ -n ${DRONE_TAG} ] || (echo 'DRONE_TAG is not set. Is the commit tagged?' && exit 1)", }, @@ -317,6 +332,7 @@ func cloneRepoStep(clonePath, commit string) step { return step{ Name: "Check out code", Image: "alpine/git:latest", + Pull: "if-not-exists", Commands: cloneRepoCommands(clonePath, commit), } } diff --git a/dronegen/container_image_products.go b/dronegen/container_image_products.go index 2da9b4f644806..1863e7345ed3d 100644 --- a/dronegen/container_image_products.go +++ b/dronegen/container_image_products.go @@ -478,7 +478,7 @@ func (p *Product) createBuildStep(arch string, version *ReleaseVersion, publicEc step := step{ Name: p.GetBuildStepName(arch, version), Image: "docker", - Volumes: dockerVolumeRefs(volumeRefAwsConfig), + Volumes: []volumeRef{volumeRefAwsConfig, volumeRefDocker}, // no docker config volume, as this will race Environment: envVars, Commands: commands, DependsOn: getStepNames(publicEcrPullRegistry.SetupSteps), diff --git a/dronegen/container_images_release_version.go b/dronegen/container_images_release_version.go index 4dd0e69b989a1..7e5d74811c283 100644 --- a/dronegen/container_images_release_version.go +++ b/dronegen/container_images_release_version.go @@ -48,7 +48,7 @@ func (rv *ReleaseVersion) buildVersionPipeline(triggerSetupSteps []step, flags * dockerService(), dockerRegistryService(), } - pipeline.Volumes = dockerVolumes(volumeAwsConfig) + pipeline.Volumes = []volume{volumeAwsConfig, volumeDocker, volumeDockerConfig} pipeline.Environment = map[string]value{ "DEBIAN_FRONTEND": { raw: "noninteractive", diff --git a/dronegen/container_images_repos.go b/dronegen/container_images_repos.go index fa46b62fd710e..13d78c6e79fb9 100644 --- a/dronegen/container_images_repos.go +++ b/dronegen/container_images_repos.go @@ -62,6 +62,7 @@ func NewEcrContainerRepo(accessKeyIDSecret, secretAccessKeySecret, roleSecret, d loginCommands := []string{ "apk add --no-cache aws-cli", fmt.Sprintf("aws %s get-login-password --region=%s | docker login -u=\"AWS\" --password-stdin %s", loginSubcommand, ecrRegion, domain), + `printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin`, } if guaranteeUnique { @@ -72,7 +73,9 @@ func NewEcrContainerRepo(accessKeyIDSecret, secretAccessKeySecret, roleSecret, d Name: repoName, IsImmutable: isImmutable, EnvironmentVars: map[string]value{ - "AWS_PROFILE": {raw: profileName}, + "AWS_PROFILE": {raw: profileName}, + "DOCKERHUB_USERNAME": {fromSecret: "DOCKERHUB_USERNAME"}, + "DOCKERHUB_PASSWORD": {fromSecret: "DOCKERHUB_READONLY_TOKEN"}, }, RegistryDomain: domain, RegistryOrg: registryOrg, @@ -112,17 +115,16 @@ func NewQuayContainerRepo(dockerUsername, dockerPassword string) *ContainerRepo Name: "Quay", IsImmutable: false, EnvironmentVars: map[string]value{ - "QUAY_USERNAME": { - fromSecret: dockerUsername, - }, - "QUAY_PASSWORD": { - fromSecret: dockerPassword, - }, + "QUAY_USERNAME": {fromSecret: dockerUsername}, + "QUAY_PASSWORD": {fromSecret: dockerPassword}, + "DOCKERHUB_USERNAME": {fromSecret: "DOCKERHUB_USERNAME"}, + "DOCKERHUB_PASSWORD": {fromSecret: "DOCKERHUB_READONLY_TOKEN"}, }, RegistryDomain: ProductionRegistryQuay, RegistryOrg: registryOrg, LoginCommands: []string{ fmt.Sprintf("docker login -u=\"$QUAY_USERNAME\" -p=\"$QUAY_PASSWORD\" %q", ProductionRegistryQuay), + `printenv DOCKERHUB_PASSWORD | docker login -u="$DOCKERHUB_USERNAME" --password-stdin`, }, } } @@ -254,7 +256,7 @@ func (cr *ContainerRepo) pullPushStep(image *Image, dependencySteps []string) (s return step{ Name: fmt.Sprintf("Pull %s and push it to %s", image.GetDisplayName(), localRepo.Name), Image: "docker", - Volumes: dockerVolumeRefs(volumeRefAwsConfig), + Volumes: []volumeRef{volumeRefAwsConfig, volumeRefDocker}, // no docker config volume, as this will race Environment: cr.EnvironmentVars, Commands: commands, DependsOn: dependencySteps, @@ -304,7 +306,7 @@ func (cr *ContainerRepo) tagAndPushStep(buildStepDetails *buildStepOutput, image step := step{ Name: fmt.Sprintf("Tag and push image %q to %s", buildStepDetails.BuiltImage.GetDisplayName(), cr.Name), Image: "docker", - Volumes: dockerVolumeRefs(volumeRefAwsConfig), + Volumes: []volumeRef{volumeRefAwsConfig, volumeRefDocker}, // no docker config volume, as this will race Environment: cr.EnvironmentVars, Commands: commands, DependsOn: dependencySteps, @@ -332,7 +334,7 @@ func (cr *ContainerRepo) createAndPushManifestStep(manifestImage *Image, pushSte return step{ Name: fmt.Sprintf("Create manifest and push %q to %s", manifestImage.GetDisplayName(), cr.Name), Image: "docker", - Volumes: dockerVolumeRefs(volumeRefAwsConfig), + Volumes: []volumeRef{volumeRefAwsConfig, volumeRefDocker}, // no docker config volume, as this will race Environment: cr.EnvironmentVars, Commands: cr.buildCommandsWithLogin(commands), DependsOn: pushStepNames, diff --git a/dronegen/gha.go b/dronegen/gha.go index 0cf3c0a1c4faf..9ab3dcbb6b58c 100644 --- a/dronegen/gha.go +++ b/dronegen/gha.go @@ -62,6 +62,7 @@ func ghaBuildPipeline(b ghaBuildType) pipeline { { Name: "Check out code", Image: "docker:git", + Pull: "if-not-exists", Environment: map[string]value{ "GITHUB_PRIVATE_KEY": {fromSecret: "GITHUB_PRIVATE_KEY"}, }, @@ -70,6 +71,7 @@ func ghaBuildPipeline(b ghaBuildType) pipeline { { Name: "Delegate build to GitHub", Image: fmt.Sprintf("golang:%s-alpine", GoVersion), + Pull: "if-not-exists", Environment: map[string]value{ "GHA_APP_KEY": {fromSecret: "GITHUB_WORKFLOW_APP_PRIVATE_KEY"}, }, diff --git a/dronegen/main.go b/dronegen/main.go index 517b80fd518e4..5ce4e6998ad31 100644 --- a/dronegen/main.go +++ b/dronegen/main.go @@ -39,6 +39,22 @@ func main() { pipelines = append(pipelines, buildContainerImagePipelines()...) pipelines = append(pipelines, publishReleasePipeline()) + // Inject the Drone-level dockerhub credentials into all non-exec + // pipelines. Drone will then use the docker credentials file in + // the named secret as its credentials when pulling images from + // dockerhub. + // + // Exec pipelines do not have the `image_pull_secrets` option, as + // their steps are invoked directly on the host runner and not + // into a per-step container. + for pidx := range pipelines { + p := &pipelines[pidx] + if p.Type == "exec" { + continue + } + p.ImagePullSecrets = append(p.ImagePullSecrets, "DOCKERHUB_CREDENTIALS") + } + if err := writePipelines(".drone.yml", pipelines); err != nil { fmt.Println("failed writing drone pipelines:", err) os.Exit(1) diff --git a/dronegen/push.go b/dronegen/push.go index 45419110fa360..1cea3b2ec4182 100644 --- a/dronegen/push.go +++ b/dronegen/push.go @@ -126,7 +126,7 @@ func pushPipeline(b buildType) pipeline { } p.Trigger = triggerPush p.Workspace = workspace{Path: "/go"} - p.Volumes = []volume{volumeDocker} + p.Volumes = []volume{volumeDocker, volumeDockerConfig} p.Services = []service{ dockerService(), } @@ -134,6 +134,7 @@ func pushPipeline(b buildType) pipeline { { Name: "Check out code", Image: "docker:git", + Pull: "if-not-exists", Environment: map[string]value{ "GITHUB_PRIVATE_KEY": {fromSecret: "GITHUB_PRIVATE_KEY"}, }, @@ -143,8 +144,9 @@ func pushPipeline(b buildType) pipeline { { Name: "Build artifacts", Image: "docker", + Pull: "if-not-exists", Environment: pushEnvironment, - Volumes: []volumeRef{volumeRefDocker}, + Volumes: []volumeRef{volumeRefDocker, volumeRefDockerConfig}, Commands: pushBuildCommands(b), }, sendErrorToSlackStep(), diff --git a/dronegen/relcli.go b/dronegen/relcli.go index c119bb7132215..f6f4fc7464568 100644 --- a/dronegen/relcli.go +++ b/dronegen/relcli.go @@ -44,11 +44,7 @@ func relcliPipeline(trigger trigger, name string, stepName string, command strin } p.Services = []service{dockerService(volumeRefTmpfs)} - p.Volumes = []volume{ - volumeDocker, - volumeTmpfs, - volumeAwsConfig, - } + p.Volumes = []volume{volumeTmpfs, volumeAwsConfig, volumeDocker, volumeDockerConfig} return p } @@ -60,10 +56,7 @@ func pullRelcliStep(awsConfigVolumeRef volumeRef) step { Environment: map[string]value{ "AWS_DEFAULT_REGION": {raw: "us-west-2"}, }, - Volumes: []volumeRef{ - volumeRefDocker, - volumeRefAwsConfig, - }, + Volumes: []volumeRef{volumeRefDocker, volumeRefAwsConfig}, Commands: []string{ `apk add --no-cache aws-cli`, `aws ecr get-login-password | docker login -u="AWS" --password-stdin 146628656107.dkr.ecr.us-west-2.amazonaws.com`, @@ -83,11 +76,7 @@ func executeRelcliStep(name string, command string) step { "RELCLI_CERT": {raw: "/tmpfs/creds/releases.crt"}, "RELCLI_KEY": {raw: "/tmpfs/creds/releases.key"}, }, - Volumes: []volumeRef{ - volumeRefDocker, - volumeRefTmpfs, - volumeRefAwsConfig, - }, + Volumes: []volumeRef{volumeRefDocker, volumeRefTmpfs, volumeRefAwsConfig}, Commands: []string{ `mkdir -p /tmpfs/creds`, `echo "$RELEASES_CERT" | base64 -d > "$RELCLI_CERT"`, diff --git a/dronegen/tag.go b/dronegen/tag.go index f82b505fd5494..1c9a6959f67d6 100644 --- a/dronegen/tag.go +++ b/dronegen/tag.go @@ -274,7 +274,7 @@ func tagPipeline(b buildType) pipeline { p.Trigger = triggerTag p.DependsOn = []string{tagCleanupPipelineName} p.Workspace = workspace{Path: "/go"} - p.Volumes = []volume{volumeAwsConfig, volumeDocker} + p.Volumes = []volume{volumeAwsConfig, volumeDocker, volumeDockerConfig} p.Services = []service{ dockerService(), } @@ -282,6 +282,7 @@ func tagPipeline(b buildType) pipeline { { Name: "Check out code", Image: "docker:git", + Pull: "if-not-exists", Environment: map[string]value{ "GITHUB_PRIVATE_KEY": {fromSecret: "GITHUB_PRIVATE_KEY"}, }, @@ -291,13 +292,15 @@ func tagPipeline(b buildType) pipeline { { Name: "Build artifacts", Image: "docker", + Pull: "if-not-exists", Environment: tagEnvironment, - Volumes: []volumeRef{volumeRefDocker}, + Volumes: []volumeRef{volumeRefDocker, volumeRefDockerConfig}, Commands: tagBuildCommands(b), }, { Name: "Copy artifacts", Image: "docker", + Pull: "if-not-exists", Commands: tagCopyArtifactCommands(b), }, kubernetesAssumeAwsRoleStep(kubernetesRoleSettings{ @@ -317,6 +320,7 @@ func tagPipeline(b buildType) pipeline { { Name: "Register artifacts", Image: "docker", + Pull: "if-not-exists", Commands: tagCreateReleaseAssetCommands(b, "", extraQualifications), Environment: map[string]value{ "RELEASES_CERT": {fromSecret: "RELEASES_CERT"}, @@ -465,12 +469,10 @@ func tagPackagePipeline(packageType string, b buildType) pipeline { environment["OSS_TARBALL_PATH"] = value{raw: "/go/artifacts"} } - packageDockerVolumes := []volume{ - volumeDocker, - volumeAwsConfig, - } + packageDockerVolumes := []volume{volumeAwsConfig, volumeDocker, volumeDockerConfig} packageDockerVolumeRefs := []volumeRef{ volumeRefDocker, + volumeRefDockerConfig, volumeRefAwsConfig, } packageDockerService := dockerService() diff --git a/dronegen/types.go b/dronegen/types.go index 750761768d5c0..ff72da0f00944 100644 --- a/dronegen/types.go +++ b/dronegen/types.go @@ -29,20 +29,21 @@ import ( type pipeline struct { comment string - Kind string `yaml:"kind"` - Type string `yaml:"type"` - Name string `yaml:"name"` - Environment map[string]value `yaml:"environment,omitempty"` - Trigger trigger `yaml:"trigger"` - Workspace workspace `yaml:"workspace,omitempty"` - Platform platform `yaml:"platform,omitempty"` - Node map[string]value `yaml:"node,omitempty"` - Clone clone `yaml:"clone,omitempty"` - DependsOn []string `yaml:"depends_on,omitempty"` - Concurrency concurrency `yaml:"concurrency,omitempty"` - Steps []step `yaml:"steps"` - Services []service `yaml:"services,omitempty"` - Volumes []volume `yaml:"volumes,omitempty"` + Kind string `yaml:"kind"` + Type string `yaml:"type"` + Name string `yaml:"name"` + Environment map[string]value `yaml:"environment,omitempty"` + Trigger trigger `yaml:"trigger"` + Workspace workspace `yaml:"workspace,omitempty"` + Platform platform `yaml:"platform,omitempty"` + Node map[string]value `yaml:"node,omitempty"` + Clone clone `yaml:"clone,omitempty"` + DependsOn []string `yaml:"depends_on,omitempty"` + Concurrency concurrency `yaml:"concurrency,omitempty"` + Steps []step `yaml:"steps"` + Services []service `yaml:"services,omitempty"` + Volumes []volume `yaml:"volumes,omitempty"` + ImagePullSecrets []string `yaml:"image_pull_secrets,omitempty"` } func newKubePipeline(name string) pipeline { @@ -169,6 +170,7 @@ type volumeRef struct { type step struct { Name string `yaml:"name"` Image string `yaml:"image,omitempty"` + Pull string `yaml:"pull,omitempty"` Commands []string `yaml:"commands,omitempty"` Environment map[string]value `yaml:"environment,omitempty"` Volumes []volumeRef `yaml:"volumes,omitempty"` diff --git a/e b/e index 06f4ac949dc79..0382c9899a900 160000 --- a/e +++ b/e @@ -1 +1 @@ -Subproject commit 06f4ac949dc7989b116d04119f028a2d5e904a0a +Subproject commit 0382c9899a90047aa887fdbb2e734dc8b2c36249 diff --git a/examples/api-sync-roles/README.md b/examples/api-sync-roles/README.md new file mode 100644 index 0000000000000..2842d8448f377 --- /dev/null +++ b/examples/api-sync-roles/README.md @@ -0,0 +1,6 @@ +# Minimal API Client for Generating Roles + +This is a minimal working example of a Teleport API client that generates +Teleport roles based on an external RBAC system, in this case, a Kubernetes +cluster. To use it, follow the setup steps in the [Teleport +documentation](https://goteleport.com/docs/api/rbac). diff --git a/examples/api-sync-roles/go.mod b/examples/api-sync-roles/go.mod new file mode 100644 index 0000000000000..67884cd3ca080 --- /dev/null +++ b/examples/api-sync-roles/go.mod @@ -0,0 +1,81 @@ +module sync-roles + +go 1.19 + +require ( + github.com/gravitational/teleport/api v0.0.0-20230320182000-4099bf7d2364 + github.com/gravitational/trace v1.2.1 + google.golang.org/grpc v1.53.0 + k8s.io/api v0.26.3 + k8s.io/apimachinery v0.26.3 + k8s.io/client-go v0.26.3 +) + +require ( + github.com/beevik/etree v1.1.0 // indirect + github.com/cenkalti/backoff/v4 v4.2.0 // indirect + github.com/coreos/go-semver v0.3.1 // indirect + github.com/crewjam/httperr v0.2.0 // indirect + github.com/crewjam/saml v0.4.13 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/emicklei/go-restful/v3 v3.9.0 // indirect + github.com/felixge/httpsnoop v1.0.3 // indirect + github.com/go-logr/logr v1.2.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.20.0 // indirect + github.com/go-openapi/swag v0.19.14 // indirect + github.com/go-piv/piv-go v1.10.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt/v4 v4.4.3 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/google/gnostic v0.5.7-v3refs // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/google/gofuzz v1.1.0 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect + github.com/imdario/mergo v0.3.6 // indirect + github.com/jonboulle/clockwork v0.3.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.7.6 // indirect + github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/russellhaering/gosaml2 v0.9.1 // indirect + github.com/russellhaering/goxmldsig v1.3.0 // indirect + github.com/sirupsen/logrus v1.9.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.40.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.40.0 // indirect + go.opentelemetry.io/otel v1.14.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.14.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0 // indirect + go.opentelemetry.io/otel/metric v0.37.0 // indirect + go.opentelemetry.io/otel/sdk v1.14.0 // indirect + go.opentelemetry.io/otel/trace v1.14.0 // indirect + go.opentelemetry.io/proto/otlp v0.19.0 // indirect + golang.org/x/crypto v0.6.0 // indirect + golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9 // indirect + golang.org/x/net v0.8.0 // indirect + golang.org/x/oauth2 v0.4.0 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/term v0.6.0 // indirect + golang.org/x/text v0.8.0 // indirect + golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc // indirect + google.golang.org/protobuf v1.29.1 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/klog/v2 v2.80.1 // indirect + k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect + k8s.io/utils v0.0.0-20221107191617-1a15be271d1d // indirect + sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect +) diff --git a/examples/api-sync-roles/go.sum b/examples/api-sync-roles/go.sum new file mode 100644 index 0000000000000..c3077ccb6c0f7 --- /dev/null +++ b/examples/api-sync-roles/go.sum @@ -0,0 +1,620 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.105.0 h1:DNtEKRBAAzeS4KyIory52wWHuClNaXJ5x1F7xa4q+5Y= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute v1.15.1 h1:7UGq3QknM33pw5xATlpzeoomNxsacIVvTqTTvbfajmE= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= +github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= +github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4= +github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= +github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/crewjam/httperr v0.2.0 h1:b2BfXR8U3AlIHwNeFFvZ+BV1LFvKLlzMjzaTnZMybNo= +github.com/crewjam/httperr v0.2.0/go.mod h1:Jlz+Sg/XqBQhyMjdDiC+GNNRzZTD7x39Gu3pglZ5oH4= +github.com/crewjam/saml v0.4.13 h1:TYHggH/hwP7eArqiXSJUvtOPNzQDyQ7vwmwEqlFWhMc= +github.com/crewjam/saml v0.4.13/go.mod h1:igEejV+fihTIlHXYP8zOec3V5A8y3lws5bQBFsTm4gA= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dchest/uniuri v1.2.0/go.mod h1:fSzm4SLHzNZvWLvWJew423PhAzkpNQYq+uNLq4kxhkY= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= +github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= +github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= +github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-piv/piv-go v1.10.0 h1:P1Y1VjBI5DnXW0+YkKmTuh5opWnMIrKriUaIOblee9Q= +github.com/go-piv/piv-go v1.10.0/go.mod h1:NZ2zmjVkfFaL/CF8cVQ/pXdXtuj110zEKGdJM6fJZZM= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU= +github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= +github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gravitational/teleport/api v0.0.0-20230320182000-4099bf7d2364 h1:4gNP29h/hP/WAYtgYzxULyir/fFUgaEy04MOzMlNUIE= +github.com/gravitational/teleport/api v0.0.0-20230320182000-4099bf7d2364/go.mod h1:biEKf+i55ETv3u3hOcdC81h7joKAKPa5PEJNgQxtjpQ= +github.com/gravitational/trace v1.2.1 h1:Iaf43aqbKV5H8bdiRs1qByjEHgAfADJ0lt0JwRyu+q8= +github.com/gravitational/trace v1.2.1/go.mod h1:n0ijrq6psJY0sOI/NzLp+xdd8xl79jjwzVOFHDY6+kQ= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= +github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/jonboulle/clockwork v0.3.0 h1:9BSCMi8C+0qdApAp4auwX0RkLGUjs956h0EkuQymUhg= +github.com/jonboulle/clockwork v0.3.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU= +github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs= +github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/russellhaering/gosaml2 v0.9.1 h1:H/whrl8NuSoxyW46Ww5lKPskm+5K+qYLw9afqJ/Zef0= +github.com/russellhaering/gosaml2 v0.9.1/go.mod h1:ja+qgbayxm+0mxBRLMSUuX3COqy+sb0RRhIGun/W2kc= +github.com/russellhaering/goxmldsig v1.2.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= +github.com/russellhaering/goxmldsig v1.3.0 h1:DllIWUgMy0cRUMfGiASiYEa35nsieyD3cigIwLonTPM= +github.com/russellhaering/goxmldsig v1.3.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.40.0 h1:5jD3teb4Qh7mx/nfzq4jO2WFFpvXD0vYWFDrdvNWmXk= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.40.0/go.mod h1:UMklln0+MRhZC4e3PwmN3pCtq4DyIadWw4yikh6bNrw= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.40.0 h1:lE9EJyw3/JhrjWH/hEy9FptnalDQgj7vpbgC2KCCCxE= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.40.0/go.mod h1:pcQ3MM3SWvrA71U4GDqv9UFDJ3HQsW7y5ZO3tDTlUdI= +go.opentelemetry.io/otel v1.14.0 h1:/79Huy8wbf5DnIPhemGB+zEPVwnN6fuQybr/SRXa6hM= +go.opentelemetry.io/otel v1.14.0/go.mod h1:o4buv+dJzx8rohcUeRmWUZhqupFvzWis188WlggnNeU= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0 h1:/fXHZHGvro6MVqV34fJzDhi7sHGpX3Ej/Qjmfn003ho= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0/go.mod h1:UFG7EBMRdXyFstOwH028U0sVf+AvukSGhF0g8+dmNG8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.14.0 h1:TKf2uAs2ueguzLaxOCBXNpHxfO/aC7PAdDsSH0IbeRQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.14.0/go.mod h1:HrbCVv40OOLTABmOn1ZWty6CHXkU8DK/Urc43tHug70= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0 h1:ap+y8RXX3Mu9apKVtOkM6WSFESLM8K3wNQyOU8sWHcc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0/go.mod h1:5w41DY6S9gZrbjuq6Y+753e96WfPha5IcsOSZTtullM= +go.opentelemetry.io/otel/metric v0.37.0 h1:pHDQuLQOZwYD+Km0eb657A25NaRzy0a+eLyKfDXedEs= +go.opentelemetry.io/otel/metric v0.37.0/go.mod h1:DmdaHfGt54iV6UKxsV9slj2bBRJcKC1B1uvDLIioc1s= +go.opentelemetry.io/otel/sdk v1.14.0 h1:PDCppFRDq8A1jL9v6KMI6dYesaq+DFcDZvjsoGvxGzY= +go.opentelemetry.io/otel/sdk v1.14.0/go.mod h1:bwIC5TjrNG6QDCHNWvW4HLHtUQ4I+VQDsnjhvyZCALM= +go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyKcFq/M= +go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= +go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20220126234351-aa10faf2a1f8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9 h1:yZNXmy+j/JpX19vZkVktWqAo7Gny4PBWYYK3zskGpx4= +golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M= +golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc h1:ijGwO+0vL2hJt5gaygqP2j6PfflOBrRot0IczKbmtio= +google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= +google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.29.1 h1:7QBf+IK2gx70Ap/hDsOmam3GE0v9HicjfEdAxE62UoM= +google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.26.3 h1:emf74GIQMTik01Aum9dPP0gAypL8JTLl/lHa4V9RFSU= +k8s.io/api v0.26.3/go.mod h1:PXsqwPMXBSBcL1lJ9CYDKy7kIReUydukS5JiRlxC3qE= +k8s.io/apimachinery v0.26.3 h1:dQx6PNETJ7nODU3XPtrwkfuubs6w7sX0M8n61zHIV/k= +k8s.io/apimachinery v0.26.3/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I= +k8s.io/client-go v0.26.3 h1:k1UY+KXfkxV2ScEL3gilKcF7761xkYsSD6BC9szIu8s= +k8s.io/client-go v0.26.3/go.mod h1:ZPNu9lm8/dbRIPAgteN30RSXea6vrCpFvq+MateTUuQ= +k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= +k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= +k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= +k8s.io/utils v0.0.0-20221107191617-1a15be271d1d h1:0Smp/HP1OH4Rvhe+4B8nWGERtlqAGSftbSbbmm45oFs= +k8s.io/utils v0.0.0-20221107191617-1a15be271d1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= +sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/examples/api-sync-roles/main.go b/examples/api-sync-roles/main.go new file mode 100644 index 0000000000000..e33a4904c6917 --- /dev/null +++ b/examples/api-sync-roles/main.go @@ -0,0 +1,234 @@ +// Copyright 2023 Gravitational, Inc +// +// 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. + +package main + +import ( + "context" + "fmt" + "io" + "os" + "time" + + "github.com/gravitational/teleport/api/client" + "github.com/gravitational/teleport/api/types" + "github.com/gravitational/trace" + "google.golang.org/grpc" + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + v1 "k8s.io/client-go/kubernetes/typed/rbac/v1" + "k8s.io/client-go/tools/clientcmd" +) + +const ( + proxyAddr string = "" + initTimeout = time.Duration(30) * time.Second + identityFilePath string = "auth.pem" + kubeconfigPath string = "kubeconfig" + clusterName string = "minikube" + roleAnnotationKey string = "create-teleport-role" +) + +func getRBACClient() (v1.RbacV1Interface, error) { + f, err := os.Open(kubeconfigPath) + if err != nil { + return nil, trace.Wrap(err) + } + + kc, err := io.ReadAll(f) + if err != nil { + return nil, trace.Wrap(err) + } + n, err := clientcmd.RESTConfigFromKubeConfig(kc) + if err != nil { + return nil, trace.Wrap(err) + } + + c, err := kubernetes.NewForConfig(n) + if err != nil { + return nil, trace.Wrap(err) + } + + return c.RbacV1(), nil +} + +func createTeleportRoleFromClusterRoleBinding(teleport *client.Client, k types.KubeCluster, r rbacv1.ClusterRoleBinding) error { + if e, ok := r.Annotations[roleAnnotationKey]; !ok || e != "true" { + return nil + } + + role := types.RoleV6{} + role.SetMetadata(types.Metadata{ + Name: k.GetName() + "-" + r.RoleRef.Name + "-" + "cluster", + }) + + b := k.GetStaticLabels() + labels := make(types.Labels) + for k, v := range b { + labels[k] = []string{v} + } + role.SetKubernetesLabels(types.Allow, labels) + role.SetKubeResources(types.Allow, []types.KubernetesResource{ + types.KubernetesResource{ + Kind: "pod", + Namespace: "*", + Name: "*", + }, + }) + + var g []string + var u []string + for _, s := range r.Subjects { + if s.Kind == "User" || s.Kind == "ServiceAccount" { + u = append(u, s.Name) + continue + } + if s.Kind == "Group" { + g = append(g, s.Name) + continue + } + } + role.SetKubeGroups(types.Allow, g) + role.SetKubeUsers(types.Allow, u) + if err := teleport.UpsertRole( + context.Background(), + &role, + ); err != nil { + return trace.Wrap(err) + } + fmt.Println("Upserted Teleport role:", role.GetName()) + return nil +} + +func createTeleportRoleFromRoleBinding(teleport *client.Client, k types.KubeCluster, r rbacv1.RoleBinding) error { + if e, ok := r.Annotations[roleAnnotationKey]; !ok || e != "true" { + return nil + } + + role := types.RoleV6{} + role.SetMetadata(types.Metadata{ + Name: k.GetName() + "-" + r.RoleRef.Name + "-" + r.Namespace, + }) + + b := k.GetStaticLabels() + labels := make(types.Labels) + for k, v := range b { + labels[k] = []string{v} + } + role.SetKubernetesLabels(types.Allow, labels) + role.SetKubeResources(types.Allow, []types.KubernetesResource{ + types.KubernetesResource{ + Kind: "pod", + Namespace: r.Namespace, + Name: "*", + }, + }) + var g []string + var u []string + for _, s := range r.Subjects { + if s.Kind == "User" || s.Kind == "ServiceAccount" { + u = append(u, s.Name) + continue + } + if s.Kind == "Group" { + g = append(g, s.Name) + continue + } + } + role.SetKubeGroups(types.Allow, g) + role.SetKubeUsers(types.Allow, u) + + if err := teleport.UpsertRole( + context.Background(), + &role, + ); err != nil { + return trace.Wrap(err) + } + fmt.Println("Upserted Teleport role:", role.GetName()) + return nil +} + +func createTeleportRolesForKubeCluster(teleport *client.Client, k types.KubeCluster) error { + rbac, err := getRBACClient() + if err != nil { + return trace.Wrap(err) + } + + crb, err := rbac.ClusterRoleBindings().List( + context.Background(), + metav1.ListOptions{}, + ) + if err != nil { + return trace.Wrap(err) + } + + for _, i := range crb.Items { + if err := createTeleportRoleFromClusterRoleBinding(teleport, k, i); err != nil { + return trace.Wrap(err) + } + } + + rb, err := rbac.RoleBindings("").List( + context.Background(), + metav1.ListOptions{}, + ) + if err != nil { + return trace.Wrap(err) + } + + for _, i := range rb.Items { + if err := createTeleportRoleFromRoleBinding(teleport, k, i); err != nil { + return trace.Wrap(err) + } + } + return nil +} + +func main() { + ctx := context.Background() + ctx, cancel := context.WithTimeout(context.Background(), initTimeout) + defer cancel() + creds := client.LoadIdentityFile(identityFilePath) + + teleport, err := client.New(ctx, client.Config{ + Addrs: []string{proxyAddr}, + Credentials: []client.Credentials{creds}, + DialOpts: []grpc.DialOption{ + grpc.WithReturnConnectionError(), + }, + }) + if err != nil { + panic(err) + } + fmt.Println("Connected to Teleport") + + ks, err := teleport.GetKubernetesServers(context.Background()) + if err != nil { + panic(err) + } + for _, k := range ks { + if k.GetCluster().GetName() != clusterName { + continue + } + fmt.Println("Retrieved Kubernetes cluster", clusterName) + + if err := createTeleportRolesForKubeCluster(teleport, k.GetCluster()); err != nil { + panic(err) + } + fmt.Println("Created roles for Kubernetes cluster", clusterName) + return + } + panic("Unable to locate a Kubernetes Service instance for " + clusterName) +} diff --git a/examples/bench/example.go b/examples/bench/example.go index 1d925c014f027..00b0c5b699cb9 100644 --- a/examples/bench/example.go +++ b/examples/bench/example.go @@ -19,6 +19,7 @@ import ( "context" "fmt" "os" + "strings" "time" "github.com/gravitational/teleport/lib/benchmark" @@ -34,7 +35,16 @@ func main() { } // Run Linear generator - results, err := benchmark.Run(context.TODO(), linear, "ls -l /", "host", "username", "teleport.example.com") + results, err := benchmark.Run( + context.TODO(), + linear, + "host", + "username", + "teleport.example.com", + benchmark.SSHBenchmark{ + Command: strings.Split("ls -l /", " "), + }, + ) if err != nil { fmt.Println(err) os.Exit(1) diff --git a/examples/chart/teleport-cluster/.lint/imagepullsecrets.yaml b/examples/chart/teleport-cluster/.lint/imagepullsecrets.yaml new file mode 100644 index 0000000000000..f414f8c331302 --- /dev/null +++ b/examples/chart/teleport-cluster/.lint/imagepullsecrets.yaml @@ -0,0 +1,4 @@ +clusterName: test-standalone-cluster +chartMode: standalone +imagePullSecrets: +- name: myRegistryKeySecretName diff --git a/examples/chart/teleport-cluster/templates/auth/deployment.yaml b/examples/chart/teleport-cluster/templates/auth/deployment.yaml index 5b74c87d0647a..4940e7382209c 100644 --- a/examples/chart/teleport-cluster/templates/auth/deployment.yaml +++ b/examples/chart/teleport-cluster/templates/auth/deployment.yaml @@ -87,6 +87,10 @@ spec: {{- if $auth.tolerations }} tolerations: {{- toYaml $auth.tolerations | nindent 6 }} {{- end }} +{{- if $auth.imagePullSecrets }} + imagePullSecrets: + {{- toYaml $auth.imagePullSecrets | nindent 6 }} +{{- end }} {{- if $auth.initContainers }} initContainers: {{- range $initContainer := $auth.initContainers }} diff --git a/examples/chart/teleport-cluster/templates/auth/predeploy_config.yaml b/examples/chart/teleport-cluster/templates/auth/predeploy_config.yaml index fcbcab24572c7..14194400dd22f 100644 --- a/examples/chart/teleport-cluster/templates/auth/predeploy_config.yaml +++ b/examples/chart/teleport-cluster/templates/auth/predeploy_config.yaml @@ -10,7 +10,7 @@ metadata: annotations: "helm.sh/hook": pre-install,pre-upgrade "helm.sh/hook-weight": "4" - "helm.sh/hook-delete-policy": before-hook-creation + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded data: {{- if $auth.createProxyToken }} apply-on-startup.yaml: |2 diff --git a/examples/chart/teleport-cluster/templates/auth/predeploy_job.yaml b/examples/chart/teleport-cluster/templates/auth/predeploy_job.yaml index 495d2d781d7a3..0b2357249b009 100644 --- a/examples/chart/teleport-cluster/templates/auth/predeploy_job.yaml +++ b/examples/chart/teleport-cluster/templates/auth/predeploy_job.yaml @@ -9,7 +9,7 @@ metadata: annotations: "helm.sh/hook": pre-install,pre-upgrade "helm.sh/hook-weight": "5" - "helm.sh/hook-delete-policy": hook-succeeded + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded spec: backoffLimit: 1 template: diff --git a/examples/chart/teleport-cluster/templates/proxy/deployment.yaml b/examples/chart/teleport-cluster/templates/proxy/deployment.yaml index 7be901f1bbf97..8e502e209db56 100644 --- a/examples/chart/teleport-cluster/templates/proxy/deployment.yaml +++ b/examples/chart/teleport-cluster/templates/proxy/deployment.yaml @@ -93,6 +93,10 @@ spec: {{- end }} {{- if $proxy.tolerations }} tolerations: {{- toYaml $proxy.tolerations | nindent 6 }} +{{- end }} +{{- if $proxy.imagePullSecrets }} + imagePullSecrets: + {{- toYaml $proxy.imagePullSecrets | nindent 6 }} {{- end }} initContainers: # wait-auth-update is responsible for holding off the proxy rollout until all auths are running the diff --git a/examples/chart/teleport-cluster/templates/proxy/predeploy_config.yaml b/examples/chart/teleport-cluster/templates/proxy/predeploy_config.yaml index c72214efdbf72..6e2d374bec292 100644 --- a/examples/chart/teleport-cluster/templates/proxy/predeploy_config.yaml +++ b/examples/chart/teleport-cluster/templates/proxy/predeploy_config.yaml @@ -9,7 +9,7 @@ metadata: annotations: "helm.sh/hook": pre-install,pre-upgrade "helm.sh/hook-weight": "4" - "helm.sh/hook-delete-policy": before-hook-creation + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded data: teleport.yaml: |2 {{- mustMergeOverwrite (include $configTemplate . | fromYaml) $proxy.teleportConfig | toYaml | nindent 4 -}} diff --git a/examples/chart/teleport-cluster/templates/proxy/predeploy_job.yaml b/examples/chart/teleport-cluster/templates/proxy/predeploy_job.yaml index eeed525383296..2efc7eaab84b9 100644 --- a/examples/chart/teleport-cluster/templates/proxy/predeploy_job.yaml +++ b/examples/chart/teleport-cluster/templates/proxy/predeploy_job.yaml @@ -9,7 +9,7 @@ metadata: annotations: "helm.sh/hook": pre-install,pre-upgrade "helm.sh/hook-weight": "5" - "helm.sh/hook-delete-policy": hook-succeeded + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded spec: backoffLimit: 1 template: diff --git a/examples/chart/teleport-cluster/tests/__snapshot__/auth_deployment_test.yaml.snap b/examples/chart/teleport-cluster/tests/__snapshot__/auth_deployment_test.yaml.snap index e43854a4bc4a7..1e01b4485b29e 100644 --- a/examples/chart/teleport-cluster/tests/__snapshot__/auth_deployment_test.yaml.snap +++ b/examples/chart/teleport-cluster/tests/__snapshot__/auth_deployment_test.yaml.snap @@ -127,6 +127,9 @@ should set affinity when set in values: operator: In values: - teleport +should set imagePullSecrets when set in values: + 1: | + - name: myRegistryKeySecretName should set nodeSelector when set in values: 1: | affinity: diff --git a/examples/chart/teleport-cluster/tests/__snapshot__/proxy_deployment_test.yaml.snap b/examples/chart/teleport-cluster/tests/__snapshot__/proxy_deployment_test.yaml.snap index 46558fc006ebf..5be342d26a0b4 100644 --- a/examples/chart/teleport-cluster/tests/__snapshot__/proxy_deployment_test.yaml.snap +++ b/examples/chart/teleport-cluster/tests/__snapshot__/proxy_deployment_test.yaml.snap @@ -51,6 +51,9 @@ should set affinity when set in values: operator: In values: - teleport +should set imagePullSecrets when set in values: + 1: | + - name: myRegistryKeySecretName should set nodeSelector when set in values: 1: | affinity: diff --git a/examples/chart/teleport-cluster/tests/auth_deployment_test.yaml b/examples/chart/teleport-cluster/tests/auth_deployment_test.yaml index 10ae92b9d6fc8..59e6b03a8533c 100644 --- a/examples/chart/teleport-cluster/tests/auth_deployment_test.yaml +++ b/examples/chart/teleport-cluster/tests/auth_deployment_test.yaml @@ -325,6 +325,17 @@ tests: name: SOME_ENVIRONMENT_VARIABLE value: "some-value" + - it: should set imagePullSecrets when set in values + template: auth/deployment.yaml + values: + - ../.lint/imagepullsecrets.yaml + asserts: + - equal: + path: spec.template.spec.imagePullSecrets[0].name + value: myRegistryKeySecretName + - matchSnapshot: + path: spec.template.spec.imagePullSecrets + - it: should provision initContainer correctly when set in values template: auth/deployment.yaml values: diff --git a/examples/chart/teleport-cluster/tests/proxy_deployment_test.yaml b/examples/chart/teleport-cluster/tests/proxy_deployment_test.yaml index d9647799e3bcf..84eaa7878a77e 100644 --- a/examples/chart/teleport-cluster/tests/proxy_deployment_test.yaml +++ b/examples/chart/teleport-cluster/tests/proxy_deployment_test.yaml @@ -343,6 +343,17 @@ tests: name: SOME_ENVIRONMENT_VARIABLE value: "some-value" + - it: should set imagePullSecrets when set in values + template: proxy/deployment.yaml + values: + - ../.lint/imagepullsecrets.yaml + asserts: + - equal: + path: spec.template.spec.imagePullSecrets[0].name + value: myRegistryKeySecretName + - matchSnapshot: + path: spec.template.spec.imagePullSecrets + - it: should provision initContainer correctly when set in values template: proxy/deployment.yaml values: diff --git a/examples/chart/teleport-cluster/values.schema.json b/examples/chart/teleport-cluster/values.schema.json index fff25ec020870..5c7aa96b8235d 100644 --- a/examples/chart/teleport-cluster/values.schema.json +++ b/examples/chart/teleport-cluster/values.schema.json @@ -614,6 +614,11 @@ "type": "string", "default": "public.ecr.aws/gravitational/teleport-ent" }, + "imagePullSecrets": { + "$id": "#/properties/imagePullSecrets", + "type": "array", + "default": [] + }, "logLevel": { "$id": "#/properties/logLevel", "type": "string", diff --git a/examples/chart/teleport-cluster/values.yaml b/examples/chart/teleport-cluster/values.yaml index e238c7ee4b42c..ae4613ecc503e 100644 --- a/examples/chart/teleport-cluster/values.yaml +++ b/examples/chart/teleport-cluster/values.yaml @@ -434,6 +434,8 @@ tls: image: public.ecr.aws/gravitational/teleport # Enterprise version of the image enterpriseImage: public.ecr.aws/gravitational/teleport-ent +# Optional array of imagePullSecrets, to use when pulling from a private registry +imagePullSecrets: [] # Teleport logging configuration log: # Log level for the Teleport process. diff --git a/examples/chart/teleport-kube-agent/templates/delete_hook.yaml b/examples/chart/teleport-kube-agent/templates/delete_hook.yaml index fe0d78d990893..024b15a2b4871 100644 --- a/examples/chart/teleport-kube-agent/templates/delete_hook.yaml +++ b/examples/chart/teleport-kube-agent/templates/delete_hook.yaml @@ -6,7 +6,7 @@ metadata: annotations: "helm.sh/hook": post-delete "helm.sh/hook-weight": "-4" - "helm.sh/hook-delete-policy": hook-succeeded + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role @@ -16,7 +16,7 @@ metadata: annotations: "helm.sh/hook": post-delete "helm.sh/hook-weight": "-3" - "helm.sh/hook-delete-policy": hook-succeeded + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded rules: - apiGroups: [""] resources: ["secrets",] @@ -30,7 +30,7 @@ metadata: annotations: "helm.sh/hook": post-delete "helm.sh/hook-weight": "-2" - "helm.sh/hook-delete-policy": hook-succeeded + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded roleRef: apiGroup: rbac.authorization.k8s.io kind: Role @@ -48,7 +48,7 @@ metadata: annotations: "helm.sh/hook": post-delete "helm.sh/hook-weight": "-1" - "helm.sh/hook-delete-policy": hook-succeeded + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded spec: template: metadata: @@ -81,4 +81,4 @@ spec: imagePullPolicy: {{ toYaml .Values.imagePullPolicy }} {{- end }} command: ["teleport"] - args: ["kube-state", "delete"] \ No newline at end of file + args: ["kube-state", "delete"] diff --git a/examples/chart/teleport-kube-agent/templates/hook.yaml b/examples/chart/teleport-kube-agent/templates/hook.yaml index 5b7bd719c9baa..67ff9fe991618 100644 --- a/examples/chart/teleport-kube-agent/templates/hook.yaml +++ b/examples/chart/teleport-kube-agent/templates/hook.yaml @@ -8,7 +8,7 @@ metadata: annotations: "helm.sh/hook": post-upgrade "helm.sh/hook-weight": "-4" - "helm.sh/hook-delete-policy": hook-succeeded + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role @@ -18,7 +18,7 @@ metadata: annotations: "helm.sh/hook": post-upgrade "helm.sh/hook-weight": "-3" - "helm.sh/hook-delete-policy": hook-succeeded + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded rules: - apiGroups: ["apps"] resources: ["statefulsets"] @@ -40,7 +40,7 @@ metadata: annotations: "helm.sh/hook": post-upgrade "helm.sh/hook-weight": "-2" - "helm.sh/hook-delete-policy": hook-succeeded + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded roleRef: apiGroup: rbac.authorization.k8s.io kind: Role @@ -58,7 +58,7 @@ metadata: annotations: "helm.sh/hook": post-upgrade "helm.sh/hook-weight": "-1" - "helm.sh/hook-delete-policy": hook-succeeded + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded spec: template: metadata: @@ -87,4 +87,4 @@ spec: # delete deployment kubectl delete deployment/{{ .Release.Name }} EOF -{{- end}} \ No newline at end of file +{{- end}} diff --git a/gen/proto/go/prehog/v1alpha/connect.pb.go b/gen/proto/go/prehog/v1alpha/connect.pb.go index 48518a2b278e6..37f4c943f5384 100644 --- a/gen/proto/go/prehog/v1alpha/connect.pb.go +++ b/gen/proto/go/prehog/v1alpha/connect.pb.go @@ -144,6 +144,8 @@ type ConnectProtocolUseEvent struct { UserName string `protobuf:"bytes,2,opt,name=user_name,json=userName,proto3" json:"user_name,omitempty"` // one of ssh/db/kube Protocol string `protobuf:"bytes,3,opt,name=protocol,proto3" json:"protocol,omitempty"` + // one of resource_table/search_bar/connection_list/reopened_session (optional) + Origin string `protobuf:"bytes,4,opt,name=origin,proto3" json:"origin,omitempty"` } func (x *ConnectProtocolUseEvent) Reset() { @@ -199,6 +201,13 @@ func (x *ConnectProtocolUseEvent) GetProtocol() string { return "" } +func (x *ConnectProtocolUseEvent) GetOrigin() string { + if x != nil { + return x.Origin + } + return "" +} + type ConnectAccessRequestCreateEvent struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -721,111 +730,113 @@ var file_prehog_v1alpha_connect_proto_rawDesc = []byte{ 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x70, 0x70, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x70, 0x70, - 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x75, 0x0a, 0x17, 0x43, 0x6f, 0x6e, 0x6e, 0x65, - 0x63, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x55, 0x73, 0x65, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, - 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x4e, 0x61, - 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x22, 0x75, + 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x8d, 0x01, 0x0a, 0x17, 0x43, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x55, 0x73, 0x65, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6c, 0x75, 0x73, 0x74, + 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, + 0x16, 0x0a, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x22, 0x75, 0x0a, 0x1f, 0x43, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, + 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, + 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, + 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x22, 0x61, 0x0a, 0x1f, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x76, 0x65, 0x6e, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, - 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x22, 0x61, 0x0a, 0x1f, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, - 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x65, 0x76, - 0x69, 0x65, 0x77, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x75, 0x73, + 0x65, 0x22, 0x65, 0x0a, 0x23, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x41, 0x63, 0x63, 0x65, + 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x41, 0x73, 0x73, 0x75, 0x6d, 0x65, 0x52, + 0x6f, 0x6c, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x75, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x65, 0x0a, 0x23, 0x43, 0x6f, 0x6e, 0x6e, - 0x65, 0x63, 0x74, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x41, 0x73, 0x73, 0x75, 0x6d, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, - 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, - 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x22, - 0x7a, 0x0a, 0x1b, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x54, 0x72, - 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x52, 0x75, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x21, - 0x0a, 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, - 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, - 0x0a, 0x09, 0x69, 0x73, 0x5f, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x08, 0x69, 0x73, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x3a, 0x0a, 0x1d, 0x43, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x55, 0x73, 0x65, 0x72, 0x4a, 0x6f, 0x62, 0x52, 0x6f, 0x6c, - 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, - 0x6a, 0x6f, 0x62, 0x5f, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x6a, 0x6f, 0x62, 0x52, 0x6f, 0x6c, 0x65, 0x22, 0x9d, 0x06, 0x0a, 0x19, 0x53, 0x75, 0x62, 0x6d, - 0x69, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x63, - 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x69, 0x73, 0x74, - 0x69, 0x6e, 0x63, 0x74, 0x49, 0x64, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x12, 0x4f, 0x0a, 0x0d, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6c, 0x6f, 0x67, 0x69, - 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, + 0x75, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x7a, 0x0a, 0x1b, 0x43, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x52, + 0x75, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, + 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, + 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, + 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x73, 0x5f, 0x75, 0x70, + 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x73, 0x55, 0x70, + 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x3a, 0x0a, 0x1d, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x55, + 0x73, 0x65, 0x72, 0x4a, 0x6f, 0x62, 0x52, 0x6f, 0x6c, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6a, 0x6f, 0x62, 0x5f, 0x72, 0x6f, 0x6c, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6a, 0x6f, 0x62, 0x52, 0x6f, 0x6c, 0x65, + 0x22, 0x9d, 0x06, 0x0a, 0x19, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, + 0x0a, 0x0b, 0x64, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x63, 0x74, 0x49, 0x64, 0x12, + 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, + 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x4f, 0x0a, 0x0d, 0x63, 0x6c, 0x75, + 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x28, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, + 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0c, 0x63, 0x6c, + 0x75, 0x73, 0x74, 0x65, 0x72, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x4c, 0x0a, 0x0c, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x75, 0x73, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x27, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, + 0x6c, 0x55, 0x73, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x55, 0x73, 0x65, 0x12, 0x65, 0x0a, 0x15, 0x61, 0x63, 0x63, 0x65, + 0x73, 0x73, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, - 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x48, 0x00, 0x52, 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4c, 0x6f, 0x67, 0x69, - 0x6e, 0x12, 0x4c, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x75, 0x73, - 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, - 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, - 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x55, 0x73, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x48, 0x00, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x55, 0x73, 0x65, 0x12, + 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x13, 0x61, 0x63, 0x63, 0x65, + 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x65, 0x0a, 0x15, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, + 0x74, 0x5f, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, + 0x75, 0x65, 0x73, 0x74, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x13, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x65, 0x0a, 0x15, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, - 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, - 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x41, 0x63, - 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x65, 0x76, 0x69, 0x65, - 0x77, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x13, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x12, 0x72, 0x0a, - 0x1a, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, - 0x61, 0x73, 0x73, 0x75, 0x6d, 0x65, 0x5f, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x33, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x41, 0x73, 0x73, 0x75, 0x6d, 0x65, 0x52, 0x6f, 0x6c, - 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x17, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x41, 0x73, 0x73, 0x75, 0x6d, 0x65, 0x52, 0x6f, 0x6c, - 0x65, 0x12, 0x59, 0x0a, 0x11, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, - 0x65, 0x72, 0x5f, 0x72, 0x75, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x70, - 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x43, 0x6f, - 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, - 0x72, 0x52, 0x75, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0f, 0x66, 0x69, 0x6c, - 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x52, 0x75, 0x6e, 0x12, 0x60, 0x0a, 0x14, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6a, 0x6f, 0x62, 0x5f, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x75, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x70, 0x72, 0x65, + 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x12, 0x72, 0x0a, 0x1a, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x61, 0x73, 0x73, 0x75, 0x6d, 0x65, 0x5f, + 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, - 0x65, 0x63, 0x74, 0x55, 0x73, 0x65, 0x72, 0x4a, 0x6f, 0x62, 0x52, 0x6f, 0x6c, 0x65, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x11, 0x75, 0x73, 0x65, - 0x72, 0x4a, 0x6f, 0x62, 0x52, 0x6f, 0x6c, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x42, 0x07, - 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x1c, 0x0a, 0x1a, 0x53, 0x75, 0x62, 0x6d, 0x69, - 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x88, 0x01, 0x0a, 0x17, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, - 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x12, 0x6d, 0x0a, 0x12, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, - 0x63, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x29, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, - 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x43, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, - 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x42, 0x3f, 0x5a, 0x3d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, - 0x72, 0x61, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x6c, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, - 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x63, 0x74, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x41, 0x73, 0x73, 0x75, 0x6d, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, + 0x00, 0x52, 0x17, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x41, 0x73, 0x73, 0x75, 0x6d, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x59, 0x0a, 0x11, 0x66, 0x69, + 0x6c, 0x65, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x5f, 0x72, 0x75, 0x6e, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x46, 0x69, + 0x6c, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x52, 0x75, 0x6e, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0f, 0x66, 0x69, 0x6c, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, + 0x65, 0x72, 0x52, 0x75, 0x6e, 0x12, 0x60, 0x0a, 0x14, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6a, 0x6f, + 0x62, 0x5f, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x55, 0x73, 0x65, 0x72, + 0x4a, 0x6f, 0x62, 0x52, 0x6f, 0x6c, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x48, 0x00, 0x52, 0x11, 0x75, 0x73, 0x65, 0x72, 0x4a, 0x6f, 0x62, 0x52, 0x6f, 0x6c, + 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, + 0x22, 0x1c, 0x0a, 0x1a, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x88, + 0x01, 0x0a, 0x17, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x6d, 0x0a, 0x12, 0x53, 0x75, + 0x62, 0x6d, 0x69, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x12, 0x29, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x70, 0x72, + 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x53, 0x75, 0x62, + 0x6d, 0x69, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x3f, 0x5a, 0x3d, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x76, 0x69, 0x74, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x67, + 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x65, 0x68, + 0x6f, 0x67, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, } var ( diff --git a/gen/proto/go/prehog/v1alpha/teleport.pb.go b/gen/proto/go/prehog/v1alpha/teleport.pb.go index 8ba810fc60ea8..6f77c16fc470f 100644 --- a/gen/proto/go/prehog/v1alpha/teleport.pb.go +++ b/gen/proto/go/prehog/v1alpha/teleport.pb.go @@ -102,24 +102,43 @@ func (ResourceKind) EnumDescriptor() ([]byte, []int) { type DiscoverResource int32 const ( - DiscoverResource_DISCOVER_RESOURCE_UNSPECIFIED DiscoverResource = 0 - DiscoverResource_DISCOVER_RESOURCE_SERVER DiscoverResource = 1 - DiscoverResource_DISCOVER_RESOURCE_KUBERNETES DiscoverResource = 2 - DiscoverResource_DISCOVER_RESOURCE_DATABASE_POSTGRES_SELF_HOSTED DiscoverResource = 3 - DiscoverResource_DISCOVER_RESOURCE_DATABASE_MYSQL_SELF_HOSTED DiscoverResource = 4 - DiscoverResource_DISCOVER_RESOURCE_DATABASE_MONGODB_SELF_HOSTED DiscoverResource = 5 - DiscoverResource_DISCOVER_RESOURCE_DATABASE_POSTGRES_RDS DiscoverResource = 6 - DiscoverResource_DISCOVER_RESOURCE_DATABASE_MYSQL_RDS DiscoverResource = 7 - DiscoverResource_DISCOVER_RESOURCE_APPLICATION_HTTP DiscoverResource = 8 - DiscoverResource_DISCOVER_RESOURCE_APPLICATION_TCP DiscoverResource = 9 - DiscoverResource_DISCOVER_RESOURCE_WINDOWS_DESKTOP DiscoverResource = 10 - DiscoverResource_DISCOVER_RESOURCE_DATABASE_SQLSERVER_RDS DiscoverResource = 11 - DiscoverResource_DISCOVER_RESOURCE_DATABASE_POSTGRES_REDSHIFT DiscoverResource = 12 - DiscoverResource_DISCOVER_RESOURCE_DATABASE_SQLSERVER_SELF_HOSTED DiscoverResource = 13 - DiscoverResource_DISCOVER_RESOURCE_DATABASE_REDIS_SELF_HOSTED DiscoverResource = 14 - DiscoverResource_DISCOVER_RESOURCE_DATABASE_POSTGRES_GCP DiscoverResource = 15 - DiscoverResource_DISCOVER_RESOURCE_DATABASE_MYSQL_GCP DiscoverResource = 16 - DiscoverResource_DISCOVER_RESOURCE_DATABASE_SQLSERVER_GCP DiscoverResource = 17 + DiscoverResource_DISCOVER_RESOURCE_UNSPECIFIED DiscoverResource = 0 + DiscoverResource_DISCOVER_RESOURCE_SERVER DiscoverResource = 1 + DiscoverResource_DISCOVER_RESOURCE_KUBERNETES DiscoverResource = 2 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_POSTGRES_SELF_HOSTED DiscoverResource = 3 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_MYSQL_SELF_HOSTED DiscoverResource = 4 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_MONGODB_SELF_HOSTED DiscoverResource = 5 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_POSTGRES_RDS DiscoverResource = 6 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_MYSQL_RDS DiscoverResource = 7 + DiscoverResource_DISCOVER_RESOURCE_APPLICATION_HTTP DiscoverResource = 8 + DiscoverResource_DISCOVER_RESOURCE_APPLICATION_TCP DiscoverResource = 9 + DiscoverResource_DISCOVER_RESOURCE_WINDOWS_DESKTOP DiscoverResource = 10 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_SQLSERVER_RDS DiscoverResource = 11 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_POSTGRES_REDSHIFT DiscoverResource = 12 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_SQLSERVER_SELF_HOSTED DiscoverResource = 13 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_REDIS_SELF_HOSTED DiscoverResource = 14 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_POSTGRES_GCP DiscoverResource = 15 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_MYSQL_GCP DiscoverResource = 16 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_SQLSERVER_GCP DiscoverResource = 17 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_POSTGRES_REDSHIFT_SERVERLESS DiscoverResource = 18 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_POSTGRES_AZURE DiscoverResource = 19 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_DYNAMODB DiscoverResource = 20 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_CASSANDRA_KEYSPACES DiscoverResource = 21 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_CASSANDRA_SELF_HOSTED DiscoverResource = 22 // Cassandra & ScyllaDb + DiscoverResource_DISCOVER_RESOURCE_DATABASE_ELASTICSEARCH_SELF_HOSTED DiscoverResource = 23 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_REDIS_ELASTICACHE DiscoverResource = 24 // Elasticache & MemoryDb + DiscoverResource_DISCOVER_RESOURCE_DATABASE_REDIS_MEMORYDB DiscoverResource = 25 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_REDIS_AZURE_CACHE DiscoverResource = 26 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_REDIS_CLUSTER_SELF_HOSTED DiscoverResource = 27 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_MYSQL_AZURE DiscoverResource = 28 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_SQLSERVER_AZURE DiscoverResource = 29 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_SQLSERVER_MICROSOFT DiscoverResource = 30 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_COCKROACHDB_SELF_HOSTED DiscoverResource = 31 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_MONGODB_ATLAS DiscoverResource = 32 + DiscoverResource_DISCOVER_RESOURCE_DATABASE_SNOWFLAKE DiscoverResource = 33 + DiscoverResource_DISCOVER_RESOURCE_DOC_DATABASE_RDS_PROXY DiscoverResource = 34 + DiscoverResource_DISCOVER_RESOURCE_DOC_DATABASE_HIGH_AVAILABILITY DiscoverResource = 35 + DiscoverResource_DISCOVER_RESOURCE_DOC_DATABASE_DYNAMIC_REGISTRATION DiscoverResource = 36 ) // Enum value maps for DiscoverResource. @@ -143,26 +162,64 @@ var ( 15: "DISCOVER_RESOURCE_DATABASE_POSTGRES_GCP", 16: "DISCOVER_RESOURCE_DATABASE_MYSQL_GCP", 17: "DISCOVER_RESOURCE_DATABASE_SQLSERVER_GCP", + 18: "DISCOVER_RESOURCE_DATABASE_POSTGRES_REDSHIFT_SERVERLESS", + 19: "DISCOVER_RESOURCE_DATABASE_POSTGRES_AZURE", + 20: "DISCOVER_RESOURCE_DATABASE_DYNAMODB", + 21: "DISCOVER_RESOURCE_DATABASE_CASSANDRA_KEYSPACES", + 22: "DISCOVER_RESOURCE_DATABASE_CASSANDRA_SELF_HOSTED", + 23: "DISCOVER_RESOURCE_DATABASE_ELASTICSEARCH_SELF_HOSTED", + 24: "DISCOVER_RESOURCE_DATABASE_REDIS_ELASTICACHE", + 25: "DISCOVER_RESOURCE_DATABASE_REDIS_MEMORYDB", + 26: "DISCOVER_RESOURCE_DATABASE_REDIS_AZURE_CACHE", + 27: "DISCOVER_RESOURCE_DATABASE_REDIS_CLUSTER_SELF_HOSTED", + 28: "DISCOVER_RESOURCE_DATABASE_MYSQL_AZURE", + 29: "DISCOVER_RESOURCE_DATABASE_SQLSERVER_AZURE", + 30: "DISCOVER_RESOURCE_DATABASE_SQLSERVER_MICROSOFT", + 31: "DISCOVER_RESOURCE_DATABASE_COCKROACHDB_SELF_HOSTED", + 32: "DISCOVER_RESOURCE_DATABASE_MONGODB_ATLAS", + 33: "DISCOVER_RESOURCE_DATABASE_SNOWFLAKE", + 34: "DISCOVER_RESOURCE_DOC_DATABASE_RDS_PROXY", + 35: "DISCOVER_RESOURCE_DOC_DATABASE_HIGH_AVAILABILITY", + 36: "DISCOVER_RESOURCE_DOC_DATABASE_DYNAMIC_REGISTRATION", } DiscoverResource_value = map[string]int32{ - "DISCOVER_RESOURCE_UNSPECIFIED": 0, - "DISCOVER_RESOURCE_SERVER": 1, - "DISCOVER_RESOURCE_KUBERNETES": 2, - "DISCOVER_RESOURCE_DATABASE_POSTGRES_SELF_HOSTED": 3, - "DISCOVER_RESOURCE_DATABASE_MYSQL_SELF_HOSTED": 4, - "DISCOVER_RESOURCE_DATABASE_MONGODB_SELF_HOSTED": 5, - "DISCOVER_RESOURCE_DATABASE_POSTGRES_RDS": 6, - "DISCOVER_RESOURCE_DATABASE_MYSQL_RDS": 7, - "DISCOVER_RESOURCE_APPLICATION_HTTP": 8, - "DISCOVER_RESOURCE_APPLICATION_TCP": 9, - "DISCOVER_RESOURCE_WINDOWS_DESKTOP": 10, - "DISCOVER_RESOURCE_DATABASE_SQLSERVER_RDS": 11, - "DISCOVER_RESOURCE_DATABASE_POSTGRES_REDSHIFT": 12, - "DISCOVER_RESOURCE_DATABASE_SQLSERVER_SELF_HOSTED": 13, - "DISCOVER_RESOURCE_DATABASE_REDIS_SELF_HOSTED": 14, - "DISCOVER_RESOURCE_DATABASE_POSTGRES_GCP": 15, - "DISCOVER_RESOURCE_DATABASE_MYSQL_GCP": 16, - "DISCOVER_RESOURCE_DATABASE_SQLSERVER_GCP": 17, + "DISCOVER_RESOURCE_UNSPECIFIED": 0, + "DISCOVER_RESOURCE_SERVER": 1, + "DISCOVER_RESOURCE_KUBERNETES": 2, + "DISCOVER_RESOURCE_DATABASE_POSTGRES_SELF_HOSTED": 3, + "DISCOVER_RESOURCE_DATABASE_MYSQL_SELF_HOSTED": 4, + "DISCOVER_RESOURCE_DATABASE_MONGODB_SELF_HOSTED": 5, + "DISCOVER_RESOURCE_DATABASE_POSTGRES_RDS": 6, + "DISCOVER_RESOURCE_DATABASE_MYSQL_RDS": 7, + "DISCOVER_RESOURCE_APPLICATION_HTTP": 8, + "DISCOVER_RESOURCE_APPLICATION_TCP": 9, + "DISCOVER_RESOURCE_WINDOWS_DESKTOP": 10, + "DISCOVER_RESOURCE_DATABASE_SQLSERVER_RDS": 11, + "DISCOVER_RESOURCE_DATABASE_POSTGRES_REDSHIFT": 12, + "DISCOVER_RESOURCE_DATABASE_SQLSERVER_SELF_HOSTED": 13, + "DISCOVER_RESOURCE_DATABASE_REDIS_SELF_HOSTED": 14, + "DISCOVER_RESOURCE_DATABASE_POSTGRES_GCP": 15, + "DISCOVER_RESOURCE_DATABASE_MYSQL_GCP": 16, + "DISCOVER_RESOURCE_DATABASE_SQLSERVER_GCP": 17, + "DISCOVER_RESOURCE_DATABASE_POSTGRES_REDSHIFT_SERVERLESS": 18, + "DISCOVER_RESOURCE_DATABASE_POSTGRES_AZURE": 19, + "DISCOVER_RESOURCE_DATABASE_DYNAMODB": 20, + "DISCOVER_RESOURCE_DATABASE_CASSANDRA_KEYSPACES": 21, + "DISCOVER_RESOURCE_DATABASE_CASSANDRA_SELF_HOSTED": 22, + "DISCOVER_RESOURCE_DATABASE_ELASTICSEARCH_SELF_HOSTED": 23, + "DISCOVER_RESOURCE_DATABASE_REDIS_ELASTICACHE": 24, + "DISCOVER_RESOURCE_DATABASE_REDIS_MEMORYDB": 25, + "DISCOVER_RESOURCE_DATABASE_REDIS_AZURE_CACHE": 26, + "DISCOVER_RESOURCE_DATABASE_REDIS_CLUSTER_SELF_HOSTED": 27, + "DISCOVER_RESOURCE_DATABASE_MYSQL_AZURE": 28, + "DISCOVER_RESOURCE_DATABASE_SQLSERVER_AZURE": 29, + "DISCOVER_RESOURCE_DATABASE_SQLSERVER_MICROSOFT": 30, + "DISCOVER_RESOURCE_DATABASE_COCKROACHDB_SELF_HOSTED": 31, + "DISCOVER_RESOURCE_DATABASE_MONGODB_ATLAS": 32, + "DISCOVER_RESOURCE_DATABASE_SNOWFLAKE": 33, + "DISCOVER_RESOURCE_DOC_DATABASE_RDS_PROXY": 34, + "DISCOVER_RESOURCE_DOC_DATABASE_HIGH_AVAILABILITY": 35, + "DISCOVER_RESOURCE_DOC_DATABASE_DYNAMIC_REGISTRATION": 36, } ) @@ -1114,6 +1171,8 @@ type DiscoverMetadata struct { Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // anonymized UserName string `protobuf:"bytes,2,opt,name=user_name,json=userName,proto3" json:"user_name,omitempty"` + // SSO indicates whether the user is from an SSO provider. + Sso bool `protobuf:"varint,3,opt,name=sso,proto3" json:"sso,omitempty"` } func (x *DiscoverMetadata) Reset() { @@ -1162,6 +1221,13 @@ func (x *DiscoverMetadata) GetUserName() string { return "" } +func (x *DiscoverMetadata) GetSso() bool { + if x != nil { + return x.Sso + } + return false +} + // DiscoverResourceMetadata contains common metadata identifying resource type being added. type DiscoverResourceMetadata struct { state protoimpl.MessageState @@ -3409,108 +3475,64 @@ var file_prehog_v1alpha_teleport_proto_rawDesc = []byte{ 0x1e, 0x55, 0x49, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x73, 0x50, 0x72, 0x69, 0x6e, 0x74, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x3f, 0x0a, 0x10, + 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x51, 0x0a, 0x10, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x58, 0x0a, - 0x18, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x3c, 0x0a, 0x08, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x70, 0x72, - 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, - 0x63, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x08, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0x62, 0x0a, 0x12, 0x44, 0x69, 0x73, 0x63, 0x6f, - 0x76, 0x65, 0x72, 0x53, 0x74, 0x65, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x36, 0x0a, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1e, 0x2e, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, + 0x03, 0x73, 0x73, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x03, 0x73, 0x73, 0x6f, 0x22, + 0x58, 0x0a, 0x18, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x3c, 0x0a, 0x08, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, - 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x92, 0x01, 0x0a, 0x16, - 0x55, 0x49, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, - 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, - 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, - 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x12, 0x3a, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, - 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x53, 0x74, - 0x65, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x22, 0xe2, 0x01, 0x0a, 0x20, 0x55, 0x49, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, - 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, - 0x72, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 0x12, 0x44, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, - 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, - 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x3a, 0x0a, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x72, 0x65, 0x68, - 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, - 0x76, 0x65, 0x72, 0x53, 0x74, 0x65, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xde, 0x01, 0x0a, 0x1c, 0x55, 0x49, 0x44, 0x69, 0x73, 0x63, - 0x6f, 0x76, 0x65, 0x72, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, - 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, - 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x12, 0x44, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x3a, 0x0a, 0x06, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x72, 0x65, - 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, - 0x6f, 0x76, 0x65, 0x72, 0x53, 0x74, 0x65, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xe1, 0x01, 0x0a, 0x1f, 0x55, 0x49, 0x44, 0x69, 0x73, - 0x63, 0x6f, 0x76, 0x65, 0x72, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x67, - 0x69, 0x73, 0x74, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x08, 0x6d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, - 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, - 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, - 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x44, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x70, 0x72, 0x65, + 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, + 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0x62, 0x0a, 0x12, 0x44, 0x69, 0x73, + 0x63, 0x6f, 0x76, 0x65, 0x72, 0x53, 0x74, 0x65, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, + 0x36, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x1e, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x92, 0x01, + 0x0a, 0x16, 0x55, 0x49, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x72, + 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, - 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x3a, - 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, - 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, - 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x53, 0x74, 0x65, 0x70, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xe6, 0x01, 0x0a, 0x24, 0x55, - 0x49, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, - 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x4d, 0x54, 0x4c, 0x53, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, - 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x4d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0x12, 0x44, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x3a, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, + 0x6f, 0x76, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x3a, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, + 0x53, 0x74, 0x65, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x22, 0xe2, 0x01, 0x0a, 0x20, 0x55, 0x49, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, + 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x65, 0x68, + 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, + 0x76, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x44, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, - 0x72, 0x53, 0x74, 0x65, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x22, 0xf3, 0x01, 0x0a, 0x31, 0x55, 0x49, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, - 0x65, 0x72, 0x44, 0x65, 0x73, 0x6b, 0x74, 0x6f, 0x70, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x44, - 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x54, 0x6f, 0x6f, 0x6c, 0x73, 0x49, 0x6e, 0x73, - 0x74, 0x61, 0x6c, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x08, 0x6d, 0x65, 0x74, - 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x72, + 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x3a, 0x0a, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, - 0x63, 0x6f, 0x76, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x44, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x70, 0x72, 0x65, 0x68, - 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, - 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x3a, 0x0a, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, - 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, - 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x53, 0x74, 0x65, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xf0, 0x01, 0x0a, 0x2e, 0x55, 0x49, - 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x44, 0x65, 0x73, 0x6b, 0x74, 0x6f, 0x70, 0x41, - 0x63, 0x74, 0x69, 0x76, 0x65, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x08, + 0x63, 0x6f, 0x76, 0x65, 0x72, 0x53, 0x74, 0x65, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xde, 0x01, 0x0a, 0x1c, 0x55, 0x49, 0x44, 0x69, + 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x65, + 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, + 0x6f, 0x76, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x44, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, + 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, + 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x3a, 0x0a, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, + 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, + 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x53, 0x74, 0x65, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xe1, 0x01, 0x0a, 0x1f, 0x55, 0x49, 0x44, + 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x52, + 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, @@ -3522,27 +3544,40 @@ var file_prehog_v1alpha_teleport_proto_rawDesc = []byte{ 0x12, 0x3a, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x53, 0x74, 0x65, 0x70, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x91, 0x02, 0x0a, - 0x26, 0x55, 0x49, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x41, 0x75, 0x74, 0x6f, 0x44, - 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x65, 0x68, - 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, - 0x76, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, - 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x44, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, + 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xe6, 0x01, 0x0a, + 0x24, 0x55, 0x49, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x44, 0x61, 0x74, 0x61, 0x62, + 0x61, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x4d, 0x54, 0x4c, 0x53, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, - 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x3a, 0x0a, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x72, + 0x72, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x12, 0x44, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, + 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x3a, 0x0a, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x72, 0x65, 0x68, + 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, + 0x76, 0x65, 0x72, 0x53, 0x74, 0x65, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xf3, 0x01, 0x0a, 0x31, 0x55, 0x49, 0x44, 0x69, 0x73, 0x63, + 0x6f, 0x76, 0x65, 0x72, 0x44, 0x65, 0x73, 0x6b, 0x74, 0x6f, 0x70, 0x41, 0x63, 0x74, 0x69, 0x76, + 0x65, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x54, 0x6f, 0x6f, 0x6c, 0x73, 0x49, + 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x08, 0x6d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, + 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, + 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, + 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x44, 0x0a, 0x08, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, - 0x63, 0x6f, 0x76, 0x65, 0x72, 0x53, 0x74, 0x65, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, - 0x22, 0xeb, 0x01, 0x0a, 0x29, 0x55, 0x49, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x44, - 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, - 0x49, 0x41, 0x4d, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x3c, + 0x63, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, + 0x3a, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x22, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x53, 0x74, 0x65, 0x70, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xf0, 0x01, 0x0a, 0x2e, + 0x55, 0x49, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x44, 0x65, 0x73, 0x6b, 0x74, 0x6f, + 0x70, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, @@ -3554,24 +3589,42 @@ var file_prehog_v1alpha_teleport_proto_rawDesc = []byte{ 0x63, 0x65, 0x12, 0x3a, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x53, 0x74, 0x65, 0x70, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xe4, - 0x01, 0x0a, 0x22, 0x55, 0x49, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x50, 0x72, 0x69, - 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, - 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, - 0x72, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 0x12, 0x44, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, - 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, - 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x3a, 0x0a, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x72, 0x65, 0x68, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x91, + 0x02, 0x0a, 0x26, 0x55, 0x49, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x41, 0x75, 0x74, + 0x6f, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x08, 0x6d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x72, + 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, + 0x63, 0x6f, 0x76, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x44, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, - 0x76, 0x65, 0x72, 0x53, 0x74, 0x65, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xdf, 0x01, 0x0a, 0x1d, 0x55, 0x49, 0x44, 0x69, 0x73, 0x63, - 0x6f, 0x76, 0x65, 0x72, 0x54, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, + 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x3a, 0x0a, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, + 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, + 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x53, 0x74, 0x65, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x22, 0xeb, 0x01, 0x0a, 0x29, 0x55, 0x49, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, + 0x72, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, + 0x72, 0x65, 0x49, 0x41, 0x4d, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x12, 0x3c, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x44, + 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x28, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x12, 0x3a, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x53, 0x74, + 0x65, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x22, 0xe4, 0x01, 0x0a, 0x22, 0x55, 0x49, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x50, + 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, + 0x72, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, @@ -3583,454 +3636,528 @@ var file_prehog_v1alpha_teleport_proto_rawDesc = []byte{ 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x53, 0x74, 0x65, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xda, 0x01, 0x0a, 0x18, 0x55, 0x49, 0x44, 0x69, - 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, - 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x12, 0x44, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, - 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x3a, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xdf, 0x01, 0x0a, 0x1d, 0x55, 0x49, 0x44, 0x69, + 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x54, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x08, 0x6d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x72, + 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, + 0x63, 0x6f, 0x76, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x44, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x70, 0x72, 0x65, 0x68, + 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, + 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x3a, 0x0a, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, + 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, + 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x53, 0x74, 0x65, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xda, 0x01, 0x0a, 0x18, 0x55, 0x49, + 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, + 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, - 0x65, 0x72, 0x53, 0x74, 0x65, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x22, 0x4b, 0x0a, 0x0f, 0x52, 0x6f, 0x6c, 0x65, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, - 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x6f, 0x6c, 0x65, 0x4e, 0x61, 0x6d, - 0x65, 0x22, 0x38, 0x0a, 0x19, 0x55, 0x49, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x65, 0x77, - 0x52, 0x6f, 0x6c, 0x65, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1b, + 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x12, 0x44, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x3a, 0x0a, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x72, 0x65, + 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x69, 0x73, 0x63, + 0x6f, 0x76, 0x65, 0x72, 0x53, 0x74, 0x65, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x4b, 0x0a, 0x0f, 0x52, 0x6f, 0x6c, 0x65, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, + 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x6f, 0x6c, 0x65, 0x4e, + 0x61, 0x6d, 0x65, 0x22, 0x38, 0x0a, 0x19, 0x55, 0x49, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, + 0x65, 0x77, 0x52, 0x6f, 0x6c, 0x65, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x3c, 0x0a, + 0x1d, 0x55, 0x49, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x65, 0x77, 0x52, 0x6f, 0x6c, 0x65, + 0x53, 0x61, 0x76, 0x65, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x3c, 0x0a, 0x1d, 0x55, - 0x49, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x65, 0x77, 0x52, 0x6f, 0x6c, 0x65, 0x53, 0x61, - 0x76, 0x65, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x75, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x3e, 0x0a, 0x1f, 0x55, 0x49, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x65, 0x77, 0x52, 0x6f, 0x6c, 0x65, 0x43, 0x61, 0x6e, 0x63, - 0x65, 0x6c, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x75, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x49, 0x0a, 0x2a, 0x55, 0x49, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x65, 0x77, 0x52, 0x6f, 0x6c, 0x65, 0x56, 0x69, 0x65, 0x77, - 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6c, 0x69, - 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, - 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x2f, 0x0a, 0x10, 0x4b, 0x75, 0x62, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, - 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, - 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x40, 0x0a, 0x09, 0x53, 0x46, 0x54, 0x50, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, - 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x9e, 0x03, 0x0a, 0x12, 0x41, 0x67, 0x65, 0x6e, - 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x18, - 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x68, 0x6f, 0x73, 0x74, - 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x68, 0x6f, 0x73, 0x74, 0x49, - 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x0e, 0x0a, - 0x02, 0x6f, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x6f, 0x73, 0x12, 0x1d, 0x0a, - 0x0a, 0x6f, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x09, 0x6f, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2b, 0x0a, 0x11, - 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, - 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x68, 0x6f, 0x73, 0x74, 0x41, 0x72, 0x63, - 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x67, 0x6c, 0x69, - 0x62, 0x63, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0c, 0x67, 0x6c, 0x69, 0x62, 0x63, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x27, - 0x0a, 0x0f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, - 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, - 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x63, 0x6f, 0x6e, 0x74, 0x61, - 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x75, 0x6e, - 0x74, 0x69, 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x16, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, - 0x72, 0x5f, 0x6f, 0x72, 0x63, 0x68, 0x65, 0x73, 0x74, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x0a, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x4f, - 0x72, 0x63, 0x68, 0x65, 0x73, 0x74, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x2b, 0x0a, 0x11, 0x63, - 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, - 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x45, 0x6e, 0x76, - 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0xc3, 0x21, 0x0a, 0x12, 0x53, 0x75, 0x62, - 0x6d, 0x69, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, - 0x6d, 0x65, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x3f, 0x0a, 0x0a, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x48, 0x00, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x3f, 0x0a, - 0x0a, 0x73, 0x73, 0x6f, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x2e, 0x53, 0x53, 0x4f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x48, 0x00, 0x52, 0x09, 0x73, 0x73, 0x6f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x4e, - 0x0a, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, - 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0e, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x48, - 0x0a, 0x0d, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, - 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x74, - 0x61, 0x72, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0c, 0x73, 0x65, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x4c, 0x0a, 0x0f, 0x75, 0x69, 0x5f, 0x62, - 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x5f, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x2e, 0x55, 0x49, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6c, 0x69, 0x63, 0x6b, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x75, 0x69, 0x42, 0x61, 0x6e, 0x6e, 0x65, - 0x72, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x12, 0x92, 0x01, 0x0a, 0x29, 0x75, 0x69, 0x5f, 0x6f, 0x6e, - 0x62, 0x6f, 0x61, 0x72, 0x64, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x5f, 0x67, - 0x6f, 0x5f, 0x74, 0x6f, 0x5f, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x5f, 0x63, - 0x6c, 0x69, 0x63, 0x6b, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x70, 0x72, 0x65, - 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x49, 0x4f, 0x6e, - 0x62, 0x6f, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x47, 0x6f, 0x54, - 0x6f, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x23, 0x75, 0x69, 0x4f, 0x6e, 0x62, 0x6f, 0x61, 0x72, - 0x64, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x47, 0x6f, 0x54, 0x6f, 0x44, 0x61, 0x73, - 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x12, 0x82, 0x01, 0x0a, 0x23, - 0x75, 0x69, 0x5f, 0x6f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x5f, 0x61, 0x64, 0x64, 0x5f, 0x66, - 0x69, 0x72, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x6c, - 0x69, 0x63, 0x6b, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x70, 0x72, 0x65, 0x68, - 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x49, 0x4f, 0x6e, 0x62, - 0x6f, 0x61, 0x72, 0x64, 0x41, 0x64, 0x64, 0x46, 0x69, 0x72, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, - 0x52, 0x1e, 0x75, 0x69, 0x4f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x41, 0x64, 0x64, 0x46, 0x69, - 0x72, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6c, 0x69, 0x63, 0x6b, - 0x12, 0x92, 0x01, 0x0a, 0x29, 0x75, 0x69, 0x5f, 0x6f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x5f, - 0x61, 0x64, 0x64, 0x5f, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x5f, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x18, 0x0b, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, - 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x49, 0x4f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x41, - 0x64, 0x64, 0x46, 0x69, 0x72, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4c, - 0x61, 0x74, 0x65, 0x72, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, - 0x52, 0x23, 0x75, 0x69, 0x4f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x41, 0x64, 0x64, 0x46, 0x69, - 0x72, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4c, 0x61, 0x74, 0x65, 0x72, - 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x12, 0x7b, 0x0a, 0x20, 0x75, 0x69, 0x5f, 0x6f, 0x6e, 0x62, 0x6f, - 0x61, 0x72, 0x64, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, - 0x61, 0x6c, 0x5f, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x31, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x2e, 0x55, 0x49, 0x4f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x53, 0x65, 0x74, 0x43, 0x72, 0x65, - 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x48, 0x00, 0x52, 0x1c, 0x75, 0x69, 0x4f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x53, - 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x75, 0x62, 0x6d, - 0x69, 0x74, 0x12, 0x87, 0x01, 0x0a, 0x24, 0x75, 0x69, 0x5f, 0x6f, 0x6e, 0x62, 0x6f, 0x61, 0x72, - 0x64, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x63, 0x68, 0x61, 0x6c, 0x6c, - 0x65, 0x6e, 0x67, 0x65, 0x5f, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x35, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x2e, 0x55, 0x49, 0x4f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x67, 0x69, - 0x73, 0x74, 0x65, 0x72, 0x43, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x53, 0x75, 0x62, - 0x6d, 0x69, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x20, 0x75, 0x69, 0x4f, 0x6e, - 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x43, 0x68, 0x61, - 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x12, 0x7b, 0x0a, 0x20, - 0x75, 0x69, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, - 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x65, 0x5f, 0x63, 0x6c, 0x69, 0x63, 0x6b, - 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x49, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, - 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x73, 0x43, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x65, 0x43, - 0x6c, 0x69, 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x1c, 0x75, 0x69, 0x52, - 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x73, 0x43, 0x6f, 0x6e, 0x74, - 0x69, 0x6e, 0x75, 0x65, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x12, 0x6f, 0x0a, 0x1c, 0x75, 0x69, 0x5f, - 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x73, 0x5f, 0x63, - 0x6f, 0x70, 0x79, 0x5f, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x2d, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x2e, 0x55, 0x49, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x73, - 0x43, 0x6f, 0x70, 0x79, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, - 0x52, 0x18, 0x75, 0x69, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, - 0x73, 0x43, 0x6f, 0x70, 0x79, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x12, 0x72, 0x0a, 0x1d, 0x75, 0x69, - 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x73, 0x5f, - 0x70, 0x72, 0x69, 0x6e, 0x74, 0x5f, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x18, 0x10, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x2e, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x3e, 0x0a, 0x1f, 0x55, + 0x49, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x65, 0x77, 0x52, 0x6f, 0x6c, 0x65, 0x43, 0x61, + 0x6e, 0x63, 0x65, 0x6c, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1b, + 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x49, 0x0a, 0x2a, 0x55, + 0x49, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x65, 0x77, 0x52, 0x6f, 0x6c, 0x65, 0x56, 0x69, + 0x65, 0x77, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, + 0x6c, 0x69, 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, + 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x2f, 0x0a, 0x10, 0x4b, 0x75, 0x62, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, + 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x40, 0x0a, 0x09, 0x53, 0x46, 0x54, 0x50, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, + 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x9e, 0x03, 0x0a, 0x12, 0x41, 0x67, + 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x68, 0x6f, + 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x68, 0x6f, 0x73, + 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, + 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, + 0x0e, 0x0a, 0x02, 0x6f, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x6f, 0x73, 0x12, + 0x1d, 0x0a, 0x0a, 0x6f, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2b, + 0x0a, 0x11, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, + 0x75, 0x72, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x68, 0x6f, 0x73, 0x74, 0x41, + 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x67, + 0x6c, 0x69, 0x62, 0x63, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0c, 0x67, 0x6c, 0x69, 0x62, 0x63, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x5f, 0x6d, 0x65, 0x74, 0x68, + 0x6f, 0x64, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x69, 0x6e, 0x73, 0x74, 0x61, + 0x6c, 0x6c, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x63, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, + 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x16, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, + 0x6e, 0x65, 0x72, 0x5f, 0x6f, 0x72, 0x63, 0x68, 0x65, 0x73, 0x74, 0x72, 0x61, 0x74, 0x6f, 0x72, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, + 0x72, 0x4f, 0x72, 0x63, 0x68, 0x65, 0x73, 0x74, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x2b, 0x0a, + 0x11, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, + 0x6e, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x45, + 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0xc3, 0x21, 0x0a, 0x12, 0x53, + 0x75, 0x62, 0x6d, 0x69, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, + 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x3f, + 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x48, 0x00, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, + 0x3f, 0x0a, 0x0a, 0x73, 0x73, 0x6f, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x53, 0x53, 0x4f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x09, 0x73, 0x73, 0x6f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x12, 0x4e, 0x0a, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x70, 0x72, 0x65, 0x68, + 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, + 0x52, 0x0e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x12, 0x48, 0x0a, 0x0d, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x72, + 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x53, 0x74, 0x61, 0x72, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0c, 0x73, 0x65, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x4c, 0x0a, 0x0f, 0x75, 0x69, + 0x5f, 0x62, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x5f, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x49, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6c, 0x69, + 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x75, 0x69, 0x42, 0x61, 0x6e, + 0x6e, 0x65, 0x72, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x12, 0x92, 0x01, 0x0a, 0x29, 0x75, 0x69, 0x5f, + 0x6f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, + 0x5f, 0x67, 0x6f, 0x5f, 0x74, 0x6f, 0x5f, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, + 0x5f, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x70, + 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x49, + 0x4f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x47, + 0x6f, 0x54, 0x6f, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x43, 0x6c, 0x69, 0x63, + 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x23, 0x75, 0x69, 0x4f, 0x6e, 0x62, 0x6f, + 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x47, 0x6f, 0x54, 0x6f, 0x44, + 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x12, 0x82, 0x01, + 0x0a, 0x23, 0x75, 0x69, 0x5f, 0x6f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x5f, 0x61, 0x64, 0x64, + 0x5f, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, + 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x70, 0x72, + 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x49, 0x4f, + 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x41, 0x64, 0x64, 0x46, 0x69, 0x72, 0x73, 0x74, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x48, 0x00, 0x52, 0x1e, 0x75, 0x69, 0x4f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x41, 0x64, 0x64, + 0x46, 0x69, 0x72, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6c, 0x69, + 0x63, 0x6b, 0x12, 0x92, 0x01, 0x0a, 0x29, 0x75, 0x69, 0x5f, 0x6f, 0x6e, 0x62, 0x6f, 0x61, 0x72, + 0x64, 0x5f, 0x61, 0x64, 0x64, 0x5f, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x5f, 0x63, 0x6c, 0x69, 0x63, 0x6b, + 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x49, 0x4f, 0x6e, 0x62, 0x6f, 0x61, 0x72, + 0x64, 0x41, 0x64, 0x64, 0x46, 0x69, 0x72, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x4c, 0x61, 0x74, 0x65, 0x72, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x48, 0x00, 0x52, 0x23, 0x75, 0x69, 0x4f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x41, 0x64, 0x64, + 0x46, 0x69, 0x72, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4c, 0x61, 0x74, + 0x65, 0x72, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x12, 0x7b, 0x0a, 0x20, 0x75, 0x69, 0x5f, 0x6f, 0x6e, + 0x62, 0x6f, 0x61, 0x72, 0x64, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x31, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x2e, 0x55, 0x49, 0x4f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x53, 0x65, 0x74, 0x43, + 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x1c, 0x75, 0x69, 0x4f, 0x6e, 0x62, 0x6f, 0x61, 0x72, + 0x64, 0x53, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x75, + 0x62, 0x6d, 0x69, 0x74, 0x12, 0x87, 0x01, 0x0a, 0x24, 0x75, 0x69, 0x5f, 0x6f, 0x6e, 0x62, 0x6f, + 0x61, 0x72, 0x64, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x63, 0x68, 0x61, + 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x5f, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x18, 0x0d, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x49, 0x4f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, + 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x43, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x53, + 0x75, 0x62, 0x6d, 0x69, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x20, 0x75, 0x69, + 0x4f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x43, + 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x12, 0x7b, + 0x0a, 0x20, 0x75, 0x69, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x63, 0x6f, + 0x64, 0x65, 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x65, 0x5f, 0x63, 0x6c, 0x69, + 0x63, 0x6b, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, + 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x49, 0x52, 0x65, 0x63, 0x6f, + 0x76, 0x65, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x73, 0x43, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, + 0x65, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x1c, 0x75, + 0x69, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x73, 0x43, 0x6f, + 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x65, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x12, 0x6f, 0x0a, 0x1c, 0x75, + 0x69, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x73, + 0x5f, 0x63, 0x6f, 0x70, 0x79, 0x5f, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x18, 0x0f, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x2d, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x49, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x43, 0x6f, 0x64, - 0x65, 0x73, 0x50, 0x72, 0x69, 0x6e, 0x74, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x48, 0x00, 0x52, 0x19, 0x75, 0x69, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x43, - 0x6f, 0x64, 0x65, 0x73, 0x50, 0x72, 0x69, 0x6e, 0x74, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x12, 0x63, - 0x0a, 0x19, 0x75, 0x69, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x73, 0x74, - 0x61, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x11, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x2e, 0x55, 0x49, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, - 0x72, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x16, 0x75, 0x69, 0x44, - 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x12, 0x82, 0x01, 0x0a, 0x24, 0x75, 0x69, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, - 0x76, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x6c, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x12, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x2e, 0x55, 0x49, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x20, 0x75, 0x69, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, - 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x6f, 0x0a, 0x1d, 0x75, 0x73, 0x65, 0x72, - 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x73, 0x73, - 0x75, 0x65, 0x64, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x2a, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x2e, 0x55, 0x73, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, - 0x49, 0x73, 0x73, 0x75, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x1a, 0x75, - 0x73, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x49, 0x73, - 0x73, 0x75, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x4d, 0x0a, 0x10, 0x73, 0x65, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x76, 0x32, 0x18, 0x14, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x72, - 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x56, 0x32, 0x12, 0x76, 0x0a, 0x20, 0x75, 0x69, 0x5f, 0x64, - 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x5f, 0x73, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x15, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x2e, 0x55, 0x49, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x44, 0x65, - 0x70, 0x6c, 0x6f, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x48, 0x00, 0x52, 0x1c, 0x75, 0x69, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x44, 0x65, - 0x70, 0x6c, 0x6f, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x12, 0x7f, 0x0a, 0x23, 0x75, 0x69, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, - 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, - 0x72, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x16, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, - 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, - 0x49, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, - 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, - 0x52, 0x1f, 0x75, 0x69, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x44, 0x61, 0x74, 0x61, - 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x12, 0x8f, 0x01, 0x0a, 0x29, 0x75, 0x69, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, - 0x72, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x75, 0x72, 0x65, 0x5f, 0x6d, 0x74, 0x6c, 0x73, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, - 0x17, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, - 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x49, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, - 0x72, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, - 0x72, 0x65, 0x4d, 0x54, 0x4c, 0x53, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x24, 0x75, - 0x69, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, - 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x4d, 0x74, 0x6c, 0x73, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x12, 0xb8, 0x01, 0x0a, 0x38, 0x75, 0x69, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, - 0x76, 0x65, 0x72, 0x5f, 0x64, 0x65, 0x73, 0x6b, 0x74, 0x6f, 0x70, 0x5f, 0x61, 0x63, 0x74, 0x69, - 0x76, 0x65, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x5f, 0x74, 0x6f, 0x6f, - 0x6c, 0x73, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, - 0x18, 0x18, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x41, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x49, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, - 0x65, 0x72, 0x44, 0x65, 0x73, 0x6b, 0x74, 0x6f, 0x70, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x44, - 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x54, 0x6f, 0x6f, 0x6c, 0x73, 0x49, 0x6e, 0x73, - 0x74, 0x61, 0x6c, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x31, 0x75, 0x69, 0x44, - 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x44, 0x65, 0x73, 0x6b, 0x74, 0x6f, 0x70, 0x41, 0x63, - 0x74, 0x69, 0x76, 0x65, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x54, 0x6f, 0x6f, - 0x6c, 0x73, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0xae, - 0x01, 0x0a, 0x34, 0x75, 0x69, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x64, - 0x65, 0x73, 0x6b, 0x74, 0x6f, 0x70, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x64, 0x69, - 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, - 0x65, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x19, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3e, 0x2e, - 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, - 0x49, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x44, 0x65, 0x73, 0x6b, 0x74, 0x6f, 0x70, - 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, - 0x2e, 0x75, 0x69, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x44, 0x65, 0x73, 0x6b, 0x74, - 0x6f, 0x70, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, - 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, - 0x95, 0x01, 0x0a, 0x2b, 0x75, 0x69, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, - 0x61, 0x75, 0x74, 0x6f, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x65, 0x64, 0x5f, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, - 0x1a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, - 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x49, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, - 0x72, 0x41, 0x75, 0x74, 0x6f, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x65, 0x64, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, - 0x26, 0x75, 0x69, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x41, 0x75, 0x74, 0x6f, 0x44, - 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x9f, 0x01, 0x0a, 0x2f, 0x75, 0x69, 0x5f, 0x64, - 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, - 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x5f, 0x69, 0x61, 0x6d, 0x5f, 0x70, - 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x1b, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x39, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x2e, 0x55, 0x49, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x44, 0x61, 0x74, - 0x61, 0x62, 0x61, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x49, 0x41, - 0x4d, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x29, - 0x75, 0x69, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, - 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x49, 0x61, 0x6d, 0x50, 0x6f, - 0x6c, 0x69, 0x63, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x88, 0x01, 0x0a, 0x26, 0x75, 0x69, - 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, - 0x70, 0x61, 0x6c, 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x5f, 0x65, - 0x76, 0x65, 0x6e, 0x74, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x70, 0x72, 0x65, - 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x49, 0x44, 0x69, - 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, - 0x52, 0x22, 0x75, 0x69, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x50, 0x72, 0x69, 0x6e, - 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x12, 0x79, 0x0a, 0x21, 0x75, 0x69, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, - 0x76, 0x65, 0x72, 0x5f, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x1d, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x2d, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x2e, 0x55, 0x49, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x54, 0x65, 0x73, 0x74, 0x43, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, - 0x52, 0x1d, 0x75, 0x69, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x54, 0x65, 0x73, 0x74, - 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, - 0x69, 0x0a, 0x1b, 0x75, 0x69, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x63, - 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x1e, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, + 0x65, 0x73, 0x43, 0x6f, 0x70, 0x79, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x48, 0x00, 0x52, 0x18, 0x75, 0x69, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x43, 0x6f, + 0x64, 0x65, 0x73, 0x43, 0x6f, 0x70, 0x79, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x12, 0x72, 0x0a, 0x1d, + 0x75, 0x69, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, + 0x73, 0x5f, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x5f, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x18, 0x10, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x49, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x43, + 0x6f, 0x64, 0x65, 0x73, 0x50, 0x72, 0x69, 0x6e, 0x74, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x19, 0x75, 0x69, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, + 0x79, 0x43, 0x6f, 0x64, 0x65, 0x73, 0x50, 0x72, 0x69, 0x6e, 0x74, 0x43, 0x6c, 0x69, 0x63, 0x6b, + 0x12, 0x63, 0x0a, 0x19, 0x75, 0x69, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x11, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x49, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x53, + 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x16, 0x75, + 0x69, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x82, 0x01, 0x0a, 0x24, 0x75, 0x69, 0x5f, 0x64, 0x69, 0x73, + 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x73, + 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x12, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x49, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, - 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, - 0x52, 0x18, 0x75, 0x69, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6d, 0x70, - 0x6c, 0x65, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x42, 0x0a, 0x0b, 0x72, 0x6f, - 0x6c, 0x65, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x18, 0x1f, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1f, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x48, 0x00, 0x52, 0x0a, 0x72, 0x6f, 0x6c, 0x65, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x63, - 0x0a, 0x18, 0x75, 0x69, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x6e, 0x65, 0x77, 0x5f, - 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x18, 0x20, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x29, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x20, 0x75, 0x69, 0x44, 0x69, 0x73, 0x63, + 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x6f, 0x0a, 0x1d, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x69, + 0x73, 0x73, 0x75, 0x65, 0x64, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x13, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x2a, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, + 0x74, 0x65, 0x49, 0x73, 0x73, 0x75, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, + 0x1a, 0x75, 0x73, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, + 0x49, 0x73, 0x73, 0x75, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x4d, 0x0a, 0x10, 0x73, + 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x76, 0x32, 0x18, + 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x74, + 0x61, 0x72, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x73, 0x65, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x56, 0x32, 0x12, 0x76, 0x0a, 0x20, 0x75, 0x69, + 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, + 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x15, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x49, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, + 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x48, 0x00, 0x52, 0x1c, 0x75, 0x69, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, + 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x12, 0x7f, 0x0a, 0x23, 0x75, 0x69, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, + 0x72, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x73, + 0x74, 0x65, 0x72, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x16, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x2f, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x2e, 0x55, 0x49, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x44, 0x61, 0x74, 0x61, 0x62, + 0x61, 0x73, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x48, 0x00, 0x52, 0x1f, 0x75, 0x69, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x44, 0x61, + 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x12, 0x8f, 0x01, 0x0a, 0x29, 0x75, 0x69, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, + 0x76, 0x65, 0x72, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x5f, 0x6d, 0x74, 0x6c, 0x73, 0x5f, 0x65, 0x76, 0x65, 0x6e, + 0x74, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x49, 0x44, 0x69, 0x73, 0x63, 0x6f, + 0x76, 0x65, 0x72, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x75, 0x72, 0x65, 0x4d, 0x54, 0x4c, 0x53, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, + 0x24, 0x75, 0x69, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x44, 0x61, 0x74, 0x61, 0x62, + 0x61, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x4d, 0x74, 0x6c, 0x73, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0xb8, 0x01, 0x0a, 0x38, 0x75, 0x69, 0x5f, 0x64, 0x69, 0x73, + 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x64, 0x65, 0x73, 0x6b, 0x74, 0x6f, 0x70, 0x5f, 0x61, 0x63, + 0x74, 0x69, 0x76, 0x65, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x5f, 0x74, + 0x6f, 0x6f, 0x6c, 0x73, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x5f, 0x65, 0x76, 0x65, + 0x6e, 0x74, 0x18, 0x18, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x41, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, + 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x49, 0x44, 0x69, 0x73, 0x63, + 0x6f, 0x76, 0x65, 0x72, 0x44, 0x65, 0x73, 0x6b, 0x74, 0x6f, 0x70, 0x41, 0x63, 0x74, 0x69, 0x76, + 0x65, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x54, 0x6f, 0x6f, 0x6c, 0x73, 0x49, + 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x31, 0x75, + 0x69, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x44, 0x65, 0x73, 0x6b, 0x74, 0x6f, 0x70, + 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x54, + 0x6f, 0x6f, 0x6c, 0x73, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x12, 0xae, 0x01, 0x0a, 0x34, 0x75, 0x69, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, + 0x5f, 0x64, 0x65, 0x73, 0x6b, 0x74, 0x6f, 0x70, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, + 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x75, 0x72, 0x65, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x19, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x3e, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x2e, 0x55, 0x49, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x44, 0x65, 0x73, 0x6b, 0x74, + 0x6f, 0x70, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, + 0x00, 0x52, 0x2e, 0x75, 0x69, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x44, 0x65, 0x73, + 0x6b, 0x74, 0x6f, 0x70, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x12, 0x95, 0x01, 0x0a, 0x2b, 0x75, 0x69, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, + 0x72, 0x5f, 0x61, 0x75, 0x74, 0x6f, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x65, + 0x64, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x5f, 0x65, 0x76, 0x65, 0x6e, + 0x74, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x49, 0x44, 0x69, 0x73, 0x63, 0x6f, + 0x76, 0x65, 0x72, 0x41, 0x75, 0x74, 0x6f, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x65, + 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, + 0x00, 0x52, 0x26, 0x75, 0x69, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x41, 0x75, 0x74, + 0x6f, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x9f, 0x01, 0x0a, 0x2f, 0x75, 0x69, + 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, + 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x5f, 0x69, 0x61, 0x6d, + 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x1b, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x49, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x44, + 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, + 0x49, 0x41, 0x4d, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, + 0x52, 0x29, 0x75, 0x69, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x44, 0x61, 0x74, 0x61, + 0x62, 0x61, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x49, 0x61, 0x6d, + 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x88, 0x01, 0x0a, 0x26, + 0x75, 0x69, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x69, 0x6e, + 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, + 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x70, + 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x49, + 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, + 0x6c, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x48, 0x00, 0x52, 0x22, 0x75, 0x69, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x50, 0x72, + 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, + 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x79, 0x0a, 0x21, 0x75, 0x69, 0x5f, 0x64, 0x69, 0x73, + 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x1d, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x2d, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x2e, 0x55, 0x49, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x54, 0x65, 0x73, + 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x48, 0x00, 0x52, 0x1d, 0x75, 0x69, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x54, 0x65, + 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x12, 0x69, 0x0a, 0x1b, 0x75, 0x69, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, + 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, + 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x49, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, + 0x65, 0x72, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x48, 0x00, 0x52, 0x18, 0x75, 0x69, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x43, 0x6f, + 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x42, 0x0a, 0x0b, + 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x18, 0x1f, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0a, 0x72, 0x6f, 0x6c, 0x65, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x12, 0x63, 0x0a, 0x18, 0x75, 0x69, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x6e, 0x65, + 0x77, 0x5f, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x18, 0x20, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x2e, 0x55, 0x49, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x65, 0x77, 0x52, + 0x6f, 0x6c, 0x65, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, + 0x14, 0x75, 0x69, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x65, 0x77, 0x52, 0x6f, 0x6c, 0x65, + 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x12, 0x70, 0x0a, 0x1d, 0x75, 0x69, 0x5f, 0x63, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x5f, 0x6e, 0x65, 0x77, 0x5f, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x73, 0x61, 0x76, 0x65, + 0x5f, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x18, 0x21, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x70, + 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x49, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x65, 0x77, 0x52, 0x6f, 0x6c, 0x65, 0x53, 0x61, 0x76, + 0x65, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x18, 0x75, + 0x69, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x65, 0x77, 0x52, 0x6f, 0x6c, 0x65, 0x53, 0x61, + 0x76, 0x65, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x12, 0x76, 0x0a, 0x1f, 0x75, 0x69, 0x5f, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x5f, 0x6e, 0x65, 0x77, 0x5f, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x63, 0x61, + 0x6e, 0x63, 0x65, 0x6c, 0x5f, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x18, 0x22, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x2f, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x49, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x65, 0x77, 0x52, 0x6f, 0x6c, - 0x65, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x14, 0x75, - 0x69, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x65, 0x77, 0x52, 0x6f, 0x6c, 0x65, 0x43, 0x6c, - 0x69, 0x63, 0x6b, 0x12, 0x70, 0x0a, 0x1d, 0x75, 0x69, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x5f, 0x6e, 0x65, 0x77, 0x5f, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x73, 0x61, 0x76, 0x65, 0x5f, 0x63, - 0x6c, 0x69, 0x63, 0x6b, 0x18, 0x21, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x70, 0x72, 0x65, - 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x49, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x4e, 0x65, 0x77, 0x52, 0x6f, 0x6c, 0x65, 0x53, 0x61, 0x76, 0x65, 0x43, - 0x6c, 0x69, 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x18, 0x75, 0x69, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x65, 0x77, 0x52, 0x6f, 0x6c, 0x65, 0x53, 0x61, 0x76, 0x65, - 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x12, 0x76, 0x0a, 0x1f, 0x75, 0x69, 0x5f, 0x63, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x5f, 0x6e, 0x65, 0x77, 0x5f, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x63, 0x61, 0x6e, 0x63, - 0x65, 0x6c, 0x5f, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x18, 0x22, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, - 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, - 0x55, 0x49, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x65, 0x77, 0x52, 0x6f, 0x6c, 0x65, 0x43, - 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, - 0x00, 0x52, 0x1a, 0x75, 0x69, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x65, 0x77, 0x52, 0x6f, - 0x6c, 0x65, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x12, 0x98, 0x01, - 0x0a, 0x2b, 0x75, 0x69, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x6e, 0x65, 0x77, 0x5f, - 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, - 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x18, 0x23, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x49, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x65, 0x77, + 0x65, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x48, 0x00, 0x52, 0x1a, 0x75, 0x69, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x65, 0x77, + 0x52, 0x6f, 0x6c, 0x65, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x12, + 0x98, 0x01, 0x0a, 0x2b, 0x75, 0x69, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x6e, 0x65, + 0x77, 0x5f, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x64, 0x6f, 0x63, 0x75, + 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x18, + 0x23, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x49, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, + 0x65, 0x77, 0x52, 0x6f, 0x6c, 0x65, 0x56, 0x69, 0x65, 0x77, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, + 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x48, 0x00, 0x52, 0x25, 0x75, 0x69, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x65, 0x77, 0x52, 0x6f, 0x6c, 0x65, 0x56, 0x69, 0x65, 0x77, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, - 0x00, 0x52, 0x25, 0x75, 0x69, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x65, 0x77, 0x52, 0x6f, - 0x6c, 0x65, 0x56, 0x69, 0x65, 0x77, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x12, 0x45, 0x0a, 0x0c, 0x6b, 0x75, 0x62, 0x65, - 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x24, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, - 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, - 0x4b, 0x75, 0x62, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x48, 0x00, 0x52, 0x0b, 0x6b, 0x75, 0x62, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x2f, 0x0a, 0x04, 0x73, 0x66, 0x74, 0x70, 0x18, 0x25, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, - 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x53, - 0x46, 0x54, 0x50, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x04, 0x73, 0x66, 0x74, 0x70, - 0x12, 0x56, 0x0a, 0x14, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x26, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x12, 0x45, 0x0a, 0x0c, 0x6b, 0x75, + 0x62, 0x65, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x24, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x20, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x2e, 0x4b, 0x75, 0x62, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x6b, 0x75, 0x62, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x73, 0x66, 0x74, 0x70, 0x18, 0x25, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x19, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x2e, 0x53, 0x46, 0x54, 0x50, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x04, 0x73, 0x66, + 0x74, 0x70, 0x12, 0x56, 0x0a, 0x14, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x26, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x22, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x12, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x57, 0x0a, 0x12, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, + 0x18, 0x27, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, + 0x52, 0x11, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, + 0x65, 0x61, 0x74, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x4a, 0x04, 0x08, 0x08, + 0x10, 0x09, 0x52, 0x1c, 0x75, 0x69, 0x5f, 0x6f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x5f, 0x67, + 0x65, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x6c, 0x69, 0x63, 0x6b, + 0x22, 0x15, 0x0a, 0x13, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x51, 0x0a, 0x13, 0x53, 0x75, 0x62, 0x6d, 0x69, + 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3a, + 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, - 0x41, 0x67, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x48, 0x00, 0x52, 0x12, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x57, 0x0a, 0x12, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x5f, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x18, 0x27, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, - 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x48, 0x65, - 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x11, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, - 0x74, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x4a, 0x04, 0x08, 0x08, 0x10, 0x09, - 0x52, 0x1c, 0x75, 0x69, 0x5f, 0x6f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x5f, 0x67, 0x65, 0x74, - 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x22, 0x15, - 0x0a, 0x13, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x51, 0x0a, 0x13, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3a, 0x0a, 0x06, - 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, - 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x53, 0x75, - 0x62, 0x6d, 0x69, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x52, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x16, 0x0a, 0x14, 0x53, 0x75, 0x62, 0x6d, - 0x69, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x16, 0x0a, 0x14, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x54, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x17, 0x0a, 0x15, 0x48, 0x65, 0x6c, 0x6c, - 0x6f, 0x54, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x2a, 0xe2, 0x01, 0x0a, 0x0c, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x69, - 0x6e, 0x64, 0x12, 0x1d, 0x0a, 0x19, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x4b, - 0x49, 0x4e, 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, - 0x00, 0x12, 0x16, 0x0a, 0x12, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x4b, 0x49, - 0x4e, 0x44, 0x5f, 0x4e, 0x4f, 0x44, 0x45, 0x10, 0x01, 0x12, 0x1c, 0x0a, 0x18, 0x52, 0x45, 0x53, - 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x41, 0x50, 0x50, 0x5f, 0x53, - 0x45, 0x52, 0x56, 0x45, 0x52, 0x10, 0x02, 0x12, 0x1d, 0x0a, 0x19, 0x52, 0x45, 0x53, 0x4f, 0x55, - 0x52, 0x43, 0x45, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x4b, 0x55, 0x42, 0x45, 0x5f, 0x53, 0x45, - 0x52, 0x56, 0x45, 0x52, 0x10, 0x03, 0x12, 0x1b, 0x0a, 0x17, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, - 0x43, 0x45, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x44, 0x42, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x45, - 0x52, 0x10, 0x04, 0x12, 0x21, 0x0a, 0x1d, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, - 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x57, 0x49, 0x4e, 0x44, 0x4f, 0x57, 0x53, 0x5f, 0x44, 0x45, 0x53, - 0x4b, 0x54, 0x4f, 0x50, 0x10, 0x05, 0x12, 0x1e, 0x0a, 0x1a, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, - 0x43, 0x45, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x4f, 0x50, 0x45, - 0x4e, 0x53, 0x53, 0x48, 0x10, 0x06, 0x2a, 0xaa, 0x06, 0x0a, 0x10, 0x44, 0x69, 0x73, 0x63, 0x6f, - 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x21, 0x0a, 0x1d, 0x44, - 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, - 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1c, - 0x0a, 0x18, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, - 0x52, 0x43, 0x45, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x10, 0x01, 0x12, 0x20, 0x0a, 0x1c, - 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, - 0x45, 0x5f, 0x4b, 0x55, 0x42, 0x45, 0x52, 0x4e, 0x45, 0x54, 0x45, 0x53, 0x10, 0x02, 0x12, 0x33, - 0x0a, 0x2f, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, - 0x52, 0x43, 0x45, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x50, 0x4f, 0x53, - 0x54, 0x47, 0x52, 0x45, 0x53, 0x5f, 0x53, 0x45, 0x4c, 0x46, 0x5f, 0x48, 0x4f, 0x53, 0x54, 0x45, - 0x44, 0x10, 0x03, 0x12, 0x30, 0x0a, 0x2c, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, - 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, - 0x45, 0x5f, 0x4d, 0x59, 0x53, 0x51, 0x4c, 0x5f, 0x53, 0x45, 0x4c, 0x46, 0x5f, 0x48, 0x4f, 0x53, - 0x54, 0x45, 0x44, 0x10, 0x04, 0x12, 0x32, 0x0a, 0x2e, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, + 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x52, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x16, 0x0a, 0x14, 0x53, 0x75, + 0x62, 0x6d, 0x69, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x16, 0x0a, 0x14, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x54, 0x65, 0x6c, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x17, 0x0a, 0x15, 0x48, 0x65, + 0x6c, 0x6c, 0x6f, 0x54, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x2a, 0xe2, 0x01, 0x0a, 0x0c, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x1d, 0x0a, 0x19, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, + 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, + 0x44, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, + 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x4e, 0x4f, 0x44, 0x45, 0x10, 0x01, 0x12, 0x1c, 0x0a, 0x18, 0x52, + 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x41, 0x50, 0x50, + 0x5f, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x10, 0x02, 0x12, 0x1d, 0x0a, 0x19, 0x52, 0x45, 0x53, + 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x4b, 0x55, 0x42, 0x45, 0x5f, + 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x10, 0x03, 0x12, 0x1b, 0x0a, 0x17, 0x52, 0x45, 0x53, 0x4f, + 0x55, 0x52, 0x43, 0x45, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x44, 0x42, 0x5f, 0x53, 0x45, 0x52, + 0x56, 0x45, 0x52, 0x10, 0x04, 0x12, 0x21, 0x0a, 0x1d, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, + 0x45, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x57, 0x49, 0x4e, 0x44, 0x4f, 0x57, 0x53, 0x5f, 0x44, + 0x45, 0x53, 0x4b, 0x54, 0x4f, 0x50, 0x10, 0x05, 0x12, 0x1e, 0x0a, 0x1a, 0x52, 0x45, 0x53, 0x4f, + 0x55, 0x52, 0x43, 0x45, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x4f, + 0x50, 0x45, 0x4e, 0x53, 0x53, 0x48, 0x10, 0x06, 0x2a, 0xed, 0x0d, 0x0a, 0x10, 0x44, 0x69, 0x73, + 0x63, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x21, 0x0a, + 0x1d, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, + 0x43, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, + 0x12, 0x1c, 0x0a, 0x18, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, + 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x10, 0x01, 0x12, 0x20, + 0x0a, 0x1c, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, + 0x52, 0x43, 0x45, 0x5f, 0x4b, 0x55, 0x42, 0x45, 0x52, 0x4e, 0x45, 0x54, 0x45, 0x53, 0x10, 0x02, + 0x12, 0x33, 0x0a, 0x2f, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, + 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x50, + 0x4f, 0x53, 0x54, 0x47, 0x52, 0x45, 0x53, 0x5f, 0x53, 0x45, 0x4c, 0x46, 0x5f, 0x48, 0x4f, 0x53, + 0x54, 0x45, 0x44, 0x10, 0x03, 0x12, 0x30, 0x0a, 0x2c, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x42, - 0x41, 0x53, 0x45, 0x5f, 0x4d, 0x4f, 0x4e, 0x47, 0x4f, 0x44, 0x42, 0x5f, 0x53, 0x45, 0x4c, 0x46, - 0x5f, 0x48, 0x4f, 0x53, 0x54, 0x45, 0x44, 0x10, 0x05, 0x12, 0x2b, 0x0a, 0x27, 0x44, 0x49, 0x53, - 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x44, - 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x50, 0x4f, 0x53, 0x54, 0x47, 0x52, 0x45, 0x53, - 0x5f, 0x52, 0x44, 0x53, 0x10, 0x06, 0x12, 0x28, 0x0a, 0x24, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, - 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x44, 0x41, 0x54, 0x41, - 0x42, 0x41, 0x53, 0x45, 0x5f, 0x4d, 0x59, 0x53, 0x51, 0x4c, 0x5f, 0x52, 0x44, 0x53, 0x10, 0x07, - 0x12, 0x26, 0x0a, 0x22, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, - 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x41, 0x50, 0x50, 0x4c, 0x49, 0x43, 0x41, 0x54, 0x49, 0x4f, - 0x4e, 0x5f, 0x48, 0x54, 0x54, 0x50, 0x10, 0x08, 0x12, 0x25, 0x0a, 0x21, 0x44, 0x49, 0x53, 0x43, - 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x41, 0x50, - 0x50, 0x4c, 0x49, 0x43, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x43, 0x50, 0x10, 0x09, 0x12, - 0x25, 0x0a, 0x21, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, - 0x55, 0x52, 0x43, 0x45, 0x5f, 0x57, 0x49, 0x4e, 0x44, 0x4f, 0x57, 0x53, 0x5f, 0x44, 0x45, 0x53, - 0x4b, 0x54, 0x4f, 0x50, 0x10, 0x0a, 0x12, 0x2c, 0x0a, 0x28, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, + 0x41, 0x53, 0x45, 0x5f, 0x4d, 0x59, 0x53, 0x51, 0x4c, 0x5f, 0x53, 0x45, 0x4c, 0x46, 0x5f, 0x48, + 0x4f, 0x53, 0x54, 0x45, 0x44, 0x10, 0x04, 0x12, 0x32, 0x0a, 0x2e, 0x44, 0x49, 0x53, 0x43, 0x4f, + 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x44, 0x41, 0x54, + 0x41, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x4d, 0x4f, 0x4e, 0x47, 0x4f, 0x44, 0x42, 0x5f, 0x53, 0x45, + 0x4c, 0x46, 0x5f, 0x48, 0x4f, 0x53, 0x54, 0x45, 0x44, 0x10, 0x05, 0x12, 0x2b, 0x0a, 0x27, 0x44, + 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, + 0x5f, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x50, 0x4f, 0x53, 0x54, 0x47, 0x52, + 0x45, 0x53, 0x5f, 0x52, 0x44, 0x53, 0x10, 0x06, 0x12, 0x28, 0x0a, 0x24, 0x44, 0x49, 0x53, 0x43, + 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x44, 0x41, + 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x4d, 0x59, 0x53, 0x51, 0x4c, 0x5f, 0x52, 0x44, 0x53, + 0x10, 0x07, 0x12, 0x26, 0x0a, 0x22, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, + 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x41, 0x50, 0x50, 0x4c, 0x49, 0x43, 0x41, 0x54, + 0x49, 0x4f, 0x4e, 0x5f, 0x48, 0x54, 0x54, 0x50, 0x10, 0x08, 0x12, 0x25, 0x0a, 0x21, 0x44, 0x49, + 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, + 0x41, 0x50, 0x50, 0x4c, 0x49, 0x43, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x43, 0x50, 0x10, + 0x09, 0x12, 0x25, 0x0a, 0x21, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, + 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x57, 0x49, 0x4e, 0x44, 0x4f, 0x57, 0x53, 0x5f, 0x44, + 0x45, 0x53, 0x4b, 0x54, 0x4f, 0x50, 0x10, 0x0a, 0x12, 0x2c, 0x0a, 0x28, 0x44, 0x49, 0x53, 0x43, + 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x44, 0x41, + 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x53, 0x51, 0x4c, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, + 0x5f, 0x52, 0x44, 0x53, 0x10, 0x0b, 0x12, 0x30, 0x0a, 0x2c, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x44, 0x41, 0x54, 0x41, - 0x42, 0x41, 0x53, 0x45, 0x5f, 0x53, 0x51, 0x4c, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x52, - 0x44, 0x53, 0x10, 0x0b, 0x12, 0x30, 0x0a, 0x2c, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, + 0x42, 0x41, 0x53, 0x45, 0x5f, 0x50, 0x4f, 0x53, 0x54, 0x47, 0x52, 0x45, 0x53, 0x5f, 0x52, 0x45, + 0x44, 0x53, 0x48, 0x49, 0x46, 0x54, 0x10, 0x0c, 0x12, 0x34, 0x0a, 0x30, 0x44, 0x49, 0x53, 0x43, + 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x44, 0x41, + 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x53, 0x51, 0x4c, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, + 0x5f, 0x53, 0x45, 0x4c, 0x46, 0x5f, 0x48, 0x4f, 0x53, 0x54, 0x45, 0x44, 0x10, 0x0d, 0x12, 0x30, + 0x0a, 0x2c, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, + 0x52, 0x43, 0x45, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x52, 0x45, 0x44, + 0x49, 0x53, 0x5f, 0x53, 0x45, 0x4c, 0x46, 0x5f, 0x48, 0x4f, 0x53, 0x54, 0x45, 0x44, 0x10, 0x0e, + 0x12, 0x2b, 0x0a, 0x27, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, + 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x50, + 0x4f, 0x53, 0x54, 0x47, 0x52, 0x45, 0x53, 0x5f, 0x47, 0x43, 0x50, 0x10, 0x0f, 0x12, 0x28, 0x0a, + 0x24, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, + 0x43, 0x45, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x4d, 0x59, 0x53, 0x51, + 0x4c, 0x5f, 0x47, 0x43, 0x50, 0x10, 0x10, 0x12, 0x2c, 0x0a, 0x28, 0x44, 0x49, 0x53, 0x43, 0x4f, + 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x44, 0x41, 0x54, + 0x41, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x53, 0x51, 0x4c, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, + 0x47, 0x43, 0x50, 0x10, 0x11, 0x12, 0x3b, 0x0a, 0x37, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, + 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x42, + 0x41, 0x53, 0x45, 0x5f, 0x50, 0x4f, 0x53, 0x54, 0x47, 0x52, 0x45, 0x53, 0x5f, 0x52, 0x45, 0x44, + 0x53, 0x48, 0x49, 0x46, 0x54, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x4c, 0x45, 0x53, 0x53, + 0x10, 0x12, 0x12, 0x2d, 0x0a, 0x29, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, + 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, + 0x5f, 0x50, 0x4f, 0x53, 0x54, 0x47, 0x52, 0x45, 0x53, 0x5f, 0x41, 0x5a, 0x55, 0x52, 0x45, 0x10, + 0x13, 0x12, 0x27, 0x0a, 0x23, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, + 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x5f, + 0x44, 0x59, 0x4e, 0x41, 0x4d, 0x4f, 0x44, 0x42, 0x10, 0x14, 0x12, 0x32, 0x0a, 0x2e, 0x44, 0x49, + 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, + 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x43, 0x41, 0x53, 0x53, 0x41, 0x4e, 0x44, + 0x52, 0x41, 0x5f, 0x4b, 0x45, 0x59, 0x53, 0x50, 0x41, 0x43, 0x45, 0x53, 0x10, 0x15, 0x12, 0x34, + 0x0a, 0x30, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, + 0x52, 0x43, 0x45, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x43, 0x41, 0x53, + 0x53, 0x41, 0x4e, 0x44, 0x52, 0x41, 0x5f, 0x53, 0x45, 0x4c, 0x46, 0x5f, 0x48, 0x4f, 0x53, 0x54, + 0x45, 0x44, 0x10, 0x16, 0x12, 0x38, 0x0a, 0x34, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, - 0x53, 0x45, 0x5f, 0x50, 0x4f, 0x53, 0x54, 0x47, 0x52, 0x45, 0x53, 0x5f, 0x52, 0x45, 0x44, 0x53, - 0x48, 0x49, 0x46, 0x54, 0x10, 0x0c, 0x12, 0x34, 0x0a, 0x30, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, - 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x44, 0x41, 0x54, 0x41, - 0x42, 0x41, 0x53, 0x45, 0x5f, 0x53, 0x51, 0x4c, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x53, - 0x45, 0x4c, 0x46, 0x5f, 0x48, 0x4f, 0x53, 0x54, 0x45, 0x44, 0x10, 0x0d, 0x12, 0x30, 0x0a, 0x2c, - 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, - 0x45, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x52, 0x45, 0x44, 0x49, 0x53, - 0x5f, 0x53, 0x45, 0x4c, 0x46, 0x5f, 0x48, 0x4f, 0x53, 0x54, 0x45, 0x44, 0x10, 0x0e, 0x12, 0x2b, - 0x0a, 0x27, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, - 0x52, 0x43, 0x45, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x50, 0x4f, 0x53, - 0x54, 0x47, 0x52, 0x45, 0x53, 0x5f, 0x47, 0x43, 0x50, 0x10, 0x0f, 0x12, 0x28, 0x0a, 0x24, 0x44, + 0x53, 0x45, 0x5f, 0x45, 0x4c, 0x41, 0x53, 0x54, 0x49, 0x43, 0x53, 0x45, 0x41, 0x52, 0x43, 0x48, + 0x5f, 0x53, 0x45, 0x4c, 0x46, 0x5f, 0x48, 0x4f, 0x53, 0x54, 0x45, 0x44, 0x10, 0x17, 0x12, 0x30, + 0x0a, 0x2c, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, + 0x52, 0x43, 0x45, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x52, 0x45, 0x44, + 0x49, 0x53, 0x5f, 0x45, 0x4c, 0x41, 0x53, 0x54, 0x49, 0x43, 0x41, 0x43, 0x48, 0x45, 0x10, 0x18, + 0x12, 0x2d, 0x0a, 0x29, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, + 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x52, + 0x45, 0x44, 0x49, 0x53, 0x5f, 0x4d, 0x45, 0x4d, 0x4f, 0x52, 0x59, 0x44, 0x42, 0x10, 0x19, 0x12, + 0x30, 0x0a, 0x2c, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, + 0x55, 0x52, 0x43, 0x45, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x52, 0x45, + 0x44, 0x49, 0x53, 0x5f, 0x41, 0x5a, 0x55, 0x52, 0x45, 0x5f, 0x43, 0x41, 0x43, 0x48, 0x45, 0x10, + 0x1a, 0x12, 0x38, 0x0a, 0x34, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, + 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x5f, + 0x52, 0x45, 0x44, 0x49, 0x53, 0x5f, 0x43, 0x4c, 0x55, 0x53, 0x54, 0x45, 0x52, 0x5f, 0x53, 0x45, + 0x4c, 0x46, 0x5f, 0x48, 0x4f, 0x53, 0x54, 0x45, 0x44, 0x10, 0x1b, 0x12, 0x2a, 0x0a, 0x26, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x4d, 0x59, 0x53, 0x51, 0x4c, 0x5f, - 0x47, 0x43, 0x50, 0x10, 0x10, 0x12, 0x2c, 0x0a, 0x28, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, - 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x42, - 0x41, 0x53, 0x45, 0x5f, 0x53, 0x51, 0x4c, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x47, 0x43, - 0x50, 0x10, 0x11, 0x2a, 0xa3, 0x01, 0x0a, 0x0e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1f, 0x0a, 0x1b, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, - 0x45, 0x52, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, - 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x44, 0x49, 0x53, 0x43, 0x4f, - 0x56, 0x45, 0x52, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, - 0x53, 0x53, 0x10, 0x01, 0x12, 0x1b, 0x0a, 0x17, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, - 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x53, 0x4b, 0x49, 0x50, 0x50, 0x45, 0x44, 0x10, - 0x02, 0x12, 0x19, 0x0a, 0x15, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x53, 0x54, - 0x41, 0x54, 0x55, 0x53, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x1b, 0x0a, 0x17, + 0x41, 0x5a, 0x55, 0x52, 0x45, 0x10, 0x1c, 0x12, 0x2e, 0x0a, 0x2a, 0x44, 0x49, 0x53, 0x43, 0x4f, + 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x44, 0x41, 0x54, + 0x41, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x53, 0x51, 0x4c, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, + 0x41, 0x5a, 0x55, 0x52, 0x45, 0x10, 0x1d, 0x12, 0x32, 0x0a, 0x2e, 0x44, 0x49, 0x53, 0x43, 0x4f, + 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x44, 0x41, 0x54, + 0x41, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x53, 0x51, 0x4c, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, + 0x4d, 0x49, 0x43, 0x52, 0x4f, 0x53, 0x4f, 0x46, 0x54, 0x10, 0x1e, 0x12, 0x36, 0x0a, 0x32, 0x44, + 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, + 0x5f, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x43, 0x4f, 0x43, 0x4b, 0x52, 0x4f, + 0x41, 0x43, 0x48, 0x44, 0x42, 0x5f, 0x53, 0x45, 0x4c, 0x46, 0x5f, 0x48, 0x4f, 0x53, 0x54, 0x45, + 0x44, 0x10, 0x1f, 0x12, 0x2c, 0x0a, 0x28, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, + 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, + 0x45, 0x5f, 0x4d, 0x4f, 0x4e, 0x47, 0x4f, 0x44, 0x42, 0x5f, 0x41, 0x54, 0x4c, 0x41, 0x53, 0x10, + 0x20, 0x12, 0x28, 0x0a, 0x24, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, + 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x5f, + 0x53, 0x4e, 0x4f, 0x57, 0x46, 0x4c, 0x41, 0x4b, 0x45, 0x10, 0x21, 0x12, 0x2c, 0x0a, 0x28, 0x44, + 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, + 0x5f, 0x44, 0x4f, 0x43, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x52, 0x44, + 0x53, 0x5f, 0x50, 0x52, 0x4f, 0x58, 0x59, 0x10, 0x22, 0x12, 0x34, 0x0a, 0x30, 0x44, 0x49, 0x53, + 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x44, + 0x4f, 0x43, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x48, 0x49, 0x47, 0x48, + 0x5f, 0x41, 0x56, 0x41, 0x49, 0x4c, 0x41, 0x42, 0x49, 0x4c, 0x49, 0x54, 0x59, 0x10, 0x23, 0x12, + 0x37, 0x0a, 0x33, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x53, 0x4f, + 0x55, 0x52, 0x43, 0x45, 0x5f, 0x44, 0x4f, 0x43, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, + 0x45, 0x5f, 0x44, 0x59, 0x4e, 0x41, 0x4d, 0x49, 0x43, 0x5f, 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, + 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x24, 0x2a, 0xa3, 0x01, 0x0a, 0x0e, 0x44, 0x69, 0x73, + 0x63, 0x6f, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1f, 0x0a, 0x1b, 0x44, + 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, + 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, - 0x41, 0x42, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x10, 0x04, 0x32, 0xb4, 0x02, 0x0a, 0x18, 0x54, 0x65, - 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5b, 0x0a, 0x0b, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x22, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, - 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x70, 0x72, 0x65, 0x68, - 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, - 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, - 0x88, 0x02, 0x01, 0x12, 0x5b, 0x0a, 0x0c, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x73, 0x12, 0x23, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, + 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x01, 0x12, 0x1b, 0x0a, 0x17, 0x44, 0x49, 0x53, + 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x53, 0x4b, 0x49, + 0x50, 0x50, 0x45, 0x44, 0x10, 0x02, 0x12, 0x19, 0x0a, 0x15, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, + 0x45, 0x52, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, + 0x03, 0x12, 0x1b, 0x0a, 0x17, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x53, 0x54, + 0x41, 0x54, 0x55, 0x53, 0x5f, 0x41, 0x42, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x10, 0x04, 0x32, 0xb4, + 0x02, 0x0a, 0x18, 0x54, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5b, 0x0a, 0x0b, 0x53, + 0x75, 0x62, 0x6d, 0x69, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x22, 0x2e, 0x70, 0x72, 0x65, + 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x53, 0x75, 0x62, 0x6d, + 0x69, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, + 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, + 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x12, 0x5b, 0x0a, 0x0c, 0x53, 0x75, 0x62, 0x6d, + 0x69, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x23, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x12, 0x5e, 0x0a, 0x0d, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x54, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, - 0x74, 0x12, 0x24, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x54, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, - 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x54, 0x65, - 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x42, 0x3f, 0x5a, 0x3d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, - 0x72, 0x61, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x6c, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, - 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, + 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x53, + 0x75, 0x62, 0x6d, 0x69, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5e, 0x0a, 0x0d, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x54, 0x65, + 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x24, 0x2e, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x54, 0x65, 0x6c, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x70, + 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x48, 0x65, + 0x6c, 0x6c, 0x6f, 0x54, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x3f, 0x5a, 0x3d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x61, + 0x6c, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x65, 0x68, 0x6f, 0x67, 0x2f, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/gen/proto/js/prehog/v1alpha/connect_pb.d.ts b/gen/proto/js/prehog/v1alpha/connect_pb.d.ts index b3995986e7f95..ff5eda22009a8 100644 --- a/gen/proto/js/prehog/v1alpha/connect_pb.d.ts +++ b/gen/proto/js/prehog/v1alpha/connect_pb.d.ts @@ -62,6 +62,9 @@ export class ConnectProtocolUseEvent extends jspb.Message { getProtocol(): string; setProtocol(value: string): ConnectProtocolUseEvent; + getOrigin(): string; + setOrigin(value: string): ConnectProtocolUseEvent; + serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): ConnectProtocolUseEvent.AsObject; @@ -78,6 +81,7 @@ export namespace ConnectProtocolUseEvent { clusterName: string, userName: string, protocol: string, + origin: string, } } diff --git a/gen/proto/js/prehog/v1alpha/connect_pb.js b/gen/proto/js/prehog/v1alpha/connect_pb.js index 31114b8c9c074..7e365b0ad48cc 100644 --- a/gen/proto/js/prehog/v1alpha/connect_pb.js +++ b/gen/proto/js/prehog/v1alpha/connect_pb.js @@ -560,7 +560,8 @@ proto.prehog.v1alpha.ConnectProtocolUseEvent.toObject = function(includeInstance var f, obj = { clusterName: jspb.Message.getFieldWithDefault(msg, 1, ""), userName: jspb.Message.getFieldWithDefault(msg, 2, ""), - protocol: jspb.Message.getFieldWithDefault(msg, 3, "") + protocol: jspb.Message.getFieldWithDefault(msg, 3, ""), + origin: jspb.Message.getFieldWithDefault(msg, 4, "") }; if (includeInstance) { @@ -609,6 +610,10 @@ proto.prehog.v1alpha.ConnectProtocolUseEvent.deserializeBinaryFromReader = funct var value = /** @type {string} */ (reader.readString()); msg.setProtocol(value); break; + case 4: + var value = /** @type {string} */ (reader.readString()); + msg.setOrigin(value); + break; default: reader.skipField(); break; @@ -659,6 +664,13 @@ proto.prehog.v1alpha.ConnectProtocolUseEvent.serializeBinaryToWriter = function( f ); } + f = message.getOrigin(); + if (f.length > 0) { + writer.writeString( + 4, + f + ); + } }; @@ -716,6 +728,24 @@ proto.prehog.v1alpha.ConnectProtocolUseEvent.prototype.setProtocol = function(va }; +/** + * optional string origin = 4; + * @return {string} + */ +proto.prehog.v1alpha.ConnectProtocolUseEvent.prototype.getOrigin = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 4, "")); +}; + + +/** + * @param {string} value + * @return {!proto.prehog.v1alpha.ConnectProtocolUseEvent} returns this + */ +proto.prehog.v1alpha.ConnectProtocolUseEvent.prototype.setOrigin = function(value) { + return jspb.Message.setProto3StringField(this, 4, value); +}; + + diff --git a/gen/proto/js/prehog/v1alpha/teleport_pb.d.ts b/gen/proto/js/prehog/v1alpha/teleport_pb.d.ts index 17d0992dc9c93..870e01ea2411f 100644 --- a/gen/proto/js/prehog/v1alpha/teleport_pb.d.ts +++ b/gen/proto/js/prehog/v1alpha/teleport_pb.d.ts @@ -391,6 +391,9 @@ export class DiscoverMetadata extends jspb.Message { getUserName(): string; setUserName(value: string): DiscoverMetadata; + getSso(): boolean; + setSso(value: boolean): DiscoverMetadata; + serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): DiscoverMetadata.AsObject; @@ -406,6 +409,7 @@ export namespace DiscoverMetadata { export type AsObject = { id: string, userName: string, + sso: boolean, } } @@ -1608,6 +1612,25 @@ export enum DiscoverResource { DISCOVER_RESOURCE_DATABASE_POSTGRES_GCP = 15, DISCOVER_RESOURCE_DATABASE_MYSQL_GCP = 16, DISCOVER_RESOURCE_DATABASE_SQLSERVER_GCP = 17, + DISCOVER_RESOURCE_DATABASE_POSTGRES_REDSHIFT_SERVERLESS = 18, + DISCOVER_RESOURCE_DATABASE_POSTGRES_AZURE = 19, + DISCOVER_RESOURCE_DATABASE_DYNAMODB = 20, + DISCOVER_RESOURCE_DATABASE_CASSANDRA_KEYSPACES = 21, + DISCOVER_RESOURCE_DATABASE_CASSANDRA_SELF_HOSTED = 22, + DISCOVER_RESOURCE_DATABASE_ELASTICSEARCH_SELF_HOSTED = 23, + DISCOVER_RESOURCE_DATABASE_REDIS_ELASTICACHE = 24, + DISCOVER_RESOURCE_DATABASE_REDIS_MEMORYDB = 25, + DISCOVER_RESOURCE_DATABASE_REDIS_AZURE_CACHE = 26, + DISCOVER_RESOURCE_DATABASE_REDIS_CLUSTER_SELF_HOSTED = 27, + DISCOVER_RESOURCE_DATABASE_MYSQL_AZURE = 28, + DISCOVER_RESOURCE_DATABASE_SQLSERVER_AZURE = 29, + DISCOVER_RESOURCE_DATABASE_SQLSERVER_MICROSOFT = 30, + DISCOVER_RESOURCE_DATABASE_COCKROACHDB_SELF_HOSTED = 31, + DISCOVER_RESOURCE_DATABASE_MONGODB_ATLAS = 32, + DISCOVER_RESOURCE_DATABASE_SNOWFLAKE = 33, + DISCOVER_RESOURCE_DOC_DATABASE_RDS_PROXY = 34, + DISCOVER_RESOURCE_DOC_DATABASE_HIGH_AVAILABILITY = 35, + DISCOVER_RESOURCE_DOC_DATABASE_DYNAMIC_REGISTRATION = 36, } export enum DiscoverStatus { diff --git a/gen/proto/js/prehog/v1alpha/teleport_pb.js b/gen/proto/js/prehog/v1alpha/teleport_pb.js index 621a24b44b877..2c6c36ead56fa 100644 --- a/gen/proto/js/prehog/v1alpha/teleport_pb.js +++ b/gen/proto/js/prehog/v1alpha/teleport_pb.js @@ -3439,7 +3439,8 @@ proto.prehog.v1alpha.DiscoverMetadata.prototype.toObject = function(opt_includeI proto.prehog.v1alpha.DiscoverMetadata.toObject = function(includeInstance, msg) { var f, obj = { id: jspb.Message.getFieldWithDefault(msg, 1, ""), - userName: jspb.Message.getFieldWithDefault(msg, 2, "") + userName: jspb.Message.getFieldWithDefault(msg, 2, ""), + sso: jspb.Message.getBooleanFieldWithDefault(msg, 3, false) }; if (includeInstance) { @@ -3484,6 +3485,10 @@ proto.prehog.v1alpha.DiscoverMetadata.deserializeBinaryFromReader = function(msg var value = /** @type {string} */ (reader.readString()); msg.setUserName(value); break; + case 3: + var value = /** @type {boolean} */ (reader.readBool()); + msg.setSso(value); + break; default: reader.skipField(); break; @@ -3527,6 +3532,13 @@ proto.prehog.v1alpha.DiscoverMetadata.serializeBinaryToWriter = function(message f ); } + f = message.getSso(); + if (f) { + writer.writeBool( + 3, + f + ); + } }; @@ -3566,6 +3578,24 @@ proto.prehog.v1alpha.DiscoverMetadata.prototype.setUserName = function(value) { }; +/** + * optional bool sso = 3; + * @return {boolean} + */ +proto.prehog.v1alpha.DiscoverMetadata.prototype.getSso = function() { + return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 3, false)); +}; + + +/** + * @param {boolean} value + * @return {!proto.prehog.v1alpha.DiscoverMetadata} returns this + */ +proto.prehog.v1alpha.DiscoverMetadata.prototype.setSso = function(value) { + return jspb.Message.setProto3BooleanField(this, 3, value); +}; + + @@ -10991,7 +11021,26 @@ proto.prehog.v1alpha.DiscoverResource = { DISCOVER_RESOURCE_DATABASE_REDIS_SELF_HOSTED: 14, DISCOVER_RESOURCE_DATABASE_POSTGRES_GCP: 15, DISCOVER_RESOURCE_DATABASE_MYSQL_GCP: 16, - DISCOVER_RESOURCE_DATABASE_SQLSERVER_GCP: 17 + DISCOVER_RESOURCE_DATABASE_SQLSERVER_GCP: 17, + DISCOVER_RESOURCE_DATABASE_POSTGRES_REDSHIFT_SERVERLESS: 18, + DISCOVER_RESOURCE_DATABASE_POSTGRES_AZURE: 19, + DISCOVER_RESOURCE_DATABASE_DYNAMODB: 20, + DISCOVER_RESOURCE_DATABASE_CASSANDRA_KEYSPACES: 21, + DISCOVER_RESOURCE_DATABASE_CASSANDRA_SELF_HOSTED: 22, + DISCOVER_RESOURCE_DATABASE_ELASTICSEARCH_SELF_HOSTED: 23, + DISCOVER_RESOURCE_DATABASE_REDIS_ELASTICACHE: 24, + DISCOVER_RESOURCE_DATABASE_REDIS_MEMORYDB: 25, + DISCOVER_RESOURCE_DATABASE_REDIS_AZURE_CACHE: 26, + DISCOVER_RESOURCE_DATABASE_REDIS_CLUSTER_SELF_HOSTED: 27, + DISCOVER_RESOURCE_DATABASE_MYSQL_AZURE: 28, + DISCOVER_RESOURCE_DATABASE_SQLSERVER_AZURE: 29, + DISCOVER_RESOURCE_DATABASE_SQLSERVER_MICROSOFT: 30, + DISCOVER_RESOURCE_DATABASE_COCKROACHDB_SELF_HOSTED: 31, + DISCOVER_RESOURCE_DATABASE_MONGODB_ATLAS: 32, + DISCOVER_RESOURCE_DATABASE_SNOWFLAKE: 33, + DISCOVER_RESOURCE_DOC_DATABASE_RDS_PROXY: 34, + DISCOVER_RESOURCE_DOC_DATABASE_HIGH_AVAILABILITY: 35, + DISCOVER_RESOURCE_DOC_DATABASE_DYNAMIC_REGISTRATION: 36 }; /** diff --git a/go.mod b/go.mod index 71ea2856b3745..c09eb259d0c88 100644 --- a/go.mod +++ b/go.mod @@ -12,14 +12,14 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.2 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v3 v3.0.1 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v2 v2.4.0 - github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.0.0 - github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.0.0 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.1.0 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.1.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.1.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresqlflexibleservers v1.1.0 - github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/redis/armredis/v2 v2.1.0 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/redis/armredis/v2 v2.2.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/redisenterprise/armredisenterprise v1.0.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/sql/armsql v1.1.0 - github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/subscription/armsubscription v1.0.0 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/subscription/armsubscription v1.1.0 github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 github.com/HdrHistogram/hdrhistogram-go v1.1.2 github.com/Microsoft/go-winio v0.6.0 @@ -27,12 +27,12 @@ require ( github.com/alicebob/miniredis/v2 v2.30.1 github.com/aquasecurity/libbpfgo v0.4.5-libbpf-1.0.1 github.com/armon/go-radix v1.0.0 - github.com/aws/aws-sdk-go v1.44.231 + github.com/aws/aws-sdk-go v1.44.234 github.com/aws/aws-sdk-go-v2 v1.17.7 github.com/aws/aws-sdk-go-v2/config v1.18.19 github.com/aws/aws-sdk-go-v2/credentials v1.13.18 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.1 - github.com/aws/aws-sdk-go-v2/service/ec2 v1.91.0 + github.com/aws/aws-sdk-go-v2/service/ec2 v1.92.1 github.com/aws/aws-sdk-go-v2/service/sts v1.18.7 github.com/aws/aws-sigv4-auth-cassandra-gocql-driver-plugin v0.0.0-20220331165046-e4d000c0d6a6 github.com/beevik/etree v1.1.0 @@ -43,7 +43,7 @@ require ( github.com/crewjam/saml v0.4.13 github.com/datastax/go-cassandra-native-protocol v0.0.0-20220706104457-5e8aad05cf90 github.com/dustin/go-humanize v1.0.1 - github.com/elastic/go-elasticsearch/v8 v8.6.0 + github.com/elastic/go-elasticsearch/v8 v8.7.0 github.com/flynn/hid v0.0.0-20190502022136-f1b9b6cc019a github.com/flynn/u2f v0.0.0-20180613185708-15554eb68e5d github.com/fsouza/fake-gcs-server v1.44.1 @@ -51,7 +51,7 @@ require ( github.com/ghodss/yaml v1.0.0 github.com/gizak/termui/v3 v3.1.0 github.com/go-ldap/ldap/v3 v3.4.4 - github.com/go-logr/logr v1.2.3 + github.com/go-logr/logr v1.2.4 github.com/go-mysql-org/go-mysql v1.5.0 // replaced github.com/go-piv/piv-go v1.11.0 github.com/go-redis/redis/v9 v9.0.0-rc.1 // replaced @@ -131,7 +131,7 @@ require ( go.opentelemetry.io/otel/trace v1.14.0 go.opentelemetry.io/proto/otlp v0.19.0 golang.org/x/crypto v0.7.0 // replaced - golang.org/x/exp v0.0.0-20230116083435-1de6713980de + golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb golang.org/x/mod v0.9.0 golang.org/x/net v0.8.0 golang.org/x/oauth2 v0.6.0 @@ -162,6 +162,7 @@ require ( sigs.k8s.io/controller-runtime v0.14.6 sigs.k8s.io/controller-tools v0.11.3 sigs.k8s.io/yaml v1.3.0 + software.sslmate.com/src/go-pkcs12 v0.2.0 ) // Indirect mailgun dependencies. @@ -223,7 +224,7 @@ require ( github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/docker/distribution v2.8.1+incompatible // indirect github.com/dvsekhvalnov/jose2go v1.5.0 // indirect - github.com/elastic/elastic-transport-go/v8 v8.1.0 // indirect + github.com/elastic/elastic-transport-go/v8 v8.2.0 // indirect github.com/emicklei/go-restful/v3 v3.9.0 // indirect github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect @@ -334,7 +335,7 @@ require ( github.com/spf13/cobra v1.6.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/thales-e-security/pool v0.0.2 // indirect - github.com/tidwall/pretty v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.1.1 // indirect @@ -378,8 +379,8 @@ replace ( github.com/gravitational/teleport/api => ./api github.com/julienschmidt/httprouter => github.com/gravitational/httprouter v1.3.1-0.20220408074523-c876c5e705a5 github.com/keys-pub/go-libfido2 => github.com/gravitational/go-libfido2 v1.5.3-0.20230202181331-c71192ef1c8a - github.com/microsoft/go-mssqldb => github.com/gravitational/go-mssqldb v0.11.1-0.20230209180904-0f00ae61fb60 - github.com/pkg/sftp => github.com/gravitational/sftp v1.13.6-0.20220927202521-0e74d42f8055 + github.com/microsoft/go-mssqldb => github.com/gravitational/go-mssqldb v0.11.1-0.20230331180905-0f76f1751cd3 + github.com/pkg/sftp => github.com/gravitational/sftp v1.13.6-0.20230328150159-dfe4e0d94419 github.com/sirupsen/logrus => github.com/gravitational/logrus v1.4.4-0.20210817004754-047e20245621 github.com/vulcand/predicate => github.com/gravitational/predicate v1.3.0 // Use our internal crypto fork, to work around the issue with OpenSSH <= 7.6 mentioned here: https://github.com/golang/go/issues/53391 diff --git a/go.sum b/go.sum index 4b1e0edb6281a..cd3277265421d 100644 --- a/go.sum +++ b/go.sum @@ -77,24 +77,24 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v3 v3.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v2 v2.4.0 h1:1u/K2BFv0MwkG6he8RYuUcbbeK22rkoZbg4lKa/msZU= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v2 v2.4.0/go.mod h1:U5gpsREQZE6SLk1t/cFfc1eMhYAlYpEzvaYXuDfefy8= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2 h1:mLY+pNLjCUeKhgnAJWAKhEUQM+RJQo2H1fuGSw1Ky1E= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.0.0 h1:3AMzl6OaajsEhmlQJBxZviy1jq5HNrQSRHlS87acXb4= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.0.0/go.mod h1:yFGqqJ4W/nOViqHDfuwmjyJtZXLmmMoHN0DNPCigKUE= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.0.0 h1:6h+fZ0pCuAHGa8EkXibCEAN4aT7y4zWzv8XZbU6LFro= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.0.0/go.mod h1:2cZdDuCHZPvz8rrOIJAifpcnaW59TO0C/PFhpYgWcjA= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.1.0 h1:4w2eD8N0/T9Ju7NcV+ssLrA1QCq2MHFHrBduV3DkUKU= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.1.0/go.mod h1:R74AaoH5TpsNGNfyMzZEmf326Wdn5UUez3+hhx28T7U= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.1.0 h1:b3HXcgghJxdrth4y8wZ7p8v4J/1HBkL0CpAv5t9K3QY= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.1.0/go.mod h1:X+dUuMBFMvqsmSkQ2LcCNxq14KFkZ6V/3INYs3suNYY= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v1.0.0 h1:nBy98uKOIfun5z6wx6jwWLrULcM0+cjBalBFZlEZ7CA= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.1.0 h1:H1b0KV9BVHK/GCffGzEYoH9vCjuHNe0Z0IEnSxQt0N0= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.1.0/go.mod h1:XuVagOJ3SVFrcbWgteWmWc9688zWn16w4sVkxdIu7sw= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresqlflexibleservers v1.1.0 h1:HzqcSJWx32XQdr8KtxAu/SZJj0PqDo9tKf2YGPdynV0= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresqlflexibleservers v1.1.0/go.mod h1:nKcJObAisSPDrO9lMuuCBoYY7Ki7ADt8p6XmBhpKNTk= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/redis/armredis/v2 v2.1.0 h1:np26UAXwK6IGy0y1WONpykT73Zy2zjsYQ+rGp2y3ZL0= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/redis/armredis/v2 v2.1.0/go.mod h1:9cvVd/AvgCW3ki18aWkDiqmR0u1DTmZ2fQoHoH3gZbA= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/redis/armredis/v2 v2.2.0 h1:vbseS1qYczYV5z3gpJostn7GYw1uNCch9sMddpWaPEc= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/redis/armredis/v2 v2.2.0/go.mod h1:FMVQhV2nfxsI9cDUBqn/rWfN5y1KxwZ/+q1Bl1oqko0= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/redisenterprise/armredisenterprise v1.0.0 h1:ZyZb7h9t0hw6pNdCJ1bEwoRsVJ+gHngUlexPfSfj3ZA= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/redisenterprise/armredisenterprise v1.0.0/go.mod h1:KOdfFo3kIa8BlSMMNTslBV7MTVyVnrKgxhn0rjlMNxM= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0 h1:ECsQtyERDVz3NP3kvDOTLvbQhqWp/x9EsGKtb4ogUr8= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/sql/armsql v1.1.0 h1:G2MvNS98bjXD7Vks+psbTU/uBiBH7gicij12Xc8q6lM= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/sql/armsql v1.1.0/go.mod h1:f/IvRlQ/eFP31UXVUwh3BzTOOC2cEo6/u+7g9+KTzPk= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/subscription/armsubscription v1.0.0 h1:vsovXlTyKHZXnqzQyt7QMVkwpJBDkHchQL53qXaGBRY= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/subscription/armsubscription v1.0.0/go.mod h1:UZy1vHcRdEymNP1d6fTrvYHpSdkXoUdowfrvffcQOOU= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/subscription/armsubscription v1.1.0 h1:pYhaMoTHP/zYIJGDA1sWsfyTDjdglaoYjIFMOEcL+/U= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/subscription/armsubscription v1.1.0/go.mod h1:iLq8GwpQhj09gpI4EdELwifR9kHrb/Q0LThq6iQq9yY= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0 h1:u/LLAOFgsMv7HmNL4Qufg58y+qElGOt5qv0z1mURkRY= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0/go.mod h1:2e8rMJtl2+2j+HXbTBwnyGpm5Nou7KhvSfxOq8JpTag= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= @@ -174,8 +174,8 @@ github.com/aws/aws-sdk-go v1.17.4/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.44.231 h1:wH/ihcZzBv8F443PyRoUogWnEdDp1KYtSew7ji9LNIY= -github.com/aws/aws-sdk-go v1.44.231/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.234 h1:8YbQ5AhpgV/cC7jYX8qS34Am/vcn2ZoIFJ1qIgwOL+0= +github.com/aws/aws-sdk-go v1.44.234/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/aws/aws-sdk-go-v2 v1.17.3/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= github.com/aws/aws-sdk-go-v2 v1.17.7 h1:CLSjnhJSTSogvqUGhIC6LqFKATMRexcxLZ0i/Nzk9Eg= @@ -204,8 +204,8 @@ github.com/aws/aws-sdk-go-v2/internal/ini v1.3.32 h1:p5luUImdIqywn6JpQsW3tq5GNOx github.com/aws/aws-sdk-go-v2/internal/ini v1.3.32/go.mod h1:XGhIBZDEgfqmFIugclZ6FU7v75nHhBDtzuB4xB/tEi4= github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.18 h1:H/mF2LNWwX00lD6FlYfKpLLZgUW7oIzCBkig78x4Xok= github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.18/go.mod h1:T2Ku+STrYQ1zIkL1wMvj8P3wWQaaCMKNdz70MT2FLfE= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.91.0 h1:b4Qme29Ml9nl3QBxWobytF5UxlfmYUJI7+u1FTqjehs= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.91.0/go.mod h1:ZZLfkd1Y7fjXujjMg1CFqNmaTl314eCbShlHQO7VTWo= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.92.1 h1:xn5CI639mnWvdiweqoRx/H221Ia9Asx9XxfIRhe0MPo= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.92.1/go.mod h1:ZZLfkd1Y7fjXujjMg1CFqNmaTl314eCbShlHQO7VTWo= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 h1:y2+VQzC6Zh2ojtV2LoC0MNwHWc6qXv/j2vrQtlftkdA= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11/go.mod h1:iV4q2hsqtNECrfmlXyord9u4zyuFEJX9eLgLpSPzWA8= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.22 h1:kv5vRAl00tozRxSnI0IszPWGXsJOyA7hmEUHFYqsyvw= @@ -345,11 +345,10 @@ github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5m github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= -github.com/elastic/elastic-transport-go/v8 v8.0.0-20211216131617-bbee439d559c/go.mod h1:87Tcz8IVNe6rVSLdBux1o/PEItLtyabHU3naC7IoqKI= -github.com/elastic/elastic-transport-go/v8 v8.1.0 h1:NeqEz1ty4RQz+TVbUrpSU7pZ48XkzGWQj02k5koahIE= -github.com/elastic/elastic-transport-go/v8 v8.1.0/go.mod h1:87Tcz8IVNe6rVSLdBux1o/PEItLtyabHU3naC7IoqKI= -github.com/elastic/go-elasticsearch/v8 v8.6.0 h1:xMaSe8jIh7NHzmNo9YBkewmaD2Pr+tX+zLkXxhieny4= -github.com/elastic/go-elasticsearch/v8 v8.6.0/go.mod h1:Usvydt+x0dv9a1TzEUaovqbJor8rmOHy5dSmPeMAE2k= +github.com/elastic/elastic-transport-go/v8 v8.2.0 h1:hkK5IIs/15mpSXzd5THWVlWTKJyMw6cbCWM3T/B2S5E= +github.com/elastic/elastic-transport-go/v8 v8.2.0/go.mod h1:87Tcz8IVNe6rVSLdBux1o/PEItLtyabHU3naC7IoqKI= +github.com/elastic/go-elasticsearch/v8 v8.7.0 h1:ZvbT1YHppBC0QxGnMmaDUxoDa26clwhRaB3Gp5E3UcY= +github.com/elastic/go-elasticsearch/v8 v8.7.0/go.mod h1:lVb8SvJV8McVkdswpL8YR5QKIkhlWaoSq60YpHilOLI= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= @@ -420,8 +419,8 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= @@ -623,8 +622,8 @@ github.com/gravitational/go-cassandra-native-protocol v0.0.0-20221005103706-b9e6 github.com/gravitational/go-cassandra-native-protocol v0.0.0-20221005103706-b9e66c056e90/go.mod h1:6FzirJfdffakAVqmHjwVfFkpru/gNbIazUOK5rIhndc= github.com/gravitational/go-libfido2 v1.5.3-0.20230202181331-c71192ef1c8a h1:Wudq53GAiVk1Z+4A1tFzyvidhA6X7rGDb5JKGU9NV0c= github.com/gravitational/go-libfido2 v1.5.3-0.20230202181331-c71192ef1c8a/go.mod h1:92J9LtSBl0UyUWljElJpTbMMNhC6VeY8dshsu40qjjo= -github.com/gravitational/go-mssqldb v0.11.1-0.20230209180904-0f00ae61fb60 h1:scheLo0b1jteZf9mfgjCgi+0VFGK+3GVENBQQ1DDmfE= -github.com/gravitational/go-mssqldb v0.11.1-0.20230209180904-0f00ae61fb60/go.mod h1:+8BKOZF/H/PTp3h+jsAcA4z5LVmG4vATYcuOs3CfY6k= +github.com/gravitational/go-mssqldb v0.11.1-0.20230331180905-0f76f1751cd3 h1:3JGTacvAeV5tIC4/9XsdLC2K5K7FWaXxIwpW4t+dGH0= +github.com/gravitational/go-mssqldb v0.11.1-0.20230331180905-0f76f1751cd3/go.mod h1:LbRWqr72fXehxAGLXO8nDNQAi4gthRz4j7f8L2LS0XM= github.com/gravitational/go-mysql v1.5.0-teleport.1 h1:EyFryeiTYyP6KslLVp0Q5QTKwtUG5RioVrTIoP4pOuI= github.com/gravitational/go-mysql v1.5.0-teleport.1/go.mod h1:GX0clmylJLdZEYAojPCDTCvwZxbTBrke93dV55715u0= github.com/gravitational/go-oidc v0.1.0 h1:qZkEZCuD7DfPbpFfewvRmDvq82XoDsmZtqgpQ0Ep2i4= @@ -647,8 +646,8 @@ github.com/gravitational/redis/v9 v9.0.0-teleport.3 h1:Eg/j3jiNUZ558KDXOqzF682EF github.com/gravitational/redis/v9 v9.0.0-teleport.3/go.mod h1:8et+z03j0l8N+DvsVnclzjf3Dl/pFHgRk+2Ct1qw66A= github.com/gravitational/roundtrip v1.0.2 h1:eOCY0NEKKaB0ksJmvhO6lPMFz1pIIef+vyPBTBROQ5c= github.com/gravitational/roundtrip v1.0.2/go.mod h1:fuI1booM2hLRA/B/m5MRAPOU6mBZNYcNycono2UuTw0= -github.com/gravitational/sftp v1.13.6-0.20220927202521-0e74d42f8055 h1:iUQqKNaW1lywWSVZkeCQX0vygPfJ63UGx6Ng94e2t1o= -github.com/gravitational/sftp v1.13.6-0.20220927202521-0e74d42f8055/go.mod h1:wHDZ0IZX6JcBYRK1TH9bcVq8G7TLpVHYIGJRFnmPfxg= +github.com/gravitational/sftp v1.13.6-0.20230328150159-dfe4e0d94419 h1:520SBVjHrTsfTPs7Udp+aXJV4WXOlKexKoIyiN0BJys= +github.com/gravitational/sftp v1.13.6-0.20230328150159-dfe4e0d94419/go.mod h1:wHDZ0IZX6JcBYRK1TH9bcVq8G7TLpVHYIGJRFnmPfxg= github.com/gravitational/trace v1.2.1 h1:Iaf43aqbKV5H8bdiRs1qByjEHgAfADJ0lt0JwRyu+q8= github.com/gravitational/trace v1.2.1/go.mod h1:n0ijrq6psJY0sOI/NzLp+xdd8xl79jjwzVOFHDY6+kQ= github.com/gravitational/ttlmap v0.0.0-20171116003245-91fd36b9004c h1:C2iWDiod8vQ3YnOiCdMP9qYeg2UifQ8KSk36r0NswSE= @@ -1195,8 +1194,8 @@ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69 github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg= github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= -github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20200427203606-3cfed13b9966/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -1323,8 +1322,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= -golang.org/x/exp v0.0.0-20230116083435-1de6713980de h1:DBWn//IJw30uYCgERoxCg84hWtA97F4wMiKOIh00Uf0= -golang.org/x/exp v0.0.0-20230116083435-1de6713980de/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb h1:PaBZQdo+iSDyHT053FjUCgZQ/9uqVwPOcl7KSWhKn6w= +golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -1490,7 +1489,6 @@ golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1832,4 +1830,6 @@ sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ih sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +software.sslmate.com/src/go-pkcs12 v0.2.0 h1:nlFkj7bTysH6VkC4fGphtjXRbezREPgrHuJG20hBGPE= +software.sslmate.com/src/go-pkcs12 v0.2.0/go.mod h1:23rNcYsMabIc1otwLpTkCCPwUq6kQsTyowttG/as0kQ= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/integration/integration_test.go b/integration/integration_test.go index b33ab0d56bb16..06a2330311130 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -212,25 +212,30 @@ func testDifferentPinnedIP(t *testing.T, suite *integrationTestSuite) { site := teleInstance.GetSiteAPI(helpers.Site) require.NotNil(t, site) + accessDenied := func(t require.TestingT, err error, i ...interface{}) { + require.Error(t, err, i...) + require.True(t, trace.IsAccessDenied(err), "expected an access denied error, got: %v", err) + } + testCases := []struct { - desc string - ip string - wantErr string + desc string + ip string + errAssertion require.ErrorAssertionFunc }{ { - desc: "Correct connecting IP", - ip: "127.0.0.1", - wantErr: "", + desc: "Correct connecting IP", + ip: "127.0.0.1", + errAssertion: require.NoError, }, { - desc: "Wrong connecting IPv4", - ip: "1.2.3.4", - wantErr: "ssh: unable to authenticate", + desc: "Wrong connecting IPv4", + ip: "1.2.3.4", + errAssertion: accessDenied, }, { - desc: "Wrong connecting IPv6", - ip: "1843:4545::12", - wantErr: "ssh: unable to authenticate", + desc: "Wrong connecting IPv6", + ip: "1843:4545::12", + errAssertion: accessDenied, }, } @@ -243,15 +248,9 @@ func testDifferentPinnedIP(t *testing.T, suite *integrationTestSuite) { SourceIP: test.ip, }) require.NoError(t, err) - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - err = cl.SSH(ctx, []string{"echo hi"}, false) - if test.wantErr != "" { - require.Error(t, err) - require.Contains(t, err.Error(), "ssh: unable to authenticate") - } else { - require.NoError(t, err) - } + test.errAssertion(t, cl.SSH(ctx, []string{"echo hi"}, false)) }) } } @@ -1279,6 +1278,29 @@ func testEscapeSequenceNoTrigger(t *testing.T, terminal *Terminal, sess <-chan e } } +type localAddr struct { + mu sync.Mutex + addr net.Addr +} + +func (a *localAddr) set(addr net.Addr) { + a.mu.Lock() + defer a.mu.Unlock() + + a.addr = addr +} + +func (a *localAddr) get() net.Addr { + a.mu.Lock() + defer a.mu.Unlock() + + if a.addr == nil { + return &utils.NetAddr{} + } + + return a.addr +} + // testIPPropagation makes sure that we can correctly propagate initial client IP observed by proxy. func testIPPropagation(t *testing.T, suite *integrationTestSuite) { tr := utils.NewTracer(utils.ThisFunction()).Start() @@ -1341,7 +1363,7 @@ func testIPPropagation(t *testing.T, suite *integrationTestSuite) { wg.Wait() } - testNodeConnection := func(t *testing.T, instance *helpers.TeleInstance, clusterName, nodeName string) { + testSSHNodeConnection := func(t *testing.T, instance *helpers.TeleInstance, clusterName, nodeName string) { person := NewTerminal(250) ctx := context.Background() @@ -1355,23 +1377,112 @@ func testIPPropagation(t *testing.T, suite *integrationTestSuite) { tc.Stdout = person tc.Stdin = person - pc, err := tc.ConnectToProxy(ctx) + clt, err := tc.ConnectToProxy(ctx) + require.NoError(t, err) + defer clt.Close() + + nodeClient, err := clt.ConnectToNode( + ctx, + client.NodeDetails{Addr: nodeName, Namespace: tc.Namespace, Cluster: tc.SiteName}, + tc.Config.HostLogin, + sshutils.ClusterDetails{}, + ) + require.NoError(t, err) + defer nodeClient.Close() + + err = nodeClient.RunCommand(ctx, []string{"echo $SSH_CLIENT"}) + require.NoError(t, err) + + require.Eventually(t, func() bool { + return getRemoteAddrString(person.Output(1000)) == clt.Client.LocalAddr().String() + }, time.Millisecond*100, time.Millisecond*10, "client IP:port that node sees doesn't match to real one") + } + + testGRPCNodeConnection := func(t *testing.T, instance *helpers.TeleInstance, clusterName, nodeName string) { + person := NewTerminal(250) + ctx := context.Background() + + tc, err := instance.NewClient(helpers.ClientConfig{ + Login: suite.Me.Username, + Cluster: clusterName, + Host: nodeName, + }) require.NoError(t, err) - defer pc.Close() - tc.Config.MockConnectToProxy = func(ctx context.Context) (*client.ProxyClient, error) { - return pc, nil + tc.Stdout = person + tc.Stdin = person + + local := &localAddr{} + + tc.Config.DialOpts = []grpc.DialOption{ + grpc.WithContextDialer(func(ctx context.Context, s string) (net.Conn, error) { + d := net.Dialer{Timeout: defaults.DefaultIOTimeout} + conn, err := d.DialContext(ctx, "tcp", s) + if err != nil { + return nil, trace.Wrap(err) + } + + local.set(conn.LocalAddr()) + return conn, nil + }), } - err = tc.SSH(ctx, []string{"echo $SSH_CLIENT"}, false) + clt, err := tc.ConnectToCluster(ctx) + require.NoError(t, err) + defer clt.Close() + + nodeClient, err := tc.ConnectToNode( + ctx, + clt, + client.NodeDetails{Addr: nodeName, Namespace: tc.Namespace, Cluster: clt.ClusterName()}, + tc.Config.HostLogin, + ) + require.NoError(t, err) + defer nodeClient.Close() + + err = nodeClient.RunCommand(ctx, []string{"echo $SSH_CLIENT"}) require.NoError(t, err) require.Eventually(t, func() bool { - return pc.Client.LocalAddr().String() == getRemoteAddrString(person.Output(1000)) + return getRemoteAddrString(person.Output(1000)) == local.get().String() }, time.Millisecond*100, time.Millisecond*10, "client IP:port that node sees doesn't match to real one") } - testAuthConnection := func(t *testing.T, instance *helpers.TeleInstance, clusterName string) { + testGRPCAuthConnection := func(t *testing.T, instance *helpers.TeleInstance, clusterName string) { + ctx := context.Background() + + tc, err := instance.NewClient(helpers.ClientConfig{ + Login: suite.Me.Username, + Cluster: clusterName, + Host: Host, + }) + require.NoError(t, err) + + local := &localAddr{} + + tc.Config.DialOpts = []grpc.DialOption{ + grpc.WithContextDialer(func(ctx context.Context, s string) (net.Conn, error) { + d := net.Dialer{Timeout: defaults.DefaultIOTimeout} + conn, err := d.DialContext(ctx, "tcp", s) + if err != nil { + return nil, trace.Wrap(err) + } + + local.set(conn.LocalAddr()) + return conn, nil + }), + } + + clt, err := tc.ConnectToCluster(ctx) + require.NoError(t, err) + defer clt.Close() + + pingResp, err := clt.AuthClient.Ping(ctx) + require.NoError(t, err) + require.Equal(t, local.get().String(), pingResp.RemoteAddr, "client IP:port that auth server sees doesn't match the real one") + } + + testSSHAuthConnection := func(t *testing.T, instance *helpers.TeleInstance, clusterName string) { ctx := context.Background() tc, err := instance.NewClient(helpers.ClientConfig{ @@ -1381,17 +1492,17 @@ func testIPPropagation(t *testing.T, suite *integrationTestSuite) { }) require.NoError(t, err) - pc, err := tc.ConnectToProxy(ctx) + clt, err := tc.ConnectToProxy(ctx) require.NoError(t, err) - defer pc.Close() + defer clt.Close() - site, err := pc.ConnectToCluster(ctx, clusterName) + site, err := clt.ConnectToCluster(ctx, clusterName) require.NoError(t, err) pingResp, err := site.Ping(ctx) require.NoError(t, err) - expected := pc.Client.LocalAddr().String() + expected := clt.Client.LocalAddr().String() require.Equal(t, expected, pingResp.RemoteAddr, "client IP:port that auth server sees doesn't match the real one") } _, root, leaf := createTrustedClusterPair(t, suite, startNodes) @@ -1407,32 +1518,49 @@ func testIPPropagation(t *testing.T, suite *integrationTestSuite) { testNodeCases := []struct { instance *helpers.TeleInstance clusterName string - nodeNme string + nodeAddr string }{ - {instance: root, clusterName: "root-test", nodeNme: "root-zero"}, - {instance: root, clusterName: "root-test", nodeNme: "root-one"}, - {instance: root, clusterName: "root-test", nodeNme: "root-two"}, - {instance: root, clusterName: "leaf-test", nodeNme: "leaf-zero"}, - {instance: root, clusterName: "leaf-test", nodeNme: "leaf-one"}, - {instance: root, clusterName: "leaf-test", nodeNme: "leaf-two"}, - {instance: leaf, clusterName: "leaf-test", nodeNme: "leaf-zero"}, - {instance: leaf, clusterName: "leaf-test", nodeNme: "leaf-one"}, - {instance: leaf, clusterName: "leaf-test", nodeNme: "leaf-two"}, - } - - for _, test := range testAuthCases { - t.Run(fmt.Sprintf("Auth test source cluster %q -> target cluster %q", - test.instance.Secrets.SiteName, test.clusterName), func(t *testing.T) { - testAuthConnection(t, test.instance, test.clusterName) - }) - } - for _, test := range testNodeCases { - test := test - t.Run(fmt.Sprintf("Node test, node name %q source cluster %q -> target cluster %q", - test.nodeNme, test.instance.Secrets.SiteName, test.clusterName), func(t *testing.T) { - testNodeConnection(t, test.instance, test.clusterName, test.nodeNme) - }) - } + {instance: root, clusterName: "root-test", nodeAddr: "root-zero:0"}, + {instance: root, clusterName: "root-test", nodeAddr: "root-one:0"}, + {instance: root, clusterName: "root-test", nodeAddr: "root-two:0"}, + {instance: root, clusterName: "leaf-test", nodeAddr: "leaf-zero:0"}, + {instance: root, clusterName: "leaf-test", nodeAddr: "leaf-one:0"}, + {instance: root, clusterName: "leaf-test", nodeAddr: "leaf-two:0"}, + {instance: leaf, clusterName: "leaf-test", nodeAddr: "leaf-zero:0"}, + {instance: leaf, clusterName: "leaf-test", nodeAddr: "leaf-one:0"}, + {instance: leaf, clusterName: "leaf-test", nodeAddr: "leaf-two:0"}, + } + + t.Run("Auth Connections", func(t *testing.T) { + for _, test := range testAuthCases { + t.Run(fmt.Sprintf("source cluster=%q target cluster=%q", + test.instance.Secrets.SiteName, test.clusterName), func(t *testing.T) { + t.Run("ssh connection", func(t *testing.T) { + testSSHAuthConnection(t, test.instance, test.clusterName) + }) + + t.Run("grpc connection", func(t *testing.T) { + testGRPCAuthConnection(t, test.instance, test.clusterName) + }) + }) + } + }) + + t.Run("Host Connections", func(t *testing.T) { + for _, test := range testNodeCases { + test := test + t.Run(fmt.Sprintf("target=%q source cluster=%q target cluster=%q", + test.nodeAddr, test.instance.Secrets.SiteName, test.clusterName), func(t *testing.T) { + t.Run("ssh connection", func(t *testing.T) { + testSSHNodeConnection(t, test.instance, test.clusterName, test.nodeAddr) + }) + + t.Run("grpc connection", func(t *testing.T) { + testGRPCNodeConnection(t, test.instance, test.clusterName, test.nodeAddr) + }) + }) + } + }) } // verifySessionJoin covers SSH into shell and joining the same session from another client @@ -1670,6 +1798,7 @@ func errorContains(text string) errorVerifier { } type disconnectTestCase struct { + name string recordingMode string options types.RoleOptions disconnectTimeout time.Duration @@ -1692,6 +1821,7 @@ func testDisconnectScenarios(t *testing.T, suite *integrationTestSuite) { testCases := []disconnectTestCase{ { + name: "client idle timeout node recoding", recordingMode: types.RecordAtNode, options: types.RoleOptions{ ClientIdleTimeout: types.NewDuration(500 * time.Millisecond), @@ -1699,6 +1829,7 @@ func testDisconnectScenarios(t *testing.T, suite *integrationTestSuite) { disconnectTimeout: time.Second, }, { + name: "client idle timeout proxy recording", recordingMode: types.RecordAtProxy, options: types.RoleOptions{ ForwardAgent: types.NewBool(true), @@ -1707,6 +1838,7 @@ func testDisconnectScenarios(t *testing.T, suite *integrationTestSuite) { disconnectTimeout: time.Second, }, { + name: "expired cert node recording", recordingMode: types.RecordAtNode, options: types.RoleOptions{ DisconnectExpiredCert: types.NewBool(true), @@ -1715,6 +1847,7 @@ func testDisconnectScenarios(t *testing.T, suite *integrationTestSuite) { disconnectTimeout: 4 * time.Second, }, { + name: "expired cert proxy recording", recordingMode: types.RecordAtProxy, options: types.RoleOptions{ ForwardAgent: types.NewBool(true), @@ -1724,7 +1857,7 @@ func testDisconnectScenarios(t *testing.T, suite *integrationTestSuite) { disconnectTimeout: 4 * time.Second, }, { - // "verify that concurrent connection limits are applied when recording at node", + name: "concurrent connection limits exceeded node recording", recordingMode: types.RecordAtNode, options: types.RoleOptions{ MaxConnections: 1, @@ -1734,7 +1867,7 @@ func testDisconnectScenarios(t *testing.T, suite *integrationTestSuite) { verifyError: errorContains("administratively prohibited"), }, { - // "verify that concurrent connection limits are applied when recording at proxy", + name: "concurrent connection limits exceeded proxy recording", recordingMode: types.RecordAtProxy, options: types.RoleOptions{ ForwardAgent: types.NewBool(true), @@ -1745,7 +1878,7 @@ func testDisconnectScenarios(t *testing.T, suite *integrationTestSuite) { verifyError: errorContains("administratively prohibited"), }, { - // "verify that lost connections to auth server terminate controlled conns", + name: "verify that lost connections to auth server terminate controlled connections", recordingMode: types.RecordAtNode, options: types.RoleOptions{ MaxConnections: 1, @@ -1774,7 +1907,7 @@ func testDisconnectScenarios(t *testing.T, suite *integrationTestSuite) { require.NoError(t, err) require.Len(t, sems, 1) - timeoutCtx, cancel := context.WithTimeout(ctx, 1*time.Second) + timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() ss, err := waitForSessionToBeEstablished(timeoutCtx, defaults.Namespace, site) @@ -1785,8 +1918,8 @@ func testDisconnectScenarios(t *testing.T, suite *integrationTestSuite) { }, } - for i, tc := range testCases { - t.Run(fmt.Sprintf("Test %d", i), func(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { runDisconnectTest(t, suite, tc) }) } @@ -2005,8 +2138,8 @@ func testInvalidLogins(t *testing.T, suite *integrationTestSuite) { require.NoError(t, err) err = tc.SSH(context.Background(), cmd, false) - require.True(t, trace.IsConnectionProblem(err)) - require.Contains(t, err.Error(), `unknown cluster "wrong-site"`) + require.True(t, trace.IsNotFound(err)) + require.Contains(t, err.Error(), `cluster "wrong-site" is not found`) } // TestTwoClustersTunnel creates two teleport clusters: "a" and "b" and creates a @@ -7240,13 +7373,6 @@ func testAgentlessConnection(t *testing.T, suite *integrationTestSuite) { require.NoError(t, teleInst.StopAll()) }) - tc, err := teleInst.NewClient(helpers.ClientConfig{ - Login: suite.Me.Username, - Cluster: helpers.Site, - Host: Host, - }) - require.NoError(t, err) - // get OpenSSH CA public key and create host certs ctx := context.Background() authClient := teleInst.Process.GetAuthServer() @@ -7325,18 +7451,32 @@ func testAgentlessConnection(t *testing.T, suite *integrationTestSuite) { } require.NoError(t, w.Close()) - // connect to node - proxyClient, err := tc.ConnectToProxy(ctx) + // create client + tc, err := teleInst.NewClient(helpers.ClientConfig{ + Login: suite.Me.Username, + Cluster: helpers.Site, + Host: Host, + }) + require.NoError(t, err) + + // connect to cluster + clt, err := tc.ConnectToCluster(ctx) require.NoError(t, err) t.Cleanup(func() { - require.NoError(t, proxyClient.Close()) + require.NoError(t, clt.Close()) }) - nodeClient, err := tc.ConnectToNode(ctx, proxyClient, client.NodeDetails{ - Addr: sshAddr, - Namespace: tc.Namespace, - Cluster: helpers.Site, - }, tc.Username) + // connect to node + nodeClient, err := tc.ConnectToNode( + ctx, + clt, + client.NodeDetails{ + Addr: sshAddr, + Namespace: tc.Namespace, + Cluster: helpers.Site, + }, + tc.Username, + ) require.NoError(t, err) // forward SSH agent diff --git a/integration/kube_integration_test.go b/integration/kube_integration_test.go index d36326729cdd6..442db7f33e89c 100644 --- a/integration/kube_integration_test.go +++ b/integration/kube_integration_test.go @@ -1053,12 +1053,14 @@ loop: func testKubeDisconnect(t *testing.T, suite *KubeSuite) { testCases := []disconnectTestCase{ { + name: "idle timeout", options: types.RoleOptions{ ClientIdleTimeout: types.NewDuration(500 * time.Millisecond), }, disconnectTimeout: 2 * time.Second, }, { + name: "expired cert", options: types.RoleOptions{ DisconnectExpiredCert: types.NewBool(true), MaxSessionTTL: types.NewDuration(3 * time.Second), @@ -1066,12 +1068,15 @@ func testKubeDisconnect(t *testing.T, suite *KubeSuite) { disconnectTimeout: 6 * time.Second, }, } + for i := 0; i < utils.GetIterations(); i++ { - for j, tc := range testCases { - t.Run(fmt.Sprintf("#%02d_iter_%d", j, i), func(t *testing.T) { - runKubeDisconnectTest(t, suite, tc) - }) - } + t.Run(fmt.Sprintf("Iteration=%d", i), func(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + runKubeDisconnectTest(t, suite, tc) + }) + } + }) } } diff --git a/integration/port_forwarding_test.go b/integration/port_forwarding_test.go index e797e9bc700eb..50907b8ab9025 100644 --- a/integration/port_forwarding_test.go +++ b/integration/port_forwarding_test.go @@ -22,10 +22,12 @@ import ( "net/http" "net/http/httptest" "net/url" + "os/user" "strconv" "testing" "time" + "github.com/google/uuid" "github.com/gravitational/trace" "github.com/stretchr/testify/require" @@ -71,19 +73,49 @@ func waitForSessionToBeEstablished(ctx context.Context, namespace string, site a } func testPortForwarding(t *testing.T, suite *integrationTestSuite) { + invalidOSLogin := uuid.NewString()[:12] + notFound := false + for i := 0; i < 10; i++ { + if _, err := user.Lookup(invalidOSLogin); err == nil { + invalidOSLogin = uuid.NewString()[:12] + continue + } + notFound = true + break + } + require.True(t, notFound, "unable to locate invalid os user") + + // Providing our own logins to Teleport so we can verify that a user + // that exists within Teleport but does not exist on the local node + // cannot port forward. + logins := []string{ + invalidOSLogin, + suite.Me.Username, + } + testCases := []struct { desc string portForwardingAllowed bool expectSuccess bool + login string }{ { desc: "Enabled", portForwardingAllowed: true, expectSuccess: true, - }, { + login: suite.Me.Username, + }, + { desc: "Disabled", portForwardingAllowed: false, expectSuccess: false, + login: suite.Me.Username, + }, + { + desc: "Enabled with invalid user", + portForwardingAllowed: true, + expectSuccess: false, + login: invalidOSLogin, }, } @@ -106,7 +138,7 @@ func testPortForwarding(t *testing.T, suite *integrationTestSuite) { cfg.SSH.Enabled = true cfg.SSH.AllowTCPForwarding = tt.portForwardingAllowed - teleport := suite.NewTeleportWithConfig(t, nil, nil, cfg) + teleport := suite.NewTeleportWithConfig(t, logins, nil, cfg) defer teleport.StopAll() site := teleport.GetSiteAPI(helpers.Site) @@ -127,7 +159,7 @@ func testPortForwarding(t *testing.T, suite *integrationTestSuite) { nodeSSHPort := helpers.Port(t, teleport.SSH) cl, err := teleport.NewClient(helpers.ClientConfig{ - Login: suite.Me.Username, + Login: tt.login, Cluster: helpers.Site, Host: Host, Port: nodeSSHPort, diff --git a/integrations/kube-agent-updater/DEBUG.md b/integrations/kube-agent-updater/DEBUG.md new file mode 100644 index 0000000000000..2156f080f353c --- /dev/null +++ b/integrations/kube-agent-updater/DEBUG.md @@ -0,0 +1,26 @@ +## Debugging tips for the kube-agent-updater + +### Running locally the updater against a remote Kubernetes cluster + +Running locally let you attach a debugger while still working against a real +cluster. This can be used to reproduce most complex issues and troubleshoot +specific cases. + +- Validate your current context works + ```shell + kubectl cluster-info + ``` +- Open a proxy to the api-server, then let the shell open and running + ```shell + kubectl proxy + ``` +- open a new terminal, create a new temporary directory and create your new kubeconfig + ```shell + export kubeconfig="$(mktemp)" + kubectl config set-credentials myself --username=foo + kubectl config set-cluster local-server --server=http://localhost:8001 + kubectl config set-context default-context --cluster=local-server --user=myself + kubectl config use-context default-context + echo "$KUBECONFIG" + ``` +- run the controller with the `KUBECONFIG` environment variable set diff --git a/integrations/kube-agent-updater/cmd/teleport-kube-agent-updater/constants.go b/integrations/kube-agent-updater/cmd/teleport-kube-agent-updater/constants.go new file mode 100644 index 0000000000000..3015b7150ae0f --- /dev/null +++ b/integrations/kube-agent-updater/cmd/teleport-kube-agent-updater/constants.go @@ -0,0 +1,36 @@ +/* +Copyright 2023 Gravitational, Inc. + +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. +*/ + +package main + +// teleportProdOCIPubKey is the key used to sign Teleport distroless images. +// The key lives in the Teleport production AWS KMS. +// In case of controlled rotation, we will want to add a second validator with +// the new key to support the transition period. +var teleportProdOCIPubKey = []byte(`-----BEGIN PUBLIC KEY----- +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAx+9UZboMl9ibwu/IWqbX ++wEJeKJqVpaLEsy1ODRpzIgcgaMh2n3BWtFEIoEszR3ZNlGdfqoPmb0nNnWx/qSf +eEsoSXievXa63M/gAUBB+jecbGEJH+SNaJPMVuvjabPqKtoMT2Spw3cacqpINzq1 +rkWU8IawY333gXbwzgsuK7izT7ymgOLPO9qPuX7Q3EBaGw3EvY7u6UKtqhvSGdyr +MirEErOERQ8EP8TrkCcJk0UfPAukzIcj91uHlXaqYBD/IyNYiC70EOlSLoN5/EeA +I4jQnGRfaKF6H6K+WieX9tP9k8/02S+1EVJW592pdQZhJZEq1B/dMc8UR3IjPMMC +qCT2xT6TsinaVzDaAbaRf0hvp311GxwrckNofGm/OSLn1+HqM6q4/A7qHubeRXGO +byabRr93CHSLegZ7OBMswHqqnu6/DuXjc6gOsQkH09dVTFeh34rQy4GKrvnpmOwj +Er1ccxzKcF/pw+lxi07hkpihR/uHUPxFboA/Wl7H2Jub21MFwIFQrDJv7z8yQgxJ +EuIXJJox2oAL7NzdSi9VIUYnEnx+2EtkU/spAFRR6i1BnT6aoIy3521B76wnmRr9 +atCSKjt6MdRxgj4htCjBWWJAGM9Z/avF4CYFmK7qiVxgpdrSM8Esbt2Ta+Lu3QMJ +T8LjqFu3u3dxVOo9RuLk+BkCAwEAAQ== +-----END PUBLIC KEY-----`) diff --git a/integrations/kube-agent-updater/cmd/teleport-kube-agent-updater/main.go b/integrations/kube-agent-updater/cmd/teleport-kube-agent-updater/main.go index 8af57348ad29c..bb312a8c62f18 100644 --- a/integrations/kube-agent-updater/cmd/teleport-kube-agent-updater/main.go +++ b/integrations/kube-agent-updater/cmd/teleport-kube-agent-updater/main.go @@ -18,19 +18,21 @@ package main import ( "flag" + "net/url" "os" + "strings" "time" "github.com/docker/distribution/reference" "github.com/gravitational/trace" appsv1 "k8s.io/api/apps/v1" + v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/cache" "sigs.k8s.io/controller-runtime/pkg/log/zap" - runtimescheme "sigs.k8s.io/controller-runtime/pkg/scheme" "github.com/gravitational/teleport/integrations/kube-agent-updater/pkg/controller" "github.com/gravitational/teleport/integrations/kube-agent-updater/pkg/img" @@ -39,18 +41,12 @@ import ( ) var ( - SchemeBuilder = &runtimescheme.Builder{GroupVersion: appsv1.SchemeGroupVersion} - scheme = runtime.NewScheme() + scheme = runtime.NewScheme() ) func init() { - SchemeBuilder.Register( - &appsv1.Deployment{}, - &appsv1.DeploymentList{}, - &appsv1.StatefulSet{}, - &appsv1.StatefulSetList{}, - ) - utilruntime.Must(SchemeBuilder.AddToScheme(scheme)) + utilruntime.Must(appsv1.AddToScheme(scheme)) + utilruntime.Must(v1.AddToScheme(scheme)) } func main() { @@ -61,12 +57,22 @@ func main() { var metricsAddr string var probeAddr string var syncPeriod time.Duration + var baseImageName string + var versionServer string + var versionChannel string + var insecureNoVerify bool + var disableLeaderElection bool flag.StringVar(&agentName, "agent-name", "", "The name of the agent that should be updated. This is mandatory.") flag.StringVar(&agentNamespace, "agent-namespace", "", "The namespace of the agent that should be updated. This is mandatory.") flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.") flag.StringVar(&probeAddr, "healthz-addr", ":8081", "The address the probe endpoint binds to.") flag.DurationVar(&syncPeriod, "sync-period", 10*time.Hour, "Operator sync period (format: https://pkg.go.dev/time#ParseDuration)") + flag.BoolVar(&insecureNoVerify, "insecure-no-verify-image", false, "Disable image signature verification.") + flag.BoolVar(&disableLeaderElection, "disable-leader-election", false, "Disable leader election, used when running the kube-agent-updater outside of Kubernetes.") + flag.StringVar(&versionServer, "version-server", "https://update.gravitational.io/v1/", "URL of the HTTP server advertising target version and critical maintenances. Trailing slash is optional.") + flag.StringVar(&versionChannel, "version-channel", "cloud/stable", "Version channel to get updates from.") + flag.StringVar(&baseImageName, "base-image", "public.ecr.aws/gravitational/teleport", "Image reference containing registry and repository.") opts := zap.Options{ Development: true, @@ -88,7 +94,7 @@ func main() { MetricsBindAddress: metricsAddr, Port: 9443, HealthProbeBindAddress: probeAddr, - LeaderElection: true, + LeaderElection: !disableLeaderElection, LeaderElectionID: agentName, Namespace: agentNamespace, SyncPeriod: &syncPeriod, @@ -108,16 +114,36 @@ func main() { os.Exit(1) } - // TODO: replace those mocks by the real thing - versionGetter := version.NewGetterMock("12.0.3", nil) - imageValidators := []img.Validator{ - img.NewImageValidatorMock("mock", true, img.NewImageRef("", "", "", "")), + versionServerURL, err := url.Parse(strings.TrimRight(versionServer, "/") + "/" + versionChannel) + if err != nil { + ctrl.Log.Error(err, "failed to pasre version server URL, exiting") + os.Exit(1) + } + versionGetter := version.NewBasicHTTPVersionGetter(versionServerURL) + maintenanceTriggers := maintenance.Triggers{ + maintenance.NewBasicHTTPMaintenanceTrigger("critical update", versionServerURL), + maintenance.NewUnhealthyWorkloadTrigger("unhealthy pods", mgr.GetClient()), + maintenance.NewWindowTrigger("maintenance window", mgr.GetClient()), + } + + var imageValidators img.Validators + if insecureNoVerify { + ctrl.Log.Info("INSECURE: Image validation disabled") + imageValidators = append(imageValidators, img.NewInsecureValidator("insecure always verify")) + } else { + validator, err := img.NewCosignSingleKeyValidator(teleportProdOCIPubKey, "cosign signature validator") + if err != nil { + ctrl.Log.Error(err, "failed to build image validator, exiting") + os.Exit(1) + } + imageValidators = append(imageValidators, validator) } - maintenanceTriggers := []maintenance.Trigger{ - maintenance.NewMaintenanceTriggerMock("never", false), + + baseImage, err := reference.ParseNamed(baseImageName) + if err != nil { + ctrl.Log.Error(err, "failed to parse base image reference, exiting") + os.Exit(1) } - baseImage, _ := reference.ParseNamed("public.ecr.aws/trent-playground/gravitational/teleport") - // End of mocks versionUpdater := controller.NewVersionUpdater(versionGetter, imageValidators, maintenanceTriggers, baseImage) diff --git a/integrations/kube-agent-updater/pkg/controller/constants.go b/integrations/kube-agent-updater/pkg/controller/constants.go index fe0bdea604bc4..72b36655f9d20 100644 --- a/integrations/kube-agent-updater/pkg/controller/constants.go +++ b/integrations/kube-agent-updater/pkg/controller/constants.go @@ -28,6 +28,9 @@ const ( defaultRequeue = 30 * time.Minute reconciliationTimeout = 2 * time.Minute kubeClientTimeout = 1 * time.Minute + // skipReconciliationAnnotation is inspired by the tenant-operator one + // (from the Teleport Cloud) but namespaced under `teleport.dev` + skipReconciliationAnnotation = "teleport.dev/skipreconcile" ) var ( diff --git a/integrations/kube-agent-updater/pkg/controller/deployment.go b/integrations/kube-agent-updater/pkg/controller/deployment.go index 7ddf5a7bcdae3..4f6b1d9a0cbba 100644 --- a/integrations/kube-agent-updater/pkg/controller/deployment.go +++ b/integrations/kube-agent-updater/pkg/controller/deployment.go @@ -55,6 +55,10 @@ func (r *DeploymentVersionUpdater) Reconcile(ctx context.Context, req ctrl.Reque } return ctrl.Result{}, trace.Wrap(err) } + if skipReconciliation(&obj) { + log.Info("Reconciliation disabled by resource annotations. Skipping.") + return requeueLater, nil + } // Get the current and past version currentVersion, err := getWorkloadVersion(obj.Spec.Template.Spec) diff --git a/integrations/kube-agent-updater/pkg/controller/statefulset.go b/integrations/kube-agent-updater/pkg/controller/statefulset.go index a9c651144f44d..93f9895323dbe 100644 --- a/integrations/kube-agent-updater/pkg/controller/statefulset.go +++ b/integrations/kube-agent-updater/pkg/controller/statefulset.go @@ -80,6 +80,10 @@ func (r *StatefulSetVersionUpdater) Reconcile(ctx context.Context, req ctrl.Requ } return ctrl.Result{}, trace.Wrap(err) } + if skipReconciliation(&obj) { + log.Info("Reconciliation disabled by resource annotations. Skipping.") + return requeueLater, nil + } // Get the current and past version currentVersion, err := getWorkloadVersion(obj.Spec.Template.Spec) diff --git a/integrations/kube-agent-updater/pkg/controller/utils.go b/integrations/kube-agent-updater/pkg/controller/utils.go index c28f7af5c582b..091f869a8a3a3 100644 --- a/integrations/kube-agent-updater/pkg/controller/utils.go +++ b/integrations/kube-agent-updater/pkg/controller/utils.go @@ -17,9 +17,12 @@ limitations under the License. package controller import ( + "strconv" + "github.com/docker/distribution/reference" "github.com/gravitational/trace" v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/gravitational/teleport/integrations/kube-agent-updater/pkg/version" ) @@ -65,3 +68,19 @@ func setContainerImageFromPodSpec(spec *v1.PodSpec, container, image string) err } return trace.NotFound("container %q not found in podSpec", container) } + +// skipReconciliation checks if the object has an annotation specifying that we +// must skip the reconciliation. Disabling reconciliation is useful for +// debugging purposes or when the user wants to suspend the updater for some +// reason. +func skipReconciliation(object metav1.Object) bool { + annotations := object.GetAnnotations() + if reconciliationAnnotation, ok := annotations[skipReconciliationAnnotation]; ok { + skip, err := strconv.ParseBool(reconciliationAnnotation) + if err != nil { + return false + } + return skip + } + return false +} diff --git a/integrations/kube-agent-updater/pkg/img/insecure.go b/integrations/kube-agent-updater/pkg/img/insecure.go new file mode 100644 index 0000000000000..1b30655917af1 --- /dev/null +++ b/integrations/kube-agent-updater/pkg/img/insecure.go @@ -0,0 +1,63 @@ +/* +Copyright 2023 Gravitational, Inc. + +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. +*/ + +package img + +import ( + "context" + + "github.com/docker/distribution/reference" + "github.com/gravitational/trace" + "github.com/opencontainers/go-digest" +) + +type insecureValidator struct { + name string +} + +// Name returns the validator name +func (v *insecureValidator) Name() string { + return v.name +} + +// TODO: cache this to protect against registry quotas +// The image validation is only invoked when we are in a maintenance window and +// the target version is different than our current version. In regular usage we +// are called only once per update. However, Kubernetes controllers failure mode +// is usually infinite retry loop. If something fails after the image validation, +// we might get called in a loop indefinitely. To mitigate the impact of such +// failure, ValidateAndResolveDigest should cache its result. + +// ValidateAndResolveDigest resolves the image digest and always return the +// image is valid. Using this validator makes you vulnerable in case of image +// registry compromise. +func (v *insecureValidator) ValidateAndResolveDigest(ctx context.Context, image reference.NamedTagged) (NamedTaggedDigested, error) { + ref, err := NamedTaggedToDigest(image) + if err != nil { + return nil, trace.Wrap(err) + } + + digestedImage := NewImageRef(ref.RegistryStr(), ref.RepositoryStr(), image.Tag(), digest.Digest(ref.DigestStr())) + return digestedImage, nil +} + +// NewInsecureValidator returns an img.Validator that only resolves the image +// but does not check its signature. +func NewInsecureValidator(name string) Validator { + return &insecureValidator{ + name: name, + } +} diff --git a/integrations/kube-agent-updater/pkg/podutils/filter.go b/integrations/kube-agent-updater/pkg/podutils/filter.go index 625bbd788cd3f..b91bdf52cb1ee 100644 --- a/integrations/kube-agent-updater/pkg/podutils/filter.go +++ b/integrations/kube-agent-updater/pkg/podutils/filter.go @@ -63,7 +63,13 @@ func Not(filterFunc FilterFunc) FilterFunc { } } -const podReadinessGracePeriod = 10 * time.Minute +// podReadinessGracePeriod represents how much time we wait before we consider +// the pod (and a fortiori the workload) unhealthy. We might want to empirically +// tune this value. A higher value can lead to workloads being stuck longer in +// case of error. A shorter value might cause false positives and trigger +// updates because of other cluster-related events like network issues, registry +// downtime or missing capacity. +const podReadinessGracePeriod = 5 * time.Minute // IsUnhealthy checks if a pod has not been ready since at least 10 minutes/ // This heuristic also detects infrastructure issues like not enough room to diff --git a/integrations/lib/testing/integration/integration.go b/integrations/lib/testing/integration/integration.go index f229241867360..209a393c74c53 100644 --- a/integrations/lib/testing/integration/integration.go +++ b/integrations/lib/testing/integration/integration.go @@ -228,7 +228,12 @@ func NewFromEnv(ctx context.Context) (*Integration, error) { if !ok { return nil, trace.Errorf("failed to get caller information") } - outDir := path.Join(path.Dir(goFile), "..", "..", "..", ".teleport") // subdir in repo root + // Use GHA temp directory by default + outDir := os.Getenv("RUNNER_TEMP") + if outDir == "" { + outDir = path.Join(path.Dir(goFile), "..", "..", "..") // gravitational/teleport repo root + } + outDir = path.Join(outDir, ".teleport") if licenseStr != "" { paths, err = GetEnterprise(ctx, version, outDir) if err != nil { diff --git a/integrations/operator/Dockerfile b/integrations/operator/Dockerfile index 0f3a642c89b8c..d4e17dffa70eb 100644 --- a/integrations/operator/Dockerfile +++ b/integrations/operator/Dockerfile @@ -1,5 +1,4 @@ ARG BUILDBOX -ARG BASE_IMAGE=gcr.io/distroless/static-debian11 # BUILDPLATFORM is provided by Docker/buildx FROM --platform=$BUILDPLATFORM $BUILDBOX as builder @@ -26,17 +25,20 @@ COPY integrations/operator/sidecar/ integrations/operator/sidecar/ COPY integrations/operator/main.go integrations/operator/main.go COPY integrations/operator/namespace.go integrations/operator/namespace.go -ARG TARGETOS -ARG TARGETARCH +# Compiler package should use host-triplet-agnostic name (i.e. "x86-64-linux-gnu-gcc" instead of "gcc") +# in most cases, to avoid issues on systems with multiple versions of gcc (i.e. buildboxes) +# TARGETOS and TARGETARCH are provided by Docker/buildx, but must be explicitly listed here +ARG COMPILER_NAME TARGETOS TARGETARCH -# Build the program. We rely on golang's cross-compilation capabilities for multiarch building. -RUN echo "Targeting $TARGETOS/$TARGETARCH" && \ - GOOS=$TARGETOS GOARCH=$TARGETARCH \ +# Build the program +# CGO is required for github.com/gravitational/teleport/lib/system +RUN echo "Targeting $TARGETOS/$TARGETARCH with CC=$COMPILER_NAME" && \ + CGO_ENABLED=1 CC=$COMPILER_NAME GOOS=$TARGETOS GOARCH=$TARGETARCH \ go build -a -o /go/bin/teleport-operator github.com/gravitational/teleport/integrations/operator # Create the image with the build operator on the $TARGETPLATFORM # TARGETPLATFORM is provided by Docker/buildx -FROM --platform=$TARGETPLATFORM $BASE_IMAGE +FROM --platform=$TARGETPLATFORM gcr.io/distroless/cc WORKDIR / COPY --from=builder /go/bin/teleport-operator . diff --git a/integrations/operator/Makefile b/integrations/operator/Makefile index ebe8af7cded46..d7cf0b20f4b65 100644 --- a/integrations/operator/Makefile +++ b/integrations/operator/Makefile @@ -23,6 +23,21 @@ include ./envtest.mk # Configure which compiler and buildbox to use OS ?= $(shell go env GOOS) ARCH ?= $(shell go env GOARCH) +ifeq ("$(OS)","linux") +ifeq ("$(ARCH)","amd64") +COMPILER ?= x86_64-linux-gnu-gcc +PLATFORM_BUILDBOX ?= $(BUILDBOX) +else ifeq ("$(ARCH)","386") +COMPILER ?= x86_64-linux-gnu-gcc +PLATFORM_BUILDBOX ?= $(BUILDBOX) +else ifeq ("$(ARCH)","arm") +COMPILER ?= arm-linux-gnueabihf-gcc +PLATFORM_BUILDBOX ?= $(BUILDBOX_ARM) +else ifeq ("$(ARCH)","arm64") +COMPILER ?= aarch64-linux-gnu-gcc +PLATFORM_BUILDBOX ?= $(BUILDBOX_ARM) +endif +endif .PHONY: all all: build @@ -110,8 +125,8 @@ run: manifests generate fmt vet ## Run a controller from your host. .PHONY: docker-build docker-build: ## Build docker image with the manager. - docker buildx build --platform="$(OS)/$(ARCH)" --build-arg BUILDBOX=$(BUILDBOX) \ - -t ${IMG} --load ../.. -f ./Dockerfile + docker buildx build --platform="$(OS)/$(ARCH)" --build-arg BUILDBOX=$(PLATFORM_BUILDBOX) \ + --build-arg COMPILER_NAME=$(COMPILER) -t ${IMG} --load ../.. -f ./Dockerfile .PHONY: docker-push docker-push: ## Push docker image with the manager. diff --git a/lib/agentless/agentless.go b/lib/agentless/agentless.go index baf099121caf8..cb2037027823c 100644 --- a/lib/agentless/agentless.go +++ b/lib/agentless/agentless.go @@ -27,18 +27,48 @@ import ( "github.com/gravitational/teleport/api/client/proto" "github.com/gravitational/teleport/api/utils/sshutils" - "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/auth/native" + "github.com/gravitational/teleport/lib/authz" + "github.com/gravitational/teleport/lib/utils" ) -// SiteClientGetter returns an auth client to a given cluster. -type SiteClientGetter interface { - GetSiteClient(ctx context.Context, clusterName string) (auth.ClientI, error) +// CertGenerator generates certificates from a certificate request. +type CertGenerator interface { + GenerateOpenSSHCert(ctx context.Context, req *proto.OpenSSHCertRequest) (*proto.OpenSSHCert, error) } -// CreateAuthSigner attempts to create a [ssh.Signer] that is signed with +// SignerFromSSHCertificate returns a function that attempts to +// create a [ssh.Signer] for the Identity in the provided [ssh.Certificate] +// that is signed with the OpenSSH CA and can be used to authenticate to agentless nodes. +func SignerFromSSHCertificate(certificate *ssh.Certificate, generator CertGenerator) func(context.Context) (ssh.Signer, error) { + return func(ctx context.Context) (ssh.Signer, error) { + validBefore := time.Unix(int64(certificate.ValidBefore), 0) + ttl := time.Until(validBefore) + + clusterName := certificate.Permissions.Extensions[utils.CertExtensionAuthority] + user := certificate.Permissions.Extensions[utils.CertTeleportUser] + + signer, err := createAuthSigner(ctx, user, clusterName, ttl, generator) + return signer, trace.Wrap(err) + } +} + +// SignerFromAuthzContext returns a function that attempts to +// create a [ssh.Signer] for the [tlsca.Identity] in the provided [authz.Context] +// that is signed with the OpenSSH CA and can be used to authenticate to agentless nodes. +func SignerFromAuthzContext(authzCtx *authz.Context, generator CertGenerator) func(context.Context) (ssh.Signer, error) { + return func(ctx context.Context) (ssh.Signer, error) { + identity := authzCtx.Identity.GetIdentity() + ttl := time.Until(identity.Expires) + + signer, err := createAuthSigner(ctx, authzCtx.User.GetName(), identity.TeleportCluster, ttl, generator) + return signer, trace.Wrap(err) + } +} + +// createAuthSigner creates a [ssh.Signer] that is signed with // OpenSSH CA and can be used to authenticate to agentless nodes. -func CreateAuthSigner(ctx context.Context, username, clusterName string, ttl time.Duration, clientGetter SiteClientGetter) (ssh.Signer, error) { +func createAuthSigner(ctx context.Context, username, clusterName string, ttl time.Duration, generator CertGenerator) (ssh.Signer, error) { // generate a new key pair priv, err := native.GeneratePrivateKey() if err != nil { @@ -46,11 +76,7 @@ func CreateAuthSigner(ctx context.Context, username, clusterName string, ttl tim } // sign new public key with OpenSSH CA - client, err := clientGetter.GetSiteClient(ctx, clusterName) - if err != nil { - return nil, trace.Wrap(err) - } - reply, err := client.GenerateOpenSSHCert(ctx, &proto.OpenSSHCertRequest{ + reply, err := generator.GenerateOpenSSHCert(ctx, &proto.OpenSSHCertRequest{ Username: username, PublicKey: priv.MarshalSSHPublicKey(), TTL: proto.Duration(ttl), diff --git a/lib/auth/auth.go b/lib/auth/auth.go index 1fe81d13ade67..047319a452736 100644 --- a/lib/auth/auth.go +++ b/lib/auth/auth.go @@ -2073,6 +2073,7 @@ func generateCert(a *Server, req certRequest, caType types.CertAuthType) (*proto AssetTag: req.deviceExtensions.AssetTag, CredentialID: req.deviceExtensions.CredentialID, }, + UserType: req.user.GetUserType(), } var signedTLSCert []byte @@ -3816,9 +3817,26 @@ func (a *Server) UpsertNode(ctx context.Context, server types.Server) (*types.Ke return lease, nil } +// enforceLicense checks if the license allows the given resource type to be +// created. +func enforceLicense(t string) error { + switch t { + case types.KindKubeServer, types.KindKubernetesCluster: + if !modules.GetModules().Features().Kubernetes { + return trace.AccessDenied( + "this Teleport cluster is not licensed for Kubernetes, please contact the cluster administrator") + } + } + return nil +} + // UpsertKubernetesServer implements [services.Presence] by delegating to // [Server.Services] and then potentially emitting a [usagereporter] event. func (a *Server) UpsertKubernetesServer(ctx context.Context, server types.KubeServer) (*types.KeepAlive, error) { + if err := enforceLicense(types.KindKubeServer); err != nil { + return nil, trace.Wrap(err) + } + k, err := a.Services.UpsertKubernetesServer(ctx, server) if err != nil { return nil, trace.Wrap(err) @@ -4241,6 +4259,9 @@ func (a *Server) ListResources(ctx context.Context, req proto.ListResourcesReque // CreateKubernetesCluster creates a new kubernetes cluster resource. func (a *Server) CreateKubernetesCluster(ctx context.Context, kubeCluster types.KubeCluster) error { + if err := enforceLicense(types.KindKubernetesCluster); err != nil { + return trace.Wrap(err) + } if err := a.Services.CreateKubernetesCluster(ctx, kubeCluster); err != nil { return trace.Wrap(err) } @@ -4265,6 +4286,9 @@ func (a *Server) CreateKubernetesCluster(ctx context.Context, kubeCluster types. // UpdateKubernetesCluster updates an existing kubernetes cluster resource. func (a *Server) UpdateKubernetesCluster(ctx context.Context, kubeCluster types.KubeCluster) error { + if err := enforceLicense(types.KindKubernetesCluster); err != nil { + return trace.Wrap(err) + } if err := a.Kubernetes.UpdateKubernetesCluster(ctx, kubeCluster); err != nil { return trace.Wrap(err) } @@ -4314,7 +4338,17 @@ func (a *Server) SubmitUsageEvent(ctx context.Context, req *proto.SubmitUsageEve return trace.Wrap(err) } - event, err := usagereporter.ConvertUsageEvent(req.GetEvent(), username) + userIsSSO, err := authz.GetClientUserIsSSO(ctx) + if err != nil { + return trace.Wrap(err) + } + + userMetadata := usagereporter.UserMetadata{ + Username: username, + IsSSO: userIsSSO, + } + + event, err := usagereporter.ConvertUsageEvent(req.GetEvent(), userMetadata) if err != nil { return trace.Wrap(err) } diff --git a/lib/auth/auth_with_roles.go b/lib/auth/auth_with_roles.go index 40917b7c00a84..0d99197ef187c 100644 --- a/lib/auth/auth_with_roles.go +++ b/lib/auth/auth_with_roles.go @@ -1390,6 +1390,17 @@ func (a *ServerWithRoles) ListResources(ctx context.Context, req proto.ListResou if req.ResourceType == kubeService { return &types.ListResourcesResponse{}, nil } + + // Check if auth server has a license for this resource type but only return an + // error if the user is not a builtin proxy or kube role. + // Builtin proxy and kube roles are allowed to list resources to avoid crashes + // even if the license is missing. + // Users with other roles will get an error if the license is missing so they + // can request a license with the correct features. + if err := enforceLicense(req.ResourceType); err != nil && !a.hasBuiltinRole(types.RoleProxy, types.RoleKube) { + return nil, trace.Wrap(err) + } + if req.UseSearchAsRoles || req.UsePreviewAsRoles { var extraRoles []string if req.UseSearchAsRoles { @@ -2608,7 +2619,7 @@ func (a *ServerWithRoles) generateUserCerts(ctx context.Context, req proto.UserC } // Do not allow SSO users to be impersonated. - if req.Username != a.context.User.GetName() && user.GetCreatedBy().Connector != nil { + if req.Username != a.context.User.GetName() && user.GetUserType() == types.UserTypeSSO { log.Warningf("User %v tried to issue a cert for externally managed user %v, this is not supported.", a.context.User.GetName(), req.Username) return nil, trace.AccessDenied("access denied") } diff --git a/lib/auth/bot.go b/lib/auth/bot.go index 21ce02a5609a6..4f948fc7dda89 100644 --- a/lib/auth/bot.go +++ b/lib/auth/bot.go @@ -485,7 +485,7 @@ func (s *Server) generateInitialBotCerts(ctx context.Context, username string, p } // Do not allow SSO users to be impersonated. - if user.GetCreatedBy().Connector != nil { + if user.GetUserType() == types.UserTypeSSO { log.Warningf("Tried to issue a renewable cert for externally managed user %v, this is not supported.", username) return nil, trace.AccessDenied("access denied") } diff --git a/lib/auth/kube.go b/lib/auth/kube.go index 0a843b1049989..b3b9c56d36805 100644 --- a/lib/auth/kube.go +++ b/lib/auth/kube.go @@ -25,7 +25,6 @@ import ( "github.com/gravitational/teleport" apidefaults "github.com/gravitational/teleport/api/defaults" "github.com/gravitational/teleport/api/types" - "github.com/gravitational/teleport/lib/modules" "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/tlsca" ) @@ -64,9 +63,8 @@ type KubeCSRResponse struct { // signed certificate if successful. func (s *Server) ProcessKubeCSR(req KubeCSR) (*KubeCSRResponse, error) { ctx := context.TODO() - if !modules.GetModules().Features().Kubernetes { - return nil, trace.AccessDenied( - "this Teleport cluster is not licensed for Kubernetes, please contact the cluster administrator") + if err := enforceLicense(types.KindKubernetesCluster); err != nil { + return nil, trace.Wrap(err) } if err := req.CheckAndSetDefaults(); err != nil { return nil, trace.Wrap(err) diff --git a/lib/auth/session_access.go b/lib/auth/session_access.go index 28a6df0a76340..bd7e6e0cd9868 100644 --- a/lib/auth/session_access.go +++ b/lib/auth/session_access.go @@ -162,7 +162,7 @@ func (e *SessionAccessEvaluator) matchesJoin(allow *types.SessionJoinPolicy) boo for _, allowRole := range allow.Roles { // GlobToRegexp makes sure this is always a valid regexp. - expr := regexp.MustCompile(utils.GlobToRegexp(allowRole)) + expr := regexp.MustCompile("^" + utils.GlobToRegexp(allowRole) + "$") for _, policySet := range e.policySets { if expr.MatchString(policySet.Name) { diff --git a/lib/auth/session_access_test.go b/lib/auth/session_access_test.go index 1932c189b6f4a..851052738145a 100644 --- a/lib/auth/session_access_test.go +++ b/lib/auth/session_access_test.go @@ -459,6 +459,35 @@ func failKindJoinTestCase(t *testing.T) joinTestCase { } } +// Tests to make sure that the regexp matching for roles only matches a full string +// match and not just any substring match. +// In this test case, we are making sure that having access to sessions hosted +// by someone with the role `test` doesn't also grant you access to sessions +// hosted by someone with the role `prod-test`. +func failJoinRoleNameInSubstringTestCase(t *testing.T) joinTestCase { + hostRole, err := types.NewRole("prod-test", types.RoleSpecV6{}) + require.NoError(t, err) + participantRole, err := types.NewRole("participant", types.RoleSpecV6{}) + require.NoError(t, err) + + participantRole.SetSessionJoinPolicies([]*types.SessionJoinPolicy{{ + Roles: []string{"test"}, + Kinds: []string{string(types.SSHSessionKind), string(types.KubernetesSessionKind)}, + Modes: []string{types.Wildcard}, + }}) + + return joinTestCase{ + name: "failRoleInSubstring", + host: hostRole, + sessionKinds: []types.SessionKind{types.SSHSessionKind, types.KubernetesSessionKind}, + participant: SessionAccessContext{ + Username: "participant", + Roles: []types.Role{participantRole}, + }, + expected: []bool{false, false}, + } +} + func versionDefaultJoinTestCase(t *testing.T) joinTestCase { hostRole, err := types.NewRole("host", types.RoleSpecV6{}) require.NoError(t, err) @@ -495,6 +524,7 @@ func TestSessionAccessJoin(t *testing.T) { successSameUserJoinTestCase(t), failRoleJoinTestCase(t), failKindJoinTestCase(t), + failJoinRoleNameInSubstringTestCase(t), versionDefaultJoinTestCase(t), } diff --git a/lib/auth/windows/certificate_authority.go b/lib/auth/windows/certificate_authority.go index 3067200f10e8f..6f4d1cc43c46e 100644 --- a/lib/auth/windows/certificate_authority.go +++ b/lib/auth/windows/certificate_authority.go @@ -59,8 +59,9 @@ func (c *CertificateStoreClient) Update(ctx context.Context) error { // have to do it here. // // TODO(zmb3): support multiple CA certs per cluster (such as with HSMs). + caType := types.UserCA ca, err := c.cfg.AccessPoint.GetCertAuthority(ctx, types.CertAuthID{ - Type: types.UserCA, + Type: caType, DomainName: c.cfg.ClusterName, }, false) if err != nil { @@ -90,7 +91,7 @@ func (c *CertificateStoreClient) Update(ctx context.Context) error { if err := c.updateCAInNTAuthStore(ctx, caDER); err != nil { return trace.Wrap(err, "updating NTAuth store over LDAP: %v", err) } - if err := c.updateCRL(ctx, crlDER); err != nil { + if err := c.updateCRL(ctx, crlDER, caType); err != nil { return trace.Wrap(err, "updating CRL over LDAP: %v", err) } return nil @@ -156,7 +157,7 @@ func (c *CertificateStoreClient) updateCAInNTAuthStore(ctx context.Context, caDE return nil } -func (c *CertificateStoreClient) updateCRL(ctx context.Context, crlDER []byte) error { +func (c *CertificateStoreClient) updateCRL(ctx context.Context, crlDER []byte, caType types.CertAuthType) error { // Publish the CRL for current cluster CA. For trusted clusters, their // respective windows_desktop_services will publish CRLs of their CAs so we // don't have to do it here. @@ -165,12 +166,13 @@ func (c *CertificateStoreClient) updateCRL(ctx context.Context, crlDER []byte) e // another nested container with the CA name, I think, and then multiple // separate CRL objects in that container. // - // We name our parent container "Teleport" and the CRL object is named - // after the Teleport cluster name. For example, CRL for cluster "prod" - // will be placed at: + // We name our parent container based on the CA type (for example, for User + // CA, it is called "Teleport"), and the CRL object is named after the + // Teleport cluster name. So, for instance, CRL for cluster "prod" and User + // CA will be placed at: // ... > CDP > Teleport > prod - containerDN := crlContainerDN(c.cfg.LDAPConfig) - crlDN := crlDN(c.cfg.ClusterName, c.cfg.LDAPConfig) + containerDN := crlContainerDN(c.cfg.LDAPConfig, caType) + crlDN := crlDN(c.cfg.ClusterName, c.cfg.LDAPConfig, caType) // Create the parent container. if err := c.cfg.LC.CreateContainer(containerDN); err != nil { diff --git a/lib/auth/windows/ldap.go b/lib/auth/windows/ldap.go index 1f328db4e1c6b..f896e188130f2 100644 --- a/lib/auth/windows/ldap.go +++ b/lib/auth/windows/ldap.go @@ -24,6 +24,8 @@ import ( "github.com/go-ldap/ldap/v3" "github.com/gravitational/trace" + + "github.com/gravitational/teleport/api/types" ) // LDAPConfig contains parameters for connecting to an LDAP server. @@ -276,10 +278,22 @@ func CombineLDAPFilters(filters []string) string { return "(&" + strings.Join(filters, "") + ")" } -func crlContainerDN(config LDAPConfig) string { - return "CN=Teleport,CN=CDP,CN=Public Key Services,CN=Services,CN=Configuration," + config.DomainDN() +func crlContainerDN(config LDAPConfig, caType types.CertAuthType) string { + return fmt.Sprintf("CN=%s,CN=CDP,CN=Public Key Services,CN=Services,CN=Configuration,%s", crlKeyName(caType), config.DomainDN()) +} + +func crlDN(clusterName string, config LDAPConfig, caType types.CertAuthType) string { + return "CN=" + clusterName + "," + crlContainerDN(config, caType) } -func crlDN(clusterName string, config LDAPConfig) string { - return "CN=" + clusterName + "," + crlContainerDN(config) +// crlKeyName returns the appropriate LDAP key given the CA type. +// +// Note: UserCA must use "Teleport" to keep backwards compatibility. +func crlKeyName(caType types.CertAuthType) string { + switch caType { + case types.DatabaseCA: + return "TeleportDB" + default: + return "Teleport" + } } diff --git a/lib/auth/windows/windows.go b/lib/auth/windows/windows.go index 0f9cbf90fb63f..f8f7dea91b319 100644 --- a/lib/auth/windows/windows.go +++ b/lib/auth/windows/windows.go @@ -107,7 +107,7 @@ func getCertRequest(req *GenerateCredentialsRequest) (*certRequest, error) { // CRLs in it. Each service can also handle RDP connections for a different // domain, with the assumption that some other windows_desktop_service // published a CRL there. - crlDN := crlDN(req.ClusterName, req.LDAPConfig) + crlDN := crlDN(req.ClusterName, req.LDAPConfig, req.CAType) return &certRequest{csrPEM: csrPEM, crlEndpoint: fmt.Sprintf("ldap:///%s?certificateRevocationList?base?objectClass=cRLDistributionPoint", crlDN), keyDER: keyDER}, nil } @@ -142,6 +142,9 @@ type GenerateCredentialsRequest struct { LDAPConfig LDAPConfig // AuthClient is the windows AuthInterface AuthClient AuthInterface + // CAType is the certificate authority type used to generate the certificate. + // This is used to proper generate the CRL LDAP path. + CAType types.CertAuthType } // GenerateWindowsDesktopCredentials generates a private key / certificate pair for the given diff --git a/lib/auth/windows/windows_test.go b/lib/auth/windows/windows_test.go index 7104c74547058..ed2c1c7ca238f 100644 --- a/lib/auth/windows/windows_test.go +++ b/lib/auth/windows/windows_test.go @@ -139,23 +139,39 @@ func TestGenerateCredentials(t *testing.T) { func TestCRLDN(t *testing.T) { for _, test := range []struct { + name string clusterName string crlDN string + caType types.CertAuthType }{ { + name: "test cluster name", clusterName: "test", crlDN: "CN=test,CN=Teleport,CN=CDP,CN=Public Key Services,CN=Services,CN=Configuration,DC=test,DC=goteleport,DC=com", }, { + name: "full cluster name", clusterName: "cluster.goteleport.com", crlDN: "CN=cluster.goteleport.com,CN=Teleport,CN=CDP,CN=Public Key Services,CN=Services,CN=Configuration,DC=test,DC=goteleport,DC=com", }, + { + name: "database CA", + clusterName: "cluster.goteleport.com", + caType: types.DatabaseCA, + crlDN: "CN=cluster.goteleport.com,CN=TeleportDB,CN=CDP,CN=Public Key Services,CN=Services,CN=Configuration,DC=test,DC=goteleport,DC=com", + }, + { + name: "user CA", + clusterName: "cluster.goteleport.com", + caType: types.UserCA, + crlDN: "CN=cluster.goteleport.com,CN=Teleport,CN=CDP,CN=Public Key Services,CN=Services,CN=Configuration,DC=test,DC=goteleport,DC=com", + }, } { - t.Run(test.clusterName, func(t *testing.T) { + t.Run(test.name, func(t *testing.T) { cfg := LDAPConfig{ Domain: "test.goteleport.com", } - require.Equal(t, test.crlDN, crlDN(test.clusterName, cfg)) + require.Equal(t, test.crlDN, crlDN(test.clusterName, cfg, test.caType)) }) } } diff --git a/lib/authz/permissions.go b/lib/authz/permissions.go index a5230ae2eddc9..c62aa90104ca5 100644 --- a/lib/authz/permissions.go +++ b/lib/authz/permissions.go @@ -369,6 +369,7 @@ func (a *authorizer) authorizeRemoteUser(ctx context.Context, u RemoteUser) (*Co LoginIP: u.Identity.LoginIP, PinnedIP: u.Identity.PinnedIP, PrivateKeyPolicy: u.Identity.PrivateKeyPolicy, + UserType: u.Identity.UserType, } if checker.PinSourceIP() && identity.PinnedIP == "" { return nil, trace.AccessDenied("pinned IP is required for the user, but is not present on identity") @@ -748,7 +749,7 @@ func definitionForBuiltinRole(clusterName string, recConfig types.SessionRecordi types.NewRule(types.KindRole, services.RO()), types.NewRule(types.KindNamespace, services.RO()), types.NewRule(types.KindLock, services.RO()), - types.NewRule(types.KindKubernetesCluster, services.RW()), + types.NewRule(types.KindKubernetesCluster, services.RO()), types.NewRule(types.KindSemaphore, services.RW()), }, }, @@ -931,21 +932,42 @@ func ClientUsername(ctx context.Context) string { return identity.Username } -// GetClientUsername returns the username of a remote HTTP client making the call. -// If ctx didn't pass through auth middleware or did not come from an HTTP -// request, returns an error. -func GetClientUsername(ctx context.Context) (string, error) { +func userIdentityFromContext(ctx context.Context) (*tlsca.Identity, error) { userWithIdentity, err := UserFromContext(ctx) if err != nil { - return "", trace.AccessDenied("missing identity") + return nil, trace.AccessDenied("missing identity") } + identity := userWithIdentity.GetIdentity() if identity.Username == "" { - return "", trace.AccessDenied("missing identity username") + return nil, trace.AccessDenied("missing identity username") + } + + return &identity, nil +} + +// GetClientUsername returns the username of a remote HTTP client making the call. +// If ctx didn't pass through auth middleware or did not come from an HTTP +// request, returns an error. +func GetClientUsername(ctx context.Context) (string, error) { + identity, err := userIdentityFromContext(ctx) + if err != nil { + return "", trace.Wrap(err) } return identity.Username, nil } +// GetClientUserIsSSO extracts the identity of a remote HTTP client and indicates whether that is an SSO user. +// If ctx didn't pass through auth middleware or did not come from an HTTP +// request, returns an error. +func GetClientUserIsSSO(ctx context.Context) (bool, error) { + identity, err := userIdentityFromContext(ctx) + if err != nil { + return false, trace.Wrap(err) + } + return identity.UserType == types.UserTypeSSO, nil +} + // ClientImpersonator returns the impersonator username of a remote client // making the call. If not present, returns an empty string func ClientImpersonator(ctx context.Context) string { diff --git a/lib/authz/permissions_test.go b/lib/authz/permissions_test.go index 2e85a92094ec2..f00a995e3062f 100644 --- a/lib/authz/permissions_test.go +++ b/lib/authz/permissions_test.go @@ -182,6 +182,32 @@ func upsertLockWithPutEvent(ctx context.Context, t *testing.T, client *testClien } } +func TestGetClientUserIsSSO(t *testing.T) { + ctx := context.Background() + + u := LocalUser{ + Username: "someuser", + Identity: tlsca.Identity{ + Username: "someuser", + Groups: []string{"somerole"}, + }, + } + + // Non SSO user must return false + nonSSOUserCtx := context.WithValue(ctx, contextUser, u) + + isSSO, err := GetClientUserIsSSO(nonSSOUserCtx) + require.NoError(t, err) + require.False(t, isSSO, "expected a non-SSO user") + + // An SSO user must return true + u.Identity.UserType = types.UserTypeSSO + ssoUserCtx := context.WithValue(ctx, contextUser, u) + localUserIsSSO, err := GetClientUserIsSSO(ssoUserCtx) + require.NoError(t, err) + require.True(t, localUserIsSSO, "expected an SSO user") +} + func TestAuthorizer_Authorize_deviceTrust(t *testing.T) { t.Parallel() diff --git a/lib/benchmark/benchmark.go b/lib/benchmark/benchmark.go index 565d9dc86606f..c52c52a52c526 100644 --- a/lib/benchmark/benchmark.go +++ b/lib/benchmark/benchmark.go @@ -24,7 +24,6 @@ import ( "os" "os/signal" "path/filepath" - "strings" "syscall" "time" @@ -34,7 +33,6 @@ import ( "github.com/gravitational/teleport/lib/client" "github.com/gravitational/teleport/lib/observability/tracing" - "github.com/gravitational/teleport/lib/utils" ) const ( @@ -48,14 +46,20 @@ const ( pauseTimeBetweenBenchmarks = time.Second * 5 ) +// Service is a the Teleport service to benchmark. +type Service string + +const ( + // SSHService is the SSH service + SSHService Service = "ssh" + // KubernetesService is the Kubernetes service + KubernetesService Service = "kube" +) + // Config specifies benchmark requests to run type Config struct { // Rate is requests per second origination rate Rate int - // Command is a command to run - Command []string - // Interactive turns on interactive sessions - Interactive bool // MinimumWindow is the min duration MinimumWindow time.Duration // MinimumMeasurments is the min amount of requests @@ -79,9 +83,8 @@ type Result struct { // Run is used to run the benchmarks, it is given a generator, command to run, // a host, host login, and proxy. If host login or proxy is an empty string, it will // use the default login -func Run(ctx context.Context, lg *Linear, cmd, host, login, proxy string) ([]Result, error) { - c := strings.Split(cmd, " ") - lg.config = &Config{Command: c} +func Run(ctx context.Context, lg *Linear, host, login, proxy string, suite BenchmarkSuite) ([]Result, error) { + lg.config = &Config{} if err := validateConfig(lg); err != nil { return nil, trace.Wrap(err) } @@ -112,7 +115,7 @@ func Run(ctx context.Context, lg *Linear, cmd, host, login, proxy string) ([]Res if benchmarkC == nil { break } - result, err := benchmarkC.Benchmark(ctx, tc) + result, err := benchmarkC.Benchmark(ctx, tc, suite) if err != nil { return results, trace.Wrap(err) } @@ -128,7 +131,7 @@ func ExportLatencyProfile(path string, h *hdrhistogram.Histogram, ticks int32, v timeStamp := time.Now().Format("2006-01-02_15:04:05") suffix := fmt.Sprintf("latency_profile_%s.txt", timeStamp) if path != "." { - if err := os.MkdirAll(path, 0700); err != nil { + if err := os.MkdirAll(path, 0o700); err != nil { return "", trace.Wrap(err) } } @@ -140,7 +143,6 @@ func ExportLatencyProfile(path string, h *hdrhistogram.Histogram, ticks int32, v if _, err := h.PercentilesPrint(fo, ticks, valueScale); err != nil { if err := fo.Close(); err != nil { - logrus.WithError(err).Warningf("failed to close file") } return "", trace.Wrap(err) @@ -152,13 +154,33 @@ func ExportLatencyProfile(path string, h *hdrhistogram.Histogram, ticks int32, v return fo.Name(), nil } +// WorkloadFunc is a function that executes a single benchmark call. +type WorkloadFunc func(context.Context) error + +// BenchmarkSuite is an interface that defines a benchmark suite. +type BenchmarkSuite interface { + // BenchBuilder returns a function that executes a single benchmark call. + // The returned function is called in a loop until the context is canceled. + BenchBuilder(context.Context, *client.TeleportClient) (WorkloadFunc, error) +} + // Benchmark connects to remote server and executes requests in parallel according // to benchmark spec. It returns benchmark result when completed. // This is a blocking function that can be canceled via context argument. -func (c *Config) Benchmark(ctx context.Context, tc *client.TeleportClient) (Result, error) { +func (c *Config) Benchmark(ctx context.Context, tc *client.TeleportClient, suite BenchmarkSuite) (Result, error) { + if suite == nil { + return Result{}, trace.BadParameter("missing benchmark suite") + } + + workload, err := suite.BenchBuilder(ctx, tc) + if err != nil { + return Result{}, trace.Wrap(err) + } + tc.Stdout = io.Discard tc.Stderr = io.Discard tc.Stdin = &bytes.Buffer{} + var delay time.Duration ctx, cancel := context.WithCancel(ctx) defer cancel() @@ -180,11 +202,8 @@ func (c *Config) Benchmark(ctx context.Context, tc *client.TeleportClient) (Resu t := start.Add(delay) measure := benchMeasure{ ResponseStart: t, - command: c.Command, - client: tc, - interactive: c.Interactive, } - go work(ctx, measure, resultC) + go work(ctx, measure, resultC, workload) case <-ctx.Done(): close(requestsC) return @@ -226,13 +245,10 @@ type benchMeasure struct { ResponseStart time.Time End time.Time Error error - client *client.TeleportClient - command []string - interactive bool } -func work(ctx context.Context, m benchMeasure, send chan<- benchMeasure) { - m.Error = execute(m) +func work(ctx context.Context, m benchMeasure, send chan<- benchMeasure, workload WorkloadFunc) { + m.Error = workload(ctx) m.End = time.Now() select { case send <- m: @@ -241,33 +257,6 @@ func work(ctx context.Context, m benchMeasure, send chan<- benchMeasure) { } } -func execute(m benchMeasure) error { - if !m.interactive { - // do not use parent context that will cancel in flight requests - // because we give test some time to gracefully wrap up - // the in-flight connections to avoid extra errors - return m.client.SSH(context.TODO(), m.command, false) - } - config := m.client.Config - client, err := client.NewClient(&config) - if err != nil { - return err - } - reader, writer := io.Pipe() - defer reader.Close() - defer writer.Close() - client.Stdin = reader - out := &utils.SyncBuffer{} - client.Stdout = out - client.Stderr = out - err = m.client.SSH(context.TODO(), nil, false) - if err != nil { - return err - } - writer.Write([]byte(strings.Join(m.command, " ") + "\r\nexit\r\n")) - return nil -} - // makeTeleportClient creates an instance of a teleport client func makeTeleportClient(host, login, proxy string) (*client.TeleportClient, error) { c := client.Config{ diff --git a/lib/benchmark/benchmark_suites.go b/lib/benchmark/benchmark_suites.go new file mode 100644 index 0000000000000..79c1443dd2dc7 --- /dev/null +++ b/lib/benchmark/benchmark_suites.go @@ -0,0 +1,250 @@ +/* +Copyright 2023 Gravitational, Inc. +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. +*/ + +// Package benchmark package provides tools to run progressive or independent benchmarks against teleport services. +package benchmark + +import ( + "bytes" + "context" + "io" + "net/http" + "strings" + + "github.com/gravitational/trace" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/remotecommand" + "k8s.io/kubectl/pkg/scheme" + + "github.com/gravitational/teleport/lib/client" + "github.com/gravitational/teleport/lib/utils" +) + +// SSHBenchmark is a benchmark suite that runs a single SSH command +// against a Teleport node for a given duration and rate. +type SSHBenchmark struct { + // Command is a command to run + Command []string + // Interactive turns on interactive sessions + Interactive bool +} + +// BenchBuilder returns a WorkloadFunc for the given benchmark suite. +func (s SSHBenchmark) BenchBuilder(ctx context.Context, tc *client.TeleportClient) (WorkloadFunc, error) { + return func(ctx context.Context) error { + if !s.Interactive { + // do not use parent context that will cancel in flight requests + // because we give test some time to gracefully wrap up + // the in-flight connections to avoid extra errors + return tc.SSH(ctx, s.Command, false) + } + config := tc.Config + client, err := client.NewClient(&config) + if err != nil { + return err + } + reader, writer := io.Pipe() + defer reader.Close() + defer writer.Close() + client.Stdin = reader + out := &utils.SyncBuffer{} + client.Stdout = out + client.Stderr = out + err = tc.SSH(ctx, nil, false) + if err != nil { + return err + } + writer.Write([]byte(strings.Join(s.Command, " ") + "\r\nexit\r\n")) + return nil + }, nil +} + +// KubeListBenchmark is a benchmark suite that runs successive kubectl get pods +// against a Teleport Kubernetes proxy for a given duration and rate. +type KubeListBenchmark struct { + // Namespace is the Kubernetes namespace to run the command against. + // If empty, it will include pods from all namespaces. + Namespace string +} + +// BenchBuilder returns a WorkloadFunc for the given benchmark suite. +func (k KubeListBenchmark) BenchBuilder(ctx context.Context, tc *client.TeleportClient) (WorkloadFunc, error) { + restCfg, err := newKubernetesRestConfig(ctx, tc) + if err != nil { + return nil, trace.Wrap(err) + } + clientset, err := kubernetes.NewForConfig(restCfg) + if err != nil { + return nil, trace.Wrap(err) + } + + return func(ctx context.Context) error { + // List all pods in all namespaces. + _, err := clientset.CoreV1().Pods(k.Namespace).List(ctx, metav1.ListOptions{}) + return trace.Wrap(err) + }, nil +} + +// newKubernetesRestConfig returns a new rest.Config for the kubernetes cluster +// that the client wants to connected to. +func newKubernetesRestConfig(ctx context.Context, tc *client.TeleportClient) (*rest.Config, error) { + tlsClientConfig, err := getKubeTLSClientConfig(ctx, tc) + if err != nil { + return nil, trace.Wrap(err) + } + restConfig := &rest.Config{ + Host: tc.KubeClusterAddr(), + TLSClientConfig: tlsClientConfig, + APIPath: "/api", + ContentConfig: rest.ContentConfig{ + GroupVersion: &schema.GroupVersion{Version: "v1"}, + NegotiatedSerializer: scheme.Codecs, + }, + } + return restConfig, nil +} + +// getKubeTLSClientConfig returns a TLS client config for the kubernetes cluster +// that the client wants to connected to. +func getKubeTLSClientConfig(ctx context.Context, tc *client.TeleportClient) (rest.TLSClientConfig, error) { + var k *client.Key + err := client.RetryWithRelogin(ctx, tc, func() error { + var err error + k, err = tc.IssueUserCertsWithMFA(ctx, client.ReissueParams{ + RouteToCluster: tc.SiteName, + KubernetesCluster: tc.KubernetesCluster, + }, nil /*applyOpts*/) + return err + }) + if err != nil { + return rest.TLSClientConfig{}, trace.Wrap(err) + } + + certPem := k.KubeTLSCerts[tc.KubernetesCluster] + + rsaKeyPEM, err := k.PrivateKey.RSAPrivateKeyPEM() + if err != nil { + return rest.TLSClientConfig{}, trace.Wrap(err) + } + + credentials, err := tc.LocalAgent().GetCoreKey() + if err != nil { + return rest.TLSClientConfig{}, trace.Wrap(err) + } + + var clusterCAs [][]byte + if tc.LoadAllCAs { + clusterCAs = credentials.TLSCAs() + } else { + clusterCAs, err = credentials.RootClusterCAs() + if err != nil { + return rest.TLSClientConfig{}, trace.Wrap(err) + } + } + if len(clusterCAs) == 0 { + return rest.TLSClientConfig{}, trace.BadParameter("no trusted CAs found") + } + + tlsServerName := "" + if tc.TLSRoutingEnabled { + k8host, _ := tc.KubeProxyHostPort() + tlsServerName = client.GetKubeTLSServerName(k8host) + } + + return rest.TLSClientConfig{ + CAData: bytes.Join(clusterCAs, []byte("\n")), + CertData: certPem, + KeyData: rsaKeyPEM, + ServerName: tlsServerName, + }, nil +} + +// KubeListBenchmark is a benchmark suite that runs successive kubectl exec +// against a Teleport Kubernetes proxy for a given duration and rate. +type KubeExecBenchmark struct { + // Namespace is the Kubernetes namespace to run the command against. + Namespace string + // PodName is the name of the pod to run the command against. + PodName string + // ContainerName is the name of the container to run the command against. + ContainerName string + // Command is the command to run. + Command []string + // Interactive turns on interactive sessions + Interactive bool +} + +// BenchBuilder returns a WorkloadFunc for the given benchmark suite. +func (k KubeExecBenchmark) BenchBuilder(ctx context.Context, tc *client.TeleportClient) (WorkloadFunc, error) { + restCfg, err := newKubernetesRestConfig(ctx, tc) + if err != nil { + return nil, trace.Wrap(err) + } + if k.Interactive { + // If interactive, we need to set up a pty and we cannot use the + // stderr stream because the server will hang. + tc.Stderr = nil + } else { + // If not interactive, we need to set up stdin to be nil so that + // the server wont wait for input. + tc.Stdin = nil + } + exec, err := k.kubeExecOnPod(ctx, tc, restCfg) + if err != nil { + return nil, trace.Wrap(err) + } + return func(ctx context.Context) error { + stdin := tc.Stdin + if k.Interactive { + stdin = bytes.NewBuffer([]byte(strings.Join(k.Command, " ") + "\r\nexit\r\n")) + } + err := exec.StreamWithContext(ctx, remotecommand.StreamOptions{ + Stdin: stdin, + Stdout: tc.Stdout, + Stderr: tc.Stderr, + Tty: k.Interactive, + }) + return trace.Wrap(err) + }, nil +} + +func (k KubeExecBenchmark) kubeExecOnPod(ctx context.Context, tc *client.TeleportClient, restConfig *rest.Config) (remotecommand.Executor, error) { + restClient, err := rest.RESTClientFor(restConfig) + if err != nil { + return nil, trace.Wrap(err) + } + + req := restClient.Post(). + Resource("pods"). + Name(k.PodName). + Namespace(k.Namespace). + SubResource("exec") + + req.VersionedParams(&corev1.PodExecOptions{ + Container: k.ContainerName, + Command: k.Command, + Stdin: tc.Stdin != nil, + Stdout: tc.Stdout != nil, + Stderr: tc.Stderr != nil, + TTY: k.Interactive, + }, scheme.ParameterCodec) + + exec, err := remotecommand.NewSPDYExecutor(restConfig, http.MethodPost, req.URL()) + return exec, trace.Wrap(err) +} diff --git a/lib/benchmark/linear.go b/lib/benchmark/linear.go index 396ad090d7e11..38d3809c4eb13 100644 --- a/lib/benchmark/linear.go +++ b/lib/benchmark/linear.go @@ -42,7 +42,6 @@ func (lg *Linear) GetBenchmark() *Config { MinimumWindow: lg.MinimumWindow, MinimumMeasurements: lg.MinimumMeasurements, Rate: lg.currentRPS, - Command: lg.config.Command, } if lg.currentRPS < lg.LowerBound { diff --git a/lib/benchmark/linear_test.go b/lib/benchmark/linear_test.go index 60055d2fc4a2c..6e38864829340 100644 --- a/lib/benchmark/linear_test.go +++ b/lib/benchmark/linear_test.go @@ -26,8 +26,6 @@ import ( func TestGetBenchmark(t *testing.T) { initial := &Config{ Rate: 0, - Command: []string{"ls"}, - Interactive: false, MinimumWindow: time.Second * 30, MinimumMeasurements: 1000, } @@ -53,8 +51,6 @@ func TestGetBenchmark(t *testing.T) { func TestGetBenchmarkNotEvenMultiple(t *testing.T) { initial := &Config{ Rate: 0, - Command: []string{"ls"}, - Interactive: false, MinimumWindow: time.Second * 30, MinimumMeasurements: 1000, } diff --git a/lib/cache/cache_test.go b/lib/cache/cache_test.go index c52f5ace8fc52..3f1ac663c6274 100644 --- a/lib/cache/cache_test.go +++ b/lib/cache/cache_test.go @@ -1622,6 +1622,34 @@ func TestApplicationServers(t *testing.T) { }) } +// TestKubernetesServers tests that CRUD operations on kube servers are +// replicated from the backend to the cache. +func TestKubernetesServers(t *testing.T) { + t.Parallel() + + p := newTestPack(t, ForProxy) + t.Cleanup(p.Close) + + testResources(t, p, testFuncs[types.KubeServer]{ + newResource: func(name string) (types.KubeServer, error) { + app, err := types.NewKubernetesClusterV3(types.Metadata{Name: name}, types.KubernetesClusterSpecV3{}) + require.NoError(t, err) + return types.NewKubernetesServerV3FromCluster(app, "host", uuid.New().String()) + }, + create: withKeepalive(p.presenceS.UpsertKubernetesServer), + list: func(ctx context.Context) ([]types.KubeServer, error) { + return p.presenceS.GetKubernetesServers(ctx) + }, + cacheList: func(ctx context.Context) ([]types.KubeServer, error) { + return p.cache.GetKubernetesServers(ctx) + }, + update: withKeepalive(p.presenceS.UpsertKubernetesServer), + deleteAll: func(ctx context.Context) error { + return p.presenceS.DeleteAllKubernetesServers(ctx) + }, + }) +} + // TestApps tests that CRUD operations on application resources are // replicated from the backend to the cache. func TestApps(t *testing.T) { diff --git a/lib/client/api.go b/lib/client/api.go index a4af7ff464b1e..999b98b467fb5 100644 --- a/lib/client/api.go +++ b/lib/client/api.go @@ -22,6 +22,7 @@ import ( "crypto/x509" "encoding/json" "encoding/pem" + "errors" "fmt" "io" "net" @@ -44,10 +45,12 @@ import ( "golang.org/x/crypto/ssh/agent" "golang.org/x/net/http2" "golang.org/x/sync/errgroup" + "google.golang.org/grpc" "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/client" "github.com/gravitational/teleport/api/client/proto" + proxyclient "github.com/gravitational/teleport/api/client/proxy" "github.com/gravitational/teleport/api/client/webclient" "github.com/gravitational/teleport/api/constants" apidefaults "github.com/gravitational/teleport/api/defaults" @@ -74,7 +77,6 @@ import ( "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/session" "github.com/gravitational/teleport/lib/shell" - "github.com/gravitational/teleport/lib/srv/alpnproxy" alpncommon "github.com/gravitational/teleport/lib/srv/alpnproxy/common" "github.com/gravitational/teleport/lib/sshutils" "github.com/gravitational/teleport/lib/sshutils/scp" @@ -365,9 +367,6 @@ type Config struct { // MockSSOLogin is used in tests for mocking the SSO login response. MockSSOLogin SSOLoginFunc - // MockConnectToProxy is used in tests to override connection to proxy - MockConnectToProxy ConnectToProxyFunc - // HomePath is where tsh stores profiles HomePath string @@ -415,6 +414,10 @@ type Config struct { // the ssh, scp, and ls commands can use headless login. Other commands will ignore // headless auth connector and default to local instead. AllowHeadless bool + + // DialOpts used by the api.client.proxy.Client when establishing a connection to + // the proxy server. Used by tests. + DialOpts []grpc.DialOption } // CachePolicy defines cache policy for local clients @@ -622,7 +625,7 @@ func (c *Config) LoadProfile(ps ProfileStore, proxyAddr string) error { log.Warnf("Unable to parse dynamic port forwarding in user profile: %v.", err) } - if required, ok := alpnproxy.OverwriteALPNConnUpgradeRequirementByEnv(c.WebProxyAddr); ok { + if required, ok := client.OverwriteALPNConnUpgradeRequirementByEnv(c.WebProxyAddr); ok { c.TLSRoutingConnUpgradeRequired = required } log.Infof("ALPN connection upgrade required for %q: %v.", c.WebProxyAddr, c.TLSRoutingConnUpgradeRequired) @@ -1181,7 +1184,7 @@ func (tc *TeleportClient) RootClusterName(ctx context.Context) (string, error) { // getTargetNodes returns a list of node addresses this SSH command needs to // operate on. -func (tc *TeleportClient) getTargetNodes(ctx context.Context, proxy *ProxyClient) ([]string, error) { +func (tc *TeleportClient) getTargetNodes(ctx context.Context, clt client.ListResourcesClient) ([]string, error) { ctx, span := tc.Tracer.Start( ctx, "teleportClient/getTargetNodes", @@ -1203,15 +1206,17 @@ func (tc *TeleportClient) getTargetNodes(ctx context.Context, proxy *ProxyClient } // find the nodes matching the labels that were provided - nodes, err := proxy.FindNodesByFilters(ctx, *tc.DefaultResourceFilter()) + filter := tc.DefaultResourceFilter() + filter.ResourceType = types.KindNode + resources, err := client.GetResourcesWithFilters(ctx, clt, *filter) if err != nil { return nil, trace.Wrap(err) } - retval := make([]string, 0, len(nodes)) - for i := 0; i < len(nodes); i++ { + retval := make([]string, 0, len(resources)) + for _, resource := range resources { // always dial nodes by UUID - retval = append(retval, fmt.Sprintf("%s:0", nodes[i].GetName())) + retval = append(retval, fmt.Sprintf("%s:0", resource.GetName())) } return retval, nil @@ -1427,7 +1432,8 @@ func (tc *TeleportClient) SSH(ctx context.Context, command []string, runLocally "teleportClient/SSH", oteltrace.WithSpanKind(oteltrace.SpanKindClient), oteltrace.WithAttributes( - attribute.String("proxy", tc.Config.WebProxyAddr), + attribute.String("proxy_web", tc.Config.WebProxyAddr), + attribute.String("proxy_ssh", tc.Config.SSHProxyAddr), ), ) defer span.End() @@ -1436,14 +1442,15 @@ func (tc *TeleportClient) SSH(ctx context.Context, command []string, runLocally if !tc.Config.ProxySpecified() { return trace.BadParameter("proxy server is not specified") } - proxyClient, err := tc.ConnectToProxy(ctx) + + clt, err := tc.ConnectToCluster(ctx) if err != nil { return trace.Wrap(err) } - defer proxyClient.Close() + defer clt.Close() // which nodes are we executing this commands on? - nodeAddrs, err := tc.getTargetNodes(ctx, proxyClient) + nodeAddrs, err := tc.getTargetNodes(ctx, clt.AuthClient) if err != nil { return trace.Wrap(err) } @@ -1452,9 +1459,9 @@ func (tc *TeleportClient) SSH(ctx context.Context, command []string, runLocally } if len(nodeAddrs) > 1 { - return tc.runShellOrCommandOnMultipleNodes(ctx, nodeAddrs, proxyClient, command) + return tc.runShellOrCommandOnMultipleNodes(ctx, clt, nodeAddrs, command) } - return tc.runShellOrCommandOnSingleNode(ctx, nodeAddrs[0], proxyClient, command, runLocally) + return tc.runShellOrCommandOnSingleNode(ctx, clt, nodeAddrs[0], command, runLocally) } // ConnectToNode attempts to establish a connection to the node resolved to by the provided @@ -1463,41 +1470,39 @@ func (tc *TeleportClient) SSH(ctx context.Context, command []string, runLocally // ceremony is performed and another connection is attempted with the freshly minted // certificates. If it is not required, then the original Access Denied error from the node // is returned. -func (tc *TeleportClient) ConnectToNode(ctx context.Context, proxyClient *ProxyClient, nodeDetails NodeDetails, user string) (*NodeClient, error) { +func (tc *TeleportClient) ConnectToNode(ctx context.Context, clt *ClusterClient, nodeDetails NodeDetails, user string) (*NodeClient, error) { node := nodeName(nodeDetails.Addr) ctx, span := tc.Tracer.Start( ctx, "teleportClient/ConnectToNode", oteltrace.WithSpanKind(oteltrace.SpanKindClient), oteltrace.WithAttributes( - attribute.String("site", nodeDetails.Cluster), + attribute.String("cluster", nodeDetails.Cluster), attribute.String("node", node), ), ) defer span.End() - // attempt to use the existing credentials first - authMethods := proxyClient.authMethods + sshConfig := clt.ProxyClient.SSHConfig(user) - // if per-session mfa is required, perform the mfa ceremony to get - // new certificates and use them instead + // if mfa is required generate new config after + // performing the mfa ceremony if nodeDetails.MFACheck != nil && nodeDetails.MFACheck.Required { - am, err := proxyClient.sessionSSHCertificate(ctx, nodeDetails) + cfg, err := clt.SessionSSHConfig(ctx, user, nodeDetails) if err != nil { return nil, trace.Wrap(err) } - authMethods = am + sshConfig = cfg } - // grab the cluster details - details, err := proxyClient.clusterDetails(ctx) + // try connecting to the node + conn, details, err := clt.ProxyClient.DialHost(ctx, nodeDetails.Addr, nodeDetails.Cluster, tc.localAgent.ExtendedAgent) if err != nil { return nil, trace.Wrap(err) } - // try connecting to the node - nodeClient, connectErr := proxyClient.ConnectToNode(ctx, nodeDetails, user, details, authMethods) + nodeClient, connectErr := NewNodeClient(ctx, sshConfig, conn, nodeDetails.ProxyFormat(), nodeDetails.Addr, tc, details.FIPS) switch { case connectErr == nil: // no error return client return nodeClient, nil @@ -1508,73 +1513,57 @@ func (tc *TeleportClient) ConnectToNode(ctx context.Context, proxyClient *ProxyC } // access was denied, determine if it was because per-session mfa is required - clt, err := proxyClient.ConnectToCluster(ctx, nodeDetails.Cluster) - if err != nil { - // return the connection error instead of any errors from connecting to auth - return nil, trace.Wrap(connectErr) - } - - check, err := clt.IsMFARequired(ctx, &proto.IsMFARequiredRequest{ + nodeDetails.MFACheck, err = clt.AuthClient.IsMFARequired(ctx, &proto.IsMFARequiredRequest{ Target: &proto.IsMFARequiredRequest_Node{ Node: &proto.NodeLogin{ Node: node, - Login: proxyClient.hostLogin, + Login: tc.HostLogin, }, }, }) if err != nil { + log.Warnf("Unable to determine if session mfa is required: %v", err) return nil, trace.Wrap(connectErr) } // per-session mfa isn't required, the user simply does not // have access to the provided node - if !check.Required { + if !nodeDetails.MFACheck.Required { return nil, trace.Wrap(connectErr) } - // per-session mfa is required, perform the mfa ceremony - key, err := proxyClient.IssueUserCertsWithMFA( - ctx, - ReissueParams{ - NodeName: node, - RouteToCluster: nodeDetails.Cluster, - MFACheck: check, - AuthClient: clt, - }, - func(ctx context.Context, proxyAddr string, c *proto.MFAAuthenticateChallenge) (*proto.MFAAuthenticateResponse, error) { - return tc.PromptMFAChallenge(ctx, proxyAddr, c, nil /* applyOpts */) - }, - ) + // generate new config after performing the mfa ceremony + cfg, err := clt.SessionSSHConfig(ctx, user, nodeDetails) if err != nil { return nil, trace.Wrap(err) } - // try connecting to the node again with the newly acquired certificates - newAuthMethods, err := key.AsAuthMethod() + conn, details, err = clt.ProxyClient.DialHost(ctx, nodeDetails.Addr, nodeDetails.Cluster, tc.localAgent.ExtendedAgent) if err != nil { return nil, trace.Wrap(err) } - nodeClient, err = proxyClient.ConnectToNode(ctx, nodeDetails, user, details, []ssh.AuthMethod{newAuthMethods}) + nodeClient, err = NewNodeClient(ctx, cfg, conn, nodeDetails.ProxyFormat(), nodeDetails.Addr, tc, details.FIPS) return nodeClient, trace.Wrap(err) } -func (tc *TeleportClient) runShellOrCommandOnSingleNode(ctx context.Context, nodeAddr string, proxyClient *ProxyClient, command []string, runLocally bool) error { +func (tc *TeleportClient) runShellOrCommandOnSingleNode(ctx context.Context, clt *ClusterClient, nodeAddr string, command []string, runLocally bool) error { + cluster := clt.ClusterName() ctx, span := tc.Tracer.Start( ctx, "teleportClient/runShellOrCommandOnSingleNode", oteltrace.WithSpanKind(oteltrace.SpanKindClient), oteltrace.WithAttributes( - attribute.String("site", tc.SiteName), attribute.String("node", nodeAddr), + attribute.String("cluster", cluster), ), ) defer span.End() nodeClient, err := tc.ConnectToNode( ctx, - proxyClient, - NodeDetails{Addr: nodeAddr, Namespace: tc.Namespace, Cluster: tc.SiteName}, + clt, + NodeDetails{Addr: nodeAddr, Namespace: tc.Namespace, Cluster: cluster}, tc.Config.HostLogin, ) if err != nil { @@ -1587,16 +1576,27 @@ func (tc *TeleportClient) runShellOrCommandOnSingleNode(ctx context.Context, nod return trace.Wrap(err) } - // If no remote command execution was requested, block on the context which - // will unblock upon error or SIGINT. + // If no remote command execution was requested block on which ever comes first: + // 1) the context which will unblock upon error or user terminating the process + // 2) ssh.Client.Wait which will unblock when the connection has shut down if tc.NoRemoteExec { - log.Debugf("Connected to node, no remote command execution was requested, blocking until context closes.") - <-ctx.Done() - - // Only return an error if the context was canceled by something other than SIGINT. - if ctx.Err() != context.Canceled { - return ctx.Err() + connClosed := make(chan error, 1) + go func() { + connClosed <- nodeClient.Client.Wait() + }() + log.Debugf("Connected to node, no remote command execution was requested, blocking indefinitely.") + select { + case <-ctx.Done(): + // Only return an error if the context was canceled by something other than SIGINT. + if err := ctx.Err(); !errors.Is(err, context.Canceled) { + return trace.Wrap(err) + } + case err := <-connClosed: + if !errors.Is(err, io.EOF) { + return trace.Wrap(err) + } } + return nil } @@ -1611,18 +1611,19 @@ func (tc *TeleportClient) runShellOrCommandOnSingleNode(ctx context.Context, nod if len(command) > 0 { // Reuse the existing nodeClient we connected above. - return tc.runCommand(ctx, nodeClient, command) + return nodeClient.RunCommand(ctx, command) } return tc.runShell(ctx, nodeClient, types.SessionPeerMode, nil, nil) } -func (tc *TeleportClient) runShellOrCommandOnMultipleNodes(ctx context.Context, nodeAddrs []string, proxyClient *ProxyClient, command []string) error { +func (tc *TeleportClient) runShellOrCommandOnMultipleNodes(ctx context.Context, clt *ClusterClient, nodeAddrs []string, command []string) error { + cluster := clt.ClusterName() ctx, span := tc.Tracer.Start( ctx, "teleportClient/runShellOrCommandOnMultipleNodes", oteltrace.WithSpanKind(oteltrace.SpanKindClient), oteltrace.WithAttributes( - attribute.String("site", tc.SiteName), + attribute.String("cluster", cluster), attribute.StringSlice("node", nodeAddrs), ), ) @@ -1631,15 +1632,15 @@ func (tc *TeleportClient) runShellOrCommandOnMultipleNodes(ctx context.Context, // There was a command provided, run a non-interactive session against each match if len(command) > 0 { fmt.Printf("\x1b[1mWARNING\x1b[0m: Multiple nodes matched label selector, running command on all.\n") - return tc.runCommandOnNodes(ctx, tc.SiteName, nodeAddrs, proxyClient, command) + return tc.runCommandOnNodes(ctx, clt, nodeAddrs, command) } // Issue "shell" request to the first matching node. fmt.Printf("\x1b[1mWARNING\x1b[0m: Multiple nodes match the label selector, picking first: %q\n", nodeAddrs[0]) nodeClient, err := tc.ConnectToNode( ctx, - proxyClient, - NodeDetails{Addr: nodeAddrs[0], Namespace: tc.Namespace, Cluster: tc.SiteName}, + clt, + NodeDetails{Addr: nodeAddrs[0], Namespace: tc.Namespace, Cluster: cluster}, tc.Config.HostLogin, ) if err != nil { @@ -1695,15 +1696,14 @@ func (tc *TeleportClient) Join(ctx context.Context, mode types.SessionParticipan if !tc.Config.ProxySpecified() { return trace.BadParameter("proxy server is not specified") } - proxyClient, err := tc.ConnectToProxy(ctx) + clt, err := tc.ConnectToCluster(ctx) if err != nil { return trace.Wrap(err) } - defer proxyClient.Close() - site := proxyClient.CurrentCluster() + defer clt.Close() // Session joining is not supported in proxy recording mode - if recConfig, err := site.GetSessionRecordingConfig(ctx); err != nil { + if recConfig, err := clt.AuthClient.GetSessionRecordingConfig(ctx); err != nil { // If the user can't see the recording mode, just let them try joining below if !trace.IsAccessDenied(err) { return trace.Wrap(err) @@ -1712,7 +1712,7 @@ func (tc *TeleportClient) Join(ctx context.Context, mode types.SessionParticipan return trace.BadParameter("session joining is not supported in proxy recording mode") } - session, err := site.GetSessionTracker(ctx, string(sessionID)) + session, err := clt.AuthClient.GetSessionTracker(ctx, string(sessionID)) if err != nil { if trace.IsNotFound(err) { return trace.NotFound("session %q not found or it has ended", sessionID) @@ -1726,8 +1726,8 @@ func (tc *TeleportClient) Join(ctx context.Context, mode types.SessionParticipan // connect to server: nc, err := tc.ConnectToNode(ctx, - proxyClient, - NodeDetails{Addr: session.GetAddress() + ":0", Namespace: tc.Namespace, Cluster: tc.SiteName}, + clt, + NodeDetails{Addr: session.GetAddress() + ":0", Namespace: tc.Namespace, Cluster: clt.ClusterName()}, tc.Config.HostLogin, ) if err != nil { @@ -1747,11 +1747,13 @@ func (tc *TeleportClient) Join(ctx context.Context, mode types.SessionParticipan if mode == types.SessionModeratorMode { beforeStart = func(out io.Writer) { nc.OnMFA = func() { - runPresenceTask(presenceCtx, out, site, tc, session.GetSessionID()) + runPresenceTask(presenceCtx, out, clt.AuthClient, tc, session.GetSessionID()) } } } + fmt.Printf("Joining session with participant mode: %v. \n\n", mode) + // running shell with a given session means "join" it: err = tc.runShell(ctx, nc, mode, session, beforeStart) return trace.Wrap(err) @@ -1894,17 +1896,17 @@ func (tc *TeleportClient) ExecuteSCP(ctx context.Context, serverAddr string, cmd return trace.BadParameter("proxy server is not specified") } - proxyClient, err := tc.ConnectToProxy(ctx) + clt, err := tc.ConnectToCluster(ctx) if err != nil { return trace.Wrap(err) } - defer proxyClient.Close() + defer clt.Close() nodeClient, err := tc.ConnectToNode( ctx, - proxyClient, + clt, // We append the ":0" to tell the server to figure out the port for us. - NodeDetails{Addr: serverAddr + ":0", Namespace: tc.Namespace, Cluster: tc.SiteName}, + NodeDetails{Addr: serverAddr + ":0", Namespace: tc.Namespace, Cluster: clt.ClusterName()}, tc.Config.HostLogin, ) if err != nil { @@ -2050,16 +2052,16 @@ func (tc *TeleportClient) TransferFiles(ctx context.Context, hostLogin, nodeAddr if !tc.Config.ProxySpecified() { return trace.BadParameter("proxy server is not specified") } - proxyClient, err := tc.ConnectToProxy(ctx) + clt, err := tc.ConnectToCluster(ctx) if err != nil { return trace.Wrap(err) } - defer proxyClient.Close() + defer clt.Close() client, err := tc.ConnectToNode( ctx, - proxyClient, - NodeDetails{Addr: nodeAddr, Namespace: tc.Namespace, Cluster: tc.SiteName}, + clt, + NodeDetails{Addr: nodeAddr, Namespace: tc.Namespace, Cluster: clt.ClusterName()}, hostLogin, ) if err != nil { @@ -2089,7 +2091,9 @@ func (tc *TeleportClient) ListNodesWithFilters(ctx context.Context) ([]types.Ser } defer proxyClient.Close() - servers, err := proxyClient.FindNodesByFilters(ctx, *tc.DefaultResourceFilter()) + filter := tc.DefaultResourceFilter() + filter.ResourceType = types.KindNode + servers, err := proxyClient.FindNodesByFilters(ctx, *filter) if err != nil { return nil, trace.Wrap(err) } @@ -2478,28 +2482,26 @@ func commandLimit(ctx context.Context, getter roleGetter, mfaRequired bool) int } // runCommandOnNodes executes a given bash command on a bunch of remote nodes. -func (tc *TeleportClient) runCommandOnNodes(ctx context.Context, siteName string, nodeAddresses []string, proxyClient *ProxyClient, command []string) error { +func (tc *TeleportClient) runCommandOnNodes(ctx context.Context, clt *ClusterClient, nodeAddresses []string, command []string) error { + cluster := clt.ClusterName() ctx, span := tc.Tracer.Start( ctx, "teleportClient/runCommandOnNodes", oteltrace.WithSpanKind(oteltrace.SpanKindClient), + oteltrace.WithAttributes( + attribute.String("cluster", cluster), + ), ) defer span.End() - clt, err := proxyClient.ConnectToCluster(ctx, siteName) - if err != nil { - return trace.Wrap(err) - } - defer clt.Close() - // Let's check if the first node requires mfa. // If it's required, run commands sequentially to avoid // race conditions and weird ux during mfa. - mfaRequiredCheck, err := clt.IsMFARequired(ctx, &proto.IsMFARequiredRequest{ + mfaRequiredCheck, err := clt.AuthClient.IsMFARequired(ctx, &proto.IsMFARequiredRequest{ Target: &proto.IsMFARequiredRequest_Node{ Node: &proto.NodeLogin{ Node: nodeName(nodeAddresses[0]), - Login: proxyClient.hostLogin, + Login: tc.Config.HostLogin, }, }, }) @@ -2508,7 +2510,7 @@ func (tc *TeleportClient) runCommandOnNodes(ctx context.Context, siteName string } g, gctx := errgroup.WithContext(ctx) - g.SetLimit(commandLimit(ctx, clt, mfaRequiredCheck.Required)) + g.SetLimit(commandLimit(ctx, clt.AuthClient, mfaRequiredCheck.Required)) for _, address := range nodeAddresses { address := address g.Go(func() error { @@ -2522,11 +2524,11 @@ func (tc *TeleportClient) runCommandOnNodes(ctx context.Context, siteName string nodeClient, err := tc.ConnectToNode( ctx, - proxyClient, + clt, NodeDetails{ Addr: address, Namespace: tc.Namespace, - Cluster: siteName, + Cluster: cluster, MFACheck: mfaRequiredCheck, }, tc.Config.HostLogin, @@ -2539,47 +2541,13 @@ func (tc *TeleportClient) runCommandOnNodes(ctx context.Context, siteName string fmt.Printf("Running command on %v:\n", nodeName(address)) - return trace.Wrap(tc.runCommand(ctx, nodeClient, command)) + return trace.Wrap(nodeClient.RunCommand(ctx, command)) }) } return trace.Wrap(g.Wait()) } -// runCommand executes a given bash command on an established NodeClient. -func (tc *TeleportClient) runCommand(ctx context.Context, nodeClient *NodeClient, command []string) error { - ctx, span := tc.Tracer.Start( - ctx, - "teleportClient/runCommand", - oteltrace.WithSpanKind(oteltrace.SpanKindClient), - ) - defer span.End() - - nodeSession, err := newSession(ctx, nodeClient, nil, tc.newSessionEnv(), tc.Stdin, tc.Stdout, tc.Stderr, tc.EnableEscapeSequences) - if err != nil { - return trace.Wrap(err) - } - defer nodeSession.Close() - if err := nodeSession.runCommand(ctx, types.SessionPeerMode, command, tc.OnShellCreated, tc.Config.InteractiveCommand); err != nil { - originErr := trace.Unwrap(err) - exitErr, ok := originErr.(*ssh.ExitError) - if ok { - tc.ExitStatus = exitErr.ExitStatus() - } else { - // if an error occurs, but no exit status is passed back, GoSSH returns - // a generic error like this. in this case the error message is printed - // to stderr by the remote process so we have to quietly return 1: - if strings.Contains(originErr.Error(), "exited without exit status") { - tc.ExitStatus = 1 - } - } - - return trace.Wrap(err) - } - - return nil -} - func (tc *TeleportClient) newSessionEnv() map[string]string { env := map[string]string{ teleport.SSHSessionWebproxyAddr: tc.WebProxyAddr, @@ -2689,52 +2657,72 @@ func formatConnectToProxyErr(err error) error { return err } -// ConnectToProxyFunc is used in tests to override connection to proxy function. -type ConnectToProxyFunc func(ctx context.Context) (*ProxyClient, error) - -// ConnectToProxy will dial to the proxy server and return a ProxyClient when -// successful. If the passed in context is canceled, this function will return -// a trace.ConnectionProblem right away. -func (tc *TeleportClient) ConnectToProxy(ctx context.Context) (*ProxyClient, error) { - if tc.Config.MockConnectToProxy != nil { - return tc.Config.MockConnectToProxy(ctx) - } - +// ConnectToCluster will dial the auth and proxy server and return a ClusterClient when +// successful. +func (tc *TeleportClient) ConnectToCluster(ctx context.Context) (*ClusterClient, error) { ctx, span := tc.Tracer.Start( ctx, - "teleportClient/ConnectToProxy", + "teleportClient/ConnectToCluster", oteltrace.WithSpanKind(oteltrace.SpanKindClient), oteltrace.WithAttributes( - attribute.String("proxy", tc.Config.WebProxyAddr), + attribute.String("proxy_web", tc.Config.WebProxyAddr), + attribute.String("proxy_ssh", tc.Config.SSHProxyAddr), ), ) defer span.End() - var err error - var proxyClient *ProxyClient + cfg, err := tc.generateClientConfig(ctx) + if err != nil { + return nil, trace.Wrap(err) + } - // Use connectContext and the cancel function to signal when a response is - // returned from connectToProxy. - connectContext, cancel := context.WithCancel(ctx) - go func() { - defer cancel() - proxyClient, err = tc.connectToProxy(ctx) - }() + tlsConfig, err := tc.LoadTLSConfig() + if err != nil { + return nil, trace.Wrap(err) + } - select { - // ConnectToProxy returned a result, return that back to the caller. - case <-connectContext.Done(): - return proxyClient, trace.Wrap(formatConnectToProxyErr(err)) - // The passed in context timed out. This is often due to the network being - // down and the user hitting Ctrl-C. - case <-ctx.Done(): - return nil, trace.ConnectionProblem(ctx.Err(), "connection canceled") + pclt, err := proxyclient.NewClient(ctx, proxyclient.ClientConfig{ + ProxyWebAddress: tc.WebProxyAddr, + ProxySSHAddress: cfg.proxyAddress, + TLSRoutingEnabled: tc.TLSRoutingEnabled, + TLSConfig: tlsConfig, + DialOpts: tc.Config.DialOpts, + UnaryInterceptors: []grpc.UnaryClientInterceptor{utils.GRPCClientUnaryErrorInterceptor}, + StreamInterceptors: []grpc.StreamClientInterceptor{utils.GRPCClientStreamErrorInterceptor}, + SSHDialer: proxyclient.SSHDialerFunc(func(ctx context.Context, network string, addr string, config *ssh.ClientConfig) (*tracessh.Client, error) { + clt, err := makeProxySSHClient(ctx, tc, config) + return clt, trace.Wrap(err) + }), + SSHConfig: cfg.ClientConfig, + }) + if err != nil { + return nil, trace.Wrap(err) + } + + aclt, err := auth.NewClient(pclt.ClientConfig(ctx, pclt.ClusterName())) + if err != nil { + return nil, trace.NewAggregate(err, pclt.Close()) } + + return &ClusterClient{ + tc: tc, + ProxyClient: pclt, + AuthClient: aclt, + Tracer: tc.Tracer, + }, nil } -// connectToProxy will dial to the proxy server and return a ProxyClient when -// successful. -func (tc *TeleportClient) connectToProxy(ctx context.Context) (*ProxyClient, error) { +// clientConfig wraps ssh.ClientConfig with additional +// information about a cluster. +type clientConfig struct { + *ssh.ClientConfig + proxyAddress string + clusterName func() string +} + +// generateClientConfig returns clientConfig that can be used to establish a +// connection to a cluster. +func (tc *TeleportClient) generateClientConfig(ctx context.Context) (*clientConfig, error) { sshProxyAddr := tc.Config.SSHProxyAddr hostKeyCallback := tc.HostKeyCallback @@ -2787,13 +2775,64 @@ func (tc *TeleportClient) connectToProxy(ctx context.Context) (*ProxyClient, err return nil, trace.BadParameter("no SSH auth methods loaded, are you logged in?") } - sshConfig := &ssh.ClientConfig{ - User: tc.getProxySSHPrincipal(), - HostKeyCallback: hostKeyCallback, - Auth: authMethods, + return &clientConfig{ + ClientConfig: &ssh.ClientConfig{ + User: tc.getProxySSHPrincipal(), + HostKeyCallback: hostKeyCallback, + Auth: authMethods, + Timeout: apidefaults.DefaultIOTimeout, + }, + proxyAddress: sshProxyAddr, + clusterName: clusterName, + }, nil +} + +// ConnectToProxy will dial to the proxy server and return a ProxyClient when +// successful. If the passed in context is canceled, this function will return +// a trace.ConnectionProblem right away. +func (tc *TeleportClient) ConnectToProxy(ctx context.Context) (*ProxyClient, error) { + ctx, span := tc.Tracer.Start( + ctx, + "teleportClient/ConnectToProxy", + oteltrace.WithSpanKind(oteltrace.SpanKindClient), + oteltrace.WithAttributes( + attribute.String("proxy_web", tc.Config.WebProxyAddr), + attribute.String("proxy_ssh", tc.Config.SSHProxyAddr), + ), + ) + defer span.End() + + var err error + var proxyClient *ProxyClient + + // Use connectContext and the cancel function to signal when a response is + // returned from connectToProxy. + connectContext, cancel := context.WithCancel(ctx) + go func() { + defer cancel() + proxyClient, err = tc.connectToProxy(connectContext) + }() + + select { + // connectToProxy returned a result, return that back to the caller. + case <-connectContext.Done(): + return proxyClient, trace.Wrap(formatConnectToProxyErr(err)) + // The passed in context timed out. This is often due to the network being + // down and the user hitting Ctrl-C. + case <-ctx.Done(): + return nil, trace.ConnectionProblem(ctx.Err(), "connection canceled") + } +} + +// connectToProxy will dial to the proxy server and return a ProxyClient when +// successful. +func (tc *TeleportClient) connectToProxy(ctx context.Context) (*ProxyClient, error) { + cfg, err := tc.generateClientConfig(ctx) + if err != nil { + return nil, trace.Wrap(err) } - sshClient, err := makeProxySSHClient(ctx, tc, sshConfig) + sshClient, err := makeProxySSHClient(ctx, tc, cfg.ClientConfig) if err != nil { return nil, trace.Wrap(err) } @@ -2801,12 +2840,12 @@ func (tc *TeleportClient) connectToProxy(ctx context.Context) (*ProxyClient, err pc := &ProxyClient{ teleportClient: tc, Client: sshClient, - proxyAddress: sshProxyAddr, - proxyPrincipal: sshConfig.User, - hostKeyCallback: sshConfig.HostKeyCallback, - authMethods: sshConfig.Auth, + proxyAddress: cfg.proxyAddress, + proxyPrincipal: cfg.User, + hostKeyCallback: cfg.HostKeyCallback, + authMethods: cfg.Auth, hostLogin: tc.HostLogin, - siteName: clusterName(), + siteName: cfg.clusterName(), clientAddr: tc.ClientAddr, Tracer: tc.Tracer, } @@ -3083,7 +3122,7 @@ func (tc *TeleportClient) Login(ctx context.Context) (*Key, error) { } // Perform the ALPN test once at login. - tc.TLSRoutingConnUpgradeRequired = alpnproxy.IsALPNConnUpgradeRequired(tc.WebProxyAddr, tc.InsecureSkipVerify) + tc.TLSRoutingConnUpgradeRequired = client.IsALPNConnUpgradeRequired(tc.WebProxyAddr, tc.InsecureSkipVerify) // Get the SSHLoginFunc that matches client and cluster settings. sshLoginFunc, err := tc.getSSHLoginFunc(pr) @@ -4490,7 +4529,8 @@ func (tc *TeleportClient) HeadlessApprove(ctx context.Context, headlessAuthentic "teleportClient/HeadlessApprove", oteltrace.WithSpanKind(oteltrace.SpanKindClient), oteltrace.WithAttributes( - attribute.String("proxy", tc.Config.WebProxyAddr), + attribute.String("proxy_web", tc.Config.WebProxyAddr), + attribute.String("proxy_ssh", tc.Config.SSHProxyAddr), ), ) defer span.End() diff --git a/lib/client/client.go b/lib/client/client.go index cae05a837b732..cc1e6941858ae 100644 --- a/lib/client/client.go +++ b/lib/client/client.go @@ -48,6 +48,7 @@ import ( tracessh "github.com/gravitational/teleport/api/observability/tracing/ssh" "github.com/gravitational/teleport/api/types" apievents "github.com/gravitational/teleport/api/types/events" + "github.com/gravitational/teleport/api/utils/retryutils" "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/events" @@ -1257,29 +1258,6 @@ func nodeName(node string) string { return n } -// clusterDetails retrieves information about the current cluster needed to connect to nodes. -func (proxy *ProxyClient) clusterDetails(ctx context.Context) (sshutils.ClusterDetails, error) { - ctx, span := proxy.Tracer.Start( - ctx, - "proxyClient/clusterDetails", - oteltrace.WithSpanKind(oteltrace.SpanKindClient), - ) - defer span.End() - - var details sshutils.ClusterDetails - ok, resp, err := proxy.Client.SendRequest(ctx, teleport.ClusterDetailsReqType, true, nil) - if err != nil { - return details, trace.Wrap(err) - } - - if ok { - err = ssh.Unmarshal(resp, &details) - return details, trace.Wrap(err) - } - - return details, trace.BadParameter("failed to get cluster details") -} - // dialAuthServer returns auth server connection forwarded via proxy func (proxy *ProxyClient) dialAuthServer(ctx context.Context, clusterName string) (net.Conn, error) { ctx, span := proxy.Tracer.Start( @@ -1400,7 +1378,7 @@ func requestSubsystem(ctx context.Context, session *tracessh.Session, name strin // ConnectToNode connects to the ssh server via Proxy. // It returns connected and authenticated NodeClient -func (proxy *ProxyClient) ConnectToNode(ctx context.Context, nodeAddress NodeDetails, user string, details sshutils.ClusterDetails, authMethods []ssh.AuthMethod) (*NodeClient, error) { +func (proxy *ProxyClient) ConnectToNode(ctx context.Context, nodeAddress NodeDetails, user string, details sshutils.ClusterDetails) (*NodeClient, error) { ctx, span := proxy.Tracer.Start( ctx, "proxyClient/ConnectToNode", @@ -1415,7 +1393,7 @@ func (proxy *ProxyClient) ConnectToNode(ctx context.Context, nodeAddress NodeDet log.Infof("Client=%v connecting to node=%v", proxy.clientAddr, nodeAddress) if len(proxy.teleportClient.JumpHosts) > 0 { - return proxy.PortForwardToNode(ctx, nodeAddress, user, details, authMethods) + return proxy.PortForwardToNode(ctx, nodeAddress, user, details, proxy.authMethods) } // parse destination first: @@ -1497,11 +1475,13 @@ func (proxy *ProxyClient) ConnectToNode(ctx context.Context, nodeAddress NodeDet sshConfig := &ssh.ClientConfig{ User: user, - Auth: authMethods, + Auth: proxy.authMethods, HostKeyCallback: proxy.hostKeyCallback, } - nc, err := NewNodeClient(ctx, sshConfig, pipeNetConn, nodeAddress.ProxyFormat(), proxy.teleportClient, details.FIPSEnabled) + nc, err := NewNodeClient(ctx, sshConfig, pipeNetConn, + nodeAddress.ProxyFormat(), nodeAddress.Addr, + proxy.teleportClient, details.FIPSEnabled) return nc, trace.Wrap(err) } @@ -1547,12 +1527,13 @@ func (proxy *ProxyClient) PortForwardToNode(ctx context.Context, nodeAddress Nod HostKeyCallback: proxy.hostKeyCallback, } - nc, err := NewNodeClient(ctx, sshConfig, proxyConn, nodeAddress.Addr, proxy.teleportClient, details.FIPSEnabled) + nc, err := NewNodeClient(ctx, sshConfig, proxyConn, nodeAddress.Addr, "", proxy.teleportClient, details.FIPSEnabled) return nc, trace.Wrap(err) } -// NewNodeClient constructs a NodeClient that is connected to the node at nodeAddress -func NewNodeClient(ctx context.Context, sshConfig *ssh.ClientConfig, conn net.Conn, nodeAddress string, tc *TeleportClient, fipsEnabled bool) (*NodeClient, error) { +// NewNodeClient constructs a NodeClient that is connected to the node at nodeAddress. +// The nodeName field is optional and is used only to present better error messages. +func NewNodeClient(ctx context.Context, sshConfig *ssh.ClientConfig, conn net.Conn, nodeAddress, nodeName string, tc *TeleportClient, fipsEnabled bool) (*NodeClient, error) { ctx, span := tc.Tracer.Start( ctx, "NewNodeClient", @@ -1567,11 +1548,14 @@ func NewNodeClient(ctx context.Context, sshConfig *ssh.ClientConfig, conn net.Co if err != nil { if utils.IsHandshakeFailedError(err) { conn.Close() + if nodeName == "" { + nodeName = nodeAddress + } // TODO(codingllama): Improve error message below for device trust. // An alternative we have here is querying the cluster to check if device // trust is required, a check similar to `IsMFARequired`. - log.Infof("Access denied to %v connecting to %v: %v", sshConfig.User, nodeAddress, err) - return nil, trace.AccessDenied(`access denied to %v connecting to %v`, sshConfig.User, nodeAddress) + log.Infof("Access denied to %v connecting to %v: %v", sshConfig.User, nodeName, err) + return nil, trace.AccessDenied(`access denied to %v connecting to %v`, sshConfig.User, nodeName) } return nil, trace.Wrap(err) } @@ -1597,22 +1581,34 @@ func NewNodeClient(ctx context.Context, sshConfig *ssh.ClientConfig, conn net.Co return nc, nil } -func (c *NodeClient) RunCommand(ctx context.Context, cmd []string, sessToJoin types.SessionTracker) error { +// RunInteractiveShell creates an interactive shell on the node and copies stdin/stdout/stderr +// to and from the node and local shell. This will block until the interactive shell on the node +// is terminated. +func (c *NodeClient) RunInteractiveShell(ctx context.Context, mode types.SessionParticipantMode, sessToJoin types.SessionTracker) error { ctx, span := c.Tracer.Start( ctx, - "nodeClient/RunCommand", + "nodeClient/RunInteractiveShell", oteltrace.WithSpanKind(oteltrace.SpanKindClient), ) defer span.End() env := c.TC.newSessionEnv() + env[teleport.EnvSSHJoinMode] = string(mode) + env[teleport.EnvSSHSessionReason] = c.TC.Config.Reason + env[teleport.EnvSSHSessionDisplayParticipantRequirements] = strconv.FormatBool(c.TC.Config.DisplayParticipantRequirements) + encoded, err := json.Marshal(&c.TC.Config.Invited) + if err != nil { + return trace.Wrap(err) + } + + env[teleport.EnvSSHSessionInvited] = string(encoded) nodeSession, err := newSession(ctx, c, sessToJoin, env, c.TC.Stdin, c.TC.Stdout, c.TC.Stderr, c.TC.EnableEscapeSequences) if err != nil { return trace.Wrap(err) } - if err = nodeSession.runCommand(ctx, types.SessionPeerMode, cmd, c.TC.OnShellCreated, false); err != nil { + if err = nodeSession.runShell(ctx, mode, nil, c.TC.OnShellCreated); err != nil { switch e := trace.Unwrap(err).(type) { case *ssh.ExitError: c.TC.ExitStatus = e.ExitStatus() @@ -1632,50 +1628,37 @@ func (c *NodeClient) RunCommand(ctx context.Context, cmd []string, sessToJoin ty return nil } -// RunInteractiveShell creates an interactive shell on the node and copies stdin/stdout/stderr -// to and from the node and local shell. This will block until the interactive shell on the node -// is terminated. -func (c *NodeClient) RunInteractiveShell(ctx context.Context, mode types.SessionParticipantMode, sessToJoin types.SessionTracker) error { +// RunCommand executes a given bash command on the node. +func (c *NodeClient) RunCommand(ctx context.Context, command []string) error { ctx, span := c.Tracer.Start( ctx, - "nodeClient/RunInteractiveShell", + "nodeClient/RunCommand", oteltrace.WithSpanKind(oteltrace.SpanKindClient), ) defer span.End() - env := c.TC.newSessionEnv() - env[teleport.EnvSSHJoinMode] = string(mode) - env[teleport.EnvSSHSessionReason] = c.TC.Config.Reason - env[teleport.EnvSSHSessionDisplayParticipantRequirements] = strconv.FormatBool(c.TC.Config.DisplayParticipantRequirements) - encoded, err := json.Marshal(&c.TC.Config.Invited) + nodeSession, err := newSession(ctx, c, nil, c.TC.newSessionEnv(), c.TC.Stdin, c.TC.Stdout, c.TC.Stderr, c.TC.EnableEscapeSequences) if err != nil { return trace.Wrap(err) } - - env[teleport.EnvSSHSessionInvited] = string(encoded) - - nodeSession, err := newSession(ctx, c, sessToJoin, env, c.TC.Stdin, c.TC.Stdout, c.TC.Stderr, c.TC.EnableEscapeSequences) - if err != nil { - return trace.Wrap(err) - } - - if err = nodeSession.runShell(ctx, mode, nil, c.TC.OnShellCreated); err != nil { - switch e := trace.Unwrap(err).(type) { - case *ssh.ExitError: - c.TC.ExitStatus = e.ExitStatus() - case *ssh.ExitMissingError: - c.TC.ExitStatus = 1 + defer nodeSession.Close() + if err := nodeSession.runCommand(ctx, types.SessionPeerMode, command, c.TC.OnShellCreated, c.TC.Config.InteractiveCommand); err != nil { + originErr := trace.Unwrap(err) + exitErr, ok := originErr.(*ssh.ExitError) + if ok { + c.TC.ExitStatus = exitErr.ExitStatus() + } else { + // if an error occurs, but no exit status is passed back, GoSSH returns + // a generic error like this. in this case the error message is printed + // to stderr by the remote process so we have to quietly return 1: + if strings.Contains(originErr.Error(), "exited without exit status") { + c.TC.ExitStatus = 1 + } } return trace.Wrap(err) } - if nodeSession.ExitMsg == "" { - fmt.Fprintln(c.TC.Stderr, "the connection was closed on the remote side at ", time.Now().Format(time.RFC822)) - } else { - fmt.Fprintln(c.TC.Stderr, nodeSession.ExitMsg) - } - return nil } @@ -1875,75 +1858,52 @@ func (c *NodeClient) TransferFiles(ctx context.Context, cfg *sftp.Config) error } type netDialer interface { - Dial(string, string) (net.Conn, error) + DialContext(context.Context, string, string) (net.Conn, error) } func proxyConnection(ctx context.Context, conn net.Conn, remoteAddr string, dialer netDialer) error { defer conn.Close() defer log.Debugf("Finished proxy from %v to %v.", conn.RemoteAddr(), remoteAddr) - var ( - remoteConn net.Conn - err error - ) - + var remoteConn net.Conn log.Debugf("Attempting to connect proxy from %v to %v.", conn.RemoteAddr(), remoteAddr) - for attempt := 1; attempt <= 5; attempt++ { - remoteConn, err = dialer.Dial("tcp", remoteAddr) - if err != nil { - log.Debugf("Proxy connection attempt %v: %v.", attempt, err) - - timer := time.NewTimer(time.Duration(100*attempt) * time.Millisecond) - defer timer.Stop() - - // Wait and attempt to connect again, if the context has closed, exit - // right away. - select { - case <-ctx.Done(): - return trace.Wrap(ctx.Err()) - case <-timer.C: - continue - } - } - // Connection established, break out of the loop. - break - } + + retry, err := retryutils.NewLinear(retryutils.LinearConfig{ + First: 100 * time.Millisecond, + Step: 100 * time.Millisecond, + Max: time.Second, + Jitter: retryutils.NewHalfJitter(), + }) if err != nil { - return trace.BadParameter("failed to connect to node: %v", remoteAddr) + return trace.Wrap(err) } - defer remoteConn.Close() - - // Start proxying, close the connection if a problem occurs on either leg. - errCh := make(chan error, 2) - go func() { - defer conn.Close() - defer remoteConn.Close() - - _, err := io.Copy(conn, remoteConn) - errCh <- err - }() - go func() { - defer conn.Close() - defer remoteConn.Close() - _, err := io.Copy(remoteConn, conn) - errCh <- err - }() + for attempt := 1; attempt <= 5; attempt++ { + conn, err := dialer.DialContext(ctx, "tcp", remoteAddr) + if err == nil { + // Connection established, break out of the loop. + remoteConn = conn + break + } - var errs []error - for i := 0; i < 2; i++ { + log.Debugf("Proxy connection attempt %v: %v.", attempt, err) + // Wait and attempt to connect again, if the context has closed, exit + // right away. select { - case err := <-errCh: - if err != nil && err != io.EOF && !strings.Contains(err.Error(), "use of closed network connection") { - log.Warnf("Failed to proxy connection: %v.", err) - errs = append(errs, err) - } case <-ctx.Done(): return trace.Wrap(ctx.Err()) + case <-retry.After(): + retry.Inc() + continue } } + if remoteConn == nil { + return trace.BadParameter("failed to connect to node: %v", remoteAddr) + } + defer remoteConn.Close() - return trace.NewAggregate(errs...) + // Start proxying, close the connection if a problem occurs on either leg. + return trace.Wrap(utils.ProxyConn(ctx, remoteConn, conn)) } // acceptWithContext calls "Accept" on the listener but will unblock when the @@ -2080,37 +2040,6 @@ func (c *NodeClient) Close() error { return c.Client.Close() } -func (proxy *ProxyClient) sessionSSHCertificate(ctx context.Context, nodeAddr NodeDetails) ([]ssh.AuthMethod, error) { - if _, err := proxy.teleportClient.localAgent.GetKey(nodeAddr.Cluster); err != nil { - if trace.IsNotFound(err) { - // Either running inside the web UI in a proxy or using an identity - // file. Fall back to whatever AuthMethod we currently have. - return proxy.authMethods, nil - } - return nil, trace.Wrap(err) - } - - key, err := proxy.IssueUserCertsWithMFA( - ctx, - ReissueParams{ - NodeName: nodeName(nodeAddr.Addr), - RouteToCluster: proxy.ClusterName(), - MFACheck: nodeAddr.MFACheck, - }, - func(ctx context.Context, proxyAddr string, c *proto.MFAAuthenticateChallenge) (*proto.MFAAuthenticateResponse, error) { - return proxy.teleportClient.PromptMFAChallenge(ctx, proxyAddr, c, nil /* applyOpts */) - }, - ) - if err != nil { - return nil, trace.Wrap(err) - } - am, err := key.AsAuthMethod() - if err != nil { - return nil, trace.Wrap(err) - } - return []ssh.AuthMethod{am}, nil -} - // localAgent returns for the Teleport client's local agent. func (proxy *ProxyClient) localAgent() *LocalKeyAgent { return proxy.teleportClient.LocalAgent() diff --git a/lib/client/cluster_client.go b/lib/client/cluster_client.go new file mode 100644 index 0000000000000..04d952ae1bb27 --- /dev/null +++ b/lib/client/cluster_client.go @@ -0,0 +1,365 @@ +// Copyright 2023 Gravitational, Inc +// +// 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. + +package client + +import ( + "context" + + "github.com/gravitational/trace" + "go.opentelemetry.io/otel/attribute" + oteltrace "go.opentelemetry.io/otel/trace" + "golang.org/x/crypto/ssh" + + "github.com/gravitational/teleport/api/client/proto" + proxyclient "github.com/gravitational/teleport/api/client/proxy" + "github.com/gravitational/teleport/lib/auth" +) + +// ClusterClient facilitates communicating with both the +// Auth and Proxy services of a cluster. +type ClusterClient struct { + tc *TeleportClient + ProxyClient *proxyclient.Client + AuthClient auth.ClientI + Tracer oteltrace.Tracer +} + +// ClusterName returns the name of the cluster that the client +// is connected to. +func (c *ClusterClient) ClusterName() string { + cluster := c.ProxyClient.ClusterName() + if len(c.tc.JumpHosts) > 0 && cluster != "" { + return cluster + } + + return c.tc.SiteName +} + +// Close terminates the connections to Auth and Proxy. +func (c *ClusterClient) Close() error { + // close auth client first since it is tunneled through the proxy client + return trace.NewAggregate(c.AuthClient.Close(), c.ProxyClient.Close()) +} + +// SessionSSHConfig returns the [ssh.ClientConfig] that should be used to connected to the +// provided target for the provided user. If per session MFA is required to establish the +// connection, then the MFA ceremony will be performed. +func (c *ClusterClient) SessionSSHConfig(ctx context.Context, user string, target NodeDetails) (*ssh.ClientConfig, error) { + ctx, span := c.Tracer.Start( + ctx, + "clusterClient/SessionSSHConfig", + oteltrace.WithSpanKind(oteltrace.SpanKindClient), + oteltrace.WithAttributes( + attribute.String("cluster", c.tc.SiteName), + ), + ) + defer span.End() + + sshConfig := c.ProxyClient.SSHConfig(user) + + if target.MFACheck != nil && !target.MFACheck.Required { + return sshConfig, nil + } + + key, err := c.tc.localAgent.GetKey(target.Cluster, WithAllCerts...) + if err != nil { + if trace.IsNotFound(err) { + // Either running inside the web UI in a proxy or using an identity + // file. Fall back to whatever AuthMethod we currently have. + return sshConfig, nil + } + return nil, trace.Wrap(err) + } + + params := ReissueParams{ + NodeName: nodeName(target.Addr), + RouteToCluster: target.Cluster, + MFACheck: target.MFACheck, + } + + // requiredCheck passed from param can be nil. + if target.MFACheck == nil { + check, err := c.AuthClient.IsMFARequired(ctx, params.isMFARequiredRequest(c.tc.HostLogin)) + if err != nil { + return nil, trace.Wrap(err) + } + target.MFACheck = check + } + + if !target.MFACheck.Required { + log.Debug("MFA not required for access.") + // MFA is not required. + // SSH certs can be used without embedding the node name. + if params.usage() == proto.UserCertsRequest_SSH { + return sshConfig, nil + } + + // All other targets need their name embedded in the cert for routing, + // fall back to non-MFA reissue. + key, err := c.reissueUserCerts(ctx, CertCacheKeep, params) + if err != nil { + return nil, trace.Wrap(err) + } + + am, err := key.AsAuthMethod() + if err != nil { + return nil, trace.Wrap(err) + } + + sshConfig.Auth = []ssh.AuthMethod{am} + return sshConfig, nil + } + + // Always connect to root for getting new credentials, but attempt to reuse + // the existing client if possible. + rootClusterName, err := key.RootClusterName() + if err != nil { + return nil, trace.Wrap(err) + } + + mfaClt := c + if params.RouteToCluster != rootClusterName { + jumpHosts := c.tc.JumpHosts + // In case of MFA connect to root teleport proxy instead of JumpHost to request + // MFA certificates. + c.tc.JumpHosts = nil + clt, err := c.tc.ConnectToCluster(ctx) + c.tc.JumpHosts = jumpHosts + if err != nil { + return nil, trace.Wrap(err) + } + + mfaClt = clt + defer clt.Close() + } + + log.Debug("Attempting to issue a single-use user certificate with an MFA check.") + key, err = performMFACeremony(ctx, mfaClt, params, key) + if err != nil { + return nil, trace.Wrap(err) + } + + log.Debug("Issued single-use user certificate after an MFA check.") + am, err := key.AsAuthMethod() + if err != nil { + return nil, trace.Wrap(err) + } + + sshConfig.Auth = []ssh.AuthMethod{am} + return sshConfig, nil +} + +// reissueUserCerts gets new user certificates from the root Auth server. +func (c *ClusterClient) reissueUserCerts(ctx context.Context, cachePolicy CertCachePolicy, params ReissueParams) (*Key, error) { + if params.RouteToCluster == "" { + params.RouteToCluster = c.tc.SiteName + } + key := params.ExistingCreds + if key == nil { + var err error + + // Don't load the certs if we're going to drop all of them all as part + // of the re-issue. If we load all of the old certs now we won't be able + // to differentiate between legacy certificates (that need to be + // deleted) and newly re-issued certs (that we definitely do *not* want + // to delete) when it comes time to drop them from the local agent. + var certOptions []CertOption + if cachePolicy == CertCacheKeep { + certOptions = WithAllCerts + } + + key, err = c.tc.localAgent.GetKey(params.RouteToCluster, certOptions...) + if err != nil { + return nil, trace.Wrap(err) + } + } + + req, err := c.prepareUserCertsRequest(params, key) + if err != nil { + return nil, trace.Wrap(err) + } + + root, err := c.tc.rootClusterName() + if err != nil { + return nil, trace.Wrap(err) + } + + clt, err := auth.NewClient(c.ProxyClient.ClientConfig(ctx, root)) + if err != nil { + return nil, trace.Wrap(err) + } + defer clt.Close() + + certs, err := clt.GenerateUserCerts(ctx, *req) + if err != nil { + return nil, trace.Wrap(err) + } + + key.ClusterName = params.RouteToCluster + + // Only update the parts of key that match the usage. See the docs on + // proto.UserCertsRequest_CertUsage for which certificates match which + // usage. + // + // This prevents us from overwriting the top-level key.TLSCert with + // usage-restricted certificates. + switch params.usage() { + case proto.UserCertsRequest_All: + key.Cert = certs.SSH + key.TLSCert = certs.TLS + case proto.UserCertsRequest_SSH: + key.Cert = certs.SSH + case proto.UserCertsRequest_App: + key.AppTLSCerts[params.RouteToApp.Name] = certs.TLS + case proto.UserCertsRequest_Database: + dbCert, err := makeDatabaseClientPEM(params.RouteToDatabase.Protocol, certs.TLS, key) + if err != nil { + return nil, trace.Wrap(err) + } + key.DBTLSCerts[params.RouteToDatabase.ServiceName] = dbCert + case proto.UserCertsRequest_Kubernetes: + key.KubeTLSCerts[params.KubernetesCluster] = certs.TLS + case proto.UserCertsRequest_WindowsDesktop: + key.WindowsDesktopCerts[params.RouteToWindowsDesktop.WindowsDesktop] = certs.TLS + } + return key, nil +} + +// prepareUserCertsRequest creates a [proto.UserCertsRequest] with the fields +// set accordingly from the provided ReissueParams. +func (c *ClusterClient) prepareUserCertsRequest(params ReissueParams, key *Key) (*proto.UserCertsRequest, error) { + tlsCert, err := key.TeleportTLSCertificate() + if err != nil { + return nil, trace.Wrap(err) + } + + if len(params.AccessRequests) == 0 { + // Get the active access requests to include in the cert. + activeRequests, err := key.ActiveRequests() + // key.ActiveRequests can return a NotFound error if it doesn't have an + // SSH cert. That's OK, we just assume that there are no AccessRequests + // in that case. + if err != nil && !trace.IsNotFound(err) { + return nil, trace.Wrap(err) + } + params.AccessRequests = activeRequests.AccessRequests + } + + return &proto.UserCertsRequest{ + PublicKey: key.MarshalSSHPublicKey(), + Username: tlsCert.Subject.CommonName, + Expires: tlsCert.NotAfter, + RouteToCluster: params.RouteToCluster, + KubernetesCluster: params.KubernetesCluster, + AccessRequests: params.AccessRequests, + DropAccessRequests: params.DropAccessRequests, + RouteToDatabase: params.RouteToDatabase, + RouteToWindowsDesktop: params.RouteToWindowsDesktop, + RouteToApp: params.RouteToApp, + NodeName: params.NodeName, + Usage: params.usage(), + Format: c.tc.CertificateFormat, + RequesterName: params.RequesterName, + }, nil +} + +// performMFACeremony runs the mfa ceremony to completion. If successful the returned +// [Key] will be authorized to connect to the target. +func performMFACeremony(ctx context.Context, clt *ClusterClient, params ReissueParams, key *Key) (*Key, error) { + stream, err := clt.AuthClient.GenerateUserSingleUseCerts(ctx) + if err != nil { + if trace.IsNotImplemented(err) { + // Probably talking to an older server, use the old non-MFA endpoint. + log.WithError(err).Debug("Auth server does not implement GenerateUserSingleUseCerts.") + // SSH certs can be used without reissuing. + if params.usage() == proto.UserCertsRequest_SSH && key.Cert != nil { + return key, nil + } + + key, err := clt.reissueUserCerts(ctx, CertCacheKeep, params) + return key, trace.Wrap(err) + } + return nil, trace.Wrap(err) + } + defer func() { + // CloseSend closes the client side of the stream + stream.CloseSend() + // Recv to wait for the server side of the stream to end, this needs to + // be called to ensure the spans are finished properly + stream.Recv() + }() + + initReq, err := clt.prepareUserCertsRequest(params, key) + if err != nil { + return nil, trace.Wrap(err) + } + + err = stream.Send(&proto.UserSingleUseCertsRequest{Request: &proto.UserSingleUseCertsRequest_Init{ + Init: initReq, + }}) + if err != nil { + return nil, trace.Wrap(err) + } + + resp, err := stream.Recv() + if err != nil { + return nil, trace.Wrap(err) + } + mfaChal := resp.GetMFAChallenge() + if mfaChal == nil { + return nil, trace.BadParameter("server sent a %T on GenerateUserSingleUseCerts, expected MFAChallenge", resp.Response) + } + mfaResp, err := clt.tc.PromptMFAChallenge(ctx, clt.tc.WebProxyAddr, mfaChal, nil /* applyOpts */) + if err != nil { + return nil, trace.Wrap(err) + } + err = stream.Send(&proto.UserSingleUseCertsRequest{Request: &proto.UserSingleUseCertsRequest_MFAResponse{MFAResponse: mfaResp}}) + if err != nil { + return nil, trace.Wrap(err) + } + + resp, err = stream.Recv() + if err != nil { + return nil, trace.Wrap(err) + } + certResp := resp.GetCert() + if certResp == nil { + return nil, trace.BadParameter("server sent a %T on GenerateUserSingleUseCerts, expected SingleUseUserCert", resp.Response) + } + switch crt := certResp.Cert.(type) { + case *proto.SingleUseUserCert_SSH: + key.Cert = crt.SSH + case *proto.SingleUseUserCert_TLS: + switch initReq.Usage { + case proto.UserCertsRequest_Kubernetes: + key.KubeTLSCerts[initReq.KubernetesCluster] = crt.TLS + case proto.UserCertsRequest_Database: + dbCert, err := makeDatabaseClientPEM(params.RouteToDatabase.Protocol, crt.TLS, key) + if err != nil { + return nil, trace.Wrap(err) + } + key.DBTLSCerts[params.RouteToDatabase.ServiceName] = dbCert + case proto.UserCertsRequest_WindowsDesktop: + key.WindowsDesktopCerts[params.RouteToWindowsDesktop.WindowsDesktop] = crt.TLS + default: + return nil, trace.BadParameter("server returned a TLS certificate but cert request usage was %s", initReq.Usage) + } + default: + return nil, trace.BadParameter("server sent a %T SingleUseUserCert in response", certResp.Cert) + } + key.ClusterName = params.RouteToCluster + + return key, nil +} diff --git a/lib/client/db/database_certificates.go b/lib/client/db/database_certificates.go index a9c5e18e5c387..1209b40465432 100644 --- a/lib/client/db/database_certificates.go +++ b/lib/client/db/database_certificates.go @@ -41,8 +41,8 @@ type GenerateDatabaseCertificatesRequest struct { IdentityFileWriter identityfile.ConfigWriter TTL time.Duration Key *client.Key - // JKSKeyStore is used to generate JKS keystore used for cassandra format. - JKSPassword string + // Password is used to generate JKS keystore used for cassandra format or Oracle wallet. + Password string } // GenerateDatabaseCertificates to be used by databases to set up mTLS authentication @@ -123,7 +123,7 @@ func GenerateDatabaseCertificates(ctx context.Context, req GenerateDatabaseCerti Format: req.OutputFormat, OverwriteDestination: req.OutputCanOverwrite, Writer: req.IdentityFileWriter, - JKSPassword: req.JKSPassword, + Password: req.Password, }) if err != nil { return nil, trace.Wrap(err) diff --git a/lib/client/db/dbcmd/dbcmd.go b/lib/client/db/dbcmd/dbcmd.go index 40fc165c45c2e..97fee3c342f21 100644 --- a/lib/client/db/dbcmd/dbcmd.go +++ b/lib/client/db/dbcmd/dbcmd.go @@ -69,6 +69,8 @@ const ( elasticsearchSQLBin = "elasticsearch-sql-cli" // awsBin is the aws CLI program name. awsBin = "aws" + // oracleBin is the Oracle CLI program name. + oracleBin = "sql" ) // Execer is an abstraction of Go's exec module, as this one doesn't specify any interfaces. @@ -192,6 +194,9 @@ func (c *CLICommandBuilder) GetConnectCommand() (*exec.Cmd, error) { case defaults.ProtocolDynamoDB: return c.getDynamoDBCommand() + + case defaults.ProtocolOracle: + return c.getOracleCommand() } return nil, trace.BadParameter("unsupported database protocol: %v", c.db) @@ -619,6 +624,36 @@ func (c *CLICommandBuilder) getDynamoDBCommand() (*exec.Cmd, error) { return c.options.exe.Command(awsBin, args...), nil } +type jdbcOracleThinConnection struct { + host string + port int + db string + tnsAdmin string +} + +func (j *jdbcOracleThinConnection) ConnString() string { + return fmt.Sprintf(`jdbc:oracle:thin:@tcps://%s:%d/%s?TNS_ADMIN=%s`, j.host, j.port, j.db, j.tnsAdmin) +} + +func (c *CLICommandBuilder) getOracleCommand() (*exec.Cmd, error) { + cs := jdbcOracleThinConnection{ + host: c.host, + port: c.port, + db: c.db.Database, + tnsAdmin: c.profile.OracleWalletDir(c.profile.Cluster, c.db.ServiceName), + } + // Quote the address for printing as the address contains "?". + connString := cs.ConnString() + if c.options.printFormat { + connString = fmt.Sprintf(`'%s'`, connString) + } + args := []string{ + "-L", // dont retry + connString, + } + return c.options.exe.Command(oracleBin, args...), nil +} + func (c *CLICommandBuilder) getElasticsearchAlternativeCommands() []CommandAlternative { var commands []CommandAlternative if c.isElasticsearchSQLBinAvailable() { diff --git a/lib/client/db/dbcmd/dbcmd_test.go b/lib/client/db/dbcmd/dbcmd_test.go index 322a825a114df..c56de1c36bb2b 100644 --- a/lib/client/db/dbcmd/dbcmd_test.go +++ b/lib/client/db/dbcmd/dbcmd_test.go @@ -585,6 +585,24 @@ func TestCLICommandBuilderGetConnectCommand(t *testing.T) { cmd: []string{"aws", "--endpoint", "http://localhost:12345/", "[dynamodb|dynamodbstreams|dax]", ""}, wantErr: false, }, + { + name: "oracle", + dbProtocol: defaults.ProtocolOracle, + opts: []ConnectCommandFunc{WithLocalProxy("localhost", 12345, "")}, + execer: &fakeExec{}, + databaseName: "oracle01", + cmd: []string{"sql", "-L", "jdbc:oracle:thin:@tcps://localhost:12345/oracle01?TNS_ADMIN=/tmp/keys/example.com/bob-db/mysql-wallet"}, + wantErr: false, + }, + { + name: "Oracle with print format", + dbProtocol: defaults.ProtocolOracle, + opts: []ConnectCommandFunc{WithLocalProxy("localhost", 12345, ""), WithPrintFormat()}, + execer: &fakeExec{}, + databaseName: "oracle01", + cmd: []string{"sql", "-L", "'jdbc:oracle:thin:@tcps://localhost:12345/oracle01?TNS_ADMIN=/tmp/keys/example.com/bob-db/mysql-wallet'"}, + wantErr: false, + }, } for _, tt := range tests { diff --git a/lib/client/db/oracle/config.go b/lib/client/db/oracle/config.go new file mode 100644 index 0000000000000..a4c79be61630e --- /dev/null +++ b/lib/client/db/oracle/config.go @@ -0,0 +1,115 @@ +/* +Copyright 2023 Gravitational, Inc. + +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. +*/ + +package oracle + +import ( + "bytes" + "os" + "path/filepath" + "text/template" + + "github.com/gravitational/trace" + + "github.com/gravitational/teleport" +) + +type jdbcSettings struct { + KeyStoreFile string + TrustStoreFile string + KeyStorePassword string + TrustStorePassword string +} + +const jdbcPropertiesTemplateContent = ` +javax.net.ssl.keyStore={{.KeyStoreFile}} +javax.net.ssl.trustStore={{.TrustStoreFile}} +javax.net.ssl.keyStorePassword={{.KeyStorePassword}} +javax.net.ssl.trustStorePassword={{.TrustStorePassword}} +javax.net.ssl.keyStoreType=jks +javax.net.ssl.trustStoreType=jks +oracle.net.authentication_services=TCPS +` + +type tnsNamesORASettings struct { + ServiceName string + Host string + Port string +} + +const sqlnetORATemplateContent = ` +SSL_CLIENT_AUTHENTICATION = TRUE +SQLNET.AUTHENTICATION_SERVICES = (TCPS) + +WALLET_LOCATION = + (SOURCE = + (METHOD = FILE) + (METHOD_DATA = + (DIRECTORY = {{.WalletDir}}) + ) + ) +` + +type sqlnetORASettings struct { + WalletDir string +} + +const tnsnamesORATemplateContent = ` +{{.ServiceName}} = + (DESCRIPTION = + (ADDRESS_LIST = + (ADDRESS = (PROTOCOL = TCPS)(HOST = {{.Host}})(PORT = {{.Port}})) + ) + (CONNECT_DATA = + (SERVER = DEDICATED) + (SERVICE_NAME = {{.ServiceName}}) + ) + (SECURITY = + (SSL_SERVER_CERT_DN = "CN=localhost") + ) + ) +` + +var ( + jdbcPropertiesTemplate = template.Must(template.New("").Parse(jdbcPropertiesTemplateContent)) + sqlnetORATemplate = template.Must(template.New("").Parse(sqlnetORATemplateContent)) + tnsnamesORATemplate = template.Must(template.New("").Parse(tnsnamesORATemplateContent)) +) + +func (c jdbcSettings) template() *template.Template { return jdbcPropertiesTemplate } +func (c sqlnetORASettings) template() *template.Template { return sqlnetORATemplate } +func (c tnsNamesORASettings) template() *template.Template { return tnsnamesORATemplate } + +func (c jdbcSettings) configFilename() string { return "ojdbc.properties" } +func (c sqlnetORASettings) configFilename() string { return "sqlnet.ora" } +func (c tnsNamesORASettings) configFilename() string { return "tnsnames.ora" } + +type templateSettings interface { + template() *template.Template + configFilename() string +} + +func writeSettings(settings templateSettings, dir string) error { + var buff bytes.Buffer + if err := settings.template().Execute(&buff, settings); err != nil { + return trace.Wrap(err) + } + filePath := filepath.Join(dir, settings.configFilename()) + if err := os.WriteFile(filePath, buff.Bytes(), teleport.FileMaskOwnerOnly); err != nil { + return trace.Wrap(err) + } + return nil +} diff --git a/lib/client/db/oracle/oracle.go b/lib/client/db/oracle/oracle.go new file mode 100644 index 0000000000000..b4ee6b108727f --- /dev/null +++ b/lib/client/db/oracle/oracle.go @@ -0,0 +1,144 @@ +/* +Copyright 2023 Gravitational, Inc. + +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. +*/ + +package oracle + +import ( + "bytes" + "crypto/x509" + "os" + "path/filepath" + "time" + + "github.com/gravitational/trace" + "github.com/pavlo-v-chernykh/keystore-go/v4" + + "github.com/gravitational/teleport" + "github.com/gravitational/teleport/lib/client" + "github.com/gravitational/teleport/lib/tlsca" + "github.com/gravitational/teleport/lib/utils" +) + +// GenerateClientConfiguration function generates following Oracle Client configuration: +// wallet.jks - Java Wallet format used by JDBC Drivers. +// sqlnet.ora - Generic Oracle Client Configuration File allowing to specify Wallet Location. +// tnsnames.ora - Oracle Net Service mapped to connections descriptors. +func GenerateClientConfiguration(key *client.Key, db tlsca.RouteToDatabase, profile *client.ProfileStatus) error { + walletPath := profile.OracleWalletDir(key.ClusterName, db.ServiceName) + if err := os.MkdirAll(walletPath, teleport.PrivateDirMode); err != nil { + return trace.Wrap(err) + } + password, err := utils.CryptoRandomHex(32) + if err != nil { + return trace.Wrap(err) + } + + localProxyCAPem, err := os.ReadFile(profile.DatabaseLocalCAPath()) + if err != nil { + return trace.ConvertSystemError(err) + } + + jksWalletPath, err := createClientWallet(key, localProxyCAPem, password, walletPath) + if err != nil { + return trace.Wrap(err) + } + + err = writeClientConfig(walletPath, jksWalletPath, password) + if err != nil { + return trace.Wrap(err) + } + return nil +} + +func createClientWallet(key *client.Key, certPem []byte, password string, walletPath string) (string, error) { + buff, err := createJKSWallet(key.PrivateKeyPEM(), certPem, certPem, password) + if err != nil { + return "", trace.Wrap(err) + } + walletFile := filepath.Join(walletPath, "wallet.jks") + if err := os.WriteFile(walletFile, buff, teleport.FileMaskOwnerOnly); err != nil { + return "", trace.Wrap(err) + } + return walletFile, nil +} + +func createJKSWallet(keyPEM, certPEM, caPEM []byte, password string) ([]byte, error) { + key, err := utils.ParsePrivateKey(keyPEM) + if err != nil { + return nil, trace.Wrap(err) + } + privateKey, err := x509.MarshalPKCS8PrivateKey(key) + if err != nil { + return nil, trace.Wrap(err) + } + ks := keystore.New() + pkeIn := keystore.PrivateKeyEntry{ + CreationTime: time.Now(), + PrivateKey: privateKey, + CertificateChain: []keystore.Certificate{ + { + Type: "x509", + Content: certPEM, + }, + }, + } + + if err := ks.SetPrivateKeyEntry("teleportUserCert", pkeIn, []byte(password)); err != nil { + return nil, trace.Wrap(err) + } + trustIn := keystore.TrustedCertificateEntry{ + CreationTime: time.Now(), + Certificate: keystore.Certificate{ + Type: "x509", + Content: caPEM, + }, + } + if err := ks.SetTrustedCertificateEntry("teleportLocalCA", trustIn); err != nil { + return nil, trace.Wrap(err) + } + var buff bytes.Buffer + if err := ks.Store(&buff, []byte(password)); err != nil { + return nil, trace.Wrap(err) + } + return buff.Bytes(), nil +} + +func writeClientConfig(path string, jksFile string, password string) error { + var clientConfiguration = []templateSettings{ + tnsNamesORASettings{ + Host: "localhost", + // User default values that will be overwritten by JDBC connection string. + ServiceName: "XE", + Port: "2484", + }, + sqlnetORASettings{ + WalletDir: path, + }, + jdbcSettings{ + KeyStoreFile: jksFile, + TrustStoreFile: jksFile, + KeyStorePassword: password, + TrustStorePassword: password, + }, + } + + for _, v := range clientConfiguration { + if err := writeSettings(v, path); err != nil { + return trace.Wrap(err, "Failed to write %v", v.configFilename()) + } + } + return nil +} diff --git a/lib/client/identityfile/identity.go b/lib/client/identityfile/identity.go index 328335675293e..f10466859533b 100644 --- a/lib/client/identityfile/identity.go +++ b/lib/client/identityfile/identity.go @@ -20,17 +20,20 @@ package identityfile import ( "bytes" "context" + "crypto/rand" "crypto/x509" "encoding/pem" "fmt" "io/fs" "os" + "os/exec" "path/filepath" "strings" "time" "github.com/gravitational/trace" "github.com/pavlo-v-chernykh/keystore-go/v4" + "software.sslmate.com/src/go-pkcs12" "github.com/gravitational/teleport/api/identityfile" "github.com/gravitational/teleport/api/profile" @@ -99,6 +102,12 @@ const ( // DefaultFormat is what Teleport uses by default DefaultFormat = FormatFile + + // FormatOracle produces CA and ke pair in the Oracle wallet format. + // The execution depend on Orapki binary and if this binary is not found + // Teleport will print intermediate steps how to convert Teleport certs + // to Oracle wallet on Oracle Server instance. + FormatOracle Format = "oracle" ) // FormatList is a list of all possible FormatList. @@ -108,6 +117,7 @@ type FormatList []Format var KnownFileFormats = FormatList{ FormatFile, FormatOpenSSH, FormatTLS, FormatKubernetes, FormatDatabase, FormatWindows, FormatMongo, FormatCockroach, FormatRedis, FormatSnowflake, FormatElasticsearch, FormatCassandra, FormatScylla, + FormatOracle, } // String returns human-readable version of FormatList, ex: @@ -181,8 +191,8 @@ type WriteConfig struct { OverwriteDestination bool // Writer is the filesystem implementation. Writer ConfigWriter - // JKSPassword is the password for the JKS keystore used by Cassandra format. - JKSPassword string + // Password is the password for the JKS keystore used by Cassandra format and Oracle wallet. + Password string } // Write writes user credentials to disk in a specified format. @@ -309,7 +319,6 @@ func Write(ctx context.Context, cfg WriteConfig) (filesWritten []string, err err if err != nil { return nil, trace.Wrap(err) } - // FormatMongo is the same as FormatTLS or FormatDatabase certificate and // key are concatenated in the same .crt file which is what Mongo expects. case FormatMongo: @@ -371,6 +380,12 @@ func Write(ctx context.Context, cfg WriteConfig) (filesWritten []string, err err return nil, trace.Wrap(err) } filesWritten = append(filesWritten, out...) + case FormatOracle: + out, err := writeOracleFormat(cfg, writer) + if err != nil { + return nil, trace.Wrap(err) + } + filesWritten = append(filesWritten, out...) case FormatKubernetes: filesWritten = append(filesWritten, cfg.OutputPath) @@ -410,12 +425,12 @@ func Write(ctx context.Context, cfg WriteConfig) (filesWritten []string, err err } func writeCassandraFormat(cfg WriteConfig, writer ConfigWriter) ([]string, error) { - if cfg.JKSPassword == "" { + if cfg.Password == "" { pass, err := utils.CryptoRandomHex(16) if err != nil { return nil, trace.Wrap(err) } - cfg.JKSPassword = pass + cfg.Password = pass } // Cassandra expects a JKS keystore file with the private key and certificate // in it. The keystore file is password protected. @@ -445,6 +460,116 @@ func writeCassandraFormat(cfg WriteConfig, writer ConfigWriter) ([]string, error return []string{certPath, casPath}, nil } +// writeOracleFormat creates an Oracle wallet files if orapki Oracle tool is available +// is user env otherwise creates a p12 key-pair file allowing to run orapki on the Oracle server +// and create the Oracle wallet manually. +func writeOracleFormat(cfg WriteConfig, writer ConfigWriter) ([]string, error) { + certBlock, err := tlsca.ParseCertificatePEM(cfg.Key.TLSCert) + if err != nil { + return nil, trace.Wrap(err) + } + keyK, err := utils.ParsePrivateKeyPEM(cfg.Key.PrivateKeyPEM()) + if err != nil { + return nil, trace.Wrap(err) + } + var caCerts []*x509.Certificate + for _, ca := range cfg.Key.TrustedCerts { + for _, cert := range ca.TLSCertificates { + c, err := tlsca.ParseCertificatePEM(cert) + if err != nil { + return nil, trace.Wrap(err) + } + caCerts = append(caCerts, c) + } + } + + pf, err := pkcs12.Encode(rand.Reader, keyK, certBlock, caCerts, cfg.Password) + if err != nil { + return nil, trace.Wrap(err) + } + + p12Path := cfg.OutputPath + ".p12" + certPath := cfg.OutputPath + ".crt" + + if err := writer.WriteFile(p12Path, pf, identityfile.FilePermissions); err != nil { + return nil, trace.Wrap(err) + } + err = writer.WriteFile(certPath, cfg.Key.TLSCert, identityfile.FilePermissions) + if err != nil { + return nil, trace.Wrap(err) + } + + // Is ORAPKI binary is available is user env run command ang generate autologin Oracle wallet. + if isOrapkiAvailable() { + // Is Orapki is available in the user env create the Oracle wallet directly. + // otherwise Orapki tool needs to be executed on the server site to import keypair to + // Oracle wallet. + if err := createOracleWallet(cfg.OutputPath, p12Path, certPath, cfg.Password); err != nil { + return nil, trace.Wrap(err) + } + // If Oracle Wallet was created the raw p12 keypair and trusted cert are no longer needed. + if err := os.Remove(p12Path); err != nil { + return nil, trace.Wrap(err) + } + if err := os.Remove(certPath); err != nil { + return nil, trace.Wrap(err) + } + // Return the path to the Oracle wallet. + return []string{cfg.OutputPath}, nil + } + + // Otherwise return destinations to p12 keypair and trusted CA allowing a user to run the convert flow on the + // Oracle server instance in order to create Oracle wallet file. + return []string{p12Path, certPath}, nil +} + +const ( + orapkiBinary = "orapki" +) + +func isOrapkiAvailable() bool { + _, err := exec.LookPath(orapkiBinary) + return err == nil +} + +func createOracleWallet(walletPath, p12Path, certPath, password string) error { + errDetailsFormat := "\n\nOrapki command:\n%s \n\nCompleted with following error: \n%s" + // Create Raw Oracle wallet with auto_login_only flag - no password required. + args := []string{ + "wallet", "create", "-wallet", walletPath, + "-auto_login_only", + } + cmd := exec.Command(orapkiBinary, args...) + if output, err := cmd.CombinedOutput(); err != nil { + return trace.Wrap(err, fmt.Sprintf(errDetailsFormat, cmd.String(), output)) + } + + // Import keypair into oracle wallet as a user cert. + args = []string{ + "wallet", "import_pkcs12", "-wallet", walletPath, + "-auto_login_only", + "-pkcs12file", p12Path, + "-pkcs12pwd", password, + } + cmd = exec.Command(orapkiBinary, args...) + if output, err := exec.Command(orapkiBinary, args...).CombinedOutput(); err != nil { + return trace.Wrap(err, fmt.Sprintf(errDetailsFormat, cmd.String(), output)) + } + + // Add import teleport CA to the oracle wallet. + args = []string{ + "wallet", "add", "-wallet", walletPath, + "-trusted_cert", + "-auto_login_only", + "-cert", certPath, + } + cmd = exec.Command(orapkiBinary, args...) + if output, err := exec.Command(orapkiBinary, args...).CombinedOutput(); err != nil { + return trace.Wrap(err, fmt.Sprintf(errDetailsFormat, cmd.String(), output)) + } + return nil +} + func prepareCassandraTruststore(cfg WriteConfig) (*bytes.Buffer, error) { var caCerts []byte for _, ca := range cfg.Key.TrustedCerts { @@ -466,7 +591,7 @@ func prepareCassandraTruststore(cfg WriteConfig) (*bytes.Buffer, error) { return nil, trace.Wrap(err) } var buff bytes.Buffer - if err := ks.Store(&buff, []byte(cfg.JKSPassword)); err != nil { + if err := ks.Store(&buff, []byte(cfg.Password)); err != nil { return nil, trace.Wrap(err) } return &buff, nil @@ -497,11 +622,11 @@ func prepareCassandraKeystore(cfg WriteConfig) (*bytes.Buffer, error) { }, }, } - if err := ks.SetPrivateKeyEntry("cassandra", pkeIn, []byte(cfg.JKSPassword)); err != nil { + if err := ks.SetPrivateKeyEntry("cassandra", pkeIn, []byte(cfg.Password)); err != nil { return nil, trace.Wrap(err) } var buff bytes.Buffer - if err := ks.Store(&buff, []byte(cfg.JKSPassword)); err != nil { + if err := ks.Store(&buff, []byte(cfg.Password)); err != nil { return nil, trace.Wrap(err) } return &buff, nil diff --git a/lib/client/interfaces.go b/lib/client/interfaces.go index b1b938716da1f..48ca71d73a374 100644 --- a/lib/client/interfaces.go +++ b/lib/client/interfaces.go @@ -367,7 +367,7 @@ func isTeleportAgentKey(key *agent.Key) bool { return strings.HasPrefix(key.Comment, agentKeyCommentPrefix+agentKeyCommentSeparator) } -// AsAgentKeys converts client.Key struct to an agent.AddedKey. Any agent.AddedKey +// AsAgentKey converts client.Key struct to an agent.AddedKey. Any agent.AddedKey // can be added to a local agent (keyring), nut non-standard keys cannot be added // to an SSH system agent through the ssh agent protocol. Check canAddToSystemAgent // before adding this key to an SSH system agent. diff --git a/lib/client/keystore.go b/lib/client/keystore.go index 8ee58fe4543b7..739516a2b2601 100644 --- a/lib/client/keystore.go +++ b/lib/client/keystore.go @@ -160,17 +160,19 @@ func (fs *FSKeyStore) AddKey(key *Key) error { if err := fs.writeBytes(key.TLSCert, fs.tlsCertPath(key.KeyIndex)); err != nil { return trace.Wrap(err) } + + // We only generate PPK files for use by PuTTY when running tsh on Windows. if runtime.GOOS == constants.WindowsOS { ppkFile, err := key.PPKFile() - if err == nil { + // PPKFile can only be generated from an RSA private key. If the key is in a different + // format, a BadParameter error is returned and we can skip PPK generation. + if err != nil && !trace.IsBadParameter(err) { + fs.log.Debugf("Cannot convert private key to PPK-formatted keypair: %v", err) + } else { if err := fs.writeBytes(ppkFile, fs.ppkFilePath(key.KeyIndex)); err != nil { return trace.Wrap(err) } - } else if !trace.IsBadParameter(err) { - return trace.Wrap(err) } - // PPKFile can only be generated from an RSA private key. - fs.log.WithError(err).Debugf("Cannot convert private key to PPK-formatted keypair.") } // Store per-cluster key data. diff --git a/lib/client/kubesession.go b/lib/client/kubesession.go index 3450c4d0880f2..0e260e286dae0 100644 --- a/lib/client/kubesession.go +++ b/lib/client/kubesession.go @@ -58,6 +58,8 @@ func NewKubeSession(ctx context.Context, tc *TeleportClient, meta types.SessionT TLSClientConfig: tlsConfig, } + fmt.Printf("Joining session with participant mode: %v. \n\n", mode) + ws, resp, err := dialer.Dial(joinEndpoint, nil) if resp != nil && resp.Body != nil { defer resp.Body.Close() diff --git a/lib/client/profile.go b/lib/client/profile.go index 493e1d718f744..43f3ec1356bd4 100644 --- a/lib/client/profile.go +++ b/lib/client/profile.go @@ -449,6 +449,36 @@ func (p *ProfileStatus) DatabaseCertPathForCluster(clusterName string, databaseN return keypaths.DatabaseCertPath(p.Dir, p.Name, p.Username, clusterName, databaseName) } +// OracleWalletDir returns path to the specified database access +// certificate for this profile, for the specified cluster. +// +// It's kept in /keys//-db//dbname-wallet/ +// +// If the input cluster name is an empty string, the selected cluster in the +// profile will be used. +func (p *ProfileStatus) OracleWalletDir(clusterName string, databaseName string) string { + if clusterName == "" { + clusterName = p.Cluster + } + + if path, ok := p.virtualPathFromEnv(VirtualPathDatabase, VirtualPathDatabaseParams(databaseName)); ok { + return path + } + + return keypaths.DatabaseOracleWalletDirectory(p.Dir, p.Name, p.Username, clusterName, databaseName) +} + +// DatabaseLocalCAPath returns the specified db 's self-signed localhost CA path for +// this profile. +// +// It's kept in /keys//-db/proxy-localca.pem +func (p *ProfileStatus) DatabaseLocalCAPath() string { + if path, ok := p.virtualPathFromEnv(VirtualPathDatabase, nil); ok { + return path + } + return filepath.Join(keypaths.DatabaseDir(p.Dir, p.Name, p.Username), "proxy-localca.pem") +} + // AppCertPath returns path to the specified app access certificate // for this profile. // diff --git a/lib/defaults/defaults.go b/lib/defaults/defaults.go index 145d34764a07b..bdd687139bbfb 100644 --- a/lib/defaults/defaults.go +++ b/lib/defaults/defaults.go @@ -421,6 +421,8 @@ const ( ProtocolMySQL = "mysql" // ProtocolMongoDB is the MongoDB database protocol. ProtocolMongoDB = "mongodb" + // ProtocolOracle is the Oracle database protocol. + ProtocolOracle = "oracle" // ProtocolRedis is the Redis database protocol. ProtocolRedis = "redis" // ProtocolCockroachDB is the CockroachDB database protocol. @@ -446,6 +448,7 @@ var DatabaseProtocols = []string{ ProtocolPostgres, ProtocolMySQL, ProtocolMongoDB, + ProtocolOracle, ProtocolCockroachDB, ProtocolRedis, ProtocolSnowflake, @@ -465,6 +468,8 @@ func ReadableDatabaseProtocol(p string) string { return "MySQL" case ProtocolMongoDB: return "MongoDB" + case ProtocolOracle: + return "Oracle" case ProtocolCockroachDB: return "CockroachDB" case ProtocolRedis: diff --git a/lib/devicetrust/friendly_enums.go b/lib/devicetrust/friendly_enums.go index 08144d08e10ca..e37fd84175781 100644 --- a/lib/devicetrust/friendly_enums.go +++ b/lib/devicetrust/friendly_enums.go @@ -14,11 +14,7 @@ package devicetrust -import ( - "github.com/gravitational/trace" - - devicepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1" -) +import devicepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1" // FriendlyOSType returns a user-friendly OSType representation. // Recommended for user-facing messages. @@ -47,59 +43,3 @@ func FriendlyDeviceEnrollStatus(enrollStatus devicepb.DeviceEnrollStatus) string return enrollStatus.String() } } - -// ResourceOSTypeToString converts OSType to a string representation suitable for use in resource fields. -func ResourceOSTypeToString(osType devicepb.OSType) string { - switch osType { - case devicepb.OSType_OS_TYPE_LINUX: - return "linux" - case devicepb.OSType_OS_TYPE_MACOS: - return "macos" - case devicepb.OSType_OS_TYPE_WINDOWS: - return "windows" - default: - return osType.String() - } -} - -// ResourceOSTypeFromString converts a string representation of OSType suitable for resource fields to OSType. -func ResourceOSTypeFromString(osType string) (devicepb.OSType, error) { - switch osType { - case "linux": - return devicepb.OSType_OS_TYPE_LINUX, nil - case "macos": - return devicepb.OSType_OS_TYPE_MACOS, nil - case "windows": - return devicepb.OSType_OS_TYPE_WINDOWS, nil - default: - return devicepb.OSType_OS_TYPE_UNSPECIFIED, trace.BadParameter("unknown os type %q", osType) - } -} - -// ResourceEnrollStatusToString converts DeviceEnrollStatus to a string representation suitable for use in resource fields. -func ResourceEnrollStatusToString(enrollStatus devicepb.DeviceEnrollStatus) string { - switch enrollStatus { - case devicepb.DeviceEnrollStatus_DEVICE_ENROLL_STATUS_ENROLLED: - return "enrolled" - case devicepb.DeviceEnrollStatus_DEVICE_ENROLL_STATUS_NOT_ENROLLED: - return "not_enrolled" - case devicepb.DeviceEnrollStatus_DEVICE_ENROLL_STATUS_UNSPECIFIED: - return "unspecified" - default: - return enrollStatus.String() - } -} - -// ResourceEnrollStatusFromString converts a string representation of DeviceEnrollStatus suitable for resource fields to DeviceEnrollStatus. -func ResourceEnrollStatusFromString(enrollStatus string) (devicepb.DeviceEnrollStatus, error) { - switch enrollStatus { - case "enrolled": - return devicepb.DeviceEnrollStatus_DEVICE_ENROLL_STATUS_ENROLLED, nil - case "not_enrolled": - return devicepb.DeviceEnrollStatus_DEVICE_ENROLL_STATUS_NOT_ENROLLED, nil - case "unspecified": - return devicepb.DeviceEnrollStatus_DEVICE_ENROLL_STATUS_UNSPECIFIED, nil - default: - return devicepb.DeviceEnrollStatus_DEVICE_ENROLL_STATUS_UNSPECIFIED, trace.BadParameter("unknown enroll status %q", enrollStatus) - } -} diff --git a/lib/events/athena/athena.go b/lib/events/athena/athena.go new file mode 100644 index 0000000000000..39b5cfaf42748 --- /dev/null +++ b/lib/events/athena/athena.go @@ -0,0 +1,372 @@ +// Copyright 2023 Gravitational, Inc +// +// 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. + +package athena + +import ( + "context" + "math" + "net/url" + "regexp" + "strconv" + "strings" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + awsconfig "github.com/aws/aws-sdk-go-v2/config" + "github.com/gravitational/trace" + "github.com/jonboulle/clockwork" + log "github.com/sirupsen/logrus" + + "github.com/gravitational/teleport" + "github.com/gravitational/teleport/api/types" + apievents "github.com/gravitational/teleport/api/types/events" + "github.com/gravitational/teleport/lib/events" + "github.com/gravitational/teleport/lib/session" + "github.com/gravitational/teleport/lib/utils" +) + +const ( + // TODO(tobiaszheller): move to batcher.go in other PR. + // maxWaitTimeOnReceiveMessageFromSQS defines how long single + // receiveFromQueue will wait if there is no max events (10). + maxWaitTimeOnReceiveMessageFromSQS = 5 * time.Second +) + +// Config structure represents Athena configuration. +// Right now the only way to set config is via url params. +type Config struct { + // Region is where Athena, SQS and SNS lives (required). + Region string + + // Publisher settings. + + // TopicARN where to emit events in SNS (required). + TopicARN string + // LargeEventsS3 is location on S3 where temporary large events (>256KB) + // are stored before converting it to Parquet and moving to long term + // storage (required). + LargeEventsS3 string + + // Query settings. + + // Database is name of Glue Database that Athena will query against (required). + Database string + // TableName is name of Glue Table that Athena will query against (required). + TableName string + // LocationS3 is location on S3 where Parquet files partitioned by date are + // stored (required). + LocationS3 string + // QueryResultsS3 is location on S3 where Athena stored query results (optional). + // Default results path can be defined by in workgroup settings. + QueryResultsS3 string + // Workgroup is Glue workgroup where Athena queries are executed (optional). + Workgroup string + // GetQueryResultsInterval is used to define how long query will wait before + // checking again for results status if previous status was not ready (optional). + GetQueryResultsInterval time.Duration + // LimiterRate defines rate at which search_event rate limiter is filled (optional). + LimiterRate float64 + // LimiterBurst defines rate limit bucket capacity (optional). + LimiterBurst int + + // Batcher settings. + + // QueueURL is URL of SQS, which is set as subscriber to SNS topic (required). + QueueURL string + // BatchMaxItems defines how many items can be stored in single Parquet + // batch (optional). + // It's soft limit. + BatchMaxItems int + // BatchMaxInterval defined interval at which parquet files will be created (optional). + BatchMaxInterval time.Duration + + // Clock is a clock interface, used in tests. + Clock clockwork.Clock + // UIDGenerator is unique ID generator. + UIDGenerator utils.UID + + // TODO(tobiaszheller): add FIPS config in later phase. +} + +// CheckAndSetDefaults is a helper returns an error if the supplied configuration +// is not enough to setup Athena based audit log. +func (cfg *Config) CheckAndSetDefaults() error { + // AWS restrictions (https://docs.aws.amazon.com/athena/latest/ug/tables-databases-columns-names.html) + const glueNameMaxLen = 255 + if cfg.Database == "" { + return trace.BadParameter("Database is not specified") + } + if len(cfg.Database) > glueNameMaxLen { + return trace.BadParameter("Database name too long") + } + if !isAlphanumericOrUnderscore(cfg.Database) { + return trace.BadParameter("Database name can contains only alphanumeric or underscore characters") + } + + if cfg.TableName == "" { + return trace.BadParameter("TableName is not specified") + } + if len(cfg.TableName) > glueNameMaxLen { + return trace.BadParameter("TableName too long") + } + // TableName is appended directly to athena query. That's why we put extra care + // that no weird chars are passed here. + if !isAlphanumericOrUnderscore(cfg.TableName) { + return trace.BadParameter("TableName can contains only alphanumeric or underscore characters") + } + + if cfg.TopicARN == "" { + return trace.BadParameter("TopicARN is not specified") + } + + if cfg.LocationS3 == "" { + return trace.BadParameter("LocationS3 is not specified") + } + if scheme, ok := isValidUrlWithScheme(cfg.LocationS3); !ok || scheme != "s3" { + return trace.BadParameter("LocationS3 must be valid url and start with s3") + } + + if cfg.LargeEventsS3 == "" { + return trace.BadParameter("LargeEventsS3 is not specified") + } + if scheme, ok := isValidUrlWithScheme(cfg.LargeEventsS3); !ok || scheme != "s3" { + return trace.BadParameter("LargeEventsS3 must be valid url and start with s3") + } + + if cfg.QueueURL == "" { + return trace.BadParameter("QueueURL is not specified") + } + if scheme, ok := isValidUrlWithScheme(cfg.QueueURL); !ok || scheme != "https" { + return trace.BadParameter("QueueURL must be valid url and start with https") + } + + if cfg.GetQueryResultsInterval == 0 { + cfg.GetQueryResultsInterval = 100 * time.Millisecond + } + + if cfg.BatchMaxItems == 0 { + // 20000 items, per average 500KB event size = 10MB + cfg.BatchMaxItems = 20000 + } + + if cfg.BatchMaxInterval == 0 { + cfg.BatchMaxInterval = 1 * time.Minute + } + + if cfg.BatchMaxInterval < maxWaitTimeOnReceiveMessageFromSQS { + // If BatchMaxInterval is shorter it will mean we will cancel all + // requests when there is less messages than 10 on queue. + // This can be fixed by shortening timeout on read, but realisticly + // no-one should use that short interval, so it's easier to check here. + // For high load operation, BatchMaxItems will happen first. + return trace.BadParameter("BatchMaxInterval too short, must be greater than 5s") + } + + if cfg.LimiterRate < 0 { + return trace.BadParameter("LimiterRate cannot be nagative") + } + if cfg.LimiterBurst < 0 { + return trace.BadParameter("LimiterBurst cannot be nagative") + } + + if cfg.LimiterRate > 0 && cfg.LimiterBurst == 0 { + return trace.BadParameter("LimiterBurst must be greater than 0 if LimiterRate is used") + } + + if cfg.LimiterBurst > 0 && math.Abs(cfg.LimiterRate) < 1e-9 { + return trace.BadParameter("LimiterRate must be greater than 0 if LimiterBurst is used") + } + + if cfg.Clock == nil { + cfg.Clock = clockwork.NewRealClock() + } + if cfg.UIDGenerator == nil { + cfg.UIDGenerator = utils.NewRealUID() + } + + return nil +} + +// SetFromURL establishes values on an EventsConfig from the supplied URI +func (cfg *Config) SetFromURL(url *url.URL) error { + splitted := strings.Split(url.Host, ".") + if len(splitted) != 2 { + return trace.BadParameter("invalid athena address, supported format is 'athena://database.table', got %q", url.Host) + } + cfg.Database, cfg.TableName = splitted[0], splitted[1] + + topicARN := url.Query().Get("topicArn") + if topicARN != "" { + cfg.TopicARN = topicARN + } + largeEventsS3 := url.Query().Get("largeEventsS3") + if largeEventsS3 != "" { + cfg.LargeEventsS3 = largeEventsS3 + } + + locationS3 := url.Query().Get("locationS3") + if locationS3 != "" { + cfg.LocationS3 = locationS3 + } + queryResultsS3 := url.Query().Get("queryResultsS3") + if queryResultsS3 != "" { + cfg.QueryResultsS3 = queryResultsS3 + } + workgroup := url.Query().Get("workgroup") + if workgroup != "" { + cfg.Workgroup = workgroup + } + getQueryResultsInterval := url.Query().Get("getQueryResultsInterval") + if getQueryResultsInterval != "" { + dur, err := time.ParseDuration(getQueryResultsInterval) + if err != nil { + return trace.BadParameter("invalid getQueryResultsInterval value: %v", err) + } + cfg.GetQueryResultsInterval = dur + } + rateInString := url.Query().Get("limiterRate") + if rateInString != "" { + rate, err := strconv.ParseFloat(rateInString, 32) + if err != nil { + return trace.BadParameter("invalid limiterRate value (it must be float32): %v", err) + } + cfg.LimiterRate = rate + } + burstInString := url.Query().Get("limiterBurst") + if burstInString != "" { + burst, err := strconv.Atoi(burstInString) + if err != nil { + return trace.BadParameter("invalid limiterBurst value (it must be int): %v", err) + } + cfg.LimiterBurst = burst + } + + queueURL := url.Query().Get("queueURL") + if queueURL != "" { + cfg.QueueURL = queueURL + } + batchMaxItems := url.Query().Get("batchMaxItems") + if batchMaxItems != "" { + intMaxItems, err := strconv.Atoi(batchMaxItems) + if err != nil { + return trace.BadParameter("invalid batchMaxItems value (it must be int): %v", err) + } + cfg.BatchMaxItems = intMaxItems + } + batchMaxInterval := url.Query().Get("batchMaxInterval") + if batchMaxInterval != "" { + dur, err := time.ParseDuration(batchMaxInterval) + if err != nil { + return trace.BadParameter("invalid batchMaxInterval value: %v", err) + } + cfg.BatchMaxInterval = dur + } + + return nil +} + +// Log is an events storage backend. +// +// It's using SNS for emitting events. +// SQS is used as subscriber for SNS topic. +// Consumer uses SQS to read multiple events, create batch, convert it to +// Parquet and send it to S3 for long term storage. +// Athena is used for quering Parquet files on S3. +type Log struct { + // Entry is a log entry + *log.Entry + // Config is a backend configuration + Config + + awsConfig aws.Config +} + +// New creates an instance of an Athena based audit log. +func New(ctx context.Context, cfg Config) (*Log, error) { + err := cfg.CheckAndSetDefaults() + if err != nil { + return nil, trace.Wrap(err) + } + logEntry := log.WithFields(log.Fields{ + trace.Component: teleport.ComponentAthena, + }) + l := &Log{ + Entry: logEntry, + Config: cfg, + } + + l.awsConfig, err = awsconfig.LoadDefaultConfig(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + // override the default environment (region + credentials) with the values + // from the config. + if cfg.Region != "" { + l.awsConfig.Region = cfg.Region + } + + // TODO(tobiaszheller): initialize publisher + // TODO(tobiaszheller): initialize batcher + // TODO(tobiaszheller): initialize querier + + return l, nil +} + +func (l *Log) EmitAuditEvent(ctx context.Context, in apievents.AuditEvent) error { + return trace.NotImplemented("not implemented") +} + +func (l *Log) GetSessionChunk(namespace string, sid session.ID, offsetBytes, maxBytes int) ([]byte, error) { + return nil, trace.NotImplemented("not implemented") +} + +func (l *Log) GetSessionEvents(namespace string, sid session.ID, after int, includePrintEvents bool) ([]events.EventFields, error) { + return nil, trace.NotImplemented("not implemented") +} + +func (l *Log) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventTypes []string, limit int, order types.EventOrder, startKey string) ([]apievents.AuditEvent, string, error) { + return nil, "", trace.NotImplemented("not implemented") +} + +func (l *Log) SearchSessionEvents(fromUTC, toUTC time.Time, limit int, order types.EventOrder, startKey string, cond *types.WhereExpr, sessionID string) ([]apievents.AuditEvent, string, error) { + return nil, "", trace.NotImplemented("not implemented") +} + +func (l *Log) Close() error { + return nil +} + +func (l *Log) StreamSessionEvents(ctx context.Context, sessionID session.ID, startIndex int64) (chan apievents.AuditEvent, chan error) { + c, e := make(chan apievents.AuditEvent), make(chan error, 1) + e <- trace.NotImplemented("not implemented") + return c, e +} + +var isAlphanumericOrUnderscoreRe = regexp.MustCompile("^[a-zA-Z0-9_]+$") + +func isAlphanumericOrUnderscore(s string) bool { + return isAlphanumericOrUnderscoreRe.MatchString(s) +} + +func isValidUrlWithScheme(s string) (string, bool) { + u, err := url.Parse(s) + if err != nil { + return "", false + } + if u.Scheme == "" { + return "", false + } + return u.Scheme, true +} diff --git a/lib/events/athena/athena_test.go b/lib/events/athena/athena_test.go new file mode 100644 index 0000000000000..60d084c2df5a0 --- /dev/null +++ b/lib/events/athena/athena_test.go @@ -0,0 +1,232 @@ +// Copyright 2023 Gravitational, Inc +// +// 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. + +package athena + +import ( + "net/url" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/stretchr/testify/require" +) + +func TestConfig_SetFromURL(t *testing.T) { + tests := []struct { + name string + url string + want Config + wantErr string + }{ + { + name: "params to emiter", + url: "athena://db.tbl/?topicArn=arn:topic&largeEventsS3=s3://large-events-bucket", + want: Config{ + TableName: "tbl", + Database: "db", + TopicARN: "arn:topic", + LargeEventsS3: "s3://large-events-bucket", + }, + }, + { + name: "params to querier - part 1", + url: "athena://db.tbl/?locationS3=s3://events-bucket&queryResultsS3=s3://results-bucket&workgroup=default", + want: Config{ + TableName: "tbl", + Database: "db", + LocationS3: "s3://events-bucket", + QueryResultsS3: "s3://results-bucket", + Workgroup: "default", + }, + }, + { + name: "params to querier - part 2", + url: "athena://db.tbl/?getQueryResultsInterval=200ms&limiterRate=0.642&limiterBurst=3", + want: Config{ + TableName: "tbl", + Database: "db", + GetQueryResultsInterval: 200 * time.Millisecond, + LimiterRate: 0.642, + LimiterBurst: 3, + }, + }, + { + name: "params to batcher", + url: "athena://db.tbl/?queueURL=https://queueURL&batchMaxItems=1000&batchMaxInterval=10s", + want: Config{ + TableName: "tbl", + Database: "db", + QueueURL: "https://queueURL", + BatchMaxItems: 1000, + BatchMaxInterval: 10 * time.Second, + }, + }, + { + name: "invalid database/table format", + url: "athena://dsa.dsa.dsa", + wantErr: "invalid athena address, supported format is 'athena://database.table'", + }, + { + name: "invalid limiterRate format", + url: "athena://db.tbl/?limiterRate=abc", + wantErr: "invalid limiterRate value (it must be float32)", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := &Config{} + u, err := url.Parse(tt.url) + require.NoError(t, err, "Failed to parse url") + err = cfg.SetFromURL(u) + if tt.wantErr == "" { + require.NoError(t, err, "SetFromURL return unexpected err") + require.Empty(t, cmp.Diff(tt.want, *cfg, cmpopts.EquateApprox(0, 0.0001))) + } else { + require.ErrorContains(t, err, tt.wantErr) + } + }) + } +} + +func TestConfig_CheckAndSetDefaults(t *testing.T) { + validConfig := Config{ + Database: "db", + TableName: "tbl", + TopicARN: "arn:topic", + LargeEventsS3: "s3://large-payloads-bucket", + LocationS3: "s3://events-bucket", + QueueURL: "https://queue-url", + } + tests := []struct { + name string + input func() Config + want Config + wantErr string + }{ + { + name: "minimum config with defaults", + input: func() Config { + return validConfig + }, + want: Config{ + Database: "db", + TableName: "tbl", + TopicARN: "arn:topic", + LargeEventsS3: "s3://large-payloads-bucket", + LocationS3: "s3://events-bucket", + QueueURL: "https://queue-url", + GetQueryResultsInterval: 100 * time.Millisecond, + BatchMaxItems: 20000, + BatchMaxInterval: 1 * time.Minute, + }, + }, + { + name: "missing table name", + input: func() Config { + cfg := validConfig + cfg.TableName = "" + return cfg + }, + wantErr: "TableName is not specified", + }, + { + name: "invalid table name", + input: func() Config { + cfg := validConfig + cfg.TableName = "table with space" + return cfg + }, + wantErr: "TableName can contains only alphanumeric or underscore character", + }, + { + name: "missing topicARN", + input: func() Config { + cfg := validConfig + cfg.TopicARN = "" + return cfg + }, + wantErr: "TopicARN is not specified", + }, + { + name: "missing LocationS3", + input: func() Config { + cfg := validConfig + cfg.LocationS3 = "" + return cfg + }, + wantErr: "LocationS3 is not specified", + }, + { + name: "invalid LocationS3", + input: func() Config { + cfg := validConfig + cfg.LocationS3 = "https://abc" + return cfg + }, + wantErr: "LocationS3 must be valid url and start with s3", + }, + { + name: "missing QueueURL", + input: func() Config { + cfg := validConfig + cfg.QueueURL = "" + return cfg + }, + wantErr: "QueueURL is not specified", + }, + { + name: "invalid QueueURL", + input: func() Config { + cfg := validConfig + cfg.QueueURL = "s3://abc" + return cfg + }, + wantErr: "QueueURL must be valid url and start with https", + }, + { + name: "invalid LimiterBurst and LimiterRate combination", + input: func() Config { + cfg := validConfig + cfg.LimiterBurst = 0 + cfg.LimiterRate = 2.5 + return cfg + }, + wantErr: "LimiterBurst must be greater than 0 if LimiterRate is used", + }, + { + name: "invalid LimiterRate and LimiterBurst combination", + input: func() Config { + cfg := validConfig + cfg.LimiterBurst = 3 + cfg.LimiterRate = 0 + return cfg + }, + wantErr: "LimiterRate must be greater than 0 if LimiterBurst is used", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := tt.input() + err := cfg.CheckAndSetDefaults() + if tt.wantErr == "" { + require.NoError(t, err, "CheckAndSetDefaults return unexpected err") + require.Empty(t, cmp.Diff(tt.want, cfg, cmpopts.EquateApprox(0, 0.0001), cmpopts.IgnoreFields(Config{}, "Clock", "UIDGenerator"))) + } else { + require.ErrorContains(t, err, tt.wantErr) + } + }) + } +} diff --git a/lib/fixtures/fixtures.go b/lib/fixtures/fixtures.go index 9d6ce39dc1a4b..cf86dd07d1959 100644 --- a/lib/fixtures/fixtures.go +++ b/lib/fixtures/fixtures.go @@ -19,6 +19,8 @@ import ( "testing" "github.com/gravitational/trace" + + apifixtures "github.com/gravitational/teleport/api/fixtures" ) // AssertNotFound expects not found error @@ -149,52 +151,8 @@ spec: pHM7WKwFyW1dvEDax3BGj9/cbKvpvcwRurn:oasis:names:tc:SAML:1.1:nameid-format:emailAddressurn:oasis:names:tc:SAML:1.1:nameid-format:unspecified` const ( - TLSCACertPEM = `-----BEGIN CERTIFICATE----- -MIIDKjCCAhKgAwIBAgIQJtJDJZZBkg/afM8d2ZJCTjANBgkqhkiG9w0BAQsFADBA -MRUwEwYDVQQKEwxUZWxlcG9ydCBPU1MxJzAlBgNVBAMTHnRlbGVwb3J0LmxvY2Fs -aG9zdC5sb2NhbGRvbWFpbjAeFw0xNzA1MDkxOTQwMzZaFw0yNzA1MDcxOTQwMzZa -MEAxFTATBgNVBAoTDFRlbGVwb3J0IE9TUzEnMCUGA1UEAxMedGVsZXBvcnQubG9j -YWxob3N0LmxvY2FsZG9tYWluMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC -AQEAuKFLaf2iII/xDR+m2Yj6PnUEa+qzqwxsdLUjnunFZaAXG+hZm4Ml80SCiBgI -gTHQlJyLIkTtuRoH5aeMyz1ERUCtii4ZsTqDrjjUybxP4r+4HVX6m34s6hwEr8Fi -fts9pMp4iS3tQguRc28gPdDo/T6VrJTVYUfUUsNDRtIrlB5O9igqqLnuaY9eqGi4 -PUx0G0wRYJpRywoj8G0IkpfQTiX+CAC7dt5ws7ZrnGqCNBLGi5bGsaMmptVbsSEp -1TenntF54V1iR49IV5JqDhm1S0HmkleoJzKdc+6sP/xNepz9PJzuF9d9NubTLWgB -sK28YItcmWHdHXD/ODxVaehRjwIDAQABoyAwHjAOBgNVHQ8BAf8EBAMCB4AwDAYD -VR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAAVU6sNBdj76saHwOxGSdnEqQ -o2tMuR3msSM4F6wFK2UkKepsD7CYIf/PzNSNUqA5JIEUVeMqGyiHuAbU4C655nT1 -IyJX1D/+r73sSp5jbIpQm2xoQGZnj6g/Kltw8OSOAw+DsMF/PLVqoWJp07u6ew/m -NxWsJKcZ5k+q4eMxci9mKRHHqsquWKXzQlURMNFI+mGaFwrKM4dmzaR0BEc+ilSx -QqUvQ74smsLK+zhNikmgjlGC5ob9g8XkhVAkJMAh2rb9onDNiRl68iAgczP88mXu -vN/o98dypzsPxXmw6tkDqIRPUAUbh465rlY5sKMmRgXi2rUfl/QV5nbozUo/HQ== ------END CERTIFICATE-----` - TLSCAKeyPEM = `-----BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEAuKFLaf2iII/xDR+m2Yj6PnUEa+qzqwxsdLUjnunFZaAXG+hZ -m4Ml80SCiBgIgTHQlJyLIkTtuRoH5aeMyz1ERUCtii4ZsTqDrjjUybxP4r+4HVX6 -m34s6hwEr8Fifts9pMp4iS3tQguRc28gPdDo/T6VrJTVYUfUUsNDRtIrlB5O9igq -qLnuaY9eqGi4PUx0G0wRYJpRywoj8G0IkpfQTiX+CAC7dt5ws7ZrnGqCNBLGi5bG -saMmptVbsSEp1TenntF54V1iR49IV5JqDhm1S0HmkleoJzKdc+6sP/xNepz9PJzu -F9d9NubTLWgBsK28YItcmWHdHXD/ODxVaehRjwIDAQABAoIBABy4orWrShRMsA/9 -k4QVpfAfXf+3tBlwxlJld1QaQ6XqgI3L2FyzyyyLxM6NBo2qhSsJKy+6j0yTOxVD -ukhHkJ5BUH3FbCPA2Yk5uAhl7ft1HZwaqvCTcUM99pCswbjAPFetU5DrfxQeHpNZ -fyd+ny/+E2SUhpkqhmIVlBqpSTQyOywbiEvZ6ZiFmncdHhXaCy3YZsylrKUGPzsJ -jfU2iOE167eTOIjPStsaoCPv9jLSyy2OvuNNudS+Y1qkFz8ZGvPp+HB+Iig+AlAE -7KMzNrIW7PlHTDgUly1cRCl3+84yE2mJ97+hHiEy//HIwVDUpI529i2hMYM/u4qz -Wso/2tkCgYEA2FdE4bmCrZiA9eS8qobwGLE1+MJME4YwfJkynZUHHX93xORPQ66e -WYpN7/xbMvBDa8LZZYVTNVtZ/SkEUaTb5NQW2zXKoIutk1PFBb8NbA0m8Ss/mOJA -d5nUYGr987O9fRh1yP9TksBshHB/5A8U2UG8MFFCNvJTZDPRkuSlMiUCgYEA2nnb -hAJrhY7PaF6jdfimGvvponkUiEbWLppg7/SjgPg+QgqIwuLybryXyOAp+TEnNzgU -ujAjhNtIiyB/B13TDxOgUgWUWPbPvUAWGEvwI9h+RLie1umGHd48G1NR76fwqSf1 -y7z3YRnq8vCdz8ywB3o5GO6SH6QkMJBIxfIMlKMCgYA55akOi7oYQT8KD4waSwCI -ayyZhU4cz4W8Yrd0CsUbtNhVvhAked/w8J2JA01Y5Yn1lfDeRX8OQYNkyAxa2Tbs -F4KCafPvYVIzonCQ6B9sclygoEVl4e8E0wtOPnP2O30TtG8ZOpOgK5UfIIhpfUvE -FN6LQ8PntpRwtZl5qW04bQKBgGnHhFxHG64fthZPdA9jY3E/NSCgRSuyOHN59aNY -rG1+RA6PsSXC4iRxlYAB4PCxNs6KjaaUNi5WSaprAnYbnFv5Ya802l20qmJ0C/6Z -jdydLo2xYd6mVHRTrICCd/J0OpZ8LYsGpDPUa6hSjeYVscj9CXYj1IYTYB5PTZzh -k+vHAoGBAJyA+RtBF5m64/TqhZFcesTtnpWaRhQ50xXnNVF3W1eKGPtdTDKOaENA -LJxgC1GdoEz2ilXW802H9QrdKf9GPqxwi2TVzfO6pzWkdZcmbItu+QCCFz+co+r8 -+ki49FmlfbR5YVPN+8X40aLQB4xDkCHwRwTkrigzWQhIOv8NAhDA ------END RSA PRIVATE KEY-----` + TLSCACertPEM = apifixtures.TLSCACertPEM + TLSCAKeyPEM = apifixtures.TLSCAKeyPEM // Backwards-compatibility alias for teleport.e SigningCertPEM = TLSCACertPEM ) diff --git a/lib/gitlab/gitlab.go b/lib/gitlab/gitlab.go index 4434bced22db9..0e50be59a7bab 100644 --- a/lib/gitlab/gitlab.go +++ b/lib/gitlab/gitlab.go @@ -45,6 +45,9 @@ import ( // https://gitlab.com/.well-known/openid-configuration // For GitLab self-hosted servers, this will be at // https://$HOSTNAME/.well-known/openid-configuration +// +// The minimum supported GitLab version is 15.7, as this is when ID token +// support was introduced. // IDTokenClaims is the structure of claims contained within a GitLab issued // ID token. diff --git a/lib/httplib/csrf/csrf.go b/lib/httplib/csrf/csrf.go index c5d3d3945a28f..47dbf68a727f1 100644 --- a/lib/httplib/csrf/csrf.go +++ b/lib/httplib/csrf/csrf.go @@ -34,6 +34,8 @@ const ( CookieName = "__Host-grv_csrf" // HeaderName is the default HTTP request header to inspect. HeaderName = "X-CSRF-Token" + // FormFieldName is the default form field to inspect. + FormFieldName = "csrf_token" // tokenLenBytes is CSRF token length in bytes. tokenLenBytes = 32 // defaultMaxAge is the default MaxAge for cookies. @@ -76,6 +78,21 @@ func VerifyHTTPHeader(r *http.Request) error { return nil } +// VerifyFormField checks if HTTP form value matches the cookie. +func VerifyFormField(r *http.Request) error { + token := r.FormValue(FormFieldName) + if len(token) == 0 { + return trace.BadParameter("cannot retrieve CSRF token from form field %q", FormFieldName) + } + + err := VerifyToken(token, r) + if err != nil { + return trace.Wrap(err) + } + + return nil +} + // VerifyToken validates given token based on HTTP request cookie func VerifyToken(token string, r *http.Request) error { realToken, err := ExtractTokenFromCookie(r) diff --git a/lib/httplib/httplib.go b/lib/httplib/httplib.go index 1e26d590df118..92a82e5f9c5df 100644 --- a/lib/httplib/httplib.go +++ b/lib/httplib/httplib.go @@ -139,15 +139,18 @@ func MakeStdHandlerWithErrorWriter(fn StdHandlerFunc, errWriter ErrorWriter) htt // WithCSRFProtection ensures that request to unauthenticated API is checked against CSRF attacks func WithCSRFProtection(fn HandlerFunc) httprouter.Handle { - hanlderFn := MakeHandler(fn) + handlerFn := MakeHandler(fn) return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { - err := csrf.VerifyHTTPHeader(r) - if err != nil { - log.Warningf("unable to validate CSRF token %v", err) - trace.WriteError(w, trace.AccessDenied("access denied")) - return + if r.Method != http.MethodGet && r.Method != http.MethodHead { + errHeader := csrf.VerifyHTTPHeader(r) + errForm := csrf.VerifyFormField(r) + if errForm != nil && errHeader != nil { + log.Warningf("unable to validate CSRF token: %v, %v", errHeader, errForm) + trace.WriteError(w, trace.AccessDenied("access denied")) + return + } } - hanlderFn(w, r, p) + handlerFn(w, r, p) } } diff --git a/lib/kube/proxy/exec_test.go b/lib/kube/proxy/exec_test.go index 5c34494ace37d..844a2d9746f6b 100644 --- a/lib/kube/proxy/exec_test.go +++ b/lib/kube/proxy/exec_test.go @@ -23,6 +23,7 @@ import ( "io" "net/http" "net/url" + "strings" "testing" "github.com/stretchr/testify/require" @@ -32,6 +33,7 @@ import ( "k8s.io/client-go/tools/remotecommand" "k8s.io/kubectl/pkg/scheme" + "github.com/gravitational/teleport" testingkubemock "github.com/gravitational/teleport/lib/kube/proxy/testing/kube_server" ) @@ -182,12 +184,14 @@ func TestExecKubeService(t *testing.T) { } req, err := generateExecRequest( - testCtx.KubeServiceAddress(), - podName, - podNamespace, - podContainerName, - containerCommmandExecute, // placeholder for commands to execute in the dummy pod - streamOpts, + generateExecRequestConfig{ + addr: testCtx.KubeServiceAddress(), + podName: podName, + podNamespace: podNamespace, + containerName: podContainerName, + cmd: containerCommmandExecute, // placeholder for commands to execute in the dummy pod + options: streamOpts, + }, ) require.NoError(t, err) // configure the client to impersonate the user. @@ -208,12 +212,31 @@ func TestExecKubeService(t *testing.T) { } } +type generateExecRequestConfig struct { + // addr is the address of the Kube API server. + addr string + // podName is the name of the pod to execute the command in. + podName string + // podNamespace is the namespace of the pod to execute the command in. + podNamespace string + // containerName is the name of the container to execute the command in. + containerName string + // cmd is the command to execute in the container. + cmd []string + // options are the options for the command execution. + options remotecommand.StreamOptions + // reason is the reason for the command execution. + reason string + // invite is the list of users to invite. + invite []string +} + // generateExecRequest generates a Kube API url for executing commands in pods. // The url format is the following: -// "/api/v1/namespaces/{podNamespace}/pods/{podName}/exec?stderr={stdout}&stdout={stdout}&tty={tty}. -func generateExecRequest(addr, podName, podNamespace, containerName string, cmd []string, options remotecommand.StreamOptions) (*rest.Request, error) { +// "/api/v1/namespaces/{podNamespace}/pods/{podName}/exec?stderr={stdout}&stdout={stdout}&tty={tty}&reason={reason}&container={containerName}&command={command}" +func generateExecRequest(cfg generateExecRequestConfig) (*rest.Request, error) { restClient, err := rest.RESTClientFor(&rest.Config{ - Host: addr, + Host: cfg.addr, APIPath: "/api", ContentConfig: rest.ContentConfig{ GroupVersion: &corev1.SchemeGroupVersion, @@ -231,13 +254,15 @@ func generateExecRequest(addr, podName, podNamespace, containerName string, cmd Namespace(podNamespace). SubResource("exec"). VersionedParams(&corev1.PodExecOptions{ - Container: containerName, - Command: cmd, - Stdin: options.Stdin != nil, - Stdout: options.Stdout != nil, - Stderr: options.Stderr != nil, - TTY: options.Tty, - }, scheme.ParameterCodec) + Container: cfg.containerName, + Command: cfg.cmd, + Stdin: cfg.options.Stdin != nil, + Stdout: cfg.options.Stdout != nil, + Stderr: cfg.options.Stderr != nil, + TTY: cfg.options.Tty, + }, scheme.ParameterCodec). + Param(teleport.KubeSessionInvitedQueryParam, strings.Join(cfg.invite, ",")). + Param(teleport.KubeSessionReasonQueryParam, cfg.reason) return req, nil } diff --git a/lib/kube/proxy/forwarder.go b/lib/kube/proxy/forwarder.go index a5fedc5c82bf1..39eaa18fd0d7e 100644 --- a/lib/kube/proxy/forwarder.go +++ b/lib/kube/proxy/forwarder.go @@ -352,8 +352,19 @@ type Forwarder struct { sessions map[uuid.UUID]*session // upgrades connections to websockets upgrader websocket.Upgrader + // getKubernetesServersForKubeCluster is a function that returns a list of + // kubernetes servers for a given kube cluster but uses different methods + // depending on the service type. + // For example, if the service type is KubeService, it will use the + // local kubernetes clusters. If the service type is Proxy, it will + // use the heartbeat clusters. + getKubernetesServersForKubeCluster getKubeServersByNameFunc } +// getKubeServersByNameFunc is a function that returns a list of +// kubernetes servers for a given kube cluster. +type getKubeServersByNameFunc = func(ctx context.Context, name string) ([]types.KubeServer, error) + // Close signals close to all outstanding or background operations // to complete func (f *Forwarder) Close() error { @@ -396,6 +407,9 @@ type authContext struct { kubeResource *types.KubernetesResource // httpMethod is the request HTTP Method. httpMethod string + // kubeServers are the registered agents for the kubernetes cluster the request + // is targeted to. + kubeServers []types.KubeServer } func (c authContext) String() string { @@ -454,6 +468,9 @@ type handlerWithAuthFunc func(ctx *authContext, w http.ResponseWriter, r *http.R // handlerWithAuthFuncStd is http handler with passed auth context type handlerWithAuthFuncStd func(ctx *authContext, w http.ResponseWriter, r *http.Request) (any, error) +// accessDeniedMsg is a message returned to the client when access is denied. +const accessDeniedMsg = "[00] access denied" + // authenticate function authenticates request func (f *Forwarder) authenticate(req *http.Request) (*authContext, error) { ctx, span := f.cfg.tracer.Start( @@ -467,7 +484,6 @@ func (f *Forwarder) authenticate(req *http.Request) (*authContext, error) { ) defer span.End() - const accessDeniedMsg = "[00] access denied" var isRemoteUser bool userTypeI, err := authz.UserFromContext(ctx) if err != nil { @@ -647,9 +663,7 @@ func (f *Forwarder) withAuthPassthrough(handler handlerWithAuthFunc) httprouter. authContext, err := f.authenticate(req) if err != nil { - if !trace.IsAccessDenied(err) && !trace.IsNotFound(err) { - return nil, trace.Wrap(err) - } + return nil, trace.Wrap(err) } err = f.acquireConnectionLockWithIdentity(req.Context(), authContext) if err != nil { @@ -729,7 +743,9 @@ func (f *Forwarder) setupContext(ctx context.Context, authCtx authz.Context, req } kubeCluster := identity.KubernetesCluster - if !isRemoteCluster { + // Only set a default kube cluster if the user is not accessing a specific cluster. + // The check for kubeCluster != "" is happens in the next code section. + if !isRemoteCluster && kubeCluster == "" { kc, err := kubeutils.CheckOrSetKubeCluster(ctx, f.cfg.CachingAuthClient, identity.KubernetesCluster, teleportClusterName) if err != nil { if !trace.IsNotFound(err) { @@ -744,35 +760,20 @@ func (f *Forwarder) setupContext(ctx context.Context, authCtx authz.Context, req } var ( - kubeUsers, kubeGroups []string - kubeLabels map[string]string + kubeServers []types.KubeServer + err error ) // Only check k8s principals for local clusters. // // For remote clusters, everything will be remapped to new roles on the // leaf and checked there. if !isRemoteCluster { - // check signing TTL and return a list of allowed logins for local cluster based on Kubernetes service labels. - kubeAccessDetails, err := f.getKubeAccessDetails(roles, kubeCluster, sessionTTL, kubeResource) - if err != nil && !trace.IsNotFound(err) { - return nil, trace.Wrap(err) - // roles.CheckKubeGroupsAndUsers returns trace.NotFound if the user does - // does not have at least one configured kubernetes_users or kubernetes_groups. - } else if trace.IsNotFound(err) { - const errMsg = "Your user's Teleport role does not allow Kubernetes access." + - " Please ask cluster administrator to ensure your role has appropriate kubernetes_groups and kubernetes_users set." - return nil, trace.NotFound(errMsg) + kubeServers, err = f.getKubernetesServersForKubeCluster(ctx, kubeCluster) + if err != nil || len(kubeServers) == 0 { + return nil, trace.NotFound("cluster %q not found", kubeCluster) } - - kubeUsers = kubeAccessDetails.kubeUsers - kubeGroups = kubeAccessDetails.kubeGroups - kubeLabels = kubeAccessDetails.clusterLabels } - // fillDefaultKubePrincipalDetails fills the default details in order to keep - // the correct behavior when forwarding the request to the Kubernetes API. - kubeUsers, kubeGroups = fillDefaultKubePrincipalDetails(kubeUsers, kubeGroups, authCtx.User.GetName()) - clientSrc, clientDst := utils.ClientAddrFromContext(req.Context()) forwarderType := f.cfg.KubeServiceType @@ -909,9 +910,6 @@ func (f *Forwarder) setupContext(ctx context.Context, authCtx authz.Context, req clientIdleTimeout: roles.AdjustClientIdleTimeout(netConfig.GetClientIdleTimeout()), sessionTTL: sessionTTL, Context: authCtx, - kubeGroups: utils.StringsSet(kubeGroups), - kubeUsers: utils.StringsSet(kubeUsers), - kubeClusterLabels: kubeLabels, recordingConfig: recordingConfig, kubeClusterName: kubeCluster, kubeResource: kubeResource, @@ -924,7 +922,8 @@ func (f *Forwarder) setupContext(ctx context.Context, authCtx authz.Context, req isRemote: isRemoteCluster, isRemoteClosed: isRemoteClosed, }, - httpMethod: req.Method, + httpMethod: req.Method, + kubeServers: kubeServers, }, nil } @@ -1008,16 +1007,12 @@ type kubeAccessDetails struct { // getKubeAccessDetails returns the allowed kube groups/users names and the cluster labels for a local kube cluster. func (f *Forwarder) getKubeAccessDetails( + kubeServers []types.KubeServer, roles services.AccessChecker, kubeClusterName string, sessionTTL time.Duration, kubeResource *types.KubernetesResource, ) (kubeAccessDetails, error) { - kubeServers, err := f.cfg.CachingAuthClient.GetKubernetesServers(f.ctx) - if err != nil { - return kubeAccessDetails{}, trace.Wrap(err) - } - // Find requested kubernetes cluster name and get allowed kube users/groups names. for _, s := range kubeServers { c := s.GetCluster() @@ -1123,10 +1118,7 @@ func (f *Forwarder) authorize(ctx context.Context, actx *authContext) error { f.log.WithField("auth_context", actx.String()).Debug("Skipping authorization due to unknown kubernetes cluster name") return nil } - servers, err := f.cfg.CachingAuthClient.GetKubernetesServers(ctx) - if err != nil { - return trace.Wrap(err) - } + authPref, err := f.cfg.CachingAuthClient.GetAuthPreference(ctx) if err != nil { return trace.Wrap(err) @@ -1148,12 +1140,44 @@ func (f *Forwarder) authorize(ctx context.Context, actx *authContext) error { services.NewKubernetesResourceMatcher(*actx.kubeResource), } } + var kubeUsers, kubeGroups []string + // Only check k8s principals for local clusters. + // + // For remote clusters, everything will be remapped to new roles on the + // leaf and checked there. + if !actx.teleportCluster.isRemote { + // check signing TTL and return a list of allowed logins for local cluster based on Kubernetes service labels. + kubeAccessDetails, err := f.getKubeAccessDetails(actx.kubeServers, actx.Checker, actx.kubeClusterName, actx.sessionTTL, actx.kubeResource) + if err != nil && !trace.IsNotFound(err) { + if actx.kubeResource != nil { + return trace.AccessDenied(notFoundMessage) + } + // TODO (tigrato): should return another message here. + return trace.AccessDenied(accessDeniedMsg) + // roles.CheckKubeGroupsAndUsers returns trace.NotFound if the user does + // does not have at least one configured kubernetes_users or kubernetes_groups. + } else if trace.IsNotFound(err) { + const errMsg = "Your user's Teleport role does not allow Kubernetes access." + + " Please ask cluster administrator to ensure your role has appropriate kubernetes_groups and kubernetes_users set." + return trace.NotFound(errMsg) + } + + kubeUsers = kubeAccessDetails.kubeUsers + kubeGroups = kubeAccessDetails.kubeGroups + actx.kubeClusterLabels = kubeAccessDetails.clusterLabels + } + + // fillDefaultKubePrincipalDetails fills the default details in order to keep + // the correct behavior when forwarding the request to the Kubernetes API. + kubeUsers, kubeGroups = fillDefaultKubePrincipalDetails(kubeUsers, kubeGroups, actx.User.GetName()) + actx.kubeUsers = utils.StringsSet(kubeUsers) + actx.kubeGroups = utils.StringsSet(kubeGroups) // Check authz against the first match. // // We assume that users won't register two identically-named clusters with // mis-matched labels. If they do, expect weirdness. - for _, s := range servers { + for _, s := range actx.kubeServers { ks := s.GetCluster() if ks.GetName() != actx.kubeClusterName { continue @@ -1253,7 +1277,7 @@ func (f *Forwarder) join(ctx *authContext, w http.ResponseWriter, req *http.Requ // the resources as soon as we know the session is no longer active. defer sess.close() - if err := f.setupForwardingHeaders(sess, req); err != nil { + if err := f.setupForwardingHeaders(sess, req, false /* withImpersonationHeaders */); err != nil { return nil, trace.Wrap(err) } @@ -1694,7 +1718,7 @@ func (f *Forwarder) exec(authCtx *authContext, w http.ResponseWriter, req *http. onResize: func(remotecommand.TerminalSize) {}, } - if err := f.setupForwardingHeaders(sess, req); err != nil { + if err := f.setupForwardingHeaders(sess, req, true /* withImpersonationHeaders */); err != nil { return nil, trace.Wrap(err) } @@ -1791,7 +1815,7 @@ func (f *Forwarder) portForward(authCtx *authContext, w http.ResponseWriter, req return nil, trace.Wrap(err) } - if err := f.setupForwardingHeaders(sess, req); err != nil { + if err := f.setupForwardingHeaders(sess, req, true /* withImpersonationHeaders */); err != nil { f.log.Debugf("DENIED Port forward: %v.", req.URL.String()) return nil, trace.Wrap(err) } @@ -1873,11 +1897,12 @@ const ( ImpersonationRequestDeniedMessage = "impersonation request has been denied" ) -func (f *Forwarder) setupForwardingHeaders(sess *clusterSession, req *http.Request) error { - if err := setupImpersonationHeaders(f.log, sess.authContext, req.Header); err != nil { - return trace.Wrap(err) +func (f *Forwarder) setupForwardingHeaders(sess *clusterSession, req *http.Request, withImpersonationHeaders bool) error { + if withImpersonationHeaders { + if err := setupImpersonationHeaders(f.log, sess.authContext, req.Header); err != nil { + return trace.Wrap(err) + } } - // Setup scheme, override target URL to the destination address req.URL.Scheme = "https" req.RequestURI = req.URL.Path + "?" + req.URL.RawQuery @@ -2060,7 +2085,7 @@ func (f *Forwarder) catchAll(authCtx *authContext, w http.ResponseWriter, req *h return nil, trace.Wrap(err) } - if err := f.setupForwardingHeaders(sess, req); err != nil { + if err := f.setupForwardingHeaders(sess, req, true /* withImpersonationHeaders */); err != nil { // This error goes to kubernetes client and is not visible in the logs // of the teleport server if not logged here. f.log.Errorf("Failed to set up forwarding headers: %v.", err) @@ -2281,11 +2306,7 @@ func (f *Forwarder) newClusterSessionSameCluster(ctx context.Context, authCtx au return sess, nil } - kubeServers, err := f.cfg.CachingAuthClient.GetKubernetesServers(f.ctx) - if err != nil && !trace.IsNotFound(err) { - return nil, trace.Wrap(err) - } - + kubeServers := authCtx.kubeServers if len(kubeServers) == 0 && authCtx.kubeClusterName == authCtx.teleportCluster.name { return nil, trace.Wrap(localErr) } @@ -2314,12 +2335,8 @@ func (f *Forwarder) newClusterSessionSameCluster(ctx context.Context, authCtx au } func (f *Forwarder) newClusterSessionLocal(ctx authContext) (*clusterSession, error) { - if len(f.clusterDetails) == 0 { - return nil, trace.NotFound("this Teleport process is not configured for direct Kubernetes access; you likely need to 'tsh login' into a leaf cluster or 'tsh kube login' into a different kubernetes cluster") - } - - details, ok := f.clusterDetails[ctx.kubeClusterName] - if !ok { + details, err := f.findKubeDetailsByClusterName(ctx.kubeClusterName) + if err != nil { return nil, trace.NotFound("kubernetes cluster %q not found", ctx.kubeClusterName) } @@ -2670,7 +2687,7 @@ func (f *Forwarder) listPods(authCtx *authContext, w http.ResponseWriter, req *h return nil, trace.Wrap(err) } - if err := f.setupForwardingHeaders(sess, req); err != nil { + if err := f.setupForwardingHeaders(sess, req, true /* withImpersonationHeaders */); err != nil { // This error goes to kubernetes client and is not visible in the logs // of the teleport server if not logged here. f.log.Errorf("Failed to set up forwarding headers: %v.", err) @@ -2789,7 +2806,7 @@ func (f *Forwarder) deletePodsCollection(authCtx *authContext, w http.ResponseWr return nil, trace.Wrap(err) } - if err := f.setupForwardingHeaders(sess, req); err != nil { + if err := f.setupForwardingHeaders(sess, req, true /* withImpersonationHeaders */); err != nil { // This error goes to kubernetes client and is not visible in the logs // of the teleport server if not logged here. f.log.Errorf("Failed to set up forwarding headers: %v.", err) diff --git a/lib/kube/proxy/forwarder_test.go b/lib/kube/proxy/forwarder_test.go index c53aaeb0648b4..2b4ef96db92b7 100644 --- a/lib/kube/proxy/forwarder_test.go +++ b/lib/kube/proxy/forwarder_test.go @@ -163,6 +163,19 @@ func TestAuthenticate(t *testing.T) { TracerProvider: otel.GetTracerProvider(), tracer: otel.Tracer(teleport.ComponentKube), }, + getKubernetesServersForKubeCluster: func(ctx context.Context, name string) ([]types.KubeServer, error) { + servers, err := ap.GetKubernetesServers(ctx) + if err != nil { + return nil, err + } + var filtered []types.KubeServer + for _, server := range servers { + if server.GetCluster().GetName() == name { + filtered = append(filtered, server) + } + } + return filtered, nil + }, } const remoteAddr = "user.example.com" @@ -220,6 +233,21 @@ func TestAuthenticate(t *testing.T) { name: "local", remoteAddr: *utils.MustParseAddr(remoteAddr), }, + kubeServers: newKubeServersFromKubeClusters( + t, + &types.KubernetesClusterV3{ + Metadata: types.Metadata{ + Name: "local", + Labels: map[string]string{ + "static_label1": "static_value1", + "static_label2": "static_value2", + }, + }, + Spec: types.KubernetesClusterSpecV3{ + DynamicLabels: map[string]types.CommandLabelV2{}, + }, + }, + ), }, }, { @@ -243,6 +271,30 @@ func TestAuthenticate(t *testing.T) { DynamicLabels: map[string]types.CommandLabelV2{}, }, }, + &types.KubernetesClusterV3{ + Metadata: types.Metadata{ + Name: "foo", + Labels: map[string]string{ + "static_label1": "static_value1", + "static_label2": "static_value2", + }, + }, + Spec: types.KubernetesClusterSpecV3{ + DynamicLabels: map[string]types.CommandLabelV2{}, + }, + }, + &types.KubernetesClusterV3{ + Metadata: types.Metadata{ + Name: "bar", + Labels: map[string]string{ + "static_label1": "static_value1", + "static_label2": "static_value2", + }, + }, + Spec: types.KubernetesClusterSpecV3{ + DynamicLabels: map[string]types.CommandLabelV2{}, + }, + }, ), wantCtx: &authContext{ kubeUsers: utils.StringsSet([]string{"user-a"}), @@ -257,6 +309,21 @@ func TestAuthenticate(t *testing.T) { name: "local", remoteAddr: *utils.MustParseAddr(remoteAddr), }, + kubeServers: newKubeServersFromKubeClusters( + t, + &types.KubernetesClusterV3{ + Metadata: types.Metadata{ + Name: "local", + Labels: map[string]string{ + "static_label1": "static_value1", + "static_label2": "static_value2", + }, + }, + Spec: types.KubernetesClusterSpecV3{ + DynamicLabels: map[string]types.CommandLabelV2{}, + }, + }, + ), }, }, { @@ -289,6 +356,18 @@ func TestAuthenticate(t *testing.T) { name: "local", remoteAddr: *utils.MustParseAddr(remoteAddr), }, + kubeServers: newKubeServersFromKubeClusters( + t, + &types.KubernetesClusterV3{ + Metadata: types.Metadata{ + Name: "local", + Labels: map[string]string{}, + }, + Spec: types.KubernetesClusterSpecV3{ + DynamicLabels: map[string]types.CommandLabelV2{}, + }, + }, + ), }, }, { @@ -320,6 +399,19 @@ func TestAuthenticate(t *testing.T) { name: "local", remoteAddr: *utils.MustParseAddr(remoteAddr), }, + + kubeServers: newKubeServersFromKubeClusters( + t, + &types.KubernetesClusterV3{ + Metadata: types.Metadata{ + Name: "local", + Labels: map[string]string{}, + }, + Spec: types.KubernetesClusterSpecV3{ + DynamicLabels: map[string]types.CommandLabelV2{}, + }, + }, + ), }, }, { @@ -352,6 +444,18 @@ func TestAuthenticate(t *testing.T) { name: "local", remoteAddr: *utils.MustParseAddr(remoteAddr), }, + kubeServers: newKubeServersFromKubeClusters( + t, + &types.KubernetesClusterV3{ + Metadata: types.Metadata{ + Name: "local", + Labels: map[string]string{}, + }, + Spec: types.KubernetesClusterSpecV3{ + DynamicLabels: map[string]types.CommandLabelV2{}, + }, + }, + ), }, }, { @@ -363,8 +467,6 @@ func TestAuthenticate(t *testing.T) { tunnel: tun, wantCtx: &authContext{ - kubeUsers: utils.StringsSet([]string{"user-a"}), - kubeGroups: utils.StringsSet([]string{teleport.KubeSystemAuthenticated}), certExpires: certExpiration, teleportCluster: teleportClusterClient{ name: "remote", @@ -382,8 +484,6 @@ func TestAuthenticate(t *testing.T) { tunnel: tun, wantCtx: &authContext{ - kubeUsers: utils.StringsSet([]string{"user-a"}), - kubeGroups: utils.StringsSet([]string{teleport.KubeSystemAuthenticated}), certExpires: certExpiration, teleportCluster: teleportClusterClient{ name: "remote", @@ -401,8 +501,6 @@ func TestAuthenticate(t *testing.T) { tunnel: tun, wantCtx: &authContext{ - kubeUsers: utils.StringsSet([]string{"user-a"}), - kubeGroups: utils.StringsSet([]string{teleport.KubeSystemAuthenticated}), certExpires: certExpiration, teleportCluster: teleportClusterClient{ name: "remote", @@ -453,6 +551,18 @@ func TestAuthenticate(t *testing.T) { name: "local", remoteAddr: *utils.MustParseAddr(remoteAddr), }, + kubeServers: newKubeServersFromKubeClusters( + t, + &types.KubernetesClusterV3{ + Metadata: types.Metadata{ + Name: "local", + Labels: map[string]string{}, + }, + Spec: types.KubernetesClusterSpecV3{ + DynamicLabels: map[string]types.CommandLabelV2{}, + }, + }, + ), }, }, { @@ -501,6 +611,18 @@ func TestAuthenticate(t *testing.T) { name: "local", remoteAddr: *utils.MustParseAddr(remoteAddr), }, + kubeServers: newKubeServersFromKubeClusters( + t, + &types.KubernetesClusterV3{ + Metadata: types.Metadata{ + Name: "local", + Labels: map[string]string{}, + }, + Spec: types.KubernetesClusterSpecV3{ + DynamicLabels: map[string]types.CommandLabelV2{}, + }, + }, + ), }, }, { @@ -559,6 +681,21 @@ func TestAuthenticate(t *testing.T) { name: "local", remoteAddr: *utils.MustParseAddr(remoteAddr), }, + kubeServers: newKubeServersFromKubeClusters( + t, + &types.KubernetesClusterV3{ + Metadata: types.Metadata{ + Name: "foo", + Labels: map[string]string{ + "static_label1": "static_value1", + "static_label2": "static_value2", + }, + }, + Spec: types.KubernetesClusterSpecV3{ + DynamicLabels: map[string]types.CommandLabelV2{}, + }, + }, + ), }, }, { @@ -571,8 +708,6 @@ func TestAuthenticate(t *testing.T) { tunnel: tun, wantCtx: &authContext{ - kubeUsers: utils.StringsSet([]string{"user-a"}), - kubeGroups: utils.StringsSet([]string{teleport.KubeSystemAuthenticated}), kubeClusterName: "foo", certExpires: certExpiration, teleportCluster: teleportClusterClient{ @@ -642,11 +777,12 @@ func TestAuthenticate(t *testing.T) { require.Equal(t, trace.IsAccessDenied(err), tt.wantAuthErr) return } + err = f.authorize(context.Background(), gotCtx) require.NoError(t, err) require.Empty(t, cmp.Diff(gotCtx, tt.wantCtx, cmp.AllowUnexported(authContext{}, teleportClusterClient{}), - cmpopts.IgnoreFields(authContext{}, "clientIdleTimeout", "sessionTTL", "Context", "recordingConfig", "disconnectExpiredCert"), + cmpopts.IgnoreFields(authContext{}, "clientIdleTimeout", "sessionTTL", "Context", "recordingConfig", "disconnectExpiredCert", "kubeCluster"), cmpopts.IgnoreFields(teleportClusterClient{}, "dial", "isRemoteClosed"), )) @@ -958,6 +1094,8 @@ func TestNewClusterSessionDirect(t *testing.T) { f.cfg.CachingAuthClient = mockAccessPoint{ kubeServers: []types.KubeServer{publicKubeService, otherKubeService, tunnelKubeService, otherKubeService}, } + authCtx.kubeServers, err = f.cfg.CachingAuthClient.GetKubernetesServers(context.Background()) + require.NoError(t, err) sess, err := f.newClusterSession(ctx, authCtx) require.NoError(t, err) require.Equal(t, []kubeClusterEndpoint{publicEndpoint, tunnelEndpoint}, sess.kubeClusterEndpoints) diff --git a/lib/kube/proxy/moderated_sessions_test.go b/lib/kube/proxy/moderated_sessions_test.go index 534f4f2365078..fd157c27219dd 100644 --- a/lib/kube/proxy/moderated_sessions_test.go +++ b/lib/kube/proxy/moderated_sessions_test.go @@ -22,6 +22,7 @@ import ( "fmt" "io" "net/http" + "reflect" "regexp" "strings" "sync" @@ -43,7 +44,7 @@ import ( func TestModeratedSessions(t *testing.T) { // enable enterprise features to have access to ModeratedSessions. - modules.SetTestModules(t, &modules.TestModules{TestBuildType: modules.BuildEnterprise}) + modules.SetTestModules(t, &modules.TestModules{TestBuildType: modules.BuildEnterprise, TestFeatures: modules.Features{Kubernetes: true}}) const ( moderatorUsername = "moderator_user" moderatorRoleName = "mod_role" @@ -118,9 +119,7 @@ func TestModeratedSessions(t *testing.T) { t, moderatorUsername, RoleSpec{ - Name: moderatorRoleName, - KubeUsers: roleKubeUsers, - KubeGroups: roleKubeGroups, + Name: moderatorRoleName, // sessionJoin: SessionJoin: []*types.SessionJoinPolicy{ { @@ -130,6 +129,10 @@ func TestModeratedSessions(t *testing.T) { Modes: []string{string(types.SessionModeratorMode)}, }, }, + SetupRoleFunc: func(r types.Role) { + // set kubernetes labels to empty to test relaxed join rules + r.SetKubernetesLabels(types.Allow, types.Labels{}) + }, }) // create a userRequiringModerator with access to kubernetes thar requires @@ -158,6 +161,8 @@ func TestModeratedSessions(t *testing.T) { moderator types.User closeSession bool moderatorForcedClose bool + reason string + invite []string } type want struct { sessionEndEvent bool @@ -170,7 +175,9 @@ func TestModeratedSessions(t *testing.T) { { name: "create session for user without moderation", args: args{ - user: user, + user: user, + reason: "reason 1", + invite: []string{"user1", "user2"}, }, want: want{ sessionEndEvent: true, @@ -181,6 +188,8 @@ func TestModeratedSessions(t *testing.T) { args: args{ user: userRequiringModerator, moderator: moderator, + reason: "reason 2", + invite: []string{"user1", "user2"}, }, want: want{ sessionEndEvent: true, @@ -191,6 +200,8 @@ func TestModeratedSessions(t *testing.T) { args: args{ user: user, closeSession: true, + reason: "reason 3", + invite: []string{"user1", "user2"}, }, want: want{ sessionEndEvent: true, @@ -201,6 +212,8 @@ func TestModeratedSessions(t *testing.T) { args: args{ user: userRequiringModerator, closeSession: true, + reason: "reason 4", + invite: []string{"user1", "user2"}, }, want: want{ // until moderator joins the session is not started. If the connection @@ -214,6 +227,8 @@ func TestModeratedSessions(t *testing.T) { user: userRequiringModerator, moderator: moderator, moderatorForcedClose: true, + reason: "reason 5", + invite: []string{"user1", "user2"}, }, want: want{ sessionEndEvent: true, @@ -249,12 +264,16 @@ func TestModeratedSessions(t *testing.T) { Tty: true, } req, err := generateExecRequest( - testCtx.KubeServiceAddress(), - podName, - podNamespace, - podContainerName, - containerCommmandExecute, // placeholder for commands to execute in the dummy pod - streamOpts, + generateExecRequestConfig{ + addr: testCtx.KubeServiceAddress(), + podName: podName, + podNamespace: podNamespace, + containerName: podContainerName, + cmd: containerCommmandExecute, // placeholder for commands to execute in the dummy pod + options: streamOpts, + reason: tt.args.reason, + invite: tt.args.invite, + }, ) require.NoError(t, err) @@ -279,6 +298,10 @@ func TestModeratedSessions(t *testing.T) { group.Go(func() error { // waits for user to send the sessionID of his exec request. sessionID := <-sessionIDC + // validate that the sessionID is valid and the reason is the one we expect. + if err := validateSessionTracker(testCtx, sessionID, tt.args.reason, tt.args.invite); err != nil { + return trace.Wrap(err) + } t.Logf("moderator is joining sessionID %q", sessionID) // join the session. stream, err := testCtx.NewJoiningSession(config, sessionID, types.SessionModeratorMode) @@ -295,10 +318,13 @@ func TestModeratedSessions(t *testing.T) { // moderator waits for the user informed that he joined the session. <-moderatorJoined + dataFound := false for { p := make([]byte, 1024) n, err := stream.Read(p) - if err != nil { + if errors.Is(err, io.EOF) { + break + } else if err != nil { return trace.Wrap(err) } stringData := string(p[:n]) @@ -311,16 +337,22 @@ func TestModeratedSessions(t *testing.T) { // stdinPayload is sent by the user after the session started. if strings.Contains(stringData, stdinPayload) { - break + dataFound = true } // podContainerName is returned by the kubemock server and it's used // to control that the session has effectively started. // return to force the defer to run. if strings.Contains(stringData, podContainerName) && tt.args.moderatorForcedClose { - return nil + if err := stream.ForceTerminate(); err != nil { + return trace.Wrap(err) + } + continue } } + if !dataFound && !tt.args.moderatorForcedClose { + return trace.Wrap(errors.New("stdinPayload was not received")) + } return nil }) } @@ -378,8 +410,8 @@ func TestModeratedSessions(t *testing.T) { // checks if moderator has joined the session. // Each time a user joins a session the following message is broadcasted - // User joined the session. - if strings.Contains(stringData, fmt.Sprintf("User %s joined the session.", moderatorUsername)) { + // User joined the session with participant mode: . + if strings.Contains(stringData, fmt.Sprintf("User %s joined the session with participant mode: moderator.", moderatorUsername)) { t.Logf("identified that moderator joined the session") // inform moderator goroutine that the user detected that he joined the // session. @@ -429,3 +461,19 @@ func TestModeratedSessions(t *testing.T) { }) } } + +// validateSessionTracker validates that the session tracker has the expected +// reason and invited users. +func validateSessionTracker(testCtx *TestContext, sessionID string, reason string, invited []string) error { + sessionTracker, err := testCtx.AuthClient.GetSessionTracker(testCtx.Context, sessionID) + if err != nil { + return trace.Wrap(err) + } + if sessionTracker.GetReason() != reason { + return trace.BadParameter("expected reason %q, got %q", reason, sessionTracker.GetReason()) + } + if !reflect.DeepEqual(sessionTracker.GetInvited(), invited) { + return trace.BadParameter("expected invited %q, got %q", invited, sessionTracker.GetInvited()) + } + return nil +} diff --git a/lib/kube/proxy/server.go b/lib/kube/proxy/server.go index c8a530b7b5e86..50012e7a67f6c 100644 --- a/lib/kube/proxy/server.go +++ b/lib/kube/proxy/server.go @@ -151,8 +151,8 @@ type TLSServer struct { heartbeats map[string]*srv.Heartbeat closeContext context.Context closeFunc context.CancelFunc - // watcher monitors changes to kube cluster resources. - watcher *services.KubeClusterWatcher + // kubeClusterWatcher monitors changes to kube cluster resources. + kubeClusterWatcher *services.KubeClusterWatcher // reconciler reconciles proxied kube clusters with kube_clusters resources. reconciler *services.Reconciler // monitoredKubeClusters contains all kube clusters the proxied kube_clusters are @@ -229,6 +229,11 @@ func NewTLSServer(cfg TLSServerConfig) (*TLSServer, error) { } server.TLS.GetConfigForClient = server.GetConfigForClient server.closeContext, server.closeFunc = context.WithCancel(cfg.Context) + // register into the forwarder the method to get kubernetes servers for a kube cluster. + server.fwd.getKubernetesServersForKubeCluster, err = server.getKubernetesServersForKubeClusterFunc() + if err != nil { + return nil, trace.Wrap(err) + } return server, nil } @@ -282,7 +287,9 @@ func (t *TLSServer) Serve(listener net.Listener) error { // Initialize watcher that will be dynamically (un-)registering // proxied clusters based on the kube_cluster resources. - if t.watcher, err = t.startResourceWatcher(t.closeContext); err != nil { + // This watcher is only started for the kube_service if a resource watcher + // is configured. + if t.kubeClusterWatcher, err = t.startKubeClusterResourceWatcher(t.closeContext); err != nil { return trace.Wrap(err) } @@ -314,8 +321,8 @@ func (t *TLSServer) close(ctx context.Context) error { t.closeFunc() // Stop the kube_cluster resource watcher. - if t.watcher != nil { - t.watcher.Close() + if t.kubeClusterWatcher != nil { + t.kubeClusterWatcher.Close() } t.mu.Lock() listClose := t.listener.Close() @@ -350,7 +357,7 @@ func (t *TLSServer) getServerInfo(name string) (types.Resource, error) { addr = t.listener.Addr().String() } - cluster, err := t.getKubeClusterForHeartbeat(name) + cluster, err := t.getKubeClusterWithServiceLabels(name) if err != nil { return nil, trace.Wrap(err) } @@ -385,12 +392,12 @@ func (t *TLSServer) getServerInfo(name string) (types.Resource, error) { return srv, nil } -// getKubeClusterForHeartbeat finds the kube cluster by name, strips the credentials, +// getKubeClusterWithServiceLabels finds the kube cluster by name, strips the credentials, // replaces the cluster dynamic labels with their latest value available and updates // the cluster with the service dynamic and static labels. // We strip the Azure, AWS and Kubeconfig credentials so they are not leaked when // heartbeating the cluster. -func (t *TLSServer) getKubeClusterForHeartbeat(name string) (*types.KubernetesClusterV3, error) { +func (t *TLSServer) getKubeClusterWithServiceLabels(name string) (*types.KubernetesClusterV3, error) { // it is safe do read from details since the structure is never updated. // we replace the whole structure each time an update happens to a dynamic cluster. details, err := t.fwd.findKubeDetailsByClusterName(name) @@ -524,3 +531,59 @@ func (t *TLSServer) setServiceLabels(cluster types.KubeCluster) { cluster.SetDynamicLabels(dstDynLabels) } } + +// getKubernetesServersForKubeClusterFunc returns a function that returns the kubernetes servers +// for a given kube cluster depending on the type of service. +func (t *TLSServer) getKubernetesServersForKubeClusterFunc() (getKubeServersByNameFunc, error) { + switch t.KubeServiceType { + case KubeService: + return func(_ context.Context, name string) ([]types.KubeServer, error) { + // If this is a kube_service, we can just return the local kube servers. + kube, err := t.getKubeClusterWithServiceLabels(name) + if err != nil { + return nil, trace.Wrap(err) + } + srv, err := types.NewKubernetesServerV3FromCluster(kube, "", t.HostID) + if err != nil { + return nil, trace.Wrap(err) + } + return []types.KubeServer{srv}, nil + }, nil + case ProxyService: + return t.getAuthKubeServers, nil + case LegacyProxyService: + return func(ctx context.Context, name string) ([]types.KubeServer, error) { + kube, err := t.getKubeClusterWithServiceLabels(name) + if err != nil { + servers, err := t.getAuthKubeServers(ctx, name) + return servers, trace.Wrap(err) + } + srv, err := types.NewKubernetesServerV3FromCluster(kube, "", t.HostID) + if err != nil { + return nil, trace.Wrap(err) + } + return []types.KubeServer{srv}, nil + }, nil + default: + return nil, trace.BadParameter("unknown kubernetes service type %q", t.KubeServiceType) + } +} + +// getAuthKubeServers returns the kubernetes servers for a given kube cluster +// using the Auth server client. +func (t *TLSServer) getAuthKubeServers(ctx context.Context, name string) ([]types.KubeServer, error) { + servers, err := t.CachingAuthClient.GetKubernetesServers(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + var returnServers []types.KubeServer + for _, server := range servers { + if server.GetCluster().GetName() == name { + returnServers = append(returnServers, server) + } + } + if len(returnServers) == 0 { + return nil, trace.NotFound("no kubernetes servers found for cluster %q", name) + } + return returnServers, nil +} diff --git a/lib/kube/proxy/sess.go b/lib/kube/proxy/sess.go index 5c62525b3d896..3651e6fac2b59 100644 --- a/lib/kube/proxy/sess.go +++ b/lib/kube/proxy/sess.go @@ -352,6 +352,11 @@ type session struct { // Set if we should broadcast information about participant requirements to the session. displayParticipantRequirements bool + // invitedUsers is a list of users that were invited to the session. + invitedUsers []string + // reason is the reason for the session. + reason string + // eventsWaiter is used to wait for events to be emitted and goroutines closed // when a session is closed. eventsWaiter sync.WaitGroup @@ -405,7 +410,9 @@ func newSession(ctx authContext, forwarder *Forwarder, req *http.Request, params initiator: initiator.ID, expires: time.Now().UTC().Add(sessionMaxLifetime), PresenceEnabled: ctx.Identity.GetIdentity().MFAVerified != "", - displayParticipantRequirements: utils.AsBool(q.Get("displayParticipantRequirements")), + displayParticipantRequirements: utils.AsBool(q.Get(teleport.KubeSessionDisplayParticipantRequirementsQueryParam)), + invitedUsers: strings.Split(q.Get(teleport.KubeSessionInvitedQueryParam), ","), + reason: q.Get(teleport.KubeSessionReasonQueryParam), streamContext: streamContext, streamContextCancel: streamContextCancel, partiesWg: sync.WaitGroup{}, @@ -828,11 +835,7 @@ func (s *session) lockedSetupLaunch(request *remoteCommandRequest, q url.Values, // join attempts to connect a party to the session. func (s *session) join(p *party) error { if p.Ctx.User.GetName() != s.ctx.User.GetName() { - roleNames := p.Ctx.Identity.GetIdentity().Groups - roles, err := getRolesByName(s.forwarder, roleNames) - if err != nil { - return trace.Wrap(err) - } + roles := p.Ctx.Checker.Roles() accessContext := auth.SessionAccessContext{ Username: p.Ctx.User.GetName(), @@ -905,7 +908,7 @@ func (s *session) join(p *party) error { } s.io.AddWriter(stringID, p.Client.stdoutStream()) - s.BroadcastMessage("User %v joined the session.", p.Ctx.User.GetName()) + s.BroadcastMessage("User %v joined the session with participant mode: %v.", p.Ctx.User.GetName(), p.Mode) if p.Mode == types.SessionModeratorMode { s.eventsWaiter.Add(1) @@ -1190,6 +1193,8 @@ func (s *session) trackSession(p *party, policySet []*types.SessionTrackerPolicy HostPolicies: policySet, Login: "root", Created: time.Now(), + Reason: s.reason, + Invited: s.invitedUsers, } s.log.Debug("Creating session tracker") diff --git a/lib/kube/proxy/utils_testing.go b/lib/kube/proxy/utils_testing.go index 29964eda0c515..9bd78c249a744 100644 --- a/lib/kube/proxy/utils_testing.go +++ b/lib/kube/proxy/utils_testing.go @@ -172,7 +172,7 @@ func SetupTestContext(ctx context.Context, t *testing.T, cfg TestConfig) *TestCo // heartbeatsWaitChannel waits for clusters heartbeats to start. heartbeatsWaitChannel := make(chan struct{}, len(cfg.Clusters)+1) - + client := newAuthClientWithStreamer(testCtx) // Create kubernetes service server. testCtx.KubeServer, err = NewTLSServer(TLSServerConfig{ ForwarderConfig: ForwarderConfig{ @@ -186,12 +186,12 @@ func SetupTestContext(ctx context.Context, t *testing.T, cfg TestConfig) *TestCo // directly to AuthClient solves the issue. // We wrap the AuthClient with an events.TeeStreamer to send non-disk // events like session.end to testCtx.emitter as well. - AuthClient: newAuthClientWithStreamer(testCtx), + AuthClient: client, // StreamEmitter is required although not used because we are using // "node-sync" as session recording mode. StreamEmitter: testCtx.Emitter, DataDir: t.TempDir(), - CachingAuthClient: testCtx.AuthClient, + CachingAuthClient: client, HostID: testCtx.HostID, Context: testCtx.Context, KubeconfigPath: kubeConfigLocation, @@ -206,7 +206,7 @@ func SetupTestContext(ctx context.Context, t *testing.T, cfg TestConfig) *TestCo }, DynamicLabels: nil, TLS: tlsConfig, - AccessPoint: testCtx.AuthClient, + AccessPoint: client, LimiterConfig: limiter.Config{ MaxConnections: 1000, MaxNumberOfUsers: 1000, diff --git a/lib/kube/proxy/watcher.go b/lib/kube/proxy/watcher.go index 44a093bb9950a..04373f33d02be 100644 --- a/lib/kube/proxy/watcher.go +++ b/lib/kube/proxy/watcher.go @@ -82,9 +82,9 @@ func (s *TLSServer) startReconciler(ctx context.Context) (err error) { return nil } -// startResourceWatcher starts watching changes to Kube Clusters resources and +// startKubeClusterResourceWatcher starts watching changes to Kube Clusters resources and // registers/unregisters the proxied Kube Cluster accordingly. -func (s *TLSServer) startResourceWatcher(ctx context.Context) (*services.KubeClusterWatcher, error) { +func (s *TLSServer) startKubeClusterResourceWatcher(ctx context.Context) (*services.KubeClusterWatcher, error) { if len(s.ResourceMatchers) == 0 || s.KubeServiceType != KubeService { s.log.Debug("Not initializing Kube Cluster resource watcher.") return nil, nil diff --git a/lib/proxy/router.go b/lib/proxy/router.go index 3427bd95d483e..7be0099c47954 100644 --- a/lib/proxy/router.go +++ b/lib/proxy/router.go @@ -191,23 +191,19 @@ func NewRouter(cfg RouterConfig) (*Router, error) { }, nil } -// SignerCreator allows the caller to configure a [ssh.Signer] if a ssh -// user agent isn't available, ie when connecting to agentless nodes. -type SignerCreator func() (ssh.Signer, error) - // DialHost dials the node that matches the provided host, port and cluster. If no matching node // is found an error is returned. If more than one matching node is found and the cluster networking // configuration is not set to route to the most recent an error is returned. Also returns teleport version of the // target server if it's a teleport server // DELETE IN 14.0: remove returning teleport version, it was needed for compatibility -func (r *Router) DialHost(ctx context.Context, clientSrcAddr, clientDstAddr net.Addr, host, port, clusterName string, accessChecker services.AccessChecker, agentGetter teleagent.Getter, singerCreator SignerCreator) (_ net.Conn, teleportVersion string, err error) { +func (r *Router) DialHost(ctx context.Context, clientSrcAddr, clientDstAddr net.Addr, host, port, clusterName string, accessChecker services.AccessChecker, agentGetter teleagent.Getter, signer func(context.Context) (ssh.Signer, error)) (_ net.Conn, teleportVersion string, err error) { ctx, span := r.tracer.Start( ctx, "router/DialHost", oteltrace.WithAttributes( attribute.String("host", host), attribute.String("port", port), - attribute.String("site", clusterName), + attribute.String("cluster", clusterName), ), ) defer func() { @@ -279,9 +275,9 @@ func (r *Router) DialHost(ctx context.Context, clientSrcAddr, clientDstAddr net. // if the node is a registered openssh node, create a signer for auth // and don't set agentGetter so a SSH user agent will not be created // when connecting to the remote node - var signer ssh.Signer + var sshSigner ssh.Signer if isAgentlessNode { - signer, err = singerCreator() + sshSigner, err = signer(ctx) if err != nil { return nil, "", trace.Wrap(err) } @@ -293,7 +289,7 @@ func (r *Router) DialHost(ctx context.Context, clientSrcAddr, clientDstAddr net. To: &utils.NetAddr{AddrNetwork: "tcp", Addr: serverAddr}, OriginalClientDstAddr: clientDstAddr, GetUserAgent: agentGetter, - AgentlessSigner: signer, + AgentlessSigner: sshSigner, Address: host, Principals: principals, ServerID: serverID, @@ -316,7 +312,7 @@ func (r *Router) getRemoteCluster(ctx context.Context, clusterName string, check ctx, "router/getRemoteCluster", oteltrace.WithAttributes( - attribute.String("site", clusterName), + attribute.String("cluster", clusterName), ), ) defer span.End() @@ -458,7 +454,7 @@ func (r *Router) DialSite(ctx context.Context, clusterName string, clientSrcAddr ctx, "router/DialSite", oteltrace.WithAttributes( - attribute.String("site", clusterName), + attribute.String("cluster", clusterName), ), ) defer span.End() diff --git a/lib/proxy/router_test.go b/lib/proxy/router_test.go index 70465a32ef85c..c5102fac7e32e 100644 --- a/lib/proxy/router_test.go +++ b/lib/proxy/router_test.go @@ -360,7 +360,7 @@ func TestRouter_DialHost(t *testing.T) { agentGetter := func() (teleagent.Agent, error) { return nil, nil } - createSigner := func() (ssh.Signer, error) { + createSigner := func(context.Context) (ssh.Signer, error) { key, err := native.GeneratePrivateKey() if err != nil { return nil, err diff --git a/lib/reversetunnel/agentpool_test.go b/lib/reversetunnel/agentpool_test.go index 566ee9e03673b..d38ab5482cbc7 100644 --- a/lib/reversetunnel/agentpool_test.go +++ b/lib/reversetunnel/agentpool_test.go @@ -138,7 +138,7 @@ func TestAgentPoolConnectionCount(t *testing.T) { default: return false } - }, time.Second*1, time.Millisecond*10, "expected a lease to be available") + }, time.Second*5, time.Millisecond*10, "expected a lease to be available") require.False(t, pool.isAgentRequired()) require.Equal(t, pool.Count(), 1) @@ -161,7 +161,7 @@ func TestAgentPoolConnectionCount(t *testing.T) { require.Eventually(t, func() bool { return pool.Count() == 3 - }, time.Second*1, time.Millisecond*10) + }, time.Second*5, time.Millisecond*10) select { case <-pool.tracker.Acquire(): diff --git a/lib/service/service.go b/lib/service/service.go index 3c44e3fb129bb..2bf330c6ec90d 100644 --- a/lib/service/service.go +++ b/lib/service/service.go @@ -62,9 +62,12 @@ import ( "github.com/gravitational/teleport/api/constants" apidefaults "github.com/gravitational/teleport/api/defaults" kubeproto "github.com/gravitational/teleport/api/gen/proto/go/teleport/kube/v1" + transportpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/transport/v1" "github.com/gravitational/teleport/api/types" apievents "github.com/gravitational/teleport/api/types/events" apiutils "github.com/gravitational/teleport/api/utils" + "github.com/gravitational/teleport/lib" + "github.com/gravitational/teleport/lib/agentless" "github.com/gravitational/teleport/lib/auditd" "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/auth/keygen" @@ -82,6 +85,7 @@ import ( "github.com/gravitational/teleport/lib/cloud" "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/events" + "github.com/gravitational/teleport/lib/events/athena" "github.com/gravitational/teleport/lib/events/dynamoevents" "github.com/gravitational/teleport/lib/events/filesessions" "github.com/gravitational/teleport/lib/events/firestoreevents" @@ -115,6 +119,7 @@ import ( "github.com/gravitational/teleport/lib/srv/desktop" "github.com/gravitational/teleport/lib/srv/ingress" "github.com/gravitational/teleport/lib/srv/regular" + "github.com/gravitational/teleport/lib/srv/transport/transportv1" "github.com/gravitational/teleport/lib/system" "github.com/gravitational/teleport/lib/utils" "github.com/gravitational/teleport/lib/utils/cert" @@ -1355,6 +1360,20 @@ func initAuthExternalAuditLog(ctx context.Context, auditConfig types.ClusterAudi return nil, trace.Wrap(err) } loggers = append(loggers, logger) + case teleport.ComponentAthena: + hasNonFileLog = true + cfg := athena.Config{ + Region: auditConfig.Region(), + } + err = cfg.SetFromURL(uri) + if err != nil { + return nil, trace.Wrap(err) + } + logger, err := athena.New(ctx, cfg) + if err != nil { + return nil, trace.Wrap(err) + } + loggers = append(loggers, logger) case teleport.SchemeFile: if uri.Path == "" { return nil, trace.BadParameter("unsupported audit uri: %q (missing path component)", uri) @@ -3159,6 +3178,7 @@ func (process *TeleportProcess) setupProxyListeners(networkingConfig types.Clust } listeners.db.postgres = listener } + } tunnelStrategy, err := networkingConfig.GetTunnelStrategyType() @@ -3828,11 +3848,16 @@ func (process *TeleportProcess) initProxyEndpoint(conn *Connector) error { ClusterName: clusterName, } + tlscfg := serverTLSConfig.Clone() + tlscfg.ClientAuth = tls.RequireAndVerifyClientCert + if lib.IsInsecureDevMode() { + tlscfg.InsecureSkipVerify = true + tlscfg.ClientAuth = tls.RequireAnyClientCert + } creds, err := auth.NewTransportCredentials(auth.TransportCredentialsConfig{ - TransportCredentials: credentials.NewTLS(serverTLSConfig), + TransportCredentials: credentials.NewTLS(tlscfg), UserGetter: authMiddleware, Authorizer: authorizer, - Enforcer: sessionController, }) if err != nil { return trace.Wrap(err) @@ -3850,6 +3875,33 @@ func (process *TeleportProcess) initProxyEndpoint(conn *Connector) error { grpc.Creds(creds), ) + connMonitor, err := srv.NewConnectionMonitor(srv.ConnectionMonitorConfig{ + AccessPoint: accessPoint, + LockWatcher: lockWatcher, + Clock: process.Clock, + ServerID: serverID, + Emitter: streamEmitter, + Logger: process.log, + }) + if err != nil { + return trace.Wrap(err) + } + + transportService, err := transportv1.NewService(transportv1.ServerConfig{ + FIPS: cfg.FIPS, + Logger: process.log.WithField(trace.Component, "transport"), + Dialer: proxyRouter, + SignerFn: func(authzCtx *authz.Context) func(context.Context) (ssh.Signer, error) { + return agentless.SignerFromAuthzContext(authzCtx, conn.Client) + }, + ConnectionMonitor: connMonitor, + LocalAddr: listeners.sshGRPC.Addr(), + }) + if err != nil { + return trace.Wrap(err) + } + transportpb.RegisterTransportServiceServer(sshGRPCServer, transportService) + process.RegisterCriticalFunc("proxy.ssh", func() error { utils.Consolef(cfg.Console, log, teleport.ComponentProxy, "SSH proxy service %s:%s is starting on %v.", teleport.Version, teleport.Gitref, cfg.Proxy.SSHAddr.Addr) @@ -4041,6 +4093,7 @@ func (process *TeleportProcess) initProxyEndpoint(conn *Connector) error { // route extracted connection to ALPN Proxy DB TLS Handler. MatchFunc: alpnproxy.MatchByProtocol( alpncommon.ProtocolMongoDB, + alpncommon.ProtocolOracle, alpncommon.ProtocolRedisDB, alpncommon.ProtocolSnowflake, alpncommon.ProtocolSQLServer, diff --git a/lib/service/service_test.go b/lib/service/service_test.go index e093055a0dd81..403745df1e867 100644 --- a/lib/service/service_test.go +++ b/lib/service/service_test.go @@ -489,6 +489,7 @@ func TestSetupProxyTLSConfig(t *testing.T) { "teleport-postgres-ping", "teleport-mysql-ping", "teleport-mongodb-ping", + "teleport-oracle-ping", "teleport-redis-ping", "teleport-sqlserver-ping", "teleport-snowflake-ping", @@ -505,6 +506,7 @@ func TestSetupProxyTLSConfig(t *testing.T) { "teleport-postgres", "teleport-mysql", "teleport-mongodb", + "teleport-oracle", "teleport-redis", "teleport-sqlserver", "teleport-snowflake", @@ -521,6 +523,7 @@ func TestSetupProxyTLSConfig(t *testing.T) { "teleport-postgres-ping", "teleport-mysql-ping", "teleport-mongodb-ping", + "teleport-oracle-ping", "teleport-redis-ping", "teleport-sqlserver-ping", "teleport-snowflake-ping", @@ -540,6 +543,7 @@ func TestSetupProxyTLSConfig(t *testing.T) { "teleport-postgres", "teleport-mysql", "teleport-mongodb", + "teleport-oracle", "teleport-redis", "teleport-sqlserver", "teleport-snowflake", diff --git a/lib/service/servicecfg/database.go b/lib/service/servicecfg/database.go index 91cc52e1292e3..f30ad087c7baa 100644 --- a/lib/service/servicecfg/database.go +++ b/lib/service/servicecfg/database.go @@ -24,6 +24,7 @@ import ( "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/limiter" "github.com/gravitational/teleport/lib/services" + "github.com/gravitational/teleport/lib/srv/db/common/enterprise" awsutils "github.com/gravitational/teleport/lib/utils/aws" ) @@ -73,6 +74,9 @@ type Database struct { // CheckAndSetDefaults validates the database proxy configuration. func (d *Database) CheckAndSetDefaults() error { + if err := enterprise.ProtocolValidation(d.Protocol); err != nil { + return trace.Wrap(err) + } if d.Name == "" { return trace.BadParameter("empty database name") } diff --git a/lib/services/database.go b/lib/services/database.go index abde8216e94dd..ef4f72a7d5dd3 100644 --- a/lib/services/database.go +++ b/lib/services/database.go @@ -50,6 +50,7 @@ import ( libcloudaws "github.com/gravitational/teleport/lib/cloud/aws" "github.com/gravitational/teleport/lib/cloud/azure" "github.com/gravitational/teleport/lib/defaults" + "github.com/gravitational/teleport/lib/srv/db/common/enterprise" "github.com/gravitational/teleport/lib/srv/db/redis/connection" "github.com/gravitational/teleport/lib/tlsca" "github.com/gravitational/teleport/lib/utils" @@ -137,6 +138,9 @@ func UnmarshalDatabase(data []byte, opts ...MarshalOption) (types.Database, erro // ValidateDatabase validates a types.Database. func ValidateDatabase(db types.Database) error { + if err := enterprise.ProtocolValidation(db.GetProtocol()); err != nil { + return trace.Wrap(err) + } if err := db.CheckAndSetDefaults(); err != nil { return trace.Wrap(err) } diff --git a/lib/services/kubernetes.go b/lib/services/kubernetes.go index b8571220ec2a9..cd719f6d12d6f 100644 --- a/lib/services/kubernetes.go +++ b/lib/services/kubernetes.go @@ -34,8 +34,8 @@ import ( "github.com/gravitational/teleport/lib/utils" ) -// KubernetesGetter defines interface for fetching kubernetes cluster resources. -type KubernetesGetter interface { +// KubernetesClusterGetter defines interface for fetching kubernetes cluster resources. +type KubernetesClusterGetter interface { // GetKubernetesClusters returns all kubernetes cluster resources. GetKubernetesClusters(context.Context) ([]types.KubeCluster, error) // GetKubernetesCluster returns the specified kubernetes cluster resource. @@ -45,7 +45,7 @@ type KubernetesGetter interface { // Kubernetes defines an interface for managing kubernetes clusters resources. type Kubernetes interface { // KubernetesGetter provides methods for fetching kubernetes resources. - KubernetesGetter + KubernetesClusterGetter // CreateKubernetesCluster creates a new kubernetes cluster resource. CreateKubernetesCluster(context.Context, types.KubeCluster) error // UpdateKubernetesCluster updates an existing kubernetes cluster resource. diff --git a/lib/services/watcher.go b/lib/services/watcher.go index c89b4ff7762eb..015d9dc05a595 100644 --- a/lib/services/watcher.go +++ b/lib/services/watcher.go @@ -1016,7 +1016,7 @@ type KubeClusterWatcherConfig struct { // ResourceWatcherConfig is the resource watcher configuration. ResourceWatcherConfig // KubernetesGetter is responsible for fetching kube_cluster resources. - KubernetesGetter + KubernetesClusterGetter // KubeClustersC receives up-to-date list of all kube_cluster resources. KubeClustersC chan types.KubeClusters } @@ -1026,12 +1026,12 @@ func (cfg *KubeClusterWatcherConfig) CheckAndSetDefaults() error { if err := cfg.ResourceWatcherConfig.CheckAndSetDefaults(); err != nil { return trace.Wrap(err) } - if cfg.KubernetesGetter == nil { - getter, ok := cfg.Client.(KubernetesGetter) + if cfg.KubernetesClusterGetter == nil { + getter, ok := cfg.Client.(KubernetesClusterGetter) if !ok { return trace.BadParameter("missing parameter KubernetesGetter and Client not usable as KubernetesGetter") } - cfg.KubernetesGetter = getter + cfg.KubernetesClusterGetter = getter } if cfg.KubeClustersC == nil { cfg.KubeClustersC = make(chan types.KubeClusters) @@ -1087,7 +1087,7 @@ func (k *kubeCollector) resourceKind() string { // getResourcesAndUpdateCurrent refreshes the list of current resources. func (k *kubeCollector) getResourcesAndUpdateCurrent(ctx context.Context) error { - clusters, err := k.KubernetesGetter.GetKubernetesClusters(ctx) + clusters, err := k.KubernetesClusterGetter.GetKubernetesClusters(ctx) if err != nil { return trace.Wrap(err) } diff --git a/lib/srv/alpnproxy/common/protocols.go b/lib/srv/alpnproxy/common/protocols.go index 97224a3b9e4e8..40d3c91ad35b5 100644 --- a/lib/srv/alpnproxy/common/protocols.go +++ b/lib/srv/alpnproxy/common/protocols.go @@ -39,6 +39,9 @@ const ( // ProtocolMongoDB is TLS ALPN protocol value used to indicate Mongo protocol. ProtocolMongoDB Protocol = "teleport-mongodb" + // ProtocolOracle is TLS ALPN protocol value used to indicate Oracle protocol. + ProtocolOracle Protocol = "teleport-oracle" + // ProtocolRedisDB is TLS ALPN protocol value used to indicate Redis protocol. ProtocolRedisDB Protocol = "teleport-redis" @@ -145,6 +148,8 @@ func ToALPNProtocol(dbProtocol string) (Protocol, error) { return ProtocolPostgres, nil case defaults.ProtocolMongoDB: return ProtocolMongoDB, nil + case defaults.ProtocolOracle: + return ProtocolOracle, nil case defaults.ProtocolRedis: return ProtocolRedisDB, nil case defaults.ProtocolSQLServer: @@ -170,6 +175,7 @@ func ToALPNProtocol(dbProtocol string) (Protocol, error) { func IsDBTLSProtocol(protocol Protocol) bool { dbTLSProtocols := []Protocol{ ProtocolMongoDB, + ProtocolOracle, ProtocolRedisDB, ProtocolSQLServer, ProtocolSnowflake, @@ -188,6 +194,7 @@ var DatabaseProtocols = []Protocol{ ProtocolPostgres, ProtocolMySQL, ProtocolMongoDB, + ProtocolOracle, ProtocolRedisDB, ProtocolSQLServer, ProtocolSnowflake, diff --git a/lib/srv/alpnproxy/conn.go b/lib/srv/alpnproxy/conn.go index ef566dbb9fd81..c8bed17e08112 100644 --- a/lib/srv/alpnproxy/conn.go +++ b/lib/srv/alpnproxy/conn.go @@ -17,16 +17,10 @@ limitations under the License. package alpnproxy import ( - "crypto/tls" - "encoding/binary" "io" - "math" "net" - "sync" "time" - "github.com/gravitational/trace" - "github.com/gravitational/teleport/lib/utils" ) @@ -103,109 +97,3 @@ func (conn readOnlyConn) RemoteAddr() net.Addr { return &utils.Net func (conn readOnlyConn) SetDeadline(t time.Time) error { return nil } func (conn readOnlyConn) SetReadDeadline(t time.Time) error { return nil } func (conn readOnlyConn) SetWriteDeadline(t time.Time) error { return nil } - -// NewPingConn returns a ping connection wrapping the provided net.Conn. -func NewPingConn(conn *tls.Conn) *PingConn { - return &PingConn{Conn: conn} -} - -// PingConn wraps a *tls.Conn and add ping capabilities to it, including the -// `WritePing` function and `Read` (which excludes ping packets). -// -// When using this connection, the packets written will contain an initial data: -// the packet size. When reading, this information is taken into account, but it -// is not returned to the caller. -// -// Ping messages have a packet size of zero and are produced only when -// `WritePing` is called. On `Read`, any Ping packet is discarded. -type PingConn struct { - //net.Conn - *tls.Conn - - muRead sync.Mutex - muWrite sync.Mutex - - // currentSize size of bytes of the current packet. - currentSize uint32 -} - -// Read reads content from the underlaying connection, discarding any ping -// messages it finds. -func (c *PingConn) Read(p []byte) (int, error) { - c.muRead.Lock() - defer c.muRead.Unlock() - - err := c.discardPingReads() - if err != nil { - return 0, err - } - - // Check if the current size is larger than the provided buffer. - readSize := c.currentSize - if c.currentSize > uint32(len(p)) { - readSize = uint32(len(p)) - } - - n, err := c.Conn.Read(p[:readSize]) - c.currentSize -= uint32(n) - - return n, err -} - -// WritePing writes the ping packet to the connection. -func (c *PingConn) WritePing() error { - c.muWrite.Lock() - defer c.muWrite.Unlock() - - return binary.Write(c.Conn, binary.BigEndian, uint32(0)) -} - -// discardPingReads reads from the wrapped net.Conn until it encounters a -// non-ping packet. -func (c *PingConn) discardPingReads() error { - for c.currentSize == 0 { - err := binary.Read(c.Conn, binary.BigEndian, &c.currentSize) - if err != nil { - return err - } - } - - return nil -} - -// Write writes provided content to the underlying connection with proper -// protocol fields. -func (c *PingConn) Write(p []byte) (int, error) { - c.muWrite.Lock() - defer c.muWrite.Unlock() - - // Avoid overflow when casting data length. It is only present to avoid - // panicking if the size cannot be cast. Callers should handle packet length - // limits, such as protocol implementations and audits. - if uint64(len(p)) > math.MaxUint32 { - return 0, trace.BadParameter("invalid content size, max size permitted is %d", uint64(math.MaxUint32)) - } - - size := uint32(len(p)) - if size == 0 { - return 0, nil - } - - // Write packet size. - if err := binary.Write(c.Conn, binary.BigEndian, size); err != nil { - return 0, trace.Wrap(err) - } - - // Iterate until everything is written. - var written int - for written < len(p) { - n, err := c.Conn.Write(p) - written += n - - if err != nil { - return written, trace.Wrap(err) - } - } - - return written, nil -} diff --git a/lib/srv/alpnproxy/local_proxy.go b/lib/srv/alpnproxy/local_proxy.go index 103a45af5e60d..d914002faf7d9 100644 --- a/lib/srv/alpnproxy/local_proxy.go +++ b/lib/srv/alpnproxy/local_proxy.go @@ -34,6 +34,8 @@ import ( "github.com/sirupsen/logrus" "golang.org/x/exp/slices" + "github.com/gravitational/teleport/api/client" + "github.com/gravitational/teleport/api/utils/pingconn" "github.com/gravitational/teleport/lib/srv/alpnproxy/common" commonApp "github.com/gravitational/teleport/lib/srv/app/common" "github.com/gravitational/teleport/lib/tlsca" @@ -218,7 +220,7 @@ func (l *LocalProxy) handleDownstreamConnection(ctx context.Context, downstreamC return trace.Wrap(err) } - tlsConn, err := DialALPN(ctx, l.cfg.RemoteProxyAddr, l.getALPNDialerConfig(certs)) + tlsConn, err := client.DialALPN(ctx, l.cfg.RemoteProxyAddr, l.getALPNDialerConfig(certs)) if err != nil { return trace.Wrap(err) } @@ -227,7 +229,7 @@ func (l *LocalProxy) handleDownstreamConnection(ctx context.Context, downstreamC var upstreamConn net.Conn = tlsConn if common.IsPingProtocol(common.Protocol(tlsConn.ConnectionState().NegotiatedProtocol)) { l.cfg.Log.Debug("Using ping connection") - upstreamConn = NewPingConn(tlsConn) + upstreamConn = pingconn.New(tlsConn) } return trace.Wrap(utils.ProxyConn(ctx, downstreamConn, upstreamConn)) @@ -243,8 +245,8 @@ func (l *LocalProxy) Close() error { return nil } -func (l *LocalProxy) getALPNDialerConfig(certs []tls.Certificate) ALPNDialerConfig { - return ALPNDialerConfig{ +func (l *LocalProxy) getALPNDialerConfig(certs []tls.Certificate) client.ALPNDialerConfig { + return client.ALPNDialerConfig{ ALPNConnUpgradeRequired: l.cfg.ALPNConnUpgradeRequired, TLSConfig: &tls.Config{ NextProtos: common.ProtocolsToString(l.cfg.Protocols), @@ -285,7 +287,7 @@ func (l *LocalProxy) makeHTTPReverseProxy(certs []tls.Certificate) *httputil.Rev http.Error(w, http.StatusText(code), code) }, Transport: &http.Transport{ - DialTLSContext: NewALPNDialer(l.getALPNDialerConfig(certs)).DialContext, + DialTLSContext: client.NewALPNDialer(l.getALPNDialerConfig(certs)).DialContext, }, } } diff --git a/lib/srv/alpnproxy/local_proxy_config_opt.go b/lib/srv/alpnproxy/local_proxy_config_opt.go index 20b5e61e23dde..f2af0b5ed81ae 100644 --- a/lib/srv/alpnproxy/local_proxy_config_opt.go +++ b/lib/srv/alpnproxy/local_proxy_config_opt.go @@ -24,6 +24,7 @@ import ( "github.com/gravitational/trace" + "github.com/gravitational/teleport/api/client" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/srv/alpnproxy/common" ) @@ -41,7 +42,7 @@ type GetClusterCACertPoolFunc func(ctx context.Context) (*x509.CertPool, error) // already been set. func WithALPNConnUpgradeTest(ctx context.Context, getClusterCertPool GetClusterCACertPoolFunc) LocalProxyConfigOpt { return func(config *LocalProxyConfig) error { - config.ALPNConnUpgradeRequired = IsALPNConnUpgradeRequired(config.RemoteProxyAddr, config.InsecureSkipVerify) + config.ALPNConnUpgradeRequired = client.IsALPNConnUpgradeRequired(config.RemoteProxyAddr, config.InsecureSkipVerify) return trace.Wrap(WithClusterCAsIfConnUpgrade(ctx, getClusterCertPool)(config)) } } diff --git a/lib/srv/alpnproxy/proxy.go b/lib/srv/alpnproxy/proxy.go index 4ac5613fb538d..2c0595cf7f8cf 100644 --- a/lib/srv/alpnproxy/proxy.go +++ b/lib/srv/alpnproxy/proxy.go @@ -33,6 +33,7 @@ import ( "github.com/sirupsen/logrus" "github.com/gravitational/teleport/api/constants" + "github.com/gravitational/teleport/api/utils/pingconn" "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/srv/alpnproxy/common" @@ -410,7 +411,7 @@ func (p *Proxy) handleConn(ctx context.Context, clientConn net.Conn, defaultOver // handlePingConnection starts the server ping routine and returns `pingConn`. func (p *Proxy) handlePingConnection(ctx context.Context, conn *tls.Conn) net.Conn { - pingConn := NewPingConn(conn) + pingConn := pingconn.New(conn) // Start ping routine. It will continuously send pings in a defined // interval. diff --git a/lib/srv/alpnproxy/proxy_test.go b/lib/srv/alpnproxy/proxy_test.go index 6c81f62696de1..7e7e9451c8132 100644 --- a/lib/srv/alpnproxy/proxy_test.go +++ b/lib/srv/alpnproxy/proxy_test.go @@ -32,6 +32,7 @@ import ( "github.com/stretchr/testify/require" "github.com/gravitational/teleport/api/constants" + "github.com/gravitational/teleport/api/utils/pingconn" "github.com/gravitational/teleport/lib/srv/alpnproxy/common" "github.com/gravitational/teleport/lib/srv/db/dbutils" "github.com/gravitational/teleport/lib/tlsca" @@ -191,7 +192,7 @@ func TestProxyTLSDatabaseHandler(t *testing.T) { }) require.NoError(t, err) - conn := NewPingConn(baseConn) + conn := pingconn.New(baseConn) tlsConn := tls.Client(conn, &tls.Config{ Certificates: []tls.Certificate{ clientCert, diff --git a/lib/srv/ctx.go b/lib/srv/ctx.go index 7fd0a11cada84..375414aaf2710 100644 --- a/lib/srv/ctx.go +++ b/lib/srv/ctx.go @@ -18,6 +18,7 @@ package srv import ( "context" + "encoding/json" "fmt" "io" "net" @@ -182,6 +183,48 @@ type Server interface { TargetMetadata() apievents.ServerMetadata } +// childProcessError is used to provide an underlying error +// from a re-executed Teleport child process to its parent. +type childProcessError struct { + Code int `json:"code"` + RawError []byte `json:"rawError"` +} + +// writeChildError encodes the provided error +// as json and writes it to w. Special care +// is taken to preserve the error type by +// including the error code and raw message +// so that [DecodeChildError] will return +// the matching error type and message. +func writeChildError(w io.Writer, err error) { + if w == nil || err == nil { + return + } + + data, jerr := json.Marshal(err) + if jerr != nil { + return + } + + _ = json.NewEncoder(w).Encode(childProcessError{ + Code: trace.ErrorToCode(err), + RawError: data, + }) + +} + +// DecodeChildError consumes the output from a child +// process decoding it from its raw form back into +// a concrete error. +func DecodeChildError(r io.Reader) error { + var c childProcessError + if err := json.NewDecoder(r).Decode(&c); err != nil { + return nil + } + + return trace.ReadError(c.Code, c.RawError) +} + // IdentityContext holds all identity information associated with the user // logged on the connection. type IdentityContext struct { @@ -374,6 +417,12 @@ type ServerContext struct { x11rdyr *os.File x11rdyw *os.File + // err{r,w} is used to propagate errors from the child process to the + // parent process so the parent can get more information about why the child + // process failed and act accordingly. + errr *os.File + errw *os.File + // x11Config holds the xauth and XServer listener config for this session. x11Config *X11Config @@ -523,6 +572,15 @@ func NewServerContext(ctx context.Context, parent *sshutils.ConnectionContext, s child.AddCloser(child.x11rdyr) child.AddCloser(child.x11rdyw) + // Create pipe used to get errors from the child process. + child.errr, child.errw, err = os.Pipe() + if err != nil { + childErr := child.Close() + return nil, nil, trace.NewAggregate(err, childErr) + } + child.AddCloser(child.errr) + child.AddCloser(child.errw) + return ctx, child, nil } @@ -833,6 +891,11 @@ func (c *ServerContext) x11Ready() (bool, error) { return true, nil } +// GetChildError returns the error from the child process +func (c *ServerContext) GetChildError() error { + return DecodeChildError(c.errr) +} + // takeClosers returns all resources that should be closed and sets the properties to null // we do this to avoid calling Close() under lock to avoid potential deadlocks func (c *ServerContext) takeClosers() []io.Closer { diff --git a/lib/srv/ctx_test.go b/lib/srv/ctx_test.go index 809fa5d2a439f..0143dd3cf952b 100644 --- a/lib/srv/ctx_test.go +++ b/lib/srv/ctx_test.go @@ -17,10 +17,13 @@ limitations under the License. package srv import ( + "bytes" + "os/user" "testing" "github.com/gogo/protobuf/proto" "github.com/google/go-cmp/cmp" + "github.com/gravitational/trace" "github.com/stretchr/testify/require" "golang.org/x/crypto/ssh" "google.golang.org/protobuf/testing/protocmp" @@ -31,6 +34,19 @@ import ( "github.com/gravitational/teleport/lib/services" ) +// TestDecodeChildError ensures that child error message marshaling +// and unmarshaling returns the original values. +func TestDecodeChildError(t *testing.T) { + var buf bytes.Buffer + require.NoError(t, DecodeChildError(&buf)) + + targetErr := trace.NotFound(user.UnknownUserError("test").Error()) + + writeChildError(&buf, targetErr) + + require.ErrorIs(t, DecodeChildError(&buf), targetErr) +} + func TestCheckSFTPAllowed(t *testing.T) { srv := newMockServer(t) ctx := newTestServerContext(t, srv, nil) diff --git a/lib/srv/db/common/engines.go b/lib/srv/db/common/engines.go index f363c00fc1bdd..8066e567eb490 100644 --- a/lib/srv/db/common/engines.go +++ b/lib/srv/db/common/engines.go @@ -26,6 +26,7 @@ import ( "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/cloud" + "github.com/gravitational/teleport/lib/srv/db/common/enterprise" ) var ( @@ -69,6 +70,10 @@ func CheckEngines(names ...string) error { enginesMu.RLock() defer enginesMu.RUnlock() for _, name := range names { + if err := enterprise.ProtocolValidation(name); err != nil { + // Don't assert Enterprise protocol is a build is OSS + continue + } if engines[name] == nil { return trace.NotFound("database engine %q is not registered", name) } diff --git a/lib/srv/db/common/enterprise/enterprise.go b/lib/srv/db/common/enterprise/enterprise.go new file mode 100644 index 0000000000000..264847764e25c --- /dev/null +++ b/lib/srv/db/common/enterprise/enterprise.go @@ -0,0 +1,35 @@ +/* +Copyright 2023 Gravitational, Inc. + +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. +*/ + +package enterprise + +import ( + "github.com/gravitational/trace" + + "github.com/gravitational/teleport/lib/defaults" + "github.com/gravitational/teleport/lib/modules" +) + +// ProtocolValidation checks if protocol is supported for current build. +func ProtocolValidation(dbProtocol string) error { + switch dbProtocol { + case defaults.ProtocolOracle: + if modules.GetModules().BuildType() != modules.BuildEnterprise { + return trace.BadParameter("%s database protocol is only available with an enterprise license", dbProtocol) + } + } + return nil +} diff --git a/lib/srv/db/proxyserver.go b/lib/srv/db/proxyserver.go index e3c6b5c857ddc..113f5322e2a4f 100644 --- a/lib/srv/db/proxyserver.go +++ b/lib/srv/db/proxyserver.go @@ -44,6 +44,7 @@ import ( "github.com/gravitational/teleport/lib/limiter" "github.com/gravitational/teleport/lib/reversetunnel" "github.com/gravitational/teleport/lib/srv/db/common" + "github.com/gravitational/teleport/lib/srv/db/common/enterprise" "github.com/gravitational/teleport/lib/srv/db/dbutils" "github.com/gravitational/teleport/lib/srv/db/mysql" "github.com/gravitational/teleport/lib/srv/db/postgres" @@ -333,6 +334,9 @@ func (s *ProxyServer) handleConnection(conn net.Conn) error { s.cfg.IngressReporter.ConnectionAuthenticated(ingress.DatabaseTLS, conn) defer s.cfg.IngressReporter.AuthenticatedConnectionClosed(ingress.DatabaseTLS, conn) } + if enterprise.ProtocolValidation(proxyCtx.Identity.RouteToDatabase.Protocol); err != nil { + return trace.Wrap(err) + } switch proxyCtx.Identity.RouteToDatabase.Protocol { case defaults.ProtocolPostgres, defaults.ProtocolCockroachDB: @@ -345,6 +349,7 @@ func (s *ProxyServer) handleConnection(conn net.Conn) error { case defaults.ProtocolSQLServer: return s.SQLServerProxy().HandleConnection(s.closeCtx, proxyCtx, tlsConn) } + serviceConn, err := s.Connect(s.closeCtx, proxyCtx, conn.RemoteAddr(), conn.LocalAddr()) if err != nil { return trace.Wrap(err) diff --git a/lib/srv/db/sqlserver/connect.go b/lib/srv/db/sqlserver/connect.go index ee099b3f01f40..631b247be5368 100644 --- a/lib/srv/db/sqlserver/connect.go +++ b/lib/srv/db/sqlserver/connect.go @@ -29,12 +29,22 @@ import ( "github.com/microsoft/go-mssqldb/azuread" "github.com/microsoft/go-mssqldb/msdsn" + "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/auth/windows" "github.com/gravitational/teleport/lib/srv/db/common" "github.com/gravitational/teleport/lib/srv/db/sqlserver/kinit" "github.com/gravitational/teleport/lib/srv/db/sqlserver/protocol" ) +const ( + // ResourceIDDSNKey represents the resource ID DSN parameter key. This value + // is defined by the go-mssqldb library. + ResourceIDDSNKey = "resource id" + // FederatedAuthDSNKey represents the federated auth DSN parameter key. This + // value is defined by the go-mssqldb library. + FederatedAuthDSNKey = "fedauth" +) + // Connector defines an interface for connecting to a SQL Server so it can be // swapped out in tests. type Connector interface { @@ -109,35 +119,19 @@ func (c *connector) Connect(ctx context.Context, sessionCtx *common.Session, log } var connector *mssql.Connector - if sessionCtx.Database.IsAzure() && sessionCtx.Database.GetAD().Domain == "" { + switch { + case sessionCtx.Database.IsAzure() && sessionCtx.Database.GetAD().Domain == "": // If the client is connecting to Azure SQL, and no AD configuration is // provided, authenticate using the Azure AD Integrated authentication // method. - managedIdentityID, err := c.DBAuth.GetAzureIdentityResourceID(ctx, sessionCtx.DatabaseUser) - if err != nil { - return nil, nil, trace.Wrap(err) - } - - dsnConfig.Parameters = map[string]string{ - "fedauth": azuread.ActiveDirectoryManagedIdentity, - "resource id": managedIdentityID, - } - - connector, err = azuread.NewConnectorFromConfig(dsnConfig) - if err != nil { - return nil, nil, trace.Wrap(err) - } - } else { - kc, err := c.getKerberosClient(ctx, sessionCtx) - if err != nil { - return nil, nil, trace.Wrap(err) - } - dbAuth, err := c.getAuth(sessionCtx, kc) - if err != nil { - return nil, nil, trace.Wrap(err) - } - - connector = mssql.NewConnectorConfig(dsnConfig, dbAuth) + connector, err = c.getAzureConnector(ctx, sessionCtx, dsnConfig) + case sessionCtx.Database.GetType() == types.DatabaseTypeRDSProxy: + connector, err = c.getAccessTokenConnector(sessionCtx, dsnConfig) + default: + connector, err = c.getKerberosConnector(ctx, sessionCtx, dsnConfig) + } + if err != nil { + return nil, nil, trace.Wrap(err) } conn, err := connector.Connect(ctx) @@ -154,3 +148,46 @@ func (c *connector) Connect(ctx context.Context, sessionCtx *common.Session, log // back to the client. return mssqlConn.GetUnderlyingConn(), mssqlConn.GetLoginFlags(), nil } + +// getKerberosConnector generates a Kerberos connector using proper Kerberos +// client. +func (c *connector) getKerberosConnector(ctx context.Context, sessionCtx *common.Session, dsnConfig msdsn.Config) (*mssql.Connector, error) { + kc, err := c.getKerberosClient(ctx, sessionCtx) + if err != nil { + return nil, trace.Wrap(err) + } + dbAuth, err := c.getAuth(sessionCtx, kc) + if err != nil { + return nil, trace.Wrap(err) + } + + return mssql.NewConnectorConfig(dsnConfig, dbAuth), nil +} + +// getAzureConnector generates a connector that authenticates using Azure AD. +func (c *connector) getAzureConnector(ctx context.Context, sessionCtx *common.Session, dsnConfig msdsn.Config) (*mssql.Connector, error) { + managedIdentityID, err := c.DBAuth.GetAzureIdentityResourceID(ctx, sessionCtx.DatabaseUser) + if err != nil { + return nil, trace.Wrap(err) + } + + dsnConfig.Parameters = map[string]string{ + FederatedAuthDSNKey: azuread.ActiveDirectoryManagedIdentity, + ResourceIDDSNKey: managedIdentityID, + } + + connector, err := azuread.NewConnectorFromConfig(dsnConfig) + if err != nil { + return nil, trace.Wrap(err) + } + + return connector, nil +} + +// getAccessTokenConnector generates a connector that uses a token to +// authenticate. +func (c *connector) getAccessTokenConnector(sessionCtx *common.Session, dsnConfig msdsn.Config) (*mssql.Connector, error) { + return mssql.NewSecurityTokenConnector(dsnConfig, func(ctx context.Context) (string, error) { + return c.DBAuth.GetRDSAuthToken(sessionCtx) + }) +} diff --git a/lib/srv/db/sqlserver/connect_test.go b/lib/srv/db/sqlserver/connect_test.go index 495b86d74b14a..7c77d53d5b5fe 100644 --- a/lib/srv/db/sqlserver/connect_test.go +++ b/lib/srv/db/sqlserver/connect_test.go @@ -97,6 +97,24 @@ func TestConnectorSelection(t *testing.T) { require.Contains(t, err.Error(), "unable to open tcp connection with host") }, }, + { + desc: "RDS Proxied database", + databaseSpec: types.DatabaseSpecV3{ + Protocol: defaults.ProtocolSQLServer, + URI: "proxy-sqlserver.proxy-000000000000.us-east-1.rds.amazonaws.com:1433", + AWS: types.AWS{ + RDSProxy: types.RDSProxy{ + Name: "proxy-sqlserver", + }, + }, + }, + // RDS proxies cannot be accessed outside their VPC. So, this test + // case should not resolve their host. + errAssertion: func(t require.TestingT, err error, _ ...interface{}) { + require.Error(t, err) + require.Contains(t, err.Error(), "no such host") + }, + }, } { t.Run(tt.desc, func(t *testing.T) { database, err := types.NewDatabaseV3(types.Metadata{ diff --git a/lib/srv/db/sqlserver/kinit/kinit.go b/lib/srv/db/sqlserver/kinit/kinit.go index b7fc77bc6c0b5..a0a80264d4060 100644 --- a/lib/srv/db/sqlserver/kinit/kinit.go +++ b/lib/srv/db/sqlserver/kinit/kinit.go @@ -31,6 +31,7 @@ import ( "github.com/jcmturner/gokrb5/v8/credentials" "github.com/sirupsen/logrus" + "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/auth/windows" ) @@ -208,6 +209,7 @@ func (d *DBCertGetter) GetCertificateBytes(ctx context.Context) (*WindowsCAAndKe } certPEM, keyPEM, caCerts, err := windows.CertKeyPEM(ctx, &windows.GenerateCredentialsRequest{ + CAType: types.DatabaseCA, Username: d.UserName, Domain: d.RealmName, TTL: certTTL, diff --git a/lib/srv/desktop/windows_server.go b/lib/srv/desktop/windows_server.go index c17b854c86f80..10b10e28530de 100644 --- a/lib/srv/desktop/windows_server.go +++ b/lib/srv/desktop/windows_server.go @@ -1140,6 +1140,7 @@ func (s *WindowsService) generateUserCert(ctx context.Context, username string, // https://docs.microsoft.com/en-us/windows/security/identity-protection/smart-cards/smart-card-certificate-requirements-and-enumeration func (s *WindowsService) generateCredentials(ctx context.Context, username, domain string, ttl time.Duration, activeDirectorySID string) (certDER, keyDER []byte, err error) { return windows.GenerateWindowsDesktopCredentials(ctx, &windows.GenerateCredentialsRequest{ + CAType: types.UserCA, Username: username, Domain: domain, TTL: ttl, diff --git a/lib/srv/discovery/fetchers/db/aws_rds_proxy.go b/lib/srv/discovery/fetchers/db/aws_rds_proxy.go index f23d52f95b71b..cac0f6dcf9e6f 100644 --- a/lib/srv/discovery/fetchers/db/aws_rds_proxy.go +++ b/lib/srv/discovery/fetchers/db/aws_rds_proxy.go @@ -82,14 +82,6 @@ func (f *rdsDBProxyFetcher) getRDSProxyDatabases(ctx context.Context) (types.Dat var databases types.Databases for _, dbProxy := range rdsProxies { - // TODO(greedy52) RDS Proxy supports MS SQL Server but it requires MS - // SQL Server engine support for IAM auth. - // - // https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-proxy-setup.html#rds-proxy-connecting-sqlserver - if aws.StringValue(dbProxy.EngineFamily) == rds.EngineFamilySqlserver { - f.log.Debugf("RDS Proxy %q with engine family %q is not supported yet. Skipping.", aws.StringValue(dbProxy.DBProxyName), aws.StringValue(dbProxy.EngineFamily)) - continue - } if !aws.BoolValue(dbProxy.RequireTLS) { f.log.Debugf("RDS Proxy %q doesn't support TLS. Skipping.", aws.StringValue(dbProxy.DBProxyName)) continue diff --git a/lib/srv/forward/sshserver.go b/lib/srv/forward/sshserver.go index a00cd0b4e7295..cd8e21fdbae9b 100644 --- a/lib/srv/forward/sshserver.go +++ b/lib/srv/forward/sshserver.go @@ -1135,8 +1135,13 @@ func (s *Server) dispatch(ctx context.Context, ch ssh.Channel, req *ssh.Request, case sshutils.PuTTYWinadjRequest: return s.handlePuTTYWinadj(ch, req) default: - return trace.BadParameter( - "%v doesn't support request type '%v'", s.Component(), req.Type) + s.log.Warnf("%v doesn't support request type '%v'", s.Component(), req.Type) + if req.WantReply { + if err := req.Reply(false, nil); err != nil { + s.log.Errorf("sending error reply on SSH channel: %v", err) + } + } + return nil } } diff --git a/lib/srv/reexec.go b/lib/srv/reexec.go index 205871addf9fe..8488cde31d81b 100644 --- a/lib/srv/reexec.go +++ b/lib/srv/reexec.go @@ -68,6 +68,9 @@ const ( // X11File is used to communicate to the parent process that the child // process has set up X11 forwarding. X11File + // ErrorFile is used to communicate any errors terminating the child process + // to the parent process + ErrorFile // PTYFile is a PTY the parent process passes to the child process. PTYFile // TTYFile is a TTY the parent process passes to the child process. @@ -75,9 +78,13 @@ const ( // FirstExtraFile is the first file descriptor that will be valid when // extra files are passed to child processes without a terminal. - FirstExtraFile = X11File + 1 + FirstExtraFile FileFD = ErrorFile + 1 ) +func fdName(f FileFD) string { + return fmt.Sprintf("/proc/self/fd/%d", f) +} + // ExecCommand contains the payload to "teleport exec" which will be used to // construct and execute a shell. type ExecCommand struct { @@ -191,29 +198,23 @@ func RunCommand() (errw io.Writer, code int, err error) { errorWriter := os.Stdout // Parent sends the command payload in the third file descriptor. - cmdfd := os.NewFile(CommandFile, fmt.Sprintf("/proc/self/fd/%d", CommandFile)) + cmdfd := os.NewFile(CommandFile, fdName(CommandFile)) if cmdfd == nil { return errorWriter, teleport.RemoteCommandFailure, trace.BadParameter("command pipe not found") } - contfd := os.NewFile(ContinueFile, fmt.Sprintf("/proc/self/fd/%d", ContinueFile)) + contfd := os.NewFile(ContinueFile, fdName(ContinueFile)) if contfd == nil { return errorWriter, teleport.RemoteCommandFailure, trace.BadParameter("continue pipe not found") } - termiantefd := os.NewFile(TerminateFile, fmt.Sprintf("/proc/self/fd/%d", TerminateFile)) + termiantefd := os.NewFile(TerminateFile, fdName(TerminateFile)) if termiantefd == nil { return errorWriter, teleport.RemoteCommandFailure, trace.BadParameter("terminate pipe not found") } // Read in the command payload. - var b bytes.Buffer - _, err = b.ReadFrom(cmdfd) - if err != nil { - return errorWriter, teleport.RemoteCommandFailure, trace.Wrap(err) - } var c ExecCommand - err = json.Unmarshal(b.Bytes(), &c) - if err != nil { - return errorWriter, teleport.RemoteCommandFailure, trace.Wrap(err) + if err := json.NewDecoder(cmdfd).Decode(&c); err != nil { + return io.Discard, teleport.RemoteCommandFailure, trace.Wrap(err) } auditdMsg := auditd.Message{ @@ -251,8 +252,8 @@ func RunCommand() (errw io.Writer, code int, err error) { // PTY and TTY. Extract them and set the controlling TTY. Otherwise, connect // std{in,out,err} directly. if c.Terminal { - pty = os.NewFile(PTYFile, fmt.Sprintf("/proc/self/fd/%d", PTYFile)) - tty = os.NewFile(TTYFile, fmt.Sprintf("/proc/self/fd/%d", TTYFile)) + pty = os.NewFile(PTYFile, fdName(PTYFile)) + tty = os.NewFile(TTYFile, fdName(TTYFile)) if pty == nil || tty == nil { return errorWriter, teleport.RemoteCommandFailure, trace.BadParameter("pty and tty not found") } @@ -391,7 +392,7 @@ func RunCommand() (errw io.Writer, code int, err error) { cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", x11.DisplayEnv, c.X11Config.XAuthEntry.Display.String())) // Open x11rdy fd to signal parent process once X11 forwarding is set up. - x11rdyfd := os.NewFile(X11File, fmt.Sprintf("/proc/self/fd/%d", X11File)) + x11rdyfd := os.NewFile(X11File, fdName(X11File)) if x11rdyfd == nil { return errorWriter, teleport.RemoteCommandFailure, trace.BadParameter("continue pipe not found") } @@ -568,20 +569,24 @@ func RunForward() (errw io.Writer, code int, err error) { errorWriter := os.Stderr // Parent sends the command payload in the third file descriptor. - cmdfd := os.NewFile(CommandFile, fmt.Sprintf("/proc/self/fd/%d", CommandFile)) + cmdfd := os.NewFile(CommandFile, fdName(CommandFile)) if cmdfd == nil { return errorWriter, teleport.RemoteCommandFailure, trace.BadParameter("command pipe not found") } - // Read in the command payload. - var b bytes.Buffer - _, err = b.ReadFrom(cmdfd) - if err != nil { - return errorWriter, teleport.RemoteCommandFailure, trace.Wrap(err) + // Parent receives any errors on the sixth file descriptor. + errfd := os.NewFile(ErrorFile, fdName(ErrorFile)) + if errfd == nil { + return errorWriter, teleport.RemoteCommandFailure, trace.BadParameter("error pipe not found") } + + defer func() { + writeChildError(errfd, err) + }() + + // Read in the command payload. var c ExecCommand - err = json.Unmarshal(b.Bytes(), &c) - if err != nil { + if err := json.NewDecoder(cmdfd).Decode(&c); err != nil { return errorWriter, teleport.RemoteCommandFailure, trace.Wrap(err) } @@ -607,6 +612,10 @@ func RunForward() (errw io.Writer, code int, err error) { defer pamContext.Close() } + if _, err := user.Lookup(c.Login); err != nil { + return errorWriter, teleport.RemoteCommandFailure, trace.NotFound(err.Error()) + } + // Connect to the target host. conn, err := net.Dial("tcp", c.DestinationAddress) if err != nil { @@ -614,33 +623,12 @@ func RunForward() (errw io.Writer, code int, err error) { } defer conn.Close() - // Start copy routines that copy from channel to stdin pipe and from stdout - // pipe to channel. - errorCh := make(chan error, 2) - go func() { - defer conn.Close() - defer os.Stdout.Close() - defer os.Stdin.Close() - - _, err := io.Copy(os.Stdout, conn) - errorCh <- err - }() - go func() { - defer conn.Close() - defer os.Stdout.Close() - defer os.Stdin.Close() - - _, err := io.Copy(conn, os.Stdin) - errorCh <- err - }() - - // Block until copy is complete in either direction. The other direction - // will get cleaned up automatically. - if err = <-errorCh; err != nil && err != io.EOF { + err = utils.ProxyConn(context.Background(), utils.CombineReadWriteCloser(os.Stdin, os.Stdout), conn) + if err != nil && !errors.Is(err, io.EOF) { return errorWriter, teleport.RemoteCommandFailure, trace.Wrap(err) } - return io.Discard, teleport.RemoteCommandSuccess, nil + return errorWriter, teleport.RemoteCommandSuccess, nil } // runCheckHomeDir check's if the active user's $HOME dir exists. @@ -877,11 +865,7 @@ func ConfigureCommand(ctx *ServerContext, extraFiles ...*os.File) (*exec.Cmd, er cmdmsg.ExtraFilesLen = len(extraFiles) } - cmdbytes, err := json.Marshal(cmdmsg) - if err != nil { - return nil, trace.Wrap(err) - } - go copyCommand(ctx, cmdbytes) + go copyCommand(ctx, cmdmsg) // Find the Teleport executable and its directory on disk. executable, err := os.Executable() @@ -911,6 +895,7 @@ func ConfigureCommand(ctx *ServerContext, extraFiles ...*os.File) (*exec.Cmd, er ctx.contr, ctx.killShellr, ctx.x11rdyw, + ctx.errw, }, } // Add extra files if applicable. @@ -926,7 +911,7 @@ func ConfigureCommand(ctx *ServerContext, extraFiles ...*os.File) (*exec.Cmd, er // copyCommand will copy the provided command to the child process over the // pipe attached to the context. -func copyCommand(ctx *ServerContext, cmdbytes []byte) { +func copyCommand(ctx *ServerContext, cmdmsg *ExecCommand) { defer func() { err := ctx.cmdw.Close() if err != nil { @@ -939,8 +924,7 @@ func copyCommand(ctx *ServerContext, cmdbytes []byte) { // Write command bytes to pipe. The child process will read the command // to execute from this pipe. - _, err := io.Copy(ctx.cmdw, bytes.NewReader(cmdbytes)) - if err != nil { + if err := json.NewEncoder(ctx.cmdw).Encode(cmdmsg); err != nil { log.Errorf("Failed to copy command over pipe: %v.", err) return } diff --git a/lib/srv/regular/proxy.go b/lib/srv/regular/proxy.go index fad37e5c90bcb..65bbf9e3d9bc5 100644 --- a/lib/srv/regular/proxy.go +++ b/lib/srv/regular/proxy.go @@ -24,7 +24,6 @@ import ( "io" "net" "strings" - "time" "github.com/gravitational/trace" "github.com/sirupsen/logrus" @@ -261,12 +260,14 @@ func (t *proxySubsys) proxyToHost(ctx context.Context, ch ssh.Channel, clientSrc t.log.Debugf("proxy connecting to host=%v port=%v, exact port=%v", t.host, t.port, t.SpecifiedPort()) aGetter := t.ctx.StartAgentChannel - signerCreator := func() (ssh.Signer, error) { - validBefore := time.Unix(int64(t.ctx.Identity.Certificate.ValidBefore), 0) - ttl := time.Until(validBefore) - return agentless.CreateAuthSigner(ctx, t.ctx.Identity.TeleportUser, t.clusterName, ttl, t.router) + + client, err := t.router.GetSiteClient(ctx, t.clusterName) + if err != nil { + return trace.Wrap(err) } - conn, teleportVersion, err := t.router.DialHost(ctx, clientSrcAddr, clientDstAddr, t.host, t.port, t.clusterName, t.ctx.Identity.AccessChecker, aGetter, signerCreator) + + signer := agentless.SignerFromSSHCertificate(t.ctx.Identity.Certificate, client) + conn, teleportVersion, err := t.router.DialHost(ctx, clientSrcAddr, clientDstAddr, t.host, t.port, t.clusterName, t.ctx.Identity.AccessChecker, aGetter, signer) if err != nil { return trace.Wrap(err) } diff --git a/lib/srv/regular/sshserver.go b/lib/srv/regular/sshserver.go index d16568b8b5ca2..ec693b8cb4916 100644 --- a/lib/srv/regular/sshserver.go +++ b/lib/srv/regular/sshserver.go @@ -21,6 +21,7 @@ package regular import ( "context" "encoding/json" + "errors" "fmt" "io" "net" @@ -1345,8 +1346,8 @@ func (s *Server) handleDirectTCPIPRequest(ctx context.Context, ccx *sshutils.Con defer scx.Debugf("Closing direct-tcpip channel from %v to %v.", scx.SrcAddr, scx.DstAddr) // Create command to re-exec Teleport which will perform a net.Dial. The - // reason it's not done directly is because the PAM stack needs to be called - // from another process. + // reason it's not done directly because the PAM stack needs to be called + // from the child process. cmd, err := srv.ConfigureCommand(scx) if err != nil { writeStderr(channel, err.Error()) @@ -1378,63 +1379,48 @@ func (s *Server) handleDirectTCPIPRequest(ctx context.Context, ccx *sshutils.Con return } - // Start copy routines that copy from channel to stdin pipe and from stdout - // pipe to channel. - errorCh := make(chan error, 2) - go func() { - defer channel.Close() - defer pw.Close() - defer pr.Close() - - _, err := io.Copy(pw, channel) - errorCh <- err - }() - go func() { - defer channel.Close() - defer pw.Close() - defer pr.Close() - - _, err := io.Copy(channel, pr) - errorCh <- err - }() - - // Block until copy is complete and the child process is done executing. -Loop: - for i := 0; i < 2; i++ { - select { - case err := <-errorCh: - if err != nil && err != io.EOF { - s.Logger.Warnf("Connection problem in \"direct-tcpip\" channel: %v %T.", trace.DebugReport(err), err) - } - case <-ctx.Done(): - break Loop - case <-s.ctx.Done(): - break Loop + if err := utils.ProxyConn(ctx, utils.CombineReadWriteCloser(pr, pw), channel); err != nil && !errors.Is(err, io.EOF) && !errors.Is(err, os.ErrClosed) { + s.Logger.Warnf("Connection problem in direct-tcpip channel: %v %T.", trace.DebugReport(err), err) + } + + // Emit a port forwarding event if the command exited successfully. + if err := cmd.Wait(); err == nil { + if err := s.EmitAuditEvent(s.ctx, &apievents.PortForward{ + Metadata: apievents.Metadata{ + Type: events.PortForwardEvent, + Code: events.PortForwardCode, + }, + UserMetadata: scx.Identity.GetUserMetadata(), + ConnectionMetadata: apievents.ConnectionMetadata{ + LocalAddr: scx.ServerConn.LocalAddr().String(), + RemoteAddr: scx.ServerConn.RemoteAddr().String(), + }, + Addr: scx.DstAddr, + Status: apievents.Status{ + Success: true, + }, + }); err != nil { + s.Logger.WithError(err).Warn("Failed to emit port forward event.") } - } - err = cmd.Wait() - if err != nil { - writeStderr(channel, err.Error()) return } - // Emit a port forwarding event. - if err := s.EmitAuditEvent(s.ctx, &apievents.PortForward{ - Metadata: apievents.Metadata{ - Type: events.PortForwardEvent, - Code: events.PortForwardCode, - }, - UserMetadata: scx.Identity.GetUserMetadata(), - ConnectionMetadata: apievents.ConnectionMetadata{ - LocalAddr: scx.ServerConn.LocalAddr().String(), - RemoteAddr: scx.ServerConn.RemoteAddr().String(), - }, - Addr: scx.DstAddr, - Status: apievents.Status{ - Success: true, - }, - }); err != nil { - s.Logger.WithError(err).Warn("Failed to emit port forward event.") + // Get the error to see why the child process failed and + // determine the correct course of action. + err = scx.GetChildError() + switch { + case err == nil: + s.Logger.Warn("Forwarding data via direct-tcpip channel failed for unknown reason") + return + // The user does not exist for the provided login. Terminate the connection. + case errors.Is(err, trace.NotFound(user.UnknownUserError(scx.Identity.Login).Error())), + errors.Is(err, trace.BadParameter("unknown user")): + s.Logger.Warnf("Forwarding data via direct-tcpip channel failed. Terminating connection because user %q does not exist", scx.Identity.Login) + if err := ccx.ServerConn.Close(); err != nil { + s.Logger.Warnf("Unable to terminate connection: %v", err) + } + default: + s.Logger.WithError(err).Error("Forwarding data via direct-tcpip channel failed") } } @@ -1590,8 +1576,13 @@ func (s *Server) dispatch(ctx context.Context, ch ssh.Channel, req *ssh.Request, s.Logger.Debugf("%v: deliberately ignoring request for '%v' channel", s.Component(), sshutils.PuTTYSimpleRequest) return nil default: - return trace.BadParameter( - "(%v) proxy doesn't support request type '%v'", s.Component(), req.Type) + s.Logger.Warnf("(%v) proxy doesn't support request type '%v'", s.Component(), req.Type) + if req.WantReply { + if err := req.Reply(false, nil); err != nil { + s.Logger.Errorf("sending error reply on SSH channel: %v", err) + } + } + return nil } } @@ -1688,8 +1679,13 @@ func (s *Server) dispatch(ctx context.Context, ch ssh.Channel, req *ssh.Request, case sshutils.PuTTYWinadjRequest: return s.handlePuTTYWinadj(ch, req) default: - return trace.BadParameter( - "%v doesn't support request type '%v'", s.Component(), req.Type) + s.Logger.Warnf("%v doesn't support request type '%v'", s.Component(), req.Type) + if req.WantReply { + if err := req.Reply(false, nil); err != nil { + s.Logger.Errorf("sending error reply on SSH channel: %v", err) + } + } + return nil } } diff --git a/lib/srv/regular/sshserver_test.go b/lib/srv/regular/sshserver_test.go index 7fca6e3c966d2..8b6be168e11f7 100644 --- a/lib/srv/regular/sshserver_test.go +++ b/lib/srv/regular/sshserver_test.go @@ -1695,6 +1695,30 @@ func TestEnvs(t *testing.T) { } } +// TestUnknownRequest validates that any unknown session +// requests do not terminate the session. +func TestUnknownRequest(t *testing.T) { + t.Parallel() + ctx := context.Background() + + f := newFixtureWithoutDiskBasedLogging(t) + + se, err := f.ssh.clt.NewSession(ctx) + require.NoError(t, err) + defer se.Close() + + // send a random request that won't be handled + ok, err := se.SendRequest(ctx, uuid.NewString(), true, nil) + require.NoError(t, err) + require.False(t, ok) + + // ensure the session is still active + require.NoError(t, se.Setenv(ctx, "HOME_TEST", "/test")) + output, err := se.Output(ctx, "env") + require.NoError(t, err) + require.Contains(t, string(output), "HOME_TEST=/test") +} + // TestNoAuth tries to log in with no auth methods and should be rejected func TestNoAuth(t *testing.T) { t.Parallel() diff --git a/lib/srv/sess.go b/lib/srv/sess.go index 364a4358e6b63..2f692b3e0edaf 100644 --- a/lib/srv/sess.go +++ b/lib/srv/sess.go @@ -1486,8 +1486,8 @@ func (s *session) addParty(p *party, mode types.SessionParticipantMode) error { // Register this party as one of the session writers (output will go to it). s.io.AddWriter(string(p.id), p) - s.BroadcastMessage("User %v joined the session.", p.user) - s.log.Infof("New party %v joined session", p.String()) + s.BroadcastMessage("User %v joined the session with participant mode: %v.", p.user, p.mode) + s.log.Infof("New party %v joined the session with participant mode: %v.", p.String(), p.mode) if mode == types.SessionPeerMode { s.term.AddParty(1) diff --git a/lib/srv/transport/transportv1/transport.go b/lib/srv/transport/transportv1/transport.go index abb073b7d6e8c..a9eb17e524341 100644 --- a/lib/srv/transport/transportv1/transport.go +++ b/lib/srv/transport/transportv1/transport.go @@ -22,6 +22,7 @@ import ( "github.com/gravitational/trace" "github.com/sirupsen/logrus" + "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/agent" "google.golang.org/grpc/credentials" "google.golang.org/grpc/peer" @@ -29,6 +30,7 @@ import ( transportv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/transport/v1" streamutils "github.com/gravitational/teleport/api/utils/grpc/stream" "github.com/gravitational/teleport/lib/auth" + "github.com/gravitational/teleport/lib/authz" "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/teleagent" "github.com/gravitational/teleport/lib/utils" @@ -36,24 +38,38 @@ import ( // Dialer is the interface that groups basic dialing methods. type Dialer interface { - DialSite(ctx context.Context, clusterName string) (net.Conn, error) - DialHost(ctx context.Context, from net.Addr, host, port, clusterName string, accessChecker services.AccessChecker, agentGetter teleagent.Getter) (net.Conn, error) + DialSite(ctx context.Context, cluster string, clientSrcAddr, clientDstAddr net.Addr) (net.Conn, error) + DialHost(ctx context.Context, clientSrcAddr, clientDstAddr net.Addr, host, port, cluster string, checker services.AccessChecker, agentGetter teleagent.Getter, singer func(context.Context) (ssh.Signer, error)) (_ net.Conn, teleportVersion string, err error) +} + +// ConnMonitor monitors authorized connnections and terminates them when +// session controls dictate so. +type ConnectionMonitor interface { + MonitorConn(ctx context.Context, authCtx *authz.Context, conn net.Conn) (context.Context, error) } // ServerConfig holds creation parameters for Service. type ServerConfig struct { // FIPS indicates whether the cluster if configured - // to run in FIPS mode + // to run in FIPS mode. FIPS bool - // Logger provides a mechanism to log output + // Logger provides a mechanism to log output. Logger logrus.FieldLogger - // Dialer is used to establish remote connections + // Dialer is used to establish remote connections. Dialer Dialer + // SignerFn is used to create an [ssh.Signer] for an authenticated connection. + SignerFn func(authzCtx *authz.Context) func(context.Context) (ssh.Signer, error) + // ConnectionMonitor is used to monitor the connection for activity and terminate it + // when conditions are met. + ConnectionMonitor ConnectionMonitor + // LocalAddr is the local address of the service. + LocalAddr net.Addr // agentGetterFn used by tests to serve the agent directly agentGetterFn func(rw io.ReadWriter) teleagent.Getter - accessCheckerFn func(info credentials.AuthInfo) (services.AccessChecker, error) + // authzContextFn used by tests to inject an access checker + authzContextFn func(info credentials.AuthInfo) (*authz.Context, error) } // CheckAndSetDefaults ensures required parameters are set @@ -63,6 +79,10 @@ func (c *ServerConfig) CheckAndSetDefaults() error { return trace.BadParameter("parameter Dialer required") } + if c.LocalAddr == nil { + return trace.BadParameter("parameter LocalAddr required") + } + if c.Logger == nil { c.Logger = utils.NewLogger().WithField(trace.Component, "transport") } @@ -75,14 +95,14 @@ func (c *ServerConfig) CheckAndSetDefaults() error { } } - if c.accessCheckerFn == nil { - c.accessCheckerFn = func(info credentials.AuthInfo) (services.AccessChecker, error) { + if c.authzContextFn == nil { + c.authzContextFn = func(info credentials.AuthInfo) (*authz.Context, error) { identityInfo, ok := info.(auth.IdentityInfo) if !ok { return nil, trace.AccessDenied("client is not authenticated") } - return identityInfo.AuthContext.Checker, nil + return identityInfo.AuthContext, nil } } @@ -120,7 +140,13 @@ func (s *Service) ProxyCluster(stream transportv1pb.TransportService_ProxyCluste return trace.Wrap(err, "failed receiving first frame") } - conn, err := s.cfg.Dialer.DialSite(stream.Context(), req.Cluster) + ctx := stream.Context() + p, ok := peer.FromContext(ctx) + if !ok { + return trace.BadParameter("unable to find peer") + } + + conn, err := s.cfg.Dialer.DialSite(ctx, req.Cluster, p.Addr, s.cfg.LocalAddr) if err != nil { return trace.Wrap(err, "failed dialing cluster %q", req.Cluster) } @@ -139,7 +165,7 @@ func (s *Service) ProxyCluster(stream transportv1pb.TransportService_ProxyCluste return trace.Wrap(err, "failed constructing streamer") } - return trace.Wrap(utils.ProxyConn(stream.Context(), conn, streamRW)) + return trace.Wrap(utils.ProxyConn(ctx, conn, streamRW)) } // clusterStream implements the [streamutils.Source] interface @@ -168,7 +194,7 @@ func (c clusterStream) Send(frame []byte) error { // ProxySSH establishes a connection to a host and proxies both the SSH and SSH // Agent protocol over the stream. The first request from the client must contain // a valid dial target before the connection can be established. -func (s *Service) ProxySSH(stream transportv1pb.TransportService_ProxySSHServer) error { +func (s *Service) ProxySSH(stream transportv1pb.TransportService_ProxySSHServer) (err error) { ctx := stream.Context() p, ok := peer.FromContext(ctx) @@ -176,7 +202,7 @@ func (s *Service) ProxySSH(stream transportv1pb.TransportService_ProxySSHServer) return trace.BadParameter("unable to find peer") } - checker, err := s.cfg.accessCheckerFn(p.AuthInfo) + authzContext, err := s.cfg.authzContextFn(p.AuthInfo) if err != nil { return trace.Wrap(err) } @@ -235,15 +261,34 @@ func (s *Service) ProxySSH(stream transportv1pb.TransportService_ProxySSHServer) } defer agentStreamRW.Close() - conn, err := s.cfg.Dialer.DialHost(ctx, p.Addr, host, port, req.DialTarget.Cluster, checker, s.cfg.agentGetterFn(agentStreamRW)) + // create a reader/writer for SSH protocol + sshStreamRW, err := streamutils.NewReadWriter(sshStream) + if err != nil { + return trace.Wrap(err, "failed constructing ssh streamer") + } + + signer := s.cfg.SignerFn(authzContext) + hostConn, _, err := s.cfg.Dialer.DialHost(ctx, p.Addr, s.cfg.LocalAddr, host, port, req.DialTarget.Cluster, authzContext.Checker, s.cfg.agentGetterFn(agentStreamRW), signer) if err != nil { return trace.Wrap(err, "failed to dial target host") } - // create a reader/writer for SSH protocol - sshStreamRW, err := streamutils.NewReadWriter(sshStream) + // ensure the connection to the target host + // gets closed when exiting + defer func() { + hostConn.Close() + }() + + targetAddr, err := utils.ParseAddr(req.DialTarget.HostPort) if err != nil { - return trace.Wrap(err, "failed constructing ssh streamer") + return trace.Wrap(err) + } + + // monitor the user connection + userConn := streamutils.NewConn(sshStreamRW, p.Addr, targetAddr) + monitorCtx, err := s.cfg.ConnectionMonitor.MonitorConn(ctx, authzContext, userConn) + if err != nil { + return trace.Wrap(err) } // send back the cluster details to alert the other side that @@ -254,8 +299,8 @@ func (s *Service) ProxySSH(stream transportv1pb.TransportService_ProxySSHServer) return trace.Wrap(err, "failed sending cluster details ") } - // copy data to/from the connection and ssh stream - return trace.Wrap(utils.ProxyConn(ctx, conn, sshStreamRW)) + // copy data to/from the host/user + return trace.Wrap(utils.ProxyConn(monitorCtx, hostConn, userConn)) } // sshStream implements the [streamutils.Source] interface diff --git a/lib/srv/transport/transportv1/transport_test.go b/lib/srv/transport/transportv1/transport_test.go index 8085d7efc712a..1a1c54f2b6b06 100644 --- a/lib/srv/transport/transportv1/transport_test.go +++ b/lib/srv/transport/transportv1/transport_test.go @@ -41,6 +41,7 @@ import ( transportv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/transport/v1" streamutils "github.com/gravitational/teleport/api/utils/grpc/stream" + "github.com/gravitational/teleport/lib/authz" "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/teleagent" "github.com/gravitational/teleport/lib/utils" @@ -99,7 +100,7 @@ type fakeDialer struct { hostConns map[string]net.Conn } -func (f fakeDialer) DialSite(ctx context.Context, clusterName string) (net.Conn, error) { +func (f fakeDialer) DialSite(ctx context.Context, clusterName string, clientSrcAddr, clientDstAddr net.Addr) (net.Conn, error) { conn, ok := f.siteConns[clusterName] if !ok { return nil, trace.NotFound(clusterName) @@ -108,14 +109,14 @@ func (f fakeDialer) DialSite(ctx context.Context, clusterName string) (net.Conn, return conn, nil } -func (f fakeDialer) DialHost(ctx context.Context, from net.Addr, host, port, clusterName string, accessChecker services.AccessChecker, agentGetter teleagent.Getter) (net.Conn, error) { - key := fmt.Sprintf("%s.%s.%s", host, port, clusterName) +func (f fakeDialer) DialHost(ctx context.Context, clientSrcAddr, clientDstAddr net.Addr, host, port, cluster string, checker services.AccessChecker, agentGetter teleagent.Getter, singer func(context.Context) (ssh.Signer, error)) (_ net.Conn, teleportVersion string, err error) { + key := fmt.Sprintf("%s.%s.%s", host, port, cluster) conn, ok := f.hostConns[key] if !ok { - return nil, trace.NotFound(key) + return nil, "", trace.NotFound(key) } - return conn, nil + return conn, "", nil } // testPack used to test a [Service]. @@ -178,6 +179,18 @@ func newServer(t *testing.T, cfg ServerConfig) testPack { } } +func fakeSigner(authzCtx *authz.Context) func(context.Context) (ssh.Signer, error) { + return func(context.Context) (ssh.Signer, error) { + return nil, nil + } +} + +type fakeMonitor struct{} + +func (f fakeMonitor) MonitorConn(ctx context.Context, authCtx *authz.Context, conn net.Conn) (context.Context, error) { + return ctx, nil +} + // TestService_GetClusterDetails validates that a [Service] returns // the expected cluster details. func TestService_GetClusterDetails(t *testing.T) { @@ -200,8 +213,12 @@ func TestService_GetClusterDetails(t *testing.T) { t.Run(test.name, func(t *testing.T) { t.Parallel() srv := newServer(t, ServerConfig{ - Dialer: fakeDialer{}, - FIPS: test.FIPS, + Dialer: fakeDialer{}, + Logger: utils.NewLoggerForTests(), + FIPS: test.FIPS, + SignerFn: fakeSigner, + ConnectionMonitor: fakeMonitor{}, + LocalAddr: &utils.NetAddr{}, }) resp, err := srv.Client.GetClusterDetails(context.Background(), &transportv1pb.GetClusterDetailsRequest{}) @@ -280,6 +297,10 @@ func TestService_ProxyCluster(t *testing.T) { cluster: conn, }, }, + Logger: utils.NewLoggerForTests(), + SignerFn: fakeSigner, + ConnectionMonitor: fakeMonitor{}, + LocalAddr: &utils.NetAddr{}, }) stream, err := srv.Client.ProxyCluster(context.Background()) @@ -407,8 +428,18 @@ func TestService_ProxySSH_Errors(t *testing.T) { fakeHost: conn, }, }, - Logger: utils.NewLoggerForTests(), - accessCheckerFn: test.checkerFn, + SignerFn: fakeSigner, + ConnectionMonitor: fakeMonitor{}, + Logger: utils.NewLoggerForTests(), + LocalAddr: &utils.NetAddr{}, + authzContextFn: func(info credentials.AuthInfo) (*authz.Context, error) { + checker, err := test.checkerFn(info) + if err != nil { + return nil, trace.Wrap(err) + } + + return &authz.Context{Checker: checker}, nil + }, }) stream, err := srv.Client.ProxySSH(context.Background()) @@ -461,7 +492,11 @@ func TestService_ProxySSH(t *testing.T) { // create a server that will open a new connection to the // ssh server created above on each dial request srv := newServer(t, ServerConfig{ - Dialer: sshSrv, + Dialer: sshSrv, + SignerFn: fakeSigner, + Logger: utils.NewLoggerForTests(), + LocalAddr: &utils.NetAddr{}, + ConnectionMonitor: fakeMonitor{}, agentGetterFn: func(rw io.ReadWriter) teleagent.Getter { return func() (teleagent.Agent, error) { srw, ok := rw.(*streamutils.ReadWriter) @@ -474,8 +509,8 @@ func TestService_ProxySSH(t *testing.T) { }, nil } }, - accessCheckerFn: func(info credentials.AuthInfo) (services.AccessChecker, error) { - return fakeChecker{}, nil + authzContextFn: func(info credentials.AuthInfo) (*authz.Context, error) { + return &authz.Context{Checker: fakeChecker{}}, nil }, }) @@ -619,7 +654,7 @@ type sshServer struct { } // DialSite returns a connection to the sshServer -func (s *sshServer) DialSite(context.Context, string) (net.Conn, error) { +func (s *sshServer) DialSite(ctx context.Context, clusterName string, clientSrcAddr, clientDstAddr net.Addr) (net.Conn, error) { conn, err := s.dial() if err != nil { return nil, trace.Wrap(err) @@ -632,31 +667,31 @@ func (s *sshServer) DialSite(context.Context, string) (net.Conn, error) { // nil and is of type testAgent, then the server will serve its keyring // over the underlying [streamutils.ReadWriter] so that tests can exercise // ssh agent multiplexing. -func (s *sshServer) DialHost(ctx context.Context, from net.Addr, host, port, clusterName string, accessChecker services.AccessChecker, agentGetter teleagent.Getter) (net.Conn, error) { +func (s *sshServer) DialHost(ctx context.Context, clientSrcAddr, clientDstAddr net.Addr, host, port, cluster string, checker services.AccessChecker, agentGetter teleagent.Getter, singer func(context.Context) (ssh.Signer, error)) (_ net.Conn, teleportVersion string, err error) { conn, err := s.dial() if err != nil { - return nil, trace.Wrap(err) + return nil, "", trace.Wrap(err) } if agentGetter == nil { - return conn, nil + return conn, "", nil } agnt, err := agentGetter() if err != nil { - return nil, trace.Wrap(err) + return nil, "", trace.Wrap(err) } rw, ok := agnt.(testAgent) if !ok { - return conn, nil + return conn, "", nil } go func() { agent.ServeAgent(s.keyring, rw) }() - return conn, nil + return conn, "", nil } func (s *sshServer) Run() { diff --git a/lib/tlsca/ca.go b/lib/tlsca/ca.go index ed55400bcc3a5..9b242abb5e596 100644 --- a/lib/tlsca/ca.go +++ b/lib/tlsca/ca.go @@ -190,6 +190,9 @@ type Identity struct { // DeviceExtensions holds device-aware extensions for the identity. DeviceExtensions DeviceExtensions + + // UserType indicates if the User was created by an SSO Provider or locally. + UserType types.UserType } // RouteToApp holds routing information for applications. diff --git a/lib/usagereporter/teleport/types.go b/lib/usagereporter/teleport/types.go index 150abefbacc98..b9c8cbfe7b2ae 100644 --- a/lib/usagereporter/teleport/types.go +++ b/lib/usagereporter/teleport/types.go @@ -407,10 +407,18 @@ func (u *ResourceHeartbeatEvent) Anonymize(a utils.Anonymizer) prehogv1.SubmitEv } } +// UserMetadata contains user metadata information which is used to contextualize events with user information. +type UserMetadata struct { + // Username contains the user's name. + Username string + // IsSSO indicates if the user was created by an SSO provider. + IsSSO bool +} + // ConvertUsageEvent converts a usage event from an API object into an // anonymizable event. All events that can be submitted externally via the Auth // API need to be defined here. -func ConvertUsageEvent(event *usageeventsv1.UsageEventOneOf, identityUsername string) (Anonymizable, error) { +func ConvertUsageEvent(event *usageeventsv1.UsageEventOneOf, userMD UserMetadata) (Anonymizable, error) { // Note: events (especially pre-registration) that embed a username of their // own should generally pass that through rather than using the identity // username provided to the function. It may be the username of a Teleport @@ -419,16 +427,16 @@ func ConvertUsageEvent(event *usageeventsv1.UsageEventOneOf, identityUsername st switch e := event.GetEvent().(type) { case *usageeventsv1.UsageEventOneOf_UiBannerClick: return &UIBannerClickEvent{ - UserName: identityUsername, + UserName: userMD.Username, Alert: e.UiBannerClick.Alert, }, nil case *usageeventsv1.UsageEventOneOf_UiOnboardAddFirstResourceClick: return &UIOnboardAddFirstResourceClickEvent{ - UserName: identityUsername, + UserName: userMD.Username, }, nil case *usageeventsv1.UsageEventOneOf_UiOnboardAddFirstResourceLaterClick: return &UIOnboardAddFirstResourceLaterClickEvent{ - UserName: identityUsername, + UserName: userMD.Username, }, nil case *usageeventsv1.UsageEventOneOf_UiOnboardCompleteGoToDashboardClick: return &UIOnboardCompleteGoToDashboardClickEvent{ @@ -458,23 +466,23 @@ func ConvertUsageEvent(event *usageeventsv1.UsageEventOneOf, identityUsername st }, nil case *usageeventsv1.UsageEventOneOf_UiCreateNewRoleClick: return &UICreateNewRoleClickEvent{ - UserName: identityUsername, + UserName: userMD.Username, }, nil case *usageeventsv1.UsageEventOneOf_UiCreateNewRoleSaveClick: return &UICreateNewRoleSaveClickEvent{ - UserName: identityUsername, + UserName: userMD.Username, }, nil case *usageeventsv1.UsageEventOneOf_UiCreateNewRoleCancelClick: return &UICreateNewRoleCancelClickEvent{ - UserName: identityUsername, + UserName: userMD.Username, }, nil case *usageeventsv1.UsageEventOneOf_UiCreateNewRoleViewDocumentationClick: return &UICreateNewRoleViewDocumentationClickEvent{ - UserName: identityUsername, + UserName: userMD.Username, }, nil case *usageeventsv1.UsageEventOneOf_UiDiscoverStartedEvent: ret := &UIDiscoverStartedEvent{ - Metadata: discoverMetadataToPrehog(e.UiDiscoverStartedEvent.Metadata, identityUsername), + Metadata: discoverMetadataToPrehog(e.UiDiscoverStartedEvent.Metadata, userMD), Status: discoverStatusToPrehog(e.UiDiscoverStartedEvent.Status), } if err := ret.CheckAndSetDefaults(); err != nil { @@ -484,7 +492,7 @@ func ConvertUsageEvent(event *usageeventsv1.UsageEventOneOf, identityUsername st return ret, nil case *usageeventsv1.UsageEventOneOf_UiDiscoverResourceSelectionEvent: ret := &UIDiscoverResourceSelectionEvent{ - Metadata: discoverMetadataToPrehog(e.UiDiscoverResourceSelectionEvent.Metadata, identityUsername), + Metadata: discoverMetadataToPrehog(e.UiDiscoverResourceSelectionEvent.Metadata, userMD), Resource: discoverResourceToPrehog(e.UiDiscoverResourceSelectionEvent.Resource), Status: discoverStatusToPrehog(e.UiDiscoverResourceSelectionEvent.Status), } @@ -495,7 +503,7 @@ func ConvertUsageEvent(event *usageeventsv1.UsageEventOneOf, identityUsername st return ret, nil case *usageeventsv1.UsageEventOneOf_UiDiscoverDeployServiceEvent: ret := &UIDiscoverDeployServiceEvent{ - Metadata: discoverMetadataToPrehog(e.UiDiscoverDeployServiceEvent.Metadata, identityUsername), + Metadata: discoverMetadataToPrehog(e.UiDiscoverDeployServiceEvent.Metadata, userMD), Resource: discoverResourceToPrehog(e.UiDiscoverDeployServiceEvent.Resource), Status: discoverStatusToPrehog(e.UiDiscoverDeployServiceEvent.Status), } @@ -506,7 +514,7 @@ func ConvertUsageEvent(event *usageeventsv1.UsageEventOneOf, identityUsername st return ret, nil case *usageeventsv1.UsageEventOneOf_UiDiscoverDatabaseRegisterEvent: ret := &UIDiscoverDatabaseRegisterEvent{ - Metadata: discoverMetadataToPrehog(e.UiDiscoverDatabaseRegisterEvent.Metadata, identityUsername), + Metadata: discoverMetadataToPrehog(e.UiDiscoverDatabaseRegisterEvent.Metadata, userMD), Resource: discoverResourceToPrehog(e.UiDiscoverDatabaseRegisterEvent.Resource), Status: discoverStatusToPrehog(e.UiDiscoverDatabaseRegisterEvent.Status), } @@ -517,7 +525,7 @@ func ConvertUsageEvent(event *usageeventsv1.UsageEventOneOf, identityUsername st return ret, nil case *usageeventsv1.UsageEventOneOf_UiDiscoverDatabaseConfigureMtlsEvent: ret := &UIDiscoverDatabaseConfigureMTLSEvent{ - Metadata: discoverMetadataToPrehog(e.UiDiscoverDatabaseConfigureMtlsEvent.Metadata, identityUsername), + Metadata: discoverMetadataToPrehog(e.UiDiscoverDatabaseConfigureMtlsEvent.Metadata, userMD), Resource: discoverResourceToPrehog(e.UiDiscoverDatabaseConfigureMtlsEvent.Resource), Status: discoverStatusToPrehog(e.UiDiscoverDatabaseConfigureMtlsEvent.Status), } @@ -528,7 +536,7 @@ func ConvertUsageEvent(event *usageeventsv1.UsageEventOneOf, identityUsername st return ret, nil case *usageeventsv1.UsageEventOneOf_UiDiscoverDesktopActiveDirectoryToolsInstallEvent: ret := &UIDiscoverDesktopActiveDirectoryToolsInstallEvent{ - Metadata: discoverMetadataToPrehog(e.UiDiscoverDesktopActiveDirectoryToolsInstallEvent.Metadata, identityUsername), + Metadata: discoverMetadataToPrehog(e.UiDiscoverDesktopActiveDirectoryToolsInstallEvent.Metadata, userMD), Resource: discoverResourceToPrehog(e.UiDiscoverDesktopActiveDirectoryToolsInstallEvent.Resource), Status: discoverStatusToPrehog(e.UiDiscoverDesktopActiveDirectoryToolsInstallEvent.Status), } @@ -539,7 +547,7 @@ func ConvertUsageEvent(event *usageeventsv1.UsageEventOneOf, identityUsername st return ret, nil case *usageeventsv1.UsageEventOneOf_UiDiscoverDesktopActiveDirectoryConfigureEvent: ret := &UIDiscoverDesktopActiveDirectoryConfigureEvent{ - Metadata: discoverMetadataToPrehog(e.UiDiscoverDesktopActiveDirectoryConfigureEvent.Metadata, identityUsername), + Metadata: discoverMetadataToPrehog(e.UiDiscoverDesktopActiveDirectoryConfigureEvent.Metadata, userMD), Resource: discoverResourceToPrehog(e.UiDiscoverDesktopActiveDirectoryConfigureEvent.Resource), Status: discoverStatusToPrehog(e.UiDiscoverDesktopActiveDirectoryConfigureEvent.Status), } @@ -550,7 +558,7 @@ func ConvertUsageEvent(event *usageeventsv1.UsageEventOneOf, identityUsername st return ret, nil case *usageeventsv1.UsageEventOneOf_UiDiscoverAutoDiscoveredResourcesEvent: ret := &UIDiscoverAutoDiscoveredResourcesEvent{ - Metadata: discoverMetadataToPrehog(e.UiDiscoverAutoDiscoveredResourcesEvent.Metadata, identityUsername), + Metadata: discoverMetadataToPrehog(e.UiDiscoverAutoDiscoveredResourcesEvent.Metadata, userMD), Resource: discoverResourceToPrehog(e.UiDiscoverAutoDiscoveredResourcesEvent.Resource), Status: discoverStatusToPrehog(e.UiDiscoverAutoDiscoveredResourcesEvent.Status), ResourcesCount: e.UiDiscoverAutoDiscoveredResourcesEvent.ResourcesCount, @@ -562,7 +570,7 @@ func ConvertUsageEvent(event *usageeventsv1.UsageEventOneOf, identityUsername st return ret, nil case *usageeventsv1.UsageEventOneOf_UiDiscoverDatabaseConfigureIamPolicyEvent: ret := &UIDiscoverDatabaseConfigureIAMPolicyEvent{ - Metadata: discoverMetadataToPrehog(e.UiDiscoverDatabaseConfigureIamPolicyEvent.Metadata, identityUsername), + Metadata: discoverMetadataToPrehog(e.UiDiscoverDatabaseConfigureIamPolicyEvent.Metadata, userMD), Resource: discoverResourceToPrehog(e.UiDiscoverDatabaseConfigureIamPolicyEvent.Resource), Status: discoverStatusToPrehog(e.UiDiscoverDatabaseConfigureIamPolicyEvent.Status), } @@ -573,7 +581,7 @@ func ConvertUsageEvent(event *usageeventsv1.UsageEventOneOf, identityUsername st return ret, nil case *usageeventsv1.UsageEventOneOf_UiDiscoverPrincipalsConfigureEvent: ret := &UIDiscoverPrincipalsConfigureEvent{ - Metadata: discoverMetadataToPrehog(e.UiDiscoverPrincipalsConfigureEvent.Metadata, identityUsername), + Metadata: discoverMetadataToPrehog(e.UiDiscoverPrincipalsConfigureEvent.Metadata, userMD), Resource: discoverResourceToPrehog(e.UiDiscoverPrincipalsConfigureEvent.Resource), Status: discoverStatusToPrehog(e.UiDiscoverPrincipalsConfigureEvent.Status), } @@ -584,7 +592,7 @@ func ConvertUsageEvent(event *usageeventsv1.UsageEventOneOf, identityUsername st return ret, nil case *usageeventsv1.UsageEventOneOf_UiDiscoverTestConnectionEvent: ret := &UIDiscoverTestConnectionEvent{ - Metadata: discoverMetadataToPrehog(e.UiDiscoverTestConnectionEvent.Metadata, identityUsername), + Metadata: discoverMetadataToPrehog(e.UiDiscoverTestConnectionEvent.Metadata, userMD), Resource: discoverResourceToPrehog(e.UiDiscoverTestConnectionEvent.Resource), Status: discoverStatusToPrehog(e.UiDiscoverTestConnectionEvent.Status), } @@ -595,7 +603,7 @@ func ConvertUsageEvent(event *usageeventsv1.UsageEventOneOf, identityUsername st return ret, nil case *usageeventsv1.UsageEventOneOf_UiDiscoverCompletedEvent: ret := &UIDiscoverCompletedEvent{ - Metadata: discoverMetadataToPrehog(e.UiDiscoverCompletedEvent.Metadata, identityUsername), + Metadata: discoverMetadataToPrehog(e.UiDiscoverCompletedEvent.Metadata, userMD), Resource: discoverResourceToPrehog(e.UiDiscoverCompletedEvent.Resource), Status: discoverStatusToPrehog(e.UiDiscoverCompletedEvent.Status), } diff --git a/lib/usagereporter/teleport/types_discover.go b/lib/usagereporter/teleport/types_discover.go index ef92ba6dfbe47..42f31ffe11919 100644 --- a/lib/usagereporter/teleport/types_discover.go +++ b/lib/usagereporter/teleport/types_discover.go @@ -24,10 +24,11 @@ import ( "github.com/gravitational/teleport/lib/utils" ) -func discoverMetadataToPrehog(u *usageeventsv1.DiscoverMetadata, identityUsername string) *prehogv1.DiscoverMetadata { +func discoverMetadataToPrehog(u *usageeventsv1.DiscoverMetadata, userMD UserMetadata) *prehogv1.DiscoverMetadata { return &prehogv1.DiscoverMetadata{ Id: u.Id, - UserName: identityUsername, + UserName: userMD.Username, + Sso: userMD.IsSSO, } } @@ -121,6 +122,7 @@ func (u *UIDiscoverStartedEvent) Anonymize(a utils.Anonymizer) prehogv1.SubmitEv Metadata: &prehogv1.DiscoverMetadata{ Id: u.Metadata.Id, UserName: a.AnonymizeString(u.Metadata.UserName), + Sso: u.Metadata.Sso, }, Status: u.Status, }, diff --git a/lib/usagereporter/teleport/usagereporter_test.go b/lib/usagereporter/teleport/usagereporter_test.go index 419b545bad7e4..b2d42aa828174 100644 --- a/lib/usagereporter/teleport/usagereporter_test.go +++ b/lib/usagereporter/teleport/usagereporter_test.go @@ -35,6 +35,7 @@ func TestConvertUsageEvent(t *testing.T) { name string event *usageeventsv1.UsageEventOneOf identityUsername string + isSSOUser bool errCheck require.ErrorAssertionFunc expected *prehogv1.SubmitEventRequest }{ @@ -53,6 +54,7 @@ func TestConvertUsageEvent(t *testing.T) { Metadata: &prehogv1.DiscoverMetadata{ Id: "someid", UserName: expectedAnonymizedUserString, + Sso: false, }, Status: &prehogv1.DiscoverStepStatus{Status: prehogv1.DiscoverStatus_DISCOVER_STATUS_SUCCESS}, }, @@ -74,6 +76,7 @@ func TestConvertUsageEvent(t *testing.T) { Metadata: &prehogv1.DiscoverMetadata{ Id: "someid", UserName: expectedAnonymizedUserString, + Sso: false, }, Resource: &prehogv1.DiscoverResourceMetadata{Resource: prehogv1.DiscoverResource_DISCOVER_RESOURCE_SERVER}, Status: &prehogv1.DiscoverStepStatus{Status: prehogv1.DiscoverStatus_DISCOVER_STATUS_SUCCESS}, @@ -139,6 +142,7 @@ func TestConvertUsageEvent(t *testing.T) { Metadata: &prehogv1.DiscoverMetadata{ Id: "someid", UserName: expectedAnonymizedUserString, + Sso: false, }, Resource: &prehogv1.DiscoverResourceMetadata{Resource: prehogv1.DiscoverResource_DISCOVER_RESOURCE_SERVER}, Status: &prehogv1.DiscoverStepStatus{Status: prehogv1.DiscoverStatus_DISCOVER_STATUS_SUCCESS}, @@ -163,6 +167,7 @@ func TestConvertUsageEvent(t *testing.T) { Metadata: &prehogv1.DiscoverMetadata{ Id: "someid", UserName: expectedAnonymizedUserString, + Sso: false, }, Resource: &prehogv1.DiscoverResourceMetadata{Resource: prehogv1.DiscoverResource_DISCOVER_RESOURCE_SERVER}, Status: &prehogv1.DiscoverStepStatus{Status: prehogv1.DiscoverStatus_DISCOVER_STATUS_SUCCESS}, @@ -184,13 +189,38 @@ func TestConvertUsageEvent(t *testing.T) { errCheck: func(tt require.TestingT, err error, i ...interface{}) { require.True(tt, trace.IsBadParameter(err), "exepcted trace.IsBadParameter error, got: %v", err) }, + }, { + name: "discover started event with sso user", + event: &usageeventsv1.UsageEventOneOf{Event: &usageeventsv1.UsageEventOneOf_UiDiscoverStartedEvent{ + UiDiscoverStartedEvent: &usageeventsv1.UIDiscoverStartedEvent{ + Metadata: &usageeventsv1.DiscoverMetadata{Id: "someid"}, + Status: &usageeventsv1.DiscoverStepStatus{Status: usageeventsv1.DiscoverStatus_DISCOVER_STATUS_SUCCESS}, + }, + }}, + identityUsername: "myuser", + isSSOUser: true, + errCheck: require.NoError, + expected: &prehogv1.SubmitEventRequest{Event: &prehogv1.SubmitEventRequest_UiDiscoverStartedEvent{ + UiDiscoverStartedEvent: &prehogv1.UIDiscoverStartedEvent{ + Metadata: &prehogv1.DiscoverMetadata{ + Id: "someid", + UserName: expectedAnonymizedUserString, + Sso: true, + }, + Status: &prehogv1.DiscoverStepStatus{Status: prehogv1.DiscoverStatus_DISCOVER_STATUS_SUCCESS}, + }, + }}, }, } { t.Run(tt.name, func(t *testing.T) { tt := tt t.Parallel() - usageEvent, err := ConvertUsageEvent(tt.event, tt.identityUsername) + userMD := UserMetadata{ + Username: tt.identityUsername, + IsSSO: tt.isSSOUser, + } + usageEvent, err := ConvertUsageEvent(tt.event, userMD) tt.errCheck(t, err) if err != nil { return diff --git a/lib/utils/fs.go b/lib/utils/fs.go index 94b128b5b1a68..bd4a2a358be65 100644 --- a/lib/utils/fs.go +++ b/lib/utils/fs.go @@ -236,3 +236,13 @@ func overwriteFile(filePath string) (err error) { _, err = io.CopyN(f, rand.Reader, size) return trace.Wrap(err) } + +// RemoveFileIfExist removes file if exits. +func RemoveFileIfExist(filePath string) { + if !FileExists(filePath) { + return + } + if err := os.Remove(filePath); err != nil { + log.WithError(err).Warnf("Failed to remove %v", filePath) + } +} diff --git a/lib/utils/proxyconn.go b/lib/utils/proxyconn.go index 3856f9d998ce7..2493ad222edef 100644 --- a/lib/utils/proxyconn.go +++ b/lib/utils/proxyconn.go @@ -23,6 +23,36 @@ import ( "github.com/gravitational/trace" ) +// CombinedReadWriteCloser wraps an [io.ReadCloser] and an [io.WriteCloser] to +// implement [io.ReadWriteCloser]. Reads are performed on the [io.ReadCloser] and +// writes are performed on the [io.WriteCloser]. Closing will return the +// aggregated errors of both. +type CombinedReadWriteCloser struct { + r io.ReadCloser + w io.WriteCloser +} + +func (o CombinedReadWriteCloser) Read(p []byte) (int, error) { + return o.r.Read(p) +} + +func (o CombinedReadWriteCloser) Write(p []byte) (int, error) { + return o.w.Write(p) +} + +func (o CombinedReadWriteCloser) Close() error { + return trace.NewAggregate(o.r.Close(), o.w.Close()) +} + +// CombineReadWriteCloser creates a CombinedReadWriteCloser from the provided +// [io.ReadCloser] and [io.WriteCloser] that implements [io.ReadWriteCloser] +func CombineReadWriteCloser(r io.ReadCloser, w io.WriteCloser) CombinedReadWriteCloser { + return CombinedReadWriteCloser{ + r: r, + w: w, + } +} + // ProxyConn launches a double-copy loop that proxies traffic between the // provided client and server connections. // diff --git a/lib/web/apiserver.go b/lib/web/apiserver.go index 5ac7228b8674b..e23afe8129347 100644 --- a/lib/web/apiserver.go +++ b/lib/web/apiserver.go @@ -91,21 +91,8 @@ import ( const ( // SSOLoginFailureMessage is a generic error message to avoid disclosing sensitive SSO failure messages. SSOLoginFailureMessage = "Failed to login. Please check Teleport's log for more details." - metaRedirectHTML = ` - - - - Teleport Redirection Service - - - - - -` ) -var metaRedirectTemplate = template.Must(template.New("meta-redirect").Parse(metaRedirectHTML)) - // healthCheckAppServerFunc defines a function used to perform a health check // to AppServer that can handle application requests (based on cluster name and // public address). @@ -558,12 +545,6 @@ func (h *Handler) bindDefaultEndpoints() { // App sessions h.POST("/webapi/sessions/app", h.WithAuth(h.createAppSession)) - // DELETE IN 13, deprecated/unused web sessions routes (avatus) - // https://github.com/gravitational/teleport/pull/19892 - h.POST("/webapi/sessions", httplib.WithCSRFProtection(h.WithLimiterHandlerFunc(h.createWebSession))) - h.DELETE("/webapi/sessions", h.WithAuth(h.deleteWebSession)) - h.POST("/webapi/sessions/renew", h.WithAuth(h.renewWebSession)) - // Web sessions h.POST("/webapi/sessions/web", httplib.WithCSRFProtection(h.WithLimiterHandlerFunc(h.createWebSession))) h.DELETE("/webapi/sessions/web", h.WithAuth(h.deleteWebSession)) @@ -862,6 +843,11 @@ func (h *Handler) getUserContext(w http.ResponseWriter, r *http.Request, p httpr return userContext, nil } +// PublicProxyAddr returns the publicly advertised proxy address +func (h *Handler) PublicProxyAddr() string { + return h.cfg.PublicProxyAddr +} + func localSettings(cap types.AuthPreference) (webclient.AuthenticationSettings, error) { as := webclient.AuthenticationSettings{ Type: constants.Local, @@ -1573,7 +1559,7 @@ func (h *Handler) installer(w http.ResponseWriter, r *http.Request, p httprouter } tmpl := installers.Template{ - PublicProxyAddr: h.cfg.PublicProxyAddr, + PublicProxyAddr: h.PublicProxyAddr(), TeleportPackage: teleportPackage, RepoChannel: repoChannel, } @@ -2936,12 +2922,6 @@ func (h *Handler) siteSessionsGet(w http.ResponseWriter, r *http.Request, p http return nil, trace.Wrap(err) } - var policySets []*types.SessionTrackerPolicySet - for _, role := range userRoles { - policySet := role.GetSessionPolicySet() - policySets = append(policySets, &policySet) - } - accessContext := auth.SessionAccessContext{ Username: sctx.GetUser(), Roles: userRoles, @@ -2952,7 +2932,7 @@ func (h *Handler) siteSessionsGet(w http.ResponseWriter, r *http.Request, p http if tracker.GetState() != types.SessionState_SessionStateTerminated { session := trackerToLegacySession(tracker, p.ByName("site")) // Get the participant modes available to the user from their roles. - accessEvaluator := auth.NewSessionAccessEvaluator(policySets, types.SSHSessionKind, session.Owner) + accessEvaluator := auth.NewSessionAccessEvaluator(tracker.GetHostPolicySets(), types.SSHSessionKind, tracker.GetHostUser()) participantModes := accessEvaluator.CanJoin(accessContext) sessions = append(sessions, siteSessionsGetResponseSession{Session: session, ParticipantModes: participantModes}) @@ -3643,14 +3623,13 @@ func (h *Handler) WithRedirect(fn redirectHandlerFunc) httprouter.Handle { // See https://github.com/gravitational/teleport/issues/7467. func (h *Handler) WithMetaRedirect(fn redirectHandlerFunc) httprouter.Handle { return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { - app.SetRedirectPageHeaders(w.Header(), "") redirectURL := fn(w, r, p) if !isValidRedirectURL(redirectURL) { redirectURL = client.LoginFailedRedirectURL } - err := metaRedirectTemplate.Execute(w, redirectURL) + err := app.MetaRedirect(w, redirectURL) if err != nil { - h.log.WithError(err).Warn("Failed to execute template.") + h.log.WithError(err).Warn("Failed to issue a redirect.") } } } @@ -3666,6 +3645,21 @@ func (h *Handler) WithAuth(fn ContextHandler) httprouter.Handle { }) } +// WithAuthCookieAndCSRF ensures that a request is authenticated +// for plain old non-AJAX requests (does not check the Bearer header). +// It enforces CSRF checks (except for "safe" methods). +func (h *Handler) WithAuthCookieAndCSRF(fn ContextHandler) httprouter.Handle { + f := func(w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) { + sctx, err := h.AuthenticateRequest(w, r, false) + if err != nil { + return nil, trace.Wrap(err) + } + return fn(w, r, p, sctx) + } + + return httplib.WithCSRFProtection(f) +} + // WithLimiter adds IP-based rate limiting to fn. func (h *Handler) WithLimiter(fn httplib.HandlerFunc) httprouter.Handle { return httplib.MakeHandler(func(w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) { diff --git a/lib/web/apiserver_test.go b/lib/web/apiserver_test.go index ca33674784323..5ce2895145e68 100644 --- a/lib/web/apiserver_test.go +++ b/lib/web/apiserver_test.go @@ -1924,6 +1924,9 @@ func TestActiveSessions(t *testing.T) { s := newWebSuite(t) pack := s.authPack(t, "foo") + // Use enterprise license (required for moderated sessions). + modules.SetTestModules(t, &modules.TestModules{TestBuildType: modules.BuildEnterprise}) + start := time.Now() kinds := []types.SessionKind{ types.SSHSessionKind, @@ -1951,6 +1954,17 @@ func TestActiveSessions(t *testing.T) { Participants: []types.Participant{ {ID: "id", User: "user-1", LastActive: start}, }, + HostPolicies: []*types.SessionTrackerPolicySet{ + { + Name: "foo", + Version: "5", + RequireSessionJoin: []*types.SessionRequirePolicy{ + { + Name: "foo", + }, + }, + }, + }, }) require.NoError(t, err) ids[tracker.GetSessionID()] = struct{}{} @@ -2301,7 +2315,6 @@ func TestMotD(t *testing.T) { // TestPingAutomaticUpgrades ensures /webapi/ping returns whether AutomaticUpgrades are enabled. func TestPingAutomaticUpgrades(t *testing.T) { - t.Run("Automatic Upgrades are enabled", func(t *testing.T) { // Enable Automatic Upgrades modules.SetTestModules(t, &modules.TestModules{TestFeatures: modules.Features{ @@ -6728,6 +6741,10 @@ func (mock authProviderMock) GenerateUserSingleUseCerts(ctx context.Context) (au return nil, nil } +func (mock authProviderMock) GenerateOpenSSHCert(ctx context.Context, req *authproto.OpenSSHCertRequest) (*authproto.OpenSSHCert, error) { + return nil, nil +} + type terminalOpt func(t *TerminalRequest) func withSessionID(sid session.ID) terminalOpt { @@ -8204,12 +8221,10 @@ func TestForwardingTraces(t *testing.T) { } } -type mockPROXYSigner struct { -} +type mockPROXYSigner struct{} func (m *mockPROXYSigner) SignPROXYHeader(source, destination net.Addr) ([]byte, error) { return nil, nil - } type mockTraceClient struct { diff --git a/lib/web/app/redirect.go b/lib/web/app/redirect.go index 5fe990a1163b2..23f58310f3ea8 100644 --- a/lib/web/app/redirect.go +++ b/lib/web/app/redirect.go @@ -18,12 +18,29 @@ package app import ( "fmt" + "html/template" "net/http" "strings" + "github.com/gravitational/trace" + "github.com/gravitational/teleport/lib/httplib" ) +const metaRedirectHTML = ` + + + + Teleport Redirection Service + + + + + +` + +var metaRedirectTemplate = template.Must(template.New("meta-redirect").Parse(metaRedirectHTML)) + func SetRedirectPageHeaders(h http.Header, nonce string) { httplib.SetNoCacheHeaders(h) httplib.SetDefaultSecurityHeaders(h) @@ -42,3 +59,9 @@ func SetRedirectPageHeaders(h http.Header, nonce string) { }, ";") h.Set("Content-Security-Policy", csp) } + +// MetaRedirect issues a "meta refresh" redirect. +func MetaRedirect(w http.ResponseWriter, redirectURL string) error { + SetRedirectPageHeaders(w.Header(), "") + return trace.Wrap(metaRedirectTemplate.Execute(w, redirectURL)) +} diff --git a/lib/web/command.go b/lib/web/command.go index f84cf8bc7931e..658df118eaa00 100644 --- a/lib/web/command.go +++ b/lib/web/command.go @@ -433,16 +433,15 @@ func (t *commandHandler) streamOutput(ctx context.Context, ws WSConn, tc *client getAgent := func() (teleagent.Agent, error) { return teleagent.NopCloser(tc.LocalAgent()), nil } - signerCreator := func() (ssh.Signer, error) { - cert, err := t.ctx.GetSSHCertificate() - if err != nil { - return nil, trace.Wrap(err) - } - validBefore := time.Unix(int64(cert.ValidBefore), 0) - ttl := time.Until(validBefore) - return agentless.CreateAuthSigner(ctx, t.ctx.GetUser(), tc.SiteName, ttl, t.router) + cert, err := t.ctx.GetSSHCertificate() + if err != nil { + t.log.WithError(err).Warn("Unable to stream terminal - failed to get certificate") + t.writeError(err) + return } - conn, _, err := t.router.DialHost(ctx, ws.RemoteAddr(), ws.LocalAddr(), t.sessionData.ServerID, strconv.Itoa(t.sessionData.ServerHostPort), tc.SiteName, accessChecker, getAgent, signerCreator) + + signer := agentless.SignerFromSSHCertificate(cert, t.authProvider) + conn, _, err := t.router.DialHost(ctx, ws.RemoteAddr(), ws.LocalAddr(), t.sessionData.ServerID, strconv.Itoa(t.sessionData.ServerHostPort), tc.SiteName, accessChecker, getAgent, signer) if err != nil { t.log.WithError(err).Warn("Unable to stream terminal - failed to dial host.") @@ -472,7 +471,10 @@ func (t *commandHandler) streamOutput(ctx context.Context, ws WSConn, tc *client HostKeyCallback: tc.HostKeyCallback, } - nc, connectErr := client.NewNodeClient(ctx, sshConfig, conn, net.JoinHostPort(t.sessionData.ServerID, strconv.Itoa(t.sessionData.ServerHostPort)), tc, modules.GetModules().IsBoringBinary()) + nc, connectErr := client.NewNodeClient(ctx, sshConfig, conn, + net.JoinHostPort(t.sessionData.ServerID, strconv.Itoa(t.sessionData.ServerHostPort)), + t.sessionData.ServerHostname, + tc, modules.GetModules().IsBoringBinary()) switch { case connectErr != nil && !trace.IsAccessDenied(connectErr): // catastrophic error, return it t.log.WithError(connectErr).Warn("Unable to stream terminal - failed to create node client") @@ -509,7 +511,7 @@ func (t *commandHandler) streamOutput(ctx context.Context, ws WSConn, tc *client // Establish SSH connection to the server. This function will block until // either an error occurs or it completes successfully. - if err = nc.RunCommand(ctx, t.interactiveCommand, nil); err != nil { + if err = nc.RunCommand(ctx, t.interactiveCommand); err != nil { t.log.WithError(err).Warn("Unable to stream terminal - failure running interactive shell") t.writeError(err) return diff --git a/lib/web/conn_upgrade.go b/lib/web/conn_upgrade.go index 9f1638a74a2d2..cdfe3dae0556a 100644 --- a/lib/web/conn_upgrade.go +++ b/lib/web/conn_upgrade.go @@ -25,17 +25,17 @@ import ( "github.com/gravitational/trace" "github.com/julienschmidt/httprouter" - "github.com/gravitational/teleport" + "github.com/gravitational/teleport/api/constants" "github.com/gravitational/teleport/lib/utils" ) // selectConnectionUpgrade selects the requested upgrade type and returns the // corresponding handler. func (h *Handler) selectConnectionUpgrade(r *http.Request) (string, ConnectionHandler, error) { - upgrades := r.Header.Values(teleport.WebAPIConnUpgradeHeader) + upgrades := r.Header.Values(constants.WebAPIConnUpgradeHeader) for _, upgradeType := range upgrades { switch upgradeType { - case teleport.WebAPIConnUpgradeTypeALPN: + case constants.WebAPIConnUpgradeTypeALPN: return upgradeType, h.upgradeALPN, nil } } @@ -90,7 +90,7 @@ func (h *Handler) upgradeALPN(ctx context.Context, conn net.Conn) error { func writeUpgradeResponse(w io.Writer, upgradeType string) error { header := make(http.Header) - header.Add(teleport.WebAPIConnUpgradeHeader, upgradeType) + header.Add(constants.WebAPIConnUpgradeHeader, upgradeType) response := &http.Response{ Status: http.StatusText(http.StatusSwitchingProtocols), StatusCode: http.StatusSwitchingProtocols, diff --git a/lib/web/connection_diagnostic.go b/lib/web/connection_diagnostic.go index 653197c25c286..d2b71731d5e80 100644 --- a/lib/web/connection_diagnostic.go +++ b/lib/web/connection_diagnostic.go @@ -74,7 +74,7 @@ func (h *Handler) diagnoseConnection(w http.ResponseWriter, r *http.Request, p h ResourceKind: req.ResourceKind, UserClient: userClt, ProxyHostPort: h.ProxyHostPort(), - PublicProxyAddr: h.cfg.PublicProxyAddr, + PublicProxyAddr: h.PublicProxyAddr(), KubernetesPublicProxyAddr: h.kubeProxyHostPort(), TLSRoutingEnabled: proxySettings.TLSRoutingEnabled, } diff --git a/lib/web/terminal.go b/lib/web/terminal.go index f419e073995b0..d1cdb3d7dda6e 100644 --- a/lib/web/terminal.go +++ b/lib/web/terminal.go @@ -95,6 +95,7 @@ type AuthProvider interface { GetSessionTracker(ctx context.Context, sessionID string) (types.SessionTracker, error) IsMFARequired(ctx context.Context, req *authproto.IsMFARequiredRequest) (*authproto.IsMFARequiredResponse, error) GenerateUserSingleUseCerts(ctx context.Context) (authproto.AuthService_GenerateUserSingleUseCertsClient, error) + GenerateOpenSSHCert(ctx context.Context, req *authproto.OpenSSHCertRequest) (*authproto.OpenSSHCert, error) } // NewTerminal creates a web-based terminal based on WebSockets and returns a @@ -291,13 +292,6 @@ func (t *TerminalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - // If the displayLogin is set then use it instead of the login name used in - // the SSH connection. This is specifically for the use case when joining - // a session to avoid displaying "-teleport-internal-join" as the username. - if t.displayLogin != "" { - t.sessionData.Login = t.displayLogin - } - sendError := func(errMsg string, err error, ws *websocket.Conn) { envelope := &Envelope{ Version: defaults.WebsocketVersion, @@ -309,7 +303,19 @@ func (t *TerminalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { ws.WriteMessage(websocket.BinaryMessage, envelopeBytes) } - sessionMetadataResponse, err := json.Marshal(siteSessionGenerateResponse{Session: t.sessionData}) + var sessionMetadataResponse []byte + + // If the displayLogin is set then use it in the session metadata instead of the + // login name used in the SSH connection. This is specifically for the use case + // when joining a session to avoid displaying "-teleport-internal-join" as the username. + if t.displayLogin != "" { + sessionDataTemp := t.sessionData + sessionDataTemp.Login = t.displayLogin + sessionMetadataResponse, err = json.Marshal(siteSessionGenerateResponse{Session: sessionDataTemp}) + } else { + sessionMetadataResponse, err = json.Marshal(siteSessionGenerateResponse{Session: t.sessionData}) + } + if err != nil { sendError("unable to marshal session response", err, ws) return @@ -628,16 +634,15 @@ func (t *TerminalHandler) streamTerminal(ws *websocket.Conn, tc *client.Teleport getAgent := func() (teleagent.Agent, error) { return teleagent.NopCloser(tc.LocalAgent()), nil } - signerCreator := func() (ssh.Signer, error) { - cert, err := t.ctx.GetSSHCertificate() - if err != nil { - return nil, trace.Wrap(err) - } - validBefore := time.Unix(int64(cert.ValidBefore), 0) - ttl := time.Until(validBefore) - return agentless.CreateAuthSigner(t.terminalContext, t.ctx.GetUser(), tc.SiteName, ttl, t.router) + cert, err := t.ctx.GetSSHCertificate() + if err != nil { + t.log.WithError(err).Warn("Unable to stream terminal - failed to get certificate") + t.writeError(err) + return } - conn, _, err := t.router.DialHost(ctx, ws.RemoteAddr(), ws.LocalAddr(), t.sessionData.ServerID, strconv.Itoa(t.sessionData.ServerHostPort), tc.SiteName, accessChecker, getAgent, signerCreator) + + signer := agentless.SignerFromSSHCertificate(cert, t.authProvider) + conn, _, err := t.router.DialHost(ctx, ws.RemoteAddr(), ws.LocalAddr(), t.sessionData.ServerID, strconv.Itoa(t.sessionData.ServerHostPort), tc.SiteName, accessChecker, getAgent, signer) if err != nil { t.log.WithError(err).Warn("Unable to stream terminal - failed to dial host.") @@ -667,7 +672,10 @@ func (t *TerminalHandler) streamTerminal(ws *websocket.Conn, tc *client.Teleport HostKeyCallback: tc.HostKeyCallback, } - nc, connectErr := client.NewNodeClient(ctx, sshConfig, conn, net.JoinHostPort(t.sessionData.ServerID, strconv.Itoa(t.sessionData.ServerHostPort)), tc, modules.GetModules().IsBoringBinary()) + nc, connectErr := client.NewNodeClient(ctx, sshConfig, conn, + net.JoinHostPort(t.sessionData.ServerID, strconv.Itoa(t.sessionData.ServerHostPort)), + t.sessionData.ServerHostname, + tc, modules.GetModules().IsBoringBinary()) switch { case connectErr != nil && !trace.IsAccessDenied(connectErr): // catastrophic error, return it t.log.WithError(connectErr).Warn("Unable to stream terminal - failed to create node client") @@ -707,14 +715,17 @@ func (t *TerminalHandler) streamTerminal(ws *websocket.Conn, tc *client.Teleport sshConfig.Auth = tc.AuthMethods // connect to the node again with the new certs - conn, _, err = t.router.DialHost(ctx, ws.RemoteAddr(), ws.LocalAddr(), t.sessionData.ServerID, strconv.Itoa(t.sessionData.ServerHostPort), tc.SiteName, accessChecker, getAgent, signerCreator) + conn, _, err = t.router.DialHost(ctx, ws.RemoteAddr(), ws.LocalAddr(), t.sessionData.ServerID, strconv.Itoa(t.sessionData.ServerHostPort), tc.SiteName, accessChecker, getAgent, signer) if err != nil { t.log.WithError(err).Warn("Unable to stream terminal - failed to dial host") t.writeError(err) return } - nc, err = client.NewNodeClient(ctx, sshConfig, conn, net.JoinHostPort(t.sessionData.ServerID, strconv.Itoa(t.sessionData.ServerHostPort)), tc, modules.GetModules().IsBoringBinary()) + nc, err = client.NewNodeClient(ctx, sshConfig, conn, + net.JoinHostPort(t.sessionData.ServerID, strconv.Itoa(t.sessionData.ServerHostPort)), + t.sessionData.ServerHostname, + tc, modules.GetModules().IsBoringBinary()) if err != nil { t.log.WithError(err).Warn("Unable to stream terminal - failed to create node client") t.writeError(err) diff --git a/lib/web/ui/user.go b/lib/web/ui/user.go index 68fad8a03edfe..62f52edf91b25 100644 --- a/lib/web/ui/user.go +++ b/lib/web/ui/user.go @@ -65,7 +65,7 @@ func NewUserListEntry(teleUser types.User) (*UserListEntry, error) { } authType := "local" - if teleUser.GetCreatedBy().Connector != nil { + if teleUser.GetUserType() == types.UserTypeSSO { authType = teleUser.GetCreatedBy().Connector.Type } diff --git a/proto/prehog/v1alpha/connect.proto b/proto/prehog/v1alpha/connect.proto index bbc744de10d3b..36237c4b67f2d 100644 --- a/proto/prehog/v1alpha/connect.proto +++ b/proto/prehog/v1alpha/connect.proto @@ -40,6 +40,8 @@ message ConnectProtocolUseEvent { string user_name = 2; // one of ssh/db/kube string protocol = 3; + // one of resource_table/search_bar/connection_list/reopened_session (optional) + string origin = 4; } message ConnectAccessRequestCreateEvent { diff --git a/proto/prehog/v1alpha/teleport.proto b/proto/prehog/v1alpha/teleport.proto index c27c7e32c50d6..67d60af77fd81 100644 --- a/proto/prehog/v1alpha/teleport.proto +++ b/proto/prehog/v1alpha/teleport.proto @@ -146,6 +146,9 @@ message DiscoverMetadata { // anonymized string user_name = 2; + + // SSO indicates whether the user is from an SSO provider. + bool sso = 3; } // DiscoverResource represents a resource type. @@ -168,6 +171,28 @@ enum DiscoverResource { DISCOVER_RESOURCE_DATABASE_POSTGRES_GCP = 15; DISCOVER_RESOURCE_DATABASE_MYSQL_GCP = 16; DISCOVER_RESOURCE_DATABASE_SQLSERVER_GCP = 17; + + DISCOVER_RESOURCE_DATABASE_POSTGRES_REDSHIFT_SERVERLESS = 18; + DISCOVER_RESOURCE_DATABASE_POSTGRES_AZURE = 19; + DISCOVER_RESOURCE_DATABASE_DYNAMODB = 20; + DISCOVER_RESOURCE_DATABASE_CASSANDRA_KEYSPACES = 21; + DISCOVER_RESOURCE_DATABASE_CASSANDRA_SELF_HOSTED = 22; // Cassandra & ScyllaDb + DISCOVER_RESOURCE_DATABASE_ELASTICSEARCH_SELF_HOSTED = 23; + DISCOVER_RESOURCE_DATABASE_REDIS_ELASTICACHE = 24; // Elasticache & MemoryDb + DISCOVER_RESOURCE_DATABASE_REDIS_MEMORYDB = 25; + DISCOVER_RESOURCE_DATABASE_REDIS_AZURE_CACHE = 26; + DISCOVER_RESOURCE_DATABASE_REDIS_CLUSTER_SELF_HOSTED = 27; + + DISCOVER_RESOURCE_DATABASE_MYSQL_AZURE = 28; + DISCOVER_RESOURCE_DATABASE_SQLSERVER_AZURE = 29; + DISCOVER_RESOURCE_DATABASE_SQLSERVER_MICROSOFT = 30; + DISCOVER_RESOURCE_DATABASE_COCKROACHDB_SELF_HOSTED = 31; + DISCOVER_RESOURCE_DATABASE_MONGODB_ATLAS = 32; + DISCOVER_RESOURCE_DATABASE_SNOWFLAKE = 33; + + DISCOVER_RESOURCE_DOC_DATABASE_RDS_PROXY = 34; + DISCOVER_RESOURCE_DOC_DATABASE_HIGH_AVAILABILITY = 35; + DISCOVER_RESOURCE_DOC_DATABASE_DYNAMIC_REGISTRATION = 36; } // DiscoverResourceMetadata contains common metadata identifying resource type being added. diff --git a/rfd/0000-rfds.md b/rfd/0000-rfds.md index 29a1e2d0965a0..9f2ec6d710adb 100644 --- a/rfd/0000-rfds.md +++ b/rfd/0000-rfds.md @@ -117,7 +117,7 @@ something like the following. ``` # Required Approvers * Engineering @zmb3 && (@codingllama || @nklaassen ) -* Security @reed +* Security: (@reedloden || @jentfoo) * Product: (@xinding33 || @klizhentas ) ``` diff --git a/rfd/0007-rbac-oss.md b/rfd/0007-rbac-oss.md index 7219f4e263c9c..33848f7780105 100644 --- a/rfd/0007-rbac-oss.md +++ b/rfd/0007-rbac-oss.md @@ -36,7 +36,7 @@ Some access workflows plugins will become available in the open source: * JIRA Plugin * PagerDuty Plugin -### Enteprise Only Features +### Enterprise Only Features The following features will remain enterprise only. diff --git a/rfd/0008-application-access.md b/rfd/0008-application-access.md index 2baf3ef267c86..89ec8b837a3fd 100644 --- a/rfd/0008-application-access.md +++ b/rfd/0008-application-access.md @@ -247,7 +247,7 @@ app_service: # from the server running the app_service. uri: http://localhost:8080 # Public address is used to override the default address the application - # is avaiable at. + # is available at. public_addr: jenkins.acme.teleport.dev # Insecure skip verify is used to disable server TLS certificate # verification. Useful for internal applications using a self signed @@ -288,7 +288,7 @@ $ teleport start \ --app-name="jenkins" \ # Define the URI that the application is running at. --app-uri="http://localhost:8080" \ - # Define the public address, used to overide the automatically generated + # Define the public address, used to override the automatically generated # address of appName.proxyPublicAddr. --app-public-addr="jenkins.acme.teleport.dev" ``` diff --git a/rfd/0010-api.md b/rfd/0010-api.md index 5fccfa605c2ba..85be98f5e0187 100644 --- a/rfd/0010-api.md +++ b/rfd/0010-api.md @@ -443,7 +443,7 @@ func main() { clt, err := client.New(...) ... someHandler := func(ctx context.Context, req types.AccessRequest) (updateParams types.AccessRequestUpdate, err error) { - clt.SumbitAccessReview(...) + clt.SubmitAccessReview(...) // if updateParams are nil, the router won't try to run SetAccessRequestState. return nil, nil } diff --git a/rfd/0011-database-access.md b/rfd/0011-database-access.md index e10384be6dc04..55f8895471ca5 100644 --- a/rfd/0011-database-access.md +++ b/rfd/0011-database-access.md @@ -63,9 +63,9 @@ and so on. For the initial release we're focusing on supporting a single type of database, PostgreSQL, with protocol parsing. -Supported procotols: +Supported protocols: -* PostgreSQL [wire procotol version 3.0](https://www.postgresql.org/docs/13/protocol.html), +* PostgreSQL [wire protocol version 3.0](https://www.postgresql.org/docs/13/protocol.html), implemented in PostgreSQL 7.4 and later. Supported authentication models: diff --git a/rfd/0015-2fa-management.md b/rfd/0015-2fa-management.md index 19dfc50714061..28df9d084117b 100644 --- a/rfd/0015-2fa-management.md +++ b/rfd/0015-2fa-management.md @@ -67,7 +67,7 @@ $ tsh login --login=sso-user --auth=oidc ```sh $ tsh mfa ls -MFA deivice name Type Added at Last used +MFA device name Type Added at Last used ---------------- ---- ------------------------------- ------------------------------- android OTP OTP Tue 08 Dec 2020 01:29:42 PM PST Tue 15 Dec 2020 01:29:42 PM PST yubikey U2F Wed 09 Dec 2020 02:00:13 PM PST Wed 16 Dec 2020 02:00:13 PM PST @@ -92,7 +92,7 @@ Enter the OTP code generated by your OTP app: 123456 MFA device "my-otp-app" added. $ tsh mfa ls -MFA deivice name Type Added at Last used +MFA device name Type Added at Last used ---------------- ---- ------------------------------- ------------------------------- android OTP OTP Tue 08 Dec 2020 01:29:42 PM PST Tue 15 Dec 2020 01:29:42 PM PST yubikey U2F Wed 09 Dec 2020 02:00:13 PM PST Wed 16 Dec 2020 02:00:13 PM PST @@ -110,7 +110,7 @@ Tap any *registered* security key or enter an OTP code: MFA device "android OTP" removed. $ tsh mfa ls -MFA deivice name Type Added at Last used +MFA device name Type Added at Last used ---------------- ---- ------------------------------- ------------------------------- solokey U2F Wed 16 Dec 2020 02:05:46 PM PST Wed 16 Dec 2020 02:05:46 PM PST my-otp-app OTP Wed 16 Dec 2020 02:06:46 PM PST Wed 16 Dec 2020 02:06:46 PM PST diff --git a/rfd/0016-dynamic-configuration.md b/rfd/0016-dynamic-configuration.md index fbbbf2d1c633c..9440ae7434923 100644 --- a/rfd/0016-dynamic-configuration.md +++ b/rfd/0016-dynamic-configuration.md @@ -25,7 +25,7 @@ by deriving it from static configuration during auth server initialization. This RFD allows the dynamic configuration to be created/updated explicitly via `tctl`, independently of static configuration, for two main reasons: -1. To faciliate automated/programmatic management of Teleport clusters. +1. To facilitate automated/programmatic management of Teleport clusters. 2. To add configuration capability to Teleport-as-a-service offerings that do not allow direct access to `teleport.yaml` files. @@ -65,7 +65,7 @@ ensure backward compatibility with the already established workflows. #### Choice 2.A In this option, dynamic-configuration resources are understood to exist only -if they have been committed as a result of having been specified in static +if they have been comitted as a result of having been specified in static configuration or via `tctl create`. 3. The command `tctl get cap` would therefore return an error saying diff --git a/rfd/0019-event-iteration-api.md b/rfd/0019-event-iteration-api.md index 3b2f86fade67e..a9c7a76146d5c 100644 --- a/rfd/0019-event-iteration-api.md +++ b/rfd/0019-event-iteration-api.md @@ -95,7 +95,7 @@ Handling of these new GRPC endpoints will be implemented in [lib/auth/grpcserver #### Backends 3.3.2 -Performed some investigative work on how we can implement this pagination support in the different backends. The standard solution that works with the current backend interface is to simply query all events in a given date range from the backend and then filter out the paginated section using the given start key and limit. This is however something we want to avoid as it involves a lot of uneeded processing on every request by the authentication server. +Performed some investigative work on how we can implement this pagination support in the different backends. The standard solution that works with the current backend interface is to simply query all events in a given date range from the backend and then filter out the paginated section using the given start key and limit. This is however something we want to avoid as it involves a lot of unneeded processing on every request by the authentication server. Pagination support will needed to be added to the `IAuditLog` interface defined in `lib/events/api.go`. This involves adding a start key function parameter and an optional last key parameter in the return value of the `SearchEvents` and `SearchSessionEvents` methods. diff --git a/rfd/0021-cluster-routing.md b/rfd/0021-cluster-routing.md index 96220f73e2db9..ccf6e8f55c388 100644 --- a/rfd/0021-cluster-routing.md +++ b/rfd/0021-cluster-routing.md @@ -33,7 +33,7 @@ This would allow users to use `tsh ssh -J clusterName serverName` to work on mul ## Actual Implementation: cluster name inferred from jumphost cert -To address Problem 1, we should not use the jumphost address as such to establish the cluster name. Instead, the host certificate presented while connceting to the jumphost should be analyzed and if it indeed belongs to a Teleport proxy, it should come with an [extension containing its cluster name](https://github.com/gravitational/teleport/blob/026d3419c2454163678de9b43d5c69b81702fb7f/lib/auth/native/native.go#L225). From that it should be reliably known which `RouteToCluster` value is expected of the user certificate, and request its re-issue if it is not available. +To address Problem 1, we should not use the jumphost address as such to establish the cluster name. Instead, the host certificate presented while connecting to the jumphost should be analyzed and if it indeed belongs to a Teleport proxy, it should come with an [extension containing its cluster name](https://github.com/gravitational/teleport/blob/026d3419c2454163678de9b43d5c69b81702fb7f/lib/auth/native/native.go#L225). From that it should be reliably known which `RouteToCluster` value is expected of the user certificate, and request its re-issue if it is not available. However, Problem 2 still remains. It seems unavoidable to simply ignore the `-J` override for the needed cert re-issual request. Ignoring `-J` would presumably make the client fall back to the root proxy capable of issuing an SSH certificate pointing to the leaf cluster. diff --git a/rfd/0024-dynamo-event-overflow.md b/rfd/0024-dynamo-event-overflow.md index cf43d19fa778e..d00d46bf17c03 100644 --- a/rfd/0024-dynamo-event-overflow.md +++ b/rfd/0024-dynamo-event-overflow.md @@ -23,7 +23,7 @@ Another concern is that allowing a single partition to contain too many events m Currently we do not store a suitable field on audit event entries in Dynamo to partition on. This means that we need to add a new field storing the date of the event in the string format `yyyy-mm-dd`. I propose to create a new Global Secondary index that is identical to existing one except it shall partition on the date key instead of the namespace key. A scheme like this will prevent size blowup on a single partition and instead spread long term data over multiple partitions. -Searching over this new Global Secondary Index is trival since the partition keys are simply a set of dates which can be generated easily from the start and end timestamps provided internally. We generate a set of partition keys to search over and include it in the query. +Searching over this new Global Secondary Index is trivial since the partition keys are simply a set of dates which can be generated easily from the start and end timestamps provided internally. We generate a set of partition keys to search over and include it in the query. Since the new date field will not exist on all past events on existing deployments by default, the Teleport auth server will need to go back and retroactively calculate and add this field to all past events. This will be done as a once-off background task created when the DynamoDB backend is created. The consequence of this is that past events will not be visible or searchable until this field as been added but due to the background process they will appear quickly again. diff --git a/rfd/0026-custom-approval-conditions.md b/rfd/0026-custom-approval-conditions.md index 189cac15b49b6..89a43f452eb86 100644 --- a/rfd/0026-custom-approval-conditions.md +++ b/rfd/0026-custom-approval-conditions.md @@ -39,7 +39,7 @@ to only approve certain access requests (e.g. members of team `dev` may be permi approve access requests for `dev`, without necessarily being able to approve access requests for `admin`). -- Support sufficiently granular confuguration to allow scenarios such as "requires 2 dev +- Support sufficiently granular configuration to allow scenarios such as "requires 2 dev approvals or 1 admin approval" or "requires 2 dev approvals, but may be denied by any non-contractor". @@ -137,7 +137,7 @@ spec: ``` Within the above framework, we can model the current default behavior of access requests -(applying the first proposed state-transition immmediately) like so: +(applying the first proposed state-transition immediately) like so: ```yaml kind: role @@ -202,10 +202,10 @@ state once whatever conditions that plugin cares about happen to be met. With more users being potentially eligible to approve requests, it may become necessary to provide hints for who should be reviewing a given request. While it isn't feasible to -direcly notify a user via teleport (non-static users are lazily created on login), that +directly notify a user via teleport (non-static users are lazily created on login), that doesn't stop us from supporting a loose concept of *suggested* reviewers. -When a user generates an access request, we can accept a list of arbitrary strings identiying +When a user generates an access request, we can accept a list of arbitrary strings identifying suggested reviewers: ``` @@ -259,7 +259,7 @@ Given that annotations are of the form `map[string][]string`, the annotations of reviews could theoretically be "summed" (e.g. `{"hello": ["world"]}` and `{"hello": ["there"]}` become `{"hello": ["there","world"]}`). This isn't a perfect solution, as it would prevent users from treating the order of annotations as meaningful, but that may be for the best. -They were never intended to be meaninfully ordered, and this same kind of summing already +They were never intended to be meaningfully ordered, and this same kind of summing already happens elsewhere. @@ -321,7 +321,7 @@ they would resolve to (i.e. an approval for a specific set of roles counts only request state with that exact set of roles). Taking this strategy, the access request in the above example would remain in a `PENDING` state because `alice`, `bob`, and `carol` effectively proposed three separate possible outcomes. Each possible outcome only has one supporting review, failing -to meet the threshold of `2`. This doesn't elminate the possibility of nondeterminism due to ordering +to meet the threshold of `2`. This doesn't eliminate the possibility of nondeterminism due to ordering (after all, 4 people voting for two possible states still results in a race), but it does ensure that no `APPROVED` state is reached that wasn't exactly supported by the requisite number of approvals. Whether this is better than simply disabling the role override feature in the @@ -339,7 +339,7 @@ a request for `staging` *and* `prod`, can `alice` approve the request if she sub `staging`? She obviously can't approve for `prod`, but granting `bob` access to `staging` is within her power. If so, can she *deny* the request? It would be strange to have someone with no explicit permissions allowing them to control access to `prod` be able to indirectly deny it. On the other -hand, if a given user is allowed to control acces to a role, it seems equally strange to have them +hand, if a given user is allowed to control access to a role, it seems equally strange to have them be powerless to deny access to the role because of the presence of an unrelated role within the request. diff --git a/rfd/0027-mtls-metrics.md b/rfd/0027-mtls-metrics.md index 410931d6ce2d3..ad60a445b3572 100644 --- a/rfd/0027-mtls-metrics.md +++ b/rfd/0027-mtls-metrics.md @@ -62,7 +62,7 @@ Teleport will still support shipping metrics over the `diag-addr` endpoint for t To use the new metrics service, prometheus will have to be reconfigured to start listening at the new address defined in the config alongside using certs for mtls if needed. -Here are the steps to a simple migration senario: +Here are the steps to a simple migration scenario: - Add a new job to the prometheus config that aims to start listening on the new `metrics_service` endpoint. mTLS is optional. Example: ``` - job_name: 'teleport_new_metrics_service' diff --git a/rfd/0028-cluster-config-resources.md b/rfd/0028-cluster-config-resources.md index 7763a69de8076..67cb7665c217f 100644 --- a/rfd/0028-cluster-config-resources.md +++ b/rfd/0028-cluster-config-resources.md @@ -46,7 +46,7 @@ message ClusterConfig { // if true, connections with expired client certificates will get disconnected bool DisconnectExpiredCert; - // KeepAliveInterval is the interval the server sends keep-alive messsages + // KeepAliveInterval is the interval the server sends keep-alive messages // to the client at. int64 KeepAliveInterval; diff --git a/rfd/0029-account-lifecycle.md b/rfd/0029-account-lifecycle.md index b349fe5979f15..eb39774cd0b26 100644 --- a/rfd/0029-account-lifecycle.md +++ b/rfd/0029-account-lifecycle.md @@ -497,7 +497,7 @@ Please check documentation: https://goteleport.com/docs/cloud/locksmiths for mor * If account owner or locksmith can edit or create roles and users, display notification: ``` -A user can lock the account or cancel it, but has too many privileges and can modifify roles +A user can lock the account or cancel it, but has too many privileges and can modify roles to override protections. To improve security and prevent account loss please add a second account locksmith. diff --git a/rfd/0034-desktop-access-windows.md b/rfd/0034-desktop-access-windows.md index ac456cf2fbfd7..eb2e01e33e1bd 100644 --- a/rfd/0034-desktop-access-windows.md +++ b/rfd/0034-desktop-access-windows.md @@ -160,7 +160,7 @@ Smart card authentication and emulator are described in more detail in RFD 35. #### Username/password Although username and password are the most universal, a major part of -Teleport's value is to provide strong autentication using short-lived +Teleport's value is to provide strong authentication using short-lived certificates. Supporting username and password authentication would weaken the overall security of the system and as such will not be implemented. diff --git a/rfd/0040-webauthn-support.md b/rfd/0040-webauthn-support.md index bad1b6df708e3..f70d484e32e50 100644 --- a/rfd/0040-webauthn-support.md +++ b/rfd/0040-webauthn-support.md @@ -73,7 +73,7 @@ Authentication (also referred simply as Login) follows a similar "challenge -> sign -> verify" protocol. In simple terms, the client requests a [CredentialAssertion](https://pkg.go.dev/github.com/duo-labs/webauthn/protocol#CredentialAssertion) from the server, signs it by calling -[navigartor.credentials.get()](https://developer.mozilla.org/en-US/docs/Web/API/CredentialsContainer/get), +[navigator.credentials.get()](https://developer.mozilla.org/en-US/docs/Web/API/CredentialsContainer/get), and replies with a [CredentialAssertionResponse](https://pkg.go.dev/github.com/duo-labs/webauthn/protocol#CredentialAssertionResponse). Once again, assuming all is well, login is complete. @@ -506,11 +506,11 @@ suggested, only changes to existing messages to accommodate WebAuthn challenges are required. __MFAAuthenticateChallenge__ and __MFAAuthenticateResponse__ must change to -accomodate, respectivelly, a CredentialAssertion and +accommodate, respectively, a CredentialAssertion and CredentialAssertionResponse (modeled as protos). __MFARegisterChallenge__ and __MFARegisterResponse__, similarly to their -authenticate counterparts, must change to accomodate a +authenticate counterparts, must change to accommodate a [CredentialCreation](https://pkg.go.dev/github.com/duo-labs/webauthn/protocol#CredentialCreation) and a [CredentialCreationResponse](https://pkg.go.dev/github.com/duo-labs/webauthn/protocol#CredentialCreationResponse) diff --git a/rfd/0041-aws-node-join.md b/rfd/0041-aws-node-join.md index 62a6c8a2041f8..e6eab6b8335ef 100644 --- a/rfd/0041-aws-node-join.md +++ b/rfd/0041-aws-node-join.md @@ -286,7 +286,7 @@ Response: } ``` -The pkcs7 signature is seperate from the document and looks like: +The pkcs7 signature is separate from the document and looks like: ``` MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAaCAJIAEggHZewog ICJhY2NvdW50SWQiIDogIjI3ODU3NjIyMDQ1MyIsCiAgImFyY2hpdGVjdHVyZSIgOiAieDg2XzY0 diff --git a/rfd/0043-kubeaccess-multiparty.md b/rfd/0043-kubeaccess-multiparty.md index 89651ace5762c..d4e5b9af26cb1 100644 --- a/rfd/0043-kubeaccess-multiparty.md +++ b/rfd/0043-kubeaccess-multiparty.md @@ -372,7 +372,7 @@ and session types that the role grants privileges to join. We will only initially support the modes `moderator` for Kubernetes Access and `peer` for SSH sessions. An `observer` mode also exists which only grants access to view but does not terminate an ongoing session. -This RBAC model replaces the existing RBAC model for accessing SSH sessions. The existing model allows you to join all sessions to a node that you have login access to. If this is kept, this new RBAC model becomes inflexible as it is no longer possible to configure observers or moderators that do not themselves have access to start a session. The pratical implication of this is that we no longer perform RBAC authorization at the node level when joining sessions, but instead deferring all authorization duties to the downstream authorizer for the session. +This RBAC model replaces the existing RBAC model for accessing SSH sessions. The existing model allows you to join all sessions to a node that you have login access to. If this is kept, this new RBAC model becomes inflexible as it is no longer possible to configure observers or moderators that do not themselves have access to start a session. The practical implication of this is that we no longer perform RBAC authorization at the node level when joining sessions, but instead deferring all authorization duties to the downstream authorizer for the session. Imagine you have 4 roles: - `prod-access` diff --git a/rfd/0046-database-access-config.md b/rfd/0046-database-access-config.md index fa83487ab3b8c..db7a4bdb45fe3 100644 --- a/rfd/0046-database-access-config.md +++ b/rfd/0046-database-access-config.md @@ -266,7 +266,7 @@ AWS Confirm actions? [y/N] y ✅[AWS] Creating IAM Policy "DatabaseAccess"... done. -✅[AWS] Creating IAM policy boundary "DatabaseAcessBoundary"... done. +✅[AWS] Creating IAM policy boundary "DatabaseAccessBoundary"... done. ✅[AWS] Attaching IAM policy and boundary to IAM user "SampleUser"... done. ``` @@ -286,7 +286,7 @@ AWS 3. Attach policy and boundary to "SampleUser". ✅[AWS] Creating IAM Policy "DatabaseAccess"... done. -✅[AWS] Creating IAM policy boundary "DatabaseAcessBoundary"... done. +✅[AWS] Creating IAM policy boundary "DatabaseAccessBoundary"... done. ✅[AWS] Attaching IAM policy and boundary to IAM user "SampleUser"... done. ``` @@ -338,7 +338,7 @@ AWS Confirm actions? [y/N] y ✅[AWS] Creating IAM Policy "DatabaseAccess"... done. -✅[AWS] Creating IAM policy boundary "DatabaseAcessBoundary"... done. +✅[AWS] Creating IAM policy boundary "DatabaseAccessBoundary"... done. ❌[AWS] Attaching IAM policy and boundary to IAM user "SampleUser"... failed. Failure reason: unable to find user "SampleUser". ``` diff --git a/rfd/0047-drop-vendor.md b/rfd/0047-drop-vendor.md index e9226d93a90e4..34e5565bc1c09 100644 --- a/rfd/0047-drop-vendor.md +++ b/rfd/0047-drop-vendor.md @@ -77,7 +77,7 @@ running our own proxy is warranted. Committing the vendor directory has led to a number of minor issues, and dropping it would result in an improved developer experience. -First, comitting these changes results in repo bloat. Since go.mod and go.sum +First, committing these changes results in repo bloat. Since go.mod and go.sum already contain everything needed for a reproducible build, the teleport repo ends up being larger than necessary. This causes clone operations to take longer, increases the time taken for CI builds, etc. @@ -97,7 +97,7 @@ which is difficult to maintain in a cross-platform way, and has broken [gopls integration](https://github.com/gravitational/teleport/blob/30effc1f08b6a699772ff22f79ebe756fe1a1e34/Makefile#L942-L952) a common tool used in Go development environments. -Lastly, there is no guarantee that the code committed to vendor actually +Lastly, there is no guarantee that the code comitted to vendor actually reflects the contents of go.mod. The onus is on the developer to remember to run `make update-vendor` and commit the results after making changes to dependencies. This has created several cases of confusing build results amongst diff --git a/rfd/0048-desktop-access-session-recording.md b/rfd/0048-desktop-access-session-recording.md index 2440263de9e6a..3ecb32d56fdf8 100644 --- a/rfd/0048-desktop-access-session-recording.md +++ b/rfd/0048-desktop-access-session-recording.md @@ -166,7 +166,7 @@ events. In order to support a new type of session recording, a new audit event will be defined for capturing desktop session data. The `DesktopRecordingEvent` will -implement the `AuditEvent` interface and is analagous to the `PrintEvent` used +implement the `AuditEvent` interface and is analogous to the `PrintEvent` used for SSH recordings. ```protobuf @@ -185,7 +185,7 @@ message DesktopRecordingEvent { } ``` -The `Message` field contains an artibrary TDP-encoded message, as defined in +The `Message` field contains an arbitrary TDP-encoded message, as defined in [RFD 0037](./0037-desktop-access-protocol.md). The vast majority of these events will contain TDP message 2 (PNG frame), but we will also capture: @@ -207,7 +207,7 @@ bitmaps sent from the Windows desktop do _not_ include the mouse cursor When this new event is introduced, several parts of the code that assume `SessionPrintEvent` is the only type of session recording will need to be -udpated: +updated: - `AuditWriter` assumes every `[]byte` it receives needs to be packaged up into a `SessionPrintEvent`. We will extend `AuditWriterConfig` with a new field: @@ -215,7 +215,7 @@ udpated: configure `AuditWriter` to produce `SessionPrintEvent`s, while allowing desktop sessions to emit `DesktopRecordingEvent`s. - `AuditWriter` does some tracking of `lastPrintEvent` in order to update - timestamps and chunk indicdes. This needs to be generalized to be able to + timestamps and chunk indices. This needs to be generalized to be able to track the last event in desktop mode as well. - The `TeeStreamer` that filters out `SessionPrintEvent`s needs to be updated to also filter out `DesktopRecordingEvent`s, as there will be a large number of diff --git a/rfd/0049-desktop-clipboard.md b/rfd/0049-desktop-clipboard.md index c3d09ef489767..12a2210238358 100644 --- a/rfd/0049-desktop-clipboard.md +++ b/rfd/0049-desktop-clipboard.md @@ -7,7 +7,7 @@ state: implemented ## What -This RFD defines the high-level goals and architecture for supporing copy and +This RFD defines the high-level goals and architecture for supporting copy and paste between a Teleport user's workstation and a remote Windows Desktop. ## Details @@ -34,7 +34,7 @@ An RDP connection contains a number of "virtual channels" that provide separate logical streams of data. Today, we use the _global_ channel for desktop data, and the _device redirection_ ("RDPDR") channel to implement a smart card emulator. -Clipboard support for RDP is implmemented as a series of messages sent over a +Clipboard support for RDP is implemented as a series of messages sent over a dedicated virtual channel called "CLIPRDR". The details of the protocol are specified in [MS-RDPECLIP](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpeclip/fb9b7e0b-6db4-41c2-b83c-f889c1ee7688). @@ -171,7 +171,7 @@ will extend support for other browsers if and when they support the required API ### Implementation Notes -Since the TDP protocol does not implement the delayed rendering appraoch used by +Since the TDP protocol does not implement the delayed rendering approach used by RDP, the Windows Desktop Service will be responsible for storing and synchronizing the state of the clipboard between the local workstation and the remote Windows desktop. @@ -205,7 +205,7 @@ the Teleport backend of the latest data. The general sequence of operations is: In this scenario, the user copies text in the remote Windows Desktop, and expects that data to end up on their system clipboard. The sequence is: -- The Windows Desktop sends a notfication to the Windows Desktop Service that +- The Windows Desktop sends a notification to the Windows Desktop Service that new data has been copied to the clipboard. - The Teleport Windows Desktop Service fakes a "paste" operation by immediately responding with a request for the clipboard data. diff --git a/rfd/0055-webui-ss-paginate-filter.md b/rfd/0055-webui-ss-paginate-filter.md index fef0b92bbe6f1..0c488440aa0d1 100644 --- a/rfd/0055-webui-ss-paginate-filter.md +++ b/rfd/0055-webui-ss-paginate-filter.md @@ -146,7 +146,7 @@ type Sort struct { ### Filter: Fuzzy Search -The fields we allow for searching should be mostly the same as sorting. Search values will be stored as simple list of strings that will be iterated through select resource fields to look for a fuzzy match, ignoring case and order. If a user wants to search a phrase, they must supply the phrase within paranthesis. +The fields we allow for searching should be mostly the same as sorting. Search values will be stored as simple list of strings that will be iterated through select resource fields to look for a fuzzy match, ignoring case and order. If a user wants to search a phrase, they must supply the phrase within parentheses. | Search Examples | Interpretation | |-----------------|--------------------| diff --git a/rfd/0057-automatic-aws-server-discovery.md b/rfd/0057-automatic-aws-server-discovery.md index 7f2e81b8315b3..25991823c2519 100644 --- a/rfd/0057-automatic-aws-server-discovery.md +++ b/rfd/0057-automatic-aws-server-discovery.md @@ -148,7 +148,7 @@ SSM commands[3] for example: "Resource": [ # Allow running commands on all us-west-2 instances "arn:aws:ssm:us-west-2:*:instance/*", - # Allows running the installTeleport docuemnt on the allowed instances + # Allows running the installTeleport document on the allowed instances "arn:aws:ssm:us-east-2:aws-account-ID:document/installTeleport" ] }, @@ -168,8 +168,8 @@ SSM commands[3] for example: } ``` -The machines being discovered will need to allow recieving `ec2messages` in -order to recieve the SSM commands: +The machines being discovered will need to allow receiving `ec2messages` in +order to receive the SSM commands: ```js { @@ -190,7 +190,7 @@ instances a new system role will be added -- `RoleNodeDiscovery`, that will have permissions to create tokens. Each EC2 instance that is to be discovered will also require that they have an IAM -role attached, in order to be able to send and recieve messages for the SSM agent. +role attached, in order to be able to send and receive messages for the SSM agent. Example: @@ -459,7 +459,7 @@ The SSH discovery node should have permission to call `ec2:DescribeInstances` ], "Effect": "Allow", "Resource": [ - "*", # for example, allow on all ec2 instance with SSM availablea + "*", # for example, allow on all ec2 instance with SSM available ] } ] diff --git a/rfd/0057-automatic-user-provisioning.md b/rfd/0057-automatic-user-provisioning.md index d63cc4ecb5e2b..69893af60941a 100644 --- a/rfd/0057-automatic-user-provisioning.md +++ b/rfd/0057-automatic-user-provisioning.md @@ -223,7 +223,7 @@ have users be automatically created. ### Teleport user has multiple roles but not all of them enable `create_host_user` -In the situtation where a user has roles as below, the user would not +In the situation where a user has roles as below, the user would not be able to make use of automatically provisioning users as both roles do not enable `create_host_user`. diff --git a/rfd/0058-desktop-file-transfer.md b/rfd/0058-desktop-file-transfer.md index 9fee80f2a69d1..c0ff018f97100 100644 --- a/rfd/0058-desktop-file-transfer.md +++ b/rfd/0058-desktop-file-transfer.md @@ -89,7 +89,7 @@ text, it will be inefficient for large files. Additionally, files transferred via this mechanism are limited to a maximum size of 4GB unless "huge file support" is enabled. This is a protocol extension that -would require extra implmentation to support. +would require extra implementation to support. Lastly, this approach is best suited to one-time transfers of a specific file (copy file, paste on other machine, make some edits, copy and paste the updated @@ -98,7 +98,7 @@ many files available to the remote system simultaneously. ### File System Virtual Channel Extension (RDP Option 2) -The other option for tranferring files over an RDP connection is the file system +The other option for transferring files over an RDP connection is the file system virtual channel extension. This is a feature supported in many native RDP clients. After the connection is established, the RDP client announces a directory it wishes to share. This announcement includes basic metadata about the @@ -115,7 +115,7 @@ copied, opened directly, or even edited. As long as the RDP client responds to the server's various requests, the RDP server takes care of making the remote file behave like any other file. -From an implmentation perspective, this approach is simpler because the client does +From an implementation perspective, this approach is simpler because the client does not need to monitor the directory for changes or alert the server. From an efficiency standpoint, this approach saves on network bandwidth as the RDP @@ -236,7 +236,7 @@ system access API. From a user-experience perspective, the user never has to think about a file "transfer" - they just select a directory and then interact with it from the remote machine. -The biggest drawback with this appraoch is that this API is currently only +The biggest drawback with this approach is that this API is currently only [available](https://caniuse.com/?search=File%20System%20Access) in some Chromium browsers, the most popular being Chrome and Edge (enabling it in Brave requires modifying the setting at brave://flags/#file-system-access-api). However, this diff --git a/rfd/0058-package-distribution.md b/rfd/0058-package-distribution.md index cca13488a6842..3a25f6d897878 100644 --- a/rfd/0058-package-distribution.md +++ b/rfd/0058-package-distribution.md @@ -23,7 +23,7 @@ GCP Artifact Registry is in preview and has not had an update since 11/2021. It' As discussed below, JFrog Artifactory and PackageCloud are non-starters due to their signing key requirements. #### Implementation details and proof of concept -The follwing channel scheme is proposed for APT and YUM with the S3-hosted option: +The following channel scheme is proposed for APT and YUM with the S3-hosted option: APT: `deb https://apt./ non-free//` @@ -36,7 +36,7 @@ A tool implementing the required APT changes is available [here](./build.assets/ ### Future work While a specific solution is outside of the scope of this RFD, it is pertinent to discuss a disaster scenario that is common to all solutions, including the current one. If the hosting solution that contains the repo (i.e. a S3 bucket or GCP Artifact Registry) is deleted then all artifacts must be rebuilt and published from scratch. It looks like the Drone pipeline for Teleport takes around 90 minutes to run. Depending on how many instances can be ran at once without conflicting with each other, it could take several hours to get the repository back online to it's previous state. This could be alleviated by backing up artifacts after they're built, or by backing up the entire hosting solution. -#### Backwards compatiblity +#### Backwards compatibility To maintain backwards compatibility with our current solution we will host both the new and old repos in parallel. We will also remove the old repo from Teleport's documentation, replacing it with the new repo. This will prevent our customers from seeing a breaking change while migrating new users to the new repo. ### Research @@ -60,7 +60,7 @@ Several potential solutions were investigated and their features compared as sho | Supports self hosting | N/A | N/A | Yes | Yes | No | | Has official Terraform provider | N/A | N/A | Yes | No | Yes | | Pricing ($) | N/A | N/A | $700/month | $700/month | $0.1/GB/month stored, $0.09/GB/month egress | -| Notes: | | Can do anything we want, just depends on the amount of initial and reoccuring engineering effort is required | Higher complexity, but supports pretty much any use case we'd ever need | Easy to use but probably not the best solution | Still in preview, not generally available | +| Notes: | | Can do anything we want, just depends on the amount of initial and recurring engineering effort is required | Higher complexity, but supports pretty much any use case we'd ever need | Easy to use but probably not the best solution | Still in preview, not generally available | ### Signing key management JFrog Artifactory and PackageCloud require handing over our repo signing keys to them, which is a non-starter. While GCP requires using their own signing key (which is used for all GCP repositories in a given region), it is assumed that their security is sufficient to protect said key. Lastly, fixing the current solution will keep us in control of the key, but will require us to continuing storing and securing it ourselves. diff --git a/rfd/0059-search-based-access-requests.md b/rfd/0059-search-based-access-requests.md index 4cb1c5f2f04cf..820923ef9f515 100644 --- a/rfd/0059-search-based-access-requests.md +++ b/rfd/0059-search-based-access-requests.md @@ -24,7 +24,7 @@ about roles, just the resource(s) which they need access to. Teleport admins would like to avoid having to create many different custom roles for different levels of access to different sets of resources. -Requesting access to a "blanket" role violates the principle of least priviledge. +Requesting access to a "blanket" role violates the principle of least privilege. Most of the time incident responders only need access to 1 or 2 nodes, search-based access requests will allow them to request access to only the nodes they need. @@ -211,7 +211,7 @@ root@db-1:~$ ### Which roles will be requested When creating a search-based access request the underlying roles being requested -will be determined automaticially. For simplicity, all roles which the user has +will be determined automatically. For simplicity, all roles which the user has permission to search as (included in `search_as_roles` on any of the roles the user has) will be requested. This request will be limited to only the exact resources found in the search, and (if approved) the user will have access to diff --git a/rfd/0065-distributed-tracing.md b/rfd/0065-distributed-tracing.md index 62d3fae18ba71..f8c9e85ef90e9 100644 --- a/rfd/0065-distributed-tracing.md +++ b/rfd/0065-distributed-tracing.md @@ -100,7 +100,7 @@ service boundaries a number of standard propagation mechanisms (W3C Trace Contex to forward the **SpanContext**. For more details on OpenTelemetry Propagators see https://opentelemetry.io/docs/reference/specification/context/api-propagators/. This means that any functions within Teleport that end up making a network call **must** take a `context.Context` as the -first parameter. While this has been idomatic Go regarding network calls for a while, it is not something that Teleport +first parameter. While this has been idiomatic Go regarding network calls for a while, it is not something that Teleport currently abides by. Take the [`auth.ClientI`](https://github.com/gravitational/teleport/blob/632d851783ac5dbd7d1ba60f4fce8c95d81041e3/lib/auth/clt.go#L1925) interface, which is used to abstract making calls to the `auth` service, as an example. If we look at just the first few functions defined within the interface more than half don't take a `context.Context` as the first parameter. diff --git a/rfd/0067-database-access-aws-redis.md b/rfd/0067-database-access-aws-redis.md index 6c78a12c26828..4ec1b0e035ad8 100644 --- a/rfd/0067-database-access-aws-redis.md +++ b/rfd/0067-database-access-aws-redis.md @@ -224,7 +224,7 @@ latest version and the previous version of the secret respectively. Using AWS Secrets Manager does incur extra [costs](https://aws.amazon.com/secrets-manager/pricing/) for users. As an example, let's say that a Teleport cluster with three database agents is -managing one ElastiCache Redis user. The montly cost to store one secret is +managing one ElastiCache Redis user. The monthly cost to store one secret is $0.40. Assuming the secret is rotated 100 times per day, there will be about 100 put calls plus 3 * 100 get calls per secret per day. This sums to 12000 API calls per month, which costs $0.06 ($0.05 for every 10000 calls). Therefore, diff --git a/rfd/0067-desktop-access-file-system-sharing.md b/rfd/0067-desktop-access-file-system-sharing.md index 9327347982009..2e507862741eb 100644 --- a/rfd/0067-desktop-access-file-system-sharing.md +++ b/rfd/0067-desktop-access-file-system-sharing.md @@ -63,7 +63,7 @@ feature more easily in the future. #### `FileId` -A `FileId` is generated by the client upon reciept of a `Device I/O Request` +A `FileId` is generated by the client upon receipt of a `Device I/O Request` where `MajorFunction = IRP_MJ_CREATE` and sent back to the server, which then uses it to denote that file in subsequent `Device I/O Request`s. The `FileId` is valid until the client receives a `Device I/O Request` with `MajorFunction = IRP_MJ_CLOSE`, at which point it becomes invalid and can be recycled. @@ -514,7 +514,7 @@ Our Windows Desktop Service receives the following `Device Read Request` ``` DeviceReadRequest { DeviceIoRequest: DeviceIoRequest { - Header: ommitted, + Header: omitted, DeviceId: 2, FileId: 3, CompletionId: 4, @@ -526,7 +526,7 @@ DeviceReadRequest { } ``` -First we consult our internal cache of psuedo "file handles" for an entry where `FileId = 3`, which would have been created while fielding a previous successful `IRP_MJ_CREATE`. +First we consult our internal cache of pseudo "file handles" for an entry where `FileId = 3`, which would have been created while fielding a previous successful `IRP_MJ_CREATE`. From that we'll grab the `path` corresponding to this file (lets say "example/file.txt"). No we have all the information required to create a TDP `Shared Directory Read Request`: ``` @@ -557,7 +557,7 @@ Which we can then package into an RDP `Device Read Response` ``` DeviceReadResponse { DeviceIoReply: DeviceIoResponse { - Header: ommitted, + Header: omitted, DeviceId: 2, CompletionId: 4, IoStatus: NTSTATUS.STATUS_SUCCESS, @@ -567,7 +567,7 @@ DeviceReadResponse { } ``` -This is clearly the "happy path" for this process, error mode handling thas been ommitted here. Note that not all `MajorFunctions` have such straightforward translations, and some will require multiple TDP messages to complete their translation. +This is clearly the "happy path" for this process, error mode handling that been omitted here. Note that not all `MajorFunctions` have such straightforward translations, and some will require multiple TDP messages to complete their translation. ## Read-Only Mode @@ -582,9 +582,9 @@ To do so, we would extend `Shared Directory Announce` to include a read-only boo When set to true, any mutating TDP Requests such as `Shared Directory Write Request` and `Shared Directory Delete Request` will become invalid. An initial thought might be to have such messages always result in a corresponding Response with a non-null `err_code`, however this would make the read-only guarantee dependent on a friendly client implementation, which we can not rely on. Instead, this read-only setting will -be enforced by the Windows Desktop Service itself: whenever we recieve an RDP message that in non-read-only mode would be translated into a mutating TDP Request, we will send back an RDP error. +be enforced by the Windows Desktop Service itself: whenever we receive an RDP message that in non-read-only mode would be translated into a mutating TDP Request, we will send back an RDP error. -For example, if we recieve an `IRP_MJ_WRITE`, at the point where would ordinarily send a `Shared Directory Write Request` to our client (the browser), we will instead send the Windows machine an RDP `Device I/O Response` with `IoStatus` +For example, if we receive an `IRP_MJ_WRITE`, at the point where would ordinarily send a `Shared Directory Write Request` to our client (the browser), we will instead send the Windows machine an RDP `Device I/O Response` with `IoStatus` set to `STATUS_ACCESS_DENIED`. Realistically this situation might happen if the user is sharing a directory in read-only mode, opens a file in Notepad, changes it and then tries to save it. ## UX @@ -613,7 +613,7 @@ which is to say `Shared Directory Read` ("desktop.directory.read") and `Shared D **TDP Shared Directory Messages Logged** -The following list includes the type of TDP messages that will be logged and their propsed corresponding event names. +The following list includes the type of TDP messages that will be logged and their proposed corresponding event names. - `Shared Directory Announce/Acknowledge` - `Shared Directory ReadRequest/ReadResponse` @@ -685,7 +685,7 @@ a unique identifier. // attempts to read from a file in a shared directory at // the behest of the remote desktop. message DesktopSharedDirectoryRead { - // Metadata, UserMetadata, SessionMetadata, ConnectionMetadata ommitted + // Metadata, UserMetadata, SessionMetadata, ConnectionMetadata omitted // DesktopAddr is the address of the desktop being accessed. string DesktopAddr = 5 [(gogoproto.jsontag) = "desktop_addr"]; @@ -709,7 +709,7 @@ message DesktopSharedDirectoryRead { // attempts to write to a file in a shared directory at // the behest of the remote desktop. message DesktopSharedDirectoryWrite { - // Metadata, UserMetadata, SessionMetadata, ConnectionMetadata ommitted + // Metadata, UserMetadata, SessionMetadata, ConnectionMetadata omitted // DesktopAddr is the address of the desktop being accessed. string DesktopAddr = 5 [(gogoproto.jsontag) = "desktop_addr"]; @@ -750,7 +750,7 @@ In the case of copying files in/out of the shared directory, that program is typ 1MB (exactly 2^20 bytes) at a time. For files larger than 1MB, this manifests as several read/writes in a row showing up at time in such a case. In order to avoid the logs getting spammed with multiple messages corresponding to one "operation" (such as a multi MB read of a large file), we can create -an algorithm which upon reciept of a read or write event, sets a short timer (say 1s), and if it receives another non-overlapping read/write for the same +an algorithm which upon receipt of a read or write event, sets a short timer (say 1s), and if it receives another non-overlapping read/write for the same file before the timer runs out, stops the timer and amalgamates the events together, repeating until the timer runs out at which point a single event is written into the log (credit to @zmb3 for the algo design). diff --git a/rfd/0069-proxy-peering.md b/rfd/0069-proxy-peering.md index a9c6c0526910e..1a2605062512f 100644 --- a/rfd/0069-proxy-peering.md +++ b/rfd/0069-proxy-peering.md @@ -103,7 +103,7 @@ type ProxyClient interface { The api will use mTLS to ensure that only other proxies are able to connect. This is done by checking certificates for the built-in role “Proxy”. This will prevent users from connecting to the service directly without going through the user-proxy logic of authorization and session recording. ### Enabling Proxy Peering -This feature will need to be explicity configured to use it. The configuration will be set in the auth_service section of the teleport config and will update the `ClusterNetworkingConfig` stored in the backend. +This feature will need to be explicitly configured to use it. The configuration will be set in the auth_service section of the teleport config and will update the `ClusterNetworkingConfig` stored in the backend. The configuration option will be called `tunnel_strategy`. This will take a `type` and each `type` can support its own custom parameters. This gives us flexibility to support future strategies. The default will be `type: agent_mesh` which is equivalent to the current node dialing behavior. @@ -192,14 +192,14 @@ Metrics will be added so we can monitor whether a single grpc connection becomes With these metrics we will be able to see if throughput begins to flatten as more streams are being used. If this does become an issue additional tcp connections will need to be created. ### Reverse Tunnel Agent Pool -Changes to the reverse tunnel agent and agent pool are required to support this design. The existing implementation creates a connection to every proxy. The new impelementation will decide how many connections to create dynamically based on the `ClusterNetworkingConfig`. If the `proxy_peering` tunnel strategy is configured the agent will try to create the configured number of connections. If the `agent_mesh` tunnel strategy is configured then a connection to every proxy will be created. Old agents can continue connecting to every proxy regardless of the tunnel strategy. +Changes to the reverse tunnel agent and agent pool are required to support this design. The existing implementation creates a connection to every proxy. The new implementation will decide how many connections to create dynamically based on the `ClusterNetworkingConfig`. If the `proxy_peering` tunnel strategy is configured the agent will try to create the configured number of connections. If the `agent_mesh` tunnel strategy is configured then a connection to every proxy will be created. Old agents can continue connecting to every proxy regardless of the tunnel strategy. -As mentioned above the `proxy_peering` tunnel strategy will have a default `agent_connection_count: 1`. This is more likedly to lead to unavailability to a subset of agents during network partitions and cluster upgrades. To help minimize this a higher `agent_connection_count` can be configured to increase the likelyhoood that an agent is reachable during these events. +As mentioned above the `proxy_peering` tunnel strategy will have a default `agent_connection_count: 1`. This is more likely to lead to unavailability to a subset of agents during network partitions and cluster upgrades. To help minimize this a higher `agent_connection_count` can be configured to increase the likelihood that an agent is reachable during these events. The `proxy_peering` strategy with a fixed `agent_connection_count` is an improvement over the `agent_mesh` strategy as it allows proxy servers to scale up without impacting the number of connections agents maintain. ### Trusted Clusters -Leaf clusters will continue connecting to all proxies in the root cluster. Supporting trusted clusters would add a non-trivial amount of work and complexity to this design and provides diminishing returns. It is expected that trusted clusters will not be connected at the same scale as other resouces like ssh nodes and therefore will not be a big contributer to the problems we are trying to address here. +Leaf clusters will continue connecting to all proxies in the root cluster. Supporting trusted clusters would add a non-trivial amount of work and complexity to this design and provides diminishing returns. It is expected that trusted clusters will not be connected at the same scale as other resources like ssh nodes and therefore will not be a big contributor to the problems we are trying to address here. ### Cluster Upgrade Upgrading a cluster to support this feature will require reconfiguration the auth service as follows: @@ -229,7 +229,7 @@ These failures will be presented to the client as follows: 3 and 4 will have the same behavior as a node agent disconnecting unexpectedly with the current implementation. This results in an [ExitMissingError](https://pkg.go.dev/golang.org/x/crypto/ssh#ExitMissingError) being displayed client side. ### TLS Routing -Load balancers between the agent and proxy servers may want to diffentiate between old agents that need to connect to every proxy and the new agents described in this document. This is important for geo distributed deployments to ensure low latency routing. +Load balancers between the agent and proxy servers may want to differentiate between old agents that need to connect to every proxy and the new agents described in this document. This is important for geo distributed deployments to ensure low latency routing. The cluster must be configure with `proxy_listener_mode: multiplex` to enable TLS ALPN routing. New agents will add an additional protocol `teleport-reversetunnelv2` to the ALPN header field resulting in the following list: `["teleport-reversetunnelv2", "teleport-reversetunnel"]`. @@ -238,7 +238,7 @@ Preserving `teleport-reversetunnel` in the list of protocols, ensures that new a ## Alternative Considerations ### Node Tracker -The orginal proposal included a separate service for tracking which proxy each node was connected to. This was ultimately decided against. The service was proposed to target scalability goals that need to be addressed in other parts of the system first. Given these limitations a simpler design was chosen to benefit the most customers. Further discussions on the node tracker proposal can be found [here](https://github.com/gravitational/teleport/pull/9121). +The original proposal included a separate service for tracking which proxy each node was connected to. This was ultimately decided against. The service was proposed to target scalability goals that need to be addressed in other parts of the system first. Given these limitations a simpler design was chosen to benefit the most customers. Further discussions on the node tracker proposal can be found [here](https://github.com/gravitational/teleport/pull/9121). ### Client Redirect -An alternative approach was considered to redirect clients to the corresponding node-proxy. This was ultimately disregarded for a couple of reasons. It increases the time to establish a session for the client as a client would need to dial and authenticate with two proxies. Proxies would need to be individually addressible by the client which makes them an easier targets for DDOS attacks. +An alternative approach was considered to redirect clients to the corresponding node-proxy. This was ultimately disregarded for a couple of reasons. It increases the time to establish a session for the client as a client would need to dial and authenticate with two proxies. Proxies would need to be individually addressable by the client which makes them an easier targets for DDOS attacks. diff --git a/rfd/0073-idp-initiated-login.md b/rfd/0073-idp-initiated-login.md index 93380c9b3a3f2..624ed0cd5ac34 100644 --- a/rfd/0073-idp-initiated-login.md +++ b/rfd/0073-idp-initiated-login.md @@ -3,7 +3,7 @@ authors: Joel Wejdenstal state: draft --- -# RFD 73 - RFD 73 - IdP-initated Login Flows for SAML and OIDC +# RFD 73 - RFD 73 - IdP-initiated Login Flows for SAML and OIDC ## Required Approvers diff --git a/rfd/0073-public-image-registry.md b/rfd/0073-public-image-registry.md index 9abe78af3bbb0..7381e92f211a1 100644 --- a/rfd/0073-public-image-registry.md +++ b/rfd/0073-public-image-registry.md @@ -12,7 +12,7 @@ Teleport images are currently hosted on [Quay.io](https://quay.io/organization/g As of August 1st, 2021 Quay.io no longer supports any other authentication provider other than Red Hat Single-Sign On. Users in the Gravitational organization on Quay must be manually removed when they leave Teleport which presents a potential security risk. Migrating to Amazon ECR will consolidate our infrastructure while also improving security with support for IAM policies and our existing SSO infrastructure. ## Details -Teleport will use Amazon ECR and Amazon ECR Public in order to host public container images. The multiple stage registry pipeline will allow Teleport to test and verify images internally before promoting them to our customers. As of authoring this RFD, Amazon ECR Public lacks support for [vulnerability scanning and tag immutabilty](https://github.com/aws/containers-roadmap/issues/1288). The two-stage pipeline will allow us to leverage these features in the internal repository before pushing to the public. +Teleport will use Amazon ECR and Amazon ECR Public in order to host public container images. The multiple stage registry pipeline will allow Teleport to test and verify images internally before promoting them to our customers. As of authoring this RFD, Amazon ECR Public lacks support for [vulnerability scanning and tag immutability](https://github.com/aws/containers-roadmap/issues/1288). The two-stage pipeline will allow us to leverage these features in the internal repository before pushing to the public. **What about name squatting on other container registry platforms?** @@ -78,7 +78,7 @@ Gold standard images (teleport, teleport-ent, etc...) will continue to exist and ### Alternatives #### Self hosted with Docker Distribution or Harbor -The [Docker Distribution](https://github.com/distribution/distribution) registry is an open source implementation of the [OCI Distribution](https://github.com/opencontainers/distribution-spec) specification. [Harbor](https://goharbor.io/) is an OSS, _all-in-one_, registry solution that is built on top of the docker registry. Harbor has a built-in UI, support for OIDC authenticatio and many more [additional features](https://goharbor.io/docs/2.5.0/). +The [Docker Distribution](https://github.com/distribution/distribution) registry is an open source implementation of the [OCI Distribution](https://github.com/opencontainers/distribution-spec) specification. [Harbor](https://goharbor.io/) is an OSS, _all-in-one_, registry solution that is built on top of the docker registry. Harbor has a built-in UI, support for OIDC authentication and many more [additional features](https://goharbor.io/docs/2.5.0/). While Harbor provides a maximally featured container registry solution, it also incurs an increased operational overhead that Teleport didn't have with Quay.io. diff --git a/rfd/0074-sftp-support.md b/rfd/0074-sftp-support.md index f1189ac799f83..a27108af0e092 100644 --- a/rfd/0074-sftp-support.md +++ b/rfd/0074-sftp-support.md @@ -44,7 +44,7 @@ the `ssh_server` yaml config to control whether scp and sftp will be enabled or for per node. 2. Adding SFTP protocol support to `tsh scp` sub command, and -ensuring SFTP trasfers work on the web UI. `tsh scp` will continue to use the scp/rcp +ensuring SFTP transfers work on the web UI. `tsh scp` will continue to use the scp/rcp protocol by default for backwards compatibility, but the SFTP protocol can be optionally enabled with the `-s` flag. diff --git a/rfd/0075-snowflake-support.md b/rfd/0075-snowflake-support.md index 05d83432483af..f1302db2c8f70 100644 --- a/rfd/0075-snowflake-support.md +++ b/rfd/0075-snowflake-support.md @@ -66,7 +66,7 @@ sequenceDiagram participant Teleport participant Snowflake client->>Teleport: HTTP /session/v1/login-request - Teleport->>Teleport: RBAC check, genererate and replace JWT + Teleport->>Teleport: RBAC check, generate and replace JWT Teleport->>Snowflake: HTTP /session/v1/login-request Snowflake->>Teleport: Authentication Token Teleport->>Teleport: Generate Teleport Auth token diff --git a/rfd/0078-login-rules.md b/rfd/0078-login-rules.md index f9fb1682bcfcc..6b78c4d87b932 100644 --- a/rfd/0078-login-rules.md +++ b/rfd/0078-login-rules.md @@ -23,13 +23,13 @@ All of these claims will be embedded in each user's Teleport certificate as `traits`. SSO providers can include hundreds to thousands of claims which are not -necessary or used by Teleport at all, unecessarily bloating the certificate size +necessary or used by Teleport at all, unnecessarily bloating the certificate size of all users. -This can adversly impact latency and throughput of Teleport operations, and in +This can adversely impact latency and throughput of Teleport operations, and in an extreme case could reach a limit which would render Teleport unusable with that identity provider. Login rules will allow the Teleport admin to filter out claims which are -unecessary before they make it into the Teleport certificate. +unnecessary before they make it into the Teleport certificate. Admins often wish to extend an incoming claim with extra values which make sense for their Teleport deployment. @@ -145,7 +145,7 @@ with static keys. The `traits_expression` could also be generated by an external tool such as an upcoming project where traits could be modelled in an external language with -support for theorom proving and formal verification. +support for theorem proving and formal verification. The `traits_map` is a more approachable syntax for Teleport admins. It is mostly YAML with some reference to external traits which should be @@ -541,7 +541,7 @@ message GetLoginRuleRequest { // ListLoginRulesRequest is a paginated request to list all login rules. message ListLoginRulesRequest { // PageSize is The maximum number of login rules to return in a single - // reponse. + // response. int32 page_size = 1; // PageToken is the NextPageToken value returned from a previous @@ -600,7 +600,7 @@ expressions to determine which roles a user should receive. This will give us a complete system for solving the problem of incomplete data coming from identity providers. -This could use a new field in the currenlty proposed login rule yaml spec. +This could use a new field in the currently proposed login rule yaml spec. We would need to figure out how to merge roles coming from multiple login rules and from SAML and OIDC connectors before adding this. diff --git a/rfd/0079-oidc-joining.md b/rfd/0079-oidc-joining.md index 3269e353d4601..4c8f41be432ff 100644 --- a/rfd/0079-oidc-joining.md +++ b/rfd/0079-oidc-joining.md @@ -37,7 +37,7 @@ The work on OIDC joining is broken down into two parts: - Support in the Auth server for trusting an OP issued JWT. This work will have a general base that can be shared between all providers, and then have a small, provider-specific part to provide additional validation that lets us guide users to correct and safe configurations. - Support in nodes for fetching their workload identity token from their environment. This work will be specific to each platform we intend to support. -OIDC supports multiple types of token (`id_token`: a JWT encoding information about the identity, which can be verified using the issuer's public key and `access_token`: an opaque token that can be used with a `userinfo` endpoint on the issuer to obtain information about the identity). However, in the case of workload identities, `id_token` is the most prevelant. For this reason, our initial implementation will solely support `id_token`. +OIDC supports multiple types of token (`id_token`: a JWT encoding information about the identity, which can be verified using the issuer's public key and `access_token`: an opaque token that can be used with a `userinfo` endpoint on the issuer to obtain information about the identity). However, in the case of workload identities, `id_token` is the most prevalent. For this reason, our initial implementation will solely support `id_token`. ### Auth server support @@ -85,7 +85,7 @@ Whilst out of scope for an initial implementation, we should eventually allow th #### Configuration -In order to introduce support for GHA joining, we will introduce a new field to `ProvisionTokenV2` called `github`. This RFD sets a new standard for expansion of the `ProvisionTokenV2` with all future providers recommended to create their own top level fields, rather than continuing to expand the existing `Allow` field. Work will eventually beging to migrate IAM and EC2 joining to their own fields. +In order to introduce support for GHA joining, we will introduce a new field to `ProvisionTokenV2` called `github`. This RFD sets a new standard for expansion of the `ProvisionTokenV2` with all future providers recommended to create their own top level fields, rather than continuing to expand the existing `Allow` field. Work will eventually begin to migrate IAM and EC2 joining to their own fields. `ProvisionTokenSpecV2` with fields added to support GHA: @@ -133,7 +133,7 @@ message ProvisionTokenSpecV2GitHub { // when trying to create rules around which workflows should be allowed to // authenticate against a cluster. message Rule { - // Sub also known as Subject is a string that roughly uniquely indentifies + // Sub also known as Subject is a string that roughly uniquely identifies // the workload. The format of this varies depending on the type of // github action run. string Sub = 1 [(gogoproto.jsontag) = "sub,omitempty"]; diff --git a/rfd/0080-hardware-key-support.md b/rfd/0080-hardware-key-support.md index 8fd222500eda4..1a3998975970a 100644 --- a/rfd/0080-hardware-key-support.md +++ b/rfd/0080-hardware-key-support.md @@ -40,7 +40,7 @@ Personal Identity Verification (PIV), described in [FIPS-201](https://csrc.nist. PIV builds upon the PKCS#11 interface and provides us with additional capabilities including: * Optional PIN and Touch requirements for accessing keys -* PIV secrets for granular [adminstrative access](https://developers.yubico.com/PIV/Introduction/Admin_access.html) +* PIV secrets for granular [administrative access](https://developers.yubico.com/PIV/Introduction/Admin_access.html) * [Attestation](https://docs.yubico.com/yesdk/users-manual/application-piv/attestation.html) of private key slots ##### Attestation @@ -55,12 +55,12 @@ For example, Yubico created their own [PIV attestation extension](https://develo We will use the [go-piv](https://github.com/go-piv/piv-go) library, which is a Golang port of Yubikey's C library [ykpiv](https://github.com/Yubico/yubico-piv-tool/blob/master/lib/ykpiv.c). This is the same library used by [yubikey-agent](https://github.com/FiloSottile/yubikey-agent). -Currently, Yubikey is one of the only PIV-compatible commercial hardware keys. As a result, current PIV implementations like piv-go are specifically designed around Yubikey's implementation of PIV - the `libykcs11.so` module. While the majority of PIV is standardized, the Yubikey PIV implementation has [some extensions](https://developers.yubico.com/PIV/Introduction/Yubico_extensions.html) and other idiosyncracies which may not be standard across future PIV implemenations. +Currently, Yubikey is one of the only PIV-compatible commercial hardware keys. As a result, current PIV implementations like piv-go are specifically designed around Yubikey's implementation of PIV - the `libykcs11.so` module. While the majority of PIV is standardized, the Yubikey PIV implementation has [some extensions](https://developers.yubico.com/PIV/Introduction/Yubico_extensions.html) and other idiosyncracies which may not be standard across future PIV implementations. -There is no common PIV library, so our best option is to use `piv-go` for a streamlined implemenation and prepare to adjust in the future as more PIV-compatible hardware keys are released. Possible adjustments include: +There is no common PIV library, so our best option is to use `piv-go` for a streamlined implementation and prepare to adjust in the future as more PIV-compatible hardware keys are released. Possible adjustments include: * using multiple PIV libraries to support custom PIV implementations -* switching to a PIV library which expressly supports all/more PIV implemenations +* switching to a PIV library which expressly supports all/more PIV implementations * working within a PIV library, through PRs or a Fork, to expand PIV support * creating our own custom PIV library which we can add custom support into as needed @@ -287,9 +287,9 @@ Teleport clients should be able to automatically determine if a user requires a First, the client will ping the Teleport Auth server to get the cluster-wide private key policy if set. Second, the client will check for an existing key in the user's key store (`~/.tsh`), and check its associated private key policy. Between the two private key policies retrieved, the stricter one will be used for initial login. This guessing logic will capture all cases except for the case where a user's role private key policy is stricter than the cluster-wide policy, and do not have an active/expired login session stored in `~/.tsh`. -If the private key policy was incorrect and a stricter requirement is neeeded, then the server will respond with a `private key policy not met: ` error. The client will parse this error and resort to re-authenticating with the correct private key policy, meaning that the user will be re-prompted for their login credentials. +If the private key policy was incorrect and a stricter requirement is needed, then the server will respond with a `private key policy not met: ` error. The client will parse this error and resort to re-authenticating with the correct private key policy, meaning that the user will be re-prompted for their login credentials. -If a user's private key policy requirement is increased during an active login, the server will respond to any requests from the user with a `private key policy not met: ` error. The Teleport client can capture this error and intiate re-login with the correct key policy. +If a user's private key policy requirement is increased during an active login, the server will respond to any requests from the user with a `private key policy not met: ` error. The Teleport client can capture this error and initiate re-login with the correct key policy. #### Hardware private key login @@ -345,7 +345,7 @@ slot= #### Unsupported clients -The WebUI will not be able to support PIV login, since it is brower-based and cannot connect directly to the user's PIV device. If a user with `require_session_mfa: hardware_key` attempts to login on the WebUI, or use an existing login session, it will fail. However, WebUI user registration and password reset logic must still work, regardless of the user's private key policy requirement. After initial registration/reset flow, the user should be directed to a page which notifies them that `tsh` or Teleport Connect must be used. +The WebUI will not be able to support PIV login, since it is browser-based and cannot connect directly to the user's PIV device. If a user with `require_session_mfa: hardware_key` attempts to login on the WebUI, or use an existing login session, it will fail. However, WebUI user registration and password reset logic must still work, regardless of the user's private key policy requirement. After initial registration/reset flow, the user should be directed to a page which notifies them that `tsh` or Teleport Connect must be used. It may be possible to work around this limitation by introducing a local proxy to connect to the hardware key, or by supporting a hardware key solution which doesn't need a direct connection, but this is out of scope and will not be explored in this PR. @@ -422,7 +422,7 @@ For Yubikey, users can also [manually add their keys](https://github.com/jamesog #### PIV secret management -Some PIV operations require [adminstrative access](https://developers.yubico.com/PIV/Introduction/Admin_access.html), which require one or more of the following secrets: +Some PIV operations require [administrative access](https://developers.yubico.com/PIV/Introduction/Admin_access.html), which require one or more of the following secrets: | Name | size | default value | function | |----------------|----------|----------------------------------------------------|-------------------------------------------| diff --git a/rfd/0081-tls-ping.md b/rfd/0081-tls-ping.md index a56fdcde2e336..eb307f4a559e8 100644 --- a/rfd/0081-tls-ping.md +++ b/rfd/0081-tls-ping.md @@ -207,7 +207,7 @@ sequenceDiagram P->>LP: Connected
(negotiated protocol = "teleport-sample") end - loop Continuosly + loop Continuously LP->P: Read/Write connection data end ``` diff --git a/rfd/0083-machine-id-host-certs.md b/rfd/0083-machine-id-host-certs.md index 892471357c882..72ab3dfae912c 100644 --- a/rfd/0083-machine-id-host-certs.md +++ b/rfd/0083-machine-id-host-certs.md @@ -165,7 +165,7 @@ Are you sure you want to continue connecting (yes/no/[fingerprint])? This is nearly identical to the usual ssh TOFU message, save for the easy-to-miss "Certificate invalid: expired" message. Users are likely conditioned to accept this, and if that happens the expired or invalid host key -will be committed to their `known_hosts` permanently, after which the "expired" +will be comitted to their `known_hosts` permanently, after which the "expired" message will not be shown again. We'll need to document this caveat along with a workaround (e.g. a diff --git a/rfd/0087-access-request-notification-routing.md b/rfd/0087-access-request-notification-routing.md index a6122b51462d3..1b9e99d0aa568 100644 --- a/rfd/0087-access-request-notification-routing.md +++ b/rfd/0087-access-request-notification-routing.md @@ -196,7 +196,7 @@ The safest way would be to not interact with it and let the plugin deal with it. Using `suggested_reviewer` implies the user knows which plugin will be used to send the access request. The string has to make sense and be allowed by the plugin. Today most plugins support email addresses -and reject other recipients, but somes are just ignoring the field (pagerduty). We might want to clarify +and reject other recipients, but some are just ignoring the field (pagerduty). We might want to clarify what can be entered in this field and how different plugins are reacting to it so user has insights on what to type and what to expect. @@ -312,7 +312,7 @@ the role `"prod-rw"` but not the role `"prod-ro"`. ``` kind: role metadata: - name: developper + name: developer spec: allow: request: @@ -330,7 +330,7 @@ impacts. We might want to guide users towards model in the documentation. This change must be backward compatible: - old plugins should ignore `targets` and get the recipients from the `role_to_recipient` map (or annotation for pagerduty); -- each plugin should expose an `honor_suggested_reviwers` configuration, true by default; +- each plugin should expose an `honor_suggested_reviewers` configuration, true by default; - each plugin should have a default name and listen to it by default in the `targets`, this name should be the same used to store plugin data; - admins can configure the plugin name through its configuration, names should always be prefixed by the plugin type (e.g. `slack-teamA`, `msteams-teamB`) to limit conflicts between two plugins with the same names; diff --git a/rfd/0088-passwordless-windows.md b/rfd/0088-passwordless-windows.md index 2b0148fc0d8bd..731d2f8375113 100644 --- a/rfd/0088-passwordless-windows.md +++ b/rfd/0088-passwordless-windows.md @@ -40,7 +40,7 @@ flowchart TB BC[Browser Client] NC[Native Client] subgraph WADLL[WEBAUTHN.dll] - PA[Platorm Authenticator] + PA[Platform Authenticator] CPA[Cross-platform Authenticator] end end @@ -57,7 +57,7 @@ flowchart TB ``` `Webauthn.dll` is designed to work with Webauthn protocol, that's why -integration with it results moslty in passing parameters from and back to +integration with it results mostly in passing parameters from and back to server. **Important:** Windows system without `webauthn.dll` (older than Windows 10 19H1) @@ -238,7 +238,7 @@ troubleshooting and credential management. Libfido2 c library (which is used on mac/linux for FIDO keys) supports Windows Hello via `webauthn.dll` and FIDO devices during old implementation. However starting from version Windows 10 19H1, access to FIDO devices using old -implementation required administrator privlidges and it is recommented to use +implementation required administrator privileges and it is recommended to use `webauthn.dll` for FIDO2 devices. Libfido2 API is limited and you cannot really specify if you want to use platform or cross-platform attachment. diff --git a/rfd/0089-merge-webapps.md b/rfd/0089-merge-webapps.md index 4d35df2b3a29d..1d2da8730e638 100644 --- a/rfd/0089-merge-webapps.md +++ b/rfd/0089-merge-webapps.md @@ -55,7 +55,7 @@ the webapps folder is an elegant way of approaching this issue ### Backports -To avoid having different proceses and build systems depending on the version +To avoid having different processes and build systems depending on the version of teleport, all supported release branches (v10, v11, v12) will have their respective versions of the webapps repository merged. @@ -155,7 +155,7 @@ build systems to successfully build Teleport. - [ ] Update [the default path to tsh in dev mode](https://github.com/gravitational/webapps/blob/27c615b3ff6f317a85fac4aa28b8e73fa4aa0d28/packages/teleterm/src/mainProcess/runtimeSettings.ts#L18-L23) for Connect. - [ ] Update the `README.md` to indicate that this repository is no longer the source of truth and instead link to the `teleport` repo. Due to us needing - topotentially update older releases we are not able to archive the + to potentially update older releases we are not able to archive the repository at this time. We can revisit this in 6mo. ### Webapps.e repository @@ -187,7 +187,7 @@ build systems to successfully build Teleport. - [ ] Remove `/webassets` submodule - This submodule is no longer required as the web UI will be built on-demand. - The folder will remain as the output location of the on-demand build but - will not be committed. + will not be comitted. - [ ] Clone the [Webapps repository](https://github.com/gravitational/webapps) into the Teleport root. [Maintaining their respective git histories](https://stackoverflow.com/questions/13040958/merge-two-git-repositories-without-breaking-file-history) - [ ] This will need to be done for every respective version branch (v9, v10, v11) diff --git a/rfd/0090-upgrade-system.md b/rfd/0090-upgrade-system.md index 9b72bc7f3ab1e..d87cd3d5570f6 100644 --- a/rfd/0090-upgrade-system.md +++ b/rfd/0090-upgrade-system.md @@ -1641,7 +1641,7 @@ with a given user should their cluster size (approximate or not) be shared for a - Cluster identifiers (both the seed/salt value and the ephemeral identifier) should be treated as secrets and not emitted in any logs or in any tctl commands that don't include `--with-secrets`. -- Addition of any new metrics in the future should be subject to hightened scrutiny and cynicism. A healthy +- Addition of any new metrics in the future should be subject to heightened scrutiny and cynicism. A healthy dose of 'professional paranoia' is beneficial here. ### Open Questions diff --git a/rfd/0091-session-streaming.md b/rfd/0091-session-streaming.md index 16d33145293ba..ffee61ba0c28e 100644 --- a/rfd/0091-session-streaming.md +++ b/rfd/0091-session-streaming.md @@ -212,7 +212,7 @@ we will: *Phase 2:* Fully remove old APIs -In the next major relase, N+1.0.0, we will: +In the next major release, N+1.0.0, we will: - remove the `GetSessionEvents` and `GetSessionChunk` from the gRPC API client - remove `EventFields` from the `lib/events` package. This was the legacy way @@ -221,7 +221,7 @@ In the next major relase, N+1.0.0, we will: ### Security -There are no *new* security impliciations to consider here, since the streaming +There are no *new* security implications to consider here, since the streaming APIs already exist and are already protected by Teleport's RBAC system. ### UX @@ -229,7 +229,7 @@ APIs already exist and are already protected by Teleport's RBAC system. From a user experience perspective, implementing this RFD will: - reduce the initial latency when starting to play a recorded sessions - (this will be more noticable with larger sessions than smaller sessions) + (this will be more noticeable with larger sessions than smaller sessions) - improve the experience of playing back sessions on resource constrained systems with smaller amounts of memory - *increase* the latency of "rewind" operations, since the session stream diff --git a/rfd/0094-discover-metrics.md b/rfd/0094-discover-metrics.md index e3c45e148c0e1..b7eb2e9a244fe 100644 --- a/rfd/0094-discover-metrics.md +++ b/rfd/0094-discover-metrics.md @@ -136,7 +136,7 @@ message DiscoverStepStatus { // Indicates the step outcome. DiscoverStatus status = 1; // Contains error details in case of Error Status. - // We have to be careful to not include any identifyable infomation like server addresses here. + // We have to be careful to not include any identifyable information like server addresses here. string error = 2; } diff --git a/rfd/0096-helm-chart-revamp.md b/rfd/0096-helm-chart-revamp.md index 09f9c03481afc..8c183b12cbe93 100644 --- a/rfd/0096-helm-chart-revamp.md +++ b/rfd/0096-helm-chart-revamp.md @@ -65,7 +65,7 @@ The chart would deploy two Deployments: one for the proxies and one for the auth - the `teleport-proxy` Deployment: Those pods are stateless by default and can be upscaled even in standalone mode. Deploying those nodes using a Deployment - means we cannot mount peristent storage on them. As Teleport does not support + means we cannot mount persistent storage on them. As Teleport does not support graceful shutdown with record shipping, users might lose active sessions recordings during a rollout if using the `proxy` mode. Teleport nodes are relying on `kube` ProvisionTokens to join the auth nodes on startup ([see @@ -228,7 +228,7 @@ but pass the custom configuration through the values. This is a breaking change for them, but by the nature of the auth/proxy split it is not possible to be backward compatible with `custom` mode. -In order to mitigate the risk of building an invalid confifguration, the chart should +In order to mitigate the risk of building an invalid configuration, the chart should run pre-install and pre-upgrade hooks validating the configuration. #### Backward compatibility diff --git a/rfd/0097-teleport-connect-usage-metrics.md b/rfd/0097-teleport-connect-usage-metrics.md index b15c997d0c40d..f82638b1e6021 100644 --- a/rfd/0097-teleport-connect-usage-metrics.md +++ b/rfd/0097-teleport-connect-usage-metrics.md @@ -130,7 +130,7 @@ Event properties: - `connect.os_version`: string (set on a user properties) - `connect.app_version`: string (set on a user properties) -### `connect.protcol.use` +### `connect.protocol.use` Connecting to the protocol. diff --git a/rfd/0098-kubernetes-access-cluster-discovery.md b/rfd/0098-kubernetes-access-cluster-discovery.md index 60a9f5cc5afcb..a86b22e190adc 100644 --- a/rfd/0098-kubernetes-access-cluster-discovery.md +++ b/rfd/0098-kubernetes-access-cluster-discovery.md @@ -393,7 +393,7 @@ The optional permissions can be included to allow Teleport to automatically conf "Microsoft.ContainerService/managedClusters/read", "Microsoft.ContainerService/managedClusters/listClusterUserCredential/action", - # optional - usefull if Teleport belongs to the admin groups or the cluster has static admin credentials + # optional - useful if Teleport belongs to the admin groups or the cluster has static admin credentials "Microsoft.ContainerService/managedClusters/listClusterAdminCredential/action", # optional - Usefull if Teleport has the ability to create ClusterRole and ClusterRoleBindings in the target cluster. diff --git a/rfd/0100-proxy-ssh-grpc.md b/rfd/0100-proxy-ssh-grpc.md index 41b4b3b1cb453..5d072b25abd50 100644 --- a/rfd/0100-proxy-ssh-grpc.md +++ b/rfd/0100-proxy-ssh-grpc.md @@ -59,7 +59,7 @@ service ProxyConnectionService { // ProxyCluster establishes a connection to the target cluster // // The client must first send a ProxyClusterRequest with the desired cluster before the - // connection is establishsed. + // connection is established. rpc ProxyCluster(stream ProxyClusterRequest) returns (stream ProxyClusterResponse); } diff --git a/rfd/0101-pod-rbac.md b/rfd/0101-pod-rbac.md index bc7d9e7a354a2..e42e24ef0e761 100644 --- a/rfd/0101-pod-rbac.md +++ b/rfd/0101-pod-rbac.md @@ -281,7 +281,7 @@ retry the command afterward. ```bash $ tsh kubectl exec/logs/edit -it -n Access to pod / denied. -Submiting an access request. +Submitting an access request. Waiting for request to be approved... Approved! # @@ -370,7 +370,7 @@ for the roles that also match the cluster labels. For this request, `kubernetes_groups`: [ `kube_group1` ]. If the user tries to execute `kubectl logs special_pod -n default` into the same cluster, Teleport collects `kubernetes_groups`: [`kube_group1`, `kube_group3`] because the `role3` now matches the pod -name. When forwarding the request to the Kubernertes Cluster, the user will +name. When forwarding the request to the Kubernetes Cluster, the user will impersonate `kube_group1` and `kube_group2` instead of `kube_group1` for the previous request. @@ -462,9 +462,9 @@ describes execution flows. | user1 | `cluster1` | `role4`, `role1` | pods in every namespace (sends `dev-admin`) | allowed by Teleport, allowed by `dev-admin` | allowed by Teleport, allowed by `dev-admin` | | user2 | `cluster2` | `role1` | pods from `default` namespace (sends `viewer`) | allowed by Teleport, denied by `viewer` | allowed by Teleport, denied by `viewer` | | user2 | `cluster2` | `role2` | pods from `default` (sends `viewer`) | allowed by Teleport, denied by `viewer` | allowed by Teleport, denied by `viewer` | -| user3 | `cluster2` | `role3` | `owned_pod` from `default` (sends `system:masters`) | allowed by Teleport, alowed by `system:masters` (sends: `system:masters`) | denied by Teleport | -| user4 | `cluster2` | `role1`, `role3` | leaks every pod in every namespace (sends `viewer`,`system:masters`) | allowed by Teleport, alowed by `system:masters` (sends: `system:masters`,`viewer`) | allowed by Teleport, denied by `viewer` (sends: `viewer`) | -| user5 | `cluster2` | `role2`, `role3` | returns every pod in `default` namespace (sends: `viewer`,`system:masters`) | allowed by Teleport, alowed by `system:masters` (sends: `system:masters`,`viewer`) | allowed by Teleport, denied by `viewer` (sends: `viewer`) | +| user3 | `cluster2` | `role3` | `owned_pod` from `default` (sends `system:masters`) | allowed by Teleport, allowed by `system:masters` (sends: `system:masters`) | denied by Teleport | +| user4 | `cluster2` | `role1`, `role3` | leaks every pod in every namespace (sends `viewer`,`system:masters`) | allowed by Teleport, allowed by `system:masters` (sends: `system:masters`,`viewer`) | allowed by Teleport, denied by `viewer` (sends: `viewer`) | +| user5 | `cluster2` | `role2`, `role3` | returns every pod in `default` namespace (sends: `viewer`,`system:masters`) | allowed by Teleport, allowed by `system:masters` (sends: `system:masters`,`viewer`) | allowed by Teleport, denied by `viewer` (sends: `viewer`) | ### Implementation details diff --git a/rfd/0105-headless-authentication.md b/rfd/0105-headless-authentication.md index aadb9a757fc7d..4ec1e89ba1cc4 100644 --- a/rfd/0105-headless-authentication.md +++ b/rfd/0105-headless-authentication.md @@ -170,7 +170,7 @@ If the headless authentication is approved with a valid MFA challenge, the backe #### Certificate retrieval -If the headless authentication is approved/denied, the Auth server's resource watcher will unblock to complete/deny the authentication attempt. If approved, the auth server will generate certificates for the user. These certs will have a 1 minute TTL and MFA-verfied by the MFA device saved in the headless authentication resource. +If the headless authentication is approved/denied, the Auth server's resource watcher will unblock to complete/deny the authentication attempt. If approved, the auth server will generate certificates for the user. These certs will have a 1 minute TTL and MFA-verified by the MFA device saved in the headless authentication resource. The resulting user certificates will then be returned to the Proxy and then to the client. Now the client can complete the `tsh` request initially requested, e.g. `tsh ssh user@node01`. @@ -387,6 +387,6 @@ Teleport Connect will also be updated to handle the approval link `https://proxy ### Additional comments -#### Utilizing the access request subsytem +#### Utilizing the access request subsystem Headless authentication could be implemented by expanding the access request subsystem. `/webapi/login/headless` could submit a special headless authentication access request that can only be approved by the user with MFA, and could be used to assume the user's roles. I thoroughly investigated designs around this idea, but found they add too much complexity to the already heavily utilized access request subsystem. diff --git a/rfd/0109-cloud-agent-upgrades.md b/rfd/0109-cloud-agent-upgrades.md index e056536b3f26a..bab60de914eef 100644 --- a/rfd/0109-cloud-agent-upgrades.md +++ b/rfd/0109-cloud-agent-upgrades.md @@ -55,7 +55,7 @@ From the start, we will support the following scenarios: - Teleport agents installed via a dedicated `apt` or `yum` package that sets up the installer. -Agents deployed in one of the above manners will automically be "enrolled" in the cloud upgrade system as part of the +Agents deployed in one of the above manners will automatically be "enrolled" in the cloud upgrade system as part of the installation process. Enrollment will not be coordinated at the cluster-level (i.e. agents will not check with the cluster to determine if upgrades should occur). It will be assumed that the latest available cloud-stable package is always the "correct" package to be running. This is an important divergence from how teleport versioning is typically @@ -86,7 +86,7 @@ Rollout of a new version to cloud-stable will generally follow the following ste 3. The target version of the cloud-stable endpoint is updated to point to the new target client version. -4. A grace period is observed, during which the control plane is not updated s.t. it breaks comatibility with +4. A grace period is observed, during which the control plane is not updated s.t. it breaks compatibility with agents that have yet to be upgraded. I.e. if cloud-stable targets `v1` at `T1`, then the control plane must maintain compatibility with `v1` until at least `T1 + grace_period`. @@ -245,14 +245,14 @@ Kubernetes based agents will be managed by a separate cloud-stable upgrade contr be responsible for all teleport agents installed by the helm chart, and will perform rolling updates, applying the latest immutable image tag to all agents. -Agents running in kuberentes mode will periodically export their schedules to a kubernetes config object to be +Agents running in kubernetes mode will periodically export their schedules to a kubernetes config object to be consumed by the controller. The k8s controller's specific loop will end up looking like this: ![Upgrade Controller](https://user-images.githubusercontent.com/16366487/219150279-9255d4b3-74fb-48f7-b966-04cb79520e66.png) -The kuberenetes controller will leverage cosign based image signing for an added layer of security. +The kubernetes controller will leverage cosign based image signing for an added layer of security. Our kube agent helm chart will be updated to support enabling all necessary cloud-stable behaviors (added controller and any necessary command/env changes) via a single flag. Ex: @@ -291,13 +291,13 @@ to firing approximately every 30min. When healthy, teleport agents will export a the default monotonic fire rate and setting an `OnCalendar` directive matching the current agent maintenance schedule, thereby limiting the upgrade timer to firing only during maintenance windows. -The `teleport-upgrade-check.timer` unit will peridoically check `cloud-stable/critical`. If it detects a critical update +The `teleport-upgrade-check.timer` unit will periodically check `cloud-stable/critical`. If it detects a critical update *and* that the currently installed teleport version differs from the cloud-stable target, it will revert the primary timer back to monotonic/high-frequency mode. Note that we *could* invoke the upgrade script directly instead and the system would be simpler overall, however this would lead to duplicate sources of truth for what a "firing" upgrade was, and potentially lead to confusing interactions between conflicting check rates. After some back and forth, I'm opting to commit to the concept of a single upgrade timer whose modes are switched by external actors, but -which utimately retains sole control over the upgrade process. +which ultimately retains sole control over the upgrade process. ### Automatic/Guided Install Changes @@ -332,7 +332,7 @@ by ec2 discovery. After initial trials, we would like tracking cloud-stable to become the default for new cloud clusters. Because of this, we will need to make some updates to existing documentation to guide new cloud users to invoke the necessary install variants. -Mostly, this will consist of adding appropiate `ScopedBlock`s to docs that display cloud variants of install +Mostly, this will consist of adding appropriate `ScopedBlock`s to docs that display cloud variants of install commands. A number of the pages under `deploy-a-cluster/helm-deployments/` will need this treatment. Most cloud guides prompt users to use a join script, so the majority of those will be able to be left untouched, as clusters will automatically serve the correct script variant. diff --git a/rfd/0111-support-connection-testers-with-per-session-mfa.md b/rfd/0111-support-connection-testers-with-per-session-mfa.md index da6421fcc502d..0672c18e93bf3 100644 --- a/rfd/0111-support-connection-testers-with-per-session-mfa.md +++ b/rfd/0111-support-connection-testers-with-per-session-mfa.md @@ -15,7 +15,7 @@ Add a [MFAAuthenticateResponse](https://github.com/gravitational/teleport/blob/d ## Why -As mentioned in the related issue, when a role or config has enabled the [require_sesion_mfa](https://goteleport.com/docs/access-controls/guides/per-session-mfa) field, users were not able to proceed testing connections to their newly added resource in the web UI, because we didn't implement a way for users to provide and authenticate their MFA device. +As mentioned in the related issue, when a role or config has enabled the [require_session_mfa](https://goteleport.com/docs/access-controls/guides/per-session-mfa) field, users were not able to proceed testing connections to their newly added resource in the web UI, because we didn't implement a way for users to provide and authenticate their MFA device. ## Details diff --git a/rfd/0113-automatic-database-users.md b/rfd/0113-automatic-database-users.md index 0f1cebf224f4c..ba807ad1bb26f 100644 --- a/rfd/0113-automatic-database-users.md +++ b/rfd/0113-automatic-database-users.md @@ -141,7 +141,7 @@ PostgreSQL users created by Teleport **will not be deleted** by Teleport. The main reason is dynamic users can have permissions to create database objects (depending on their database role) and users that own any database objects can't -be deleted until the objects are deleted or their ownership is transfered to +be deleted until the objects are deleted or their ownership is transferred to another database user. PostgreSQL provides commands for deleting objects owned by a user and changing @@ -273,7 +273,7 @@ end;$$; Multiple create/disable procedures for the same user can run simultaneously e.g. if sessions are being opened/established at the same time by user or GUI client. To avoid races, Teleport database service will use `AcquireSemaphore` to make -sure only 1 procedure runs for a partucular user: +sure only 1 procedure runs for a particular user: https://github.com/gravitational/teleport/blob/v12.1.1/api/client/client.go#L953 diff --git a/rfd/0113-scalable-audit-logs.md b/rfd/0113-scalable-audit-logs.md index 499213ba14ec0..e265f65d8041a 100644 --- a/rfd/0113-scalable-audit-logs.md +++ b/rfd/0113-scalable-audit-logs.md @@ -18,7 +18,7 @@ and searchable audit log mechanism. In this RFD we focus on integrating scalable datastore to existing interfaces. There will be separate RFD which will focus and UI changes and focus on advanced -search capabilites. +search capabilities. ## Why @@ -75,7 +75,7 @@ java](https://docs.aws.amazon.com/sns/latest/dg/large-message-payloads.html) works. It allows to specify s3 bucket where messages larger then max limit are sent. On SNS/SQS client only sends s3 link to payload. -SNS/SQS message consints of `payload` and `messageAttributes`. `Payload` can be +SNS/SQS message consists of `payload` and `messageAttributes`. `Payload` can be only valid UTF-8 string. `messageAttributes` will be used to determine on SQS which type is payload. @@ -114,10 +114,10 @@ We decided go with proto. ### Transform and store phase Consumer will be implemented in one of auth instances. We will use locking -mechanism which can be aquired on backend, so that only single instace does the +mechanism which can be acquired on backend, so that only single instance does the job. There is already mechanism for that called [RunWhileLocked](https://github.com/gravitational/teleport/blob/11eaf9657dcdd9f4c8b73a3880c5648db0139aec/lib/backend/helpers.go#L137-L171). -It's checking backend with 250ms interval if lock can be aquired. I think it +It's checking backend with 250ms interval if lock can be acquired. I think it makes sense to make that interval configurable in `RunWhileLocked` function and set it to 10s. Lock TTL should be set to 30s. It will be automatically refreshed if job is still running. So TTL will be only used is Auth died and @@ -142,7 +142,7 @@ If writing parquet file will fail, whole batch should be NACK. We will store basic information like (`event_time`, `event_type`, `session_id`, `audit_id(uid)`, `user`) as top -level columns in Parquet files. Additionaly there will be `event_data` column +level columns in Parquet files. Additionally there will be `event_data` column which will store string which contains marshaled data from whole audit event. @@ -159,7 +159,7 @@ Data retention should be defined on bucket level during creation of bucket. Athena during query first checks Glue table and its schema. AWS Glue table is used to store and retrieve table metadata for the Amazon S3 data. This schema -is used by Athena during quering data. The table metadata lets the Athena query +is used by Athena during querying data. The table metadata lets the Athena query engine know how to find, read, and process the data that you want to query. We will use dynamic projections to avoid manually creating partitions. @@ -167,7 +167,7 @@ Creating table and database should be done in tenant operator. It's added here just to bring more context. ```sql -CREATE EXTERNAL TABLE aduitevents_tenantid ( +CREATE EXTERNAL TABLE auditevents_tenantid ( `uid` string, `session_id` string, `event_type` string, @@ -199,10 +199,10 @@ Example queries: ```sql /* get events for given date */ -SELECT DISTINCT event_data, event_time, uid FROM aduitevents_tenantid +SELECT DISTINCT event_data, event_time, uid FROM auditevents_tenantid WHERE event_date=date('2023-02-14') ORDER BY event_time DESC, uid DESC /* get events for specific db instance */ -SELECT DISTINCT event_data FROM aduitevents_tenantid WHERE event_date>=date('2023-02-14') +SELECT DISTINCT event_data FROM auditevents_tenantid WHERE event_date>=date('2023-02-14') AND event_type = 'db.session.query' AND json_extract_scalar(event_data, '$.db_instance')='production.postgres' ``` @@ -216,7 +216,7 @@ Results from query execution are stored in s3 bucket (either default for workspace or one you specify during StartQueryExecution). `getQueryResults` download results from s3 bucket. -`ExecutionParamaters` field from StartQueryExecution endpoint must be used to +`ExecutionParameters` field from StartQueryExecution endpoint must be used to pass query parameters. Using that approach protect us from SQL injection. `getQueryExecution` will be check at defined interval, passed from config. @@ -224,12 +224,12 @@ pass query parameters. Using that approach protect us from SQL injection. #### Pagination support -Both `SeachEvents` and `SearchSessionEvents` supports pagination of results by +Both `SearchEvents` and `SearchSessionEvents` supports pagination of results by providing `startKey` and `limit` and part of their signature. -It is recommended in Athena when quering over large number of data, to query +It is recommended in Athena when querying over large number of data, to query without limit only once, and use `getQueryExecution` to iterate over results. -Because athena stores query results on s3, you can download it by specifing +Because athena stores query results on s3, you can download it by specifying `queryID` and optional `offsetKey`. We have decided to not follow that pattern because it opens us with risk of @@ -239,7 +239,7 @@ can result in RBAC bypass, because SearchSessionEvents RBAC is non trivial. If user has `session.list` permission with specific where condition `contains(session.participants, user.metadata.name)`, user by guessing queryID and offsetKey bypass RBAC because we would try to download results instead of -execting query. +executing query. Workaround that is using standard SQL pagination support, using limit and always reexecuting query instead of downloading it. @@ -291,7 +291,7 @@ bootstraping of infra could be added into teleport codebase. ### Rate limiting of search events -Athena Service Quotas can be tight in certian cases (for example Teleport Cloud +Athena Service Quotas can be tight in certain cases (for example Teleport Cloud with tenants sharing quota pool). To address that issue we decided to introduce new rate limiting mechanism which will work per auth instance for all users, not per IP. @@ -326,4 +326,4 @@ in UI can be delayed up to value of buffer interval. In this RFD we focus on integrating scalable datastore to existing interfaces. There will be separate RFD which will focus and UI changes and focus on advanced -search capabilites. +search capabilities. diff --git a/rfd/0117-kube-access-forward-identity.md b/rfd/0117-kube-access-forward-identity.md new file mode 100644 index 0000000000000..7de3046ecfcfa --- /dev/null +++ b/rfd/0117-kube-access-forward-identity.md @@ -0,0 +1,208 @@ +--- +authors: Tiago Silva (tiago.silva@goteleport.com) +state: draft +--- + +# RFD 117 - Forward User Identity between Teleport services + +## Required Approvers + +- Engineering: `@r0mant` +- Security: `@reedloden` + +## What + +This RFD proposes a way to forward user identity from the Teleport Proxy to +Teleport Kubernetes Service and remote Teleport proxy. + +### Related issues + +- [#21609](https://github.com/gravitational/teleport/issues/21609) + +## Why + +When a user connects to a Teleport proxy, the proxy will authenticate the user +using his X.509 certificate. Kubernetes Proxy will then forward the request to the +Teleport service responsible for serving the Kubernetes cluster (Kube Service) or +to the remote Teleport proxy when the Kubernetes Cluster belongs to a trusted cluster. +For the proxy to be able to forward the user's identity to the correct service, +it needs to generate a new X.509 key-pair with the user identity embedded that +will be used during the mTLS authentication. +The upstream service will then use the user identity to authorize the request and to +impersonate the configured Kubernetes principals when forwarding the request to +the Kubernetes API server. + +To avoid having to generate a new certificate for each request, the proxy will +generate a new certificate for each user's identity and cache it in memory for +`min(3h, certExpiration)`. This certificate cache must be keyed with a key that +uniquely identifies the user's identity so that the proxy can retrieve the certificate +for the correct identity when forwarding the request. In the past, we had situations +where the cache was not properly keyed and the proxy would forward the request +with the wrong certificate ending up with authorization errors. + +Currently, the key used to cache the certificate is the user's identity has the +following format: + +```go +func (c *authContext) key() string { + // it is important that the context key contains user, kubernetes groups and certificate expiry, + // so that new logins with different parameters will not reuse this context + return fmt.Sprintf("%v:%v:%v:%v:%v:%v:%v", c.teleportCluster.name, c.User.GetName(), c.kubeUsers, c.kubeGroups, c.kubeClusterName, c.certExpires.Unix(), c.Identity.GetIdentity().ActiveRequests) +} +``` + +Each time we introduce a new field to the user's identity, we need to make sure +that the key is updated to include the new field if the field is used by Kubernetes +Access. This process is error-prone and it's easy to forget to update the key, and +eventually introduce a bug or a security vulnerability. + +This RFD proposes a way to forward the user's identity from the proxy to the +Kube Service and remote Teleport proxy that does not require the proxy to generate +a new certificate for each user's identity. + +## Details + +The Proxy will authenticate to the Kube Service and any remote Teleport proxy using its +certificate. The certificate used by proxy does not contain any user identification +and it's signed by the Teleport CA. The proxy will then forward the user's identity +using HTTP headers or gRPC metadata instead of generating a new certificate on +the behalf of the user. Since the proxy is authenticated using its certificate, +the upstream service can trust the identity received in the request headers. + +This change will remove the requirement for the proxy to generate a new certificate +and thus call auth's `ProcessKubeCSR` endpoint to sign the key-pair containing the +user's identity. + + +### Impersonation: Forward user identity using HTTP headers + +As opposed to the majority of protocols supported by Teleport, the Kubernetes protocol +is based on HTTP 1/2. This allows us to forward the user's identity using HTTP headers +for the specific case of Kubernetes. + +The proposed solution is to use the Proxy certificate to authenticate to the upstream +service and to forward the user's identity using HTTP headers or gRPC metadata +for later Impersonation. + +Once the upstream service receives a request, it will check the certificate provided +to validate the request's provenance and its role - making sure it's a proxy - for +authenticating the request. Once authenticated, it will impersonate the identity +contained in the request headers. This impersonated identity will be available in +`authz.Context` and services won't be able to distinguish between a request forwarded +by the proxy or a request made directly by the user. + +This approach is similar to what Kubernetes API does when +forwarding requests to the API server using Impersonation. In the Teleport case, we must +forward the full user's identity instead of just the username/roles. + +Since the proxy has full access to the HTTP request, it can add the user's identity +to the request headers before forwarding it to the upstream service. + +```go +headers["TELEPORT_IMPERSONATE_IDENTITY"] = json(clientCert.Subject) +``` + +In order to prevent the user from tampering with the headers, the authorization +layer will delete the headers after checking if the request originated in a proxy, +so the user cannot send the headers directly and the proxy forwards them +to the upstream service. + +Besides the security benefits, this option also has the advantage of allowing us to +reuse the same connection to forward multiple requests from different users. It happens +because the HTTP headers are sent with each request and we do not require a new +connection per request such as in the Proxy Protocol option. This will improve +performance by reducing the number of connections that need to be established and +the time it takes to forward a new request upstream. Currently, the proxy creates a new +`http.Transport` per request which means that a new connection is established +every time a request is forwarded. This is not ideal because it increases the +latency of the request and it also increases the load on the upstream service. + +This option requires sending the user's IP address to the upstream service +using a header instead of the protocol extension implemented by [TLS IP Pinning RFD](https://github.com/gravitational/teleport/pull/22481). +This is because the connection is reused and the IP address is not available +in the connection prelude since the connection does not belong to a specific user/request. + +## Rollout plan + +To avoid breaking changes, we will implement this feature in two phases. + +### Phase 1: Add support for receiving user identity in Kube Service/Proxy from the Kube Proxy + +- Teleport 13.0 + +We will add support for receiving user identity in Kube Service/Proxy from the +Kube Proxy. Teleport heartbeats contain the agent version and the proxy will +only forward the user's identity to the Kube Service/Proxy if the agent version +is >= 13.0. For older agents, the proxy will continue to generate a new certificate +for each user's identity and call the `ProcessKubeCSR` endpoint. This will allow +us to roll out this feature without breaking changes to Teleport clusters running +older versions (<= 12.0). + +When the proxy uses its X.509 certificate to authenticate to the upstream service, +the upstream service will expect the user's identity to be +forwarded using HTTP headers or Proxy Protocol extensions. If the user's identity +is not forwarded, the upstream service will reject the request. + +### Phase 2: Enable the proxy forwarding of user identity to Kube Service/Proxy + +- Teleport 14.0 + +We will enable the proxy to forward user identity to Kube Service/Proxy. +At this point, Teleport supports clients running Teleport 13.0 and newer and we can +remove the code that generates a new certificate for each user's identity and calls +`ProcessKubeCSR` endpoint because the upstream services already support receiving +user identity from the proxy without the need for the proxy to generate a new certificate. + +## Enterprise considerations + +Currently, when the Teleport proxy calls `ProcessKubeCSR` endpoint and Auth server +is configured with an Enterprise license, the auth server checks if the license +has the Kubernetes feature enabled. If the Auth server isn't licensed to Kubernetes usage, +Auth server will return an error to the proxy and the request is rejected. + +Since we no longer need to call `ProcessKubeCSR` endpoint, we will need to verify +that the cluster is licensed to Kubernetes before forwarding the request to the Kube +Service. Auth server will forbid agents to register their `KubeServers` during +heartbeats if Auth isn't licensed to Kubernetes. Users will also receive an error +when listing Kubernetes clusters using `tsh kube ls` informing them the cluster +isn't properly licensed for Kubernetes Access. This would allow us to +automatically fix the error message returned to the user when the license is +invalid [teleport.e#661](https://github.com/gravitational/teleport.e/issues/661). + +This change only affects enterprise users that are using the Kubernetes Access feature +without a valid license. + +## Security + +Besides the performance improvements, this change will also improve security by +removing the certificate cache and removing the possible reuse of certificates +with the wrong identity. This happened in the past when the cache was not properly +keyed and resulting in the proxy forwarding the wrong certificate to the upstream service. +The bug was fixed by adding extra fields to the cache key. However, this is still error-prone and +it's easy to introduce major security vulnerabilities. + +Since the proxy will send the same Identity to the upstream service that it +received from the client, the upstream service will be able to trust the user's identity +and there is no possibility of the proxy forwarding the wrong identity. + + +## Other solutions considered + +This section describes other solutions considered for forwarding the user's identity +to the upstream service but were discarded because they either didn't provide +the same security guarantees. + +#### Forward user identity using Proxy Protocol extensions + +Similarly to the [TLS IP Pinning RFD](https://github.com/gravitational/teleport/pull/22481), +Teleport Proxy will forward the user's identity using Proxy Protocol extensions. This +means that the Teleport proxy will send the user's identity to the upstream service when dialing +the connection. The upstream service will then be able to extract the user's identity +from the connection prelude and use it to authorize the request. + +As opposed to the HTTP header option, this option doesn't require sending the user's IP +because it's already included in another Proxy Protocol extension. + +This option requires a new connection per request because the Proxy Protocol +extensions are sent when the connection is established. This means that +we cannot reuse the connection to forward multiple requests from different users. \ No newline at end of file diff --git a/tool/tctl/common/auth_command.go b/tool/tctl/common/auth_command.go index 93696b0eed27f..b43f628013fb5 100644 --- a/tool/tctl/common/auth_command.go +++ b/tool/tctl/common/auth_command.go @@ -21,6 +21,7 @@ import ( "net" "net/url" "os" + "path/filepath" "strconv" "strings" "text/template" @@ -74,7 +75,7 @@ type AuthCommand struct { windowsDomain string windowsSID string signOverwrite bool - jksPassword string + password string caType string rotateGracePeriod time.Duration @@ -256,12 +257,20 @@ func (a *AuthCommand) GenerateAndSignKeys(ctx context.Context, clusterAPI auth.C if err != nil { return trace.Wrap(err) } - a.jksPassword = jskPass + a.password = jskPass return a.generateDatabaseKeys(ctx, clusterAPI) case identityfile.FormatSnowflake: return a.generateSnowflakeKey(ctx, clusterAPI) case identityfile.FormatWindows: return a.generateWindowsCert(ctx, clusterAPI) + case identityfile.FormatOracle: + oracleWalletPass, err := utils.CryptoRandomHex(32) + if err != nil { + return trace.Wrap(err) + } + a.password = oracleWalletPass + return a.generateDBOracleCert(ctx, clusterAPI) + } switch { case a.genUser != "" && a.genHost == "": @@ -292,6 +301,7 @@ func (a *AuthCommand) generateWindowsCert(ctx context.Context, clusterAPI auth.C } certDER, _, err := windows.GenerateWindowsDesktopCredentials(ctx, &windows.GenerateCredentialsRequest{ + CAType: types.UserCA, Username: a.windowsUser, Domain: a.windowsDomain, ActiveDirectorySID: a.windowsSID, @@ -497,14 +507,14 @@ func (a *AuthCommand) generateDatabaseKeysForKey(ctx context.Context, clusterAPI OutputLocation: a.output, TTL: a.genTTL, Key: key, - JKSPassword: a.jksPassword, + Password: a.password, } filesWritten, err := db.GenerateDatabaseCertificates(ctx, dbCertReq) if err != nil { return trace.Wrap(err) } - return trace.Wrap(writeHelperMessageDBmTLS(os.Stdout, filesWritten, a.output, a.outputFormat, a.jksPassword)) + return trace.Wrap(writeHelperMessageDBmTLS(os.Stdout, filesWritten, a.output, a.outputFormat, a.password)) } var mapIdentityFileFormatHelperTemplate = map[identityfile.Format]*template.Template{ @@ -516,9 +526,10 @@ var mapIdentityFileFormatHelperTemplate = map[identityfile.Format]*template.Temp identityfile.FormatElasticsearch: elasticsearchAuthSignTpl, identityfile.FormatCassandra: cassandraAuthSignTpl, identityfile.FormatScylla: scyllaAuthSignTpl, + identityfile.FormatOracle: oracleAuthSignTpl, } -func writeHelperMessageDBmTLS(writer io.Writer, filesWritten []string, output string, outputFormat identityfile.Format, jksPassword string) error { +func writeHelperMessageDBmTLS(writer io.Writer, filesWritten []string, output string, outputFormat identityfile.Format, password string) error { if writer == nil { return nil } @@ -530,9 +541,13 @@ func writeHelperMessageDBmTLS(writer io.Writer, filesWritten []string, output st return nil } tplVars := map[string]interface{}{ - "files": strings.Join(filesWritten, ", "), - "jksPassword": jksPassword, - "output": output, + "files": strings.Join(filesWritten, ", "), + "password": password, + "output": output, + } + if outputFormat == defaults.ProtocolOracle { + tplVars["manualOrapkiFlow"] = len(filesWritten) != 1 + tplVars["walletDir"] = filepath.Dir(output) } return trace.Wrap(tpl.Execute(writer, tplVars)) @@ -617,25 +632,50 @@ https://www.elastic.co/guide/en/elasticsearch/reference/current/security-setting `)) cassandraAuthSignTpl = template.Must(template.New("").Parse(`Database credentials have been written to {{.files}}. - To enable mutual TLS on your Cassandra server, add the following to your cassandra.yaml configuration file: - client_encryption_options: enabled: true optional: false keystore: /path/to/{{.output}}.keystore - keystore_password: "{{.jksPassword}}" - + keystore_password: "{{.password}}" require_client_auth: true truststore: /path/to/{{.output}}.truststore - truststore_password: "{{.jksPassword}}" + truststore_password: "{{.password}}" protocol: TLS algorithm: SunX509 store_type: JKS cipher_suites: [TLS_RSA_WITH_AES_256_CBC_SHA] `)) + oracleAuthSignTpl = template.Must(template.New("").Parse(` +{{if .manualOrapkiFlow}} +Orapki binary was not found. Please create oracle wallet file manually by running the following commands on the Oracle server: + +orapki wallet create -wallet {{.walletDir}} -auto_login_only +orapki wallet import_pkcs12 -wallet {{.walletDir}} -auto_login_only -pkcs12file {{.output}}.p12 -pkcs12pwd {{.password}} +orapki wallet add -wallet {{.walletDir}} -trusted_cert -auto_login_only -cert {{.output}}.crt +{{end}} +To enable mutual TLS on your Oracle server, add the following settings to Oracle sqlnet.ora configuration file: + +WALLET_LOCATION = (SOURCE = (METHOD = FILE)(METHOD_DATA = (DIRECTORY = /path/to/oracleWalletDir))) +SSL_CLIENT_AUTHENTICATION = TRUE +SQLNET.AUTHENTICATION_SERVICES = (TCPS) + + +To enable mutual TLS on your Oracle server, add the following TCPS entries to listener.ora configuration file: + +LISTENER = + (DESCRIPTION_LIST = + (DESCRIPTION = + (ADDRESS = (PROTOCOL = TCPS)(HOST = 0.0.0.0)(PORT = 2484)) + ) + ) + +WALLET_LOCATION = (SOURCE = (METHOD = FILE)(METHOD_DATA = (DIRECTORY = /path/to/oracleWalletDir))) +SSL_CLIENT_AUTHENTICATION = TRUE +`)) + scyllaAuthSignTpl = template.Must(template.New("").Parse(`Database credentials have been written to {{.files}}. To enable mutual TLS on your Scylla server, add the following to your @@ -942,6 +982,14 @@ func (a *AuthCommand) checkProxyAddr(ctx context.Context, clusterAPI auth.Client return trace.BadParameter("couldn't find registered public proxies, specify --proxy when using --format=%q", identityfile.FormatKubernetes) } +func (a *AuthCommand) generateDBOracleCert(ctx context.Context, api auth.ClientI) error { + key, err := client.GenerateRSAKey() + if err != nil { + return trace.Wrap(err) + } + return a.generateDatabaseKeysForKey(ctx, api, key) +} + func parseURL(rawurl string) (*url.URL, error) { u, err := url.Parse(rawurl) if err != nil { diff --git a/tool/tctl/common/collection.go b/tool/tctl/common/collection.go index 74dad0103db36..550531bc85035 100644 --- a/tool/tctl/common/collection.go +++ b/tool/tctl/common/collection.go @@ -36,7 +36,6 @@ import ( "github.com/gravitational/teleport/lib/reversetunnel" "github.com/gravitational/teleport/lib/sshutils" "github.com/gravitational/teleport/lib/utils" - "github.com/gravitational/teleport/tool/tctl/common/device" "github.com/gravitational/teleport/tool/tctl/common/loginrule" ) @@ -1053,11 +1052,11 @@ type deviceCollection struct { } func (c *deviceCollection) resources() []types.Resource { - r := make([]types.Resource, len(c.devices)) - for i, resource := range c.devices { - r[i] = device.ProtoToResource(resource) + resources := make([]types.Resource, len(c.devices)) + for i, dev := range c.devices { + resources[i] = types.DeviceToResource(dev) } - return r + return resources } func (c *deviceCollection) writeText(w io.Writer) error { diff --git a/tool/tctl/common/device/resource.go b/tool/tctl/common/device/resource.go deleted file mode 100644 index ae05f2ee757ba..0000000000000 --- a/tool/tctl/common/device/resource.go +++ /dev/null @@ -1,211 +0,0 @@ -// Copyright 2023 Gravitational, Inc -// -// 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. - -package device - -import ( - "time" - - "github.com/google/uuid" - "github.com/gravitational/trace" - "google.golang.org/protobuf/types/known/timestamppb" - - devicepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1" - "github.com/gravitational/teleport/api/types" - "github.com/gravitational/teleport/lib/devicetrust" - "github.com/gravitational/teleport/lib/utils" -) - -// Resource is a wrapper around devicepb.Device that implements types.Resource. -type Resource struct { - // ResourceHeader is embedded to implement types.Resource. - types.ResourceHeader - // Spec is the device specification. - Spec ResourceSpec `json:"spec"` -} - -// ResourceSpec is the device resource specification. -// This spec is intended to closely mirror [devicepb.Device] but swaps some data around -// to get a UX that matches with our other resource types. -type ResourceSpec struct { - // OSType is represented as a string here for user-friendly manipulation. - OSType string `json:"os_type"` - AssetTag string `json:"asset_tag"` - CreateTime time.Time `json:"create_time,omitempty"` - UpdateTime time.Time `json:"update_time,omitempty"` - EnrollToken *devicepb.DeviceEnrollToken `json:"enroll_token,omitempty"` - // EnrollStatus is represented as a string here for user-friendly manipulation. - EnrollStatus string `json:"enroll_status"` - Credential *devicepb.DeviceCredential `json:"credential,omitempty"` - CollectedData []CollectedData `json:"collected_data,omitempty"` -} - -// CollectedData mirrors [devicepb.DeviceCollectedData] but with a different -// timestamp type to achieve consistent serialization output. -type CollectedData struct { - CollectTime time.Time `json:"collect_time"` - RecordTime time.Time `json:"record_time"` - // OSType is represented as a string here for user-friendly manipulation. - OSType string `json:"os_type"` - SerialNumber string `json:"serial_number,omitempty"` -} - -// checkAndSetDefaults sanity checks Resource fields to catch simple errors, and -// sets default values for all fields with defaults. -func (r *Resource) checkAndSetDefaults() error { - // Assign defaults: - // - Kind = device - // - Metadata.Name = UUID - // - Spec.EnrollStatus = unspecified - if r.Kind == "" { - r.Kind = types.KindDevice - } - if r.Metadata.Name == "" { - r.Metadata.Name = uuid.NewString() - } - if r.Spec.EnrollStatus == "" { - r.Spec.EnrollStatus = - devicetrust.ResourceEnrollStatusToString(devicepb.DeviceEnrollStatus_DEVICE_ENROLL_STATUS_UNSPECIFIED) - } - - // Validate Metadata. - if err := r.Metadata.CheckAndSetDefaults(); err != nil { - return trace.Wrap(err) - } - - // Validate "simple" fields. - switch { - case r.Kind != types.KindDevice: // Sanity check. - return trace.BadParameter("unexpected resource kind %q, must be %q", r.Kind, types.KindDevice) - case r.Spec.OSType == "": - return trace.BadParameter("missing OS type") - case r.Spec.AssetTag == "": - return trace.BadParameter("missing asset tag") - } - - // Validate enum conversions. - if _, err := devicetrust.ResourceOSTypeFromString(r.Spec.OSType); err != nil { - return trace.Wrap(err) - } - if _, err := devicetrust.ResourceEnrollStatusFromString(r.Spec.EnrollStatus); err != nil { - return trace.Wrap(err) - } - - return nil -} - -// UnmarshalDevice parses a device in the Resource format which matches -// the expected YAML format for Teleport resources, sets default values, and -// converts to *devicepb.Device. -func UnmarshalDevice(raw []byte) (*devicepb.Device, error) { - var resource Resource - if err := utils.FastUnmarshal(raw, &resource); err != nil { - return nil, trace.Wrap(err) - } - if err := resource.checkAndSetDefaults(); err != nil { - return nil, trace.Wrap(err) - } - return resourceToProto(&resource) -} - -// ProtoToResource converts a *devicepb.Device into a *Resource which -// implements types.Resource and can be marshaled to YAML or JSON in a -// human-friendly format. -func ProtoToResource(device *devicepb.Device) *Resource { - collectedData := make([]CollectedData, 0, len(device.CollectedData)) - for _, d := range device.CollectedData { - collectedData = append(collectedData, CollectedData{ - CollectTime: d.CollectTime.AsTime(), - RecordTime: d.RecordTime.AsTime(), - OSType: devicetrust.ResourceOSTypeToString(d.OsType), - SerialNumber: d.SerialNumber, - }) - } - - r := &Resource{ - ResourceHeader: types.ResourceHeader{ - Kind: types.KindDevice, - Version: device.ApiVersion, - Metadata: types.Metadata{ - Name: device.Id, - }, - }, - Spec: ResourceSpec{ - OSType: devicetrust.ResourceOSTypeToString(device.OsType), - AssetTag: device.AssetTag, - EnrollToken: device.EnrollToken, - EnrollStatus: devicetrust.ResourceEnrollStatusToString(device.EnrollStatus), - Credential: device.Credential, - CollectedData: collectedData, - }, - } - - if device.CreateTime != nil { - r.Spec.CreateTime = device.CreateTime.AsTime() - } - - if device.UpdateTime != nil { - r.Spec.UpdateTime = device.UpdateTime.AsTime() - } - - return r -} - -func resourceToProto(r *Resource) (*devicepb.Device, error) { - osType, err := devicetrust.ResourceOSTypeFromString(r.Spec.OSType) - if err != nil { - return nil, trace.Wrap(err) - } - - enrollStatus, err := devicetrust.ResourceEnrollStatusFromString(r.Spec.EnrollStatus) - if err != nil { - return nil, trace.Wrap(err) - } - - collectedData := make([]*devicepb.DeviceCollectedData, 0, len(r.Spec.CollectedData)) - for _, d := range r.Spec.CollectedData { - dataOSType, err := devicetrust.ResourceOSTypeFromString(d.OSType) - if err != nil { - return nil, trace.Wrap(err) - } - - collectedData = append(collectedData, &devicepb.DeviceCollectedData{ - CollectTime: timestamppb.New(d.CollectTime), - RecordTime: timestamppb.New(d.RecordTime), - OsType: dataOSType, - SerialNumber: d.SerialNumber, - }) - } - - dev := &devicepb.Device{ - ApiVersion: r.Version, - Id: r.Metadata.Name, - OsType: osType, - AssetTag: r.Spec.AssetTag, - EnrollToken: r.Spec.EnrollToken, - EnrollStatus: enrollStatus, - Credential: r.Spec.Credential, - CollectedData: collectedData, - } - - if !r.Spec.CreateTime.IsZero() { - dev.CreateTime = timestamppb.New(r.Spec.CreateTime) - } - - if !r.Spec.UpdateTime.IsZero() { - dev.UpdateTime = timestamppb.New(r.Spec.UpdateTime) - } - - return dev, nil -} diff --git a/tool/tctl/common/device/resource_test.go b/tool/tctl/common/device/resource_test.go deleted file mode 100644 index b8d3e6ce981f7..0000000000000 --- a/tool/tctl/common/device/resource_test.go +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright 2023 Gravitational, Inc -// -// 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. - -package device - -import ( - "strings" - "testing" - - "github.com/stretchr/testify/require" - kyaml "k8s.io/apimachinery/pkg/util/yaml" - - devicepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1" - "github.com/gravitational/teleport/lib/defaults" - "github.com/gravitational/teleport/lib/services" -) - -// TestUnmarshalDevice tests that devices can be successfully -// unmarshalled from YAML and JSON. -func TestUnmarshalDevice(t *testing.T) { - t.Parallel() - for _, tc := range []struct { - desc string - input string - errorContains string - expected *devicepb.Device - }{ - { - desc: "success", - input: `--- -kind: device -version: v1 -metadata: - name: xaa -spec: - asset_tag: mymachine - os_type: macos - enroll_status: enrolled -`, - expected: &devicepb.Device{ - Id: "xaa", - AssetTag: "mymachine", - OsType: devicepb.OSType_OS_TYPE_MACOS, - ApiVersion: "v1", - EnrollStatus: devicepb.DeviceEnrollStatus_DEVICE_ENROLL_STATUS_ENROLLED, - CollectedData: []*devicepb.DeviceCollectedData{}, - }, - }, - { - desc: "fail string as num", - errorContains: `ReadString: expects " or n`, - input: `--- -kind: device -version: v1 -metadata: - name: secretid -spec: - asset_tag: 4 - os_type: macos - enroll_status: enrolled -`, - }, - } { - t.Run(tc.desc, func(t *testing.T) { - // Mimic tctl resource command by using the same decoder and - // initially unmarshalling into services.UnknownResource - reader := strings.NewReader(tc.input) - decoder := kyaml.NewYAMLOrJSONDecoder(reader, defaults.LookaheadBufSize) - var raw services.UnknownResource - err := decoder.Decode(&raw) - require.NoError(t, err) - require.Equal(t, "device", raw.Kind) - - out, err := UnmarshalDevice(raw.Raw) - if tc.errorContains != "" { - require.ErrorContains(t, err, tc.errorContains, "error from UnmarshalDevice does not contain the expected string") - return - } - require.NoError(t, err, "UnmarshalDevice returned unexpected error") - - require.Equal(t, tc.expected, out, "unmarshalled device does not match what was expected") - }) - } -} diff --git a/tool/tctl/common/helpers_test.go b/tool/tctl/common/helpers_test.go index 252b9295730a3..de434c6fc8b9b 100644 --- a/tool/tctl/common/helpers_test.go +++ b/tool/tctl/common/helpers_test.go @@ -85,6 +85,13 @@ func getAuthClient(ctx context.Context, t *testing.T, fc *config.FileConfig, opt client, err := authclient.Connect(ctx, clientConfig) require.NoError(t, err) + + t.Cleanup(func() { + if closer, ok := client.(io.Closer); ok { + closer.Close() + } + }) + return client } diff --git a/tool/tctl/common/resource_command.go b/tool/tctl/common/resource_command.go index 9cfec0a53697c..970302c1a95f6 100644 --- a/tool/tctl/common/resource_command.go +++ b/tool/tctl/common/resource_command.go @@ -46,7 +46,6 @@ import ( "github.com/gravitational/teleport/lib/service/servicecfg" "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/utils" - "github.com/gravitational/teleport/tool/tctl/common/device" "github.com/gravitational/teleport/tool/tctl/common/loginrule" ) @@ -784,25 +783,42 @@ func (rc *ResourceCommand) createSAMLIdPServiceProvider(ctx context.Context, cli } func (rc *ResourceCommand) createDevice(ctx context.Context, client auth.ClientI, raw services.UnknownResource) error { - if rc.IsForced() { - fmt.Printf("Warning: Devices cannot be overwritten with the --force flag\n") + res, err := types.UnmarshalDevice(raw.Raw) + if err != nil { + return trace.Wrap(err) } - - dev, err := device.UnmarshalDevice(raw.Raw) + dev, err := types.DeviceFromResource(res) if err != nil { return trace.Wrap(err) } - // TODO(codingllama): Figure out a way to call BulkCreateDevices here? - _, err = client.DevicesClient().CreateDevice(ctx, &devicepb.CreateDeviceRequest{ - Device: dev, - CreateAsResource: true, - }) + if rc.IsForced() { + _, err = client.DevicesClient().UpsertDevice(ctx, &devicepb.UpsertDeviceRequest{ + Device: dev, + CreateAsResource: true, + }) + // err checked below + } else { + _, err = client.DevicesClient().CreateDevice(ctx, &devicepb.CreateDeviceRequest{ + Device: dev, + CreateAsResource: true, + }) + // err checked below + } if err != nil { return trail.FromGRPC(err) } - fmt.Printf("Device %v/%v added to the inventory\n", dev.AssetTag, devicetrust.FriendlyOSType(dev.OsType)) + verb := "created" + if rc.IsForced() { + verb = "updated" + } + + fmt.Printf("Device %v/%v %v\n", + dev.AssetTag, + devicetrust.FriendlyOSType(dev.OsType), + verb, + ) return nil } diff --git a/tool/tsh/app.go b/tool/tsh/app.go index 3db6aa4be2b5e..65c7574741670 100644 --- a/tool/tsh/app.go +++ b/tool/tsh/app.go @@ -487,18 +487,7 @@ func pickActiveApp(cf *CLIConf) (*tlsca.RouteToApp, error) { // removeAppLocalFiles removes generated local files for the provided app. func removeAppLocalFiles(profile *client.ProfileStatus, appName string) { - removeFileIfExist(profile.AppLocalCAPath(appName)) -} - -// removeFileIfExist removes a local file if it exists. -func removeFileIfExist(filePath string) { - if !utils.FileExists(filePath) { - return - } - - if err := os.Remove(filePath); err != nil { - log.WithError(err).Warnf("Failed to remove %v", filePath) - } + utils.RemoveFileIfExist(profile.AppLocalCAPath(appName)) } // loadAppSelfSignedCA loads self-signed CA for provided app, or tries to diff --git a/tool/tsh/db.go b/tool/tsh/db.go index 55f5e88a5ae15..592d2b53bc9c8 100644 --- a/tool/tsh/db.go +++ b/tool/tsh/db.go @@ -42,6 +42,7 @@ import ( "github.com/gravitational/teleport/lib/client" dbprofile "github.com/gravitational/teleport/lib/client/db" "github.com/gravitational/teleport/lib/client/db/dbcmd" + "github.com/gravitational/teleport/lib/client/db/oracle" "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/srv/alpnproxy" @@ -294,7 +295,7 @@ func checkAndSetDBRouteDefaults(r *tlsca.RouteToDatabase) error { // Elasticsearch needs database username too. if r.Username == "" { switch r.Protocol { - case defaults.ProtocolMongoDB, defaults.ProtocolElasticsearch: + case defaults.ProtocolMongoDB, defaults.ProtocolElasticsearch, defaults.ProtocolOracle: return trace.BadParameter("please provide the database user name using the --db-user flag") case defaults.ProtocolRedis: // Default to "default" in the same way as Redis does. We need the username to check access on our side. @@ -309,6 +310,12 @@ func checkAndSetDBRouteDefaults(r *tlsca.RouteToDatabase) error { r.ServiceName, defaults.ReadableDatabaseProtocol(r.Protocol), r.Database) r.Database = "" } + } else { + switch r.Protocol { + // Always require db-name for Oracle Protocol. + case defaults.ProtocolOracle: + return trace.BadParameter("please provide the database name using the --db-name flag") + } } return nil } @@ -324,12 +331,12 @@ func databaseLogin(cf *CLIConf, tc *client.TeleportClient, route tlsca.RouteToDa return trace.Wrap(err) } + var key *client.Key // Identity files themselves act as the database credentials (if any), so // don't bother fetching new certs. if profile.IsVirtual { log.Info("Note: already logged in due to an identity file (`-i ...`); will only update database config files.") } else { - var key *client.Key if err = client.RetryWithRelogin(cf.Context, tc, func() error { key, err = tc.IssueUserCertsWithMFA(cf.Context, client.ReissueParams{ RouteToCluster: tc.SiteName, @@ -350,6 +357,16 @@ func databaseLogin(cf *CLIConf, tc *client.TeleportClient, route tlsca.RouteToDa } } + if route.Protocol == defaults.ProtocolOracle { + if err := generateDBLocalProxyCert(key, profile); err != nil { + return trace.Wrap(err) + } + err = oracle.GenerateClientConfiguration(key, route, profile) + if err != nil { + return trace.Wrap(err) + } + } + // Refresh the profile. profile, err = tc.ProfileStatus() if err != nil { @@ -589,7 +606,7 @@ func maybeStartLocalProxy(ctx context.Context, cf *CLIConf, log.Debugf("Starting local proxy because: %v", strings.Join(requires.localProxyReasons, ", ")) } - listener, err := net.Listen("tcp", "localhost:0") + listener, err := createLocalProxyListener("localhost:0", route, profile) if err != nil { return nil, trace.Wrap(err) } @@ -653,6 +670,25 @@ type localProxyConfig struct { tunnel bool } +func createLocalProxyListener(addr string, route *tlsca.RouteToDatabase, profile *client.ProfileStatus) (net.Listener, error) { + if route.Protocol == defaults.ProtocolOracle { + localCert, err := tls.LoadX509KeyPair( + profile.DatabaseLocalCAPath(), + profile.KeyPath(), + ) + if err != nil { + return nil, trace.Wrap(err) + } + l, err := tls.Listen("tcp", addr, &tls.Config{ + Certificates: []tls.Certificate{localCert}, + ServerName: "localhost", + }) + return l, trace.Wrap(err) + } + l, err := net.Listen("tcp", addr) + return l, trace.Wrap(err) +} + // prepareLocalProxyOptions created localProxyOpts needed to create local proxy from localProxyConfig. func prepareLocalProxyOptions(arg *localProxyConfig) ([]alpnproxy.LocalProxyConfigOpt, error) { if err := checkAndSetDBRouteDefaults(&arg.route); err != nil { @@ -861,9 +897,17 @@ func getDatabase(cf *CLIConf, tc *client.TeleportClient, dbName string) (types.D func needDatabaseRelogin(cf *CLIConf, tc *client.TeleportClient, route *tlsca.RouteToDatabase, profile *client.ProfileStatus, requires *dbLocalProxyRequirement) (bool, error) { if (requires.localProxy && requires.tunnel) || isLocalProxyTunnelRequested(cf) { - // We don't need to login if using a local proxy tunnel, - // because a local proxy tunnel will handle db login itself. - return false, nil + switch route.Protocol { + case defaults.ProtocolOracle: + // Oracle Protocol needs to generate a local configuration files. + // thus even is tunnel mode was requested the login flow should check + // if the Oracle client files should be updated. + default: + // We don't need to login if using a local proxy tunnel, + // because a local proxy tunnel will handle db login itself. + return false, nil + + } } found := false activeDatabases, err := profile.DatabasesForCluster(tc.SiteName) @@ -1121,7 +1165,9 @@ func getDBLocalProxyRequirement(tc *client.TeleportClient, route *tlsca.RouteToD case defaults.ProtocolSnowflake, defaults.ProtocolDynamoDB, defaults.ProtocolSQLServer, - defaults.ProtocolCassandra: + defaults.ProtocolCassandra, + defaults.ProtocolOracle: + // Some protocols only work in the local tunnel mode. out.addLocalProxyWithTunnel(formatDBProtocolReason(route.Protocol)) case defaults.ProtocolMySQL: diff --git a/tool/tsh/db_test.go b/tool/tsh/db_test.go index 55331ee623728..9b6ba2fd46af6 100644 --- a/tool/tsh/db_test.go +++ b/tool/tsh/db_test.go @@ -661,6 +661,12 @@ func TestFormatDatabaseConnectArgs(t *testing.T) { route: tlsca.RouteToDatabase{Protocol: defaults.ProtocolDynamoDB, ServiceName: "svc"}, wantFlags: []string{"--db-user=", "svc"}, }, + { + name: "match user and db name, oracle protocol", + cluster: "", + route: tlsca.RouteToDatabase{Protocol: defaults.ProtocolOracle, ServiceName: "svc"}, + wantFlags: []string{"--db-user=", "--db-name=", "svc"}, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/tool/tsh/kube.go b/tool/tsh/kube.go index be06101147871..c2fd186331c2b 100644 --- a/tool/tsh/kube.go +++ b/tool/tsh/kube.go @@ -98,7 +98,7 @@ func newKubeJoinCommand(parent *kingpin.CmdClause) *kubeJoinCommand { CmdClause: parent.Command("join", "Join an active Kubernetes session."), } - c.Flag("mode", "Mode of joining the session, valid modes are observer and moderator").Short('m').Default("moderator").StringVar(&c.mode) + c.Flag("mode", "Mode of joining the session, valid modes are observer, moderator and peer.").Short('m').Default("observer").EnumVar(&c.mode, "observer", "moderator", "peer") c.Flag("cluster", clusterHelp).Short('c').StringVar(&c.siteName) c.Arg("session", "The ID of the target session.").Required().StringVar(&c.session) return c @@ -313,6 +313,10 @@ type ExecOptions struct { GetPodTimeout time.Duration Config *restclient.Config displayParticipantRequirements bool + // invited is a list of users that are invited to the session + invited []string + // reason is the reason for the session + reason string } // Run executes a validated remote execution against a pod. @@ -382,7 +386,9 @@ func (p *ExecOptions) Run(ctx context.Context) error { Name(pod.Name). Namespace(pod.Namespace). SubResource("exec"). - Param("displayParticipantRequirements", strconv.FormatBool(p.displayParticipantRequirements)) + Param(teleport.KubeSessionDisplayParticipantRequirementsQueryParam, strconv.FormatBool(p.displayParticipantRequirements)). + Param(teleport.KubeSessionInvitedQueryParam, strings.Join(p.invited, ",")). + Param(teleport.KubeSessionReasonQueryParam, p.reason) req.VersionedParams(&corev1.PodExecOptions{ Container: containerName, Command: p.Command, @@ -454,6 +460,8 @@ func (c *kubeExecCommand) run(cf *CLIConf) error { p.restClientGetter = f p.Executor = &DefaultRemoteExecutor{} p.displayParticipantRequirements = c.displayParticipantRequirements + p.invited = strings.Split(c.invited, ",") + p.reason = c.reason p.Namespace, p.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace() if err != nil { return trace.Wrap(err) diff --git a/tool/tsh/kube_proxy.go b/tool/tsh/kube_proxy.go index 378f694794ea9..4676537ea808f 100644 --- a/tool/tsh/kube_proxy.go +++ b/tool/tsh/kube_proxy.go @@ -38,6 +38,7 @@ import ( "github.com/gravitational/teleport/lib/kube/kubeconfig" "github.com/gravitational/teleport/lib/srv/alpnproxy" "github.com/gravitational/teleport/lib/srv/alpnproxy/common" + "github.com/gravitational/teleport/lib/utils" ) type proxyKubeCommand struct { @@ -257,7 +258,7 @@ func (k *kubeLocalProxy) Start(ctx context.Context) error { // Close removes the temporary kubeconfig and closes the listeners. func (k *kubeLocalProxy) Close() error { - removeFileIfExist(k.KubeConfigPath()) + utils.RemoveFileIfExist(k.KubeConfigPath()) return trace.NewAggregate(k.forwardProxy.Close(), k.localProxy.Close()) } diff --git a/tool/tsh/proxy.go b/tool/tsh/proxy.go index 0e61bb9225298..49b86ae8e23a3 100644 --- a/tool/tsh/proxy.go +++ b/tool/tsh/proxy.go @@ -19,6 +19,7 @@ package main import ( "context" "crypto/tls" + "crypto/x509/pkix" "fmt" "io" "net" @@ -35,6 +36,7 @@ import ( "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/agent" + "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/client" "github.com/gravitational/teleport/api/client/webclient" "github.com/gravitational/teleport/api/constants" @@ -46,6 +48,7 @@ import ( "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/srv/alpnproxy" alpncommon "github.com/gravitational/teleport/lib/srv/alpnproxy/common" + "github.com/gravitational/teleport/lib/tlsca" "github.com/gravitational/teleport/lib/utils" ) @@ -408,10 +411,12 @@ func onProxyCommandDB(cf *CLIConf) error { randomPort = false addr = fmt.Sprintf("127.0.0.1:%s", cf.LocalProxyPort) } - listener, err := net.Listen("tcp", addr) + + listener, err := createLocalProxyListener(addr, route, profile) if err != nil { return trace.Wrap(err) } + defer func() { if err := listener.Close(); err != nil { log.WithError(err).Warnf("Failed to close listener.") @@ -473,7 +478,7 @@ func onProxyCommandDB(cf *CLIConf) error { "randomPort": randomPort, } - tmpl := chooseProxyCommandTemplate(templateArgs, commands) + tmpl := chooseProxyCommandTemplate(templateArgs, commands, route.Protocol) err = tmpl.Execute(os.Stdout, templateArgs) if err != nil { return trace.Wrap(err) @@ -519,10 +524,14 @@ type templateCommandItem struct { Command string } -func chooseProxyCommandTemplate(templateArgs map[string]any, commands []dbcmd.CommandAlternative) *template.Template { +func chooseProxyCommandTemplate(templateArgs map[string]any, commands []dbcmd.CommandAlternative, protocol string) *template.Template { // there is only one command, use plain template. if len(commands) == 1 { templateArgs["command"] = formatCommand(commands[0].Command) + if protocol == defaults.ProtocolOracle { + templateArgs["args"] = commands[0].Command.Args + return dbProxyOracleAuthTpl + } return dbProxyAuthTpl } @@ -821,6 +830,32 @@ func makeBasicLocalProxyConfig(cf *CLIConf, tc *libclient.TeleportClient, listen } } +func generateDBLocalProxyCert(key *libclient.Key, profile *libclient.ProfileStatus) error { + path := profile.DatabaseLocalCAPath() + if utils.FileExists(path) { + return nil + + } + certPem, err := tlsca.GenerateSelfSignedCAWithConfig(tlsca.GenerateCAConfig{ + Entity: pkix.Name{ + CommonName: "localhost", + Organization: []string{"Teleport"}, + }, + Signer: key, + DNSNames: []string{"localhost"}, + IPAddresses: []net.IP{net.ParseIP(defaults.Localhost)}, + TTL: defaults.CATTL, + }) + if err != nil { + return trace.Wrap(err) + } + + if err := os.WriteFile(profile.DatabaseLocalCAPath(), certPem, teleport.FileMaskOwnerOnly); err != nil { + return trace.ConvertSystemError(err) + } + return nil +} + // dbProxyTpl is the message that gets printed to a user when a database proxy is started. var dbProxyTpl = template.Must(template.New("").Parse(`Started DB proxy on {{.address}} {{if .randomPort}}To avoid port randomization, you can choose the listening port using the --port flag. @@ -831,6 +866,10 @@ Use following credentials to connect to the {{.database}} proxy: key_file={{.key}} `)) +var templateFunctions = map[string]any{ + "contains": strings.Contains, +} + // dbProxyAuthTpl is the message that's printed for an authenticated db proxy. var dbProxyAuthTpl = template.Must(template.New("").Parse( `Started authenticated tunnel for the {{.type}} database "{{.database}}" in cluster "{{.cluster}}" on {{.address}}. @@ -840,6 +879,22 @@ Use the following command to connect to the database or to the address above usi $ {{.command}} `)) +// dbProxyOracleAuthTpl is the message that's printed for an authenticated db proxy. +var dbProxyOracleAuthTpl = template.Must(template.New("").Funcs(templateFunctions).Parse( + `Started authenticated tunnel for the {{.type}} database "{{.database}}" in cluster "{{.cluster}}" on {{.address}}. +{{if .randomPort}}To avoid port randomization, you can choose the listening port using the --port flag. +{{end}} +Use the following command to connect to the Oracle database server using CLI: + $ {{.command}} + +or using following Oracle JDBC connection string in order to connect with other GUI/CLI clients: +{{- range $val := .args}} + {{- if contains $val "jdbc:oracle:"}} + {{$val}} + {{- end}} +{{- end}} +`)) + // dbProxyAuthMultiTpl is the message that's printed for an authenticated db proxy if there are multiple command options. var dbProxyAuthMultiTpl = template.Must(template.New("").Parse( `Started authenticated tunnel for the {{.type}} database "{{.database}}" in cluster "{{.cluster}}" on {{.address}}. diff --git a/tool/tsh/proxy_test.go b/tool/tsh/proxy_test.go index 18feb1c9990b5..c13c623e58349 100644 --- a/tool/tsh/proxy_test.go +++ b/tool/tsh/proxy_test.go @@ -996,7 +996,7 @@ Use one of the following commands to connect to the database or to the address a for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { templateArgs := map[string]any{} - tpl := chooseProxyCommandTemplate(templateArgs, tt.commands) + tpl := chooseProxyCommandTemplate(templateArgs, tt.commands, "") require.Equal(t, tt.wantTemplate, tpl) require.Equal(t, tt.wantTemplateArgs, templateArgs) diff --git a/tool/tsh/tsh.go b/tool/tsh/tsh.go index 0b4f31a57f5b0..7bf8917fc490b 100644 --- a/tool/tsh/tsh.go +++ b/tool/tsh/tsh.go @@ -800,7 +800,7 @@ func Run(ctx context.Context, args []string, opts ...cliOption) error { // join join := app.Command("join", "Join the active SSH or Kubernetes session") join.Flag("cluster", clusterHelp).Short('c').StringVar(&cf.SiteName) - join.Flag("mode", "Mode of joining the session, valid modes are observer and moderator").Short('m').Default("peer").StringVar(&cf.JoinMode) + join.Flag("mode", "Mode of joining the session, valid modes are observer, moderator and peer.").Short('m').Default("observer").EnumVar(&cf.JoinMode, "observer", "moderator", "peer") join.Flag("reason", "The purpose of the session.").StringVar(&cf.Reason) join.Flag("invite", "A comma separated list of people to mark as invited for the session.").StringsVar(&cf.Invited) join.Arg("session-id", "ID of the session to join").Required().StringVar(&cf.SessionID) @@ -863,17 +863,30 @@ func Run(ctx context.Context, args []string, opts ...cliOption) error { // bench bench := app.Command("bench", "Run shell or execute a command on a remote SSH node").Hidden() bench.Flag("cluster", clusterHelp).Short('c').StringVar(&cf.SiteName) - bench.Arg("[user@]host", "Remote hostname and the login to use").Required().StringVar(&cf.UserHost) - bench.Arg("command", "Command to execute on a remote host").Required().StringsVar(&cf.RemoteCommand) - bench.Flag("port", "SSH port on a remote host").Short('p').Int32Var(&cf.NodePort) bench.Flag("duration", "Test duration").Default("1s").DurationVar(&cf.BenchDuration) bench.Flag("rate", "Requests per second rate").Default("10").IntVar(&cf.BenchRate) - bench.Flag("interactive", "Create interactive SSH session").BoolVar(&cf.BenchInteractive) bench.Flag("export", "Export the latency profile").BoolVar(&cf.BenchExport) bench.Flag("path", "Directory to save the latency profile to, default path is the current directory").Default(".").StringVar(&cf.BenchExportPath) bench.Flag("ticks", "Ticks per half distance").Default("100").Int32Var(&cf.BenchTicks) bench.Flag("scale", "Value scale in which to scale the recorded values").Default("1.0").Float64Var(&cf.BenchValueScale) + benchSSH := bench.Command("ssh", "Run SSH benchmark test") + benchSSH.Arg("[user@]host", "Remote hostname and the login to use").Required().StringVar(&cf.UserHost) + benchSSH.Arg("command", "Command to execute on a remote host").Required().StringsVar(&cf.RemoteCommand) + benchSSH.Flag("port", "SSH port on a remote host").Short('p').Int32Var(&cf.NodePort) + benchSSH.Flag("interactive", "Create interactive SSH session").BoolVar(&cf.BenchInteractive) + var benchKubeOpts benchKubeOptions + benchKube := bench.Command("kube", "Run Kube benchmark test") + benchKube.Flag("kube-namespace", "Selects the ").Default("default").StringVar(&benchKubeOpts.namespace) + benchListKube := benchKube.Command("ls", "Run a benchmark test to list Pods") + benchListKube.Arg("kube_cluster", "Kubernetes cluster to use").Required().StringVar(&cf.KubernetesCluster) + benchExecKube := benchKube.Command("exec", "Run a benchmark test to exec into the specified Pod") + benchExecKube.Arg("kube_cluster", "Kubernetes cluster to use").Required().StringVar(&cf.KubernetesCluster) + benchExecKube.Arg("pod", "Pod name to exec into").Required().StringVar(&benchKubeOpts.pod) + benchExecKube.Arg("command", "Command to execute on a pod").Required().StringsVar(&cf.RemoteCommand) + benchExecKube.Flag("container", "Selects the container to exec into.").StringVar(&benchKubeOpts.container) + benchExecKube.Flag("interactive", "Create interactive Kube session").BoolVar(&cf.BenchInteractive) + // show key show := app.Command("show", "Read an identity from file and print to stdout").Hidden() show.Arg("identity_file", "The file containing a public key or a certificate").Required().StringVar(&cf.IdentityFileIn) @@ -1129,8 +1142,31 @@ func Run(ctx context.Context, args []string, opts ...cliOption) error { err = onVersion(&cf) case ssh.FullCommand(): err = onSSH(&cf) - case bench.FullCommand(): - err = onBenchmark(&cf) + case benchSSH.FullCommand(): + err = onBenchmark( + &cf, + &benchmark.SSHBenchmark{ + Command: cf.RemoteCommand, + }, + ) + case benchListKube.FullCommand(): + err = onBenchmark( + &cf, + &benchmark.KubeListBenchmark{ + Namespace: benchKubeOpts.namespace, + }, + ) + case benchExecKube.FullCommand(): + err = onBenchmark( + &cf, + &benchmark.KubeExecBenchmark{ + Command: cf.RemoteCommand, + Namespace: benchKubeOpts.namespace, + PodName: benchKubeOpts.pod, + ContainerName: benchKubeOpts.container, + Interactive: cf.BenchInteractive, + }, + ) case join.FullCommand(): err = onJoin(&cf) case scp.FullCommand(): @@ -1404,6 +1440,12 @@ func fetchProxyVersion(cf *CLIConf) (string, string, error) { return pingRes.ServerVersion, pingRes.Proxy.SSH.PublicAddr, nil } +type benchKubeOptions struct { + pod string + container string + namespace string +} + func serializeVersion(format string, proxyVersion string, proxyPublicAddress string) (string, error) { versionInfo := struct { Version string `json:"version"` @@ -3084,17 +3126,16 @@ func onSSH(cf *CLIConf) error { } // onBenchmark executes benchmark -func onBenchmark(cf *CLIConf) error { +func onBenchmark(cf *CLIConf, suite benchmark.BenchmarkSuite) error { tc, err := makeClient(cf, false) if err != nil { return trace.Wrap(err) } cnf := benchmark.Config{ - Command: cf.RemoteCommand, MinimumWindow: cf.BenchDuration, Rate: cf.BenchRate, } - result, err := cnf.Benchmark(cf.Context, tc) + result, err := cnf.Benchmark(cf.Context, tc, suite) if err != nil { fmt.Fprintln(os.Stderr, utils.UserMessageFromError(err)) return trace.Wrap(&common.ExitCodeError{Code: 255}) diff --git a/web/packages/design/src/Icon/Icon.jsx b/web/packages/design/src/Icon/Icon.jsx index 4425ea6c3653b..db42a3f4c6c3f 100644 --- a/web/packages/design/src/Icon/Icon.jsx +++ b/web/packages/design/src/Icon/Icon.jsx @@ -16,13 +16,13 @@ limitations under the License. import React from 'react'; import styled from 'styled-components'; -import { space, fontSize, width, color } from 'styled-system'; +import { space, fontSize, width, color, lineHeight } from 'styled-system'; import '../assets/icomoon/style.css'; const Icon = styled.span` display: inline-block; transition: color 0.3s; - ${space} ${width} ${color} ${fontSize} + ${space} ${width} ${color} ${fontSize} ${lineHeight} `; Icon.displayName = `Icon`; @@ -164,6 +164,7 @@ export const ForwarderAdded = makeFontIcon( export const Github = makeFontIcon('Github', 'icon-github'); export const Google = makeFontIcon('Google', 'icon-google-plus'); export const Graph = makeFontIcon('Graph', 'icon-graph'); +export const Hashtag = makeFontIcon('Hashtag', 'icon-hashtag'); export const Home = makeFontIcon('Home', 'icon-home3'); export const Info = makeFontIcon('Info', 'icon-info_outline'); export const InfoFilled = makeFontIcon('Info', 'icon-info'); diff --git a/web/packages/design/src/Icon/Icon.story.js b/web/packages/design/src/Icon/Icon.story.js index a754eb0f85b29..04aa8954564e2 100644 --- a/web/packages/design/src/Icon/Icon.story.js +++ b/web/packages/design/src/Icon/Icon.story.js @@ -112,6 +112,7 @@ export const ListOfIcons = () => ( + diff --git a/web/packages/design/src/assets/icomoon/fonts/icomoon.svg b/web/packages/design/src/assets/icomoon/fonts/icomoon.svg index 7a7d4a111389c..72cb616f1ce6b 100644 --- a/web/packages/design/src/assets/icomoon/fonts/icomoon.svg +++ b/web/packages/design/src/assets/icomoon/fonts/icomoon.svg @@ -188,4 +188,5 @@ + \ No newline at end of file diff --git a/web/packages/design/src/assets/icomoon/fonts/icomoon.ttf b/web/packages/design/src/assets/icomoon/fonts/icomoon.ttf index 2c048e96ab770..51f8135983638 100644 Binary files a/web/packages/design/src/assets/icomoon/fonts/icomoon.ttf and b/web/packages/design/src/assets/icomoon/fonts/icomoon.ttf differ diff --git a/web/packages/design/src/assets/icomoon/fonts/icomoon.woff b/web/packages/design/src/assets/icomoon/fonts/icomoon.woff index 775de3ce89d87..e188aded30732 100644 Binary files a/web/packages/design/src/assets/icomoon/fonts/icomoon.woff and b/web/packages/design/src/assets/icomoon/fonts/icomoon.woff differ diff --git a/web/packages/design/src/assets/icomoon/fonts/icomoon.woff2 b/web/packages/design/src/assets/icomoon/fonts/icomoon.woff2 index 5644d418cc2ab..8dbec5707339a 100644 Binary files a/web/packages/design/src/assets/icomoon/fonts/icomoon.woff2 and b/web/packages/design/src/assets/icomoon/fonts/icomoon.woff2 differ diff --git a/web/packages/design/src/assets/icomoon/style.css b/web/packages/design/src/assets/icomoon/style.css index 67cf31a35caa2..2ddc852e47ea2 100644 --- a/web/packages/design/src/assets/icomoon/style.css +++ b/web/packages/design/src/assets/icomoon/style.css @@ -1,6 +1,6 @@ @font-face { font-family: 'icomoon'; - src: url("data:application/x-font-ttf;charset=utf-8;base64,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") format('truetype'); + src: url("data:application/x-font-ttf;charset=utf-8;base64,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") format('truetype'); font-weight: normal; font-style: normal; font-display: block; @@ -555,6 +555,9 @@ .icon-credit-card-alt:before { content: "\f283"; } +.icon-hashtag:before { + content: "\f292"; +} .icon-spinner8:before { content: "\e981"; } diff --git a/web/packages/shared/components/FieldInput/FieldInput.tsx b/web/packages/shared/components/FieldInput/FieldInput.tsx index dbcc244897040..5bde2409396f5 100644 --- a/web/packages/shared/components/FieldInput/FieldInput.tsx +++ b/web/packages/shared/components/FieldInput/FieldInput.tsx @@ -33,6 +33,7 @@ const FieldInput = forwardRef( min, max, rule = defaultRule, + name, type = 'text', autoFocus = false, autoComplete = 'off', @@ -53,6 +54,7 @@ const FieldInput = forwardRef( mt={1} ref={ref} type={type} + name={name} hasError={hasError || markAsError} placeholder={placeholder} autoFocus={autoFocus} diff --git a/web/packages/shared/components/FieldInput/__snapshots__/FieldInput.test.tsx.snap b/web/packages/shared/components/FieldInput/__snapshots__/FieldInput.test.tsx.snap index a3ad6cd25d472..fb564416b7b2d 100644 --- a/web/packages/shared/components/FieldInput/__snapshots__/FieldInput.test.tsx.snap +++ b/web/packages/shared/components/FieldInput/__snapshots__/FieldInput.test.tsx.snap @@ -82,7 +82,6 @@ exports[`snapshot tests 1`] = `
@@ -1423,10 +1463,10 @@ exports[`render with all access 1`] = ` width="24px" >
- RDS & Aurora + Redshift PostgreSQL
@@ -1451,7 +1491,7 @@ exports[`render with all access 1`] = ` @@ -1463,10 +1503,10 @@ exports[`render with all access 1`] = ` width="24px" >
- RDS Proxy + Redshift Serverless
@@ -1491,7 +1531,7 @@ exports[`render with all access 1`] = `
@@ -1517,13 +1557,13 @@ exports[`render with all access 1`] = ` color="#a8afb2" font-size="12px" > - Amazon Web Services (AWS) + Azure
- Redshift PostgreSQL + Cache for Redis
@@ -1531,7 +1571,7 @@ exports[`render with all access 1`] = `
@@ -1557,13 +1597,13 @@ exports[`render with all access 1`] = ` color="#a8afb2" font-size="12px" > - Amazon Web Services (AWS) + Azure
- Redshift Serverless + PostgreSQL
@@ -1571,7 +1611,7 @@ exports[`render with all access 1`] = `
@@ -1603,7 +1643,7 @@ exports[`render with all access 1`] = ` class="c13" color="white" > - Cache for Redis + MySQL @@ -1611,7 +1651,7 @@ exports[`render with all access 1`] = ` @@ -1643,7 +1683,7 @@ exports[`render with all access 1`] = ` class="c13" color="white" > - PostgreSQL & MySQL + SQL Server (Preview) @@ -1651,7 +1691,7 @@ exports[`render with all access 1`] = ` @@ -1677,7 +1717,7 @@ exports[`render with all access 1`] = ` color="#a8afb2" font-size="12px" > - Azure + Microsoft
+ +
+
+ +
+
+
+ Amazon Web Services (AWS) +
+
+ RDS Proxy +
+
+
+
+ +
+
+ +
+
+
+ Database +
+
+ High Availability +
+
+
+
+ +
+
+ +
+
+
+ Database +
+
+ Dynamic Registration +
+
+
+
- RDS & Aurora + Redshift PostgreSQL
@@ -2996,10 +3156,10 @@ exports[`render with no access 1`] = ` width="24px" >
- RDS Proxy + Redshift Serverless
@@ -3054,13 +3214,13 @@ exports[`render with no access 1`] = ` color="#a8afb2" font-size="12px" > - Amazon Web Services (AWS) + Azure
- Redshift PostgreSQL + Cache for Redis
@@ -3098,13 +3258,13 @@ exports[`render with no access 1`] = ` color="#a8afb2" font-size="12px" > - Amazon Web Services (AWS) + Azure
- Redshift Serverless + PostgreSQL
@@ -3148,7 +3308,7 @@ exports[`render with no access 1`] = ` class="c14" color="white" > - Cache for Redis + MySQL @@ -3192,7 +3352,7 @@ exports[`render with no access 1`] = ` class="c14" color="white" > - PostgreSQL & MySQL + SQL Server (Preview) @@ -3230,7 +3390,7 @@ exports[`render with no access 1`] = ` color="#a8afb2" font-size="12px" > - Azure + Microsoft
+ +
+ Lacking Permissions +
+
+
+ +
+
+
+ Amazon Web Services (AWS) +
+
+ RDS Proxy +
+
+
+
+ +
+ Lacking Permissions +
+
+
+ +
+
+
+ Database +
+
+ High Availability +
+
+
+
+ +
+ Lacking Permissions +
+
+
+ +
+
+
+ Database +
+
+ Dynamic Registration +
+
+
+
- RDS & Aurora + Redshift PostgreSQL
@@ -4618,10 +4910,10 @@ exports[`render with partial access 1`] = ` width="24px" >
- RDS Proxy + Redshift Serverless
@@ -4676,13 +4968,13 @@ exports[`render with partial access 1`] = ` color="#a8afb2" font-size="12px" > - Amazon Web Services (AWS) + Azure
- Redshift PostgreSQL + Cache for Redis
@@ -4720,13 +5012,13 @@ exports[`render with partial access 1`] = ` color="#a8afb2" font-size="12px" > - Amazon Web Services (AWS) + Azure
- Redshift Serverless + PostgreSQL
@@ -4770,7 +5062,7 @@ exports[`render with partial access 1`] = ` class="c13" color="white" > - Cache for Redis + MySQL @@ -4814,7 +5106,7 @@ exports[`render with partial access 1`] = ` class="c13" color="white" > - PostgreSQL & MySQL + SQL Server (Preview) @@ -4852,7 +5144,7 @@ exports[`render with partial access 1`] = ` color="#a8afb2" font-size="12px" > - Azure + Microsoft
+ +
+ Lacking Permissions +
+
+
+ +
+
+
+ Amazon Web Services (AWS) +
+
+ RDS Proxy +
+
+
+
+ +
+ Lacking Permissions +
+
+
+ +
+
+
+ Database +
+
+ High Availability +
+
+
+
+ +
+ Lacking Permissions +
+
+
+ +
+
+
+ Database +
+
+ Dynamic Registration +
+
+
+
, Application: , + Database: , Aws: , Azure: , Cockroach: , diff --git a/web/packages/teleport/src/Discover/SelectResource/resources.tsx b/web/packages/teleport/src/Discover/SelectResource/resources.tsx index 36dad739586d8..4236897602cdc 100644 --- a/web/packages/teleport/src/Discover/SelectResource/resources.tsx +++ b/web/packages/teleport/src/Discover/SelectResource/resources.tsx @@ -14,10 +14,16 @@ * limitations under the License. */ +import { DiscoverEventResource } from 'teleport/services/userEvent'; + import { ResourceKind } from '../Shared/ResourceKind'; -import { DATABASES, DATABASES_UNGUIDED } from './databases'; -import { ResourceSpec, DatabaseLocation } from './types'; +import { + DATABASES, + DATABASES_UNGUIDED, + DATABASES_UNGUIDED_DOC, +} from './databases'; +import { ResourceSpec, DatabaseLocation, DatabaseEngine } from './types'; import { icons } from './icons'; const baseServerKeywords = 'server node'; @@ -27,30 +33,35 @@ export const SERVERS: ResourceSpec[] = [ kind: ResourceKind.Server, keywords: baseServerKeywords + 'ubuntu', Icon: icons.Linux, + event: DiscoverEventResource.Server, }, { name: 'Debian 8+', kind: ResourceKind.Server, keywords: baseServerKeywords + 'debian', Icon: icons.Linux, + event: DiscoverEventResource.Server, }, { name: 'RHEL/CentOS 7+', kind: ResourceKind.Server, keywords: baseServerKeywords + 'rhel centos', Icon: icons.Linux, + event: DiscoverEventResource.Server, }, { name: 'Amazon Linux 2', kind: ResourceKind.Server, keywords: baseServerKeywords + 'amazon linux', Icon: icons.Aws, + event: DiscoverEventResource.Server, }, { name: 'macOS (Intel)', kind: ResourceKind.Server, keywords: baseServerKeywords + 'mac macos intel', Icon: icons.Apple, + event: DiscoverEventResource.Server, }, ]; @@ -62,6 +73,7 @@ export const APPLICATIONS: ResourceSpec[] = [ Icon: icons.Application, unguidedLink: 'https://goteleport.com/docs/application-access/getting-started/', + event: DiscoverEventResource.ApplicationHttp, }, ]; @@ -71,6 +83,7 @@ export const WINDOWS_DESKTOPS: ResourceSpec[] = [ kind: ResourceKind.Desktop, keywords: 'windows desktop active directory ad', Icon: icons.Windows, + event: DiscoverEventResource.WindowsDesktop, }, // { // name: 'Non Active Directory', @@ -87,6 +100,7 @@ export const KUBERNETES: ResourceSpec[] = [ kind: ResourceKind.Kubernetes, keywords: 'kubernetes cluster kubes', Icon: icons.Kube, + event: DiscoverEventResource.Kubernetes, }, ]; @@ -97,6 +111,7 @@ export const RESOURCES: ResourceSpec[] = [ ...SERVERS, ...DATABASES, ...DATABASES_UNGUIDED, + ...DATABASES_UNGUIDED_DOC, ]; export function getResourcePretitle(r: ResourceSpec) { @@ -108,14 +123,20 @@ export function getResourcePretitle(r: ResourceSpec) { case ResourceKind.Database: if (r.dbMeta) { switch (r.dbMeta.location) { - case DatabaseLocation.AWS: + case DatabaseLocation.Aws: return 'Amazon Web Services (AWS)'; - case DatabaseLocation.GCP: + case DatabaseLocation.Gcp: return 'Google Cloud Provider (GCP)'; case DatabaseLocation.SelfHosted: return 'Self-Hosted'; case DatabaseLocation.Azure: return 'Azure'; + case DatabaseLocation.Microsoft: + return 'Microsoft'; + } + + if (r.dbMeta.engine === DatabaseEngine.Doc) { + return 'Database'; } } break; diff --git a/web/packages/teleport/src/Discover/SelectResource/types.ts b/web/packages/teleport/src/Discover/SelectResource/types.ts index 5d2baeae93b31..8f5aaf6b5e757 100644 --- a/web/packages/teleport/src/Discover/SelectResource/types.ts +++ b/web/packages/teleport/src/Discover/SelectResource/types.ts @@ -16,26 +16,33 @@ import { ResourceKind } from '../Shared/ResourceKind'; +import type { DiscoverEventResource } from 'teleport/services/userEvent'; + export enum DatabaseLocation { - AWS, + Aws, SelfHosted, - GCP, + Gcp, Azure, - Snowflake, - Mongo, + Microsoft, TODO, } +// DatabaseEngine represents the db "protocol". export enum DatabaseEngine { - PostgreSQL, - MySQL, - Mongo, - SQLServer, - RedShift, + Postgres, + MySql, + MongoDb, Redis, + CoackroachDb, + SqlServer, + Snowflake, + Cassandra, + ElasticSearch, + DynamoDb, + Redshift, - TODO, + Doc, } export interface ResourceSpec { @@ -54,4 +61,8 @@ export interface ResourceSpec { // It is used as a flag, that when defined, means that // this resource is not "guided" (has no UI interactive flow). unguidedLink?: string; + // event is the expected backend enum event name that describes + // the type of this resource (eg. server v. kubernetes), + // used for usage reporting. + event: DiscoverEventResource; } diff --git a/web/packages/teleport/src/Discover/Shared/ResourceKind.ts b/web/packages/teleport/src/Discover/Shared/ResourceKind.ts index 61ad34401edf4..0852bb899a7ae 100644 --- a/web/packages/teleport/src/Discover/Shared/ResourceKind.ts +++ b/web/packages/teleport/src/Discover/Shared/ResourceKind.ts @@ -14,14 +14,6 @@ * limitations under the License. */ -import { DiscoverEventResource } from 'teleport/services/userEvent'; - -import { - DatabaseEngine, - DatabaseLocation, - ResourceSpec, -} from '../SelectResource/types'; - import type { JoinRole } from 'teleport/services/joinToken'; export enum ResourceKind { @@ -46,68 +38,3 @@ export function resourceKindToJoinRole(kind: ResourceKind): JoinRole { return 'Node'; } } - -export function resourceKindToEventResource( - resourceSpec: ResourceSpec -): DiscoverEventResource { - switch (resourceSpec.kind) { - case ResourceKind.Database: - const { engine, location } = resourceSpec.dbMeta; - if (location === DatabaseLocation.AWS) { - if (engine === DatabaseEngine.PostgreSQL) { - return DiscoverEventResource.DatabasePostgresRds; - } - if (engine === DatabaseEngine.MySQL) { - return DiscoverEventResource.DatabaseMysqlRds; - } - if (engine === DatabaseEngine.SQLServer) { - return DiscoverEventResource.DatabaseSqlServerRds; - } - if (engine === DatabaseEngine.RedShift) { - return DiscoverEventResource.DatabasePostgresRedshift; - } - } - - if (location === DatabaseLocation.SelfHosted) { - if (engine === DatabaseEngine.PostgreSQL) { - return DiscoverEventResource.DatabasePostgresSelfHosted; - } - if (engine === DatabaseEngine.MySQL) { - return DiscoverEventResource.DatabaseMysqlSelfHosted; - } - if (engine === DatabaseEngine.Mongo) { - return DiscoverEventResource.DatabaseMongodbSelfHosted; - } - if (engine === DatabaseEngine.SQLServer) { - return DiscoverEventResource.DatabaseSqlServerSelfHosted; - } - if (engine === DatabaseEngine.Redis) { - return DiscoverEventResource.DatabaseRedisSelfHosted; - } - } - - if (location === DatabaseLocation.GCP) { - if (engine === DatabaseEngine.PostgreSQL) { - return DiscoverEventResource.DatabasePostgresGcp; - } - if (engine === DatabaseEngine.MySQL) { - return DiscoverEventResource.DatabaseMysqlGcp; - } - if (engine === DatabaseEngine.SQLServer) { - return DiscoverEventResource.DatabaseMysqlGcp; - } - } - console.error(`resource database event not defined for ${resourceSpec}`); - return null; - case ResourceKind.Desktop: - return DiscoverEventResource.WindowsDesktop; - case ResourceKind.Kubernetes: - return DiscoverEventResource.Kubernetes; - case ResourceKind.Server: - return DiscoverEventResource.Server; - case ResourceKind.Application: - return DiscoverEventResource.ApplicationHttp; - default: - console.error(`resource event not defined for ${resourceSpec}`); - } -} diff --git a/web/packages/teleport/src/Discover/useDiscover.test.tsx b/web/packages/teleport/src/Discover/useDiscover.test.tsx index 28780a6bf9055..a1623a10f8196 100644 --- a/web/packages/teleport/src/Discover/useDiscover.test.tsx +++ b/web/packages/teleport/src/Discover/useDiscover.test.tsx @@ -111,7 +111,6 @@ describe('emitting events', () => { expect.objectContaining({ id: expect.any(String), currEventName: DiscoverEvent.DeployService, - eventResourceName: DiscoverEventResource.Server, }) ); @@ -162,7 +161,6 @@ describe('emitting events', () => { expect(result.current.eventState).toEqual({ id, currEventName: DiscoverEvent.PrincipalsConfigure, - eventResourceName: DiscoverEventResource.Server, }); jest.resetAllMocks(); diff --git a/web/packages/teleport/src/Discover/useDiscover.tsx b/web/packages/teleport/src/Discover/useDiscover.tsx index 5bf5c5b795582..47c292acf5e9b 100644 --- a/web/packages/teleport/src/Discover/useDiscover.tsx +++ b/web/packages/teleport/src/Discover/useDiscover.tsx @@ -32,7 +32,6 @@ import { ResourceViewConfig, View, } from './flow'; -import { resourceKindToEventResource } from './Shared/ResourceKind'; import { viewConfigs } from './resourceViewConfigs'; import type { Node } from 'teleport/services/nodes'; @@ -61,7 +60,6 @@ type EventState = { id: string; currEventName: DiscoverEvent; manuallyEmitSuccessEvent: boolean; - eventResourceName: DiscoverEventResource; }; type CustomEventInput = { @@ -87,19 +85,19 @@ export function DiscoverProvider(props: React.PropsWithChildren) { const emitEvent = useCallback( (status: DiscoverEventStepStatus, custom?: CustomEventInput) => { - const { id, currEventName, eventResourceName } = eventState; + const { id, currEventName } = eventState; userEventService.captureDiscoverEvent({ event: custom?.eventName || currEventName, eventData: { id, - resource: custom?.eventResourceName || eventResourceName, + resource: custom?.eventResourceName || resourceSpec?.event, autoDiscoverResourcesCount: custom?.autoDiscoverResourcesCount, ...status, }, }); }, - [eventState] + [eventState, resourceSpec] ); useEffect(() => { @@ -141,7 +139,6 @@ export function DiscoverProvider(props: React.PropsWithChildren) { setEventState({ id, currEventName: DiscoverEvent.Started, - eventResourceName: null, manuallyEmitSuccessEvent: null, }); userEventService.captureDiscoverEvent({ @@ -159,6 +156,20 @@ export function DiscoverProvider(props: React.PropsWithChildren) { // onSelectResources initializes all the required // variables needed to start a guided flow. function onSelectResource(resource: ResourceSpec) { + // We still want to emit an event if user clicked on + // unguided links to gather data on which unguided resource + // is most popular. + if (resource.unguidedLink) { + emitEvent( + { stepStatus: DiscoverEventStatus.Success }, + { + eventName: DiscoverEvent.ResourceSelection, + eventResourceName: resource.event, + } + ); + return; + } + // Process each view and assign each with an index number. const currCfg = viewConfigs.find(r => r.kind === resource.kind); let indexedViews = []; @@ -173,20 +184,20 @@ export function DiscoverProvider(props: React.PropsWithChildren) { indexedViews, currentStep ); - const eventResourceName = resourceKindToEventResource(resource); - // At this point it's considered the user has // successfully selected a resource, so we send an event. emitEvent( { stepStatus: DiscoverEventStatus.Success }, - { eventName: DiscoverEvent.ResourceSelection, eventResourceName } + { + eventName: DiscoverEvent.ResourceSelection, + eventResourceName: resource.event, + } ); // Init all required states to start the flow. setEventState({ ...eventState, currEventName: eventName, - eventResourceName, manuallyEmitSuccessEvent, }); setViewConfig(currCfg); diff --git a/web/packages/teleport/src/TopBar/TopBar.tsx b/web/packages/teleport/src/TopBar/TopBar.tsx index 0bfd713bb94f2..9f26861d2784c 100644 --- a/web/packages/teleport/src/TopBar/TopBar.tsx +++ b/web/packages/teleport/src/TopBar/TopBar.tsx @@ -55,7 +55,7 @@ export function TopBar() { .find(f => matchPath(history.location.pathname, { path: f.route.path, - exact: false, + exact: f.route.exact ?? false, }) ); diff --git a/web/packages/teleport/src/services/api/index.js b/web/packages/teleport/src/services/api/index.js index 08d9c054be5cf..9ec7274f74725 100644 --- a/web/packages/teleport/src/services/api/index.js +++ b/web/packages/teleport/src/services/api/index.js @@ -19,6 +19,13 @@ import api, { getNoCacheHeaders, getAccessToken, getHostName, + getXCSRFToken, } from './api'; export default api; -export { getAuthHeaders, getNoCacheHeaders, getAccessToken, getHostName }; +export { + getAuthHeaders, + getNoCacheHeaders, + getAccessToken, + getHostName, + getXCSRFToken, +}; diff --git a/web/packages/teleport/src/services/userEvent/types.ts b/web/packages/teleport/src/services/userEvent/types.ts index 3521b368399ba..313b4d06e0f1d 100644 --- a/web/packages/teleport/src/services/userEvent/types.ts +++ b/web/packages/teleport/src/services/userEvent/types.ts @@ -74,6 +74,28 @@ export enum DiscoverEventResource { DatabaseMysqlGcp = 'DISCOVER_RESOURCE_DATABASE_MYSQL_GCP', DatabaseSqlServerGcp = 'DISCOVER_RESOURCE_DATABASE_SQLSERVER_GCP', + DatabasePostgresRedshiftServerless = 'DISCOVER_RESOURCE_DATABASE_POSTGRES_REDSHIFT_SERVERLESS', + DatabasePostgresAzure = 'DISCOVER_RESOURCE_DATABASE_POSTGRES_AZURE', + DatabaseDynamoDb = 'DISCOVER_RESOURCE_DATABASE_DYNAMODB', + DatabaseCassandraKeyspaces = 'DISCOVER_RESOURCE_DATABASE_CASSANDRA_KEYSPACES', + DatabaseCassandraSelfHosted = 'DISCOVER_RESOURCE_DATABASE_CASSANDRA_SELF_HOSTED', + DatabaseElasticSearchSelfHosted = 'DISCOVER_RESOURCE_DATABASE_ELASTICSEARCH_SELF_HOSTED', + DatabaseRedisElasticache = 'DISCOVER_RESOURCE_DATABASE_REDIS_ELASTICACHE', + DatabaseRedisMemoryDb = 'DISCOVER_RESOURCE_DATABASE_REDIS_MEMORYDB', + DatabaseRedisAzureCache = 'DISCOVER_RESOURCE_DATABASE_REDIS_AZURE_CACHE', + DatabaseRedisClusterSelfHosted = 'DISCOVER_RESOURCE_DATABASE_REDIS_CLUSTER_SELF_HOSTED', + + DatabaseMysqlAzure = 'DISCOVER_RESOURCE_DATABASE_MYSQL_AZURE', + DatabaseSqlServerAzure = 'DISCOVER_RESOURCE_DATABASE_SQLSERVER_AZURE', + DatabaseSqlServerMicrosoft = 'DISCOVER_RESOURCE_DATABASE_SQLSERVER_MICROSOFT', + DatabaseCockroachDbSelfHosted = 'DISCOVER_RESOURCE_DATABASE_COCKROACHDB_SELF_HOSTED', + DatabaseMongodbAtlas = 'DISCOVER_RESOURCE_DATABASE_MONGODB_ATLAS', + DatabaseSnowflake = 'DISCOVER_RESOURCE_DATABASE_SNOWFLAKE', + + DatabaseDocRdsProxy = 'DISCOVER_RESOURCE_DOC_DATABASE_RDS_PROXY', + DatabaseDocHighAvailability = 'DISCOVER_RESOURCE_DOC_DATABASE_HIGH_AVAILABILITY', + DatabaseDocDynamicRegistration = 'DISCOVER_RESOURCE_DOC_DATABASE_DYNAMIC_REGISTRATION', + ApplicationHttp = 'DISCOVER_RESOURCE_APPLICATION_HTTP', ApplicationTcp = 'DISCOVER_RESOURCE_APPLICATION_TCP', WindowsDesktop = 'DISCOVER_RESOURCE_WINDOWS_DESKTOP', diff --git a/web/packages/teleterm/src/services/config/appConfigSchema.ts b/web/packages/teleterm/src/services/config/appConfigSchema.ts index aa93dbcf6a160..bb18acbe21225 100644 --- a/web/packages/teleterm/src/services/config/appConfigSchema.ts +++ b/web/packages/teleterm/src/services/config/appConfigSchema.ts @@ -35,6 +35,10 @@ export const createAppConfigSchema = (platform: Platform) => { .boolean() .default(false) .describe('Enables collecting of anonymous usage data.'), + 'feature.searchBar': z + .boolean() + .default(false) + .describe('Replaces the command bar with the new search bar'), 'keymap.tab1': shortcutSchema .default(defaultKeymap['tab1']) .describe(getShortcutDesc('open tab 1')), @@ -68,6 +72,9 @@ export const createAppConfigSchema = (platform: Platform) => { 'keymap.newTab': shortcutSchema .default(defaultKeymap['newTab']) .describe(getShortcutDesc('open a new tab')), + 'keymap.newTerminalTab': shortcutSchema + .default(defaultKeymap['newTerminalTab']) + .describe(getShortcutDesc('open a new terminal tab')), 'keymap.previousTab': shortcutSchema .default(defaultKeymap['previousTab']) .describe(getShortcutDesc('go to the previous tab')), @@ -118,6 +125,7 @@ export type KeyboardShortcutAction = | 'tab9' | 'closeTab' | 'newTab' + | 'newTerminalTab' | 'previousTab' | 'nextTab' | 'openCommandBar' @@ -125,7 +133,9 @@ export type KeyboardShortcutAction = | 'openClusters' | 'openProfiles'; -const getDefaultKeymap = (platform: Platform) => { +const getDefaultKeymap = ( + platform: Platform +): Record => { switch (platform) { case 'win32': return { @@ -140,6 +150,7 @@ const getDefaultKeymap = (platform: Platform) => { tab9: 'Ctrl+9', closeTab: 'Ctrl+W', newTab: 'Ctrl+T', + newTerminalTab: 'Ctrl+Shift+T', previousTab: 'Ctrl+Shift+Tab', nextTab: 'Ctrl+Tab', openCommandBar: 'Ctrl+K', @@ -160,6 +171,7 @@ const getDefaultKeymap = (platform: Platform) => { tab9: 'Alt+9', closeTab: 'Ctrl+W', newTab: 'Ctrl+T', + newTerminalTab: 'Ctrl+Shift+T', previousTab: 'Ctrl+Shift+Tab', nextTab: 'Ctrl+Tab', openCommandBar: 'Ctrl+K', @@ -180,6 +192,7 @@ const getDefaultKeymap = (platform: Platform) => { tab9: 'Command+9', closeTab: 'Command+W', newTab: 'Command+T', + newTerminalTab: 'Shift+Command+T', previousTab: 'Control+Shift+Tab', nextTab: 'Control+Tab', openCommandBar: 'Command+K', diff --git a/web/packages/teleterm/src/services/tshd/createClient.ts b/web/packages/teleterm/src/services/tshd/createClient.ts index 7541485a23276..cf2b36eb15e73 100644 --- a/web/packages/teleterm/src/services/tshd/createClient.ts +++ b/web/packages/teleterm/src/services/tshd/createClient.ts @@ -61,20 +61,24 @@ export default function createClient( async getKubes({ clusterUri, search, - sort = { fieldName: 'name', dir: 'ASC' }, + sort, query, searchAsRoles, startKey, limit, - }: types.ServerSideParams) { + }: types.GetResourcesParams) { const req = new api.GetKubesRequest() .setClusterUri(clusterUri) .setSearchAsRoles(searchAsRoles) .setStartKey(startKey) - .setSortBy(`${sort.fieldName}:${sort.dir.toLowerCase()}`) .setSearch(search) .setQuery(query) .setLimit(limit); + + if (sort) { + req.setSortBy(`${sort.fieldName}:${sort.dir.toLowerCase()}`); + } + return new Promise((resolve, reject) => { tshd.getKubes(req, (err, response) => { if (err) { @@ -128,20 +132,24 @@ export default function createClient( async getDatabases({ clusterUri, search, - sort = { fieldName: 'name', dir: 'ASC' }, + sort, query, searchAsRoles, startKey, limit, - }: types.ServerSideParams) { + }: types.GetResourcesParams) { const req = new api.GetDatabasesRequest() .setClusterUri(clusterUri) .setSearchAsRoles(searchAsRoles) .setStartKey(startKey) - .setSortBy(`${sort.fieldName}:${sort.dir.toLowerCase()}`) .setSearch(search) .setQuery(query) .setLimit(limit); + + if (sort) { + req.setSortBy(`${sort.fieldName}:${sort.dir.toLowerCase()}`); + } + return new Promise((resolve, reject) => { tshd.getDatabases(req, (err, response) => { if (err) { @@ -198,19 +206,23 @@ export default function createClient( clusterUri, search, query, - sort = { fieldName: 'hostname', dir: 'ASC' }, + sort, searchAsRoles, startKey, limit, - }: types.ServerSideParams) { + }: types.GetResourcesParams) { const req = new api.GetServersRequest() .setClusterUri(clusterUri) .setSearchAsRoles(searchAsRoles) .setStartKey(startKey) - .setSortBy(`${sort.fieldName}:${sort.dir.toLowerCase()}`) .setSearch(search) .setQuery(query) .setLimit(limit); + + if (sort) { + req.setSortBy(`${sort.fieldName}:${sort.dir.toLowerCase()}`); + } + return new Promise((resolve, reject) => { tshd.getServers(req, (err, response) => { if (err) { diff --git a/web/packages/teleterm/src/services/tshd/fixtures/mocks.ts b/web/packages/teleterm/src/services/tshd/fixtures/mocks.ts index 5809b0f5edd2e..343af90cb640c 100644 --- a/web/packages/teleterm/src/services/tshd/fixtures/mocks.ts +++ b/web/packages/teleterm/src/services/tshd/fixtures/mocks.ts @@ -29,7 +29,7 @@ import { LoginPasswordlessParams, LoginSsoParams, ReviewAccessRequestParams, - ServerSideParams, + GetResourcesParams, TshAbortController, TshAbortSignal, TshClient, @@ -39,13 +39,13 @@ import { export class MockTshClient implements TshClient { listRootClusters: () => Promise; listLeafClusters: (clusterUri: string) => Promise; - getKubes: (params: ServerSideParams) => Promise; - getDatabases: (params: ServerSideParams) => Promise; + getKubes: (params: GetResourcesParams) => Promise; + getDatabases: (params: GetResourcesParams) => Promise; listDatabaseUsers: (dbUri: string) => Promise; getRequestableRoles: ( params: GetRequestableRolesParams ) => Promise; - getServers: (params: ServerSideParams) => Promise; + getServers: (params: GetResourcesParams) => Promise; assumeRole: ( clusterUri: string, requestIds: string[], diff --git a/web/packages/teleterm/src/services/tshd/mapUsageEvent.ts b/web/packages/teleterm/src/services/tshd/mapUsageEvent.ts index 269e97caa928a..c23c1a9ad7f3a 100644 --- a/web/packages/teleterm/src/services/tshd/mapUsageEvent.ts +++ b/web/packages/teleterm/src/services/tshd/mapUsageEvent.ts @@ -65,7 +65,8 @@ function mapPrehogBody( const reqEvent = new prehogApi.ConnectProtocolUseEvent() .setClusterName(event.clusterName) .setUserName(event.userName) - .setProtocol(event.protocol); + .setProtocol(event.protocol) + .setOrigin(event.origin); return req.setProtocolUse(reqEvent); } diff --git a/web/packages/teleterm/src/services/tshd/types.ts b/web/packages/teleterm/src/services/tshd/types.ts index 043b31f40b622..2cbce08b2bc3c 100644 --- a/web/packages/teleterm/src/services/tshd/types.ts +++ b/web/packages/teleterm/src/services/tshd/types.ts @@ -26,6 +26,7 @@ import apiDb from 'gen-proto-js/teleport/lib/teleterm/v1/database_pb'; import apiGateway from 'gen-proto-js/teleport/lib/teleterm/v1/gateway_pb'; import apiServer from 'gen-proto-js/teleport/lib/teleterm/v1/server_pb'; import apiKube from 'gen-proto-js/teleport/lib/teleterm/v1/kube_pb'; +import apiLabel from 'gen-proto-js/teleport/lib/teleterm/v1/label_pb'; import apiService, { FileTransferDirection, } from 'gen-proto-js/teleport/lib/teleterm/v1/service_pb'; @@ -144,8 +145,8 @@ export type LoginPasswordlessRequest = export type TshClient = { listRootClusters: () => Promise; listLeafClusters: (clusterUri: uri.RootClusterUri) => Promise; - getKubes: (params: ServerSideParams) => Promise; - getDatabases: (params: ServerSideParams) => Promise; + getKubes: (params: GetResourcesParams) => Promise; + getDatabases: (params: GetResourcesParams) => Promise; listDatabaseUsers: (dbUri: uri.DatabaseUri) => Promise; assumeRole: ( clusterUri: uri.RootClusterUri, @@ -155,7 +156,7 @@ export type TshClient = { getRequestableRoles: ( params: GetRequestableRolesParams ) => Promise; - getServers: (params: ServerSideParams) => Promise; + getServers: (params: GetResourcesParams) => Promise; getAccessRequests: ( clusterUri: uri.RootClusterUri ) => Promise; @@ -248,18 +249,25 @@ export type CreateGatewayParams = { subresource_name?: string; }; -export type ServerSideParams = { +export type GetResourcesParams = { clusterUri: uri.ClusterUri; + // sort is a required field because it has direct implications on performance of ListResources. + sort: SortType | null; + // limit cannot be omitted and must be greater than zero, otherwise ListResources is going to + // return an error. + limit: number; // search is used for regular search. search?: string; searchAsRoles?: string; - sort?: SortType; startKey?: string; - limit?: number; // query is used for advanced search. query?: string; }; +// Compatibility type to make sure teleport.e doesn't break. +// TODO(ravicious): Remove after teleterm.e is updated to use GetResourcesParams. +export type ServerSideParams = GetResourcesParams; + export type ReviewAccessRequestParams = { state: RequestState; reason: string; @@ -288,5 +296,7 @@ export type AssumedRequest = { export { FileTransferDirection }; +export type Label = apiLabel.Label.AsObject; + // Replaces object property with a new type type Modify = Omit & R; diff --git a/web/packages/teleterm/src/ui/DocumentCluster/ClusterResources/Databases/Databases.tsx b/web/packages/teleterm/src/ui/DocumentCluster/ClusterResources/Databases/Databases.tsx index 44603cc5073e1..31f6823b1ab5f 100644 --- a/web/packages/teleterm/src/ui/DocumentCluster/ClusterResources/Databases/Databases.tsx +++ b/web/packages/teleterm/src/ui/DocumentCluster/ClusterResources/Databases/Databases.tsx @@ -170,7 +170,7 @@ function getMenuLoginOptions( async function getDatabaseUsers(appContext: IAppContext, dbUri: DatabaseUri) { try { const dbUsers = await retryWithRelogin(appContext, dbUri, () => - appContext.clustersService.getDbUsers(dbUri) + appContext.resourcesService.getDbUsers(dbUri) ); return dbUsers.map(user => ({ login: user, url: '' })); } catch (e) { diff --git a/web/packages/teleterm/src/ui/DocumentCluster/ClusterResources/Databases/useDatabases.ts b/web/packages/teleterm/src/ui/DocumentCluster/ClusterResources/Databases/useDatabases.ts index abeea60105821..f5ccd1443b048 100644 --- a/web/packages/teleterm/src/ui/DocumentCluster/ClusterResources/Databases/useDatabases.ts +++ b/web/packages/teleterm/src/ui/DocumentCluster/ClusterResources/Databases/useDatabases.ts @@ -18,7 +18,7 @@ import { useAppContext } from 'teleterm/ui/appContextProvider'; import { Database, GatewayProtocol, - ServerSideParams, + GetResourcesParams, } from 'teleterm/services/tshd/types'; import { routing } from 'teleterm/ui/uri'; import { makeDatabase } from 'teleterm/ui/services/clusters'; @@ -31,7 +31,7 @@ export function useDatabases() { const { fetchAttempt, ...serverSideResources } = useServerSideResources( { fieldName: 'name', dir: 'ASC' }, // default sort - (params: ServerSideParams) => + (params: GetResourcesParams) => appContext.resourcesService.fetchDatabases(params) ); @@ -46,13 +46,16 @@ export function useDatabases() { targetUri: db.uri, targetName: db.name, targetUser: getTargetUser(db.protocol as GatewayProtocol, dbUser), + origin: 'resource_table', }); const connectionToReuse = appContext.connectionTracker.findConnectionByDocument(doc); if (connectionToReuse) { - appContext.connectionTracker.activateItem(connectionToReuse.id); + appContext.connectionTracker.activateItem(connectionToReuse.id, { + origin: 'resource_table', + }); } else { documentsService.add(doc); documentsService.open(doc.uri); diff --git a/web/packages/teleterm/src/ui/DocumentCluster/ClusterResources/Kubes/useKubes.ts b/web/packages/teleterm/src/ui/DocumentCluster/ClusterResources/Kubes/useKubes.ts index 58f5f7b5e5347..21cca334ddb0b 100644 --- a/web/packages/teleterm/src/ui/DocumentCluster/ClusterResources/Kubes/useKubes.ts +++ b/web/packages/teleterm/src/ui/DocumentCluster/ClusterResources/Kubes/useKubes.ts @@ -14,9 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { Kube, ServerSideParams } from 'teleterm/services/tshd/types'; +import { Kube, GetResourcesParams } from 'teleterm/services/tshd/types'; import { useAppContext } from 'teleterm/ui/appContextProvider'; import { useClusterContext } from 'teleterm/ui/DocumentCluster/clusterContext'; +import { KubeUri } from 'teleterm/ui/uri'; import { useServerSideResources } from '../useServerSideResources'; @@ -25,11 +26,13 @@ export function useKubes() { const ctx = useClusterContext(); const { fetchAttempt, ...serversideResources } = useServerSideResources( { fieldName: 'name', dir: 'ASC' }, // default sort - (params: ServerSideParams) => appContext.resourcesService.fetchKubes(params) + (params: GetResourcesParams) => + appContext.resourcesService.fetchKubes(params) ); return { - connect: ctx.connectKube, + connect: (kubeUri: KubeUri) => + ctx.connectKube(kubeUri, { origin: 'resource_table' }), fetchAttempt, ...serversideResources, }; diff --git a/web/packages/teleterm/src/ui/DocumentCluster/ClusterResources/Servers/useServers.ts b/web/packages/teleterm/src/ui/DocumentCluster/ClusterResources/Servers/useServers.ts index 9dad56feb3f26..056d301cd2981 100644 --- a/web/packages/teleterm/src/ui/DocumentCluster/ClusterResources/Servers/useServers.ts +++ b/web/packages/teleterm/src/ui/DocumentCluster/ClusterResources/Servers/useServers.ts @@ -13,7 +13,7 @@ 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 { Server, ServerSideParams } from 'teleterm/services/tshd/types'; +import { Server, GetResourcesParams } from 'teleterm/services/tshd/types'; import { useAppContext } from 'teleterm/ui/appContextProvider'; import { makeServer } from 'teleterm/ui/services/clusters'; @@ -27,7 +27,7 @@ export function useServers() { const { fetchAttempt, ...serversideResources } = useServerSideResources( { fieldName: 'hostname', dir: 'ASC' }, // default sort - (params: ServerSideParams) => + (params: GetResourcesParams) => appContext.resourcesService.fetchServers(params) ); @@ -42,7 +42,9 @@ export function useServers() { ); const documentsService = appContext.workspacesService.getWorkspaceDocumentService(rootCluster.uri); - const doc = documentsService.createTshNodeDocument(server.uri); + const doc = documentsService.createTshNodeDocument(server.uri, { + origin: 'resource_table', + }); doc.title = `${login}@${server.hostname}`; doc.login = login; diff --git a/web/packages/teleterm/src/ui/DocumentCluster/ClusterResources/useServerSideResources.ts b/web/packages/teleterm/src/ui/DocumentCluster/ClusterResources/useServerSideResources.ts index 72427b65ccfd2..6a48d349e6abf 100644 --- a/web/packages/teleterm/src/ui/DocumentCluster/ClusterResources/useServerSideResources.ts +++ b/web/packages/teleterm/src/ui/DocumentCluster/ClusterResources/useServerSideResources.ts @@ -17,14 +17,19 @@ import { useState, useEffect, useMemo } from 'react'; import { SortType } from 'design/DataTable/types'; import { useAsync } from 'shared/hooks/useAsync'; -import { AgentFilter, AgentLabel } from 'teleport/services/agents'; +import { + AgentFilter as WeakAgentFilter, + AgentLabel, +} from 'teleport/services/agents'; -import { ServerSideParams } from 'teleterm/services/tshd/types'; +import { GetResourcesParams } from 'teleterm/services/tshd/types'; import { useAppContext } from 'teleterm/ui/appContextProvider'; import { retryWithRelogin } from 'teleterm/ui/utils'; import { useClusterContext } from '../clusterContext'; +type AgentFilter = WeakAgentFilter & { sort: SortType }; + function addAgentLabelToQuery(filter: AgentFilter, label: AgentLabel) { const queryParts = []; @@ -49,7 +54,7 @@ const limit = 15; export function useServerSideResources( defaultSort: SortType, - fetchFunction: (params: ServerSideParams) => Promise> + fetchFunction: (params: GetResourcesParams) => Promise> ) { const ctx = useAppContext(); const { clusterUri } = useClusterContext(); diff --git a/web/packages/teleterm/src/ui/DocumentCluster/clusterContext.tsx b/web/packages/teleterm/src/ui/DocumentCluster/clusterContext.tsx index e2057ee613532..2c90902b45da8 100644 --- a/web/packages/teleterm/src/ui/DocumentCluster/clusterContext.tsx +++ b/web/packages/teleterm/src/ui/DocumentCluster/clusterContext.tsx @@ -20,6 +20,7 @@ import { useStore, Store } from 'shared/libs/stores'; import { IAppContext } from 'teleterm/ui/types'; import { ClusterUri, DocumentUri, KubeUri, routing } from 'teleterm/ui/uri'; +import { DocumentOrigin } from 'teleterm/ui/services/workspacesService'; import type * as tsh from 'teleterm/services/tshd/types'; @@ -71,8 +72,11 @@ class ClusterContext extends Store { }); }; - connectKube = (kubeUri: KubeUri) => { - this.appCtx.commandLauncher.executeCommand('kube-connect', { kubeUri }); + connectKube = (kubeUri: KubeUri, params: { origin: DocumentOrigin }) => { + this.appCtx.commandLauncher.executeCommand('kube-connect', { + kubeUri, + origin: params.origin, + }); }; refresh = () => { diff --git a/web/packages/teleterm/src/ui/DocumentGateway/useDocumentGateway.ts b/web/packages/teleterm/src/ui/DocumentGateway/useDocumentGateway.ts index 0450cfb9f1a5c..eb7d0408a70b7 100644 --- a/web/packages/teleterm/src/ui/DocumentGateway/useDocumentGateway.ts +++ b/web/packages/teleterm/src/ui/DocumentGateway/useDocumentGateway.ts @@ -62,6 +62,7 @@ export default function useGateway(doc: types.DocumentGateway) { // same port number. port: gw.localPort, }); + ctx.usageService.captureProtocolUse(doc.targetUri, 'db', doc.origin); }); const [disconnectAttempt, disconnect] = useAsync(async () => { diff --git a/web/packages/teleterm/src/ui/DocumentTerminal/useDocumentTerminal.test.tsx b/web/packages/teleterm/src/ui/DocumentTerminal/useDocumentTerminal.test.tsx index c1f0edce17155..aa57f69678873 100644 --- a/web/packages/teleterm/src/ui/DocumentTerminal/useDocumentTerminal.test.tsx +++ b/web/packages/teleterm/src/ui/DocumentTerminal/useDocumentTerminal.test.tsx @@ -82,6 +82,7 @@ const getDocTshNodeWithServerId: () => DocumentTshNodeWithServerId = () => ({ rootClusterId: 'test', leafClusterId: undefined, login: 'user', + origin: 'resource_table', }); const getDocTshNodeWithLoginHost: () => DocumentTshNodeWithLoginHost = () => { diff --git a/web/packages/teleterm/src/ui/DocumentTerminal/useDocumentTerminal.ts b/web/packages/teleterm/src/ui/DocumentTerminal/useDocumentTerminal.ts index 72807f0309b78..471df9495ddeb 100644 --- a/web/packages/teleterm/src/ui/DocumentTerminal/useDocumentTerminal.ts +++ b/web/packages/teleterm/src/ui/DocumentTerminal/useDocumentTerminal.ts @@ -216,11 +216,11 @@ async function setUpPtyProcess( const cmd = createCmd(doc, rootCluster.proxyHost, getClusterName()); const ptyProcess = await createPtyProcess(ctx, cmd); - if (cmd.kind === 'pty.tsh-login') { - ctx.usageService.captureProtocolUse(clusterUri, 'ssh'); + if (doc.kind === 'doc.terminal_tsh_node') { + ctx.usageService.captureProtocolUse(clusterUri, 'ssh', doc.origin); } - if (cmd.kind === 'pty.tsh-kube-login') { - ctx.usageService.captureProtocolUse(clusterUri, 'kube'); + if (doc.kind === 'doc.terminal_tsh_kube') { + ctx.usageService.captureProtocolUse(clusterUri, 'kube', doc.origin); } const openContextMenu = () => ctx.mainProcessClient.openTerminalContextMenu(); diff --git a/web/packages/teleterm/src/ui/Documents/KeyboardShortcutsPanel.tsx b/web/packages/teleterm/src/ui/Documents/KeyboardShortcutsPanel.tsx index 79d79b9d80b37..266a5e48babce 100644 --- a/web/packages/teleterm/src/ui/Documents/KeyboardShortcutsPanel.tsx +++ b/web/packages/teleterm/src/ui/Documents/KeyboardShortcutsPanel.tsx @@ -31,6 +31,10 @@ export function KeyboardShortcutsPanel() { title: 'Open New Tab', shortcutAction: 'newTab', }, + { + title: 'Open New Terminal Tab', + shortcutAction: 'newTerminalTab', + }, { title: 'Go To Next Tab', shortcutAction: 'nextTab', diff --git a/web/packages/teleterm/src/ui/QuickInput/QuickInput.tsx b/web/packages/teleterm/src/ui/QuickInput/QuickInput.tsx index 2d46400a581fb..9f53219e46622 100644 --- a/web/packages/teleterm/src/ui/QuickInput/QuickInput.tsx +++ b/web/packages/teleterm/src/ui/QuickInput/QuickInput.tsx @@ -145,11 +145,13 @@ function QuickInput() { return ( props.theme.space[7]}px * 2); + height: 100%; + `} flex={1} ref={refContainer} onFocus={handleOnFocus} diff --git a/web/packages/teleterm/src/ui/QuickInput/useQuickInput.ts b/web/packages/teleterm/src/ui/QuickInput/useQuickInput.ts index 621ae556039bc..dfed5e5e7daae 100644 --- a/web/packages/teleterm/src/ui/QuickInput/useQuickInput.ts +++ b/web/packages/teleterm/src/ui/QuickInput/useQuickInput.ts @@ -35,7 +35,12 @@ import { assertUnreachable, retryWithRelogin } from '../utils'; export default function useQuickInput() { const appContext = useAppContext(); - const { quickInputService, workspacesService, commandLauncher } = appContext; + const { + quickInputService, + workspacesService, + commandLauncher, + usageService, + } = appContext; workspacesService.useState(); const documentsService = workspacesService.getActiveWorkspaceDocumentService(); @@ -105,6 +110,14 @@ export default function useQuickInput() { const params = routing.parseClusterUri( workspacesService.getActiveWorkspace()?.localClusterUri ).params; + // ugly hack but QuickInput will be removed in v13 + if (inputValue.startsWith('tsh proxy db')) { + usageService.captureProtocolUse( + workspacesService.getRootClusterUri(), + 'db', + 'search_bar' + ); + } documentsService.openNewTerminal({ initCommand: inputValue, rootClusterId: routing.parseClusterUri( @@ -120,6 +133,7 @@ export default function useQuickInput() { commandLauncher.executeCommand('tsh-ssh', { loginHost: command.loginHost, localClusterUri, + origin: 'search_bar', }); break; } diff --git a/web/packages/teleterm/src/ui/Search/SearchBar.test.tsx b/web/packages/teleterm/src/ui/Search/SearchBar.test.tsx new file mode 100644 index 0000000000000..eb60125a2e0a8 --- /dev/null +++ b/web/packages/teleterm/src/ui/Search/SearchBar.test.tsx @@ -0,0 +1,103 @@ +/** + * Copyright 2023 Gravitational, Inc + * + * 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 React from 'react'; +import { render, screen } from 'design/utils/testing'; +import { makeSuccessAttempt } from 'shared/hooks/useAsync'; + +import { MockAppContext } from 'teleterm/ui/fixtures/mocks'; +import { MockAppContextProvider } from 'teleterm/ui/fixtures/MockAppContextProvider'; + +import * as pickers from './pickers/pickers'; +import * as useSearchAttempts from './pickers/useSearchAttempts'; +import * as SearchContext from './SearchContext'; + +import { SearchBarConnected } from './SearchBar'; + +it('does not display empty results copy after selecting two filters', () => { + const appContext = new MockAppContext(); + appContext.workspacesService.setState(draft => { + draft.rootClusterUri = '/clusters/foo'; + }); + + const mockAttempts = [makeSuccessAttempt([])]; + jest + .spyOn(useSearchAttempts, 'useSearchAttempts') + .mockImplementation(() => mockAttempts); + jest.spyOn(SearchContext, 'useSearchContext').mockImplementation(() => ({ + filters: [ + { filter: 'cluster', clusterUri: '/clusters/foo' }, + { filter: 'resource-type', resourceType: 'servers' }, + ], + inputValue: '', + setFilter: () => {}, + removeFilter: () => {}, + opened: true, + open: () => {}, + close: () => {}, + closeAndResetInput: () => {}, + resetInput: () => {}, + changeActivePicker: () => {}, + onInputValueChange: () => {}, + activePicker: pickers.actionPicker, + inputRef: undefined, + })); + + render( + + + + ); + + const results = screen.getByRole('menu'); + expect(results).not.toHaveTextContent('No matching results found'); +}); + +it('does display empty results copy after providing search query for which there is no results', () => { + const appContext = new MockAppContext(); + appContext.workspacesService.setState(draft => { + draft.rootClusterUri = '/clusters/foo'; + }); + + const mockAttempts = [makeSuccessAttempt([])]; + jest + .spyOn(useSearchAttempts, 'useSearchAttempts') + .mockImplementation(() => mockAttempts); + jest.spyOn(SearchContext, 'useSearchContext').mockImplementation(() => ({ + inputValue: 'foo', + filters: [], + setFilter: () => {}, + removeFilter: () => {}, + opened: true, + open: () => {}, + close: () => {}, + closeAndResetInput: () => {}, + resetInput: () => {}, + changeActivePicker: () => {}, + onInputValueChange: () => {}, + activePicker: pickers.actionPicker, + inputRef: undefined, + })); + + render( + + + + ); + + const results = screen.getByRole('menu'); + expect(results).toHaveTextContent('No matching results found'); +}); diff --git a/web/packages/teleterm/src/ui/Search/SearchBar.tsx b/web/packages/teleterm/src/ui/Search/SearchBar.tsx new file mode 100644 index 0000000000000..27e0107349dbb --- /dev/null +++ b/web/packages/teleterm/src/ui/Search/SearchBar.tsx @@ -0,0 +1,172 @@ +/** + * Copyright 2023 Gravitational, Inc. + * + * 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 React, { useRef, useEffect } from 'react'; +import styled from 'styled-components'; +import { Box, Flex } from 'design'; +import { space, width, color, height } from 'styled-system'; + +import { + SearchContextProvider, + useSearchContext, +} from 'teleterm/ui/Search/SearchContext'; +import { KeyboardShortcutAction } from 'teleterm/services/config'; +import { + useKeyboardShortcutFormatters, + useKeyboardShortcuts, +} from 'teleterm/ui/services/keyboardShortcuts'; + +import { useAppContext } from '../appContextProvider'; + +const OPEN_COMMAND_BAR_SHORTCUT_ACTION: KeyboardShortcutAction = + 'openCommandBar'; + +export function SearchBarConnected() { + const { workspacesService } = useAppContext(); + workspacesService.useState(); + + if (!workspacesService.getRootClusterUri()) { + return null; + } + + return ( + + + + ); +} + +function SearchBar() { + const containerRef = useRef(); + const { getAccelerator } = useKeyboardShortcutFormatters(); + const { + activePicker, + inputValue, + onInputValueChange, + inputRef, + opened, + open, + close, + } = useSearchContext(); + const ctx = useAppContext(); + ctx.clustersService.useState(); + + useKeyboardShortcuts({ + [OPEN_COMMAND_BAR_SHORTCUT_ACTION]: () => { + open(); + }, + }); + + useEffect(() => { + const onClickOutside = e => { + if (!e.composedPath().includes(containerRef.current)) { + close(); + } + }; + if (opened) { + window.addEventListener('click', onClickOutside); + return () => window.removeEventListener('click', onClickOutside); + } + }, [close, opened]); + + function handleOnFocus(e: React.FocusEvent) { + open(e.relatedTarget); + } + + const defaultInputProps = { + ref: inputRef, + role: 'searchbox', + placeholder: activePicker.placeholder, + value: inputValue, + onChange: e => { + onInputValueChange(e.target.value); + }, + spellCheck: false, + }; + + return ( + props.theme.space[7]}px * 2); + height: 100%; + background: ${props => props.theme.colors.levels.surface}; + border: 1px ${props => props.theme.colors.action.disabledBackground} + solid; + `} + justifyContent="center" + ref={containerRef} + onFocus={handleOnFocus} + > + {!opened && ( + <> + + + {getAccelerator(OPEN_COMMAND_BAR_SHORTCUT_ACTION)} + + + )} + {opened && ( + } + /> + )} + + ); +} + +const Input = styled.input(props => { + const { theme } = props; + return { + height: '100%', + background: theme.colors.levels.sunkenSecondary, + boxSizing: 'border-box', + color: theme.colors.text.primary, + width: '100%', + outline: 'none', + border: 'none', + padding: `${theme.space[1]}px ${theme.space[2]}px`, + '&:hover, &:focus': { + color: theme.colors.text.contrast, + background: theme.colors.levels.surface, + + opacity: 1, + }, + '::placeholder': { + color: theme.colors.text.secondary, + }, + + ...space(props), + ...width(props), + ...height(props), + ...color(props), + }; +}); + +const Shortcut = styled(Box).attrs({ p: 1 })` + position: absolute; + right: ${props => props.theme.space[2]}px; + top: 50%; + transform: translate(0, -50%); + color: ${({ theme }) => theme.colors.text.secondary}; + background-color: ${({ theme }) => theme.colors.levels.surface}; + line-height: 12px; + font-size: 12px; + border-radius: 2px; +`; diff --git a/web/packages/teleterm/src/ui/Search/SearchContext.tsx b/web/packages/teleterm/src/ui/Search/SearchContext.tsx new file mode 100644 index 0000000000000..8885cbc49b3f7 --- /dev/null +++ b/web/packages/teleterm/src/ui/Search/SearchContext.tsx @@ -0,0 +1,143 @@ +/** + * Copyright 2022 Gravitational, Inc. + * + * 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 React, { + useContext, + useState, + FC, + useCallback, + createContext, + useRef, + MutableRefObject, +} from 'react'; + +import { SearchFilter } from 'teleterm/ui/Search/searchResult'; + +import { actionPicker, SearchPicker } from './pickers/pickers'; + +export interface SearchContext { + inputRef: MutableRefObject; + inputValue: string; + opened: boolean; + filters: SearchFilter[]; + activePicker: SearchPicker; + onInputValueChange(value: string): void; + changeActivePicker(picker: SearchPicker): void; + close(): void; + closeAndResetInput(): void; + open(fromElement?: Element): void; + resetInput(): void; + setFilter(filter: SearchFilter): void; + removeFilter(filter: SearchFilter): void; +} + +const SearchContext = createContext(null); + +export const SearchContextProvider: FC = props => { + const previouslyActive = useRef(); + const inputRef = useRef(); + const [opened, setOpened] = useState(false); + const [inputValue, setInputValue] = useState(''); + const [activePicker, setActivePicker] = useState(actionPicker); + // TODO(ravicious): Consider using another data structure for search filters as we know that we + // always have only two specific filters: one for clusters and one for resource type. + // + // This could probably be represented by an object instead plus an array for letting the user + // provide those filters in any order they want. The array would be used in the UI that renders + // the filters while code that uses the search filters, such as ResourcesService.searchResources, + // could operate on the object instead. + const [filters, setFilters] = useState([]); + + function changeActivePicker(picker: SearchPicker): void { + setActivePicker(picker); + setInputValue(''); + } + + const close = useCallback(() => { + setOpened(false); + setActivePicker(actionPicker); + if (previouslyActive.current instanceof HTMLElement) { + previouslyActive.current.focus(); + } + }, []); + + const closeAndResetInput = useCallback(() => { + close(); + setInputValue(''); + }, [close]); + + const resetInput = useCallback(() => { + setInputValue(''); + }, []); + + function open(fromElement?: Element): void { + if (opened) { + return; + } + previouslyActive.current = fromElement || document.activeElement; + setOpened(true); + } + + function setFilter(filter: SearchFilter) { + // UI prevents adding more than one filter of the same type + setFilters(prevState => [...prevState, filter]); + inputRef.current?.focus(); + } + + function removeFilter(filter: SearchFilter) { + setFilters(prevState => { + const index = prevState.indexOf(filter); + if (index >= 0) { + const copied = [...prevState]; + copied.splice(index, 1); + return copied; + } + return prevState; + }); + inputRef.current?.focus(); + } + + return ( + + ); +}; + +export const useSearchContext = () => { + const context = useContext(SearchContext); + + if (!context) { + throw new Error('SearchContext requires SearchContextProvider context.'); + } + + return context; +}; diff --git a/web/packages/teleterm/src/ui/Search/actions.tsx b/web/packages/teleterm/src/ui/Search/actions.tsx new file mode 100644 index 0000000000000..6cf57422e2c09 --- /dev/null +++ b/web/packages/teleterm/src/ui/Search/actions.tsx @@ -0,0 +1,167 @@ +/** + * Copyright 2023 Gravitational, Inc + * + * 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 { IAppContext } from 'teleterm/ui/types'; +import { routing } from 'teleterm/ui/uri'; +import { GatewayProtocol } from 'teleterm/services/tshd/types'; +import { SearchResult } from 'teleterm/ui/Search/searchResult'; +import { SearchContext } from 'teleterm/ui/Search/SearchContext'; + +export interface SimpleAction { + type: 'simple-action'; + searchResult: SearchResult; + preventAutoClose?: boolean; // TODO(gzdunek): consider other options (callback preventClose() in perform?) + + perform(): void; +} + +export interface ParametrizedAction { + type: 'parametrized-action'; + searchResult: SearchResult; + preventAutoClose?: boolean; + parameter: { + getSuggestions(): Promise; + placeholder: string; + }; + + perform(parameter: string): void; +} + +export type SearchAction = SimpleAction | ParametrizedAction; + +export function mapToActions( + ctx: IAppContext, + searchContext: SearchContext, + searchResults: SearchResult[] +): SearchAction[] { + return searchResults.map(result => { + if (result.kind === 'server') { + return { + type: 'parametrized-action', + searchResult: result, + parameter: { + getSuggestions: async () => + ctx.clustersService.findClusterByResource(result.resource.uri) + ?.loggedInUser?.sshLoginsList, + placeholder: 'Provide login', + }, + perform(login) { + ctx.commandLauncher.executeCommand('tsh-ssh', { + localClusterUri: result.resource.uri, + loginHost: `${login}@${result.resource.hostname}`, + origin: 'search_bar', + }); + }, + }; + } + if (result.kind === 'kube') { + return { + type: 'simple-action', + searchResult: result, + perform() { + ctx.commandLauncher.executeCommand('kube-connect', { + kubeUri: result.resource.uri, + origin: 'search_bar', + }); + }, + }; + } + if (result.kind === 'database') { + return { + type: 'parametrized-action', + searchResult: result, + parameter: { + getSuggestions: () => + ctx.resourcesService.getDbUsers(result.resource.uri), + placeholder: 'Provide db username', + }, + async perform(dbUsername) { + const rootClusterUri = routing.ensureRootClusterUri( + result.resource.uri + ); + const documentsService = + ctx.workspacesService.getWorkspaceDocumentService(rootClusterUri); + + const doc = documentsService.createGatewayDocument({ + // Not passing the `gatewayUri` field here, as at this point the gateway doesn't exist yet. + // `port` is not passed as well, we'll let the tsh daemon pick a random one. + targetUri: result.resource.uri, + targetName: result.resource.name, + targetUser: getTargetUser( + result.resource.protocol as GatewayProtocol, + dbUsername + ), + origin: 'search_bar', + }); + + await ctx.workspacesService.setActiveWorkspace(rootClusterUri); + + const connectionToReuse = + ctx.connectionTracker.findConnectionByDocument(doc); + + if (connectionToReuse) { + ctx.connectionTracker.activateItem(connectionToReuse.id, { + origin: 'search_bar', + }); + } else { + documentsService.add(doc); + documentsService.open(doc.uri); + } + }, + }; + } + if (result.kind === 'resource-type-filter') { + return { + type: 'simple-action', + searchResult: result, + preventAutoClose: true, + perform() { + searchContext.setFilter({ + filter: 'resource-type', + resourceType: result.resource, + }); + }, + }; + } + if (result.kind === 'cluster-filter') { + return { + type: 'simple-action', + searchResult: result, + preventAutoClose: true, + perform() { + searchContext.setFilter({ + filter: 'cluster', + clusterUri: result.resource.uri, + }); + }, + }; + } + }); +} + +function getTargetUser( + protocol: GatewayProtocol, + providedDbUser: string +): string { + // we are replicating tsh behavior (user can be omitted for Redis) + // https://github.com/gravitational/teleport/blob/796e37bdbc1cb6e0a93b07115ffefa0e6922c529/tool/tsh/db.go#L240-L244 + // but unlike tsh, Connect has to provide a user that is then used in a gateway document + if (protocol === 'redis') { + return providedDbUser || 'default'; + } + + return providedDbUser; +} diff --git a/web/packages/teleterm/src/ui/Search/index.ts b/web/packages/teleterm/src/ui/Search/index.ts new file mode 100644 index 0000000000000..bd0feaffc015f --- /dev/null +++ b/web/packages/teleterm/src/ui/Search/index.ts @@ -0,0 +1,17 @@ +/** + * Copyright 2023 Gravitational, Inc. + * + * 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. + */ + +export { SearchBarConnected as SearchBar } from './SearchBar'; diff --git a/web/packages/teleterm/src/ui/Search/pickers/ActionPicker.story.tsx b/web/packages/teleterm/src/ui/Search/pickers/ActionPicker.story.tsx new file mode 100644 index 0000000000000..63879911dae7d --- /dev/null +++ b/web/packages/teleterm/src/ui/Search/pickers/ActionPicker.story.tsx @@ -0,0 +1,267 @@ +/** + * Copyright 2023 Gravitational, Inc + * + * 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 React from 'react'; +import { makeSuccessAttempt } from 'shared/hooks/useAsync'; + +import { routing } from 'teleterm/ui/uri'; + +import { SearchResult } from '../searchResult'; +import { + makeDatabase, + makeKube, + makeResourceResult, + makeServer, + makeLabelsList, +} from '../searchResultTestHelpers'; + +import { ComponentMap } from './ActionPicker'; +import { ResultList } from './ResultList'; + +import type * as uri from 'teleterm/ui/uri'; + +export default { + title: 'Teleterm/Search/ActionPicker', +}; + +const clusterUri: uri.ClusterUri = '/clusters/teleport-local'; + +export const Items = () => { + return ( +
* { + max-height: unset; + } + `} + > + +
+ ); +}; +export const ItemsNarrow = () => { + return ( +
* { + max-height: unset; + } + `} + > + +
+ ); +}; + +const List = () => { + const searchResults: SearchResult[] = [ + makeResourceResult({ + kind: 'server', + resource: makeServer({ + hostname: 'long-label-list', + uri: `${clusterUri}/servers/2f96e498-88ec-442f-a25b-569fa915041c`, + name: '2f96e498-88ec-442f-a25b-569fa915041c', + labelsList: makeLabelsList({ + arch: 'aarch64', + external: '32.192.113.93', + internal: '10.0.0.175', + kernel: '5.13.0-1234-aws', + service: 'ansible', + }), + }), + }), + makeResourceResult({ + kind: 'server', + resource: makeServer({ + hostname: 'short-label-list', + addr: '', + tunnel: true, + uri: `${clusterUri}/servers/90a29595-aac7-42eb-a484-c6c0e23f1a21`, + name: '90a29595-aac7-42eb-a484-c6c0e23f1a21', + labelsList: makeLabelsList({ + arch: 'aarch64', + service: 'ansible', + external: '32.192.113.93', + internal: '10.0.0.175', + }), + }), + }), + makeResourceResult({ + kind: 'server', + resourceMatches: [{ field: 'name', searchTerm: 'bbaaceba-6bd1-4750' }], + resource: makeServer({ + hostname: 'uuid-match', + addr: '', + tunnel: true, + uri: `${clusterUri}/servers/bbaaceba-6bd1-4750-9d3d-1a80e0cc8a63`, + name: 'bbaaceba-6bd1-4750-9d3d-1a80e0cc8a63', + labelsList: makeLabelsList({ + internal: '10.0.0.175', + service: 'ansible', + external: '32.192.113.93', + arch: 'aarch64', + }), + }), + }), + makeResourceResult({ + kind: 'database', + resource: makeDatabase({ + uri: `${clusterUri}/dbs/no-desc`, + name: 'no-desc', + desc: '', + labelsList: makeLabelsList({ + 'aws/Accounting': 'dev-ops', + 'aws/Environment': 'demo-13-biz', + 'aws/Name': 'db-bastion-4-13biz', + 'aws/Owner': 'foobar', + 'aws/Service': 'teleport-db', + engine: '🐘', + env: 'dev', + 'teleport.dev/origin': 'config-file', + }), + }), + }), + makeResourceResult({ + kind: 'database', + resource: makeDatabase({ + uri: `${clusterUri}/dbs/short-desc`, + name: 'short-desc', + desc: 'Lorem ipsum', + labelsList: makeLabelsList({ + 'aws/Environment': 'demo-13-biz', + 'aws/Name': 'db-bastion-4-13biz', + 'aws/Accounting': 'dev-ops', + 'aws/Owner': 'foobar', + 'aws/Service': 'teleport-db', + engine: '🐘', + env: 'dev', + 'teleport.dev/origin': 'config-file', + }), + }), + }), + makeResourceResult({ + kind: 'database', + resource: makeDatabase({ + uri: `${clusterUri}/dbs/long-desc`, + name: 'long-desc', + desc: 'Eget dignissim lectus nisi vitae nunc', + labelsList: makeLabelsList({ + 'aws/Environment': 'demo-13-biz', + 'aws/Name': 'db-bastion-4-13biz', + 'aws/Accounting': 'dev-ops', + 'aws/Owner': 'foobar', + 'aws/Service': 'teleport-db', + engine: '🐘', + env: 'dev', + 'teleport.dev/origin': 'config-file', + }), + }), + }), + makeResourceResult({ + kind: 'database', + resource: makeDatabase({ + uri: `${clusterUri}/dbs/super-long-desc`, + name: 'super-long-desc', + desc: 'Duis id tortor at purus tincidunt finibus. Mauris eu semper orci, non commodo lacus. Praesent sollicitudin magna id laoreet porta. Nunc lobortis varius sem vel fringilla.', + labelsList: makeLabelsList({ + 'aws/Environment': 'demo-13-biz', + 'aws/Accounting': 'dev-ops', + 'aws/Name': 'db-bastion-4-13biz', + engine: '🐘', + 'aws/Owner': 'foobar', + 'aws/Service': 'teleport-db', + env: 'dev', + 'teleport.dev/origin': 'config-file', + }), + }), + }), + makeResourceResult({ + kind: 'kube', + resource: makeKube({ + name: 'short-label-list', + labelsList: makeLabelsList({ + 'im-just-a-smol': 'kube', + kube: 'kubersson', + with: 'little-to-no-labels', + }), + }), + }), + makeResourceResult({ + kind: 'kube', + resource: makeKube({ + name: 'long-label-list', + uri: `${clusterUri}/kubes/long-label-list`, + labelsList: makeLabelsList({ + 'aws/Environment': 'demo-13-biz', + 'aws/Owner': 'foobar', + 'aws/Name': 'db-bastion-4-13biz', + kube: 'kubersson', + with: 'little-to-no-labels', + }), + }), + }), + { + kind: 'resource-type-filter', + resource: 'kubes', + nameMatch: '', + score: 0, + }, + { + kind: 'cluster-filter', + resource: { + name: 'teleport-local', + uri: clusterUri, + authClusterId: '', + connected: true, + leaf: false, + proxyHost: 'teleport-local.dev:3090', + }, + nameMatch: '', + score: 0, + }, + ]; + const attempt = makeSuccessAttempt(searchResults); + + return ( + + attempts={[attempt]} + onPick={() => {}} + onBack={() => {}} + render={searchResult => { + const Component = ComponentMap[searchResult.kind]; + + return { + key: + searchResult.kind !== 'resource-type-filter' + ? searchResult.resource.uri + : searchResult.resource, + Component: ( + + ), + }; + }} + /> + ); +}; diff --git a/web/packages/teleterm/src/ui/Search/pickers/ActionPicker.tsx b/web/packages/teleterm/src/ui/Search/pickers/ActionPicker.tsx new file mode 100644 index 0000000000000..ee8b78d68ab30 --- /dev/null +++ b/web/packages/teleterm/src/ui/Search/pickers/ActionPicker.tsx @@ -0,0 +1,501 @@ +/** + * Copyright 2023 Gravitational, Inc + * + * 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 React, { ReactElement, useCallback } from 'react'; +import styled from 'styled-components'; +import { Box, ButtonPrimary, Flex, Label as DesignLabel, Text } from 'design'; +import * as icons from 'design/Icon'; +import { Highlight } from 'shared/components/Highlight'; + +import { useAppContext } from 'teleterm/ui/appContextProvider'; +import { + ResourceMatch, + SearchResult, + ResourceSearchResult, + SearchResultDatabase, + SearchResultKube, + SearchResultServer, + SearchResultCluster, + SearchResultResourceType, +} from 'teleterm/ui/Search/searchResult'; +import * as tsh from 'teleterm/services/tshd/types'; +import * as uri from 'teleterm/ui/uri'; + +import { SearchAction } from '../actions'; +import { useSearchContext } from '../SearchContext'; + +import { useSearchAttempts } from './useSearchAttempts'; +import { getParameterPicker } from './pickers'; +import { ResultList, EmptyListCopy } from './ResultList'; + +export function ActionPicker(props: { input: ReactElement }) { + const ctx = useAppContext(); + const { clustersService } = ctx; + ctx.clustersService.useState(); + + const { + changeActivePicker, + close, + inputValue, + resetInput, + closeAndResetInput, + filters, + removeFilter, + } = useSearchContext(); + const attempts = useSearchAttempts(); + const totalCountOfClusters = clustersService.getClusters().length; + + const getClusterName = useCallback( + (resourceUri: uri.ClusterOrResourceUri) => { + if (totalCountOfClusters === 1) { + return; + } + + const clusterUri = uri.routing.ensureClusterUri(resourceUri); + const cluster = clustersService.findCluster(clusterUri); + + return cluster ? cluster.name : uri.routing.parseClusterName(resourceUri); + }, + [clustersService, totalCountOfClusters] + ); + + const onPick = useCallback( + (action: SearchAction) => { + if (action.type === 'simple-action') { + action.perform(); + // TODO: This logic probably should be encapsulated inside SearchContext, so that ActionPicker + // and ParameterPicker can reuse it. + // + // Overall, the context should probably encapsulate more logic so that the components don't + // have to worry about low-level stuff such as input state. Input state already lives in the + // search context so it should be managed from there, if possible. + if (action.preventAutoClose === true) { + resetInput(); + } else { + closeAndResetInput(); + } + } + if (action.type === 'parametrized-action') { + changeActivePicker(getParameterPicker(action)); + } + }, + [changeActivePicker, closeAndResetInput, resetInput] + ); + + // If the input is empty, we don't want to say "No matching results found" if the user is yet to + // type anything. This can happen e.g. after selecting two filters. + const NoResultsComponent = + inputValue.length > 0 ? ( + + No matching results found. + + ) : null; + + const filterButtons = filters.map(s => { + if (s.filter === 'resource-type') { + return ( + removeFilter(s)} + > + {s.resourceType} + + ); + } + if (s.filter === 'cluster') { + const clusterName = getClusterName(s.clusterUri); + return ( + removeFilter(s)} + > + {clusterName} + + ); + } + }); + + function handleKeyDown(e: React.KeyboardEvent) { + const { length } = filters; + if (e.key === 'Backspace' && inputValue === '' && length) { + removeFilter(filters[length - 1]); + } + } + + return ( + <> + + {filterButtons} + {props.input} + + + attempts={attempts} + onPick={onPick} + onBack={close} + render={item => { + const Component = ComponentMap[item.searchResult.kind]; + return { + key: + item.searchResult.kind !== 'resource-type-filter' + ? item.searchResult.resource.uri + : item.searchResult.resource, + Component: ( + + ), + }; + }} + NoResultsComponent={NoResultsComponent} + /> + + ); +} + +export const ComponentMap: Record< + SearchResult['kind'], + React.FC> +> = { + server: ServerItem, + kube: KubeItem, + database: DatabaseItem, + 'cluster-filter': ClusterFilterItem, + 'resource-type-filter': ResourceTypeFilterItem, +}; + +type SearchResultItem = { + searchResult: T; + getClusterName: (uri: uri.ResourceUri) => string; +}; + +function Item( + props: React.PropsWithChildren<{ + Icon: React.ComponentType<{ + color: string; + fontSize: string; + lineHeight: string; + }>; + iconColor: string; + }> +) { + return ( + + {/* lineHeight of the icon needs to match the line height of the first row of props.children */} + + + {props.children} + + + ); +} + +function ClusterFilterItem(props: SearchResultItem) { + return ( + + + Search only in{' '} + + + + + + ); +} + +function ResourceTypeFilterItem( + props: SearchResultItem +) { + return ( + + + Search only for{' '} + + + + + + ); +} + +export function ServerItem(props: SearchResultItem) { + const { searchResult } = props; + const server = searchResult.resource; + const hasUuidMatches = searchResult.resourceMatches.some( + match => match.field === 'name' + ); + + return ( + + + + Connect over SSH to{' '} + + + + + + + {props.getClusterName(server.uri)} + + + + + + + {server.tunnel ? ( + + ↵ tunnel + + ) : ( + + + + )} + + {hasUuidMatches && ( + + UUID:{' '} + + + )} + + + + ); +} + +export function DatabaseItem(props: SearchResultItem) { + const { searchResult } = props; + const db = searchResult.resource; + + const $resourceFields = ( + + + + / + + + {db.desc && ( + + + + )} + + ); + + return ( + + + + Set up a db connection for{' '} + + + + + + + {props.getClusterName(db.uri)} + + + + + {/* If the description is long, put the resource fields on a separate line. + Otherwise show the resource fields and the labels together in a single line. + */} + {db.desc.length >= 30 ? ( + <> + {$resourceFields} + + + ) : ( + {$resourceFields} + )} + + ); +} + +export function KubeItem(props: SearchResultItem) { + const { searchResult } = props; + + return ( + + + + Log in to Kubernetes cluster{' '} + + + + + + + {props.getClusterName(searchResult.resource.uri)} + + + + + + + ); +} + +function Labels( + props: React.PropsWithChildren<{ + searchResult: ResourceSearchResult; + }> +) { + const { searchResult } = props; + + // Label name to score. + const scoreMap: Map = new Map(); + searchResult.labelMatches.forEach(match => { + const currentScore = scoreMap.get(match.labelName) || 0; + scoreMap.set(match.labelName, currentScore + match.score); + }); + + const sortedLabelsList = [...searchResult.resource.labelsList]; + sortedLabelsList.sort( + (a, b) => + // Highest score first. + (scoreMap.get(b.name) || 0) - (scoreMap.get(a.name) || 0) + ); + + return ( + + {props.children} + {sortedLabelsList.map(label => ( + + ); +} + +const LabelsFlex = styled(Flex).attrs({ gap: 1 })` + overflow-x: hidden; + flex-wrap: nowrap; + align-items: baseline; + + // Make the children not shrink, otherwise they would shrink in attempt to render all labels in + // the same row. + & > * { + flex-shrink: 0; + } +`; + +const ResourceFields = styled(Flex).attrs({ gap: 1 })` + color: ${props => props.theme.colors.text.primary}; + font-size: ${props => props.theme.fontSizes[0]}px; +`; + +function Label(props: { + searchResult: ResourceSearchResult; + label: tsh.Label; +}) { + const { searchResult: item, label } = props; + const labelMatches = item.labelMatches.filter( + match => match.labelName == label.name + ); + const nameMatches = labelMatches + .filter(match => match.kind === 'label-name') + .map(match => match.searchTerm); + const valueMatches = labelMatches + .filter(match => match.kind === 'label-value') + .map(match => match.searchTerm); + + return ( + + :{' '} + + + ); +} + +function HighlightField(props: { + searchResult: ResourceSearchResult; + field: ResourceMatch['field']; +}) { + // `as` used as a workaround for a TypeScript issue. + // https://github.com/microsoft/TypeScript/issues/33591 + const keywords = ( + props.searchResult.resourceMatches as ResourceMatch< + ResourceSearchResult['kind'] + >[] + ) + .filter(match => match.field === props.field) + .map(match => match.searchTerm); + + return ( + + ); +} diff --git a/web/packages/teleterm/src/ui/Search/pickers/ParameterPicker.tsx b/web/packages/teleterm/src/ui/Search/pickers/ParameterPicker.tsx new file mode 100644 index 0000000000000..9fb949fc4ba49 --- /dev/null +++ b/web/packages/teleterm/src/ui/Search/pickers/ParameterPicker.tsx @@ -0,0 +1,88 @@ +/** + * Copyright 2023 Gravitational, Inc + * + * 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 React, { ReactElement, useCallback, useEffect } from 'react'; +import { Highlight } from 'shared/components/Highlight'; +import { + makeSuccessAttempt, + mapAttempt, + useAsync, +} from 'shared/hooks/useAsync'; + +import { useSearchContext } from '../SearchContext'; +import { ParametrizedAction } from '../actions'; + +import { ResultList } from './ResultList'; +import { actionPicker } from './pickers'; + +interface ParameterPickerProps { + action: ParametrizedAction; + input: ReactElement; +} + +export function ParameterPicker(props: ParameterPickerProps) { + const { inputValue, closeAndResetInput, changeActivePicker, resetInput } = + useSearchContext(); + const [suggestionsAttempt, fetch] = useAsync( + props.action.parameter.getSuggestions + ); + const inputSuggestionAttempt = makeSuccessAttempt(inputValue && [inputValue]); + + useEffect(() => { + fetch(); + }, [props.action]); + + const attempt = mapAttempt(suggestionsAttempt, suggestions => + suggestions.filter( + v => + v.toLocaleLowerCase().includes(inputValue.toLocaleLowerCase()) && + v !== inputValue + ) + ); + + const onPick = useCallback( + (item: string) => { + props.action.perform(item); + if (props.action.preventAutoClose === true) { + resetInput(); + } else { + closeAndResetInput(); + } + }, + [closeAndResetInput, resetInput, props.action] + ); + + const onBack = useCallback(() => { + changeActivePicker(actionPicker); + }, [changeActivePicker]); + + return ( + <> + {props.input} + + attempts={[inputSuggestionAttempt, attempt]} + onPick={onPick} + onBack={onBack} + render={item => ({ + key: item, + Component: ( + + ), + })} + /> + + ); +} diff --git a/web/packages/teleterm/src/ui/Search/pickers/ResultList.tsx b/web/packages/teleterm/src/ui/Search/pickers/ResultList.tsx new file mode 100644 index 0000000000000..22fd67d05ae40 --- /dev/null +++ b/web/packages/teleterm/src/ui/Search/pickers/ResultList.tsx @@ -0,0 +1,213 @@ +/** + * Copyright 2023 Gravitational, Inc. + * + * 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 React, { + ReactElement, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; +import styled from 'styled-components'; + +import { Attempt } from 'shared/hooks/useAsync'; +import { Box } from 'design'; + +import LinearProgress from 'teleterm/ui/components/LinearProgress'; + +type ResultListProps = { + /** + * List of attempts containing results to render. + * Displayed items will follow the order of attempts. + * If any attempt is loading, then the loading bar is visible. + */ + attempts: Attempt[]; + /** + * NoResultsComponent is the element that's going to be rendered instead of the list if the + * attempt has successfully finished but there's no results to show. + */ + NoResultsComponent?: ReactElement; + onPick(item: T): void; + onBack(): void; + render(item: T): { Component: ReactElement; key: string }; +}; + +export function ResultList(props: ResultListProps) { + const { attempts, NoResultsComponent, onPick, onBack } = props; + const activeItemRef = useRef(); + const [activeItemIndex, setActiveItemIndex] = useState(0); + const shouldShowNoResultsCopy = + NoResultsComponent && + attempts.every(a => a.status === 'success' && a.data.length === 0); + + const items = useMemo(() => { + return attempts.map(a => a.data || []).flat(); + }, [attempts]); + + // Reset the active item index if it's greater than the number of available items. + // This can happen in cases where the user selects the nth item and then filters the list so that + // there's only one item. + if (activeItemIndex !== 0 && activeItemIndex >= items.length) { + setActiveItemIndex(0); + } + + useEffect(() => { + const handleArrowKey = (e: KeyboardEvent, nudge: number) => { + const next = getNext(activeItemIndex + nudge, items.length); + setActiveItemIndex(next); + // `false` - bottom of the element will be aligned to the bottom of the visible area of the scrollable ancestor + activeItemRef.current?.scrollIntoView(false); + }; + + const handleKeyDown = (e: KeyboardEvent) => { + switch (e.key) { + case 'Enter': { + e.stopPropagation(); + e.preventDefault(); + + const item = items[activeItemIndex]; + if (item) { + onPick(item); + } + break; + } + case 'Escape': { + onBack(); + break; + } + case 'ArrowUp': + e.stopPropagation(); + e.preventDefault(); + + handleArrowKey(e, -1); + break; + case 'ArrowDown': + e.stopPropagation(); + e.preventDefault(); + + handleArrowKey(e, 1); + break; + } + }; + + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, [items, onPick, onBack, activeItemIndex]); + + return ( + + {attempts.some(a => a.status === 'processing') && ( +
+ +
+ )} + {items.map((r, index) => { + const isActive = index === activeItemIndex; + const { Component, key } = props.render(r); + + return ( + props.onPick(r)} + > + {Component} + + ); + })} + {shouldShowNoResultsCopy && NoResultsComponent} +
+ ); +} + +const StyledItem = styled.div` + &:hover, + &:focus { + cursor: pointer; + background: ${props => props.theme.colors.levels.elevated}; + } + + & mark { + color: inherit; + background-color: ${props => props.theme.colors.brand.accent}; + } + + :not(:last-of-type) { + border-bottom: 2px solid + ${props => props.theme.colors.levels.surfaceSecondary}; + } + + padding: ${props => props.theme.space[2]}px; + color: ${props => props.theme.colors.text.contrast}; + background: ${props => + props.$active + ? props.theme.colors.levels.elevated + : props.theme.colors.levels.surface}; +`; + +export const EmptyListCopy = styled(Box)` + width: 100%; + height: 100%; + padding: ${props => props.theme.space[2]}px; + line-height: 1.5em; + + ul { + margin: 0; + padding-inline-start: 2em; + } +`; + +function getNext(selectedIndex = 0, max = 0) { + let index = selectedIndex % max; + if (index < 0) { + index += max; + } + return index; +} + +const StyledGlobalSearchResults = styled.div(({ theme }) => { + return { + boxShadow: '8px 8px 18px rgb(0 0 0)', + color: theme.colors.text.contrast, + background: theme.colors.levels.surface, + boxSizing: 'border-box', + // Account for border. + width: 'calc(100% + 2px)', + // Careful, this is hardcoded based on the input height. + marginTop: '38px', + display: 'block', + position: 'absolute', + border: '1px solid ' + theme.colors.action.hover, + fontSize: '12px', + listStyle: 'none outside none', + textShadow: 'none', + zIndex: '1000', + maxHeight: '350px', + overflow: 'auto', + // Hardcoded to height of the shortest item. + minHeight: '42px', + }; +}); diff --git a/web/packages/teleterm/src/ui/Search/pickers/pickers.tsx b/web/packages/teleterm/src/ui/Search/pickers/pickers.tsx new file mode 100644 index 0000000000000..5dd16cc5ab012 --- /dev/null +++ b/web/packages/teleterm/src/ui/Search/pickers/pickers.tsx @@ -0,0 +1,44 @@ +/** + * Copyright 2023 Gravitational, Inc + * + * 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 React from 'react'; + +import { ParametrizedAction } from '../actions'; + +import { ActionPicker } from './ActionPicker'; +import { ParameterPicker } from './ParameterPicker'; + +export const actionPicker: SearchPicker = { + picker: props => , + placeholder: 'Search for resources by name and labels across clusters', +}; +export const getParameterPicker = ( + parametrizedAction: ParametrizedAction +): SearchPicker => { + return { + picker: props => ( + + ), + placeholder: parametrizedAction.parameter.placeholder, + }; +}; + +export interface SearchPicker { + picker: React.ComponentType<{ + input: React.ReactElement; + }>; + placeholder: string; +} diff --git a/web/packages/teleterm/src/ui/Search/pickers/useSearchAttempts.ts b/web/packages/teleterm/src/ui/Search/pickers/useSearchAttempts.ts new file mode 100644 index 0000000000000..6c34d683efec5 --- /dev/null +++ b/web/packages/teleterm/src/ui/Search/pickers/useSearchAttempts.ts @@ -0,0 +1,113 @@ +/** + * Copyright 2023 Gravitational, Inc + * + * 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 { + useCallback, + useEffect, + useLayoutEffect, + useMemo, + useRef, +} from 'react'; +import { makeEmptyAttempt, mapAttempt, useAsync } from 'shared/hooks/useAsync'; +import { debounce } from 'shared/utils/highbar'; + +import { + sortResults, + useFilterSearch, + useResourceSearch, +} from 'teleterm/ui/Search/useSearch'; +import { mapToActions } from 'teleterm/ui/Search/actions'; +import Logger from 'teleterm/logger'; +import { useAppContext } from 'teleterm/ui/appContextProvider'; +import { useSearchContext } from 'teleterm/ui/Search/SearchContext'; + +export function useSearchAttempts() { + const searchLogger = useRef(new Logger('search')); + const ctx = useAppContext(); + const searchContext = useSearchContext(); + const { inputValue, filters } = searchContext; + + const [resourceSearchAttempt, runResourceSearch, setResourceSearchAttempt] = + useAsync(useResourceSearch()); + const [filterSearchAttempt, runFilterSearch, setFilterSearchAttempt] = + useAsync(useFilterSearch()); + + const runResourceSearchDebounced = useDebounce(runResourceSearch, 200); + + // Both states are used by mapToActions. + ctx.workspacesService.useState(); + ctx.clustersService.useState(); + + const resetAttempts = useCallback(() => { + setResourceSearchAttempt(makeEmptyAttempt()); + setFilterSearchAttempt(makeEmptyAttempt()); + }, [setResourceSearchAttempt, setFilterSearchAttempt]); + + const resourceActionsAttempt = useMemo( + () => + mapAttempt(resourceSearchAttempt, ({ results, search }) => { + const sortedResults = sortResults(results, search); + searchLogger.current.info('results for', search, sortedResults); + + return mapToActions(ctx, searchContext, sortedResults); + }), + [ctx, resourceSearchAttempt, searchContext] + ); + + const filterActionsAttempt = useMemo( + () => + mapAttempt(filterSearchAttempt, ({ results }) => + // TODO(gzdunek): filters are sorted inline, should be done here to align with resource search + mapToActions(ctx, searchContext, results) + ), + [ctx, filterSearchAttempt, searchContext] + ); + + useEffect(() => { + // Reset both attempts as soon as the input changes. If we didn't do that, then the resource + // search attempt would only get updated on debounce. This could lead to the following scenario: + // + // 1. You type in `foo`, wait for the results to show up. + // 2. You clear the input and quickly type in `bar`. + // 3. Now you see the stale results for `foo`, because the debounce didn't kick in yet. + resetAttempts(); + + runFilterSearch(inputValue, filters); + runResourceSearchDebounced(inputValue, filters); + }, [ + inputValue, + filters, + resetAttempts, + runFilterSearch, + runResourceSearchDebounced, + ]); + + return [filterActionsAttempt, resourceActionsAttempt]; +} + +function useDebounce( + callback: (...args: Args) => ReturnValue, + delay: number +) { + const callbackRef = useRef(callback); + useLayoutEffect(() => { + callbackRef.current = callback; + }); + return useMemo( + () => debounce((...args: Args) => callbackRef.current(...args), delay), + [delay] + ); +} diff --git a/web/packages/teleterm/src/ui/Search/searchResult.ts b/web/packages/teleterm/src/ui/Search/searchResult.ts new file mode 100644 index 0000000000000..b8572e7299634 --- /dev/null +++ b/web/packages/teleterm/src/ui/Search/searchResult.ts @@ -0,0 +1,114 @@ +/** + * Copyright 2023 Gravitational, Inc + * + * 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 type { ClusterUri } from 'teleterm/ui/uri'; +import type { Cluster } from 'teleterm/services/tshd/types'; + +import type * as resourcesServiceTypes from 'teleterm/ui/services/resources'; +import type { SearchResultResource } from 'teleterm/ui/services/resources'; + +export { SearchResultResource }; + +type ResourceSearchResultBase< + Result extends resourcesServiceTypes.SearchResult +> = Result & { + labelMatches: LabelMatch[]; + resourceMatches: ResourceMatch[]; + score: number; +}; + +export type SearchResultServer = + ResourceSearchResultBase; +export type SearchResultDatabase = + ResourceSearchResultBase; +export type SearchResultKube = + ResourceSearchResultBase; +export type SearchResultCluster = { + kind: 'cluster-filter'; + resource: Cluster; + nameMatch: string; + score: number; +}; +export type SearchResultResourceType = { + kind: 'resource-type-filter'; + resource: 'kubes' | 'servers' | 'databases'; + nameMatch: string; + score: number; +}; + +// TODO(gzdunek): find a better name. +// `ResourcesService` exports `SearchResult` which is then usually imported as `ResourceSearchResult`. +// Having these two thing named almost the same is confusing. +export type ResourceSearchResult = + | SearchResultServer + | SearchResultDatabase + | SearchResultKube; + +export type FilterSearchResult = SearchResultResourceType | SearchResultCluster; + +export type SearchResult = ResourceSearchResult | FilterSearchResult; + +export type LabelMatch = { + kind: 'label-name' | 'label-value'; + labelName: string; + searchTerm: string; + // Individual score of this label match; how much it contributes to the total score. + score: number; +}; + +export type ResourceMatch = { + field: typeof searchableFields[Kind][number]; + searchTerm: string; +}; + +/** + * mainResourceName returns the main identifier for the given resource displayed in the UI. + */ +export function mainResourceName(searchResult: ResourceSearchResult): string { + return searchResult.resource[mainResourceField[searchResult.kind]]; +} + +export const mainResourceField: { + [Kind in ResourceSearchResult['kind']]: keyof SearchResultResource; +} = { + server: 'hostname', + database: 'name', + kube: 'name', +} as const; + +// The usage of Exclude here is a workaround to make sure that the fields in the array point only to +// fields of string type. +export const searchableFields: { + [Kind in ResourceSearchResult['kind']]: ReadonlyArray< + Exclude, 'labelsList'> + >; +} = { + server: ['name', 'hostname', 'addr'], + database: ['name', 'desc', 'protocol', 'type'], + kube: ['name'], +} as const; + +export interface ResourceTypeSearchFilter { + filter: 'resource-type'; + resourceType: 'kubes' | 'servers' | 'databases'; +} + +export interface ClusterSearchFilter { + filter: 'cluster'; + clusterUri: ClusterUri; +} + +export type SearchFilter = ResourceTypeSearchFilter | ClusterSearchFilter; diff --git a/web/packages/teleterm/src/ui/Search/searchResultTestHelpers.ts b/web/packages/teleterm/src/ui/Search/searchResultTestHelpers.ts new file mode 100644 index 0000000000000..af48315d466d1 --- /dev/null +++ b/web/packages/teleterm/src/ui/Search/searchResultTestHelpers.ts @@ -0,0 +1,63 @@ +/** + * Copyright 2023 Gravitational, Inc + * + * 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 { ResourceSearchResult } from './searchResult'; + +import type * as tsh from 'teleterm/services/tshd/types'; + +export const makeServer = (props: Partial): tsh.Server => ({ + uri: '/clusters/teleport-local/servers/178ef081-259b-4aa5-a018-449b5ea7e694', + tunnel: false, + name: '178ef081-259b-4aa5-a018-449b5ea7e694', + hostname: 'foo', + addr: '127.0.0.1:3022', + labelsList: [], + ...props, +}); + +export const makeDatabase = (props: Partial): tsh.Database => ({ + uri: '/clusters/teleport-local/dbs/foo', + name: 'foo', + protocol: 'postgres', + type: 'self-hosted', + desc: '', + hostname: '', + addr: '', + labelsList: [], + ...props, +}); + +export const makeKube = (props: Partial): tsh.Kube => ({ + name: 'foo', + labelsList: [], + uri: '/clusters/bar/kubes/foo', + ...props, +}); + +export const makeLabelsList = (labels: Record): tsh.Label[] => + Object.entries(labels).map(([name, value]) => ({ name, value })); + +export const makeResourceResult = ( + props: Partial & { + kind: ResourceSearchResult['kind']; + resource: ResourceSearchResult['resource']; + } +): ResourceSearchResult => ({ + score: 0, + labelMatches: [], + resourceMatches: [], + ...props, +}); diff --git a/web/packages/teleterm/src/ui/Search/useSearch.test.ts b/web/packages/teleterm/src/ui/Search/useSearch.test.ts new file mode 100644 index 0000000000000..eb1a6fdfc46a3 --- /dev/null +++ b/web/packages/teleterm/src/ui/Search/useSearch.test.ts @@ -0,0 +1,66 @@ +/** + * Copyright 2023 Gravitational, Inc + * + * 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 { sortResults } from './useSearch'; +import { + makeResourceResult, + makeServer, + makeKube, + makeLabelsList, +} from './searchResultTestHelpers'; + +describe('sortResults', () => { + it('uses the displayed resource name as the tie breaker if the scores are equal', () => { + const server = makeResourceResult({ + kind: 'server', + resource: makeServer({ hostname: 'z' }), + }); + const kube = makeResourceResult({ + kind: 'kube', + resource: makeKube({ name: 'a' }), + }); + const sortedResults = sortResults([server, kube], ''); + + expect(sortedResults[0]).toEqual(kube); + expect(sortedResults[1]).toEqual(server); + }); + + it('saves individual label match scores', () => { + const server = makeResourceResult({ + kind: 'server', + resource: makeServer({ + labelsList: makeLabelsList({ quux: 'bar-baz', foo: 'bar' }), + }), + }); + + const { labelMatches } = sortResults([server], 'foo bar')[0]; + + labelMatches.forEach(match => { + expect(match.score).toBeGreaterThan(0); + }); + + const quuxMatches = labelMatches.filter( + match => match.labelName === 'quux' + ); + const quuxMatch = quuxMatches[0]; + const fooMatches = labelMatches.filter(match => match.labelName === 'foo'); + + expect(quuxMatches).toHaveLength(1); + expect(fooMatches).toHaveLength(2); + expect(fooMatches[0].score).toBeGreaterThan(quuxMatch.score); + expect(fooMatches[1].score).toBeGreaterThan(quuxMatch.score); + }); +}); diff --git a/web/packages/teleterm/src/ui/Search/useSearch.ts b/web/packages/teleterm/src/ui/Search/useSearch.ts new file mode 100644 index 0000000000000..41095239a972a --- /dev/null +++ b/web/packages/teleterm/src/ui/Search/useSearch.ts @@ -0,0 +1,312 @@ +/** + * Copyright 2023 Gravitational, Inc + * + * 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 { useCallback } from 'react'; + +import { assertUnreachable } from 'teleterm/ui/utils'; +import { useAppContext } from 'teleterm/ui/appContextProvider'; + +import { + ClusterSearchFilter, + ResourceTypeSearchFilter, + SearchFilter, + LabelMatch, + mainResourceField, + mainResourceName, + ResourceMatch, + searchableFields, + ResourceSearchResult, + FilterSearchResult, +} from './searchResult'; + +import type * as resourcesServiceTypes from 'teleterm/ui/services/resources'; + +/** + * useResourceSearch returns a function which searches for the given list of space-separated keywords across + * all root and leaf clusters that the user is currently logged in to. + * + * It does so by issuing a separate request for each resource type to each cluster. It fails if any + * of those requests fail. + */ +export function useResourceSearch() { + const { clustersService, resourcesService } = useAppContext(); + clustersService.useState(); + + return useCallback( + async (search: string, restrictions: SearchFilter[]) => { + // useResourceSearch has to return _something_ when the input is empty. Imagine this scenario: + // + // 1. The user types in 'data' into the search bar. + // 2. The search bar immediately returns filters plus it starts a resource search for 'foo'. + // 3. The user selects one of the filters before the backend response comes back. + // + // The request for 'foo' that was in flight needs to be canceled somehow. We can do that by + // issuing another one for empty input and making `useResourceSearch` return an empty array + // in that scenario. + if (!search) { + return { results: [], search }; + } + + const clusterSearchFilter = restrictions.find( + s => s.filter === 'cluster' + ) as ClusterSearchFilter; + const resourceTypeSearchFilter = restrictions.find( + s => s.filter === 'resource-type' + ) as ResourceTypeSearchFilter; + + const connectedClusters = clustersService + .getClusters() + .filter(c => c.connected); + const clustersToSearch = clusterSearchFilter + ? connectedClusters.filter( + c => clusterSearchFilter.clusterUri === c.uri + ) + : connectedClusters; + + const searchPromises = clustersToSearch.map(cluster => + resourcesService.searchResources( + cluster.uri, + search, + resourceTypeSearchFilter + ) + ); + const results = (await Promise.all(searchPromises)).flat().slice(0, 10); + + return { results, search }; + }, + [clustersService, resourcesService] + ); +} + +/** + * `useFilterSearch` returns a function which searches for clusters or resource types, + * which are later used to narrow down the requests in `useResourceSearch`. + */ +export function useFilterSearch() { + const { clustersService, workspacesService } = useAppContext(); + clustersService.useState(); + workspacesService.useState(); + + return useCallback( + async ( + search: string, + restrictions: SearchFilter[] + ): Promise<{ results: FilterSearchResult[]; search: string }> => { + const getClusters = () => { + let clusters = clustersService.getClusters(); + if (search) { + clusters = clusters.filter(cluster => + cluster.name + .toLocaleLowerCase() + .includes(search.toLocaleLowerCase()) + ); + } + return clusters.map(cluster => { + let score = getLengthScore(search, cluster.name); + if ( + cluster.uri === + workspacesService.getActiveWorkspace()?.localClusterUri + ) { + // put the active cluster first (only when there is a match, otherwise score is 0) + score *= 3; + } + return { + kind: 'cluster-filter' as const, + resource: cluster, + nameMatch: search, + score, + }; + }); + }; + const getResourceType = () => { + let resourceTypes = [ + 'kubes' as const, + 'servers' as const, + 'databases' as const, + ]; + if (search) { + resourceTypes = resourceTypes.filter(resourceType => + resourceType.toLowerCase().includes(search.toLowerCase()) + ); + } + return resourceTypes.map(resourceType => ({ + kind: 'resource-type-filter' as const, + resource: resourceType, + nameMatch: search, + score: getLengthScore(search, resourceType), + })); + }; + + const shouldReturnClusters = !restrictions.some( + r => r.filter === 'cluster' + ); + const shouldReturnResourceTypes = !restrictions.some( + r => r.filter === 'resource-type' + ); + + const results = [ + shouldReturnResourceTypes && getResourceType(), + shouldReturnClusters && getClusters(), + ] + .filter(Boolean) + .flat() + .sort((a, b) => { + // Highest score first. + return b.score - a.score; + }); + + return { results, search }; + }, + [clustersService, workspacesService] + ); +} + +export function sortResults( + searchResults: resourcesServiceTypes.SearchResult[], + search: string +): ResourceSearchResult[] { + const terms = search + .split(' ') + .filter(Boolean) + // We have to match the implementation of the search algorithm as closely as possible. It uses + // strings.ToLower from Go which unfortunately doesn't have a good equivalent in JavaScript. + // + // strings.ToLower uses some kind of a universal map for lowercasing non-ASCII characters such + // as the Turkish İ. JavaScript doesn't have such a function, possibly because it's not possible + // to have universal case mapping. [1] + // + // The closest thing that JS has is toLocaleLowerCase. Since we don't know what locale the + // search string uses, we let the runtime figure it out based on the system settings. + // The assumption is that if someone has a resource with e.g. Turkish characters, their system + // is set to the appropriate locale and the search results will be properly scored. + // + // Highlighting will have problems with some non-ASCII characters anyway because the library we + // use for highlighting uses a regex with the i flag underneath. + // + // [1] https://web.archive.org/web/20190113111936/https://blogs.msdn.microsoft.com/oldnewthing/20030905-00/?p=42643 + .map(term => term.toLocaleLowerCase()); + const collator = new Intl.Collator(); + + return searchResults + .map(searchResult => calculateScore(populateMatches(searchResult, terms))) + .sort( + (a, b) => + // Highest score first. + b.score - a.score || + collator.compare(mainResourceName(a), mainResourceName(b)) + ); +} + +function populateMatches( + searchResult: resourcesServiceTypes.SearchResult, + terms: string[] +): ResourceSearchResult { + const labelMatches: LabelMatch[] = []; + const resourceMatches: ResourceMatch[] = []; + + terms.forEach(term => { + searchResult.resource.labelsList.forEach(label => { + // indexOf is faster on Chrome than includes or regex. + // https://jsbench.me/b7lf9kvrux/1 + const nameIndex = label.name.toLocaleLowerCase().indexOf(term); + const valueIndex = label.value.toLocaleLowerCase().indexOf(term); + + if (nameIndex >= 0) { + labelMatches.push({ + kind: 'label-name', + labelName: label.name, + searchTerm: term, + score: 0, + }); + } + + if (valueIndex >= 0) { + labelMatches.push({ + kind: 'label-value', + labelName: label.name, + searchTerm: term, + score: 0, + }); + } + }); + + searchableFields[searchResult.kind].forEach(field => { + // `String` here is just to satisfy the compiler. + const index = searchResult.resource[field] + .toLocaleLowerCase() + .indexOf(term); + + if (index >= 0) { + resourceMatches.push({ + field, + searchTerm: term, + }); + } + }); + }); + + return { ...searchResult, labelMatches, resourceMatches, score: 0 }; +} + +// TODO(ravicious): Extract the scoring logic to a function to better illustrate different weight +// for different matches. +function calculateScore( + searchResult: ResourceSearchResult +): ResourceSearchResult { + let searchResultScore = 0; + + const labelMatches = searchResult.labelMatches.map(match => { + const label = searchResult.resource.labelsList.find( + label => label.name === match.labelName + ); + let matchedValue: string; + + switch (match.kind) { + case 'label-name': { + matchedValue = label.name; + break; + } + case 'label-value': { + matchedValue = label.value; + break; + } + default: { + assertUnreachable(match.kind); + } + } + + const score = getLengthScore(match.searchTerm, matchedValue); + searchResultScore += score; + + return { ...match, score }; + }); + + for (const match of searchResult.resourceMatches) { + const { searchTerm } = match; + const field = searchResult.resource[match.field]; + const isMainField = mainResourceField[searchResult.kind] === match.field; + const weight = isMainField ? 4 : 2; + + const resourceMatchScore = getLengthScore(searchTerm, field) * weight; + searchResultScore += resourceMatchScore; + } + + return { ...searchResult, labelMatches, score: searchResultScore }; +} + +function getLengthScore(searchTerm: string, matchedValue: string): number { + return Math.floor((searchTerm.length / matchedValue.length) * 100); +} diff --git a/web/packages/teleterm/src/ui/TabHost/index.ts b/web/packages/teleterm/src/ui/TabHost/index.ts index 43d0949a98fd9..5fac6e39fc54a 100644 --- a/web/packages/teleterm/src/ui/TabHost/index.ts +++ b/web/packages/teleterm/src/ui/TabHost/index.ts @@ -15,3 +15,4 @@ limitations under the License. */ export * from './TabHost'; +export { useNewTabOpener } from './useNewTabOpener'; diff --git a/web/packages/teleterm/src/ui/TabHost/useNewTabOpener.ts b/web/packages/teleterm/src/ui/TabHost/useNewTabOpener.ts index 8f7c22dced86d..5623b0a0e118d 100644 --- a/web/packages/teleterm/src/ui/TabHost/useNewTabOpener.ts +++ b/web/packages/teleterm/src/ui/TabHost/useNewTabOpener.ts @@ -17,7 +17,7 @@ import { useCallback } from 'react'; import { DocumentsService } from 'teleterm/ui/services/workspacesService'; -import { ClusterUri } from 'teleterm/ui/uri'; +import { ClusterUri, routing } from 'teleterm/ui/uri'; export function useNewTabOpener({ documentsService, @@ -27,15 +27,29 @@ export function useNewTabOpener({ localClusterUri: ClusterUri; }) { const openClusterTab = useCallback(() => { - if (localClusterUri) { - const clusterDocument = documentsService.createClusterDocument({ - clusterUri: localClusterUri, - }); + if (!localClusterUri) { + return; + } + + const clusterDocument = documentsService.createClusterDocument({ + clusterUri: localClusterUri, + }); - documentsService.add(clusterDocument); - documentsService.open(clusterDocument.uri); + documentsService.add(clusterDocument); + documentsService.open(clusterDocument.uri); + }, [documentsService, localClusterUri]); + + const openTerminalTab = useCallback(() => { + if (!localClusterUri) { + return; } + + const { params } = routing.parseClusterUri(localClusterUri); + documentsService.openNewTerminal({ + rootClusterId: params.rootClusterId, + leafClusterId: params.leafClusterId, + }); }, [documentsService, localClusterUri]); - return { openClusterTab }; + return { openClusterTab, openTerminalTab }; } diff --git a/web/packages/teleterm/src/ui/TabHost/useTabShortcuts.test.tsx b/web/packages/teleterm/src/ui/TabHost/useTabShortcuts.test.tsx index 2960113e6c6ec..4270c4f70fea0 100644 --- a/web/packages/teleterm/src/ui/TabHost/useTabShortcuts.test.tsx +++ b/web/packages/teleterm/src/ui/TabHost/useTabShortcuts.test.tsx @@ -59,6 +59,7 @@ function getMockDocuments(): Document[] { targetUri: '/clusters/bar/dbs/foobar', targetName: 'foobar', targetUser: 'foo', + origin: 'resource_table', }, { kind: 'doc.gateway', @@ -68,6 +69,7 @@ function getMockDocuments(): Document[] { targetUri: '/clusters/bar/dbs/foobar', targetName: 'foobar', targetUser: 'bar', + origin: 'resource_table', }, { kind: 'doc.cluster', diff --git a/web/packages/teleterm/src/ui/TabHost/useTabShortcuts.ts b/web/packages/teleterm/src/ui/TabHost/useTabShortcuts.ts index 74f14b962d80c..c3c2da6c1b405 100644 --- a/web/packages/teleterm/src/ui/TabHost/useTabShortcuts.ts +++ b/web/packages/teleterm/src/ui/TabHost/useTabShortcuts.ts @@ -31,20 +31,21 @@ export function useTabShortcuts({ documentsService: DocumentsService; localClusterUri: ClusterUri; }) { - const { openClusterTab } = useNewTabOpener({ + const { openClusterTab, openTerminalTab } = useNewTabOpener({ documentsService, localClusterUri, }); const tabsShortcuts = useMemo( - () => buildTabsShortcuts(documentsService, openClusterTab), - [documentsService, openClusterTab] + () => buildTabsShortcuts(documentsService, openClusterTab, openTerminalTab), + [documentsService, openClusterTab, openTerminalTab] ); useKeyboardShortcuts(tabsShortcuts); } function buildTabsShortcuts( documentService: DocumentsService, - openClusterTab: () => void + openClusterTab: () => void, + openTerminalTab: () => void ): KeyboardShortcutHandlers { const handleTabIndex = (index: number) => () => { const docs = documentService.getDocuments(); @@ -77,6 +78,7 @@ function buildTabsShortcuts( documentService.open(allDocuments[indexToOpen].uri); }; + return { tab1: handleTabIndex(0), tab2: handleTabIndex(1), @@ -91,5 +93,6 @@ function buildTabsShortcuts( previousTab: handleTabSwitch('previous'), nextTab: handleTabSwitch('next'), newTab: openClusterTab, + newTerminalTab: openTerminalTab, }; } diff --git a/web/packages/teleterm/src/ui/TopBar/Clusters/ClusterSelector/ClusterSelector.tsx b/web/packages/teleterm/src/ui/TopBar/Clusters/ClusterSelector/ClusterSelector.tsx index 2122b7302f137..739f3052bccf2 100644 --- a/web/packages/teleterm/src/ui/TopBar/Clusters/ClusterSelector/ClusterSelector.tsx +++ b/web/packages/teleterm/src/ui/TopBar/Clusters/ClusterSelector/ClusterSelector.tsx @@ -62,16 +62,16 @@ const Container = styled.button` background: inherit; color: inherit; font-family: inherit; - min-width: 0; - width: 100%; + flex: 1; + flex-shrink: 2; + min-width: calc(${props => props.theme.space[7]}px * 2); height: 100%; border: 0.5px ${props => props.theme.colors.action.disabledBackground} solid; border-radius: 4px; display: flex; - flex-grow: 1; justify-content: space-between; align-items: center; - padding: 0 12px; + padding: 0 ${props => props.theme.space[2]}px; opacity: ${props => (props.isClusterSelected ? 1 : 0.6)}; cursor: pointer; diff --git a/web/packages/teleterm/src/ui/TopBar/Clusters/ClustersFilterableList/ClusterItem.tsx b/web/packages/teleterm/src/ui/TopBar/Clusters/ClustersFilterableList/ClusterItem.tsx index 7a11ea99889dc..2360b21cb65a1 100644 --- a/web/packages/teleterm/src/ui/TopBar/Clusters/ClustersFilterableList/ClusterItem.tsx +++ b/web/packages/teleterm/src/ui/TopBar/Clusters/ClustersFilterableList/ClusterItem.tsx @@ -91,16 +91,10 @@ function getBackgroundColor(props) { } return props.theme.colors.brand.main; } - if (props.isActive) { - return props.theme.colors.secondary.lighter; - } } function getHoverBackgroundColor(props) { if (props.isSelected) { return props.theme.colors.brand.accent; } - if (props.isActive) { - return props.theme.colors.secondary.lighter; - } } diff --git a/web/packages/teleterm/src/ui/TopBar/Connections/useConnections.ts b/web/packages/teleterm/src/ui/TopBar/Connections/useConnections.ts index 843ab007406c0..aee4443f8d0b2 100644 --- a/web/packages/teleterm/src/ui/TopBar/Connections/useConnections.ts +++ b/web/packages/teleterm/src/ui/TopBar/Connections/useConnections.ts @@ -56,7 +56,8 @@ export function useConnections() { return { isAnyConnectionActive: items.some(c => c.connected), removeItem: (id: string) => connectionTracker.removeItem(id), - activateItem: (id: string) => connectionTracker.activateItem(id), + activateItem: (id: string) => + connectionTracker.activateItem(id, { origin: 'connection_list' }), disconnectItem: (id: string) => connectionTracker.disconnectItem(id), updateSorting, items: getSortedItems(), diff --git a/web/packages/teleterm/src/ui/TopBar/MoreOptions.tsx b/web/packages/teleterm/src/ui/TopBar/MoreOptions.tsx new file mode 100644 index 0000000000000..b6a735970910b --- /dev/null +++ b/web/packages/teleterm/src/ui/TopBar/MoreOptions.tsx @@ -0,0 +1,240 @@ +/** + * Copyright 2023 Gravitational, Inc + * + * 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 React, { useState, useRef } from 'react'; +import styled from 'styled-components'; + +import { Flex, Text, Popover } from 'design'; +import * as icons from 'design/Icon'; + +import { useAppContext } from 'teleterm/ui/appContextProvider'; +import { TopBarButton } from 'teleterm/ui/TopBar/TopBarButton'; +import { IAppContext } from 'teleterm/ui/types'; +import { Cluster } from 'teleterm/services/tshd/types'; +import { KeyboardShortcutAction } from 'teleterm/services/config'; +import { useKeyboardShortcutFormatters } from 'teleterm/ui/services/keyboardShortcuts'; +import { ListItem } from 'teleterm/ui/components/ListItem'; +import { useNewTabOpener } from 'teleterm/ui/TabHost'; + +type MenuItem = { + title: string; + isVisible: boolean; + Icon: React.ComponentType<{ fontSize: number }>; + onNavigate: () => void; + prependSeparator?: boolean; + keyboardShortcutAction?: KeyboardShortcutAction; +}; + +function useMenuItems(): MenuItem[] { + const ctx = useAppContext(); + const { + workspacesService, + mainProcessClient, + configService, + notificationsService, + } = ctx; + workspacesService.useState(); + ctx.clustersService.useState(); + const documentsService = + workspacesService.getActiveWorkspaceDocumentService(); + const activeRootCluster = getActiveRootCluster(ctx); + const { openTerminalTab } = useNewTabOpener({ + documentsService, + localClusterUri: workspacesService.getActiveWorkspace()?.localClusterUri, + }); + + const areAccessRequestsSupported = + !!activeRootCluster?.features?.advancedAccessWorkflows; + + const { platform } = mainProcessClient.getRuntimeSettings(); + const isDarwin = platform === 'darwin'; + const isSearchBarEnabled = configService.get('feature.searchBar').value; + + const menuItems: MenuItem[] = [ + { + title: 'Open config file', + isVisible: true, + Icon: icons.Config, + onNavigate: async () => { + const path = await mainProcessClient.openConfigFile(); + notificationsService.notifyInfo(`Opened the config file at ${path}.`); + }, + }, + { + title: 'Open new terminal', + isVisible: isSearchBarEnabled, + Icon: icons.Terminal, + keyboardShortcutAction: 'newTerminalTab', + onNavigate: openTerminalTab, + }, + { + title: 'Install tsh in PATH', + isVisible: isSearchBarEnabled && isDarwin, + Icon: icons.Link, + onNavigate: () => { + ctx.commandLauncher.executeCommand('tsh-install', undefined); + }, + }, + { + title: 'Remove tsh from PATH', + isVisible: isSearchBarEnabled && isDarwin, + Icon: icons.Unlink, + onNavigate: () => { + ctx.commandLauncher.executeCommand('tsh-uninstall', undefined); + }, + }, + { + title: 'New access request', + isVisible: areAccessRequestsSupported, + prependSeparator: true, + Icon: icons.Add, + onNavigate: () => { + const doc = documentsService.createAccessRequestDocument({ + clusterUri: activeRootCluster.uri, + state: 'creating', + title: 'New Access Request', + }); + documentsService.add(doc); + documentsService.open(doc.uri); + }, + }, + { + title: 'Review access requests', + isVisible: areAccessRequestsSupported, + Icon: icons.OpenBox, + onNavigate: () => { + const doc = documentsService.createAccessRequestDocument({ + clusterUri: activeRootCluster.uri, + state: 'browsing', + }); + documentsService.add(doc); + documentsService.open(doc.uri); + }, + }, + ]; + + return menuItems.filter(i => i.isVisible); +} + +function getActiveRootCluster(ctx: IAppContext): Cluster | undefined { + const clusterUri = ctx.workspacesService.getRootClusterUri(); + if (!clusterUri) { + return; + } + return ctx.clustersService.findCluster(clusterUri); +} + +export function MoreOptions() { + const [isPopoverOpened, setIsPopoverOpened] = useState(false); + const selectorRef = useRef(); + + const items = useMenuItems().map(item => { + return ( + + {item.prependSeparator && } + setIsPopoverOpened(false)} /> + + ); + }); + + return ( + <> + setIsPopoverOpened(true)} + > + + + setIsPopoverOpened(false)} + popoverCss={() => `max-width: min(560px, 90%)`} + > + {items} + + + ); +} + +const Menu = styled.menu` + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + min-width: 280px; + background: ${props => props.theme.colors.levels.surface}; +`; + +const Separator = styled.div` + background: ${props => props.theme.colors.levels.elevated}; + height: 1px; +`; + +function MenuItem({ + item, + closeMenu, +}: { + item: MenuItem; + closeMenu: () => void; +}) { + const { getAccelerator } = useKeyboardShortcutFormatters(); + const handleClick = () => { + item.onNavigate(); + closeMenu(); + }; + + return ( + + + + {item.title} + + {item.keyboardShortcutAction && ( + props.theme.space[1]}px + ${props => props.theme.space[1]}px; + `} + bg="primary.main" + > + {getAccelerator(item.keyboardShortcutAction)} + + )} + + + ); +} + +const StyledListItem = styled(ListItem)` + height: 38px; + gap: ${props => props.theme.space[3]}px; + padding: 0 ${props => props.theme.space[3]}px; + border-radius: 0; +`; diff --git a/web/packages/teleterm/src/ui/TopBar/NavigationMenu/NavigationItem.tsx b/web/packages/teleterm/src/ui/TopBar/NavigationMenu/NavigationItem.tsx deleted file mode 100644 index 2674572f67e63..0000000000000 --- a/web/packages/teleterm/src/ui/TopBar/NavigationMenu/NavigationItem.tsx +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright 2023 Gravitational, Inc - * - * 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 React from 'react'; -import styled from 'styled-components'; - -import { Text } from 'design'; - -import { ListItem } from 'teleterm/ui/components/ListItem'; - -export function NavigationItem({ item, closeMenu }: NavigationItemProps) { - const handleClick = () => { - item.onNavigate(); - closeMenu(); - }; - - return ( - - - {item.title} - - ); -} - -const StyledListItem = styled(ListItem)` - height: 38px; - gap: 12px; - padding: 0 12px; - border-radius: 0; -`; - -type NavigationItemProps = { - item: { - title: string; - Icon: React.ComponentType<{ fontSize: number }>; - onNavigate: () => void; - }; - closeMenu: () => void; -}; diff --git a/web/packages/teleterm/src/ui/TopBar/NavigationMenu/NavigationMenu.tsx b/web/packages/teleterm/src/ui/TopBar/NavigationMenu/NavigationMenu.tsx deleted file mode 100644 index f6cb70419f47f..0000000000000 --- a/web/packages/teleterm/src/ui/TopBar/NavigationMenu/NavigationMenu.tsx +++ /dev/null @@ -1,154 +0,0 @@ -/** - * Copyright 2023 Gravitational, Inc - * - * 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 React, { useState, useRef } from 'react'; -import styled from 'styled-components'; - -import Popover from 'design/Popover'; -import { MoreVert, OpenBox, Add, Config } from 'design/Icon'; - -import { useAppContext } from 'teleterm/ui/appContextProvider'; -import { TopBarButton } from 'teleterm/ui/TopBar/TopBarButton'; -import { IAppContext } from 'teleterm/ui/types'; -import { Cluster } from 'teleterm/services/tshd/types'; - -import { NavigationItem } from './NavigationItem'; - -function useNavigationItems(): ( - | { - title: string; - Icon: React.ComponentType<{ fontSize: number }>; - onNavigate: () => void; - } - | 'separator' -)[] { - const ctx = useAppContext(); - ctx.workspacesService.useState(); - ctx.clustersService.useState(); - - const documentsService = - ctx.workspacesService.getActiveWorkspaceDocumentService(); - const activeRootCluster = getActiveRootCluster(ctx); - const areAccessRequestsSupported = - !!activeRootCluster?.features?.advancedAccessWorkflows; - - return [ - { - title: 'Open Config File', - Icon: Config, - onNavigate: async () => { - const path = await ctx.mainProcessClient.openConfigFile(); - ctx.notificationsService.notifyInfo( - `Opened the config file at ${path}.` - ); - }, - }, - ...(areAccessRequestsSupported - ? [ - 'separator' as const, - { - title: 'New Access Request', - Icon: Add, - onNavigate: () => { - const doc = documentsService.createAccessRequestDocument({ - clusterUri: activeRootCluster.uri, - state: 'creating', - title: 'New Access Request', - }); - documentsService.add(doc); - documentsService.open(doc.uri); - }, - }, - { - title: 'Review Access Requests', - Icon: OpenBox, - onNavigate: () => { - const doc = documentsService.createAccessRequestDocument({ - clusterUri: activeRootCluster.uri, - state: 'browsing', - }); - documentsService.add(doc); - documentsService.open(doc.uri); - }, - }, - ] - : []), - ].filter(Boolean); -} - -function getActiveRootCluster(ctx: IAppContext): Cluster | undefined { - const clusterUri = ctx.workspacesService.getRootClusterUri(); - if (!clusterUri) { - return; - } - return ctx.clustersService.findCluster(clusterUri); -} - -export function NavigationMenu() { - const [isPopoverOpened, setIsPopoverOpened] = useState(false); - const selectorRef = useRef(); - - const items = useNavigationItems().map((item, index) => { - if (item === 'separator') { - return ; - } - return ( - setIsPopoverOpened(false)} - /> - ); - }); - - return ( - <> - setIsPopoverOpened(true)} - > - - - setIsPopoverOpened(false)} - popoverCss={() => `max-width: min(560px, 90%)`} - > - {items} - - - ); -} - -const Menu = styled.menu` - list-style: none; - padding: 0; - margin: 0; - display: flex; - flex-direction: column; - min-width: 280px; - background: ${props => props.theme.colors.levels.surface}; -`; - -const Separator = styled.div` - background: ${props => props.theme.colors.levels.elevated}; - height: 1px; -`; diff --git a/web/packages/teleterm/src/ui/TopBar/TopBar.tsx b/web/packages/teleterm/src/ui/TopBar/TopBar.tsx index 383ada9fa3a5d..92ddc1c671ca8 100644 --- a/web/packages/teleterm/src/ui/TopBar/TopBar.tsx +++ b/web/packages/teleterm/src/ui/TopBar/TopBar.tsx @@ -15,17 +15,22 @@ */ import React from 'react'; - import styled from 'styled-components'; +import { Flex } from 'design'; import QuickInput from '../QuickInput'; +import { SearchBar } from '../Search'; +import { useAppContext } from '../appContextProvider'; import { Connections } from './Connections'; import { Clusters } from './Clusters'; import { Identity } from './Identity'; -import { NavigationMenu } from './NavigationMenu'; +import { MoreOptions } from './MoreOptions'; export function TopBar() { + const { configService } = useAppContext(); + const isSearchBarEnabled = configService.get('feature.searchBar').value; + return ( @@ -33,35 +38,30 @@ export function TopBar() { - + {isSearchBarEnabled ? : } - + ); } -const Grid = styled.div` +const Grid = styled(Flex).attrs({ gap: 3, py: 2, px: 3 })` background: ${props => props.theme.colors.levels.surfaceSecondary}; - display: grid; - grid-template-columns: 1fr minmax(0, 700px) 1fr; width: 100%; - padding: 8px 16px; height: 56px; - box-sizing: border-box; align-items: center; + justify-content: space-between; `; -const CentralContainer = styled.div` - display: grid; - column-gap: 12px; - margin: auto 12px; - grid-auto-flow: column; - grid-auto-columns: 2fr 5fr; // 1fr for a single child, 2fr 5fr for two children +const CentralContainer = styled(Flex).attrs({ gap: 3 })` + flex: 1; align-items: center; + justify-content: center; height: 100%; + max-width: calc(${props => props.theme.space[10]}px * 9); `; const JustifyLeft = styled.div` diff --git a/web/packages/teleterm/src/ui/appContext.ts b/web/packages/teleterm/src/ui/appContext.ts index cffee7d02b573..ea1412bd06147 100644 --- a/web/packages/teleterm/src/ui/appContext.ts +++ b/web/packages/teleterm/src/ui/appContext.ts @@ -37,6 +37,7 @@ import { ReloginService } from 'teleterm/services/relogin'; import { TshdNotificationsService } from 'teleterm/services/tshdNotifications'; import { UsageService } from 'teleterm/ui/services/usage'; import { ResourcesService } from 'teleterm/ui/services/resources'; +import { ConfigService } from 'teleterm/services/config'; import { IAppContext } from 'teleterm/ui/types'; import { CommandLauncher } from './commandLauncher'; @@ -72,15 +73,17 @@ export default class AppContext implements IAppContext { reloginService: ReloginService; tshdNotificationsService: TshdNotificationsService; usageService: UsageService; + configService: ConfigService; constructor(config: ElectronGlobals) { const { tshClient, ptyServiceClient, mainProcessClient } = config; this.subscribeToTshdEvent = config.subscribeToTshdEvent; this.mainProcessClient = mainProcessClient; this.notificationsService = new NotificationsService(); + this.configService = this.mainProcessClient.configService; this.usageService = new UsageService( tshClient, - this.mainProcessClient.configService, + this.configService, this.notificationsService, clusterUri => this.clustersService.findCluster(clusterUri), mainProcessClient.getRuntimeSettings() @@ -110,7 +113,7 @@ export default class AppContext implements IAppContext { this.keyboardShortcutsService = new KeyboardShortcutsService( this.mainProcessClient.getRuntimeSettings().platform, - this.mainProcessClient.configService + this.configService ); this.commandLauncher = new CommandLauncher(this); diff --git a/web/packages/teleterm/src/ui/commandLauncher.ts b/web/packages/teleterm/src/ui/commandLauncher.ts index 301d4502bdf4f..d841caa56289e 100644 --- a/web/packages/teleterm/src/ui/commandLauncher.ts +++ b/web/packages/teleterm/src/ui/commandLauncher.ts @@ -18,25 +18,34 @@ import { IAppContext } from 'teleterm/ui/types'; import { ClusterUri, KubeUri, RootClusterUri, routing } from 'teleterm/ui/uri'; import { TrackedKubeConnection } from 'teleterm/ui/services/connectionTracker'; import { Platform } from 'teleterm/mainProcess/types'; +import { DocumentOrigin } from 'teleterm/ui/services/workspacesService'; const commands = { // For handling "tsh ssh" executed from the command bar. 'tsh-ssh': { displayName: '', description: '', - run( + async run( ctx: IAppContext, - args: { loginHost: string; localClusterUri: ClusterUri } + args: { + loginHost: string; + localClusterUri: ClusterUri; + origin: DocumentOrigin; + } ) { - const { loginHost, localClusterUri } = args; + const { loginHost, localClusterUri, origin } = args; const rootClusterUri = routing.ensureRootClusterUri(localClusterUri); const documentsService = ctx.workspacesService.getWorkspaceDocumentService(rootClusterUri); const doc = documentsService.createTshNodeDocumentFromLoginHost( localClusterUri, - loginHost + loginHost, + { origin } ); + + await ctx.workspacesService.setActiveWorkspace(rootClusterUri); + documentsService.add(doc); documentsService.setLocation(doc.uri); }, @@ -89,15 +98,23 @@ const commands = { 'kube-connect': { displayName: '', description: '', - run(ctx: IAppContext, args: { kubeUri: KubeUri }) { + async run( + ctx: IAppContext, + args: { kubeUri: KubeUri; origin: DocumentOrigin } + ) { + const rootClusterUri = routing.ensureRootClusterUri(args.kubeUri); const documentsService = - ctx.workspacesService.getActiveWorkspaceDocumentService(); + ctx.workspacesService.getWorkspaceDocumentService(rootClusterUri); const kubeDoc = documentsService.createTshKubeDocument({ kubeUri: args.kubeUri, + origin: args.origin, }); const connection = ctx.connectionTracker.findConnectionByDocument( kubeDoc ) as TrackedKubeConnection; + + await ctx.workspacesService.setActiveWorkspace(rootClusterUri); + documentsService.add({ ...kubeDoc, kubeConfigRelativePath: @@ -194,6 +211,7 @@ export class CommandLauncher { executeCommand(name: T, args: CommandArgs) { commands[name].run(this.appContext, args as any); + return undefined; } getAutocompleteCommands() { diff --git a/web/packages/teleterm/src/ui/services/clusters/clustersService.ts b/web/packages/teleterm/src/ui/services/clusters/clustersService.ts index e7760a6db96fd..24bcddc01bb88 100644 --- a/web/packages/teleterm/src/ui/services/clusters/clustersService.ts +++ b/web/packages/teleterm/src/ui/services/clusters/clustersService.ts @@ -29,7 +29,6 @@ import { CreateAccessRequestParams, GetRequestableRolesParams, ReviewAccessRequestParams, - ServerSideParams, } from 'teleterm/services/tshd/types'; import { MainProcessClient } from 'teleterm/mainProcess/types'; import { UsageService } from 'teleterm/ui/services/usage'; @@ -196,6 +195,8 @@ export class ClustersService extends ImmutableStore async getRequestableRoles(params: GetRequestableRolesParams) { const cluster = this.state.clusters.get(params.rootClusterUri); + // TODO(ravicious): Remove check for cluster.connected. This check should be done earlier in the + // UI rather than be repeated in each ClustersService method. if (!cluster.connected) { return; } @@ -205,6 +206,7 @@ export class ClustersService extends ImmutableStore getAssumedRequests(rootClusterUri: uri.RootClusterUri) { const cluster = this.state.clusters.get(rootClusterUri); + // TODO(ravicious): Remove check for cluster.connected. See the comment in getRequestableRoles. if (!cluster?.connected) { return {}; } @@ -218,6 +220,7 @@ export class ClustersService extends ImmutableStore async getAccessRequests(rootClusterUri: uri.RootClusterUri) { const cluster = this.state.clusters.get(rootClusterUri); + // TODO(ravicious): Remove check for cluster.connected. See the comment in getRequestableRoles. if (!cluster.connected) { return; } @@ -230,6 +233,7 @@ export class ClustersService extends ImmutableStore requestId: string ) { const cluster = this.state.clusters.get(rootClusterUri); + // TODO(ravicious): Remove check for cluster.connected. See the comment in getRequestableRoles. if (!cluster.connected) { return; } @@ -242,6 +246,7 @@ export class ClustersService extends ImmutableStore dropIds: string[] ) { const cluster = this.state.clusters.get(rootClusterUri); + // TODO(ravicious): Remove check for cluster.connected. See the comment in getRequestableRoles. if (!cluster.connected) { return; } @@ -255,6 +260,7 @@ export class ClustersService extends ImmutableStore requestId: string ) { const cluster = this.state.clusters.get(rootClusterUri); + // TODO(ravicious): Remove check for cluster.connected. See the comment in getRequestableRoles. if (!cluster.connected) { return; } @@ -267,6 +273,7 @@ export class ClustersService extends ImmutableStore params: ReviewAccessRequestParams ) { const cluster = this.state.clusters.get(rootClusterUri); + // TODO(ravicious): Remove check for cluster.connected. See the comment in getRequestableRoles. if (!cluster.connected) { return; } @@ -281,6 +288,7 @@ export class ClustersService extends ImmutableStore async createAccessRequest(params: CreateAccessRequestParams) { const cluster = this.state.clusters.get(params.rootClusterUri); + // TODO(ravicious): Remove check for cluster.connected. See the comment in getRequestableRoles. if (!cluster.connected) { return; } @@ -320,7 +328,6 @@ export class ClustersService extends ImmutableStore async createGateway(params: tsh.CreateGatewayParams) { const gateway = await this.client.createGateway(params); - this.usageService.captureProtocolUse(params.targetUri, 'db'); this.setState(draft => { draft.gateways.set(gateway.uri, gateway); }); @@ -426,16 +433,6 @@ export class ClustersService extends ImmutableStore return this.getClusters().filter(c => !c.leaf); } - // TODO(ravicious): Use ResourcesService instead. - async fetchKubes(params: ServerSideParams) { - return await this.client.getKubes(params); - } - - // TODO(ravicious): Move to ResourceService. - async getDbUsers(dbUri: uri.DatabaseUri): Promise { - return await this.client.listDatabaseUsers(dbUri); - } - async removeClusterKubeConfigs(clusterUri: string): Promise { const { params: { rootClusterId }, diff --git a/web/packages/teleterm/src/ui/services/connectionTracker/connectionTrackerService.test.ts b/web/packages/teleterm/src/ui/services/connectionTracker/connectionTrackerService.test.ts index 135ae9d2d0748..48b4daa1de545 100644 --- a/web/packages/teleterm/src/ui/services/connectionTracker/connectionTrackerService.test.ts +++ b/web/packages/teleterm/src/ui/services/connectionTracker/connectionTrackerService.test.ts @@ -105,6 +105,7 @@ it('updates the port of a gateway connection when the underlying doc gets update targetName: 'test', targetSubresourceName: 'pg', port: '12345', + origin: 'resource_table', }; const { connectionTrackerService, workspacesService } = @@ -142,6 +143,7 @@ it('creates a connection for doc.terminal_tsh_node docs with serverUri', () => { rootClusterId: 'localhost', leafClusterId: undefined, login: 'user', + origin: 'resource_table', }; const { connectionTrackerService } = getTestSetupWithMockedDocuments([ @@ -170,6 +172,7 @@ it('ignores doc.terminal_tsh_node docs with no serverUri', () => { loginHost: 'user@foo', rootClusterId: 'test', leafClusterId: undefined, + origin: 'resource_table', }; const { connectionTrackerService } = getTestSetupWithMockedDocuments([ diff --git a/web/packages/teleterm/src/ui/services/connectionTracker/connectionTrackerService.ts b/web/packages/teleterm/src/ui/services/connectionTracker/connectionTrackerService.ts index 358cb20d79cc3..3343447d78947 100644 --- a/web/packages/teleterm/src/ui/services/connectionTracker/connectionTrackerService.ts +++ b/web/packages/teleterm/src/ui/services/connectionTracker/connectionTrackerService.ts @@ -19,6 +19,7 @@ import { useStore } from 'shared/libs/stores'; import { ClustersService } from 'teleterm/ui/services/clusters'; import { Document, + DocumentOrigin, isDocumentTshNodeWithLoginHost, WorkspacesService, } from 'teleterm/ui/services/workspacesService'; @@ -81,7 +82,10 @@ export class ConnectionTrackerService extends ImmutableStore { + async activateItem( + id: string, + params: { origin: DocumentOrigin } + ): Promise { const connection = this.state.connections.find(c => c.id === id); const { rootClusterUri, activate } = this._trackedConnectionOperationsFactory.create(connection); @@ -89,7 +93,7 @@ export class ConnectionTrackerService extends ImmutableStore { + activate: params => { let srvDoc = documentsService .getDocuments() .find(getServerDocumentByConnection(connection)); if (!srvDoc) { - srvDoc = documentsService.createTshNodeDocument(connection.serverUri); + srvDoc = documentsService.createTshNodeDocument( + connection.serverUri, + params + ); srvDoc.status = 'connecting'; srvDoc.login = connection.login; srvDoc.title = connection.title; @@ -108,7 +114,7 @@ export class TrackedConnectionOperationsFactory { return { rootClusterUri, leafClusterUri, - activate: () => { + activate: params => { let gwDoc = documentsService .getDocuments() .find(getGatewayDocumentByConnection(connection)); @@ -122,6 +128,7 @@ export class TrackedConnectionOperationsFactory { title: connection.title, gatewayUri: connection.gatewayUri, port: connection.port, + origin: params.origin, }); documentsService.add(gwDoc); @@ -161,7 +168,7 @@ export class TrackedConnectionOperationsFactory { return { rootClusterUri, leafClusterUri, - activate: () => { + activate: params => { let kubeConn = documentsService .getDocuments() .find(getKubeDocumentByConnection(connection)); @@ -170,6 +177,7 @@ export class TrackedConnectionOperationsFactory { kubeConn = documentsService.createTshKubeDocument({ kubeUri: connection.kubeUri, kubeConfigRelativePath: connection.kubeConfigRelativePath, + origin: params.origin, }); documentsService.add(kubeConn); @@ -215,7 +223,7 @@ interface TrackedConnectionOperations { rootClusterUri: RootClusterUri; leafClusterUri: LeafClusterUri; - activate(): void; + activate(params: { origin: DocumentOrigin }): void; disconnect(): Promise; diff --git a/web/packages/teleterm/src/ui/services/keyboardShortcuts/keyboardShortcutsService.ts b/web/packages/teleterm/src/ui/services/keyboardShortcuts/keyboardShortcutsService.ts index 7eb300c67a961..9bc53945f83fd 100644 --- a/web/packages/teleterm/src/ui/services/keyboardShortcuts/keyboardShortcutsService.ts +++ b/web/packages/teleterm/src/ui/services/keyboardShortcuts/keyboardShortcutsService.ts @@ -57,6 +57,7 @@ export class KeyboardShortcutsService { previousTab: this.configService.get('keymap.previousTab').value, nextTab: this.configService.get('keymap.nextTab').value, newTab: this.configService.get('keymap.newTab').value, + newTerminalTab: this.configService.get('keymap.newTerminalTab').value, openCommandBar: this.configService.get('keymap.openCommandBar').value, openConnections: this.configService.get('keymap.openConnections').value, openClusters: this.configService.get('keymap.openClusters').value, diff --git a/web/packages/teleterm/src/ui/services/quickInput/suggesters.ts b/web/packages/teleterm/src/ui/services/quickInput/suggesters.ts index 4c22e93c01922..43ab423bc89fb 100644 --- a/web/packages/teleterm/src/ui/services/quickInput/suggesters.ts +++ b/web/packages/teleterm/src/ui/services/quickInput/suggesters.ts @@ -83,6 +83,7 @@ export class QuickServerSuggester clusterUri: localClusterUri, search: input, limit, + sort: { fieldName: 'hostname', dir: 'ASC' }, }); return servers.agentsList.map(server => ({ @@ -111,6 +112,7 @@ export class QuickDatabaseSuggester clusterUri: localClusterUri, search: input, limit, + sort: { fieldName: 'name', dir: 'ASC' }, }); return databases.agentsList.map(database => ({ diff --git a/web/packages/teleterm/src/ui/services/resources/resourcesService.test.ts b/web/packages/teleterm/src/ui/services/resources/resourcesService.test.ts index ab27d190fcb14..64bda031c21d4 100644 --- a/web/packages/teleterm/src/ui/services/resources/resourcesService.test.ts +++ b/web/packages/teleterm/src/ui/services/resources/resourcesService.test.ts @@ -86,6 +86,7 @@ test.each(getServerByHostnameTests)( clusterUri: '/clusters/bar', query: 'name == "foo"', limit: 2, + sort: null, }); } ); diff --git a/web/packages/teleterm/src/ui/services/resources/resourcesService.ts b/web/packages/teleterm/src/ui/services/resources/resourcesService.ts index c2ac329b8a611..2e8a62ffecdeb 100644 --- a/web/packages/teleterm/src/ui/services/resources/resourcesService.ts +++ b/web/packages/teleterm/src/ui/services/resources/resourcesService.ts @@ -14,16 +14,20 @@ * limitations under the License. */ +import type { ResourceTypeSearchFilter } from 'teleterm/ui/Search/searchResult'; + import type * as types from 'teleterm/services/tshd/types'; import type * as uri from 'teleterm/ui/uri'; export class ResourcesService { constructor(private tshClient: types.TshClient) {} - fetchServers(params: types.ServerSideParams) { + fetchServers(params: types.GetResourcesParams) { return this.tshClient.getServers(params); } + // TODO(ravicious): Refactor it to use logic similar to that in the Web UI. + // https://github.com/gravitational/teleport/blob/2a2b08dbfdaf71706a5af3812d3a7ec843d099b4/lib/web/apiserver.go#L2471 async getServerByHostname( clusterUri: uri.ClusterUri, hostname: string @@ -33,6 +37,7 @@ export class ResourcesService { clusterUri, query, limit: 2, + sort: null, }); if (servers.length > 1) { @@ -42,13 +47,67 @@ export class ResourcesService { return servers[0]; } - fetchDatabases(params: types.ServerSideParams) { + fetchDatabases(params: types.GetResourcesParams) { return this.tshClient.getDatabases(params); } - fetchKubes(params: types.ServerSideParams) { + fetchKubes(params: types.GetResourcesParams) { return this.tshClient.getKubes(params); } + + async getDbUsers(dbUri: uri.DatabaseUri): Promise { + return await this.tshClient.listDatabaseUsers(dbUri); + } + + /** + * searchResources searches for the given list of space-separated keywords across all resource + * types on the given cluster. + * + * It does so by issuing a separate request for each resource type. It fails if any of those + * requests fail. + * + * The results need to be wrapped in SearchResult because if we returned raw types (Server, + * Database, Kube) then there would be no easy way to differentiate between them on type level. + */ + async searchResources( + clusterUri: uri.ClusterUri, + search: string, + searchFilter: ResourceTypeSearchFilter | undefined + ): Promise { + const params = { search, clusterUri, sort: null, limit: 100 }; + + const getServers = () => + this.fetchServers(params).then(res => + res.agentsList.map(resource => ({ + kind: 'server' as const, + resource, + })) + ); + const getDatabases = () => + this.fetchDatabases(params).then(res => + res.agentsList.map(resource => ({ + kind: 'database' as const, + resource, + })) + ); + const getKubes = () => + this.fetchKubes(params).then(res => + res.agentsList.map(resource => ({ + kind: 'kube' as const, + resource, + })) + ); + + const promises = searchFilter + ? [ + searchFilter.resourceType === 'servers' && getServers(), + searchFilter.resourceType === 'databases' && getDatabases(), + searchFilter.resourceType === 'kubes' && getKubes(), + ].filter(Boolean) + : [getServers(), getDatabases(), getKubes()]; + + return (await Promise.all(promises)).flat(); + } } export class AmbiguousHostnameError extends Error { @@ -57,3 +116,24 @@ export class AmbiguousHostnameError extends Error { this.name = 'AmbiguousHostname'; } } + +export type SearchResultServer = { kind: 'server'; resource: types.Server }; +export type SearchResultDatabase = { + kind: 'database'; + resource: types.Database; +}; +export type SearchResultKube = { kind: 'kube'; resource: types.Kube }; + +export type SearchResult = + | SearchResultServer + | SearchResultDatabase + | SearchResultKube; + +export type SearchResultResource = + Kind extends 'server' + ? SearchResultServer['resource'] + : Kind extends 'database' + ? SearchResultDatabase['resource'] + : Kind extends 'kube' + ? SearchResultKube['resource'] + : never; diff --git a/web/packages/teleterm/src/ui/services/usage/usageService.ts b/web/packages/teleterm/src/ui/services/usage/usageService.ts index 03dc0a863482d..121f02c4730e3 100644 --- a/web/packages/teleterm/src/ui/services/usage/usageService.ts +++ b/web/packages/teleterm/src/ui/services/usage/usageService.ts @@ -25,6 +25,7 @@ import { ConfigService } from 'teleterm/services/config'; import Logger from 'teleterm/logger'; import { staticConfig } from 'teleterm/staticConfig'; import { NotificationsService } from 'teleterm/ui/services/notifications'; +import { DocumentOrigin } from 'teleterm/ui/services/workspacesService'; type PrehogEventReq = Omit< ReportUsageEventRequest['prehogReq'], @@ -70,7 +71,8 @@ export class UsageService { captureProtocolUse( uri: ClusterOrResourceUri, - protocol: 'ssh' | 'kube' | 'db' + protocol: 'ssh' | 'kube' | 'db', + origin: DocumentOrigin ): void { const clusterProperties = this.getClusterProperties(uri); if (!clusterProperties) { @@ -84,6 +86,7 @@ export class UsageService { clusterName: clusterProperties.clusterName, userName: clusterProperties.userName, protocol, + origin, }, }); } diff --git a/web/packages/teleterm/src/ui/services/workspacesService/documentsService/documentsService.test.ts b/web/packages/teleterm/src/ui/services/workspacesService/documentsService/documentsService.test.ts index 8b9bd2c2a6ac9..f9d7a0f1030a7 100644 --- a/web/packages/teleterm/src/ui/services/workspacesService/documentsService/documentsService.test.ts +++ b/web/packages/teleterm/src/ui/services/workspacesService/documentsService/documentsService.test.ts @@ -77,6 +77,7 @@ describe('document should be added', () => { targetUri: '/clusters/bar/dbs/quux', targetName: 'quux', targetUser: 'foo', + origin: 'resource_table', }; test('at the specific position', () => { @@ -131,6 +132,7 @@ test('only TSH node documents should be returned', () => { status: 'connecting', rootClusterId: '', leafClusterId: undefined, + origin: 'resource_table', }; service.add(tshNodeDocument); @@ -150,6 +152,7 @@ test('only gateway documents should be returned', () => { targetUri: '/clusters/bar/dbs/quux', targetName: 'quux', targetUser: 'foo', + origin: 'resource_table', }; service.add(gatewayDocument); diff --git a/web/packages/teleterm/src/ui/services/workspacesService/documentsService/documentsService.ts b/web/packages/teleterm/src/ui/services/workspacesService/documentsService/documentsService.ts index 2831c33558327..ab809037df5f3 100644 --- a/web/packages/teleterm/src/ui/services/workspacesService/documentsService/documentsService.ts +++ b/web/packages/teleterm/src/ui/services/workspacesService/documentsService/documentsService.ts @@ -33,6 +33,7 @@ import { DocumentAccessRequests, DocumentCluster, DocumentGateway, + DocumentOrigin, DocumentTshKube, DocumentTshNode, DocumentTshNodeWithLoginHost, @@ -104,23 +105,28 @@ export class DocumentsService { options.kubeConfigRelativePath || `${params.rootClusterId}/${params.kubeId}-${unique(5)}`, title: params.kubeId, + origin: options.origin, }; } - createTshNodeDocument(serverUri: ServerUri): DocumentTshNodeWithServerId { - const { params } = routing.parseServerUri(serverUri); + createTshNodeDocument( + serverUri: ServerUri, + params: { origin: DocumentOrigin } + ): DocumentTshNodeWithServerId { + const { params: routingParams } = routing.parseServerUri(serverUri); const uri = routing.getDocUri({ docId: unique() }); return { uri, kind: 'doc.terminal_tsh_node', status: 'connecting', - rootClusterId: params.rootClusterId, - leafClusterId: params.leafClusterId, - serverId: params.serverId, + rootClusterId: routingParams.rootClusterId, + leafClusterId: routingParams.leafClusterId, + serverId: routingParams.serverId, serverUri, title: '', login: '', + origin: params.origin, }; } @@ -132,12 +138,15 @@ export class DocumentsService { * the command will succeed only if the given cluster has only a single server with the hostname * matching `host`. * @param loginHost - the "user@host" pair. + * @param params - additional parameters. + * @param params.origin - where the document was opened from. */ createTshNodeDocumentFromLoginHost( clusterUri: ClusterUri, - loginHost: string + loginHost: string, + params: { origin: DocumentOrigin } ): DocumentTshNodeWithLoginHost { - const { params } = routing.parseClusterUri(clusterUri); + const { params: routingParams } = routing.parseClusterUri(clusterUri); const uri = routing.getDocUri({ docId: unique() }); return { @@ -145,9 +154,10 @@ export class DocumentsService { kind: 'doc.terminal_tsh_node', title: loginHost, status: 'connecting', - rootClusterId: params.rootClusterId, - leafClusterId: params.leafClusterId, + rootClusterId: routingParams.rootClusterId, + leafClusterId: routingParams.leafClusterId, loginHost, + origin: params.origin, }; } @@ -162,6 +172,7 @@ export class DocumentsService { targetSubresourceName, port, gatewayUri, + origin, } = opts; const uri = routing.getDocUri({ docId: unique() }); const title = `${targetUser}@${targetName}`; @@ -176,6 +187,7 @@ export class DocumentsService { gatewayUri, title, port, + origin, }; } diff --git a/web/packages/teleterm/src/ui/services/workspacesService/documentsService/types.test.ts b/web/packages/teleterm/src/ui/services/workspacesService/documentsService/types.test.ts index 82e61d7cf390d..971082a365777 100644 --- a/web/packages/teleterm/src/ui/services/workspacesService/documentsService/types.test.ts +++ b/web/packages/teleterm/src/ui/services/workspacesService/documentsService/types.test.ts @@ -31,6 +31,7 @@ const docWithServerId: DocumentTshNodeWithServerId = { rootClusterId: 'test', leafClusterId: undefined, login: 'user', + origin: 'resource_table', }; // eslint-disable-next-line @typescript-eslint/no-unused-vars const { serverId, serverUri, login, ...rest } = docWithServerId; diff --git a/web/packages/teleterm/src/ui/services/workspacesService/documentsService/types.ts b/web/packages/teleterm/src/ui/services/workspacesService/documentsService/types.ts index a7c7f625a1699..a28ed57ba62e3 100644 --- a/web/packages/teleterm/src/ui/services/workspacesService/documentsService/types.ts +++ b/web/packages/teleterm/src/ui/services/workspacesService/documentsService/types.ts @@ -25,6 +25,12 @@ export type Kind = | 'doc.terminal_tsh_node' | 'doc.terminal_tsh_kube'; +export type DocumentOrigin = + | 'resource_table' + | 'search_bar' + | 'connection_list' + | 'reopened_session'; + interface DocumentBase { uri: uri.DocumentUri; title: string; @@ -45,6 +51,7 @@ interface DocumentTshNodeBase extends DocumentBase { status: '' | 'connecting' | 'connected' | 'error'; rootClusterId: string; leafClusterId: string | undefined; + origin: DocumentOrigin; } export interface DocumentTshNodeWithServerId extends DocumentTshNodeBase { @@ -83,6 +90,7 @@ export interface DocumentTshKube extends DocumentBase { kubeConfigRelativePath: string; rootClusterId: string; leafClusterId?: string; + origin: DocumentOrigin; } export interface DocumentGateway extends DocumentBase { @@ -93,6 +101,7 @@ export interface DocumentGateway extends DocumentBase { targetName: string; targetSubresourceName?: string; port?: string; + origin: DocumentOrigin; } export interface DocumentCluster extends DocumentBase { @@ -151,6 +160,7 @@ export type CreateGatewayDocumentOpts = { targetSubresourceName?: string; title?: string; port?: string; + origin: DocumentOrigin; }; export type CreateClusterDocumentOpts = { @@ -160,6 +170,7 @@ export type CreateClusterDocumentOpts = { export type CreateTshKubeDocumentOptions = { kubeUri: uri.KubeUri; kubeConfigRelativePath?: string; + origin: DocumentOrigin; }; export type CreateAccessRequestDocumentOpts = { diff --git a/web/packages/teleterm/src/ui/services/workspacesService/workspacesService.ts b/web/packages/teleterm/src/ui/services/workspacesService/workspacesService.ts index 1d0262de35afa..e6d5b143aebad 100644 --- a/web/packages/teleterm/src/ui/services/workspacesService/workspacesService.ts +++ b/web/packages/teleterm/src/ui/services/workspacesService/workspacesService.ts @@ -316,6 +316,14 @@ export class WorkspacesService extends ImmutableStore { return { ...d, status: 'connecting', + origin: 'reopened_session', + }; + } + + if (d.kind === 'doc.gateway') { + return { + ...d, + origin: 'reopened_session', }; } return d; diff --git a/web/packages/teleterm/src/ui/types.ts b/web/packages/teleterm/src/ui/types.ts index a1ac23bc08a9f..f1bac8b6f9ab1 100644 --- a/web/packages/teleterm/src/ui/types.ts +++ b/web/packages/teleterm/src/ui/types.ts @@ -30,6 +30,7 @@ import { ResourcesService } from 'teleterm/ui/services/resources'; import { ReloginService } from 'teleterm/services/relogin'; import { TshdNotificationsService } from 'teleterm/services/tshdNotifications'; import { UsageService } from 'teleterm/ui/services/usage'; +import { ConfigService } from 'teleterm/services/config'; export interface IAppContext { clustersService: ClustersService; @@ -49,6 +50,7 @@ export interface IAppContext { reloginService: ReloginService; tshdNotificationsService: TshdNotificationsService; usageService: UsageService; + configService: ConfigService; init(): Promise; } diff --git a/web/packages/teleterm/src/ui/uri.ts b/web/packages/teleterm/src/ui/uri.ts index 1e9b4fe771b99..8411e49a5ce72 100644 --- a/web/packages/teleterm/src/ui/uri.ts +++ b/web/packages/teleterm/src/ui/uri.ts @@ -109,6 +109,13 @@ export const routing = { return matchPath(path, route); }, + /** + * parseClusterName should be used only when getting the cluster object from ClustersService is + * not possible. + * + * rootClusterId in the URI is not the name of the cluster but rather just the hostname of the + * proxy. These two might be different. + */ parseClusterName(clusterUri: string) { const parsed = routing.parseClusterUri(clusterUri); if (!parsed) { diff --git a/yarn.lock b/yarn.lock index 213054e3c80d1..553f920a3fe40 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8448,6 +8448,11 @@ headers-polyfill@^3.1.0: resolved "https://registry.yarnpkg.com/headers-polyfill/-/headers-polyfill-3.1.2.tgz#9a4dcb545c5b95d9569592ef7ec0708aab763fbe" integrity sha512-tWCK4biJ6hcLqTviLXVR9DTRfYGQMXEIUj3gwJ2rZ5wO/at3XtkI4g8mCvFdUF9l1KMBNCfmNAdnahm1cgavQA== +highlight-words-core@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/highlight-words-core/-/highlight-words-core-1.2.2.tgz#1eff6d7d9f0a22f155042a00791237791b1eeaaa" + integrity sha512-BXUKIkUuh6cmmxzi5OIbUJxrG8OAk2MqoL1DtO3Wo9D2faJg2ph5ntyuQeLqaHJmzER6H5tllCDA9ZnNe9BVGg== + highlight.js@^11.7.0: version "11.7.0" resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.7.0.tgz#3ff0165bc843f8c9bce1fd89e2fda9143d24b11e"