Infograb logo
액세스 요청 플러그인 만들기

Teleport 액세스 요청 기능을 사용하면 기본적으로 Teleport 사용자에게 권한이 적은 역할을 할당하고 일시적으로 권한을 상승시킬 수 있습니다. 검토자는 조직의 기존 커뮤니케이션 워크플로우(예: Slack, 이메일 및 PagerDuty)를 사용하여 액세스 요청을 허용하거나 거부할 수 있으며, 액세스 요청 플러그인을 통해 가능합니다.

Teleport의 API 클라이언트 라이브러리를 사용하여 조직의 고유한 워크플로우와 통합된 액세스 요청 플러그인을 구축할 수 있습니다.

이 가이드에서는 Google Sheets를 통한 액세스 요청 관리를 허용하는 플러그인을 작성하는 방법을 보여주는 여러 Teleport API 클라이언트 라이브러리를 탐색할 것입니다. 플러그인은 Google Sheets 스프레드시트에서 새로운 액세스 요청을 나열하고, 각 요청을 허용하거나 거부할 수 있는 링크를 제공합니다.

이 가이드에서 구축할 플러그인은 학습 도구로 의도되었습니다. 이를 프로덕션 Teleport 클러스터에 연결하지 마십시오. 대신 데모 클러스터를 사용하십시오.

필수 조건

  • 실행 중인 Teleport 클러스터. Teleport를 시작하려면 가입하여 무료 평가판을 이용해 보십시오.

  • tctl 관리자 도구 및 tsh 클라이언트 도구.

    tctltsh 다운로드에 대한 지침은 설치 를 방문하십시오.

  • Go 버전 1.22+이 설치된 작업 공간. Go 다운로드 페이지를 참조하십시오. 이 가이드를 완료하기 위해 Go에 익숙할 필요는 없지만, 자체 액세스 요청 플러그인을 구축하려면 Go 지식이 필요합니다.

데모 플러그인을 설정하려면 다음이 필요합니다. 이는 Google Sheets API에 인증이 필요합니다.

  • 서비스 계정을 생성할 수 있는 권한이 있는 Google Cloud 프로젝트.
  • Google Sheets 스프레드시트를 생성하는 데 사용할 Google 계정. 플러그인에 사용되는 서비스 계정에 스프레드시트를 수정할 수 있는 권한을 부여합니다.
Tip

데모 프로젝트를 설정할 계획이 없더라도 이 가이드를 따라 액세스 요청 플러그인을 개발하는 데 사용할 수 있는 라이브러리, 유형 및 함수를 확인할 수 있습니다.

데모는 최소한의 작동 예제이며, GitHub에서 gravitational/teleport 리포지토리에서 완전한 플러그인을 볼 수 있습니다.

1/5단계. Go 프로젝트 설정

최소 액세스 요청 플러그인의 소스 코드를 다운로드합니다:

git clone https://github.com/gravitational/teleport -b branch/v17
cd teleport/examples/access-plugin-minimal

이 가이드의 나머지 부분에서는 이 플러그인을 설정하는 방법과 플러그인이 Teleport의 API를 사용하여 액세스 요청을 특정 워크플로우와 통합하는 방법을 탐색할 것입니다.

2/5단계. Google Sheets API 설정

액세스 요청 플러그인은 일반적으로 두 개의 API와 통신합니다. Teleport Auth Service의 gRPC API에서 액세스 요청 이벤트를 수신하고, 이 데이터를 사용하여 선택한 메시징 또는 협업 도구의 API와 상호작용합니다.

이 섹션에서는 Google Sheets API를 활성화하고, 플러그인을 위한 Google Cloud 서비스 계정을 만든 후, 이 서비스 계정을 사용하여 Google Sheets에 플러그인을 인증합니다.

Google Sheets API 활성화

다음 Google Cloud 콘솔 URL을 방문하여 Google Sheets API를 활성화합니다:

https://console.cloud.google.com/apis/enableflow?apiid=sheets.googleapis.com

사용하려는 Google Cloud 프로젝트가 맞는지 확인합니다.

다음 > 활성화를 클릭합니다.

플러그인을 위한 Google Cloud 서비스 계정 생성하기

다음 Google Cloud 콘솔 URL을 방문하세요:

https://console.cloud.google.com/iam-admin/serviceaccounts

서비스 계정 만들기를 클릭하세요.

서비스 계정 이름에 "Teleport Google Sheets Plugin"을 입력하세요. Google Cloud가 서비스 계정 ID 필드를 자동으로 채워줍니다.

생성 및 계속을 클릭하세요. 서비스 계정에 역할을 부여하라는 메시지가 나타나면 다시 계속을 클릭하세요. 우리는 역할 없이 서비스 계정을 만들 것입니다. 서비스 계정에 사용자 액세스를 부여하는 단계는 건너뛰고 완료를 클릭하세요.

콘솔이 서비스 계정 보기로 이동합니다. 방금 생성한 서비스 계정의 이름을 클릭한 다음 탭을 클릭하세요. 키 추가를 클릭한 다음 새 키 만들기를 클릭하세요. 키 유형은 "JSON"으로 남겨두고 생성을 클릭하세요.

Go 프로젝트 디렉토리에 credentials.json 이라는 이름으로 Google Cloud 자격 증명 파일을 저장하세요.

당신의 플러그인은 Google Sheets에 인증하기 위해 이 JSON 파일을 사용할 것입니다.

Google Sheets 스프레드시트 생성하기

다음 URL을 방문하고 올바른 사용자로 인증되었는지 확인하세요:

https://sheets.new

스프레드시트에 이름을 지정하세요.

공유를 클릭하여 플러그인에게 스프레드시트에 대한 액세스를 부여하세요. 사람 및 그룹 추가 필드에 teleport-google-sheets-plugin@PROJECT_NAME.iam.gserviceaccount.com 을 입력하세요. 여기서 PROJECT_NAME 을 귀하의 프로젝트 이름으로 교체하세요. 서비스 계정이 "편집자" 권한을 갖도록 하세요. 공유를 클릭한 다음 경고 메시지가 나타나면 어쨌든 공유를 클릭하세요.

