Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 41 additions & 11 deletions create-a-container/client/src/pages/settings/SettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ import {
PageHeader,
Spinner,
Switch,
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
useToast,
} from '@mieweb/ui';
import { Plus, Settings as SettingsIcon, Trash2 } from 'lucide-react';
Expand Down Expand Up @@ -75,7 +81,7 @@ export function SettingsPage() {
return (
<div className="flex flex-col gap-6">
<PageHeader title="Settings" icon={<SettingsIcon className="size-6" />} bordered />
<form onSubmit={handleSubmit((v) => mutation.mutate(v))} className="grid max-w-3xl gap-8">
<form onSubmit={handleSubmit((v) => mutation.mutate(v))} className="grid w-full gap-8">
<section className="grid gap-4">
<h2 className="text-lg font-semibold">Push notifications</h2>
<Switch
Expand Down Expand Up @@ -117,16 +123,40 @@ export function SettingsPage() {
</Button>
</div>
{fields.length === 0 && <p className="text-sm text-(--color-muted,#6b7280)">No defaults defined.</p>}
{fields.map((f, idx) => (
<div key={f.id} className="grid grid-cols-1 items-end gap-2 sm:grid-cols-[1fr_1fr_1fr_auto]">
<Input label="Key" hideLabel={idx !== 0} placeholder="KEY" autoCapitalize="characters" autoCorrect="off" spellCheck={false} {...register(`defaultContainerEnvVars.${idx}.key`)} />
<Input label="Value" hideLabel={idx !== 0} placeholder="value" autoCorrect="off" spellCheck={false} {...register(`defaultContainerEnvVars.${idx}.value`)} />
<Input label="Description" hideLabel={idx !== 0} placeholder="optional" {...register(`defaultContainerEnvVars.${idx}.description`)} />
<Button type="button" variant="ghost" size="sm" leftIcon={<Trash2 className="size-4" />} onClick={() => remove(idx)} aria-label="Remove variable">
<span className="sm:sr-only">Remove</span>
</Button>
</div>
))}
{fields.length > 0 && (
<Table responsive>
<TableHeader>
<TableRow>
<TableHead className="w-1/4">Key</TableHead>
<TableHead className="w-1/4">Value</TableHead>
<TableHead>Description</TableHead>
<TableHead className="w-px text-right">
<span className="sr-only">Actions</span>
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{fields.map((f, idx) => (
<TableRow key={f.id}>
<TableCell>
<Input label="Key" hideLabel placeholder="KEY" autoCapitalize="characters" autoCorrect="off" spellCheck={false} {...register(`defaultContainerEnvVars.${idx}.key`)} />
</TableCell>
<TableCell>
<Input label="Value" hideLabel placeholder="value" autoCorrect="off" spellCheck={false} {...register(`defaultContainerEnvVars.${idx}.value`)} />
</TableCell>
<TableCell>
<Input label="Description" hideLabel placeholder="optional" {...register(`defaultContainerEnvVars.${idx}.description`)} />
</TableCell>
<TableCell className="text-right align-middle">
<Button type="button" variant="ghost" size="sm" leftIcon={<Trash2 className="size-4" />} onClick={() => remove(idx)} aria-label="Remove variable">
<span className="sr-only">Remove</span>
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
</section>

{mutation.error && <Alert variant="danger"><AlertDescription>{(mutation.error as ApiError).message}</AlertDescription></Alert>}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const WAZUH_DEFAULTS = [
{
key: 'WAZUH_REGISTRATION_PASSWORD',
value: '',
description: 'Enrollment password for Wazuh agent registration — deleted from /etc/environment inside the container immediately after first-boot enrollment completes'
description: 'Enrollment password for Wazuh agent registration'
}
];

Expand Down
121 changes: 121 additions & 0 deletions create-a-container/seeders/20260604000000-seed-sssd-env-vars.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
'use strict';

// Variables seeded into the default_container_env_vars setting for the
// base/sssd.conf.template. Only SSSD_LDAP_URI and SSSD_LDAP_TLS_REQCERT
// carry default values; the remaining variables are intentionally left
// blank so that sssd falls back to its builtin defaults.
const SSSD_DEFAULTS = [
{
key: 'SSSD_LDAP_URI',
value: 'ldaps://ldap1:636, ldaps://ldap2:636',
description: 'Comma-separated list of LDAP server URIs sssd connects to'
},
{
key: 'SSSD_LDAP_TLS_REQCERT',
value: 'allow',
description: 'TLS certificate validation policy for LDAP connections (e.g. never, allow, try, demand)'
},
{
key: 'SSSD_LDAP_SCHEMA',
value: '',
description: 'LDAP schema type. Leave blank to use the sssd builtin default'
},
{
key: 'SSSD_LDAP_SEARCH_BASE',
value: '',
description: 'Base DN for LDAP searches. Leave blank to use the sssd builtin default'
},
{
key: 'SSSD_LDAP_USER_SEARCH_BASE',
value: '',
description: 'Base DN for LDAP user searches. Leave blank to use the sssd builtin default'
},
{
key: 'SSSD_LDAP_GROUP_SEARCH_BASE',
value: '',
description: 'Base DN for LDAP group searches. Leave blank to use the sssd builtin default'
},
{
key: 'SSSD_LDAP_DEFAULT_BIND_DN',
value: '',
description: 'DN used to bind to the LDAP server. Leave blank to use the sssd builtin default'
},
{
key: 'SSSD_DEFAULT_AUTHTOK_TYPE',
value: '',
description: 'Type of the LDAP bind authentication token. Leave blank to use the sssd builtin default'
},
{
key: 'SSSD_DEFAULT_AUTHTOK',
value: '',
description: 'LDAP bind authentication token. Leave blank to use the sssd builtin default'
}
];

/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up(queryInterface) {
const [rows] = await queryInterface.sequelize.query(
`SELECT value FROM "Settings" WHERE key = 'default_container_env_vars'`
);

let existing = [];
if (rows.length > 0) {
try {
const parsed = JSON.parse(rows[0].value);
if (Array.isArray(parsed)) {
existing = parsed;
} else if (typeof parsed === 'object' && parsed !== null) {
// Migrate from old flat-object format {KEY: value} to array format
existing = Object.entries(parsed).map(([key, value]) => ({ key, value, description: '' }));
}
} catch (_) {
existing = [];
}
}

const existingKeys = new Set(existing.map(e => e.key));
const toAdd = SSSD_DEFAULTS.filter(e => !existingKeys.has(e.key));
if (toAdd.length === 0) return; // all keys already present

const merged = [...existing, ...toAdd];
const now = new Date();

if (rows.length > 0) {
await queryInterface.sequelize.query(
`UPDATE "Settings" SET value = :value, "updatedAt" = :now WHERE key = 'default_container_env_vars'`,
{ replacements: { value: JSON.stringify(merged), now } }
);
} else {
await queryInterface.bulkInsert('Settings', [{
key: 'default_container_env_vars',
value: JSON.stringify(merged),
createdAt: now,
updatedAt: now
}]);
}
},

async down(queryInterface) {
const [rows] = await queryInterface.sequelize.query(
`SELECT value FROM "Settings" WHERE key = 'default_container_env_vars'`
);
if (rows.length === 0) return;

let existing = [];
try {
const parsed = JSON.parse(rows[0].value);
existing = Array.isArray(parsed) ? parsed : [];
} catch (_) {
return;
}

const keysToRemove = new Set(SSSD_DEFAULTS.map(e => e.key));
const reverted = existing.filter(e => !keysToRemove.has(e.key));

await queryInterface.sequelize.query(
`UPDATE "Settings" SET value = :value, "updatedAt" = :now WHERE key = 'default_container_env_vars'`,
{ replacements: { value: JSON.stringify(reverted), now: new Date() } }
);
}
};
6 changes: 6 additions & 0 deletions images/base/50-sssd-conf-template.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[Unit]
ConditionPathExists=|/etc/sssd/sssd.conf.template

[Service]
EnvironmentFile=-/etc/environment
ExecStartPre=+-/bin/sh -c "umask 0226 && /bin/envsubst </etc/sssd/sssd.conf.template >/etc/sssd/sssd.conf"
5 changes: 3 additions & 2 deletions images/base/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ FROM debian:13 AS builder
RUN apt-get update && apt-get install -y \
curl tar zstd
ARG URL=http://download.proxmox.com/images/system/debian-13-standard_13.1-2_amd64.tar.zst
RUN mkdir /rootfs && curl "$URL" | tar --zstd -x -C /rootfs
RUN mkdir /rootfs && curl -fsSL --retry 10 "$URL" | tar --zstd -x -C /rootfs

# Stage 2 of the build uses the root filesystem built in stage 1. The rest of
# the Dockerfile builds from there.
Expand All @@ -23,7 +23,8 @@ RUN apt-get update && \
pam-auth-update --enable mkhomedir && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
COPY --chmod=0440 sssd.conf /etc/sssd/sssd.conf
COPY --chmod=0440 sssd.conf.template /etc/sssd/sssd.conf.template
COPY --chmod=0440 50-sssd-conf-template.conf /etc/systemd/system/sssd.service.d/50-sssd-conf-template.conf
COPY --chmod=0440 ldapusers /etc/sudoers.d/ldapusers
COPY --chmod=0644 ldap.conf /etc/ldap/ldap.conf
COPY --chmod=0755 git-identity.sh /etc/profile.d/git-identity.sh
Expand Down
15 changes: 0 additions & 15 deletions images/base/sssd.conf

This file was deleted.

24 changes: 24 additions & 0 deletions images/base/sssd.conf.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[sssd]
domains = default

[domain/default]
id_provider = ldap
auth_provider = ldap
ldap_uri = ${SSSD_LDAP_URI}
ldap_tls_reqcert = ${SSSD_LDAP_TLS_REQCERT}

ldap_schema = ${SSSD_LDAP_SCHEMA}
ldap_search_base = ${SSSD_LDAP_SEARCH_BASE}
ldap_user_search_base = ${SSSD_LDAP_USER_SEARCH_BASE}
ldap_group_search_base = ${SSSD_LDAP_GROUP_SEARCH_BASE}

# Map LDAP cn attribute to the NSS gecos field so that tools like getent,
# finger, and the git-identity profile script can read the user's full name.
ldap_user_gecos = cn

ldap_default_bind_dn = ${SSSD_LDAP_DEFAULT_BIND_DN}
ldap_default_authtok_type = ${SSSD_DEFAULT_AUTHTOK_TYPE}
ldap_default_authtok = ${SSSD_DEFAULT_AUTHTOK}

# set a timeout long enough for a push notification to be responded to
ldap_opt_timeout = 60
Loading