771 lines
24 KiB
TypeScript
771 lines
24 KiB
TypeScript
|
|
// modules/vault/index.ts
|
||
|
|
import * as pulumi from "@pulumi/pulumi";
|
||
|
|
import * as vault from "@pulumi/vault";
|
||
|
|
import { RandomPassword } from "@pulumi/random";
|
||
|
|
|
||
|
|
import { adminPolicyContent } from "./policy";
|
||
|
|
import { Secret } from "@pulumi/vault/generic";
|
||
|
|
|
||
|
|
import {
|
||
|
|
type OlsitecProjectFeatureFlags,
|
||
|
|
type OlsitecCredentialTypes,
|
||
|
|
type GitProjectCredentials,
|
||
|
|
type OciRegistryCredentials,
|
||
|
|
type MinioBackupProjectCredentials,
|
||
|
|
} from "./olsitec-types";
|
||
|
|
|
||
|
|
interface VaultInitializationArgs {
|
||
|
|
url: pulumi.Input<string>;
|
||
|
|
shares: number;
|
||
|
|
threshold: number;
|
||
|
|
}
|
||
|
|
|
||
|
|
interface InitResponse {
|
||
|
|
keys: string[];
|
||
|
|
root_token: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
export class VaultInitialization extends pulumi.ComponentResource {
|
||
|
|
public readonly unsealKeys: pulumi.Output<string[] | undefined>;
|
||
|
|
public readonly rootToken: pulumi.Output<string>;
|
||
|
|
public readonly url: pulumi.Output<string>;
|
||
|
|
|
||
|
|
constructor(
|
||
|
|
name: string,
|
||
|
|
args: VaultInitializationArgs,
|
||
|
|
opts?: pulumi.ComponentResourceOptions,
|
||
|
|
) {
|
||
|
|
super("custom:resource:VaultInitialization", name, {}, opts);
|
||
|
|
|
||
|
|
const config = new pulumi.Config("vaultCredentials"); // Specify a namespace if needed
|
||
|
|
const storedUnsealKeys = config.getSecret("unsealKeys");
|
||
|
|
const storedRootToken = config.getSecret("rootToken");
|
||
|
|
|
||
|
|
this.url = pulumi.output(args.url);
|
||
|
|
|
||
|
|
const initResult = pulumi
|
||
|
|
.all([
|
||
|
|
args.url,
|
||
|
|
pulumi.output(args.shares),
|
||
|
|
pulumi.output(args.threshold),
|
||
|
|
storedUnsealKeys,
|
||
|
|
storedRootToken,
|
||
|
|
])
|
||
|
|
.apply(async ([url, shares, threshold, keys, token]) => {
|
||
|
|
// Check if it's a preview; skip initialization if so
|
||
|
|
if (pulumi.runtime.isDryRun()) {
|
||
|
|
pulumi.log.info("Skipping Vault initialization during preview.");
|
||
|
|
return { keys: [], root_token: "" };
|
||
|
|
}
|
||
|
|
|
||
|
|
// Implement polling to ensure Vault is ready
|
||
|
|
await VaultInitialization.waitForVault(url);
|
||
|
|
|
||
|
|
// Perform the initialization
|
||
|
|
pulumi.log.info(`Attempting to initialize Vault at ${url}/v1/sys/init`);
|
||
|
|
const response = await fetch(`${url}/v1/sys/init`, {
|
||
|
|
headers: {
|
||
|
|
"Content-Type": "application/json",
|
||
|
|
Referer: url,
|
||
|
|
"Referrer-Policy": "strict-origin-when-cross-origin",
|
||
|
|
},
|
||
|
|
body: JSON.stringify({
|
||
|
|
secret_shares: shares,
|
||
|
|
secret_threshold: threshold,
|
||
|
|
}),
|
||
|
|
method: "PUT",
|
||
|
|
});
|
||
|
|
|
||
|
|
if (!response.ok) {
|
||
|
|
const errorData = await response.json();
|
||
|
|
const errorMsg = errorData.errors
|
||
|
|
? errorData.errors.join(", ")
|
||
|
|
: response.statusText;
|
||
|
|
|
||
|
|
if (
|
||
|
|
response.status === 400 &&
|
||
|
|
errorMsg.toLowerCase().includes("already initialized")
|
||
|
|
) {
|
||
|
|
if (keys && token) {
|
||
|
|
pulumi.log.info(
|
||
|
|
"Vault is already initialized. Using stored keys and token.",
|
||
|
|
);
|
||
|
|
await this.unsealVault(url, JSON.parse(keys));
|
||
|
|
return {
|
||
|
|
keys: JSON.parse(keys), // Assuming keys are stored as a JSON string
|
||
|
|
root_token: token,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
pulumi.log.info("Vault is already initialized.");
|
||
|
|
return { keys: [], root_token: "" };
|
||
|
|
}
|
||
|
|
|
||
|
|
throw new Error(`Failed to initialize Vault: ${errorMsg}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
const result = (await response.json()) as InitResponse;
|
||
|
|
pulumi.log.info("Vault initialized successfully.");
|
||
|
|
await this.unsealVault(url, result.keys);
|
||
|
|
return {
|
||
|
|
keys: result.keys,
|
||
|
|
root_token: result.root_token,
|
||
|
|
};
|
||
|
|
});
|
||
|
|
|
||
|
|
// Assign the outputs
|
||
|
|
this.unsealKeys = initResult.apply((result) =>
|
||
|
|
pulumi.secret((result.keys as string[]) ?? []),
|
||
|
|
);
|
||
|
|
this.rootToken = initResult.apply((result) =>
|
||
|
|
pulumi.secret(result.root_token),
|
||
|
|
);
|
||
|
|
|
||
|
|
// Register outputs
|
||
|
|
this.registerOutputs({
|
||
|
|
unsealKeys: this.unsealKeys,
|
||
|
|
rootToken: this.rootToken,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Waits until the Vault service is reachable by polling the /v1/sys/init endpoint.
|
||
|
|
* Retries up to 10 times with a 2-second delay between attempts.
|
||
|
|
*/
|
||
|
|
private static async waitForVault(url: string): Promise<void> {
|
||
|
|
const maxRetries = 12;
|
||
|
|
const retryDelay = 10000; // 10 seconds
|
||
|
|
|
||
|
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||
|
|
try {
|
||
|
|
const response = await fetch(`${url}/v1/sys/init`, {
|
||
|
|
method: "GET",
|
||
|
|
});
|
||
|
|
if (response.ok || response.status === 403) {
|
||
|
|
// 403 means Vault is already initialized
|
||
|
|
pulumi.log.info("Vault is reachable and ready.");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
} catch (error: any) {
|
||
|
|
pulumi.log.info(
|
||
|
|
`Attempt ${attempt}: Failed to reach Vault - ${error.message}`,
|
||
|
|
);
|
||
|
|
// Ignore errors and retry
|
||
|
|
}
|
||
|
|
|
||
|
|
if (attempt < maxRetries) {
|
||
|
|
pulumi.log.info(
|
||
|
|
`Vault not ready, retrying in ${retryDelay / 1000} seconds...`,
|
||
|
|
);
|
||
|
|
await new Promise((res) => setTimeout(res, retryDelay));
|
||
|
|
} else {
|
||
|
|
throw new Error(
|
||
|
|
"Vault service is not reachable after multiple attempts.",
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Unseals the Vault service using the provided unseal keys.
|
||
|
|
*/
|
||
|
|
public async unsealVault(url: string, unsealKeys: string[]): Promise<void> {
|
||
|
|
if (unsealKeys.length === 0) {
|
||
|
|
throw new Error(
|
||
|
|
"Failed to unseal Vault: insufficient unseal keys provided.",
|
||
|
|
);
|
||
|
|
}
|
||
|
|
const sealStatusResponse = await fetch(`${url}/v1/sys/seal-status`, {
|
||
|
|
method: "GET",
|
||
|
|
});
|
||
|
|
const sealStatus = await sealStatusResponse.json();
|
||
|
|
if (!sealStatus.sealed) {
|
||
|
|
pulumi.log.info("Vault is already unsealed.");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
for (const key of unsealKeys) {
|
||
|
|
const unsealResponse = await fetch(`${url}/v1/sys/unseal`, {
|
||
|
|
headers: {
|
||
|
|
"Content-Type": "application/json",
|
||
|
|
},
|
||
|
|
body: JSON.stringify({ key }),
|
||
|
|
method: "PUT",
|
||
|
|
});
|
||
|
|
if (!unsealResponse.ok) {
|
||
|
|
throw new Error(`Failed to unseal Vault: ${unsealResponse.statusText}`);
|
||
|
|
}
|
||
|
|
const unsealResult = await unsealResponse.json();
|
||
|
|
if (!unsealResult.sealed) {
|
||
|
|
pulumi.log.info("Vault unsealed successfully.");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
throw new Error("Failed to unseal Vault.");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
interface VaultBootstrapArgs {
|
||
|
|
prefix: string;
|
||
|
|
url: pulumi.Input<string>;
|
||
|
|
token: pulumi.Input<string>;
|
||
|
|
userNames: string[];
|
||
|
|
secrets?: {
|
||
|
|
name: string;
|
||
|
|
path?: string;
|
||
|
|
data: pulumi.Input<{ [key: string]: any }>;
|
||
|
|
disableRead?: boolean;
|
||
|
|
}[];
|
||
|
|
}
|
||
|
|
export class VaultBootstrap extends pulumi.ComponentResource {
|
||
|
|
public readonly url: pulumi.Output<string>;
|
||
|
|
public readonly vaultMount: vault.Mount;
|
||
|
|
public readonly vaultProvider: vault.Provider;
|
||
|
|
public readonly sourceCredentialsPath: pulumi.Output<string>;
|
||
|
|
public readonly path: pulumi.Output<string>;
|
||
|
|
public readonly roleId: pulumi.Output<string>;
|
||
|
|
public readonly secretId: pulumi.Output<string>;
|
||
|
|
public readonly users: {
|
||
|
|
username: string;
|
||
|
|
password: pulumi.Output<string>;
|
||
|
|
}[];
|
||
|
|
private readonly secrets: Secret[];
|
||
|
|
constructor(
|
||
|
|
name: string,
|
||
|
|
args: VaultBootstrapArgs,
|
||
|
|
opts?: pulumi.ComponentResourceOptions,
|
||
|
|
) {
|
||
|
|
super("custom:resource:VaultBootstrap", name, {}, opts);
|
||
|
|
this.users = [];
|
||
|
|
this.url = pulumi.output(args.url);
|
||
|
|
// pulumi.output(args.token).apply((token) => {
|
||
|
|
// console.log("VaultBootstrap: token: ", token);
|
||
|
|
// pulumi.log.info(`VaultBootstrap: token: ${token}`);
|
||
|
|
// });
|
||
|
|
this.vaultProvider = new vault.Provider(
|
||
|
|
`${args.prefix}-vault-provider`,
|
||
|
|
{
|
||
|
|
address: args.url,
|
||
|
|
token: args.token,
|
||
|
|
},
|
||
|
|
{ parent: this },
|
||
|
|
);
|
||
|
|
|
||
|
|
this.vaultMount = new vault.Mount(
|
||
|
|
`${args.prefix}-vault-mount`,
|
||
|
|
{
|
||
|
|
path: args.prefix,
|
||
|
|
type: "kv",
|
||
|
|
options: {
|
||
|
|
version: "2",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
{ provider: this.vaultProvider, parent: this },
|
||
|
|
);
|
||
|
|
this.sourceCredentialsPath = this.vaultMount.path;
|
||
|
|
|
||
|
|
const sourceCredentialsSecret = new vault.generic.Secret(
|
||
|
|
`${args.prefix}-vault-source-credentials`,
|
||
|
|
{
|
||
|
|
path: `${args.prefix}/source-credentials`,
|
||
|
|
dataJson: "{}",
|
||
|
|
disableRead: true,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
provider: this.vaultProvider,
|
||
|
|
parent: this,
|
||
|
|
dependsOn: this.vaultMount,
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
this.secrets =
|
||
|
|
args.secrets?.map((secret) => {
|
||
|
|
return new vault.generic.Secret(
|
||
|
|
`${args.prefix}-vault-secret-${secret.name}`,
|
||
|
|
{
|
||
|
|
path: secret.path ? secret.path : `${args.prefix}/${secret.name}`,
|
||
|
|
dataJson: pulumi
|
||
|
|
.output(secret.data)
|
||
|
|
.apply((data) => JSON.stringify(data)),
|
||
|
|
disableRead: secret.disableRead,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
provider: this.vaultProvider,
|
||
|
|
parent: this,
|
||
|
|
},
|
||
|
|
);
|
||
|
|
}) || [];
|
||
|
|
|
||
|
|
const pulumiPolicy = new vault.Policy(
|
||
|
|
`${args.prefix}-vault-policy-pulumi`,
|
||
|
|
{
|
||
|
|
name: `pulumi-${args.prefix}-vault-policy-pulumi`,
|
||
|
|
policy: adminPolicyContent,
|
||
|
|
},
|
||
|
|
{ provider: this.vaultProvider, parent: this },
|
||
|
|
);
|
||
|
|
|
||
|
|
const pulumiAuthBackend = new vault.AuthBackend(
|
||
|
|
`${args.prefix}-vault-auth-backend-pulumi`,
|
||
|
|
{
|
||
|
|
type: "approle",
|
||
|
|
path: `pulumi-${args.prefix}-vault-auth-backend-pulumi`,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
provider: this.vaultProvider,
|
||
|
|
parent: this,
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
const pulumiAuthBackendRole = new vault.approle.AuthBackendRole(
|
||
|
|
`${args.prefix}-vault-auth-backend-role-pulumi`,
|
||
|
|
{
|
||
|
|
backend: pulumiAuthBackend.path,
|
||
|
|
roleName: `pulumi-${args.prefix}-vault-auth-backend-role-pulumi`,
|
||
|
|
tokenPolicies: [pulumiPolicy.name],
|
||
|
|
},
|
||
|
|
{ provider: this.vaultProvider, parent: this },
|
||
|
|
);
|
||
|
|
|
||
|
|
const pulumiSecretId = new vault.approle.AuthBackendRoleSecretID(
|
||
|
|
`${args.prefix}-vault-auth-backend-role-secret-id-pulumi`,
|
||
|
|
{
|
||
|
|
backend: pulumiAuthBackend.path,
|
||
|
|
roleName: pulumiAuthBackendRole.roleName,
|
||
|
|
},
|
||
|
|
{ provider: this.vaultProvider, parent: this },
|
||
|
|
);
|
||
|
|
|
||
|
|
const userpassAuth = new vault.AuthBackend(
|
||
|
|
`${args.prefix}-vault-auth-backend-userpass`,
|
||
|
|
{
|
||
|
|
type: "userpass",
|
||
|
|
path: `userpass`,
|
||
|
|
},
|
||
|
|
{ provider: this.vaultProvider, parent: this },
|
||
|
|
);
|
||
|
|
|
||
|
|
// Create the admin policy
|
||
|
|
const adminPolicy = new vault.Policy(
|
||
|
|
`${args.prefix}-vault-policy-admin`,
|
||
|
|
{
|
||
|
|
name: `pulumi-${args.prefix}-vault-policy-admin`,
|
||
|
|
policy: adminPolicyContent,
|
||
|
|
},
|
||
|
|
{ provider: this.vaultProvider, parent: this, dependsOn: userpassAuth },
|
||
|
|
);
|
||
|
|
// Create the admin user
|
||
|
|
this.users = args.userNames.map((userName) => {
|
||
|
|
const password = new RandomPassword(`${userName}-password`, {
|
||
|
|
length: 22,
|
||
|
|
special: false,
|
||
|
|
}).result;
|
||
|
|
const endpoint = new vault.generic.Endpoint(
|
||
|
|
"admin-user",
|
||
|
|
{
|
||
|
|
path: pulumi.interpolate`auth/${userpassAuth.path}/users/${userName}`,
|
||
|
|
dataJson: pulumi
|
||
|
|
.all({
|
||
|
|
policy: adminPolicy.name,
|
||
|
|
password: password,
|
||
|
|
})
|
||
|
|
.apply((data) => {
|
||
|
|
return JSON.stringify({
|
||
|
|
policies: [data.policy],
|
||
|
|
password: data.password,
|
||
|
|
});
|
||
|
|
}),
|
||
|
|
},
|
||
|
|
{
|
||
|
|
provider: this.vaultProvider,
|
||
|
|
parent: this,
|
||
|
|
},
|
||
|
|
);
|
||
|
|
return { username: userName, password };
|
||
|
|
});
|
||
|
|
|
||
|
|
this.path = pulumiAuthBackend.path;
|
||
|
|
this.roleId = pulumiAuthBackendRole.roleId;
|
||
|
|
this.secretId = pulumiSecretId.secretId;
|
||
|
|
|
||
|
|
this.registerOutputs({
|
||
|
|
path: this.path,
|
||
|
|
roleId: this.roleId,
|
||
|
|
secretId: this.secretId,
|
||
|
|
users: this.users.map((user) => ({
|
||
|
|
username: user.username,
|
||
|
|
password: pulumi.secret(user.password),
|
||
|
|
})),
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
interface VaultExternalSecretsClusterAppRoleArgs {
|
||
|
|
// url: pulumi.Input<string>;
|
||
|
|
// token: pulumi.Input<string>;
|
||
|
|
prefix: string;
|
||
|
|
vaultMountId: pulumi.Input<string>;
|
||
|
|
}
|
||
|
|
|
||
|
|
export class VaultExternalSecretsClusterAppRole extends pulumi.ComponentResource {
|
||
|
|
public readonly path: pulumi.Output<string>;
|
||
|
|
public readonly roleId: pulumi.Output<string>;
|
||
|
|
public readonly secretId: pulumi.Output<string>;
|
||
|
|
// public readonly vaultProvider: vault.Provider;
|
||
|
|
public readonly vaultMount: vault.Mount;
|
||
|
|
|
||
|
|
constructor(
|
||
|
|
name: string,
|
||
|
|
args: VaultExternalSecretsClusterAppRoleArgs,
|
||
|
|
opts?: pulumi.ResourceOptions,
|
||
|
|
) {
|
||
|
|
super("custom:resource:VaultExternalSecretsClusterAppRole", name, {}, opts);
|
||
|
|
// this.vaultProvider = new vault.Provider(
|
||
|
|
// `${args.prefix}-vault-provider`,
|
||
|
|
// {
|
||
|
|
// address: args.url,
|
||
|
|
// token: args.token,
|
||
|
|
// },
|
||
|
|
// { parent: this },
|
||
|
|
// );
|
||
|
|
this.vaultMount = vault.Mount.get(
|
||
|
|
`${args.prefix}-vault-mount`,
|
||
|
|
args.vaultMountId,
|
||
|
|
{
|
||
|
|
path: args.prefix,
|
||
|
|
type: "kv",
|
||
|
|
options: {
|
||
|
|
version: "2",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
{
|
||
|
|
// provider: this.vaultProvider,
|
||
|
|
parent: this,
|
||
|
|
},
|
||
|
|
);
|
||
|
|
// Define the policy
|
||
|
|
const paths = {
|
||
|
|
[`${args.prefix}/data`]: '["read", "list"]',
|
||
|
|
[`${args.prefix}/data/*`]: '["read", "list"]',
|
||
|
|
[`${args.prefix}/metadata`]: '["read", "list"]',
|
||
|
|
[`${args.prefix}/metadata/*`]: '["read", "list"]',
|
||
|
|
};
|
||
|
|
|
||
|
|
let policyStatements = "";
|
||
|
|
for (const [path, capabilities] of Object.entries(paths)) {
|
||
|
|
policyStatements += `
|
||
|
|
path "${path}" {
|
||
|
|
capabilities = ${capabilities}
|
||
|
|
}
|
||
|
|
`;
|
||
|
|
}
|
||
|
|
|
||
|
|
const externalSecretsPolicy = new vault.Policy(
|
||
|
|
`${args.prefix}-vault-policy-external-secrets`,
|
||
|
|
{
|
||
|
|
name: `pulumi-${args.prefix}-vault-policy-external-secrets`,
|
||
|
|
policy: policyStatements,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
// provider: this.vaultProvider,
|
||
|
|
parent: this,
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
const externalSecretsAuthBackend = new vault.AuthBackend(
|
||
|
|
`${args.prefix}-vault-auth-backend-external-secrets`,
|
||
|
|
{
|
||
|
|
type: "approle",
|
||
|
|
path: `pulumi-${args.prefix}-vault-auth-backend-external-secrets`,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
// provider: this.vaultProvider,
|
||
|
|
|
||
|
|
parent: this,
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
const externalSecretsAuthBackendRole = new vault.approle.AuthBackendRole(
|
||
|
|
`${args.prefix}-vault-auth-backend-role-external-secrets`,
|
||
|
|
{
|
||
|
|
backend: externalSecretsAuthBackend.path,
|
||
|
|
roleName: `pulumi-${args.prefix}-vault-auth-backend-role-external-secrets`,
|
||
|
|
tokenPolicies: [externalSecretsPolicy.name],
|
||
|
|
},
|
||
|
|
{
|
||
|
|
// provider: this.vaultProvider,
|
||
|
|
parent: this,
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
const externalSecretsSecretId = new vault.approle.AuthBackendRoleSecretID(
|
||
|
|
`${args.prefix}-vault-auth-backend-role-secret-id-external-secrets`,
|
||
|
|
{
|
||
|
|
backend: externalSecretsAuthBackend.path,
|
||
|
|
roleName: externalSecretsAuthBackendRole.roleName,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
// provider: this.vaultProvider,
|
||
|
|
parent: this,
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
this.path = externalSecretsAuthBackend.path;
|
||
|
|
this.roleId = externalSecretsAuthBackendRole.roleId;
|
||
|
|
this.secretId = externalSecretsSecretId.secretId;
|
||
|
|
|
||
|
|
this.registerOutputs({
|
||
|
|
path: this.path,
|
||
|
|
roleId: this.roleId,
|
||
|
|
secretId: this.secretId,
|
||
|
|
vaultMount: this.vaultMount,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
interface VaultProjectArgs {
|
||
|
|
// vaultProvider: vault.Provider;
|
||
|
|
// vaultMount: vault.Mount;
|
||
|
|
prefix: string;
|
||
|
|
projectName: string;
|
||
|
|
projectStage: string;
|
||
|
|
featureFlags: OlsitecProjectFeatureFlags[];
|
||
|
|
credentials: OlsitecCredentialTypes;
|
||
|
|
gitCredentials: GitProjectCredentials;
|
||
|
|
ociRegistryCredentials: OciRegistryCredentials;
|
||
|
|
minioBackupProjectCredentials?: MinioBackupProjectCredentials;
|
||
|
|
additionalPolicyPaths?: string[];
|
||
|
|
}
|
||
|
|
|
||
|
|
export class VaultProject extends pulumi.ComponentResource {
|
||
|
|
// public readonly vaultProvider: vault.Provider;
|
||
|
|
public readonly path: pulumi.Output<string>;
|
||
|
|
public readonly roleId: pulumi.Output<string>;
|
||
|
|
public readonly secretId: pulumi.Output<string>;
|
||
|
|
|
||
|
|
constructor(
|
||
|
|
name: string,
|
||
|
|
args: VaultProjectArgs,
|
||
|
|
opts?: pulumi.ResourceOptions,
|
||
|
|
) {
|
||
|
|
super("custom:resource:VaultProject", name, {}, opts);
|
||
|
|
// this.vaultProvider = args.vaultProvider;
|
||
|
|
|
||
|
|
// Define the policy
|
||
|
|
let paths = {
|
||
|
|
[`${args.prefix}/data/${args.projectName}/${args.projectStage}`]:
|
||
|
|
'["read", "list"]',
|
||
|
|
[`${args.prefix}/data/${args.projectName}/${args.projectStage}/*`]:
|
||
|
|
'["read", "list"]',
|
||
|
|
[`${args.prefix}/metadata/${args.projectName}/${args.projectStage}`]:
|
||
|
|
'["read", "list"]',
|
||
|
|
[`${args.prefix}/metadata/${args.projectName}/${args.projectStage}/*`]:
|
||
|
|
'["read", "list"]',
|
||
|
|
};
|
||
|
|
|
||
|
|
for (const path of args.additionalPolicyPaths ?? []) {
|
||
|
|
paths = {
|
||
|
|
...paths,
|
||
|
|
[`${args.prefix}/data/${path}`]: '["read", "list"]',
|
||
|
|
[`${args.prefix}/data/${path}/*`]: '["read", "list"]',
|
||
|
|
[`${args.prefix}/metadata/${path}`]: '["read", "list"]',
|
||
|
|
[`${args.prefix}/metadata/${path}/*`]: '["read", "list"]',
|
||
|
|
};
|
||
|
|
}
|
||
|
|
let policyStatements = "";
|
||
|
|
for (const [path, capabilities] of Object.entries(paths)) {
|
||
|
|
policyStatements += `
|
||
|
|
path "${path}" {
|
||
|
|
capabilities = ${capabilities}
|
||
|
|
}
|
||
|
|
`;
|
||
|
|
}
|
||
|
|
|
||
|
|
const externalSecretsPolicy = new vault.Policy(
|
||
|
|
`${name}-vault-policy-external-secrets`,
|
||
|
|
{
|
||
|
|
name: `pulumi-${args.prefix}-${args.projectName}-${args.projectStage}-vault-policy-external-secrets`,
|
||
|
|
policy: policyStatements,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
// provider: this.vaultProvider
|
||
|
|
parent: this,
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
const externalSecretsAuthBackend = new vault.AuthBackend(
|
||
|
|
`${name}-vault-auth-backend-external-secrets`,
|
||
|
|
{
|
||
|
|
type: "approle",
|
||
|
|
path: `pulumi-${args.prefix}-${args.projectName}-${args.projectStage}-vault-auth-backend-external-secrets`,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
// provider: this.vaultProvider
|
||
|
|
parent: this,
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
const externalSecretsAuthBackendRole = new vault.approle.AuthBackendRole(
|
||
|
|
`${name}-vault-auth-backend-role`,
|
||
|
|
{
|
||
|
|
backend: externalSecretsAuthBackend.path,
|
||
|
|
roleName: `pulumi-${args.prefix}-${args.projectName}-${args.projectStage}-vault-auth-backend-role`,
|
||
|
|
tokenPolicies: [externalSecretsPolicy.name],
|
||
|
|
},
|
||
|
|
{
|
||
|
|
// provider: this.vaultProvider
|
||
|
|
parent: this,
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
const externalSecretsAuthBackendRoleSecretId =
|
||
|
|
new vault.approle.AuthBackendRoleSecretID(
|
||
|
|
`${name}-vault-auth-backend-role-secret-id-external-secrets`,
|
||
|
|
{
|
||
|
|
backend: externalSecretsAuthBackend.path,
|
||
|
|
roleName: externalSecretsAuthBackendRole.roleName,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
// provider: this.vaultProvider
|
||
|
|
parent: this,
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
// minioBackup
|
||
|
|
if (
|
||
|
|
args.featureFlags.includes("minioBackup") &&
|
||
|
|
args.minioBackupProjectCredentials
|
||
|
|
) {
|
||
|
|
const minioBackupCredentialsSecret = new vault.generic.Secret(
|
||
|
|
`${name}-secret-backup-credentials`,
|
||
|
|
{
|
||
|
|
path: `${args.prefix}/${args.projectName}/${args.projectStage}/backup-credentials`,
|
||
|
|
dataJson: pulumi
|
||
|
|
.all({
|
||
|
|
minioBackupAccessKey:
|
||
|
|
args.minioBackupProjectCredentials.minioBackupAccessKey,
|
||
|
|
minioBackupSecretKey:
|
||
|
|
args.minioBackupProjectCredentials.minioBackupSecretKey,
|
||
|
|
minioBackupEndpoint:
|
||
|
|
args.minioBackupProjectCredentials.minioBackupEndpoint,
|
||
|
|
})
|
||
|
|
.apply((data) => JSON.stringify(data)),
|
||
|
|
},
|
||
|
|
{
|
||
|
|
// provider: this.vaultProvider
|
||
|
|
parent: this,
|
||
|
|
},
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// project-credentials
|
||
|
|
const projectCredentialsSecret = new vault.generic.Secret(
|
||
|
|
`${name}-secret-project-credentials`,
|
||
|
|
{
|
||
|
|
path: `${args.prefix}/${args.projectName}/${args.projectStage}/project-credentials`,
|
||
|
|
dataJson: "{}",
|
||
|
|
disableRead: true,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
// provider: this.vaultProvider
|
||
|
|
parent: this,
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
// service-credentials
|
||
|
|
const serviceCredentialsSecret = new vault.generic.Secret(
|
||
|
|
`${name}-secret-service-credentials`,
|
||
|
|
{
|
||
|
|
path: `${args.prefix}/${args.projectName}/${args.projectStage}/service-credentials`,
|
||
|
|
dataJson: pulumi
|
||
|
|
.all({
|
||
|
|
cockroachdbAdminUser: args.credentials.cockroachdbAdminUser,
|
||
|
|
cockroachdbAdminPassword: args.credentials.cockroachdbAdminPassword,
|
||
|
|
cockroachdbServiceUser: args.credentials.cockroachdbServiceUser,
|
||
|
|
cockroachdbServicePassword:
|
||
|
|
args.credentials.cockroachdbServicePassword,
|
||
|
|
mongodbAdminUser: args.credentials.mongodbAdminUser,
|
||
|
|
mongodbAdminPassword: args.credentials.mongodbAdminPassword,
|
||
|
|
mongodbBackupUser: args.credentials.mongodbBackupUser,
|
||
|
|
mongodbBackupPassword: args.credentials.mongodbBackupPassword,
|
||
|
|
mongodbServiceUser: args.credentials.mongodbServiceUser,
|
||
|
|
mongodbServicePassword: args.credentials.mongodbServicePassword,
|
||
|
|
mongodbKeyfile: args.credentials.mongodbKeyfile,
|
||
|
|
minioAdminUser: args.credentials.minioAdminUser,
|
||
|
|
minioAdminPassword: args.credentials.minioAdminPassword,
|
||
|
|
minioServiceUser: args.credentials.minioServiceUser,
|
||
|
|
minioServicePassword: args.credentials.minioServicePassword,
|
||
|
|
rustfsAdminUser: args.credentials.rustfsAdminUser,
|
||
|
|
rustfsAdminPassword: args.credentials.rustfsAdminPassword,
|
||
|
|
rustfsServiceUser: args.credentials.rustfsServiceUser,
|
||
|
|
rustfsServicePassword: args.credentials.rustfsServicePassword,
|
||
|
|
garageRpcSecret: args.credentials.garageRpcSecret,
|
||
|
|
garageAdminToken: args.credentials.garageAdminToken,
|
||
|
|
garageServiceKeyId: args.credentials.garageServiceKeyId,
|
||
|
|
garageServiceKeySecret: args.credentials.garageServiceKeySecret,
|
||
|
|
natsToken: args.credentials.natsToken,
|
||
|
|
grafanaAdminPassword: args.credentials.grafanaAdminPassword,
|
||
|
|
postgresUser: args.credentials.postgresUser,
|
||
|
|
postgresPassword: args.credentials.postgresPassword,
|
||
|
|
postgresServiceUser: args.credentials.postgresServiceUser,
|
||
|
|
postgresServicePassword: args.credentials.postgresServicePassword,
|
||
|
|
basicAuthUser: args.credentials.basicAuthUser,
|
||
|
|
basicAuthPassword: args.credentials.basicAuthPassword,
|
||
|
|
basicAuthHtpasswd: args.credentials.basicAuthHtpasswd,
|
||
|
|
nominatimPassword: args.credentials.nominatimPassword,
|
||
|
|
})
|
||
|
|
.apply((data) => JSON.stringify(data)),
|
||
|
|
},
|
||
|
|
{
|
||
|
|
// provider: this.vaultProvider
|
||
|
|
parent: this,
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
// git-credentials
|
||
|
|
const gitCredentialsSecret = new vault.generic.Secret(
|
||
|
|
`${name}-secret-git-credentials`,
|
||
|
|
{
|
||
|
|
path: `${args.prefix}/${args.projectName}/${args.projectStage}/git-credentials`,
|
||
|
|
dataJson: pulumi
|
||
|
|
.all({
|
||
|
|
gitArgocdUser: args.gitCredentials.gitArgocdUser,
|
||
|
|
gitArgocdPassword: args.gitCredentials.gitArgocdToken,
|
||
|
|
})
|
||
|
|
.apply((data) => JSON.stringify(data)),
|
||
|
|
},
|
||
|
|
{
|
||
|
|
// provider: this.vaultProvider
|
||
|
|
parent: this,
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
// registry-credentials
|
||
|
|
const ociRegistryCredentialsSecret = new vault.generic.Secret(
|
||
|
|
`${name}-secret-oci-registry-credentials`,
|
||
|
|
{
|
||
|
|
path: `${args.prefix}/${args.projectName}/${args.projectStage}/registry-credentials`,
|
||
|
|
dataJson: pulumi
|
||
|
|
.all({
|
||
|
|
ociRegistryAddress: args.ociRegistryCredentials.ociRegistryAddress,
|
||
|
|
ociRegistryUser: args.ociRegistryCredentials.ociRegistryUser,
|
||
|
|
ociRegistryPassword:
|
||
|
|
args.ociRegistryCredentials.ociRegistryPassword,
|
||
|
|
})
|
||
|
|
.apply((data) => JSON.stringify(data)),
|
||
|
|
},
|
||
|
|
{
|
||
|
|
// provider: this.vaultProvider
|
||
|
|
parent: this,
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
this.path = externalSecretsAuthBackend.path;
|
||
|
|
this.roleId = externalSecretsAuthBackendRole.roleId;
|
||
|
|
this.secretId = externalSecretsAuthBackendRoleSecretId.secretId;
|
||
|
|
|
||
|
|
this.registerOutputs({
|
||
|
|
path: this.path,
|
||
|
|
roleId: this.roleId,
|
||
|
|
secretId: this.secretId,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|