생성한 서비스 계정으로 Google Sheets에 인증함으로써, 플러그인은 스프레드시트를 수정할 수 있는 액세스를 갖게 됩니다.

다음이 귀하의 스프레드시트에 포함되어 있는지 확인하세요:

  • 시트가 하나만 있습니다.
  • 시트에는 다음 열이 포함되어 있습니다:
IDCreatedUserRolesStatusLink

Access Request 플러그인을 작성한 후, 스프레드시트에 데이터를 자동으로 채울 것입니다.

3/5 단계. Teleport RBAC 설정하기

이 섹션에서는 Access Requests를 생성하고 검토할 수 있게 하는 Teleport 역할을 설정하고, 플러그인이 Teleport에 인증할 수 있도록 자격 증명을 생성하는 또 다른 Teleport 역할을 설정합니다.

플러그인을 위한 사용자 및 역할 생성하기

Teleport의 접근 요청 플러그인은 접근 요청을 나열하고 읽을 수 있는 권한을 가진 사용자로서 Teleport 클러스터에 인증합니다. 이렇게 하면 플러그인이 Teleport Auth Service에서 접근 요청을 검색하여 리뷰어에게 제공합니다.

다음 내용을 access-plugin.yaml 이라는 파일에 추가하여 access-plugin 이라는 사용자와 역할을 정의하십시오:

kind: role
version: v5
metadata:
  name: access-plugin
spec:
  allow:
    rules:
      - resources: ["access_request"]
        verbs: ["list", "read"]
      - resources: ["access_plugin_data"]
        verbs: ["update"]
---
kind: user
metadata:
  name: access-plugin
spec:
  roles: ["access-plugin"]
version: v2

사용자와 역할을 생성하십시오:

tctl create -f access-plugin.yaml

모든 Teleport 사용자와 마찬가지로, Teleport Auth 서비스는 access-plugin 사용자를 짧은 수명의 TLS 자격 증명을 발급하여 인증합니다. 이 경우, access-plugin 역할과 사용자를 임시로 사용자화하여 자격 증명을 수동으로 요청해야 합니다.

자체 호스팅된 Teleport Enterprise 배포를 실행 중이며 Auth 서비스 호스트에서 tctl 을 사용하고 있는 경우, 이미 임시 사용자화 권한이 있을 것입니다.

사용자에게 access-plugin 에 대한 임시 사용자화 권한을 부여하려면, 다음 YAML 문서를 access-plugin-impersonator.yaml 이라는 파일에 붙여넣어 access-plugin-impersonator 라는 역할을 정의하십시오:

kind: role
version: v7
metadata:
  name: access-plugin-impersonator
spec:
  allow:
    impersonate:
      roles:
      - access-plugin
      users:
      - access-plugin

access-plugin-impersonator 역할을 생성하십시오:

tctl create -f access-plugin-impersonator.yaml

기계 ID를 가진 플러그인에 사용자 신원 파일을 제공하는 경우, 기계 ID 봇 사용자에게 access-plugin 역할을 부여하십시오. 그렇지 않으면, access-plugin 역할 및 사용자에 대한 자격 증명을 생성하려는 사용자에게 이 역할을 할당하십시오:

access-plugin-impersonator 역할을 Teleport 사용자에게 할당하려면, 인증 제공자에 맞는 적절한 명령어를 실행하십시오:

  1. 로컬 사용자의 역할을 쉼표로 구분된 목록으로 가져옵니다:

    ROLES=$(tsh status -f json | jq -r '.active.roles | join(",")')
  2. 새로운 역할을 추가하기 위해 로컬 사용자를 수정합니다:

    tctl users update $(tsh status -f json | jq -r '.active.username') \ --set-roles "${ROLES?},access-plugin-impersonator"
  3. Teleport 클러스터에서 로그아웃한 후 다시 로그인하여 새로운 역할을 가집니다.

  1. 텍스트 편집기에서 github 인증 커넥터를 엽니다:

    tctl edit github/github
  2. github 커넥터를 수정하여 teams_to_roles 섹션에 access-plugin-impersonator 을 추가합니다.

    이 역할에 매핑해야 하는 팀은 조직의 역할 기반 액세스 제어(RBAC) 설계에 따라 다릅니다. 그러나 팀은 귀하의 사용자 계정을 포함해야 하며, 조직 내에서 가장 작은 팀이어야 합니다.

    예시는 다음과 같습니다:

      teams_to_roles:
        - organization: octocats
          team: admins
          roles:
            - access
    +       - access-plugin-impersonator
    
  3. 파일을 편집하고 저장하여 변경 사항을 적용합니다.

  4. Teleport 클러스터에서 로그아웃한 후 다시 로그인하여 새로운 역할을 가집니다.

  1. saml 구성 리소스를 가져옵니다:

    tctl get --with-secrets saml/mysaml > saml.yaml

    --with-secrets 플래그는 spec.signing_key_pair.private_key 의 값을 saml.yaml 파일에 추가합니다. 이 키는 민감한 값을 포함하므로, 리소스를 업데이트한 후 즉시 saml.yaml 파일을 삭제해야 합니다.

  2. saml.yaml 을 수정하여 attributes_to_roles 섹션에 access-plugin-impersonator 을 추가합니다.

    이 역할에 매핑해야 하는 속성은 조직의 역할 기반 액세스 제어(RBAC) 설계에 따라 다릅니다. 그러나 그룹은 귀하의 사용자 계정을 포함해야 하며, 조직 내에서 가장 작은 그룹이어야 합니다.

    예시는 다음과 같습니다:

      attributes_to_roles:
        - name: "groups"
          value: "my-group"
          roles:
            - access
    +       - access-plugin-impersonator
    
  3. 변경 사항을 적용합니다:

    tctl create -f saml.yaml
  4. Teleport 클러스터에서 로그아웃한 후 다시 로그인하여 새로운 역할을 가집니다.

  1. oidc 구성 리소스를 가져옵니다:

    tctl get oidc/myoidc --with-secrets > oidc.yaml

    --with-secrets 플래그는 spec.signing_key_pair.private_key 의 값을 oidc.yaml 파일에 추가합니다. 이 키는 민감한 값을 포함하므로, 리소스를 업데이트한 후 즉시 oidc.yaml 파일을 삭제해야 합니다.

  2. oidc.yaml 을 수정하여 claims_to_roles 섹션에 access-plugin-impersonator 을 추가합니다.

    이 역할에 매핑해야 하는 클레임은 조직의 역할 기반 액세스 제어(RBAC) 설계에 따라 다릅니다. 그러나 그룹은 귀하의 사용자 계정을 포함해야 하며, 조직 내에서 가장 작은 그룹이어야 합니다.

    예시는 다음과 같습니다:

      claims_to_roles:
        - name: "groups"
          value: "my-group"
          roles:
            - access
    +       - access-plugin-impersonator
    
  3. 변경 사항을 적용합니다:

    tctl create -f oidc.yaml
  4. Teleport 클러스터에서 로그아웃한 후 다시 로그인하여 새로운 역할을 가집니다.

