将GitHub密钥注入用户仓库

huangapple go评论82阅读模式
英文:

Injecting a github secrets into the user repository

问题

我正在尝试将一个 GitHub 密钥变量注入到一个使用 OAuth 2.0 连接的用户仓库中(这意味着我有其访问令牌)。

我正在使用 Octokit 来在仓库上创建一个部署密钥(4096字节),然后使用 Octokit 文档所要求的 sodium 库来使用部署密钥对 secretValue 进行加密。经过多次尝试,我记录了以下问题:
1- 如果我将部署密钥的长度设置为32字节,GitHub 会报告密钥太短的错误。
2- 如果我将值设置为4096,GitHub 会报告密钥长度错误(应该是32字节)。
3- 如果我使用除 sodium 以外的其他库,会收到错误消息,要求使用 sodium,并且不支持其他库。

以下是您提供的代码的翻译部分:

async function generatePublicKey(): Promise<string> {
    return new Promise(
        (resolve, reject) => {
            generateKeyPair('rsa', {
                modulusLength: 4096,
                publicKeyEncoding: {
                    type: 'pkcs1',
                    format: 'pem'
                },
                privateKeyEncoding: {
                    type: 'pkcs8',
                    format: 'pem',
                }
            }, (err, publicKey, privateKey) => {
                if (err) {
                    reject(err)
                }
                else {
                    const pemKey = sshpk.parseKey(publicKey, 'pem');
                    const sshRsa = pemKey.toString('ssh');
                    console.log("ssh key is : ", sshRsa);
                    resolve(sshRsa);
                }
            });

          
    }
    );
}

async injectSecretIntoRepository(repository: string, owner: string, accessToken: string, secretName: string, secretValue: string) {
    const octokit = new Octokit({ auth: accessToken });
    console.debug("finding deploy key")
    const deployKeysResponse = await octokit.rest.repos.listDeployKeys({ owner, repo: repository });
    // Find the deploy key with write access
    let deployKey = deployKeysResponse.data.find((key) => (key.read_only === false));
    console.warn(`deploy key:  ${JSON.stringify( deployKeysResponse.data)}`)
    if (!deployKey) {
        console.warn(`Creating a new deploy key: `)
        try {
            const key = await generatePublicKey()
            deployKey = (await octokit.rest.repos.createDeployKey({
                owner,
                repo: repository,
                title: 'Componly deploy key',
                key,
                read_only: false,
            })).data;
            console.warn(`Deploy key created ${JSON.stringify(deployKey)}`)

        } catch (err) {
            console.warn(`failed to create a new deploy key`+err)
            throw new InternalServerErrorException(err)
        }
    }
        
    try {
        const sodium = await SodiumPlus.auto()

        const secretKey = CryptographyKey.from(deployKey.key)
        const nonce = await sodium.randombytes_buf(sodium.CRYPTO_BOX_NONCEBYTES)

        const encryptedValue =  await  sodium.crypto_secretbox (secretValue,nonce,secretKey)
        // Create or update the secret with the encrypted value
        await octokit.rest.actions.createOrUpdateRepoSecret({
            owner,
            repo: repository,
            secret_name: secretName,
            encrypted_value:encryptedValue.toString("base64")
        });

        console.log("Secret injected successfully.");
    } catch (err) {
        console.error("Error injecting secret:", err);
        throw new InternalServerErrorException("Failed to inject secret into the repository.");
    }
}
英文:

I'm trying to inject a github secret variable into a repository of an oauth 2.0 connected user (which means that i have its access token)

i'm using octokit to create a create a deploy key on the repository (of 4096 bytes) , then use sodium library (imposed by octokit docs) to endcrypt the secretValue with the deploy key
after a lot of trials i recorded this problems:
1- if i put the length of the deploy key 32 bytes , i'm getting an error from github saying that the key is too short ,
2- if i put a value of 4096 i'm getting an error that the key length is wrong (should be 32 bytes)
3- if i use another library different than sodium i get an error saying that i should use sodium and other libraries are not supported.

