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
62 changes: 38 additions & 24 deletions ExternalPlugins/SppSecrets/PluginDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -238,18 +238,26 @@ private bool StoreCredential(string assetName, string accountName, string payloa
}
}

var secretSaved = false;
switch (AssignedCredentialType)
{
case CredentialType.Password:
SaveAccountPassword(account, payload);
secretSaved = SaveAccountPassword(account, payload);
break;
case CredentialType.SshKey:
SaveAccountSshKey(account, payload);
secretSaved = SaveAccountSshKey(account, payload);
break;
case CredentialType.ApiKey:
SaveAccountApiKey(account, payload);
secretSaved = SaveAccountApiKey(account, payload);
break;
}

if (!secretSaved)
{
// No need to log here as the specific save method already logged the error.
return false;
}

Logger.Information($"The secret for {assetName}-{accountName} has been successfully stored in the vault.");

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

private void SaveAccountPassword(Account account, string password)
private bool SaveAccountPassword(Account account, string password)
{
if (_sppConnection == null)
return;
return false;

try
{
var result = _sppConnection.InvokeMethodFull(Service.Core, Method.Put, $"AssetAccounts/{account.Id}/Password", $"\"{password}\"");
if (result.StatusCode != HttpStatusCode.NoContent)
if (result.StatusCode == HttpStatusCode.NoContent)
{
Logger.Error(
$"Failed to save the password for asset {account.Asset.Name} account {account.Name}");
return true;
}

Logger.Error($"Failed to save the password for asset {account.Asset.Name} account {account.Name}");
}
catch (Exception ex)
{
Logger.Error(ex,
$"Failed to save the password for asset {account.Asset.Name} account {account.Name}: {ex.Message}");
Logger.Error(ex, $"Failed to save the password for asset {account.Asset.Name} account {account.Name}: {ex.Message}");
}

return false;
}

private void SaveAccountSshKey(Account account, string sshKey)
private bool SaveAccountSshKey(Account account, string sshKey)
{
if (_sppConnection == null)
return;
return false;

try
{
var result = _sppConnection.InvokeMethodFull(Service.Core, Method.Put, $"AssetAccounts/{account.Id}/SshKey", $"{{\"PrivateKey\":\"{sshKey.ReplaceLineEndings(string.Empty)}\"}}");
if (result.StatusCode != HttpStatusCode.OK)
if (result.StatusCode == HttpStatusCode.OK)
{
Logger.Error(
$"Failed to save the SSH key for asset {account.Asset.Name} account {account.Name}");
return true;
}

Logger.Error($"Failed to save the SSH key for asset {account.Asset.Name} account {account.Name}");
}
catch (Exception ex)
{
Logger.Error(ex,
$"Failed to save the SSH key for asset {account.Asset.Name} account {account.Name}: {ex.Message}");
Logger.Error(ex, $"Failed to save the SSH key for asset {account.Asset.Name} account {account.Name}: {ex.Message}");
}

return false;
}

private void SaveAccountApiKey(Account account, string apiKeyJson)
private bool SaveAccountApiKey(Account account, string apiKeyJson)
{
if (_sppConnection == null)
return;
return false;

try
{
Expand All @@ -332,29 +344,31 @@ private void SaveAccountApiKey(Account account, string apiKeyJson)
if (apiKey == null)
{
Logger.Information($"Failed to store the API key secret due to a failure to create the API key for the account {account.Name}.");
return;
return false;
}
}

var result = _sppConnection.InvokeMethodFull(Service.Core, Method.Put,
$"AssetAccounts/{account.Id}/ApiKeys/{newApiKey.Id}/ClientSecret", apiKeyJson);
if (result.StatusCode != HttpStatusCode.NoContent)
if (result.StatusCode == HttpStatusCode.NoContent)
{
Logger.Error(
$"Failed to save the API key secret for account {account.Name} API key {apiKey.Name} to {DisplayName}.");
return true;
}

Logger.Error($"Failed to save the API key secret for account {account.Name} API key {apiKey.Name} to {DisplayName}.");
}
else
{
Logger.Error($"The ApiKey {apiKey.Name} failed to save to {DisplayName}.");
}

}
catch (Exception ex)
{
Logger.Error(ex,
$"Failed to save the Api key for account {account.Name} to {DisplayName}: {ex.Message}");
}

return false;
}

private bool CheckOrAddExternalA2aRegistration()
Expand Down
4 changes: 2 additions & 2 deletions ExternalPlugins/SppSecrets/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ The Safeguard for Privileged Passwords plugin allows Secrets Broker to pull pass
### Downstream SPP configuration requirements

