Skip to content

Commit be21aff

Browse files
committed
Fixes; add convenience npm commands; Optimize calls; add Polly to retry on the main call to Safeguard; Show
when monitoring is stopping/starting; Only reload if needed.
1 parent 0c2c0e4 commit be21aff

21 files changed

Lines changed: 274 additions & 106 deletions

ExternalPlugins/SppSecrets/PluginDescriptor.cs

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -238,18 +238,26 @@ private bool StoreCredential(string assetName, string accountName, string payloa
238238
}
239239
}
240240

241+
var secretSaved = false;
241242
switch (AssignedCredentialType)
242243
{
243244
case CredentialType.Password:
244-
SaveAccountPassword(account, payload);
245+
secretSaved = SaveAccountPassword(account, payload);
245246
break;
246247
case CredentialType.SshKey:
247-
SaveAccountSshKey(account, payload);
248+
secretSaved = SaveAccountSshKey(account, payload);
248249
break;
249250
case CredentialType.ApiKey:
250-
SaveAccountApiKey(account, payload);
251+
secretSaved = SaveAccountApiKey(account, payload);
251252
break;
252253
}
254+
255+
if (!secretSaved)
256+
{
257+
// No need to log here as the specific save method already logged the error.
258+
return false;
259+
}
260+
253261
Logger.Information($"The secret for {assetName}-{accountName} has been successfully stored in the vault.");
254262

255263
// Look up A2A Registration and create one if needed.
@@ -273,52 +281,56 @@ private bool StoreCredential(string assetName, string accountName, string payloa
273281
}
274282
}
275283

276-
private void SaveAccountPassword(Account account, string password)
284+
private bool SaveAccountPassword(Account account, string password)
277285
{
278286
if (_sppConnection == null)
279-
return;
287+
return false;
280288

281289
try
282290
{
283291
var result = _sppConnection.InvokeMethodFull(Service.Core, Method.Put, $"AssetAccounts/{account.Id}/Password", $"\"{password}\"");
284-
if (result.StatusCode != HttpStatusCode.NoContent)
292+
if (result.StatusCode == HttpStatusCode.NoContent)
285293
{
286-
Logger.Error(
287-
$"Failed to save the password for asset {account.Asset.Name} account {account.Name}");
294+
return true;
288295
}
296+
297+
Logger.Error($"Failed to save the password for asset {account.Asset.Name} account {account.Name}");
289298
}
290299
catch (Exception ex)
291300
{
292-
Logger.Error(ex,
293-
$"Failed to save the password for asset {account.Asset.Name} account {account.Name}: {ex.Message}");
301+
Logger.Error(ex, $"Failed to save the password for asset {account.Asset.Name} account {account.Name}: {ex.Message}");
294302
}
303+
304+
return false;
295305
}
296306

297-
private void SaveAccountSshKey(Account account, string sshKey)
307+
private bool SaveAccountSshKey(Account account, string sshKey)
298308
{
299309
if (_sppConnection == null)
300-
return;
310+
return false;
301311

302312
try
303313
{
304314
var result = _sppConnection.InvokeMethodFull(Service.Core, Method.Put, $"AssetAccounts/{account.Id}/SshKey", $"{{\"PrivateKey\":\"{sshKey.ReplaceLineEndings(string.Empty)}\"}}");
305-
if (result.StatusCode != HttpStatusCode.OK)
315+
if (result.StatusCode == HttpStatusCode.OK)
306316
{
307-
Logger.Error(
308-
$"Failed to save the SSH key for asset {account.Asset.Name} account {account.Name}");
317+
return true;
309318
}
319+
320+
Logger.Error($"Failed to save the SSH key for asset {account.Asset.Name} account {account.Name}");
310321
}
311322
catch (Exception ex)
312323
{
313-
Logger.Error(ex,
314-
$"Failed to save the SSH key for asset {account.Asset.Name} account {account.Name}: {ex.Message}");
324+
Logger.Error(ex, $"Failed to save the SSH key for asset {account.Asset.Name} account {account.Name}: {ex.Message}");
315325
}
326+
327+
return false;
316328
}
317329

