Как сгенерировать JWT для триггера облачной функции HTTP? - PullRequest
0 голосов
/ 05 апреля 2020

Я пытаюсь создать JWT для вызова триггера HTTP в развернутой облачной функции GCP. Я уже развернул свою функцию с помощью allUsers и убедился, что она работает, но я хочу, чтобы она была более безопасной, поэтому мне нужно присоединить JWT к моему HTTP-запросу. Я следую этому коду , и следующие фрагменты в основном из этого. Я думаю, что я близко, но еще не совсем там. Во всех приведенных ниже примерах et c я изменил имя своего проекта на PROJECT_NAME.

Сначала я создал учетную запись службы с именем testharness-sa и загрузил файл ее ключа. У меня есть env var GOOGLE_APPLICATION_CREDENTIALS, указывающий на этот файл, когда я запускаю свои тесты. Затем я выполнил следующую команду:

gcloud functions add-iam-policy-binding gopubsub \
  --member='serviceAccount:testharness-sa@PROJECT_NAME.iam.gserviceaccount.com' \
  --role='roles/cloudfunctions.invoker'

Это дало мне подтверждение, перечислив все текущие привязки для моей облачной функции, включая testharness-sa.

Суть моего кода заключается в следующем :

    private String getSignedJwt() {
        GoogleCredentials credentials = GoogleCredentials
                .getApplicationDefault()
                .createScoped(Collections.singleton(IAM_SCOPE));
        long now = System.currentTimeMillis();
        RSAPrivateKey privateKey = (RSAPrivateKey) credentials.getPrivateKey();
        Algorithm algorithm = Algorithm.RSA256(null, privateKey);
        return JWT.create()
                .withKeyId(credentials.getPrivateKeyId())
                .withIssuer(credentials.getClientEmail())
                .withSubject(credentials.getClientEmail())
                .withAudience(OAUTH_TOKEN_AUDIENCE)
                .withIssuedAt(new Date(now))
                .withExpiresAt(new Date(now + EXPIRATION_TIME_IN_MILLIS))
                .withClaim("target_audience", clientId)
                .sign(algorithm);
    }

Это дает мне подписанный JWT. Как я понимаю, это используется для вызова GCP, чтобы получить окончательный JWT, который я могу использовать для вызова своей облачной функции.

Как только я генерирую подписанный JWT, я использую его следующим образом:

        String jwt = getSignedJwt();
        final GenericData tokenRequest = new GenericData()
                .set("grant_type", JWT_BEARER_TOKEN_GRANT_TYPE)
                .set("assertion", jwt);
        final UrlEncodedContent content = new UrlEncodedContent(tokenRequest);

        final HttpRequestFactory requestFactory = httpTransport.createRequestFactory();

        final HttpRequest request = requestFactory
                .buildPostRequest(new GenericUrl(OAUTH_TOKEN_URI), content)
                .setParser(new JsonObjectParser(JacksonFactory.getDefaultInstance()));

        HttpResponse response = request.execute();
...

Таким образом, он получает подписанный JWT и составляет запрос на получение окончательного JWT, а затем ... отказывает. Ошибка «Недопустимый JWT: проверка неуспешной аудитории». Похоже, у меня неверный параметр, поэтому давайте посмотрим на мои параметры (они на самом деле являются константами, а не параметрами):

    private static final String IAM_SCOPE = "https://www.googleapis.com/auth/iam";
    //"https://www.googleapis.com/auth/cloud-platform";
    private static final String OAUTH_TOKEN_URI = "https://oauth2.googleapis.com/token";
    //"https://www.googleapis.com/oauth2/v4/token";
    private static final String OAUTH_TOKEN_AUDIENCE = "https://us-central1-PROJECT_NAME.cloudfunctions.net/gopubsub";
    //"https://www.googleapis.com/token";
    private static final String JWT_BEARER_TOKEN_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer";
    private static final long EXPIRATION_TIME_IN_MILLIS = 3600 * 1000L;

Я добавил другие варианты как комментарии к константам.

Судя по сообщению об ошибке, похоже, что значение аудитории, которое я использую, неверно. На этой странице предлагается, чтобы аудитория представляла собой адрес электронной почты учетной записи службы, и я думаю, что в другом месте я видел, что это должен быть URL-адрес функции облака, но ни один из них не работает для меня.

Я знаю, что это может работать, потому что я могу выполнить эту команду:

gcloud auth print-identity-token

, которая дает мне окончательный JWT (если у меня есть GOOGLE_APPLICATION_CREDENTIALS, указывающая на мой json файл). Я могу вставить этот JWT в команду curl и успешно вызвать триггер HTTP, и если я пропущу JWT, он завершится неудачно, поэтому я знаю, что JWT проверяется. Но до сих пор я не знаю, как сделать эквивалент gcloud auth print-identity-token из моего Java кода. Кто-нибудь знает?

1 Ответ

0 голосов
/ 06 апреля 2020

Я обнаружил, что ответ не был связан с областью действия, несмотря на сообщение об ошибке. Это на самом деле связано со значением clientId, которое я передавал (и не упомянуло в моем вопросе, хотя оно есть в коде). Я использовал значение, найденное в файле json учетной записи службы, очень длинную строку цифр. Это была проблема. Это должен быть URL моего HTTP-триггера. Итак, вот константы, с которыми я закончил:

    private static final String IAM_SCOPE = "https://www.googleapis.com/auth/cloud-platform";
    private static final String OAUTH_TOKEN_URI = "https://www.googleapis.com/oauth2/v4/token";
    private static final String OAUTH_TOKEN_AUDIENCE = "https://www.googleapis.com/oauth2/v4/token";
    private static final String JWT_BEARER_TOKEN_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer";
    private static final long EXPIRATION_TIME_IN_MILLIS = 3600 * 1000L;

На самом деле это все еще работает, если я удаляю все ссылки на IAM_SCOPE, поэтому я заключаю, что это не имеет значения. Кусок кода, который объединяет его, теперь выглядит следующим образом:

        return JWT.create()
                .withKeyId(credentials.getPrivateKeyId())
                .withIssuer(credentials.getClientEmail())
                .withSubject(credentials.getClientEmail())
                .withAudience(OAUTH_TOKEN_AUDIENCE)
                .withIssuedAt(new Date(now))
                .withExpiresAt(new Date(now + EXPIRATION_TIME_IN_MILLIS))
                .withClaim("target_audience", targetURL)
                .sign(algorithm);

В частности, изменение - это вызов withClaim(), который теперь содержит URL, который я хочу вызвать. После этого код возвращает JWT, на который я надеялся, и который действительно работает, когда я вызываю защищенный URL-адрес триггера HTTP. Надеюсь, что это поможет кому-то еще.

...