이제 access-plugin 역할 및 사용자에 대한 서명된 인증서를 생성할 수 있습니다.

액세스 플러그인 ID 내보내기

다음의 tctl auth sign 명령을 사용하여 access-plugin 이 Teleport 클러스터에 연결하는 데 필요한 자격 증명을 요청할 것입니다.

다음 tctl auth sign 명령은 access-plugin 사용자를 가장하여 서명된 자격 증명을 생성하고 로컬 디렉토리에 ID 파일을 기록합니다:

tctl auth sign --user=access-plugin --out=auth.pem

Teleport의 Access Request 플러그인은 Teleport Auth Service의 gRPC 엔드포인트에 TLS로 연결하여 새로 추가되거나 업데이트된 Access Requests를 청취합니다.

ID 파일인 auth.pem 에는 TLS 및 SSH 자격 증명이 모두 포함되어 있습니다. Access Request 플러그인은 SSH 자격 증명을 사용하여 Proxy Service에 연결하고, Proxy Service는 Auth Service에 역 터널 연결을 설정합니다. 플러그인은 이 역 터널과 TLS 자격 증명을 사용하여 Auth Service의 gRPC 엔드포인트에 연결합니다.

나중에 플러그인을 구성할 때 이 파일을 참조할 것입니다.

역할 Access Requests 설정하기

이 가이드에서는 우리의 플러그인을 사용하여 역할 Access Requests를 관리할 것입니다. 이를 위해 클러스터에서 역할 Access Requests를 설정할 것입니다.

'이 가이드를 위해, 내장된 editor 역할을 요청할 수 있는 editor-requester 역할과 editor 역할에 대한 요청을 검토할 수 있는 editor-reviewer 역할을 정의하겠습니다.

다음 내용을 포함하는 editor-request-rbac.yaml 파일을 생성하십시오:

kind: role
version: v7
metadata:
  name: editor-reviewer
spec:
  allow:
    review_requests:
      roles: ['editor']
---
kind: role
version: v7
metadata:
  name: editor-requester
spec:
  allow:
    request:
      roles: ['editor']
      thresholds:
        - approve: 1
          deny: 1

정의한 역할을 생성하십시오:

tctl create -f editor-request-rbac.yaml
role 'editor-reviewer'가 생성되었습니다role 'editor-requester'가 생성되었습니다

editor-reviewer 역할을 할당하여 editor-requester 역할을 가진 사용자의 요청을 검토할 수 있도록 하십시오.

editor-reviewer 역할을 Teleport 사용자에게 할당하려면, 인증 제공자에 맞는 적절한 명령어를 실행하십시오:

  1. 로컬 사용자의 역할을 쉼표로 구분된 목록으로 가져옵니다:

    ROLES=$(tsh status -f json | jq -r '.active.roles | join(",")')
  2. 새로운 역할을 추가하기 위해 로컬 사용자를 수정합니다:

    tctl users update $(tsh status -f json | jq -r '.active.username') \ --set-roles "${ROLES?},editor-reviewer"
  3. Teleport 클러스터에서 로그아웃한 후 다시 로그인하여 새로운 역할을 가집니다.

  1. 텍스트 편집기에서 github 인증 커넥터를 엽니다:

    tctl edit github/github
  2. github 커넥터를 수정하여 teams_to_roles 섹션에 editor-reviewer 을 추가합니다.

    이 역할에 매핑해야 하는 팀은 조직의 역할 기반 액세스 제어(RBAC) 설계에 따라 다릅니다. 그러나 팀은 귀하의 사용자 계정을 포함해야 하며, 조직 내에서 가장 작은 팀이어야 합니다.

    예시는 다음과 같습니다:

      teams_to_roles:
        - organization: octocats
          team: admins
          roles:
            - access
    +       - editor-reviewer
    
  3. 파일을 편집하고 저장하여 변경 사항을 적용합니다.

  4. Teleport 클러스터에서 로그아웃한 후 다시 로그인하여 새로운 역할을 가집니다.

  1. saml 구성 리소스를 가져옵니다:

    tctl get --with-secrets saml/mysaml > saml.yaml

    --with-secrets 플래그는 spec.signing_key_pair.private_key 의 값을 saml.yaml 파일에 추가합니다. 이 키는 민감한 값을 포함하므로, 리소스를 업데이트한 후 즉시 saml.yaml 파일을 삭제해야 합니다.

  2. saml.yaml 을 수정하여 attributes_to_roles 섹션에 editor-reviewer 을 추가합니다.

    이 역할에 매핑해야 하는 속성은 조직의 역할 기반 액세스 제어(RBAC) 설계에 따라 다릅니다. 그러나 그룹은 귀하의 사용자 계정을 포함해야 하며, 조직 내에서 가장 작은 그룹이어야 합니다.

    예시는 다음과 같습니다:

      attributes_to_roles:
        - name: "groups"
          value: "my-group"
          roles:
            - access
    +       - editor-reviewer
    
  3. 변경 사항을 적용합니다:

    tctl create -f saml.yaml
  4. Teleport 클러스터에서 로그아웃한 후 다시 로그인하여 새로운 역할을 가집니다.

  1. oidc 구성 리소스를 가져옵니다:

    tctl get oidc/myoidc --with-secrets > oidc.yaml

    --with-secrets 플래그는 spec.signing_key_pair.private_key 의 값을 oidc.yaml 파일에 추가합니다. 이 키는 민감한 값을 포함하므로, 리소스를 업데이트한 후 즉시 oidc.yaml 파일을 삭제해야 합니다.

  2. oidc.yaml 을 수정하여 claims_to_roles 섹션에 editor-reviewer 을 추가합니다.

    이 역할에 매핑해야 하는 클레임은 조직의 역할 기반 액세스 제어(RBAC) 설계에 따라 다릅니다. 그러나 그룹은 귀하의 사용자 계정을 포함해야 하며, 조직 내에서 가장 작은 그룹이어야 합니다.

    예시는 다음과 같습니다:

      claims_to_roles:
        - name: "groups"
          value: "my-group"
          roles:
            - access
    +       - editor-reviewer
    
  3. 변경 사항을 적용합니다:

    tctl create -f oidc.yaml
  4. Teleport 클러스터에서 로그아웃한 후 다시 로그인하여 새로운 역할을 가집니다.

