Programmati c доступ из учетной записи службы к ресурсу, защищенному Google IAP, запрещен из-за ошибки неверной подписи - PullRequest
7 голосов
/ 07 мая 2020

Недавно я включил IAP в кластере GKE.

  • Версия кластера: 1.15.11-gke.11

Я следовал инструкциям здесь: https://cloud.google.com/iap/docs/enabling-kubernetes-howto

Сервис config выглядит следующим образом:

---
apiVersion: cloud.google.com/v1beta1
kind: BackendConfig
metadata:
  name: foo-bc-iap
  namespace: foo-test
spec:
  iap:
    enabled: true
    oauthclientCredentials:
      secretName: iap-client-secret
---
apiVersion: v1
kind: Service
metadata:
  name: foo-internal-service
  namespace: foo-test
  annotations:
    cloud.google.com/backend-config: '{"ports":{"80":"foo-bc-iap"}}'
spec:
  type: NodePort # To create Ingress using the service.
  selector:
    app: foo-test
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8081

В качестве учетных данных я использовал идентификатор клиента OAuth 2.0 (тип: веб-приложение).

После того, как я убедился, что конечная точка API, защищенная IAP, работает иначе, когда я активирую IAP для службы Kubernetes, я написал следующую тестовую программу, чтобы убедиться, что конечная точка доступна из учетной записи службы, указанной в JSON файле 'account. json'.

При написании этого примера приложения я консультировался с этим do c: https://cloud.google.com/iap/docs/authentication-howto#iap_make_request - go

  • google. golang .org / api v0.23.0
  • go 1.12
func (m *myApp) testAuthz(ctx *cli.Context) error {
    audience := "<The client ID of the credential mentioned above>"
    serviceAccountOption := idtoken.WithCredentialsFile("account.json")

    client, err := idtoken.NewClient(ctx.Context, audience, serviceAccountOption)
    if err != nil {
        return fmt.Errorf("idtoken.NewClient: %v", err)
    }

    requestBody := `{
        <some JSON payload>
    }`

    request, err := http.NewRequest("POST", "https://my.iap.protected/endpoint",
        bytes.NewBuffer([]byte(requestBody)))
    if err != nil {
        return fmt.Errorf("http.NewRequest: %v", err)
    }

    request.Header.Add("Content-Type", "application/json")

    response, err := client.Do(request)
    if err != nil {
        return fmt.Errorf("client.Do: %v", err)
    }
    defer response.Body.Close()

    fmt.Printf("request header = %#v\n", response.Request.Header)
    fmt.Printf("response header = %#v\n", response.Header)

    body, err := ioutil.ReadAll(response.Body)
    if err != nil {
        return fmt.Errorf("ioutil.ReadAll: %v", err)
    }

    fmt.Printf("%d: %s\n", response.StatusCode, string(body))

    return nil
}

Но когда я запустил это, я смог увидеть только следующий ответ.

request header = http.Header{"Authorization":[]string{"Bearer <jwt token>"}, "Content-Type":[]string{"application/json"}, "X-Cloud-Trace-Context":[]string{"c855757f20d155da1140fad1508ae3e5/17413578722158830486;o=0"}}

response header = http.Header{"Alt-Svc":[]string{"clear"}, "Content-Length":[]string{"49"}, "Content-Type":[]string{"text/html; charset=UTF-8"}, "Date":[]string{"Wed, 06 May 2020 22:17:43 GMT"}, "X-Goog-Iap-Generated-Response":[]string{"true"}}

401: Invalid IAP credentials: JWT signature is invalid

Как вы можете видеть здесь, в доступе было отказано .

Итак, я подумал, что подпись, используемая для подписи токена JWT в заголовке, может быть неправильной.

Но с помощью jwt.io я убедился, что следующее:

  • Токен JWT, используемый в заголовке, подписан закрытым ключом учетной записи службы вызывающего абонента
  • Токен JWT, используемый в заголовке, можно проверить с помощью ключа publi c учетной записи службы вызывающего абонента
  • Токен JWT был подписан с использованием алгоритма RS256

И я также изучил токен:

{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "<the service account's private key ID>"
}

{
  "iss": "<email address of the service account>",
  "aud": "",
  "exp": 1588806087,
  "iat": 1588802487,
  "sub": "<email address of the service acocunt>"
}

Ничего особенного.

Так что я не уверен, что здесь происходит. Если я отключу IAP, конечная точка вернет правильный ответ .

Может ли кто-нибудь подсказать мне, что я делаю неправильно?

