[SM-1927] Updating code so that if the machine acct name is corrupted data (#19898)
Some checks failed
Scan / Check PR run (push) Has been cancelled
Testing / Run typechecking (push) Has been cancelled
Testing / Run tests - ${{ matrix.test-group.name }} (map[artifact:jest-coverage-browser junit:junit-browser.xml name:Browser paths:apps/browser bitwarden_license/bit-browser]) (push) Has been cancelled
Testing / Run tests - ${{ matrix.test-group.name }} (map[artifact:jest-coverage-cli junit:junit-cli.xml name:CLI paths:apps/cli bitwarden_license/bit-cli]) (push) Has been cancelled
Testing / Run tests - ${{ matrix.test-group.name }} (map[artifact:jest-coverage-desktop junit:junit-desktop.xml name:Desktop paths:apps/desktop]) (push) Has been cancelled
Testing / Run tests - ${{ matrix.test-group.name }} (map[artifact:jest-coverage-libs junit:junit-libs.xml name:Libs paths:libs bitwarden_license/bit-common]) (push) Has been cancelled
Testing / Run tests - ${{ matrix.test-group.name }} (map[artifact:jest-coverage-web junit:junit-web.xml name:Web paths:apps/web bitwarden_license/bit-web]) (push) Has been cancelled
Testing / Run Rust tests on ${{ matrix.os }} (macos-14) (push) Has been cancelled
Testing / Run Rust tests on ${{ matrix.os }} (ubuntu-22.04) (push) Has been cancelled
Testing / Run Rust tests on ${{ matrix.os }} (windows-2022) (push) Has been cancelled
Testing / Rust Coverage (push) Has been cancelled
Scan / Checkmarx (push) Has been cancelled
Scan / Sonar (push) Has been cancelled
Testing / Upload to Codecov (push) Has been cancelled
Testing / Run tests (push) Has been cancelled

* Updating code so that if the machine acct name is corrupted data it doesn't lock users out of the list views

* Adding logging

* Fixing bugs with corrupted machine accounts preventing export
This commit is contained in:
cd-bitwarden 2026-05-26 07:56:22 -04:00 committed by GitHub
parent c6ec252e52
commit ae290cf8b5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 85 additions and 20 deletions

View File

@ -6,6 +6,7 @@ export class ServiceAccountView {
name: string;
creationDate: string;
revisionDate: string;
decryptionError: boolean = false;
}
export class ServiceAccountSecretsDetailsView extends ServiceAccountView {

View File

@ -7,8 +7,12 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import {
DECRYPT_ERROR,
EncString,
} from "@bitwarden/common/key-management/crypto/models/enc-string";
import { ListResponse } from "@bitwarden/common/models/response/list.response";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { OrganizationId } from "@bitwarden/common/types/guid";
import { KeyService } from "@bitwarden/key-management";
@ -38,6 +42,7 @@ export class ServiceAccountService {
private apiService: ApiService,
private encryptService: EncryptService,
private accountService: AccountService,
private logService: LogService,
) {}
private getOrganizationKey$(organizationId: string) {
@ -167,12 +172,18 @@ export class ServiceAccountService {
serviceAccountView.organizationId = serviceAccountResponse.organizationId;
serviceAccountView.creationDate = serviceAccountResponse.creationDate;
serviceAccountView.revisionDate = serviceAccountResponse.revisionDate;
serviceAccountView.name = serviceAccountResponse.name
? await this.encryptService.decryptString(
new EncString(serviceAccountResponse.name),
organizationKey,
)
: null;
if (serviceAccountResponse.name) {
const name = await this.decryptField(
new EncString(serviceAccountResponse.name),
organizationKey,
);
serviceAccountView.name = name.value;
serviceAccountView.decryptionError = name.error;
} else {
serviceAccountView.name = null;
}
return serviceAccountView;
}
@ -186,9 +197,15 @@ export class ServiceAccountService {
view.creationDate = response.creationDate;
view.revisionDate = response.revisionDate;
view.accessToSecrets = response.accessToSecrets;
view.name = response.name
? await this.encryptService.decryptString(new EncString(response.name), organizationKey)
: null;
if (response.name) {
const name = await this.decryptField(new EncString(response.name), organizationKey);
view.name = name.value;
view.decryptionError = name.error;
} else {
view.name = null;
}
return view;
}
@ -203,4 +220,17 @@ export class ServiceAccountService {
}),
);
}
private async decryptField(
encString: EncString,
organizationKey: SymmetricCryptoKey,
): Promise<{ value: string; error: boolean }> {
try {
const decrypted = await this.encryptService.decryptString(encString, organizationKey);
return { value: decrypted, error: false };
} catch (error) {
this.logService.error("Error decrypting service account field", error);
return { value: DECRYPT_ERROR, error: true };
}
}
}

