Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrated code lifecycle: Show ssh fingerprints #9650

Merged
merged 32 commits into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
fcd2c0b
resolve merge issues
SimonEntholzer Dec 4, 2024
98aee32
changed to SHA:256 to use it in shell
SimonEntholzer Dec 5, 2024
b6b21dc
Merge branch 'develop' into feature/ssh/show-ssh-fingerprints
SimonEntholzer Dec 5, 2024
ede4677
fix javadoc
SimonEntholzer Dec 5, 2024
832f156
fix tests
SimonEntholzer Dec 5, 2024
1d501d7
remove console log
SimonEntholzer Dec 5, 2024
55be87d
Merge branch 'develop' into feature/ssh/show-ssh-fingerprints
SimonEntholzer Dec 6, 2024
bd6103e
added displaying EC fingerprint
SimonEntholzer Dec 6, 2024
e56878b
fix architecture test
SimonEntholzer Dec 6, 2024
b2f251e
remove test
SimonEntholzer Dec 6, 2024
d14bdac
re add test?
SimonEntholzer Dec 6, 2024
dcde0c8
adapt tests
SimonEntholzer Dec 6, 2024
2ec7f16
comment out resource test
SimonEntholzer Dec 6, 2024
f12661b
use different yml parameter
SimonEntholzer Dec 6, 2024
2c733c0
undo config change
SimonEntholzer Dec 6, 2024
2a7d7f5
fix tests
SimonEntholzer Dec 6, 2024
2cdbdf9
fix test and improve translation
SimonEntholzer Dec 6, 2024
5cfb6c6
use assertj fail instead of junit fail to fix architecture test
SimonEntholzer Dec 6, 2024
c25eeae
remove unused code
SimonEntholzer Dec 6, 2024
4645b92
add code rabbit suggestion
SimonEntholzer Dec 6, 2024
d7527ec
Merge branch 'develop' into feature/ssh/show-ssh-fingerprints
SimonEntholzer Dec 7, 2024
f26aa53
fix style
SimonEntholzer Dec 7, 2024
7a281e9
Merge branch 'develop' into feature/ssh/show-ssh-fingerprints
SimonEntholzer Dec 10, 2024
3abbcd2
Update src/main/webapp/i18n/de/userSettings.json
SimonEntholzer Dec 10, 2024
aedbd10
Update src/main/webapp/i18n/en/userSettings.json
SimonEntholzer Dec 10, 2024
5e8ec89
Merge branch 'develop' into feature/ssh/show-ssh-fingerprints
SimonEntholzer Dec 10, 2024
3d8ab5c
Merge branch 'develop' into feature/ssh/show-ssh-fingerprints
SimonEntholzer Dec 10, 2024
d12c7ec
Merge branch 'develop' into feature/ssh/show-ssh-fingerprints
SimonEntholzer Dec 11, 2024
76d2396
remove unnecessary feature toggle
SimonEntholzer Dec 13, 2024
550879a
Merge branch 'develop' into feature/ssh/show-ssh-fingerprints
SimonEntholzer Dec 13, 2024
bab0487
Merge branch 'develop' into feature/ssh/show-ssh-fingerprints
SimonEntholzer Dec 14, 2024
fe19210
Merge branch 'develop' into feature/ssh/show-ssh-fingerprints
SimonEntholzer Dec 16, 2024
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
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()
SimonEntholzer marked this conversation as resolved.
Show resolved Hide resolved
.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;
}
SimonEntholzer marked this conversation as resolved.
Show resolved Hide resolved
}
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;
}
SimonEntholzer marked this conversation as resolved.
Show resolved Hide resolved

/**
* 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',
SimonEntholzer marked this conversation as resolved.
Show resolved Hide resolved
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();
}
SimonEntholzer marked this conversation as resolved.
Show resolved Hide resolved
}
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;
SimonEntholzer marked this conversation as resolved.
Show resolved Hide resolved

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
Loading