editor-requester 역할을 가진 myuser 라는 사용자를 생성하십시오. 이 사용자는 editor 역할을 요청하지 않는 한 클러스터 구성을 편집할 수 없습니다:

tctl users add myuser --roles=editor-requester

tctl 은 터미널에 초대 URL을 출력합니다. URL을 방문하고 myuser 로 처음 로그인하여 Teleport 클러스터에 맞게 구성된 자격 증명을 등록하십시오.

이 가이드의 후반부에서는 myusereditor 역할을 요청할 것이며, 그 요청을 Teleport 플러그인을 사용하여 검토할 수 있습니다.

4/5 단계. 액세스 요청 플러그인 작성

이 단계에서는 examples/access-plugin-minimal/main.go 의 액세스 요청 플러그인 구조를 안내합니다. 여기 제공된 예제를 사용하여 자신의 액세스 요청 플러그인을 작성할 수 있습니다.

임포트

액세스 요청 플러그인이 Go의 표준 라이브러리에서 가져올 패키지는 다음과 같습니다:

패키지설명
contextcontext.Context 유형을 포함합니다. context.Context 는 실패하거나 시간 초과될 수 있는 외부 서비스에 대한 연결과 같은 장기 실행 루틴을 제어하기 위한 추상화입니다. 프로그램은 컨텍스트를 취소하거나 타임아웃 및 메타데이터를 할당할 수 있습니다.
errors오류 처리.
fmt데이터 포매팅, 출력, 문자열 또는 오류를 위한 것입니다.
strings문자열 조작.

플러그인은 다음과 같은 타사 코드를 가져옵니다:

패키지설명
github.com/gravitational/teleport/api/clientAuth 서비스의 gRPC API에 인증하고 요청을 만드는 라이브러리입니다.
github.com/gravitational/teleport/api/typesAuth 서비스 API에서 사용하는 유형, 예: 액세스 요청.
github.com/gravitational/trace표준 라이브러리에서 제공하는 것보다 더 유용한 세부정보로 오류를 표시합니다.
google.golang.org/api/optionGoogle API 클라이언트를 구성하는 설정입니다.
google.golang.org/api/sheets/v4Google Sheets API 클라이언트 라이브러리로, 프로그램에서 sheets 로 별칭이 지정됩니다.
google.golang.org/grpcgRPC 클라이언트 및 서버 라이브러리입니다.

구성

먼저, 환경에 맞게 구성해야 하는 두 개의 상수를 선언합니다:

// 저작권 2023 Gravitational, Inc
//
// Apache License, Version 2.0 ("라이선스")에 따라 라이선스가 부여됩니다.
// 라이선스에 따라 사용하지 않는 한, 이 파일을 사용할 수 없습니다.
// 라이선스 사본은 다음에서 확인할 수 있습니다:
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// 관련 법률에 의해 요구되거나 서면으로 동의하지 않는 한,
// 이 소프트웨어는 "있는 그대로" 제공되며, 명시적이거나 묵시적인 어떠한 보증도 포함하지 않습니다.
// 라이선스에서 허용하는 권한과 제한 사항에 대한 자세한 내용은 위의 링크를 참조하십시오.


package main

const (
	proxyAddr     string = "CHANGE ME"
	spreadSheetID string = "CHANGE ME"
)

proxyAddr 는 Teleport Proxy 서비스 또는 Teleport Enterprise Cloud 테넌트의 호스트 이름 및 포트를 나타냅니다. 자신의 Proxy 서비스 주소로 설정하십시오, 예: mytenant.teleport.sh:443 .

spreadSheetID 는 앞서 생성한 스프레드시트의 ID로 설정합니다. 스프레드시트 ID를 찾으려면 Google Drive에서 스프레드시트를 방문하십시오. ID는 아래의 URL 경로 세그먼트인 SPREADSHEET_ID 에 있습니다:

https://docs.google.com/spreadsheets/d/SPREADHSEET_ID/edit#gid=0

AccessRequestPlugin 유형

plugin.go 파일은 액세스 요청 플러그인 코드를 구성하는 데 사용할 유형을 선언합니다:

