mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-23 17:33:51 +02:00
Wire provider vaults into secrets runtime
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
ef1f56ef4c
commit
57db4863ba
7 files changed
+474
-60
No files matched your search
@@ -81,6 +81,88 @@ describe("awsSecretsManagerProvider", () => {
|
||||
expect(prepared.providerVersionRef).toBe("aws-version-1");
|
||||
});
|
||||
|
||||
it("creates AWS secrets from selected provider vault config without deployment env fallback", async () => {
|
||||
delete process.env.PAPERCLIP_SECRETS_AWS_REGION;
|
||||
delete process.env.AWS_REGION;
|
||||
delete process.env.AWS_DEFAULT_REGION;
|
||||
delete process.env.PAPERCLIP_SECRETS_AWS_DEPLOYMENT_ID;
|
||||
delete process.env.PAPERCLIP_SECRETS_AWS_KMS_KEY_ID;
|
||||
|
||||
const calls: Array<{ op: string; input: Record<string, unknown> }> = [];
|
||||
const provider = createAwsSecretsManagerProvider({
|
||||
gateway: {
|
||||
async createSecret(input) {
|
||||
calls.push({ op: "createSecret", input });
|
||||
return {
|
||||
ARN: "arn:aws:secretsmanager:us-west-2:123456789012:secret:clip/prod-us-west/company-1/openai-api-key",
|
||||
VersionId: "aws-version-1",
|
||||
};
|
||||
},
|
||||
async putSecretValue(input) {
|
||||
calls.push({ op: "putSecretValue", input });
|
||||
return { ARN: String(input.SecretId), VersionId: "unused" };
|
||||
},
|
||||
async getSecretValue(input) {
|
||||
calls.push({ op: "getSecretValue", input });
|
||||
return { SecretString: "resolved-value", VersionId: "unused" };
|
||||
},
|
||||
async deleteSecret(input) {
|
||||
calls.push({ op: "deleteSecret", input });
|
||||
return {};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const providerConfig = {
|
||||
id: "vault-1",
|
||||
provider: "aws_secrets_manager" as const,
|
||||
status: "ready",
|
||||
config: {
|
||||
region: "us-west-2",
|
||||
namespace: "prod-us-west",
|
||||
secretNamePrefix: "clip",
|
||||
ownerTag: "platform",
|
||||
environmentTag: "production",
|
||||
},
|
||||
};
|
||||
|
||||
const health = await provider.healthCheck({ providerConfig });
|
||||
const prepared = await provider.createSecret({
|
||||
value: "super-secret-value",
|
||||
providerConfig,
|
||||
context: {
|
||||
companyId: "company-1",
|
||||
secretKey: "openai-api-key",
|
||||
secretName: "OpenAI API Key",
|
||||
version: 1,
|
||||
},
|
||||
});
|
||||
|
||||
expect(health.status).toBe("ok");
|
||||
expect(health.details).toMatchObject({
|
||||
region: "us-west-2",
|
||||
prefix: "clip",
|
||||
deploymentId: "prod-us-west",
|
||||
kmsKeyConfigured: false,
|
||||
});
|
||||
expect(calls).toEqual([
|
||||
expect.objectContaining({
|
||||
op: "createSecret",
|
||||
input: expect.objectContaining({
|
||||
Name: "clip/prod-us-west/company-1/openai-api-key",
|
||||
SecretString: "super-secret-value",
|
||||
Tags: expect.arrayContaining([
|
||||
{ Key: "paperclip:provider-owner", Value: "platform" },
|
||||
{ Key: "paperclip:environment", Value: "production" },
|
||||
]),
|
||||
}),
|
||||
}),
|
||||
]);
|
||||
expect(calls[0]?.input).not.toHaveProperty("KmsKeyId");
|
||||
expect(JSON.stringify(prepared)).not.toContain("super-secret-value");
|
||||
expect(prepared.externalRef).toContain("clip/prod-us-west/company-1/openai-api-key");
|
||||
});
|
||||
|
||||
it("creates new AWS secret versions against a namespace-valid existing secret reference", async () => {
|
||||
const calls: Array<{ op: string; input: Record<string, unknown> }> = [];
|
||||
const provider = createAwsSecretsManagerProvider({
|
||||
@@ -324,6 +406,22 @@ describe("awsSecretsManagerProvider", () => {
|
||||
const health = await provider.healthCheck();
|
||||
|
||||
expect(health.status).toBe("warn");
|
||||
expect(health.message).toContain("missing PAPERCLIP_SECRETS_AWS_REGION");
|
||||
expect(health.warnings).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.stringContaining("Missing required non-secret AWS provider config"),
|
||||
expect.stringContaining("AWS bootstrap credentials must be available"),
|
||||
expect.stringContaining("Do not store AWS root credentials"),
|
||||
]),
|
||||
);
|
||||
expect(health.details).toMatchObject({
|
||||
missingConfig: [
|
||||
"PAPERCLIP_SECRETS_AWS_REGION or AWS_REGION/AWS_DEFAULT_REGION",
|
||||
"PAPERCLIP_SECRETS_AWS_DEPLOYMENT_ID",
|
||||
"PAPERCLIP_SECRETS_AWS_KMS_KEY_ID",
|
||||
],
|
||||
credentialSource: "AWS SDK default credential provider chain",
|
||||
});
|
||||
await expect(
|
||||
provider.createSecret({
|
||||
value: "super-secret-value",
|
||||
|
||||
@@ -255,6 +255,72 @@ describeEmbeddedPostgres("secretService", () => {
|
||||
).rejects.toThrow(/coming soon/i);
|
||||
});
|
||||
|
||||
it("passes selected provider vault config through create, rotate, and resolve", async () => {
|
||||
const companyId = await seedCompany();
|
||||
const svc = secretService(db);
|
||||
const awsVault = await svc.createProviderConfig(companyId, {
|
||||
provider: "aws_secrets_manager",
|
||||
displayName: "AWS production",
|
||||
config: {
|
||||
region: "us-east-1",
|
||||
namespace: "prod-use1",
|
||||
secretNamePrefix: "paperclip",
|
||||
},
|
||||
});
|
||||
|
||||
const createSpy = vi.spyOn(awsSecretsManagerProvider, "createSecret").mockResolvedValue({
|
||||
material: {
|
||||
scheme: "aws_secrets_manager_v1",
|
||||
secretId: "arn:aws:secretsmanager:us-east-1:123456789012:secret:paperclip/prod-use1/company/openai-api-key",
|
||||
versionId: "aws-version-1",
|
||||
source: "managed",
|
||||
},
|
||||
valueSha256: "value-sha-1",
|
||||
fingerprintSha256: "fingerprint-sha-1",
|
||||
externalRef: "arn:aws:secretsmanager:us-east-1:123456789012:secret:paperclip/prod-use1/company/openai-api-key",
|
||||
providerVersionRef: "aws-version-1",
|
||||
});
|
||||
const createVersionSpy = vi.spyOn(awsSecretsManagerProvider, "createVersion").mockResolvedValue({
|
||||
material: {
|
||||
scheme: "aws_secrets_manager_v1",
|
||||
secretId: "arn:aws:secretsmanager:us-east-1:123456789012:secret:paperclip/prod-use1/company/openai-api-key",
|
||||
versionId: "aws-version-2",
|
||||
source: "managed",
|
||||
},
|
||||
valueSha256: "value-sha-2",
|
||||
fingerprintSha256: "fingerprint-sha-2",
|
||||
externalRef: "arn:aws:secretsmanager:us-east-1:123456789012:secret:paperclip/prod-use1/company/openai-api-key",
|
||||
providerVersionRef: "aws-version-2",
|
||||
});
|
||||
const resolveSpy = vi.spyOn(awsSecretsManagerProvider, "resolveVersion").mockResolvedValue("resolved-secret");
|
||||
|
||||
const secret = await svc.create(companyId, {
|
||||
name: `aws-managed-${randomUUID()}`,
|
||||
provider: "aws_secrets_manager",
|
||||
providerConfigId: awsVault.id,
|
||||
value: "runtime-secret",
|
||||
});
|
||||
const rotated = await svc.rotate(secret.id, { value: "rotated-runtime-secret" });
|
||||
const resolved = await svc.resolveSecretValue(companyId, rotated.id, "latest");
|
||||
|
||||
expect(resolved).toBe("resolved-secret");
|
||||
expect(createSpy).toHaveBeenCalledWith(expect.objectContaining({
|
||||
providerConfig: expect.objectContaining({
|
||||
id: awsVault.id,
|
||||
provider: "aws_secrets_manager",
|
||||
config: expect.objectContaining({ region: "us-east-1", namespace: "prod-use1" }),
|
||||
}),
|
||||
}));
|
||||
expect(createVersionSpy).toHaveBeenCalledWith(expect.objectContaining({
|
||||
providerConfig: expect.objectContaining({ id: awsVault.id }),
|
||||
}));
|
||||
expect(resolveSpy).toHaveBeenCalledWith(expect.objectContaining({
|
||||
providerConfig: expect.objectContaining({ id: awsVault.id }),
|
||||
providerVersionRef: "aws-version-2",
|
||||
}));
|
||||
expect(JSON.stringify(resolveSpy.mock.calls[0]?.[0])).not.toContain("resolved-secret");
|
||||
});
|
||||
|
||||
it("rejects externalRef overrides on managed secrets", async () => {
|
||||
const companyId = await seedCompany();
|
||||
const svc = secretService(db);
|
||||
@@ -340,6 +406,7 @@ describeEmbeddedPostgres("secretService", () => {
|
||||
version: 1,
|
||||
},
|
||||
mode: "delete",
|
||||
providerConfig: null,
|
||||
});
|
||||
expect(persisted).toBeNull();
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user