stack secrets from external providers (1Password, HashiCorp Vault, Infisical and Doppler)
This commit is contained in:
28 files changed
+667
-100
No files matched your search
@@ -70,6 +70,17 @@ export const POST: RequestHandler = async (event) => {
|
||||
return json({ error: 'secretProviderId must be a number or null' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Binding a secret provider resolves its secrets into the container at deploy;
|
||||
// require the secrets permission so a stacks-only user can't exfiltrate a
|
||||
// provider's secrets by binding it and reading the container env.
|
||||
if (
|
||||
typeof data.secretProviderId === 'number' &&
|
||||
auth.authEnabled &&
|
||||
!(await auth.can('secrets', 'view', data.environmentId || undefined))
|
||||
) {
|
||||
return json({ error: 'Permission denied: binding a secret provider requires the secrets permission' }, { status: 403 });
|
||||
}
|
||||
|
||||
// Check for name conflicts with existing stacks (regular/external/git)
|
||||
const existing = await getStackSource(trimmedStackName, data.environmentId || null);
|
||||
if (existing) {
|
||||
|
||||
@@ -59,6 +59,17 @@ export const PUT: RequestHandler = async (event) => {
|
||||
return json({ error: 'secretProviderId must be a number or null' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Binding a secret provider resolves its secrets into the container at deploy;
|
||||
// require the secrets permission so a stacks-only user can't exfiltrate a
|
||||
// provider's secrets by binding it and reading the container env.
|
||||
if (
|
||||
typeof data.secretProviderId === 'number' &&
|
||||
auth.authEnabled &&
|
||||
!(await auth.can('secrets', 'view', existing.environmentId || undefined))
|
||||
) {
|
||||
return json({ error: 'Permission denied: binding a secret provider requires the secrets permission' }, { status: 403 });
|
||||
}
|
||||
|
||||
// Validate stack name if it's being changed
|
||||
if (data.stackName !== undefined) {
|
||||
const trimmedStackName = data.stackName.trim();
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
deleteSecretProvider
|
||||
} from '$lib/server/db';
|
||||
import { hasProvider } from '$lib/server/secretproviders';
|
||||
import { redactProviderConfig } from '$lib/server/secretproviders/shared';
|
||||
import { authorize } from '$lib/server/authorize';
|
||||
import { auditSecretProvider } from '$lib/server/audit';
|
||||
|
||||
@@ -21,13 +22,15 @@ export const GET: RequestHandler = async ({ params, cookies }) => {
|
||||
return json({ error: 'Invalid secret provider ID' }, { status: 400 });
|
||||
}
|
||||
|
||||
const providers = await getSecretProviders();
|
||||
const provider = providers.find((p) => p.id === id);
|
||||
if (!provider) {
|
||||
// Load the full (decrypted) row so the edit form can pre-fill the NON-secret
|
||||
// coordinates (host, projectId, mount, ...). redactProviderConfig strips the
|
||||
// token before it leaves the server, so the secret never reaches the client.
|
||||
const full = await getSecretProviderById(id);
|
||||
if (!full) {
|
||||
return json({ error: 'Secret provider not found' }, { status: 404 });
|
||||
}
|
||||
// Summary only, never the decrypted config.
|
||||
return json(provider);
|
||||
const { config, ...summary } = full;
|
||||
return json({ ...summary, config: redactProviderConfig(config) });
|
||||
};
|
||||
|
||||
export const PUT: RequestHandler = async (event) => {
|
||||
|
||||
@@ -109,6 +109,17 @@ export const POST: RequestHandler = async (event) => {
|
||||
return json({ error: 'secretProviderId must be a number or null' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Binding a secret provider resolves its secrets into the container at deploy;
|
||||
// require the secrets permission so a stacks-only user can't exfiltrate a
|
||||
// provider's secrets by binding it and reading the container env.
|
||||
if (
|
||||
typeof secretProviderId === 'number' &&
|
||||
auth.authEnabled &&
|
||||
!(await auth.can('secrets', 'view', envIdNum))
|
||||
) {
|
||||
return json({ error: 'Permission denied: binding a secret provider requires the secrets permission' }, { status: 403 });
|
||||
}
|
||||
|
||||
// If start is false, only create the compose file without deploying
|
||||
if (start === false) {
|
||||
const result = await saveStackComposeFile(name, compose, true, envIdNum, {
|
||||
|
||||
Reference in new issue
Block a user