// 저작권 2023 Gravitational, Inc
//
// Apache License, Version 2.0 ("라이선스")에 따라 라이선스가 부여됩니다.
// 라이선스에 따라 사용하지 않는 한, 이 파일을 사용할 수 없습니다.
// 라이선스 사본은 다음에서 확인할 수 있습니다:
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// 관련 법률에 의해 요구되거나 서면으로 동의하지 않는 한,
// 이 소프트웨어는 "있는 그대로" 제공되며, 명시적이거나 묵시적인 어떠한 보증도 포함하지 않습니다.
// 라이선스에서 허용하는 권한과 제한 사항에 대한 자세한 내용은 위의 링크를 참조하십시오.


package main

import (
	"context"

	sheets "google.golang.org/api/sheets/v4"

	"github.com/gravitational/teleport/api/client"
	"github.com/gravitational/teleport/api/types"
)

type AccessRequestPlugin struct {
	TeleportClient *client.Client
	EventHandler   interface {
		HandleEvent(ctx context.Context, event types.Event) error
	}
}

type googleSheetsClient struct {
	sheetsClient *sheets.SpreadsheetsService
}

AccessRequestPlugin 유형은 일반 액세스 요청 플러그인을 나타내며, 이 유형을 사용하여 자신의 플러그인을 구축할 수 있습니다. 이 유형은 Teleport API 클라이언트와 EventHandler 를 포함하며, HandleEvent 메서드를 구현하는 모든 Go 유형입니다.

우리의 경우 HandleEvent 를 구현하는 유형은 Google Sheets를 위한 API 클라이언트를 포함하는 구조체 유형인 googleSheetsClient 입니다.

행 데이터 준비

스프레드시트의 새 행을 생성하든 기존 행을 업데이트하든, Google Sheets에 제공할 액세스 요청의 데이터를 추출하는 방법이 필요합니다. 우리는 makeRowData 메서드를 통해 이를 달성합니다:

// 저작권 2023 Gravitational, Inc
//
// Apache License, Version 2.0 ("라이선스")에 따라 라이선스가 부여됩니다.
// 라이선스에 따라 사용하지 않는 한, 이 파일을 사용할 수 없습니다.
// 라이선스 사본은 다음에서 확인할 수 있습니다:
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// 관련 법률에 의해 요구되거나 서면으로 동의하지 않는 한,
// 이 소프트웨어는 "있는 그대로" 제공되며, 명시적이거나 묵시적인 어떠한 보증도 포함하지 않습니다.
// 라이선스에서 허용하는 권한과 제한 사항에 대한 자세한 내용은 위의 링크를 참조하십시오.


package main

import (
	"fmt"
	"strings"

	sheets "google.golang.org/api/sheets/v4"

	"github.com/gravitational/teleport/api/types"
)

func stringPtr(s string) *string { return &s }

var requestStates = map[types.RequestState]string{
	types.RequestState_APPROVED: "APPROVED",
	types.RequestState_DENIED:   "DENIED",
	types.RequestState_PENDING:  "PENDING",
	types.RequestState_NONE:     "NONE",
}

func (g *googleSheetsClient) makeRowData(ar types.AccessRequest) *sheets.RowData {
	requestState := requestStates[ar.GetState()]

	viewLink := fmt.Sprintf(
		`=HYPERLINK("%v", "%v")`,
		"https://"+proxyAddr+"/web/requests/"+ar.GetName(),
		"View Access Request",
	)

	return &sheets.RowData{
		Values: []*sheets.CellData{
			&sheets.CellData{
				UserEnteredValue: &sheets.ExtendedValue{
					StringValue: stringPtr(ar.GetName()),
				},
			},
			&sheets.CellData{
				UserEnteredValue: &sheets.ExtendedValue{
					StringValue: stringPtr(ar.GetCreationTime().String()),
				},
			},
			&sheets.CellData{
				UserEnteredValue: &sheets.ExtendedValue{
					StringValue: stringPtr(ar.GetUser()),
				},
			},
			&sheets.CellData{
				UserEnteredValue: &sheets.ExtendedValue{
					StringValue: stringPtr(strings.Join(ar.GetRoles(), ",")),
				},
			},
			&sheets.CellData{
				UserEnteredValue: &sheets.ExtendedValue{
					StringValue: &requestState,
				},
			},
			&sheets.CellData{
				UserEnteredValue: &sheets.ExtendedValue{
					FormulaValue: &viewLink,
				},
			},
		},
	}
}

sheets.RowData 유형은 문자열에 대한 포인터를 광범위하게 사용하므로, 제공된 문자열에 대한 포인터를 반환하는 유틸리티 함수인 stringPtr 를 도입합니다. 이를 통해 여러 함수 호출 체인을 사용해 sheets.RowData 의 셀 값을 더 쉽게 할당할 수 있습니다.

makeRowDatagoogleSheetsClient 유형의 메서드입니다. (*는 이 메서드가 googleSheetsClient 에 대한 포인터를 받음을 나타냅니다.) 이 메서드는 Teleport의 API 라이브러리에서 액세스 요청 내의 필드를 표현하는 데 사용되는 types.AccessRequest 를 매개변수로 받습니다.

Google Sheets 클라이언트 라이브러리는 스프레드시트를 업데이트하기 위해 요청에 포함할 sheets.RowData 유형을 정의합니다. 이 함수는 types.AccessRequest*sheets.RowData (또 다른 포인터)로 변환합니다.

액세스 요청은 승인됨, 거부됨, 보류 중 또는 없음의 네 가지 상태 중 하나를 가집니다. 우리는 Teleport의 types 라이브러리에서 요청 상태를 얻고 이를 requestStates 맵의 문자열에 매핑합니다.

데이터를 추출할 때, 우리는 types.AccessRequest.GetName() 메서드를 사용하여 액세스 요청의 ID를 스프레드시트에 포함할 수 있는 문자열로 검색합니다.

사용자는 Teleport 웹 UI 내에서 요청의 ID에 해당하는 URL을 방문하여 액세스 요청을 검토할 수 있습니다. makeRowData 는 이 URL의 링크로 스프레드시트에 삽입할 수 있는 =HYPERLINK 공식을 조립합니다.

행 생성

