feat(bootstrap): Bun-workspace skeleton + typed config + vendored modules — T02

- Bun workspaces (packages/* + bootstrap); Pulumi nodejs runtime under
  packagemanager: bun (no npm fallback needed).
- bootstrap/config.ts: typed FoundationConfig per CONTRACT_001; loadConfig()
  fails closed, aggregating all missing+malformed keys in one error. Reads flat
  dotted keys; image digests excluded (they live in VERSIONS, D5).
- bootstrap/Pulumi.foundation.yaml: non-secret placeholders only (RFC-5737 vm.host,
  .invalid offsite); no encryptionsalt/secrets committed (D2). pulumi preview = 0
  resources under the passphrase provider via gitignored file:// state backend.
- Stage-1 vendoring: packages/pulumi-{docker,vault} as @olsitec/* (source-only,
  logic unchanged). vault's 5 type-only imports from modules/olsitec re-homed
  verbatim into pulumi-vault/olsitec-types.ts to keep the egg self-contained.

Realizes PLAN-002 §10 T02; ADR-005 / 000_TOPOLOGY.md §5 Stage-1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Andreas Niemann 2026-06-30 18:06:21 +02:00
parent edc708b826
commit 57c4eadea7
26 changed files with 2758 additions and 0 deletions

View file

@ -0,0 +1,26 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 2
[*.{diff,md}]
trim_trailing_whitespace = false
indent_size = 4
[Makefile]
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = tab
indent_size = 4

3
packages/pulumi-vault/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
/bin/
/node_modules/
*.stack*.json

View file

@ -0,0 +1,52 @@
# VENDORED — `@olsitec/pulumi-vault`
**Source (absolute path):** `/Users/andiolsi/work/olsicloud4/pulumi/modules/vault/`
**Copy date:** 2026-06-30
**Stage:** Stage-1 vendoring per [`documentation/000_TOPOLOGY.md` §5](../../documentation/000_TOPOLOGY.md).
## What this is
A verbatim copy of the olsicloud4 `modules/vault` Pulumi module — the Vault init/unseal
capture (`VaultInitialization`) and the secret-engine/AppRole bootstrap
(`VaultBootstrap`, `VaultExternalSecretsClusterAppRole`, `VaultProject`) plus the admin
policy (`policy.ts`). Core of the foundation secret layer (ADR-004, PLAN-002 §4). At
day-zero `bootstrap/` consumes it locally through the Bun workspace, not from a registry.
## What was copied
`index.ts`, `policy.ts`, `package.json`, `tsconfig.json`, `.editorconfig`, `.gitignore`.
**Not copied:** `node_modules/`, `package-lock.json` (lockfiles), `.git/`.
## Changes made vs. the source
- `package.json` `name`: `vault``@olsitec/pulumi-vault`; added `version` (`0.0.0`,
pre-publish placeholder) and `main`/`types` (`index.ts`) for Bun-workspace resolution.
- **Type-only re-home (no logic change):** the upstream `index.ts` imports five
*purely type-level* declarations from its sibling module `../../modules/olsitec`
(`OlsitecProjectFeatureFlags`, `OlsitecCredentialTypes`, `GitProjectCredentials`,
`OciRegistryCredentials`, `MinioBackupProjectCredentials`). That sibling transitively
pulls in `modules/minio`, `modules/gitlab`, and `modules/kubernetes`, none of which
belong in the foundation egg and none of which are vendored. To keep this package
self-contained, those five type declarations were copied **verbatim** into a new local
file `olsitec-types.ts`, and the one import line in `index.ts` was re-pointed from
`../../modules/olsitec` to `./olsitec-types`. This is the **only** edit to `index.ts`;
no runtime/behavioural logic changed.
- `tsconfig.json` `files`: added `policy.ts` and `olsitec-types.ts` so the package
type-checks standalone (`tsc --noEmit`).
> **Note (out of scope for T02):** `VaultProject` and `VaultBootstrap` still reference
> minio/garage/cockroach/mongo credential shapes inherited from the Layer-1 olsitec module.
> The foundation egg only needs `VaultInitialization` (init/unseal capture) + `VaultBootstrap`.
> Trimming the unused Layer-1 surface is a deliberate later refactor (000_TOPOLOGY.md §5.1
> "refactor for Layer 0"), NOT part of Stage-1 vendoring — Stage 1 preserves the source as-is.
## Lifecycle (000_TOPOLOGY.md §5)
- **Stage 1 — VENDOR (this commit):** copied here; consumed locally via Bun workspace.
- **Stage 2 — PUBLISH (later task):** CI publishes `@olsitec/pulumi-vault@<semver>` to the
foundation Forgejo npm registry once it exists.
- **Stage 3 — CONSUME (steady state):** downstream switches imports to the published package;
the old module is frozen then removed.
Do not refactor the vendored logic here beyond the type-only re-home documented above.

View file

@ -0,0 +1,770 @@
// 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,
});
}
}

View file

@ -0,0 +1,90 @@
// olsitec-types.ts
//
// VENDORING NOTE (Stage-1, 2026-06-30 — see VENDORED.md):
// The upstream olsicloud4 `modules/vault/index.ts` imports five PURELY TYPE-LEVEL
// declarations from its sibling `../../modules/olsitec`:
//
// OlsitecProjectFeatureFlags, OlsitecCredentialTypes,
// GitProjectCredentials, OciRegistryCredentials, MinioBackupProjectCredentials
//
// That sibling module transitively pulls in `modules/minio`, `modules/gitlab`,
// and `modules/kubernetes`, none of which belong in the foundation egg and none
// of which are vendored. To keep the vault module SELF-CONTAINED inside the
// foundation workspace WITHOUT changing any runtime behaviour, these five type
// declarations are copied here VERBATIM from
// `olsicloud4/pulumi/modules/olsitec/index.ts` (definitions only — no logic, no
// ComponentResource), and `index.ts`'s import is re-pointed from
// `../../modules/olsitec` to `./olsitec-types`.
//
// This is a type-only re-home; the vault module's logic is unchanged.
import * as pulumi from "@pulumi/pulumi";
export type OlsitecCredentialTypes = {
minioBackupEndpoint?: string;
minioBackupAccessKey?: pulumi.Output<string>;
minioBackupSecretKey?: pulumi.Output<string>;
cockroachdbAdminUser?: string;
cockroachdbAdminPassword?: pulumi.Output<string>;
cockroachdbServiceUser?: string;
cockroachdbServicePassword?: pulumi.Output<string>;
mongodbAdminUser?: string;
mongodbAdminPassword?: pulumi.Output<string>;
mongodbBackupUser?: string;
mongodbBackupPassword?: pulumi.Output<string>;
mongodbServiceUser?: string;
mongodbServicePassword?: pulumi.Output<string>;
mongodbKeyfile?: pulumi.Output<string>;
minioAdminUser?: string;
minioAdminPassword?: pulumi.Output<string>;
minioServiceUser?: string;
minioServicePassword?: pulumi.Output<string>;
rustfsAdminUser?: string;
rustfsAdminPassword?: pulumi.Output<string>;
rustfsServiceUser?: string;
rustfsServicePassword?: pulumi.Output<string>;
garageRpcSecret?: pulumi.Output<string>;
garageAdminToken?: pulumi.Output<string>;
garageServiceKeyId?: pulumi.Output<string>;
garageServiceKeySecret?: pulumi.Output<string>;
natsToken?: pulumi.Output<string>;
grafanaAdminPassword?: pulumi.Output<string>;
postgresUser?: string;
postgresPassword?: pulumi.Output<string>;
postgresServiceUser?: string;
postgresServicePassword?: pulumi.Output<string>;
basicAuthUser?: string;
basicAuthPassword?: pulumi.Output<string>;
basicAuthHtpasswd?: pulumi.Output<string>;
nominatimPassword?: pulumi.Output<string>;
};
export type OlsitecProjectFeatureFlags =
| "minioBackup"
| "cockroachdb"
| "vault"
| "mongodb"
| "nats"
| "minio"
| "rustfs"
| "garage"
| "grafana"
| "postgres"
| "basicAuth"
| "nominatim";
export type OciRegistryCredentials = {
ociRegistryAddress: string;
ociRegistryUser: pulumi.Input<string>;
ociRegistryPassword: pulumi.Input<string>;
};
export type GitProjectCredentials = {
gitArgocdUser: pulumi.Input<string>;
gitArgocdToken: pulumi.Input<string>;
};
export type MinioBackupProjectCredentials = {
minioBackupEndpoint: pulumi.Input<string>;
minioBackupAccessKey: pulumi.Output<string>;
minioBackupSecretKey: pulumi.Output<string>;
};

View file

@ -0,0 +1,19 @@
{
"name": "@olsitec/pulumi-vault",
"version": "0.0.0",
"main": "index.ts",
"types": "index.ts",
"devDependencies": {
"@eslint/eslintrc": "^3.1.0",
"@pulumi/eslint-plugin": "^0.2.0",
"@typescript-eslint/eslint-plugin": "^8.13.0",
"eslint-config-prettier": "^9.1.0",
"prettier": "^3.3.3",
"typescript": "^5.0.0"
},
"dependencies": {
"@pulumi/pulumi": "^3.138.0",
"@pulumi/random": "^4.16.8",
"@pulumi/vault": "^4.5.8"
}
}

View file

@ -0,0 +1,72 @@
export const adminPolicyContent = `
# Manage auth methods broadly across Vault
path "auth/*" {
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}
# Create, update, and delete auth methods
path "sys/auth/*" {
capabilities = ["create", "update", "delete", "sudo"]
}
# List auth methods
path "sys/auth" {
capabilities = ["read"]
}
# Create and manage ACL policies
path "sys/policies/acl/*" {
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}
# List ACL policies
path "sys/policies/acl" {
capabilities = ["list"]
}
# Create and manage secrets engines broadly across Vault.
path "sys/mounts/*" {
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}
# List enabled secrets engines
path "sys/mounts" {
capabilities = ["read", "list"]
}
# List, create, update, and delete key/value secrets at secret/
path "secret/*" {
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}
# Manage transit secrets engine
path "transit/*" {
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}
# Read health checks
path "sys/health" {
capabilities = ["read", "sudo"]
}
path "*/metadata" {
capabilities = ["create", "update", "read", "delete", "list"]
}
path "*/metadata/*" {
capabilities = ["create", "update", "read", "delete", "list"]
}
path "*/data" {
capabilities = ["create", "update", "read", "delete", "list"]
}
path "*/data/*" {
capabilities = ["create", "update", "read", "delete", "list"]
}
path "*" {
capabilities = ["create", "update", "read", "delete", "list"]
}
`;

View file

@ -0,0 +1,20 @@
{
"compilerOptions": {
"strict": true,
"outDir": "bin",
"target": "es2020",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"experimentalDecorators": true,
"pretty": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"forceConsistentCasingInFileNames": true
},
"files": [
"index.ts",
"policy.ts",
"olsitec-types.ts"
]
}