Skip to content

Commit

Permalink
Integrated code lifecycle: Show ssh fingerprints (#9650)
Browse files Browse the repository at this point in the history
  • Loading branch information
SimonEntholzer authored Dec 20, 2024
1 parent 2fa027e commit ab8bada
Show file tree
Hide file tree
Showing 18 changed files with 443 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,8 @@ public class HashUtils {
public static String getSha512Fingerprint(PublicKey key) {
return KeyUtils.getFingerPrint(BuiltinDigests.sha512.create(), key);
}

public static String getSha256Fingerprint(PublicKey key) {
return KeyUtils.getFingerPrint(BuiltinDigests.sha256.create(), key);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package de.tum.cit.aet.artemis.programming.service.localvc.ssh;

import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_LOCALVC;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.Map;

import jakarta.ws.rs.BadRequestException;

import org.apache.sshd.common.keyprovider.KeyPairProvider;
import org.apache.sshd.server.SshServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;

/**
* Service responsible for providing SSH fingerprints of the SSH server running in Artemis.
*/
@Profile(PROFILE_LOCALVC)
@Service
public class SshFingerprintsProviderService {

private static final Logger log = LoggerFactory.getLogger(SshFingerprintsProviderService.class);

private final SshServer sshServer;

public SshFingerprintsProviderService(SshServer sshServer) {
this.sshServer = sshServer;
}

/**
* Retrieves the SSH key fingerprints from the stored SSH keys
*
* @return a map containing the SSH key fingerprints, where the key is the algorithm
* of the public key and the value is its SHA-256 fingerprint.
* @throws BadRequestException if there is an error loading keys from the SSH server.
*/
public Map<String, String> getSshFingerPrints() {
Map<String, String> fingerprints = new HashMap<>();
KeyPairProvider keyPairProvider = sshServer.getKeyPairProvider();
if (keyPairProvider != null) {
try {
keyPairProvider.loadKeys(null).iterator()
.forEachRemaining(keyPair -> fingerprints.put(keyPair.getPublic().getAlgorithm(), HashUtils.getSha256Fingerprint(keyPair.getPublic())));

}
catch (IOException | GeneralSecurityException e) {
log.info("Could not load keys from the ssh server while trying to get SSH key fingerprints", e);
throw new BadRequestException("Could not load keys from the ssh server");
}
}
return fingerprints;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package de.tum.cit.aet.artemis.programming.web.localvc.ssh;

import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_LOCALVC;

import java.util.Map;

import org.springframework.context.annotation.Profile;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import de.tum.cit.aet.artemis.core.security.annotations.EnforceAtLeastStudent;
import de.tum.cit.aet.artemis.programming.service.localvc.ssh.SshFingerprintsProviderService;

/**
* REST controller for managing.
*/
@Profile(PROFILE_LOCALVC)
@RestController
@RequestMapping("api/")
public class SshFingerprintsProviderResource {

SshFingerprintsProviderService sshFingerprintsProviderService;

public SshFingerprintsProviderResource(SshFingerprintsProviderService sshFingerprintsProviderService) {
this.sshFingerprintsProviderService = sshFingerprintsProviderService;
}

/**
* GET /ssh-fingerprints
*
* @return the SSH fingerprints for the keys a user uses
*/
@GetMapping(value = "ssh-fingerprints", produces = MediaType.APPLICATION_JSON_VALUE)
@EnforceAtLeastStudent
public ResponseEntity<Map<String, String>> getSshFingerprints() {
return ResponseEntity.ok().body(sshFingerprintsProviderService.getSshFingerPrints());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<h1 jhiTranslate="artemisApp.userSettings.sshSettingsPage.sshFingerprints"></h1>

<div class="list-group d-block">
<!-- Viewing existing key and creating a new key -->
<div class="list-group-item">
<div class="d-flex flex-wrap">
<p class="font-medium">
<span class="mt-4" jhiTranslate="artemisApp.userSettings.sshSettingsPage.fingerprintsExplanation"> </span>
<jhi-documentation-link [documentationType]="documentationType" [displayString]="'artemisApp.userSettings.sshSettingsPage.fingerprintsLearnMore'">
</jhi-documentation-link>
</p>
</div>

@if (sshFingerprints && sshFingerprints['RSA']) {
<div class="row small-text">
<div class="column left">
{{ 'RSA' }}
</div>
<div class="column right">
{{ sshFingerprints['RSA'] }}
</div>
</div>
}

@if (sshFingerprints && sshFingerprints['EdDSA']) {
<div class="row small-text">
<div class="column left">
{{ 'ED25519' }}
</div>
<div class="column right">
{{ sshFingerprints['EdDSA'] }}
</div>
</div>
}

@if (sshFingerprints && sshFingerprints['ECDSA']) {
<div class="row">
<div class="column left">
{{ 'ECDSA' }}
</div>
<div class="column right">
{{ sshFingerprints['ECDSA'] }}
</div>
</div>
}

@if (sshFingerprints && sshFingerprints['EC']) {
<div class="row">
<div class="column left">
{{ 'ECDSA' }}
</div>
<div class="column right">
{{ sshFingerprints['EC'] }}
</div>
</div>
}

<div class="d-flex justify-content-between align-items-center mt-4">
<div></div>
<div>
<a class="btn rounded-btn btn-primary btn-sm" [routerLink]="['..']">
<span class="jhi-btn__title" style="font-size: small" jhiTranslate="artemisApp.userSettings.sshSettingsPage.back"></span>
</a>
</div>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.column {
float: left;
padding: 10px;
}

.left {
width: 15%;
}

.right {
width: 85%;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Component, OnInit, inject } from '@angular/core';
import { ButtonSize, ButtonType } from 'app/shared/components/button.component';
import { DocumentationType } from 'app/shared/components/documentation-button/documentation-button.component';
import { SshUserSettingsFingerprintsService } from 'app/shared/user-settings/ssh-settings/fingerprints/ssh-user-settings-fingerprints.service';

@Component({
selector: 'jhi-account-information',
templateUrl: './ssh-user-settings-fingerprints.component.html',
styleUrls: ['./ssh-user-settings-fingerprints.component.scss', '../ssh-user-settings.component.scss'],
})
export class SshUserSettingsFingerprintsComponent implements OnInit {
readonly sshUserSettingsService = inject(SshUserSettingsFingerprintsService);

protected sshFingerprints?: { [key: string]: string };

readonly documentationType: DocumentationType = 'SshSetup';
protected readonly ButtonType = ButtonType;

protected readonly ButtonSize = ButtonSize;

async ngOnInit() {
this.sshFingerprints = await this.sshUserSettingsService.getSshFingerprints();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { firstValueFrom } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class SshUserSettingsFingerprintsService {
error?: string;

private http = inject(HttpClient);

public async getSshFingerprints(): Promise<{ [key: string]: string }> {
return await firstValueFrom(this.http.get<{ [key: string]: string }>('api/ssh-fingerprints'));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ <h4 class="text-center mb-4 mt-8" jhiTranslate="artemisApp.userSettings.sshSetti
<!-- Initial state: There are keys -->
@if (keyCount > 0) {
<div class="list-group-item">
<div class="d-flex flex-wrap mt-4">
<div class="d-flex flex-wrap">
<p>
<span class="mt-4 font-medium" jhiTranslate="artemisApp.userSettings.sshSettingsPage.whatToUseSSHForInfo"> </span>
<jhi-documentation-link [documentationType]="documentationType" [displayString]="'artemisApp.userSettings.sshSettingsPage.learnMore'">
Expand Down Expand Up @@ -103,6 +103,11 @@ <h4 class="text-center mb-4 mt-8" jhiTranslate="artemisApp.userSettings.sshSetti
</table>
<div class="d-flex justify-content-between align-items-center">
<div></div>
<div>
<a [routerLink]="['fingerprints']">
<span style="font-size: small" jhiTranslate="artemisApp.userSettings.sshSettingsPage.fingerprints"></span>
</a>
</div>
<div>
<a class="btn rounded-btn btn-primary btn-sm" [routerLink]="['add']">
<span class="jhi-btn__title" style="font-size: small" jhiTranslate="artemisApp.userSettings.sshSettingsPage.addNewSshKey"></span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { FormDateTimePickerModule } from 'app/shared/date-time-picker/date-time-
import { IdeSettingsComponent } from 'app/shared/user-settings/ide-preferences/ide-settings.component';
import { DocumentationLinkComponent } from 'app/shared/components/documentation-link/documentation-link.component';
import { SshUserSettingsKeyDetailsComponent } from 'app/shared/user-settings/ssh-settings/details/ssh-user-settings-key-details.component';
import { SshUserSettingsFingerprintsComponent } from 'app/shared/user-settings/ssh-settings/fingerprints/ssh-user-settings-fingerprints.component';

@NgModule({
imports: [RouterModule.forChild(userSettingsState), ArtemisSharedModule, ArtemisSharedComponentModule, ClipboardModule, FormDateTimePickerModule, DocumentationLinkComponent],
Expand All @@ -22,6 +23,7 @@ import { SshUserSettingsKeyDetailsComponent } from 'app/shared/user-settings/ssh
ScienceSettingsComponent,
SshUserSettingsComponent,
SshUserSettingsKeyDetailsComponent,
SshUserSettingsFingerprintsComponent,
VcsAccessTokensSettingsComponent,
IdeSettingsComponent,
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { SshUserSettingsComponent } from 'app/shared/user-settings/ssh-settings/
import { VcsAccessTokensSettingsComponent } from 'app/shared/user-settings/vcs-access-tokens-settings/vcs-access-tokens-settings.component';
import { IdeSettingsComponent } from 'app/shared/user-settings/ide-preferences/ide-settings.component';
import { SshUserSettingsKeyDetailsComponent } from 'app/shared/user-settings/ssh-settings/details/ssh-user-settings-key-details.component';
import { SshUserSettingsFingerprintsComponent } from 'app/shared/user-settings/ssh-settings/fingerprints/ssh-user-settings-fingerprints.component';

export const userSettingsState: Routes = [
{
Expand Down Expand Up @@ -60,6 +61,13 @@ export const userSettingsState: Routes = [
pageTitle: 'artemisApp.userSettings.categories.SSH_SETTINGS',
},
},
{
path: 'ssh/fingerprints',
component: SshUserSettingsFingerprintsComponent,
data: {
pageTitle: 'artemisApp.userSettings.categories.SSH_SETTINGS',
},
},
{
path: 'ssh/view/:keyId',
component: SshUserSettingsKeyDetailsComponent,
Expand Down
4 changes: 4 additions & 0 deletions src/main/webapp/i18n/de/userSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@
"expiresOn": "Läuft ab am",
"hasExpiredOn": "Abgelaufen am",
"fingerprint": "Fingerabdruck",
"fingerprints": "SSH Fingerabdrücke anzeigen",
"sshFingerprints": "SSH Fingerabdrücke",
"fingerprintsExplanation": "Mit SSH-Schlüsseln kannst du eine sichere Verbindung zwischen deinem Computer und Artemis herstellen. SSH-Fingerabdrücke stellen sicher, dass du eine Verbindung zum richtigen Gerät herstellt.",
"fingerprintsLearnMore": "Lerne mehr über Fingerabdrücke",
"commentUsedAsLabel": "Wenn du kein Label hinzufügst, wird der Schlüsselkommentar (sofern vorhanden) als Label verwendet.",
"expiry": {
"title": "Ablauf",
Expand Down
4 changes: 4 additions & 0 deletions src/main/webapp/i18n/en/userSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@
"expiresOn": "Expires on",
"hasExpiredOn": "Expired on",
"fingerprint": "Fingerprint",
"fingerprints": "Show SSH fingerprints",
"sshFingerprints": "SSH Fingerprints",
"fingerprintsExplanation": "SSH keys allow you to establish a secure connection between your computer and Artemis. SSH fingerprints verify that you connect to the correct device.",
"fingerprintsLearnMore": "Learn more about fingerprints",
"commentUsedAsLabel": "If you do not add a label, the key comment will be used as the default label if present.",
"expiry": {
"title": "Expiry",
Expand Down
Loading

0 comments on commit ab8bada

Please sign in to comment.