Skip to content
Merged
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
2 changes: 1 addition & 1 deletion libs/i18n/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@
"System integrity protection disabled": "System integrity protection disabled",
"Prevents this workload from modifying critical host operating system files. We recommend keeping this enabled to maintain system integrity.": "Prevents this workload from modifying critical host operating system files. We recommend keeping this enabled to maintain system integrity.",
"Rootless user identity": "Rootless user identity",
"By default, workloads run as the '{{ runAsUser }}' user. To specify a custom user identity, edit the application configuration via YAML or CLI.": "By default, workloads run as the '{{ runAsUser }}' user. To specify a custom user identity, edit the application configuration via YAML or CLI.",
"The recommended user identity is '{{ runAsUser }}'. To specify a custom user identity, edit the application configuration via YAML or CLI.": "The recommended user identity is '{{ runAsUser }}'. To specify a custom user identity, edit the application configuration via YAML or CLI.",
"Application {{ appNum }}": "Application {{ appNum }}",
"Application type": "Application type",
"Select an application type": "Select an application type",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { DeviceApplicationStatus } from '@flightctl/types';
import { useTranslation } from '../../../hooks/useTranslation';
import ApplicationStatus from '../../Status/ApplicationStatus';
import { getAppTypeLabel } from '../../../utils/apps';
import { RUN_AS_DEFAULT_USER } from '../../../types/deviceSpec';
import { RUN_AS_ROOT_USER } from '../../../types/deviceSpec';