다음 함수는 Google Sheets API에 요청을 제출하여 수신되는 Access Request에 따라 새 행을 생성하고, makeRowData 에서 반환된 데이터를 사용합니다. 행 생성 시도가 실패하면 오류를 반환합니다:

// 저작권 2023 Gravitational, Inc
//
// Apache License, Version 2.0 ("라이선스")에 따라 라이선스가 부여됩니다.
// 라이선스에 따라 사용하지 않는 한, 이 파일을 사용할 수 없습니다.
// 라이선스 사본은 다음에서 확인할 수 있습니다:
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// 관련 법률에 의해 요구되거나 서면으로 동의하지 않는 한,
// 이 소프트웨어는 "있는 그대로" 제공되며, 명시적이거나 묵시적인 어떠한 보증도 포함하지 않습니다.
// 라이선스에서 허용하는 권한과 제한 사항에 대한 자세한 내용은 위의 링크를 참조하십시오.


package main

import (
	"github.com/gravitational/trace"
	sheets "google.golang.org/api/sheets/v4"

	"github.com/gravitational/teleport/api/types"
)

func (g *googleSheetsClient) createRow(ar types.AccessRequest) error {
	row := g.makeRowData(ar)

	req := sheets.BatchUpdateSpreadsheetRequest{
		Requests: []*sheets.Request{
			{
				AppendCells: &sheets.AppendCellsRequest{

					Fields: "*",
					Rows: []*sheets.RowData{
						row,
					},
				},
			},
		},
	}

	resp, err := g.sheetsClient.BatchUpdate(spreadSheetID, &req).Do()
	if err != nil {
		return trace.Wrap(err)
	}

	if resp.HTTPStatusCode == 201 || resp.HTTPStatusCode == 200 {
		return nil
	}

	return trace.Errorf("Unexpected response code creating a row: %v",
		resp.HTTPStatusCode)
}

createRowsheets.BatchUpdateSpreadsheetRequest 를 조립하고 g.sheetsClient.BatchUpdate()를 사용하여 Google Sheets API에 전송하며, 요청을 보낼 때 발생한 오류를 반환합니다.

예상치 못한 HTTP 상태 코드는 오류를 반환하지 않고 로깅합니다. 이는 이러한 문제가 일시적인 서버 측 문제일 수 있기 때문입니다. 프로덕션 Access Request 플러그인은 이러한 상황을 더 정교하게 처리하여 요청을 저장하고 나중에 다시 시도할 수 있습니다.

행 업데이트

행 업데이트 코드는 새 행 생성 코드와 유사합니다:

// 저작권 2023 Gravitational, Inc
//
// Apache License, Version 2.0 ("라이선스")에 따라 라이선스가 부여됩니다.
// 라이선스에 따라 사용하지 않는 한, 이 파일을 사용할 수 없습니다.
// 라이선스 사본은 다음에서 확인할 수 있습니다:
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// 관련 법률에 의해 요구되거나 서면으로 동의하지 않는 한,
// 이 소프트웨어는 "있는 그대로" 제공되며, 명시적이거나 묵시적인 어떠한 보증도 포함하지 않습니다.
// 라이선스에서 허용하는 권한과 제한 사항에 대한 자세한 내용은 위의 링크를 참조하십시오.


package main

import (
	"fmt"

	"github.com/gravitational/trace"
	sheets "google.golang.org/api/sheets/v4"

	"github.com/gravitational/teleport/api/types"
)

func (g *googleSheetsClient) updateRow(ar types.AccessRequest, rowNum int64) error {
	row := g.makeRowData(ar)

	req := sheets.BatchUpdateSpreadsheetRequest{
		Requests: []*sheets.Request{
			{
				UpdateCells: &sheets.UpdateCellsRequest{

					Fields: "*",
					Start: &sheets.GridCoordinate{
						RowIndex: rowNum,
					},
					Rows: []*sheets.RowData{
						row,
					},
				},
			},
		},
	}

	resp, err := g.sheetsClient.BatchUpdate(spreadSheetID, &req).Do()
	if err != nil {
		return trace.Wrap(err)
	}

	if resp.HTTPStatusCode == 201 || resp.HTTPStatusCode == 200 {
		return nil
	}
	return trace.Wrap(
		fmt.Errorf(
			"Unexpected response code updating a row: %v\n",
			resp.HTTPStatusCode),
	)
}

updateRowcreateRow 의 유일한 차이점은 &sheets.AppendCellsRequest 대신 &sheets.UpdateCellsRequest 를 전송한다는 것입니다. 이 함수는 업데이트할 스프레드시트 내의 행 번호를 받아 해당 행을 제공된 Access Request의 정보로 업데이트하는 요청을 보냅니다.

스프레드시트를 업데이트할 위치 결정

프로그램이 Access Request를 업데이트하는 이벤트를 수신하면 O توفر하여 Access Request에 해당하는 스프레드시트의 행을 찾고 해당 행을 업데이트할 수 있는 방법이 필요합니다:

// 저작권 2023 Gravitational, Inc
//
// Apache License, Version 2.0 ("라이선스")에 따라 라이선스가 부여됩니다.
// 라이선스에 따라 사용하지 않는 한, 이 파일을 사용할 수 없습니다.
// 라이선스 사본은 다음에서 확인할 수 있습니다:
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// 관련 법률에 의해 요구되거나 서면으로 동의하지 않는 한,
// 이 소프트웨어는 "있는 그대로" 제공되며, 명시적이거나 묵시적인 어떠한 보증도 포함하지 않습니다.
// 라이선스에서 허용하는 권한과 제한 사항에 대한 자세한 내용은 위의 링크를 참조하십시오.


package main

import (
	"errors"

	"github.com/gravitational/trace"

	"github.com/gravitational/teleport/api/types"
)

