Skip to content

[BUG]: Error authenticating API requests from Octokit JS client when using private key in Azure key vault #2623

@danielhardej

Description

@danielhardej

What happened?

Note: this could be more to do with a gap in documentation, or possibly even a limitation of using Microsoft's Azure Key Vault with a GitHub app, but I feel it's still pertinent given that it is related to advice given in GitHub's documentation (See: Secure your app's credentials under Best practices for creating a GitHub App)

The problem

When running Octokit.JS in an Azure function that is part of a GitHub app, the error Error: secretOrPrivateKey must be an asymmetric key when using RS256 gets thrown when making an API request such as:

const adminMembers = await octokit.rest.orgs.listMembers({
            org: orgName,
            role: 'admin',
        });

This happens when storing the app private key used to authenticate Octokit in Azure key vault as a key or a secret, as an environment variable, or just as a text string.

This happens with the following set up:

  • GitHub app created and subscribed to Repository, Issue, and PR events
  • The app delivers a payload containing data on these events via webhook
  • Payload gets sent to an Azure function app (via the function URL), created in Node.js
  • The app's webhook payload successfully triggers the function's HTTP trigger
  • Within the function app, an Octokit instance is created and attempts to authenticate
  • API requests, such as list members or list issues, are attempted (but the errors start appearing)

Storing as a key in Azure Key Vault

The first scenario was storing the .pem file (the one downloaded from the Private keys section of the app's settings) as a key in Azure key vault, in line with the guidance in the documentation on Private keys. It can be accessed by the Azure function app from the key vault without errors.

The private key is obtained in the following way, in line with the guidance in Microsoft's documentation: Azure Key Vault Key client library for JavaScript

const { Octokit } = require("@octokit/rest");
const { createAppAuth } = require("@octokit/auth-app");
const { DefaultAzureCredential } = require("@azure/identity");
const { KeyClient } = require("@azure/keyvault-keys");

const credential = new DefaultAzureCredential();
const vaultName = process.env.KEY_VAULT_NAME;
const vaultURL = `https://${vaultName}.vault.azure.net`;

const client = new KeyClient(vaultURL, credential);
const keyName = process.env.KEY_NAME;
const keyBundle = await client.getKey(keyName);
context.log(`Key bundle: ${JSON.stringify(keyBundle.key, null, 2)}`);
const appPrivateKey = keyBundle.key.e;

Storing as a secret in Azure Key Vault

I then also tried storing the private key (the contents of the .pem file, rather than the file itself) as a secret in AKV, rather than a key, and then accessing it with:

    const { DefaultAzureCredential } = require("@azure/identity");
    const { SecretClient } = require("@azure/keyvault-secrets");

    const credential = new DefaultAzureCredential();
    const vaultName = process.env.KEY_VAULT_NAME;
    const url = `https://${vaultName}.vault.azure.net`;

    const client = new SecretClient(url, credential);
    const secretName = process.env.SECRET_NAME;
    const secret = await client.getSecret(secretName);

This ended up with the same problem: Error: secretOrPrivateKey must be an asymmetric key when using RS256.

Additionally, attempting to print the key to the console revealed that AKV provides a buffer object, rather than the key itself. This doesn't seem to be very useful, as the guidelines on Authentication in the Octokit README suggest that the private key needs to be passed as a string.

Using environment variables/plain text

Putting Azure key vault aside, I also tried:

  • Storing the private key as an environment variable
  • Passing the key in plain text in the function code

Both gave the Error: secretOrPrivateKey must be an asymmetric key when using RS256 again.

Another interesting thing

One curious thing is when the errors are thrown: no errors are thrown when a new instance of Octokit is created and authenticated as a GitHub app installation (whether that's using Azure key vault, env variables, or plain text.)

Instead, errors are only thrown at the point when an API request is make with Octokit.

The Octokit instance is created in the following way in accordance with the guidelines in the README (note the installation ID is obtained from the webhook payload):

const octokit = new Octokit({
        authStrategy: createAppAuth,
        auth: {
            appId: process.env.APP_ID,
            privateKey: appPrivateKey,
            installationId: req.body.installation.id,
        },
    });

Versions

@octokit/rest version: 20.0.2
Node version v18.12.1

Relevant log output

No response

Code of Conduct

  • I agree to follow this project's Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Labels

    Status: Up for grabsIssues that are ready to be worked on by anyoneType: BugSomething isn't working as documented

    Type

    No type

    Projects

    Status

    🔥 Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions