auth details added
This commit is contained in:
parent
cb896736a1
commit
e845c68dc3
54
docs/GCloudAuthenticationArchitecture.md
Normal file
54
docs/GCloudAuthenticationArchitecture.md
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
# Google Cloud Authentication Architecture
|
||||||
|
|
||||||
|
While the [Google Cloud Setup Guide](GoogleCloudSetup.md) covers the step-by-step instructions for extracting your API credentials, this document outlines the architectural decisions, pitfalls, and lessons learned when authenticating from CyberChef's client-side environment to Google Cloud APIs.
|
||||||
|
|
||||||
|
## The CyberChef Context
|
||||||
|
CyberChef operates entirely within the browser. There is no middle-tier backend server acting as a proxy. This means that any REST request to a Cloud API originates directly from the user's browser via `fetch()` from a Web Worker.
|
||||||
|
|
||||||
|
This architecture has three major implications for authentication:
|
||||||
|
|
||||||
|
### 1. Browser-Side Secret Exposure
|
||||||
|
Any secret embedded in a CyberChef recipe URL or baked into an operation is visible to the browser, the network layer, and anyone who receives the recipe link.
|
||||||
|
|
||||||
|
* **API Keys**: An API Key is a static secret. If you use one, you **must** apply HTTP Referrer restrictions in the Google Cloud Console (as detailed in the Setup Guide) to ensure it can only be used from your specific CyberChef domain (e.g., `http://localhost:8080/`).
|
||||||
|
* **OAuth Tokens**: A temporary OAuth Token (from `gcloud auth print-access-token`) is generally preferred because it automatically expires (usually in 1 hour), mitigating the risk of credential leakage.
|
||||||
|
|
||||||
|
### 2. The Application Default Credentials (ADC) Quota Project Pitfall
|
||||||
|
When you use a short-lived OAuth token generated by an End User account (e.g., via `gcloud auth login`), the token identifies *you*, but Google occasionally needs to know which project to bill for the specific API usage.
|
||||||
|
|
||||||
|
If you attempt to call certain APIs (like `translate.googleapis.com`) using an End User OAuth Token, Google Cloud will throw a 403 error:
|
||||||
|
> *"Your application is authenticating by using local Application Default Credentials. The translate.googleapis.com API requires a quota project, which is not set by default."*
|
||||||
|
|
||||||
|
#### The Solution:
|
||||||
|
CyberChef cloud operations that accept an OAuth Token must implement an optional **Quota Project** input argument.
|
||||||
|
Under the hood, this argument must be injected into the `fetch()` request as the `x-goog-user-project` HTTP header:
|
||||||
|
```javascript
|
||||||
|
headers.set("Authorization", `Bearer ${authString}`);
|
||||||
|
if (quotaProject) {
|
||||||
|
headers.set("x-goog-user-project", quotaProject); // Injects the billing bound project
|
||||||
|
}
|
||||||
|
```
|
||||||
|
*Note: You configure your local `gcloud` environment's default quota project using `gcloud auth application-default set-quota-project <YOUR_PROJECT_ID>`.*
|
||||||
|
|
||||||
|
### 3. UI Argument Masking (The `toggleString` Pitfall)
|
||||||
|
To prevent your API Key or Token from being displayed in plain text within CyberChef's UI, Cloud operations should use the `toggleString` data type for the authentication argument. This renders the input cleanly and masks the text.
|
||||||
|
|
||||||
|
#### The Pitfall:
|
||||||
|
When converting a CyberChef argument from a standard `string` to a `toggleString`, the underlying payload structure changes. The Web Worker will no longer receive a raw string; it will receive an object:
|
||||||
|
```javascript
|
||||||
|
// A normal string returns: "AIzaSy..."
|
||||||
|
// A toggleString returns: { "string": "AIzaSy...", "option": "UTF8" }
|
||||||
|
```
|
||||||
|
If an operation's `run()` function expects a string and does not explicitly unpack `authStringObj.string`, the network request will silently fail, or the Web Worker will hang when attempting to serialize the object into an HTTP Header.
|
||||||
|
|
||||||
|
Always ensure the runtime safely parses masked keys:
|
||||||
|
```javascript
|
||||||
|
const authString = typeof authStringObj === "string" ? authStringObj : (authStringObj.string || "");
|
||||||
|
```
|
||||||
|
|
||||||
|
## Supported Methods Comparison
|
||||||
|
|
||||||
|
| Authentication Method | Security Profile | Setup Complexity | Best For |
|
||||||
|
| :--- | :--- | :--- | :--- |
|
||||||
|
| **API Key (Restricted)** | Moderate (Long-lived secret, but restricted by referrer) | Low | Permanent embedded recipes, public web hosting, and sharing recipes internally. |
|
||||||
|
| **OAuth Bearer Token** | High (Expires in 1 hr) | Medium (Requires `gcloud` CLI) | Local development, automated E2E testing, and one-off secure executions. |
|
||||||
@ -51,6 +51,11 @@ class GoogleTranslate extends Operation {
|
|||||||
"type": "toggleString",
|
"type": "toggleString",
|
||||||
"value": "",
|
"value": "",
|
||||||
"toggleValues": ["UTF8", "Latin1", "Base64", "Hex"]
|
"toggleValues": ["UTF8", "Latin1", "Base64", "Hex"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Quota Project (ADC only)",
|
||||||
|
"type": "string",
|
||||||
|
"value": ""
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -61,7 +66,7 @@ class GoogleTranslate extends Operation {
|
|||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
async run(input, args) {
|
async run(input, args) {
|
||||||
const [sourceLanguage, targetLanguage, authType, authStringObj] = args;
|
const [sourceLanguage, targetLanguage, authType, authStringObj, quotaProject] = args;
|
||||||
const authString = typeof authStringObj === "string" ? authStringObj : (authStringObj.string || "");
|
const authString = typeof authStringObj === "string" ? authStringObj : (authStringObj.string || "");
|
||||||
|
|
||||||
if (input.length === 0) return "";
|
if (input.length === 0) return "";
|
||||||
@ -75,6 +80,9 @@ class GoogleTranslate extends Operation {
|
|||||||
url += `?key=${encodeURIComponent(authString)}`;
|
url += `?key=${encodeURIComponent(authString)}`;
|
||||||
} else if (authType === "OAuth Token") {
|
} else if (authType === "OAuth Token") {
|
||||||
headers.set("Authorization", `Bearer ${authString}`);
|
headers.set("Authorization", `Bearer ${authString}`);
|
||||||
|
if (quotaProject) {
|
||||||
|
headers.set("x-goog-user-project", quotaProject);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const body = JSON.stringify({
|
const body = JSON.stringify({
|
||||||
|
|||||||
@ -28,7 +28,8 @@ module.exports = {
|
|||||||
"en",
|
"en",
|
||||||
"es",
|
"es",
|
||||||
"API Key",
|
"API Key",
|
||||||
{ option: "UTF8", string: "" }
|
{ option: "UTF8", string: "" },
|
||||||
|
""
|
||||||
]);
|
]);
|
||||||
|
|
||||||
browser.waitForElementNotVisible("#snackbar-container", 6000);
|
browser.waitForElementNotVisible("#snackbar-container", 6000);
|
||||||
@ -41,6 +42,37 @@ module.exports = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"Google Translate: Successful OAuth Token Translation": function (browser) {
|
||||||
|
let testToken = process.env.CYBERCHEF_GCP_TEST_TOKEN;
|
||||||
|
|
||||||
|
if (!testToken || testToken === "YOUR_OAUTH_TOKEN_HERE") {
|
||||||
|
try {
|
||||||
|
testToken = require('child_process').execSync('gcloud auth print-access-token', { stdio: 'pipe', encoding: 'utf-8' }).trim();
|
||||||
|
} catch (e) {
|
||||||
|
console.log("No valid CYBERCHEF_GCP_TEST_TOKEN found and gcloud failed. Skipping live API test.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
browserUtils.loadRecipe(browser, "Google Translate", "Hello", [
|
||||||
|
"en",
|
||||||
|
"es",
|
||||||
|
"OAuth Token",
|
||||||
|
{ option: "UTF8", string: testToken },
|
||||||
|
"cyberchefcloud"
|
||||||
|
]);
|
||||||
|
|
||||||
|
browser.waitForElementNotVisible("#snackbar-container", 6000);
|
||||||
|
browserUtils.bake(browser);
|
||||||
|
browser.pause(2000);
|
||||||
|
browser.saveScreenshot("tests/browser/output/success_oauth_debug.png");
|
||||||
|
browser.execute(function () {
|
||||||
|
return window.app.manager.output.outputEditorView.state.doc.toString();
|
||||||
|
}, [], function ({ value }) {
|
||||||
|
browser.assert.equal(value, "Hola");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
"Google Translate: Successful API Key Translation": function (browser) {
|
"Google Translate: Successful API Key Translation": function (browser) {
|
||||||
const testKey = process.env.CYBERCHEF_GCP_TEST_KEY;
|
const testKey = process.env.CYBERCHEF_GCP_TEST_KEY;
|
||||||
|
|
||||||
@ -53,13 +85,14 @@ module.exports = {
|
|||||||
"en",
|
"en",
|
||||||
"es",
|
"es",
|
||||||
"API Key",
|
"API Key",
|
||||||
{ option: "UTF8", string: testKey }
|
{ option: "UTF8", string: testKey },
|
||||||
|
""
|
||||||
]);
|
]);
|
||||||
|
|
||||||
browser.waitForElementNotVisible("#snackbar-container", 6000);
|
browser.waitForElementNotVisible("#snackbar-container", 6000);
|
||||||
browserUtils.bake(browser);
|
browserUtils.bake(browser);
|
||||||
browser.pause(2000);
|
browser.pause(2000);
|
||||||
browser.saveScreenshot("tests/browser/output/success_debug.png");
|
browser.saveScreenshot("tests/browser/output/success_apikey_debug.png");
|
||||||
browser.execute(function () {
|
browser.execute(function () {
|
||||||
return window.app.manager.output.outputEditorView.state.doc.toString();
|
return window.app.manager.output.outputEditorView.state.doc.toString();
|
||||||
}, [], function ({ value }) {
|
}, [], function ({ value }) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user