func (g *googleSheetsClient) updateSpreadsheet(ar types.AccessRequest) error {
	s, err := g.sheetsClient.Get(spreadSheetID).IncludeGridData(true).Do()
	if err != nil {
		return trace.Wrap(err)
	}

	if len(s.Sheets) != 1 {
		return trace.Wrap(
			errors.New("the spreadsheet must have a single sheet"),
		)
	}

	for _, d := range s.Sheets[0].Data {
		for i, r := range d.RowData {
			if r.Values[0] != nil &&
				r.Values[0].UserEnteredValue != nil &&
				r.Values[0].UserEnteredValue.StringValue != nil &&
				*r.Values[0].UserEnteredValue.StringValue == ar.GetName() {
				if err := g.updateRow(ar, int64(i)); err != nil {
					return trace.Wrap(err)
				}
			}
		}
	}
	return nil
}

updateSpreadSheettypes.AccessRequest 를 받아 스프레드시트에서 최신 데이터를 가져오고 업데이트할 행을 결정한 후 updateRow 를 호출합니다. 이 함수는 각 행의 첫 번째 열을 선형 검색하여 해당 열이 Access Request의 ID와 일치하는지 확인합니다. 그런 다음 Access Request와 행 번호를 가지고 updateRow 를 호출합니다.

수신 Access Requests 처리

플러그인은 이벤트를 수신하면 핸들러 함수를 호출합니다. 이를 설정하기 위해 우리는 일반 AccessRequestPlugin 유형의 Run 메서드를 사용합니다. 이 메서드는 플러그인의 주요 루프를 포함합니다:

// 저작권 2023 Gravitational, Inc
//
// Apache License, Version 2.0 ("라이선스")에 따라 라이선스가 부여됩니다.
// 라이선스에 따라 사용하지 않는 한, 이 파일을 사용할 수 없습니다.
// 라이선스 사본은 다음에서 확인할 수 있습니다:
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// 관련 법률에 의해 요구되거나 서면으로 동의하지 않는 한,
// 이 소프트웨어는 "있는 그대로" 제공되며, 명시적이거나 묵시적인 어떠한 보증도 포함하지 않습니다.
// 라이선스에서 허용하는 권한과 제한 사항에 대한 자세한 내용은 위의 링크를 참조하십시오.


package main

import (
	"context"
	"fmt"

	"github.com/gravitational/trace"

	"github.com/gravitational/teleport/api/types"
)

func (g *googleSheetsClient) HandleEvent(ctx context.Context, event types.Event) error {
	if event.Resource == nil {
		return nil
	}

	if _, ok := event.Resource.(*types.WatchStatusV1); ok {
		fmt.Println("Successfully started listening for Access Requests...")
		return nil
	}

	r, ok := event.Resource.(types.AccessRequest)
	if !ok {
		fmt.Printf("Unknown (%T) event received, skipping.\n", event.Resource)
		return nil
	}

	if r.GetState() == types.RequestState_PENDING {
		if err := g.createRow(r); err != nil {
			return err
		}
		fmt.Println("Successfully created a row")
		return nil
	}

	if err := g.updateSpreadsheet(r); err != nil {
		return err
	}
	fmt.Println("Successfully updated a spreadsheet row")
	return nil
}

func (p *AccessRequestPlugin) Run() error {
	ctx := context.Background()

	watch, err := p.TeleportClient.NewWatcher(ctx, types.Watch{
		Kinds: []types.WatchKind{
			types.WatchKind{Kind: types.KindAccessRequest},
		},
	})

	if err != nil {
		return trace.Wrap(err)
	}
	defer watch.Close()

	fmt.Println("Starting the watcher job")

	for {
		select {
		case e := <-watch.Events():
			if err := p.EventHandler.HandleEvent(ctx, e); err != nil {
				return trace.Wrap(err)
			}
		case <-watch.Done():
			fmt.Println("The watcher job is finished")
			return nil
		}
	}
}

앞서 설명한 대로 AccessRequestPlugin 유형의 EventHandler 필드는 HandleEvent 메서드가 있는 인터페이스에 할당됩니다. 이 경우 구현은 *googleSheetsClient.HandleEvent 입니다. 이 메서드는 Access Request가 보류 상태인지, 즉 요청이 새로운 것인지 확인합니다. 그렇다면 createRow 를 호출합니다. 그렇지 않으면 updateSpreadsheet 를 호출합니다.

Teleport API 클라이언트 유형인 client.Client 는 Auth Service API에서 gRPC 스트림을 통해 새로운 감사 이벤트를 수신하는 NewWatcher 메서드를 가지고 있습니다. 메서드의 두 번째 매개변수는 주시할 감사 이벤트의 유형을 나타내며, 이 경우 Teleport Access Requests와 관련된 이벤트입니다.

NewWatcher 의 결과인 types.WatcherRunEvents 메서드를 호출하여 새로운 감사 이벤트에 응답할 수 있게 합니다. 이는 동시 루틴 간에 통신할 수 있도록 하는 런타임 추상화인 Go 채널을 반환합니다. Done 에서 반환되는 또 다른 채널은 수신자가 끝났음을 나타냅니다.

for 루프 내에서 Run 메서드는 Done 채널이나 Events 채널 중에서 먼저 준비된 것을 수신합니다. Events 채널에서 수신하면 HandleEvent 메서드를 호출하여 이벤트를 처리합니다.

API 클라이언트 초기화

이제 Teleport 및 Google Sheets API 클라이언트를 사용하여 Access Request 이벤트를 수신하고 이를 스프레드시트를 유지 관리하는 데 사용할 모든 코드를 준비했습니다. 마지막 단계는 API 클라이언트를 초기화하여 프로그램을 시작하는 것입니다:

// 저작권 2023 Gravitational, Inc
//
// Apache License, Version 2.0 ("라이선스")에 따라 라이선스가 부여됩니다.
// 라이선스에 따라 사용하지 않는 한, 이 파일을 사용할 수 없습니다.
// 라이선스 사본은 다음에서 확인할 수 있습니다:
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// 관련 법률에 의해 요구되거나 서면으로 동의하지 않는 한,
// 이 소프트웨어는 "있는 그대로" 제공되며, 명시적이거나 묵시적인 어떠한 보증도 포함하지 않습니다.
// 라이선스에서 허용하는 권한과 제한 사항에 대한 자세한 내용은 위의 링크를 참조하십시오.