318-
private void SaveAccountApiKey(Account account, string apiKeyJson)
330+
private bool SaveAccountApiKey(Account account, string apiKeyJson)
319331
{
320332
if (_sppConnection == null)
321-
return;
333+
return false;
322334

323335
try
324336
{
@@ -332,29 +344,31 @@ private void SaveAccountApiKey(Account account, string apiKeyJson)
332344
if (apiKey == null)
333345
{
334346
Logger.Information($"Failed to store the API key secret due to a failure to create the API key for the account {account.Name}.");
335-
return;
347+
return false;
336348
}
337349
}
338350

339351
var result = _sppConnection.InvokeMethodFull(Service.Core, Method.Put,
340352
$"AssetAccounts/{account.Id}/ApiKeys/{newApiKey.Id}/ClientSecret", apiKeyJson);
341-
if (result.StatusCode != HttpStatusCode.NoContent)
353+
if (result.StatusCode == HttpStatusCode.NoContent)
342354
{
343-
Logger.Error(
344-
$"Failed to save the API key secret for account {account.Name} API key {apiKey.Name} to {DisplayName}.");
355+
return true;
345356
}
357+
358+
Logger.Error($"Failed to save the API key secret for account {account.Name} API key {apiKey.Name} to {DisplayName}.");
346359
}
347360
else
348361
{
349362
Logger.Error($"The ApiKey {apiKey.Name} failed to save to {DisplayName}.");
350363
}
351-
352364
}
353365
catch (Exception ex)
354366
{
355367
Logger.Error(ex,
356368
$"Failed to save the Api key for account {account.Name} to {DisplayName}: {ex.Message}");
357369
}
370+
371+
return false;
358372
}
359373

360374
private bool CheckOrAddExternalA2aRegistration()

ExternalPlugins/SppSecrets/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ The Safeguard for Privileged Passwords plugin allows Secrets Broker to pull pass
77
### Downstream SPP configuration requirements
88

99
* Create a local SPP user that will be used by Secrets Broker to manage the A2A registration and accounts group in the downstream SPP appliance.
10-
* Assign policy admin permissions to the new SPP user.
10+
* Assign asset admin and policy admin permissions to the new SPP user.
1111
* This user name will be entered as the ```SPP user``` in the configuration of the SPPtoSPP Secrets Broker plugin.
1212
* Create an A2A certificate with ```Client Authentication``` attribute.
1313
* Install the A2A certificate with private key .pfx in the ```Local Computer``` certificate store of the Secrets Broker server.
@@ -29,4 +29,4 @@ The Safeguard for Privileged Passwords plugin allows Secrets Broker to pull pass
2929
* **SPP User** - Downstream user name of an SPP local user with policy admin permissions.
3030
* **SPP A2A Registration Name** - Name of the downstream A2A registration that is used to provide A2A access to the credentials that are pushed by Secrets Broker.
3131
* **SPP A2a Certificate User** - Downstream A2A certificate user.
32-
* **SPP Account Group** - Name of an account group for the credentials that are pushed by Secrets Broker.
32+
* **SPP Account Group** - (Optional) Name of an account group for the credentials that are pushed by Secrets Broker.