type ApplicationsTableProps = {
appsStatus: DeviceApplicationStatus[];
Expand Down Expand Up @@ -45,7 +45,7 @@ const ApplicationsTable = ({ appsStatus }: ApplicationsTableProps) => {
<Td dataLabel={t('Type')}>
{app.appType ? <Label variant="outline">{getAppTypeLabel(app.appType, t)}</Label> : '-'}
</Td>
<Td dataLabel={t('Run as user')}>{app.runAs || RUN_AS_DEFAULT_USER}</Td>
<Td dataLabel={t('Run as user')}>{app.runAs || RUN_AS_ROOT_USER}</Td>
<Td dataLabel={t('Embedded')}>{app.embedded ? t('Yes') : t('No')}</Td>
</Tr>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ import {
InlineFileForm,
KubeSecretTemplate,
QuadletAppForm,
RUN_AS_DEFAULT_USER,
RUN_AS_FLIGHTCTL_USER,
RUN_AS_ROOT_USER,
SingleContainerAppForm,
SpecConfigTemplate,
SystemdUnitFormValue,
Expand Down Expand Up @@ -292,14 +293,23 @@ const haveValuesFilesChanged = (current: string[], updated: string[]): boolean =
const haveHelmValuesChanged = (current: Record<string, unknown>, updated: Record<string, unknown>): boolean =>
JSON.stringify(current) !== JSON.stringify(updated);

const hasRunAsChanged = (current: string | undefined, updated: string | undefined): boolean => {
if (!current) {
// For empty "runAs", we mark the app as changed.
// This means that we'll set the field explicitly to the default user (currently "root").
return true;
}
return current !== updated;
};

// Single container apps always have an image, and it doesn't have an inline variant
const hasContainerAppChanged = (current: ContainerApplication, updated: ContainerApplication): boolean =>
hasStringChanged(current.name, updated.name) ||
hasStringChanged(current.image, updated.image) ||
havePortsChanged(current.ports || [], updated.ports || []) ||
haveResourceLimitsChanged(current.resources?.limits, updated.resources?.limits) ||
haveEnvVarsChanged(current.envVars || {}, updated.envVars || {}) ||
hasStringChanged(current.runAs, updated.runAs, RUN_AS_DEFAULT_USER) ||
hasRunAsChanged(current.runAs, updated.runAs) ||
haveVolumesChanged(current.volumes || [], updated.volumes || []);

// Helm apps always have an image (chart), and it doesn't have an inline variant
Expand Down Expand Up @@ -346,7 +356,7 @@ const hasQuadletAppChanged = (
if (baseChanged) {
return true;
}
return hasStringChanged(current.runAs, updated.runAs, RUN_AS_DEFAULT_USER);
return hasRunAsChanged(current.runAs, updated.runAs);
};

const hasApplicationChanged = (current: ApplicationProviderSpec, updated: ApplicationProviderSpec): boolean => {
Expand Down Expand Up @@ -452,7 +462,7 @@ const toApiContainerApp = (app: SingleContainerAppForm): ContainerApplication =>
name: app.name,
image: app.image,
appType: app.appType,
runAs: app.runAs || RUN_AS_DEFAULT_USER,
runAs: app.runAs || RUN_AS_ROOT_USER,
envVars: variablesToEnvVars(app.variables || []),
volumes: formVolumesToApi(app.volumes || [], AppType.AppTypeContainer),
};
Expand Down Expand Up @@ -494,7 +504,7 @@ const toApiComposeApp = (app: ComposeAppForm): ComposeApplication => {
// Quadlet apps are currently the same as Compose apps, plus an optional "runAs" field.
const toApiQuadletApp = (app: QuadletAppForm): QuadletApplication => {
const baseApp = toApiComposeApp(app);
return { ...baseApp, appType: AppType.AppTypeQuadlet, runAs: app.runAs || RUN_AS_DEFAULT_USER };
return { ...baseApp, appType: AppType.AppTypeQuadlet, runAs: app.runAs || RUN_AS_ROOT_USER };
};

export const toApiApplication = (app: AppForm): ApplicationProviderSpec => {
Expand Down Expand Up @@ -626,6 +636,15 @@ export const getApiConfig = (ct: SpecConfigTemplate): ConfigSourceProvider => {
};
};

const getRunAsUser = (app: ContainerApplication | QuadletApplication | undefined): string => {
if (app) {
// Existing apps that don't have a "runAs" user are actually running as the "root" user.
return app.runAs || RUN_AS_ROOT_USER;
}
// For new applications, we want to promote "flightctl" as the default user.
return RUN_AS_FLIGHTCTL_USER;
};

const toContainerAppForm = (containerApp: ContainerApplication | undefined): SingleContainerAppForm => {
const ports =
containerApp?.ports?.map((portString) => {
Expand All @@ -645,7 +664,7 @@ const toContainerAppForm = (containerApp: ContainerApplication | undefined): Sin
ports,
cpuLimit: limits?.cpu || '',
memoryLimit: limits?.memory || '',
runAs: containerApp?.runAs || RUN_AS_DEFAULT_USER,
runAs: getRunAsUser(containerApp),
};
};

Expand Down Expand Up @@ -690,10 +709,11 @@ const toComposeAppForm = (app: ComposeApplication | undefined): ComposeAppForm =

const toQuadletAppForm = (app: QuadletApplication | undefined): QuadletAppForm => {
const baseApp = toComposeAppForm(app);

return {
...baseApp,
appType: AppType.AppTypeQuadlet,
runAs: app?.runAs || RUN_AS_DEFAULT_USER,
runAs: getRunAsUser(app),
};
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Content, FormGroup, FormSection, Switch } from '@patternfly/react-core'
import { useTranslation } from '../../../../hooks/useTranslation';
import TextField from '../../../form/TextField';
import { DefaultHelperText } from '../../../form/FieldHelperText';
import { RUN_AS_DEFAULT_USER, RUN_AS_ROOT_USER } from '../../../../types/deviceSpec';
import { RUN_AS_FLIGHTCTL_USER, RUN_AS_ROOT_USER } from '../../../../types/deviceSpec';

type ApplicationIntegritySettingsProps = {
index: number;
Expand All @@ -27,7 +27,7 @@ const ApplicationIntegritySettings = ({ index, isReadOnly }: ApplicationIntegrit
label={isRootless ? t('System integrity protection enabled') : t('System integrity protection disabled')}
isChecked={isRootless}
onChange={async (_, checked) => {
await setRunAs(checked ? RUN_AS_DEFAULT_USER : RUN_AS_ROOT_USER);
await setRunAs(checked ? RUN_AS_FLIGHTCTL_USER : RUN_AS_ROOT_USER);
}}
isDisabled={isReadOnly}
/>
Expand All @@ -46,13 +46,13 @@ const ApplicationIntegritySettings = ({ index, isReadOnly }: ApplicationIntegrit
<TextField
aria-label={t('Rootless user identity')}
name={`${appFieldName}.runAs`}
value={runAs || RUN_AS_DEFAULT_USER}
value={runAs}
isDisabled
readOnly
helperText={t(
"By default, workloads run as the '{{ runAsUser }}' user. To specify a custom user identity, edit the application configuration via YAML or CLI.",
"The recommended user identity is '{{ runAsUser }}'. To specify a custom user identity, edit the application configuration via YAML or CLI.",
{
runAsUser: RUN_AS_DEFAULT_USER,
runAsUser: RUN_AS_FLIGHTCTL_USER,
},
)}
/>
Expand Down
3 changes: 2 additions & 1 deletion libs/ui-components/src/types/deviceSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ import {
import { FlightCtlLabel } from './extraTypes';
import { UpdateScheduleMode } from '../utils/time';

export const RUN_AS_DEFAULT_USER = 'flightctl';
// At the moment the "root" user is the default user when no user is specified.
export const RUN_AS_ROOT_USER = 'root';
export const RUN_AS_FLIGHTCTL_USER = 'flightctl';

export enum ConfigType {
GIT = 'git',
Expand Down