package main

import (
	"context"

	"google.golang.org/api/option"
	sheets "google.golang.org/api/sheets/v4"
	"google.golang.org/grpc"

	"github.com/gravitational/teleport/api/client"
)

func main() {
	ctx := context.Background()
	svc, err := sheets.NewService(ctx, option.WithCredentialsFile("credentials.json"))
	if err != nil {
		panic(err)
	}

	creds := client.LoadIdentityFile("auth.pem")

	teleport, err := client.New(ctx, client.Config{
		Addrs:       []string{proxyAddr},
		Credentials: []client.Credentials{creds},
		DialOpts: []grpc.DialOption{
			grpc.WithReturnConnectionError(),
		},
	})
	if err != nil {
		panic(err)
	}
	defer teleport.Close()

	gs := googleSheetsClient{
		sheetsClient: sheets.NewSpreadsheetsService(svc),
	}

	plugin := AccessRequestPlugin{
		TeleportClient: teleport,
		EventHandler:   &gs,
	}

	if err := plugin.Run(); err != nil {
		panic(err)
	}
}

main 함수는 프로그램의 진입점으로, AccessRequestPlugingoogleSheetsClient 를 초기화하고 이를 사용하여 플러그인을 실행합니다.

이 함수는 이전에 다운로드한 자격 증명 파일을 상대 경로 credentials.json 에서 로드하여 Google Sheets API 클라이언트를 생성합니다.

client 는 API 클라이언트를 설정하기 위한 Teleport의 라이브러리입니다. 우리의 플러그인은 client.LoadIdentityFile 을 호출하여 client.Credentials 를 얻음으로써 이를 수행합니다. 그런 다음 client.Credentials 를 사용하여 client.New 를 호출하고, 제공된 신원 파일을 사용하여 Addrs 필드에서 지정된 Teleport Proxy Service에 연결합니다.

이 예제에서는 client.Newgrpc.WithReturnConnectionError() 함수 호출을 전달하고 있는데, 이는 gRPC 클라이언트에게 보다 상세한 연결 오류를 반환하도록 지시합니다.

Warning

이 프로그램은 자격 증명 또는 Teleport 클러스터 주소를 검증하지 않습니다. 다음 사항을 확인하십시오:

  • 이전에 내보낸 신원 파일의 TTL이 만료되지 않았습니다.
  • proxyAddr 상수에 제공한 값이 Teleport Proxy Service의 호스트 웹 포트를 모두 포함하고 있습니다(예: mytenant.teleport.sh:443 ).

5/5단계. 플러그인 테스트하기

플러그인을 실행하여 Teleport 클러스터에서 Google Sheets로 Access Requests를 전달합니다. examples/access-plugin-minimal 디렉토리 내에서 다음 명령을 실행하십시오:

go run teleport-sheets

이제 플러그인이 실행 중이므로 Access Request를 생성합니다:

Teleport 관리자는 tctl 을 사용하여 다른 사용자에 대한 액세스 요청을 생성할 수 있습니다:

tctl request create myuser --roles=editor

사용자는 tsh 를 사용하여 액세스 요청을 생성하고 승인된 역할로 로그인할 수 있습니다:

tsh request create --roles=editor
요청 승인 중... (id: 8f77d2d1-2bbf-4031-a300-58926237a807)

사용자는 "액세스 요청" 탭을 방문하고 "새 요청"을 클릭하여 웹 UI를 사용하여 액세스를 요청할 수 있습니다:

스프레드시트에서 PENDING 상태의 새로운 Access Request를 확인할 수 있어야 합니다.

스프레드시트에서 새로운 요청 옆에 있는 "Access Request 보기"를 클릭하십시오. 원래 사용자로 Teleport Web UI에 로그인합니다. 리뷰를 제출하면(예: 요청을 거부) 새로운 상태가 스프레드시트에 나타납니다.

Access Request 플러그인은 플러그인을 통해 Access Request를 검토할 수 없으며 항상 리뷰어를 Teleport Web UI로 안내하여 리뷰를 완료해야 합니다. 그렇지 않으면 권한이 없는 당사자가 플러그인으로 트래픽을 스푸핑하고 권한 상승을 할 수 있습니다.

다음 단계

이 가이드에서는 Teleport의 API 클라이언트 라이브러리를 사용하여 Access Request 플러그인을 설정하는 방법을 보여주었습니다. 이 가이드에서 설명하는 최소 플러그인을 넘어, Teleport API를 사용하여 커뮤니케이션 및 프로젝트 관리 도구를 최대한 활용하는 보다 정교한 워크플로우를 설정할 수 있습니다.

상태 관리

이 가이드에서 개발한 플러그인은 상태가 없지만 스프레드시트의 모든 행을 검색하여 Access Request 정보를 업데이트해야 하므로, 실제 Access Request 플러그인은 일반적으로 상태 관리를 필요로 합니다. plugindata 패키지를 사용하여 Access Request 플러그인이 이를 더 쉽게 수행할 수 있습니다.

예제 참조

GitHub에서 gravitational/teleport 리포지토리를 탐색하여 Teleport에서 개발된 플러그인의 예제를 확인하십시오. 이 플러그인들이 이 가이드에서 논의한 패키지를 어떻게 사용하는지, 그리고 구성 검증 및 상태 관리와 같은 더 완전한 기능을 어떻게 추가하는지 볼 수 있습니다.

단기 자격 증명으로 플러그인 프로비저닝

이 예제에서는 tctl auth sign 명령어를 사용하여 플러그인에 대한 자격 증명을 가져왔습니다. 프로덕션 환경에서는 이러한 자격 증명이 도난당할 위험을 줄이기 위해 머신 ID를 통해 단기 자격 증명을 프로비저닝할 것을 권장합니다. 자세한 내용은 Machine ID 문서를 참조하세요.

Teleport 원문 보기