Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 39 additions & 2 deletions .github/workflows/read-only-vm-metadata-diagnostic.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ on:
required: true
default: false
type: boolean
remaining_gateway_index:
description: Optionally inspect one protected remaining target by inventory index 0 through 3
required: false
type: string

concurrency:
group: read-only-gateway-vm-metadata-${{ inputs.match_current_gateway && 'match-current' || inputs.target }}
Expand All @@ -58,6 +62,7 @@ jobs:
INSPECT_SSH_POLICY: ${{ inputs.inspect_ssh_policy }}
INSPECT_SSH_KEY_BINDING: ${{ inputs.inspect_ssh_key_binding }}
REMAINING_GATEWAYS: ${{ inputs.remaining_gateways }}
REMAINING_GATEWAY_INDEX: ${{ inputs.remaining_gateway_index }}
MATCH_TARGETS_JSON: ${{ inputs.match_current_gateway && secrets.IB_GATEWAY_TARGETS_JSON || '' }}
MATCHED_INDEX: ${{ (inputs.inspect_connections || inputs.inspect_ssh_policy || inputs.remaining_gateways) && secrets.IB_GATEWAY_MATCHED_INDEX || '' }}
LEGACY_TARGETS_JSON: ${{ !inputs.match_current_gateway && vars.IB_GATEWAY_TARGETS_JSON || '' }}
Expand All @@ -71,6 +76,13 @@ jobs:
const inspectSshPolicy = process.env.INSPECT_SSH_POLICY === "true";
const inspectSshKeyBinding = process.env.INSPECT_SSH_KEY_BINDING === "true";
const remainingGateways = process.env.REMAINING_GATEWAYS === "true";
const remainingGatewayIndex = process.env.REMAINING_GATEWAY_INDEX || "";
if (remainingGatewayIndex && (!/^[0-3]$/.test(remainingGatewayIndex)
|| !matchMode || !remainingGateways || !inspectConnections
|| inspectSshPolicy || inspectSshKeyBinding)) {
core.setFailed("Selected remaining gateway index requires protected passive connection inspection");
return;
}
if ((inspectConnections || inspectSshPolicy || inspectSshKeyBinding) && !matchMode) {
core.setFailed("Passive inspection requires protected current-gateway matching");
return;
Expand Down Expand Up @@ -133,7 +145,19 @@ jobs:
const remainingTargets = targets
.map((target, target_index) => ({ target, target_index }))
.filter(({ target_index }) => target_index !== matchedIndex);
const invalidTarget = remainingTargets.some(({ target }) => {
const requestedIndex = remainingGatewayIndex === "" ? undefined : Number(remainingGatewayIndex);
if (requestedIndex !== undefined && requestedIndex === matchedIndex) {
core.setFailed("Selected gateway index is not an eligible remaining target");
return;
}
const selectedTargets = requestedIndex === undefined
? remainingTargets
: remainingTargets.filter(({ target_index }) => target_index === requestedIndex);
if (requestedIndex !== undefined && selectedTargets.length !== 1) {
core.setFailed("Selected gateway index is not an eligible remaining target");
return;
}
const invalidTarget = selectedTargets.some(({ target }) => {
if (!/^[a-z][a-z0-9-]{4,28}[a-z0-9]$/.test(target.gcp_project_id) ||
!/^[a-z]([-a-z0-9]*[a-z0-9])?$/.test(target.gce_instance_name) ||
!/^[a-z]+(?:-[a-z]+)*[0-9]+-[a-z]$/.test(target.gce_zone)) return true;
Expand All @@ -151,7 +175,7 @@ jobs:
: "Protected remaining target metadata configuration is incomplete");
return;
}
core.setOutput("matrix", JSON.stringify({include: remainingTargets.map(({ target_index }) => ({
core.setOutput("matrix", JSON.stringify({include: selectedTargets.map(({ target_index }) => ({
target_index,
inspect_connections: inspectConnections,
inspect_ssh_policy: false,
Expand Down Expand Up @@ -223,6 +247,7 @@ jobs:
INSPECT_SSH_POLICY: ${{ inputs.inspect_ssh_policy }}
INSPECT_SSH_KEY_BINDING: ${{ inputs.inspect_ssh_key_binding }}
REMAINING_GATEWAYS: ${{ inputs.remaining_gateways }}
REMAINING_GATEWAY_INDEX: ${{ inputs.remaining_gateway_index }}
MATCH_TARGETS_JSON: ${{ inputs.match_current_gateway && secrets.IB_GATEWAY_TARGETS_JSON || '' }}
MATCHED_INDEX: ${{ (inputs.inspect_connections || inputs.inspect_ssh_policy || inputs.remaining_gateways) && secrets.IB_GATEWAY_MATCHED_INDEX || '' }}
LEGACY_TARGETS_JSON: ${{ !inputs.match_current_gateway && vars.IB_GATEWAY_TARGETS_JSON || '' }}
Expand All @@ -238,6 +263,13 @@ jobs:
const inspectSshPolicy = process.env.INSPECT_SSH_POLICY === "true";
const inspectSshKeyBinding = process.env.INSPECT_SSH_KEY_BINDING === "true";
const remainingGateways = process.env.REMAINING_GATEWAYS === "true";
const remainingGatewayIndex = process.env.REMAINING_GATEWAY_INDEX || "";
if (remainingGatewayIndex && (!/^[0-3]$/.test(remainingGatewayIndex)
|| !matchMode || !remainingGateways || !inspectConnections
|| inspectSshPolicy || inspectSshKeyBinding)) {
core.setFailed("Selected remaining gateway index requires protected passive connection inspection");
return;
}
if (remainingGateways && (!matchMode || inspectSshPolicy || inspectSshKeyBinding)) {
core.setFailed("Remaining gateway inspection requires protected metadata or passive-connection mode");
return;
Expand Down Expand Up @@ -291,6 +323,11 @@ jobs:
core.setFailed("Resolved gateway target is not an eligible remaining target");
return;
}
if (remainingGatewayIndex !== "" && (Number(remainingGatewayIndex) === matchedIndex
|| targetIndex !== Number(remainingGatewayIndex))) {
core.setFailed("Resolved gateway target does not match selected remaining index");
return;
}
target = targets[targetIndex];
if (remainingGateways &&
(!/^[a-z][a-z0-9-]{4,28}[a-z0-9]$/.test(String(target?.gcp_project_id || "")) ||
Expand Down
78 changes: 76 additions & 2 deletions tests/test_gateway_workflow_resolve.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ for (const line of metadataBlock.slice(metadataScriptStart + 'script: |'.length)
const metadataScript = metadataScriptLines.join('\n');
assert.ok(workflow.includes(' resolve:\n runs-on: ubuntu-latest\n permissions:\n contents: read\n'));
assert.match(resolveBlock, /uses: actions\/checkout@v6\n\s+with:\n\s+persist-credentials: false/);
assert.match(workflow, /remaining_gateway_index:[\s\S]*?required: false[\s\S]*?type: string/);
assert.match(workflow, /Selected remaining gateway index requires protected passive connection inspection/);
const ordinarySetup = workflow.slice(workflow.indexOf('- name: Set up gcloud\n'), workflow.indexOf('- name: Set up gcloud for passive inspection\n'));
assert.match(ordinarySetup, /if: \$\{\{ !inputs\.inspect_connections && !inputs\.inspect_ssh_policy && !inputs\.remaining_gateways \}\}/);
assert.doesNotMatch(ordinarySetup, /NODE_OPTIONS/);
Expand All @@ -53,7 +55,7 @@ function runResolve(envValues) {
const originalEnv = { ...process.env };
for (const key of [
'GITHUB_WORKSPACE', 'MATCH_CURRENT_GATEWAY', 'INSPECT_CONNECTIONS', 'INSPECT_SSH_POLICY', 'MATCH_TARGETS_JSON',
'INSPECT_SSH_KEY_BINDING', 'REMAINING_GATEWAYS', 'MATCHED_INDEX', 'LEGACY_TARGETS_JSON', 'SELECTED_TARGET',
'INSPECT_SSH_KEY_BINDING', 'REMAINING_GATEWAYS', 'REMAINING_GATEWAY_INDEX', 'MATCHED_INDEX', 'LEGACY_TARGETS_JSON', 'SELECTED_TARGET',
]) delete process.env[key];
Object.assign(process.env, { GITHUB_WORKSPACE: repoRoot }, envValues);
const result = { outputs: {}, failure: null };
Expand All @@ -76,7 +78,7 @@ function runMetadataResolve(envValues) {
const originalEnv = { ...process.env };
for (const key of [
'GITHUB_WORKSPACE', 'MATCH_CURRENT_GATEWAY', 'INSPECT_CONNECTIONS', 'INSPECT_SSH_POLICY',
'INSPECT_SSH_KEY_BINDING', 'REMAINING_GATEWAYS', 'MATCH_TARGETS_JSON', 'MATCHED_INDEX',
'INSPECT_SSH_KEY_BINDING', 'REMAINING_GATEWAYS', 'REMAINING_GATEWAY_INDEX', 'MATCH_TARGETS_JSON', 'MATCHED_INDEX',
'LEGACY_TARGETS_JSON', 'SELECTED_TARGET', 'TARGET_INDEX', 'TARGET_DIGEST',
]) delete process.env[key];
Object.assign(process.env, { GITHUB_WORKSPACE: repoRoot }, envValues);
Expand Down Expand Up @@ -175,6 +177,61 @@ assert.deepEqual(JSON.parse(remainingConnections.outputs.matrix).include, [
{ target_index: 3, inspect_connections: true, inspect_ssh_policy: false },
]);

const selectedRemainingConnection = runResolve({
MATCH_CURRENT_GATEWAY: 'true',
INSPECT_CONNECTIONS: 'true',
INSPECT_SSH_POLICY: 'false',
INSPECT_SSH_KEY_BINDING: 'false',
REMAINING_GATEWAYS: 'true',
REMAINING_GATEWAY_INDEX: '1',
MATCH_TARGETS_JSON: JSON.stringify(targets),
MATCHED_INDEX: '{"target_index":2}',
});
assert.equal(selectedRemainingConnection.failure, null);
assert.deepEqual(JSON.parse(selectedRemainingConnection.outputs.matrix).include, [
{ target_index: 1, inspect_connections: true, inspect_ssh_policy: false },
]);

for (const invalidIndex of ['00', '4', '-1', '1.0', ' bad ']) {
const rejected = runResolve({
MATCH_CURRENT_GATEWAY: 'true',
INSPECT_CONNECTIONS: 'true',
INSPECT_SSH_POLICY: 'false',
INSPECT_SSH_KEY_BINDING: 'false',
REMAINING_GATEWAYS: 'true',
REMAINING_GATEWAY_INDEX: invalidIndex,
MATCH_TARGETS_JSON: JSON.stringify(targets),
MATCHED_INDEX: '{"target_index":2}',
});
assert.equal(rejected.failure, 'Selected remaining gateway index requires protected passive connection inspection');
assert.equal(rejected.outputs.matrix, undefined);
}

for (const invalidMode of [
{ REMAINING_GATEWAYS: 'true', MATCH_CURRENT_GATEWAY: 'true', INSPECT_CONNECTIONS: 'false' },
{ REMAINING_GATEWAYS: 'false', MATCH_CURRENT_GATEWAY: 'true', INSPECT_CONNECTIONS: 'true' },
{ REMAINING_GATEWAYS: 'true', MATCH_CURRENT_GATEWAY: 'false', INSPECT_CONNECTIONS: 'true' },
]) {
const rejected = runResolve({
...invalidMode,
INSPECT_SSH_POLICY: 'false',
INSPECT_SSH_KEY_BINDING: 'false',
REMAINING_GATEWAY_INDEX: '1',
MATCH_TARGETS_JSON: JSON.stringify(targets),
MATCHED_INDEX: '{"target_index":2}',
});
assert.equal(rejected.failure, 'Selected remaining gateway index requires protected passive connection inspection');
assert.equal(rejected.outputs.matrix, undefined);
}

const selectedPrimary = runResolve({
MATCH_CURRENT_GATEWAY: 'true', INSPECT_CONNECTIONS: 'true', INSPECT_SSH_POLICY: 'false',
INSPECT_SSH_KEY_BINDING: 'false', REMAINING_GATEWAYS: 'true', REMAINING_GATEWAY_INDEX: '2',
MATCH_TARGETS_JSON: JSON.stringify(targets), MATCHED_INDEX: '{"target_index":2}',
});
assert.equal(selectedPrimary.failure, 'Selected gateway index is not an eligible remaining target');
assert.equal(selectedPrimary.outputs.matrix, undefined);

const missingRemainingConnectionConfig = runResolve({
MATCH_CURRENT_GATEWAY: 'true',
INSPECT_CONNECTIONS: 'true',
Expand Down Expand Up @@ -217,6 +274,7 @@ const combinedDiagnose = runMetadataResolve({
INSPECT_CONNECTIONS: 'true',
INSPECT_SSH_POLICY: 'false',
REMAINING_GATEWAYS: 'true',
REMAINING_GATEWAY_INDEX: '1',
MATCH_TARGETS_JSON: JSON.stringify(targets),
MATCHED_INDEX: '{"target_index":2}',
TARGET_INDEX: '1',
Expand All @@ -229,6 +287,22 @@ assert.equal(combinedDiagnose.outputs.gcp_secret_project_id, 'mock-project-1');
assert.equal(combinedDiagnose.outputs.gateway_ip_expected_host, undefined);
assert.ok(combinedDiagnose.secrets.includes('mock-ssh-key'));

const rehydratedWrongIndex = runMetadataResolve({
MATCH_CURRENT_GATEWAY: 'true', INSPECT_CONNECTIONS: 'true', INSPECT_SSH_POLICY: 'false',
INSPECT_SSH_KEY_BINDING: 'false', REMAINING_GATEWAYS: 'true', REMAINING_GATEWAY_INDEX: '1',
MATCH_TARGETS_JSON: JSON.stringify(targets), MATCHED_INDEX: '{"target_index":2}', TARGET_INDEX: '0',
});
assert.equal(rehydratedWrongIndex.failure, 'Resolved gateway target does not match selected remaining index');
assert.equal(rehydratedWrongIndex.exports.GCE_USER, undefined);

const rehydratedMalformedIndex = runMetadataResolve({
MATCH_CURRENT_GATEWAY: 'true', INSPECT_CONNECTIONS: 'true', INSPECT_SSH_POLICY: 'false',
INSPECT_SSH_KEY_BINDING: 'false', REMAINING_GATEWAYS: 'true', REMAINING_GATEWAY_INDEX: '01',
MATCH_TARGETS_JSON: JSON.stringify(targets), MATCHED_INDEX: '{"target_index":2}', TARGET_INDEX: '1',
});
assert.equal(rehydratedMalformedIndex.failure, 'Selected remaining gateway index requires protected passive connection inspection');
assert.equal(rehydratedMalformedIndex.exports.GCE_USER, undefined);

const combinedMatchedDiagnose = runMetadataResolve({
MATCH_CURRENT_GATEWAY: 'true',
INSPECT_CONNECTIONS: 'true',
Expand Down
6 changes: 6 additions & 0 deletions tests/test_read_only_vm_metadata_diagnostic.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ grep -Fq 'name: Read-Only Gateway VM Metadata Diagnostic' "$workflow_file"
grep -Fq 'workflow_dispatch:' "$workflow_file"
grep -Fq 'match_current_gateway:' "$workflow_file"
grep -Fq 'remaining_gateways:' "$workflow_file"
grep -Fq 'remaining_gateway_index:' "$workflow_file"
grep -Fq 'inspect_ssh_policy:' "$workflow_file"
grep -A3 -Fq 'match_current_gateway:' "$workflow_file"
grep -Fq 'default: false' "$workflow_file"
Expand Down Expand Up @@ -55,6 +56,11 @@ grep -Fq 'GATEWAY_VM_DIAGNOSTIC_TARGET_INDEX=' "$workflow_file"
grep -Fq 'inputs.remaining_gateways' "$workflow_file"
grep -Fq 'scripts/match_gateway_metadata.py' "$workflow_file"
grep -Fq 'targets.map((_, target_index) => ({target_index}))' "$workflow_file"
grep -Fq 'Selected remaining gateway index requires protected passive connection inspection' "$workflow_file"
grep -Fq 'Selected gateway index is not an eligible remaining target' "$workflow_file"
grep -Fq 'Resolved gateway target does not match selected remaining index' "$workflow_file"
grep -Fq 'REMAINING_GATEWAY_INDEX: ${{ inputs.remaining_gateway_index }}' "$workflow_file"
test "$(grep -Fc 'REMAINING_GATEWAY_INDEX: ${{ inputs.remaining_gateway_index }}' "$workflow_file")" -eq 2
test "$(grep -Fc 'gcloud compute instances describe' "$workflow_file")" -eq 2
grep -Fq 'gcloud compute project-info describe' "$workflow_file"
grep -Fq 'scripts/parse_gateway_ssh_policy.py' "$workflow_file"
Expand Down
Loading