SafeguardDevOpsService/ClientApp/angular.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"builder": "@angular-devkit/build-angular:browser",
1919
"options": {
2020
"allowedCommonJsDependencies": [
21+
"file-saver",
2122
"moment-timezone",
2223
"jquery",
2324
"lodash"

SafeguardDevOpsService/ClientApp/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
"ng": "ng",
66
"start": "ng serve",
77
"build": "ng build",
8+
"build-debug-windows": "ng build --optimization=false && npm run copy-dist-to-debug-windows",
9+
"copy-dist-to-debug-windows": "xcopy dist\\* ..\\bin\\Debug\\ClientApp\\dist\\ /y /E",
810
"test": "ng test",
911
"lint": "ng lint",
1012
"e2e": "ng e2e"

SafeguardDevOpsService/ClientApp/src/app/confirm-dialog/confirm-dialog.component.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737

3838
<div mat-dialog-actions align='end'>
3939
<button mat-button (click)="close()" cdkFocusInitial *ngIf="showCancel">Cancel</button>
40-
<button mat-button (click)="confirm(false)" *ngIf="showNo">No</button>
41-
<button mat-flat-button color='primary' (click)="confirm(true)">{{confirmText}}</button>
40+
<button mat-button (click)="confirm('No')" *ngIf="showNo">No</button>
41+
<button mat-button (click)="confirm('custom')" *ngIf="customText">{{customText}}</button>
42+
<button mat-flat-button color='primary' (click)="confirm('OK')">{{confirmText}}</button>
4243
</div>

SafeguardDevOpsService/ClientApp/src/app/confirm-dialog/confirm-dialog.component.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export class ConfirmDialogComponent implements OnInit {
1111
title = '';
1212
message = '';
1313
confirmText = 'OK';
14+
customText = '';
1415
showCancel = true;
1516
showNo = false;
1617
showRestart = false;
@@ -29,6 +30,7 @@ export class ConfirmDialogComponent implements OnInit {
2930
this.title = this.data.title ?? this.title;
3031
this.message = this.data.message ?? this.message;
3132
this.confirmText = this.data.confirmText ?? ((this.title.length > 0) ? this.title : this.confirmText);
33+
this.customText = this.data.customText ?? this.customText;
3234
this.showCancel = this.data.showCancel ?? this.showCancel;
3335
this.showNo = this.data.showNo ?? this.showNo;
3436
this.showRestart = this.data.showRestart ?? this.showRestart;
@@ -40,7 +42,7 @@ export class ConfirmDialogComponent implements OnInit {
4042
this.dialogRef.close();
4143
}
4244

43-
confirm(confirm: boolean): void {
44-
this.dialogRef.close({ result: confirm ? 'OK' : 'No', restart: this.restart, secretsBrokerOnly: this.secretsBrokerOnly, passphrase: this.passphrase });
45+
confirm(value: string): void {
46+
this.dialogRef.close({ result: value, restart: this.restart, secretsBrokerOnly: this.secretsBrokerOnly, passphrase: this.passphrase });
4547
}
4648
}

SafeguardDevOpsService/ClientApp/src/app/edit-trusted-certificates/edit-trusted-certificates.component.html

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<div class="root-container">
22

3-
<app-error-bar [error]='error' (dismissed)='this.error=null'></app-error-bar>
3+
<app-error-bar *ngIf="error" [error]='error' (dismissed)='this.error=null'></app-error-bar>
44

55
<div mat-dialog-title>
66
<div class='dialog-title-elements'>
@@ -11,7 +11,7 @@
1111
<mat-icon class="info-icon">info</mat-icon>
1212
</button>
1313
</div>
14-
<div class='close-button'><button mat-icon-button mat-dialog-close tabindex='-1' class="link-button">
14+
<div class='close-button'><button mat-icon-button (click)="close()" tabindex='-1' class="link-button">
1515
<mat-icon>close</mat-icon>
1616
</button></div>
1717
</div>
@@ -21,11 +21,11 @@
2121
<div class="upload-button">
2222
<input #fileSelectInputDialog type="file" style="display:none" (change)="onChangeFile($event.target.files)"
2323
accept=".cer,.crt,.der,.pem" />
24-
<button mat-stroked-button (click)="browse()" color="primary">Upload</button>
24+
<button mat-stroked-button (click)="browse()" color="primary" [disabled]="isLoading">Upload</button>
2525
</div>
26-
<button mat-flat-button (click)="import()" color="primary" cdkFocusInitial>Import from Safeguard</button>
26+
<button mat-flat-button (click)="import()" color="primary" cdkFocusInitial [disabled]="isLoading">Import from Safeguard</button>
2727
<div class="spacer"></div>
28-
<mat-checkbox color="primary" [(ngModel)]="useSsl" (disabled)="trustedCertificates.length == 0" (change)="updateUseSsl()"
28+
<mat-checkbox color="primary" [(ngModel)]="useSsl" [disabled]="trustedCertificates.length == 0 || isLoading" (change)="updateUseSsl()"
2929
matTooltip="Use trusted certificates to validate an SSL connection to Safeguard">Verify TLS Certifcate
3030
</mat-checkbox>
3131
</div>
@@ -37,11 +37,13 @@
3737
Establishing a trusted connection requires that trusted certificates be added to the service.
3838
</div>
3939

40-
<mat-selection-list [multiple]="false" #certificates [ngClass]="{'cert-list': trustedCertificates.length > 0}">
41-
<mat-list-option *ngFor="let cert of trustedCertificates" [value]="cert">
42-
<span class="cert-info">{{cert.Subject}}</span>
43-
</mat-list-option>
44-
</mat-selection-list>
40+
<ng-container *ngIf="!isLoading">
41+
<mat-selection-list [multiple]="false" #certificates>
42+
<mat-list-option *ngFor="let cert of trustedCertificates" [value]="cert" (click)="selectCert(cert)">
43+
<span class="cert-info">{{cert.Subject}}</span>
44+
</mat-list-option>
45+
</mat-selection-list>
46+
</ng-container>
4547

4648
<mat-spinner color="accent" diameter="80" *ngIf="isLoading"></mat-spinner>
4749
</mat-dialog-content>

SafeguardDevOpsService/ClientApp/src/app/edit-trusted-certificates/edit-trusted-certificates.component.scss

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,6 @@
3434
min-width:1px;
3535
}
3636

37-
.cert-list {
38-
min-height: 300px;
39-
}
40-
4137
:host ::ng-deep .mdc-list-item__end {
4238
display: none;
4339
}
@@ -53,10 +49,11 @@ mat-list-option:last-of-type {
5349
.root-container {
5450
position: relative;
5551
overflow: hidden;
52+
min-height: 300px;
5653
}
5754

5855
mat-spinner {
59-
margin: 60px auto;
56+
margin: 0px auto;
6057
}
6158

6259
.cert-details-container {
@@ -80,6 +77,7 @@ mat-spinner {
8077
.cert-details-body {
8178
margin: 20px;
8279
background-color: $iris-pastel;
80+
height: 100%;
8381

8482
.cert-details-title {
8583
font-weight: 600;

SafeguardDevOpsService/ClientApp/src/app/edit-trusted-certificates/edit-trusted-certificates.component.ts

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { Component, OnInit, Inject, ViewChild, AfterViewInit, ElementRef } from '@angular/core';
2-
import { MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog';
1+
import { Component, OnInit, Inject, ViewChild, ElementRef } from '@angular/core';
2+
import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog';
33
import { DevOpsServiceClient } from '../service-client.service';
44
import { MatSelectionList } from '@angular/material/list';
55
import * as moment from 'moment-timezone';
@@ -13,15 +13,16 @@ import { MatSnackBar } from '@angular/material/snack-bar';
1313
templateUrl: './edit-trusted-certificates.component.html',
1414
styleUrls: ['./edit-trusted-certificates.component.scss']
1515
})
16-
export class EditTrustedCertificatesComponent implements OnInit, AfterViewInit {
16+
export class EditTrustedCertificatesComponent implements OnInit {
1717

1818
trustedCertificates: any[];
1919
useSsl: boolean;
2020
selectedCert: any;
2121
localizedValidFrom: string;
22-
isLoading: boolean;
22+
isLoading: boolean = true;
2323
showExplanatoryText: boolean;
2424
error = null;
25+
needsReload = false;
2526

2627
@ViewChild('certificates', { static: false }) certList: MatSelectionList;
2728
@ViewChild('fileSelectInputDialog', { static: false }) fileSelectInputDialog: ElementRef;
@@ -30,26 +31,30 @@ export class EditTrustedCertificatesComponent implements OnInit, AfterViewInit {
3031
@Inject(MAT_DIALOG_DATA) public data: any,
3132
private serviceClient: DevOpsServiceClient,
3233
private dialog: MatDialog,
34+
private dialogRef: MatDialogRef<EditTrustedCertificatesComponent>,
3335
private snackbar: MatSnackBar
3436
) { }
3537

3638
ngOnInit(): void {
3739
this.trustedCertificates = this.data?.trustedCertificates ?? [];
3840

39-
this.serviceClient.getSafeguard().subscribe((data: any) => {
41+
this.serviceClient.getSafeguard(false).subscribe((data: any) => {
4042
if (data) {
4143
this.useSsl = !data.IgnoreSsl;
4244
}
45+
this.isLoading = false;
4346
});
47+
48+
this.dialogRef.backdropClick().subscribe(() => this.close());
4449
}
45-
46-
ngAfterViewInit(): void {
47-
this.certList.selectionChange.subscribe((x) => {
48-
if (!this.selectedCert) {
49-
this.selectedCert = x.options[0].value;
50-
this.localizedValidFrom = moment(this.selectedCert.NotBefore).format('LLL (Z)') + ' - ' + moment(this.selectedCert.NotAfter).format('LLL (Z)');
51-
}
52-
});
50+
51+
close() {
52+
this.dialogRef.close(this.needsReload);
53+
}
54+
55+
selectCert(cert) {
56+
this.selectedCert = cert;
57+
this.localizedValidFrom = moment(this.selectedCert.NotBefore).format('LLL (Z)') + ' - ' + moment(this.selectedCert.NotAfter).format('LLL (Z)');
5358
}
5459

5560
browse(): void {
@@ -59,6 +64,7 @@ export class EditTrustedCertificatesComponent implements OnInit, AfterViewInit {
5964

6065
updateUseSsl(): void {
6166
this.error = null;
67+
this.needsReload = true;
6268
this.serviceClient.putSafeguardUseSsl(this.useSsl)
6369
.subscribe({
6470
next: () => { },
@@ -121,6 +127,7 @@ export class EditTrustedCertificatesComponent implements OnInit, AfterViewInit {
121127
next: () => {
122128
if (isNew) {
123129
this.snackbar.open(`Added certificate ${fileData.fileName}`, 'Dismiss', { duration: 5000 });
130+
this.needsReload = true;
124131
} else {
125132
this.snackbar.open(`Certificate ${fileData.fileName} already exists.`, 'Dismiss', { duration: 5000 });
126133
}
@@ -197,6 +204,10 @@ export class EditTrustedCertificatesComponent implements OnInit, AfterViewInit {
197204
} else {
198205
this.snackbar.open(`Imported ${newTrustedCertsCount} new certificates and ${existingTrustedCertsCount} existing certificates.`, 'Dismiss', { duration: 5000 });
199206
}
207+
208+
if (newTrustedCertsCount > 0) {
209+
this.needsReload = true;
210+
}
200211
}
201212
});
202213
}
@@ -215,6 +226,7 @@ export class EditTrustedCertificatesComponent implements OnInit, AfterViewInit {
215226
this.updateUseSsl();
216227
}
217228
this.isLoading = false;
229+
this.needsReload = true;
218230
},
219231
error: error => {
220232
this.isLoading = false;

0 commit comments

Comments
 (0)