* 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.
* Assign policy admin permissions to the new SPP user.
* Assign asset admin and policy admin permissions to the new SPP user.
* This user name will be entered as the ```SPP user``` in the configuration of the SPPtoSPP Secrets Broker plugin.
* Create an A2A certificate with ```Client Authentication``` attribute.
* Install the A2A certificate with private key .pfx in the ```Local Computer``` certificate store of the Secrets Broker server.
Expand All @@ -29,4 +29,4 @@ The Safeguard for Privileged Passwords plugin allows Secrets Broker to pull pass
* **SPP User** - Downstream user name of an SPP local user with policy admin permissions.
* **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.
* **SPP A2a Certificate User** - Downstream A2A certificate user.
* **SPP Account Group** - Name of an account group for the credentials that are pushed by Secrets Broker.
* **SPP Account Group** - (Optional) Name of an account group for the credentials that are pushed by Secrets Broker.
1 change: 1 addition & 0 deletions SafeguardDevOpsService/ClientApp/angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"builder": "@angular-devkit/build-angular:browser",
"options": {
"allowedCommonJsDependencies": [
"file-saver",
"moment-timezone",
"jquery",
"lodash"
Expand Down
2 changes: 2 additions & 0 deletions SafeguardDevOpsService/ClientApp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"build-debug-windows": "ng build --optimization=false && npm run copy-dist-to-debug-windows",
"copy-dist-to-debug-windows": "xcopy dist\\* ..\\bin\\Debug\\ClientApp\\dist\\ /y /E",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@

<div mat-dialog-actions align='end'>
<button mat-button (click)="close()" cdkFocusInitial *ngIf="showCancel">Cancel</button>
<button mat-button (click)="confirm(false)" *ngIf="showNo">No</button>
<button mat-flat-button color='primary' (click)="confirm(true)">{{confirmText}}</button>
<button mat-button (click)="confirm('No')" *ngIf="showNo">No</button>
<button mat-button (click)="confirm('custom')" *ngIf="customText">{{customText}}</button>
<button mat-flat-button color='primary' (click)="confirm('OK')">{{confirmText}}</button>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export class ConfirmDialogComponent implements OnInit {
title = '';
message = '';
confirmText = 'OK';
customText = '';
showCancel = true;
showNo = false;
showRestart = false;
Expand All @@ -29,6 +30,7 @@ export class ConfirmDialogComponent implements OnInit {
this.title = this.data.title ?? this.title;
this.message = this.data.message ?? this.message;
this.confirmText = this.data.confirmText ?? ((this.title.length > 0) ? this.title : this.confirmText);
this.customText = this.data.customText ?? this.customText;
this.showCancel = this.data.showCancel ?? this.showCancel;
this.showNo = this.data.showNo ?? this.showNo;
this.showRestart = this.data.showRestart ?? this.showRestart;
Expand All @@ -40,7 +42,7 @@ export class ConfirmDialogComponent implements OnInit {
this.dialogRef.close();
}

confirm(confirm: boolean): void {
this.dialogRef.close({ result: confirm ? 'OK' : 'No', restart: this.restart, secretsBrokerOnly: this.secretsBrokerOnly, passphrase: this.passphrase });
confirm(value: string): void {
this.dialogRef.close({ result: value, restart: this.restart, secretsBrokerOnly: this.secretsBrokerOnly, passphrase: this.passphrase });
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<div class="root-container">

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

<div mat-dialog-title>
<div class='dialog-title-elements'>
Expand All @@ -11,7 +11,7 @@
<mat-icon class="info-icon">info</mat-icon>
</button>
</div>
<div class='close-button'><button mat-icon-button mat-dialog-close tabindex='-1' class="link-button">
<div class='close-button'><button mat-icon-button (click)="close()" tabindex='-1' class="link-button">
<mat-icon>close</mat-icon>
</button></div>
</div>
Expand All @@ -21,11 +21,11 @@
<div class="upload-button">
<input #fileSelectInputDialog type="file" style="display:none" (change)="onChangeFile($event.target.files)"
accept=".cer,.crt,.der,.pem" />
<button mat-stroked-button (click)="browse()" color="primary">Upload</button>
<button mat-stroked-button (click)="browse()" color="primary" [disabled]="isLoading">Upload</button>
</div>
<button mat-flat-button (click)="import()" color="primary" cdkFocusInitial>Import from Safeguard</button>
<button mat-flat-button (click)="import()" color="primary" cdkFocusInitial [disabled]="isLoading">Import from Safeguard</button>
<div class="spacer"></div>
<mat-checkbox color="primary" [(ngModel)]="useSsl" (disabled)="trustedCertificates.length == 0" (change)="updateUseSsl()"
<mat-checkbox color="primary" [(ngModel)]="useSsl" [disabled]="trustedCertificates.length == 0 || isLoading" (change)="updateUseSsl()"
matTooltip="Use trusted certificates to validate an SSL connection to Safeguard">Verify TLS Certifcate
</mat-checkbox>
</div>
Expand All @@ -37,11 +37,13 @@
Establishing a trusted connection requires that trusted certificates be added to the service.
</div>

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

<mat-spinner color="accent" diameter="80" *ngIf="isLoading"></mat-spinner>
</mat-dialog-content>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,6 @@
min-width:1px;
}

.cert-list {
min-height: 300px;
}

:host ::ng-deep .mdc-list-item__end {
display: none;
}
Expand All @@ -53,10 +49,11 @@ mat-list-option:last-of-type {
.root-container {
position: relative;
overflow: hidden;
min-height: 300px;
}

mat-spinner {
margin: 60px auto;
margin: 0px auto;
}

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

.cert-details-title {
font-weight: 600;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Component, OnInit, Inject, ViewChild, AfterViewInit, ElementRef } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog';
import { Component, OnInit, Inject, ViewChild, ElementRef } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog';
import { DevOpsServiceClient } from '../service-client.service';
import { MatSelectionList } from '@angular/material/list';
import * as moment from 'moment-timezone';
Expand All @@ -13,15 +13,16 @@ import { MatSnackBar } from '@angular/material/snack-bar';
templateUrl: './edit-trusted-certificates.component.html',
styleUrls: ['./edit-trusted-certificates.component.scss']
})
export class EditTrustedCertificatesComponent implements OnInit, AfterViewInit {
export class EditTrustedCertificatesComponent implements OnInit {

trustedCertificates: any[];
useSsl: boolean;
selectedCert: any;
localizedValidFrom: string;
isLoading: boolean;
isLoading: boolean = true;
showExplanatoryText: boolean;
error = null;
needsReload = false;

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

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

this.serviceClient.getSafeguard().subscribe((data: any) => {
this.serviceClient.getSafeguard(false).subscribe((data: any) => {
if (data) {
this.useSsl = !data.IgnoreSsl;
}
this.isLoading = false;
});

this.dialogRef.backdropClick().subscribe(() => this.close());
}

ngAfterViewInit(): void {
this.certList.selectionChange.subscribe((x) => {
if (!this.selectedCert) {
this.selectedCert = x.options[0].value;
this.localizedValidFrom = moment(this.selectedCert.NotBefore).format('LLL (Z)') + ' - ' + moment(this.selectedCert.NotAfter).format('LLL (Z)');
}
});
close() {
this.dialogRef.close(this.needsReload);
}

selectCert(cert) {
this.selectedCert = cert;
this.localizedValidFrom = moment(this.selectedCert.NotBefore).format('LLL (Z)') + ' - ' + moment(this.selectedCert.NotAfter).format('LLL (Z)');
}

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

updateUseSsl(): void {
this.error = null;
this.needsReload = true;
this.serviceClient.putSafeguardUseSsl(this.useSsl)
.subscribe({
next: () => { },
Expand Down Expand Up @@ -121,6 +127,7 @@ export class EditTrustedCertificatesComponent implements OnInit, AfterViewInit {
next: () => {
if (isNew) {
this.snackbar.open(`Added certificate ${fileData.fileName}`, 'Dismiss', { duration: 5000 });
this.needsReload = true;
} else {
this.snackbar.open(`Certificate ${fileData.fileName} already exists.`, 'Dismiss', { duration: 5000 });
}
Expand Down Expand Up @@ -197,6 +204,10 @@ export class EditTrustedCertificatesComponent implements OnInit, AfterViewInit {
} else {
this.snackbar.open(`Imported ${newTrustedCertsCount} new certificates and ${existingTrustedCertsCount} existing certificates.`, 'Dismiss', { duration: 5000 });
}

if (newTrustedCertsCount > 0) {
this.needsReload = true;
}
}
});
}
Expand All @@ -215,6 +226,7 @@ export class EditTrustedCertificatesComponent implements OnInit, AfterViewInit {
this.updateUseSsl();
}
this.isLoading = false;
this.needsReload = true;
},
error: error => {
this.isLoading = false;
Expand Down
Loading