View File

@ -58,9 +58,31 @@
<i class="bwi bwi-wrench tw-text-muted" aria-hidden="true"></i>
</td>
<td bitCell class="tw-break-all">
<a bitLink [routerLink]="serviceAccount.id">
{{ serviceAccount.name }}
</a>
@if (serviceAccount.decryptionError) {
<span
class="tw-cursor-pointer tw-text-warning tw-underline"
role="button"
tabindex="0"
[title]="'clickToRenameServiceAccount' | i18n"
[attr.aria-label]="'clickToRenameServiceAccount' | i18n"
(click)="editServiceAccountEvent.emit(serviceAccount.id); $event.stopPropagation()"
(keydown.enter)="
editServiceAccountEvent.emit(serviceAccount.id); $event.stopPropagation()
"
(keydown.space)="
editServiceAccountEvent.emit(serviceAccount.id);
$event.stopPropagation();
$event.preventDefault()
"
>
{{ serviceAccount.name }}
<i class="bwi bwi-pencil-square tw-ml-1" aria-hidden="true"></i>
</span>
} @else {
<a bitLink [routerLink]="serviceAccount.id">
{{ serviceAccount.name }}
</a>
}
</td>
<td bitCell>
<span> {{ serviceAccount.accessToSecrets }} </span>

View File

@ -163,12 +163,18 @@ export class SecretsManagerPortingApiService {
exportData.secrets.map(async (s) => {
const secret = new SecretsManagerExportSecret();
[secret.key, secret.value, secret.note] = await Promise.all([
const decryptionResults = await Promise.allSettled([
this.encryptService.decryptString(new EncString(s.key), orgKey),
this.encryptService.decryptString(new EncString(s.value), orgKey),
this.encryptService.decryptString(new EncString(s.note), orgKey),
]);
secret.key =
decryptionResults[0].status === "fulfilled" ? decryptionResults[0].value : DECRYPT_ERROR;
secret.value =
decryptionResults[1].status === "fulfilled" ? decryptionResults[1].value : DECRYPT_ERROR;
secret.note =
decryptionResults[2].status === "fulfilled" ? decryptionResults[2].value : DECRYPT_ERROR;
secret.id = s.id;
secret.projectIds = s.projectIds;

View File

@ -412,15 +412,21 @@ export class AccessPolicyService {
): Promise<ServiceAccountAccessPolicyView[]> {
return await Promise.all(
responses.map(async (response) => {
let serviceAccountName = null;
if (response.serviceAccountName) {
try {
serviceAccountName = await this.encryptService.decryptString(
new EncString(response.serviceAccountName),
orgKey,
);
} catch {
serviceAccountName = DECRYPT_ERROR;
}
}
return {
...this.createBaseAccessPolicyView(response),
serviceAccountId: response.serviceAccountId,
serviceAccountName: response.serviceAccountName
? await this.encryptService.decryptString(
new EncString(response.serviceAccountName),
orgKey,
)
: null,
serviceAccountName,
};
}),
);