Ответы [ 3 ]

2 голосов
/ 16 мая 2020

Я попробовал ваш код и обнаружил, что он не работает с google.golang.org/api v0.23.0, но работает с (последний на момент написания).

Это действительно ошибка, в примечаниях к выпуску упоминается следующее:

When provided, use the TokenSource from options for NewTransport. This fixes a bug in idtoken.NewClient where the wrong TokenSource was being used for authentication.

Интересно, что 0.23.0 отправляет токен, подписанный с закрытым ключом учетной записи службы со следующими утверждениями:

{
  "iss":"xx@xx.iam.gserviceaccount.com",
  "aud":"",
  "exp":1589596554,
  "iat":1589592954,
  "sub":"xx@xx.iam.gserviceaccount.com"
}

и 0.24.0 отправляет токен, подписанный с закрытым ключом Google . (внутренне предыдущий токен заменяется на токен, подписанный Google)

{
    "aud":"xxxxxxx-xxxxxxxxxxxxxxxxxx.apps.googleusercontent.com",
    "azp":"xx@xx.iam.gserviceaccount.com",
    "email":"xx@xx.iam.gserviceaccount.com",
    "email_verified":true,
    "exp":1589596508,
    "iat":1589592908,
    "iss":"https://accounts.google.com",
    "sub":"11524xxxxxxxxxxxxxxxx"
}
0 голосов
/ 28 мая 2020

Как отметил @Dirbaio, я думаю, что это проблема, связанная с c до v0.23.0. Поскольку сейчас я не могу обновить зависимость, я решил создать новый клиент IAP, который не использует idtoken.NewClient. Вместо этого он просто использует idtoken.NewTokenSource для создания токена OID C. Добавить токен в заголовок авторизации очень просто, поэтому я могу обойти проблему в клиенте, созданном idtoken.NewClient.

package main

import (
    "context"
    "crypto/tls"
    "fmt"
    "io"
    "net/http"

    "golang.org/x/oauth2"
    "google.golang.org/api/idtoken"
    "google.golang.org/api/option"
)

// IAPClient is the default HTTPS client with Morse-Code KMS integration.
type IAPClient struct {
    client      *http.Client
    tokenSource oauth2.TokenSource
}

// NewIAPClient returns an HTTP client with TLS transport, but not doing the CA checks.
func NewIAPClient(audience string, opts ...option.ClientOption) *IAPClient {
    tokenSource, err := idtoken.NewTokenSource(context.Background(), audience, opts...)
    if err != nil {
        panic(fmt.Errorf("cannot create a new token source: %s", err.Error()))
    }

    return &IAPClient{
        client: &http.Client{
            Transport: &http.Transport{
                TLSClientConfig: &tls.Config{
                    InsecureSkipVerify: true,
                },
            },
        },
        tokenSource: tokenSource,
    }
}

// Do sends the http request to server with a morse-code JWT Authorization: Bearer header.
func (c *IAPClient) Do(request *http.Request) (*http.Response, error) {
    err := c.addAuthorizationHeader(request)
    if err != nil {
        return nil, fmt.Errorf("couldn't override the request with the new auth header: %s", err.Error())
    }

    return c.client.Do(request)
}

// Get sends the http GET request to server with a morse-code JWT Authorization: Bearer header.
func (c *IAPClient) Get(url string) (*http.Response, error) {
    request, err := http.NewRequest(http.MethodGet, url, nil)

    if err != nil {
        return nil, err
    }

    return c.Do(request)
}

// Post sends the http POST request to server with a morse-code JWT Authorization: Bearer header.
func (c *IAPClient) Post(url, contentType string, body io.Reader) (*http.Response, error) {
    request, err := http.NewRequest(http.MethodPost, url, body)
    if err != nil {
        return nil, err
    }

    request.Header.Add("Content-Type", contentType)

    return c.Do(request)
}

func (c *IAPClient) addAuthorizationHeader(request *http.Request) error {
    tkn, err := c.tokenSource.Token()
    if err != nil {
        return fmt.Errorf("cannot create a token: %s", err.Error())
    }

    tkn.SetAuthHeader(request)

    return nil
}
0 голосов
/ 15 мая 2020
Свойство

JWT token exp, похоже, имеет истекшую дату (7 мая, 1:01), и ваше сообщение было от (7 мая 3:53), вы пытались его регенерировать?

[EDIT: I знаю, это должен быть комментарий. Написано как ответ из-за низкой репутации]

...