async function generatePublicKey():Promise&lt;string&gt; {
return new Promise(
(resolve, reject) =&gt; {
generateKeyPair(&#39;rsa&#39;, {
modulusLength: 4096,
publicKeyEncoding: {
type: &#39;pkcs1&#39;,
format: &#39;pem&#39;
},
privateKeyEncoding: {
type: &#39;pkcs8&#39;,
format: &#39;pem&#39;,
}
}, (err, publicKey, privateKey) =&gt; {
if(err){
reject(err)
}
else{
const pemKey = sshpk.parseKey(publicKey, &#39;pem&#39;);
const sshRsa = pemKey.toString(&#39;ssh&#39;);
console.log(&quot;ssh key is : &quot;,sshRsa);
resolve(sshRsa);
}
});
}
);
}
async injectSecretIntoRepository(repository: string, owner: string, accessToken: string, secretName: string, secretValue: string) {
const octokit = new Octokit({ auth: accessToken });
console.debug(&quot;finding deploy key&quot;)
const deployKeysResponse = await octokit.rest.repos.listDeployKeys({ owner, repo: repository });
// Find the deploy key with write access
let deployKey = deployKeysResponse.data.find((key) =&gt;( key.read_only === false ));
console.warn(`deploy key:  ${JSON.stringify( deployKeysResponse.data)}`)
if (!deployKey) {
console.warn(`Creating a new deploy key: `)
try{
const key = await generatePublicKey()
deployKey =( await octokit.rest.repos.createDeployKey({
owner,
repo: repository,
title: &#39;Componly deploy key&#39;,
key,
read_only: false,
})).data;
console.warn(`Deploy key created ${JSON.stringify(deployKey)}`)
}catch(err){
console.warn(`failed to create a new deploy key`+err)
throw new InternalServerErrorException(err)
}
}
try {
const sodium = await SodiumPlus.auto()
//
//@ts-ignore
//
const secretKey = CryptographyKey.from(deployKey.key)
const nonce = await sodium.randombytes_buf(sodium.CRYPTO_BOX_NONCEBYTES)
//@ts-ignore
const encryptedValue =  await  sodium.crypto_secretbox (secretValue,nonce,secretKey)
// Create or update the secret with the encrypted value
await octokit.rest.actions.createOrUpdateRepoSecret({
owner,
repo: repository,
secret_name: secretName,
encrypted_value:encryptedValue.toString(&quot;base64&quot;)
});
console.log(&quot;Secret injected successfully.&quot;);
} catch (err) {
console.error(&quot;Error injecting secret:&quot;, err);
throw new InternalServerErrorException(&quot;Failed to inject secret into the repository.&quot;);
}
}

答案1

得分: 1

存在冲突,因为流程的不同部分需要不同类型和长度的加密密钥,似乎在为不同目的使用相同密钥时可能会导致一些混淆。

从我所看到的情况来看,您需要两个不同的密钥:

  1. SSH 部署密钥

    • 类型:非对称(在您的情况下为 RSA)。
    • 长度:通常为 2048 位或更高(在您的情况下为 4096 位)。
    • 目的:此密钥用于验证对 GitHub 存储库的访问权限。SSH 密钥的公共部分存储在存储库的设置中,私有部分由客户端用于身份验证。
    • 代码中的使用:在您的代码中,您正在生成一个长度为 4096 位的 RSA 密钥对,并将其用作存储库的部署密钥。
  2. 用于加密密钥的密钥

    • 类型:对称(用于加密和解密的单个密钥)。
    • 长度:256 位(32 字节)。
    • 目的:此密钥用于在将其存储在 GitHub 存储库之前加密秘密。sodium 库的 crypto_secretbox 函数用于对称加密,它需要一个 32 字节的密钥
    • 代码中的使用:在您的原始代码中,您尝试使用 SSH 部署密钥(它是非对称的且长度为 4096 位)作为加密密钥。但是,这与 crypto_secretbox 函数的要求不兼容,该函数期望一个 32 字节的对称密钥。

因此:

  • SSH 部署密钥是非对称的,长度为 4096 位,适用于验证对存储库的访问权限。
  • 用于加密秘密的加密密钥必须是对称的,长度为 32 字节(256 位),以便与 sodium 库的 crypto_secretbox 函数兼容。

但是:在您的原始代码中,似乎正在将 SSH 部署密钥用作加密密钥。这会导致冲突,因为密钥的类型和长度在不同目的(SSH 身份验证与秘密加密)中不同。

为了解决这个冲突,您需要生成两个不同的密钥:

  1. 继续生成用作部署密钥的 4096 位 RSA 密钥对。
  2. 生成一个专门用于使用 sodium 的 crypto_secretbox 加密秘密值的 32 字节对称密钥。
async function injectSecretIntoRepository(repository, owner, accessToken, secretName, secretValue) {
    const octokit = new Octokit({ auth: accessToken });
    
    // ...(保持不变的用于创建/查找部署密钥的现有代码)...
    
    try {
        const sodium = await SodiumPlus.auto();

        // 生成一个 32 字节的加密密钥,用于加密秘密
        const secretKey = await sodium.randombytes_buf(32); // 32 字节 = 256 位
        
        // 加密秘密值
        const nonce = await sodium.randombytes_buf(sodium.CRYPTO_BOX_NONCEBYTES);
        const encryptedValue = await sodium.crypto_secretbox(secretValue, nonce, secretKey);

        // 创建或更新带有加密值的秘密
        await octokit.rest.actions.createOrUpdateRepoSecret({
            owner,
            repo: repository,
            secret_name: secretName,
            encrypted_value: encryptedValue.toString("base64")
        });

        console.log("Secret injected successfully.");
    } catch (err) {
        console.error("Error injecting secret:", err);
        throw new InternalServerErrorException("Failed to inject secret into the repository.");
    }
}

此修改版本的函数将 deploy 密钥(用于访问存储库)和加密密钥(用于加密秘密)的关注点分离开来。通过为加密生成一个单独的 32 字节密钥,应该可以解决您在密钥长度方面遇到的问题。

所以,如果 GitHub Action 引用 secretName,GitHub 如何解密存储的加密秘密,如果我们只是使用随机生成的 32 位密钥?
难道 deploy 密钥不是 GitHub 用来解密秘密的密钥吗?

GitHub 确实需要一种方法来在 GitHub Action 中解密秘密,当秘密在 GitHub Action 中使用时。然而,deploy 密钥并不用于此目的。部署密钥主要用于授予对存储库的访问权限,不用于加密或解密秘密。

当您使用 GitHub API 将秘密存储在 GitHub 存储库中时,您需要使用 GitHub 提供的公钥对秘密进行加密。GitHub 保留相应的私钥。当 GitHub Action 运行并引用秘密时,GitHub 使用其私钥自动解密秘密。

具体操作如下:

  • 首先,获取 GitHub 为您的存储库提供的公钥。您可以通过 GitHub API 获取此密钥(octokit.rest.actions.getRepoPublicKey)。
  • 使用获取的公钥来加密您的秘密。在这里,您必须使用非对称加密。由于您使用的是 sodium 库,您可能会使用 crypto_box_seal 函数。
  • 使用 GitHub API 存储加密的秘密。当您存储秘密时,您正在存储加密后的值。
  • 当 GitHub Action 通过名称引用秘密时,GitHub 将使用其保存
英文:

There is a conflict because different parts of the process require different types of cryptographic keys with different lengths, and it appears that there might have been some confusion in using the same key for different purposes.

From what I can see, you need two different keys:

  1. SSH Deploy Key:

    • Type: Asymmetric (RSA in your case).
    • Length: Typically 2048 bits or higher (4096 bits in your case).
    • Purpose: This key is used to authenticate access to the GitHub repository. The public part of the SSH key is stored in the repository's settings, and the private part is used by clients to authenticate themselves.
    • Usage in Code: In your code, you are generating an RSA key pair with a length of 4096 bits and using it as a deploy key for the repository.
  2. Encryption Key for Secrets:

    • Type: Symmetric (a single key used for both encryption and decryption).
    • Length: 256 bits (32 bytes).
    • Purpose: This key is used to encrypt the secret before storing it in the GitHub repository. The crypto_secretbox function from the sodium library is used for symmetric encryption, and it requires a 32-byte key.
    • Usage in Code: In your original code, you were trying to use the SSH deploy key (which is asymmetric and 4096 bits in length) as an encryption key. However, this is not compatible with the requirements of the crypto_secretbox function, which expects a 32-byte symmetric key.

So:

  • The SSH deploy key is asymmetric and has a length of 4096 bits, which is suitable for authenticating access to the repository.
  • The encryption key for encrypting secrets must be symmetric and 32 bytes long (256 bits) to be compatible with the crypto_secretbox function from the sodium library.

But: in your original code, it appears that the SSH deploy key was being used as the encryption key. This causes a conflict because the types and lengths of the keys are different for different purposes (SSH authentication vs. secret encryption).

To resolve this conflict, you need to generate two separate keys:

  1. Continue to generate the 4096-bit RSA key pair for use as a deploy key.
  2. Generate a separate 32-byte symmetric key specifically for encrypting the secret value with sodium's crypto_secretbox.
async function injectSecretIntoRepository(repository, owner, accessToken, secretName, secretValue) {
    const octokit = new Octokit({ auth: accessToken });
    
    // ... (Your existing code to create/find the deploy key, unchanged) ...
    
    try {
        const sodium = await SodiumPlus.auto();

        // Generate a 32-byte encryption key for encrypting the secret
        const secretKey = await sodium.randombytes_buf(32); // 32 bytes = 256 bits
        
        // Encrypt the secret value
        const nonce = await sodium.randombytes_buf(sodium.CRYPTO_BOX_NONCEBYTES);
        const encryptedValue = await sodium.crypto_secretbox(secretValue, nonce, secretKey);

        // Create or update the secret with the encrypted value
        await octokit.rest.actions.createOrUpdateRepoSecret({
            owner,
            repo: repository,
            secret_name: secretName,
            encrypted_value: encryptedValue.toString(&quot;base64&quot;)
        });

        console.log(&quot;Secret injected successfully.&quot;);
    } catch (err) {
        console.error(&quot;Error injecting secret:&quot;, err);
        throw new InternalServerErrorException(&quot;Failed to inject secret into the repository.&quot;);
    }
}

This modified version of your function separates the concerns of the deploy key (used for repository access) and the encryption key (used for encrypting the secret).
By generating a separate 32-byte key for encryption, it should resolve the issues you were facing with key length.


> So if a GitHub action references the secretName, how will GitHub decrypt the stored encrypted secret if we just used a random 32 secret key?
>
> Isn't the deploy key the one used by GitHub to decrypt the secret ?

GitHub does indeed need a way to decrypt the secret when it is used in a GitHub Action.
However, the deploy key is not used for this purpose. The deploy key is primarily for granting access to the repository and is not used for encrypting or decrypting secrets.

When you store a secret in a GitHub repository using the GitHub API, you are expected to encrypt the secret using a public key provided by GitHub. GitHub retains the corresponding private key. When a GitHub Action runs and references a secret, GitHub uses its private key to decrypt the secret.

Meaning:

  • First, fetch the public key that GitHub provides for your repository. You can fetch this key through the GitHub API (octokit.rest.actions.getRepoPublicKey).
  • Use the fetched public key to encrypt your secret. You must use asymmetric encryption here. Since you are using the sodium library, you will likely be using the crypto_box_seal function.
  • Store the encrypted secret using the GitHub API. When you store the secret, you are storing the encrypted value.
  • When you reference the secret in a GitHub Action, GitHub will automatically decrypt it using its private key.
async function injectSecretIntoRepository(repository, owner, accessToken, secretName, secretValue) {
    const octokit = new Octokit({ auth: accessToken });
    
    try {
        // Step 1: Fetch GitHub&#39;s public key
        const { data: { key, key_id } } = await octokit.rest.actions.getRepoPublicKey({
            owner,
            repo: repository
        });

        // Step 2: Encrypt the secret using GitHub&#39;s public key
        const sodium = await SodiumPlus.auto();
        const publicKey = CryptographyKey.from(Buffer.from(key, &#39;base64&#39;));
        const encryptedValue = await sodium.crypto_box_seal(secretValue, publicKey);

        // Step 3: Store the encrypted secret
        await octokit.rest.actions.createOrUpdateRepoSecret({
            owner,
            repo: repository,
            secret_name: secretName,
            encrypted_value: Buffer.from(encryptedValue).toString(&#39;base64&#39;),
            key_id: key_id
        });

        console.log(&quot;Secret injected successfully.&quot;);
    } catch (err) {
        console.error(&quot;Error injecting secret:&quot;, err);
        throw new InternalServerErrorException(&quot;Failed to inject secret into the repository.&quot;);
    }
}

In this modified version, the script first fetches the public key from GitHub, uses it to encrypt the secret, and then stores the encrypted secret.

When a GitHub Action references the secret by name, GitHub automatically decrypts it using the corresponding private key that it holds.

huangapple
  • 本文由 发表于 2023年6月26日 00:54:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/76551512.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定