/**
* Feature: agentcore-mcp-migration, Property 2: Secrets Manager IAM Policy is Scoped to Specific Secret ARN
*
* Validates: Requirements 6.3
*
* For any IAM policy statement granting `secretsmanager:GetSecretValue` in the
* agent execution role, the Resource field SHALL reference the specific Secrets
* Manager secret ARN — not a wildcard (`*`) or broader pattern.
*/
import { describe, it, expect } from 'vitest';
import fc from 'fast-check';
import { readFileSync } from 'fs';
import { resolve } from 'path';
const IAM_TF_PATH = resolve(import.meta.dirname, '..', 'terraform', 'iam.tf');
/**
* Parse the iam.tf file and extract all Resource values from statements
* that grant secretsmanager:GetSecretValue.
*/
function extractSecretsManagerResources(hclContent) {
const resources = [];
// Find all policy statement blocks that contain secretsmanager:GetSecretValue
const statementRegex = /\{[^{}]*(?:\{[^{}]*\}[^{}]*)*secretsmanager:GetSecretValue[^{}]*(?:\{[^{}]*\}[^{}]*)*\}/gs;
const matches = hclContent.match(statementRegex);
if (!matches) return resources;
for (const statement of matches) {
// Extract Resource value(s) from this statement block
// Handles: quoted strings "...", Terraform references (unquoted), and arrays [...]
const resourceMatch = statement.match(/Resource\s*=\s*([\s\S]*?)(?:\n\s*\}|\n\s*\w)/);
if (!resourceMatch) continue;
const resourceValue = resourceMatch[1].trim();
if (resourceValue.startsWith('"')) {
// Single quoted string
const quoted = resourceValue.match(/"([^"]+)"/);
if (quoted) resources.push(quoted[1]);
} else if (resourceValue.startsWith('[')) {
// Array of values
const items = resourceValue.match(/"([^"]+)"/g);
if (items) {
resources.push(...items.map(item => item.replace(/"/g, '')));
}
// Also check for unquoted Terraform references in arrays
const refs = resourceValue.match(/\b(aws_\w+\.\w+\.\w+)\b/g);
if (refs) resources.push(...refs);
} else {
// Unquoted Terraform reference like aws_secretsmanager_secret.github_pat.arn
const ref = resourceValue.match(/^(aws_\w+\.\w+\.\w+)/);
if (ref) resources.push(ref[1]);
}
}
return resources;
}
/**
* Check if a resource ARN is a wildcard or overly broad pattern.
*/
function isWildcardOrBroad(resource) {
// Terraform references like aws_secretsmanager_secret.github_pat.arn are specific by definition
if (/^aws_\w+\.\w+\.\w+$/.test(resource)) return false;
if (resource === '*') return true;
if (resource.endsWith(':*')) return true;
if (resource.endsWith('/*')) return true;
// arn:aws:secretsmanager:*:*:secret:* is too broad
if (/^arn:aws:secretsmanager:\*/.test(resource)) return true;
return false;
}
describe('Feature: agentcore-mcp-migration, Property 2: Secrets Manager IAM Policy is Scoped to Specific Secret ARN', () => {
const iamContent = readFileSync(IAM_TF_PATH, 'utf-8');
it('should have at least one secretsmanager:GetSecretValue statement', () => {
expect(iamContent).toContain('secretsmanager:GetSecretValue');
});
it('should scope secretsmanager:GetSecretValue to a specific secret ARN, not a wildcard', () => {
const resources = extractSecretsManagerResources(iamContent);
expect(resources.length).toBeGreaterThan(0);
for (const resource of resources) {
expect(isWildcardOrBroad(resource)).toBe(false);
}
});
it('property: no generated wildcard-like ARN pattern should appear as a Secrets Manager resource', () => {
/**
* **Validates: Requirements 6.3**
*
* Generate various wildcard and broad ARN patterns and verify none of them
* appear as the Resource for secretsmanager:GetSecretValue in iam.tf.
*/
const wildcardPatterns = fc.oneof(
fc.constant('*'),
fc.constant('arn:aws:secretsmanager:*:*:secret:*'),
fc.constant('arn:aws:secretsmanager:us-east-1:*:secret:*'),
// Generate random broad patterns: arn:aws:secretsmanager:{region}:{account}:secret:*
fc.tuple(
fc.stringOf(fc.constantFrom('a', 'b', 'c', '-', '1', '2'), { minLength: 5, maxLength: 15 }),
fc.stringOf(fc.constantFrom('0', '1', '2', '3', '4', '5', '6', '7', '8', '9'), { minLength: 12, maxLength: 12 })
).map(([region, account]) => `arn:aws:secretsmanager:${region}:${account}:secret:*`),
// Wildcard endings
fc.constant('arn:aws:secretsmanager:us-east-1:123456789012:*'),
);
fc.assert(
fc.property(wildcardPatterns, (wildcardArn) => {
// The iam.tf should NOT contain any of these wildcard patterns as a
// Resource for the Secrets Manager statement
const resources = extractSecretsManagerResources(iamContent);
return resources.every(r => r !== wildcardArn);
}),
{ numRuns: 100 }
);
});
it('property: the Secrets Manager resource references a Terraform resource ARN, not a hardcoded value', () => {
/**
* **Validates: Requirements 6.3**
*
* The Resource field should reference a Terraform resource attribute
* (like aws_secretsmanager_secret.*.arn) rather than a hardcoded ARN string.
* This ensures the policy is always scoped to the exact secret being managed.
*/
// In HCL, a Terraform reference looks like: Resource = aws_secretsmanager_secret.xxx.arn
// (without quotes around it, since it's a reference not a string literal)
const secretsManagerStatementRegex = /secretsmanager:GetSecretValue[\s\S]*?Resource\s*=\s*(.*)/m;
const match = iamContent.match(secretsManagerStatementRegex);
expect(match).not.toBeNull();
const resourceLine = match[1].trim();
// Should be a Terraform reference (aws_secretsmanager_secret.*.arn), not a quoted string
expect(resourceLine).toMatch(/aws_secretsmanager_secret\.\w+\.arn/);
});
});